shared: add read-only anonymous file abstraction

The ro_anonymous_file is an abstraction around anonymous read-only files.
os_ro_anonymous_file_create creates such a file with the contents passed
in data. Subsequent calls to os_ro_anonymous_file_get_fd return a fd
that's ready to be send over the socket.
When mapmode is RO_ANONYMOUS_FILE_MAPMODE_PRIVATE the fd is only
guaranteed to be mmap-able readonly with MAP_PRIVATE but does not
require duplicating the file for each resource when memfd_create is
available. RO_ANONYMOUS_FILE_MAPMODE_SHARED may be used when the client
must be able to map the file with MAP_SHARED but it also means that the
file has to be duplicated even when memfd_create is available.

See also:
weston commit 76829fc4ea
wayland commit 905c0a341ddf0a885811d19e2b79c65a3f1d210c

Signed-off-by: Sebastian Wick <sebastian@sebastianwick.net>
This commit is contained in:
Sebastian Wick 2019-07-23 19:15:59 +02:00
parent ccf24076dd
commit 178f17e25d
2 changed files with 196 additions and 0 deletions

View file

@ -33,6 +33,7 @@
#include <sys/epoll.h>
#include <string.h>
#include <stdlib.h>
#include <libweston/zalloc.h>
#ifdef HAVE_MEMFD_CREATE
#include <sys/mman.h>
@ -40,6 +41,8 @@
#include "os-compatibility.h"
#define READONLY_SEALS (F_SEAL_SHRINK | F_SEAL_GROW | F_SEAL_WRITE)
int
os_fd_set_cloexec(int fd)
{
@ -233,3 +236,172 @@ strchrnul(const char *s, int c)
return (char *)s;
}
#endif
struct ro_anonymous_file {
int fd;
size_t size;
};
/** Create a new anonymous read-only file of the given size and the given data
*
* \param size The size of \p data.
* \param data The data of the file with the size \p size.
* \return A new \c ro_anonymous_file, or NULL on failure.
*
* The intended use-case is for sending mid-sized data from the compositor
* to clients.
* If the function fails errno is set.
*/
struct ro_anonymous_file *
os_ro_anonymous_file_create(size_t size,
const char *data)
{
struct ro_anonymous_file *file;
void *map;
file = zalloc(sizeof *file);
if (!file) {
errno = ENOMEM;
return NULL;
}
file->size = size;
file->fd = os_create_anonymous_file(size);
if (file->fd == -1)
goto err_free;
map = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, file->fd, 0);
if (map == MAP_FAILED)
goto err_close;
memcpy(map, data, size);
munmap(map, size);
#ifdef HAVE_MEMFD_CREATE
/* try to put seals on the file to make it read-only so that we can
* return the fd later directly when support_shared is not set.
* os_ro_anonymous_file_get_fd can handle the fd even if it is not
* sealed read-only and will instead create a new anonymous file on
* each invocation.
*/
fcntl(file->fd, F_ADD_SEALS, READONLY_SEALS);
#endif
return file;
err_close:
close(file->fd);
err_free:
free(file);
return NULL;
}
/** Destroy an anonymous read-only file
*
* \param file The file to destroy.
*/
void
os_ro_anonymous_file_destroy(struct ro_anonymous_file *file)
{
close(file->fd);
free(file);
}
/** Get the size of an anonymous read-only file
*
* \param file The file to get the size of.
* \return The size of the file.
*/
size_t
os_ro_anonymous_file_size(struct ro_anonymous_file *file)
{
return file->size;
}
/** Returns a file descriptor for the given file, ready to be send to a client.
*
* \param file The file for which to get a file descriptor.
* \param mapmode Describes the ways in which the returned file descriptor can
* be used with mmap.
* \return A file descriptor for the given file that can be send to a client
* or -1 on failure.
*
* The returned file descriptor must not be shared between multiple clients.
* When \p mapmode is RO_ANONYMOUS_FILE_MAPMODE_PRIVATE the file descriptor is
* only guaranteed to be mmapable with \c MAP_PRIVATE, when \p mapmode is
* RO_ANONYMOUS_FILE_MAPMODE_SHARED the file descriptor can be mmaped with
* either MAP_PRIVATE or MAP_SHARED.
* When you're done with the fd you must call \c os_ro_anonymous_file_put_fd
* instead of calling \c close.
* If the function fails errno is set.
*/
int
os_ro_anonymous_file_get_fd(struct ro_anonymous_file *file,
enum ro_anonymous_file_mapmode mapmode)
{
void *src, *dst;
int seals, fd;
seals = fcntl(file->fd, F_GET_SEALS);
/* file was sealed for read-only and we don't have to support MAP_SHARED
* so we can simply pass the memfd fd
*/
if (seals != -1 && mapmode == RO_ANONYMOUS_FILE_MAPMODE_PRIVATE &&
(seals & READONLY_SEALS) == READONLY_SEALS)
return file->fd;
/* for all other cases we create a new anonymous file that can be mapped
* with MAP_SHARED and copy the contents to it and return that instead
*/
fd = os_create_anonymous_file(file->size);
if (fd == -1)
return fd;
src = mmap(NULL, file->size, PROT_READ, MAP_PRIVATE, file->fd, 0);
if (src == MAP_FAILED) {
close(fd);
return -1;
}
dst = mmap(NULL, file->size, PROT_WRITE, MAP_SHARED, fd, 0);
if (dst == MAP_FAILED) {
close(fd);
munmap(src, file->size);
return -1;
}
memcpy(dst, src, file->size);
munmap(src, file->size);
munmap(dst, file->size);
return fd;
}
/** Release a file descriptor returned by \c os_ro_anonymous_file_get_fd
*
* \param fd A file descriptor returned by \c os_ro_anonymous_file_get_fd.
* \return 0 on success, or -1 on failure.
*
* This function must be called for every file descriptor created with
* \c os_ro_anonymous_file_get_fd to not leake any resources.
* If the function fails errno is set.
*/
int
os_ro_anonymous_file_put_fd(int fd)
{
int seals = fcntl(fd, F_GET_SEALS);
if (seals == -1 && errno != EINVAL)
return -1;
/* If the fd cannot be sealed seals is -1 at this point
* or the file can be sealed but has not been sealed for writing.
* In both cases we created a new anonymous file that we have to
* close.
*/
if (seals == -1 || !(seals & F_SEAL_WRITE))
close(fd);
return 0;
}

View file

@ -47,4 +47,28 @@ char *
strchrnul(const char *s, int c);
#endif
struct ro_anonymous_file;
enum ro_anonymous_file_mapmode {
RO_ANONYMOUS_FILE_MAPMODE_PRIVATE,
RO_ANONYMOUS_FILE_MAPMODE_SHARED,
};
struct ro_anonymous_file *
os_ro_anonymous_file_create(size_t size,
const char *data);
void
os_ro_anonymous_file_destroy(struct ro_anonymous_file *file);
size_t
os_ro_anonymous_file_size(struct ro_anonymous_file *file);
int
os_ro_anonymous_file_get_fd(struct ro_anonymous_file *file,
enum ro_anonymous_file_mapmode mapmode);
int
os_ro_anonymous_file_put_fd(int fd);
#endif /* OS_COMPATIBILITY_H */