mirror of
https://github.com/brl/mutter.git
synced 2024-09-12 10:35:54 -04:00
Add read-only anonymous file abstraction MetaAnonymousFile
Add MetaAnonymousFile, an abstraction around anonymous read-only files. Files can be created by calling meta_anonymous_file_new(), passing the data of the file. Subsequent calls to meta_anonymous_file_open_fd() return a fd that's ready to be sent over the socket. When mapmode is META_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. META_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. Pretty much all of this code was written for weston by Sebastian Wick, see https://gitlab.freedesktop.org/wayland/weston/merge_requests/240. Co-authored-by: Sebastian Wick <sebastian@sebastianwick.net> https://gitlab.gnome.org/GNOME/mutter/merge_requests/1012
This commit is contained in:
parent
b7366b5b53
commit
551a57ed7f
@ -70,3 +70,12 @@
|
||||
|
||||
/* Whether Xwayland has -initfd option */
|
||||
#mesondefine HAVE_XWAYLAND_INITFD
|
||||
|
||||
/* Whether the mkostemp function exists */
|
||||
#mesondefine HAVE_MKOSTEMP
|
||||
|
||||
/* Whether the posix_fallocate function exists */
|
||||
#mesondefine HAVE_POSIX_FALLOCATE
|
||||
|
||||
/* Whether the memfd_create function exists */
|
||||
#mesondefine HAVE_MEMFD_CREATE
|
||||
|
14
meson.build
14
meson.build
@ -408,6 +408,20 @@ if have_wayland
|
||||
endif
|
||||
endif
|
||||
|
||||
optional_functions = [
|
||||
'mkostemp',
|
||||
'posix_fallocate',
|
||||
'memfd_create',
|
||||
]
|
||||
|
||||
foreach function : optional_functions
|
||||
if cc.has_function(function)
|
||||
cdata.set('HAVE_' + function.to_upper(), 1)
|
||||
else
|
||||
message('Optional function ' + function + ' missing')
|
||||
endif
|
||||
endforeach
|
||||
|
||||
xwayland_grab_default_access_rules = get_option('xwayland_grab_default_access_rules')
|
||||
cdata.set_quoted('XWAYLAND_GRAB_DEFAULT_ACCESS_RULES',
|
||||
xwayland_grab_default_access_rules)
|
||||
|
368
src/core/meta-anonymous-file.c
Normal file
368
src/core/meta-anonymous-file.c
Normal file
@ -0,0 +1,368 @@
|
||||
/*
|
||||
* Copyright (C) 2020 Sebastian Wick
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License as
|
||||
* published by the Free Software Foundation; either version 2 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
|
||||
* 02111-1307, USA.
|
||||
*
|
||||
* Author: Sebastian Wick <sebastian@sebastianwick.net>
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
#include <sys/mman.h>
|
||||
|
||||
#include "core/meta-anonymous-file.h"
|
||||
|
||||
struct _MetaAnonymousFile
|
||||
{
|
||||
int fd;
|
||||
size_t size;
|
||||
};
|
||||
|
||||
#define READONLY_SEALS (F_SEAL_SHRINK | F_SEAL_GROW | F_SEAL_WRITE)
|
||||
|
||||
static int
|
||||
create_tmpfile_cloexec (char *tmpname)
|
||||
{
|
||||
int fd;
|
||||
|
||||
#if defined(HAVE_MKOSTEMP)
|
||||
fd = mkostemp (tmpname, O_CLOEXEC);
|
||||
if (fd >= 0)
|
||||
unlink (tmpname);
|
||||
#else
|
||||
fd = mkstemp (tmpname);
|
||||
if (fd >= 0)
|
||||
{
|
||||
long flags;
|
||||
|
||||
unlink (tmpname);
|
||||
|
||||
flags = fcntl (fd, F_GETFD);
|
||||
if (flags == -1 ||
|
||||
fcntl (fd, F_SETFD, flags | FD_CLOEXEC) == -1)
|
||||
{
|
||||
close (fd);
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
return fd;
|
||||
}
|
||||
|
||||
/*
|
||||
* Create a new, unique, anonymous file of the given size, and
|
||||
* return the file descriptor for it. The file descriptor is set
|
||||
* CLOEXEC. The file is immediately suitable for mmap()'ing
|
||||
* the given size at offset zero.
|
||||
*
|
||||
* The file should not have a permanent backing store like a disk,
|
||||
* but may have if XDG_RUNTIME_DIR is not properly implemented in OS.
|
||||
*
|
||||
* The file name is deleted from the file system.
|
||||
*
|
||||
* The file is suitable for buffer sharing between processes by
|
||||
* transmitting the file descriptor over Unix sockets using the
|
||||
* SCM_RIGHTS methods.
|
||||
*
|
||||
* If the C library implements posix_fallocate(), it is used to
|
||||
* guarantee that disk space is available for the file at the
|
||||
* given size. If disk space is insufficient, errno is set to ENOSPC.
|
||||
* If posix_fallocate() is not supported, program may receive
|
||||
* SIGBUS on accessing mmap()'ed file contents instead.
|
||||
*
|
||||
* If the C library implements memfd_create(), it is used to create the
|
||||
* file purely in memory, without any backing file name on the file
|
||||
* system, and then sealing off the possibility of shrinking it. This
|
||||
* can then be checked before accessing mmap()'ed file contents, to make
|
||||
* sure SIGBUS can't happen. It also avoids requiring XDG_RUNTIME_DIR.
|
||||
*/
|
||||
static int
|
||||
create_anonymous_file (off_t size)
|
||||
{
|
||||
int fd, ret;
|
||||
|
||||
#if defined(HAVE_MEMFD_CREATE)
|
||||
fd = memfd_create ("mutter-shared", MFD_CLOEXEC | MFD_ALLOW_SEALING);
|
||||
if (fd >= 0)
|
||||
{
|
||||
/* We can add this seal before calling posix_fallocate(), as
|
||||
* the file is currently zero-sized anyway.
|
||||
*
|
||||
* There is also no need to check for the return value, we
|
||||
* couldn't do anything with it anyway.
|
||||
*/
|
||||
fcntl (fd, F_ADD_SEALS, F_SEAL_SHRINK);
|
||||
}
|
||||
else
|
||||
#endif
|
||||
{
|
||||
static const char template[] = "/mutter-shared-XXXXXX";
|
||||
const char *path;
|
||||
char *name;
|
||||
|
||||
path = getenv ("XDG_RUNTIME_DIR");
|
||||
if (!path)
|
||||
{
|
||||
errno = ENOENT;
|
||||
return -1;
|
||||
}
|
||||
|
||||
name = g_malloc (strlen (path) + sizeof (template));
|
||||
if (!name)
|
||||
return -1;
|
||||
|
||||
strcpy (name, path);
|
||||
strcat (name, template);
|
||||
|
||||
fd = create_tmpfile_cloexec (name);
|
||||
|
||||
g_free (name);
|
||||
|
||||
if (fd < 0)
|
||||
return -1;
|
||||
}
|
||||
|
||||
#if defined(HAVE_POSIX_FALLOCATE)
|
||||
do
|
||||
{
|
||||
ret = posix_fallocate (fd, 0, size);
|
||||
}
|
||||
while (ret == EINTR);
|
||||
|
||||
if (ret != 0)
|
||||
{
|
||||
close (fd);
|
||||
errno = ret;
|
||||
return -1;
|
||||
}
|
||||
#else
|
||||
do
|
||||
{
|
||||
ret = ftruncate (fd, size);
|
||||
}
|
||||
while (ret < 0 && errno == EINTR);
|
||||
|
||||
if (ret < 0)
|
||||
{
|
||||
close (fd);
|
||||
return -1;
|
||||
}
|
||||
#endif
|
||||
|
||||
return fd;
|
||||
}
|
||||
|
||||
/**
|
||||
* meta_anonymous_file_new: (skip)
|
||||
* @size: The size of @data
|
||||
* @data: The data of the file with the size @size
|
||||
*
|
||||
* Create a new anonymous read-only file of the given size and the given data
|
||||
* The intended use-case is for sending mid-sized data from the compositor
|
||||
* to clients.
|
||||
*
|
||||
* When done, free the data using meta_anonymous_file_free().
|
||||
*
|
||||
* If this function fails errno is set.
|
||||
*
|
||||
* Returns: The newly created #MetaAnonymousFile, or NULL on failure. Use
|
||||
* meta_anonymous_file_free() to free the resources when done.
|
||||
*/
|
||||
MetaAnonymousFile *
|
||||
meta_anonymous_file_new (size_t size,
|
||||
const uint8_t *data)
|
||||
{
|
||||
MetaAnonymousFile *file;
|
||||
void *map;
|
||||
|
||||
file = g_malloc0 (sizeof *file);
|
||||
if (!file)
|
||||
{
|
||||
errno = ENOMEM;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
file->size = size;
|
||||
file->fd = 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);
|
||||
|
||||
#if defined(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 MAPMODE_SHARED is not set.
|
||||
* meta_anonymous_file_open_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:
|
||||
g_free (file);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* meta_anonymous_file_free: (skip)
|
||||
* @file: the #MetaAnonymousFile
|
||||
*
|
||||
* Free the resources used by an anonymous read-only file.
|
||||
*/
|
||||
void
|
||||
meta_anonymous_file_free (MetaAnonymousFile *file)
|
||||
{
|
||||
close (file->fd);
|
||||
g_free (file);
|
||||
}
|
||||
|
||||
/**
|
||||
* meta_anonymous_file_size: (skip)
|
||||
* @file: the #MetaAnonymousFile
|
||||
*
|
||||
* Get the size of an anonymous read-only file.
|
||||
*
|
||||
* Returns: The size of the anonymous read-only file.
|
||||
*/
|
||||
size_t
|
||||
meta_anonymous_file_size (MetaAnonymousFile *file)
|
||||
{
|
||||
return file->size;
|
||||
}
|
||||
|
||||
/**
|
||||
* meta_anonymous_file_open_fd: (skip)
|
||||
* @file: the #MetaAnonymousFile to get a file descriptor for
|
||||
* @mapmode: describes the ways in which the returned file descriptor can
|
||||
* be used with mmap
|
||||
*
|
||||
* Returns a file descriptor for the given file, ready to be sent to a client.
|
||||
* The returned file descriptor must not be shared between multiple clients.
|
||||
* If @mapmode is %META_ANONYMOUS_FILE_MAPMODE_PRIVATE the file descriptor is
|
||||
* only guaranteed to be mmapable with MAP_PRIVATE. If @mapmode is
|
||||
* %META_ANONYMOUS_FILE_MAPMODE_SHARED the file descriptor can be mmaped with
|
||||
* either MAP_PRIVATE or MAP_SHARED.
|
||||
*
|
||||
* In case %META_ANONYMOUS_FILE_MAPMODE_PRIVATE is used, it is important to
|
||||
* only read the returned fd using mmap() since using read() will move the
|
||||
* read cursor of the fd and thus may cause read() calls on other returned
|
||||
* fds to fail.
|
||||
*
|
||||
* When done using the fd, it is required to call meta_anonymous_file_close_fd()
|
||||
* instead of close().
|
||||
*
|
||||
* If this function fails errno is set.
|
||||
*
|
||||
* Returns: A file descriptor for the given file that can be sent to a client
|
||||
* or -1 on failure. Use meta_anonymous_file_close_fd() to release the fd
|
||||
* when done.
|
||||
*/
|
||||
int
|
||||
meta_anonymous_file_open_fd (MetaAnonymousFile *file,
|
||||
MetaAnonymousFileMapmode mapmode)
|
||||
{
|
||||
void *src, *dst;
|
||||
int fd;
|
||||
|
||||
#if defined(HAVE_MEMFD_CREATE)
|
||||
int seals;
|
||||
|
||||
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 == META_ANONYMOUS_FILE_MAPMODE_PRIVATE &&
|
||||
(seals & READONLY_SEALS) == READONLY_SEALS)
|
||||
return file->fd;
|
||||
#endif
|
||||
|
||||
/* 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 = 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;
|
||||
}
|
||||
|
||||
/**
|
||||
* meta_anonymous_file_close_fd: (skip)
|
||||
* @fd: A file descriptor obtained using meta_anonymous_file_open_fd()
|
||||
*
|
||||
* Release a file descriptor returned by meta_anonymous_file_open_fd().
|
||||
* This function must be called for every file descriptor created with
|
||||
* meta_anonymous_file_open_fd() to not leak any resources.
|
||||
*
|
||||
* If this function fails errno is set.
|
||||
*/
|
||||
void
|
||||
meta_anonymous_file_close_fd (int fd)
|
||||
{
|
||||
#if defined(HAVE_MEMFD_CREATE)
|
||||
int seals;
|
||||
|
||||
seals = fcntl (fd, F_GET_SEALS);
|
||||
if (seals == -1 && errno != EINVAL)
|
||||
{
|
||||
g_warning ("Reading seals of anonymous file %d failed", fd);
|
||||
return;
|
||||
}
|
||||
|
||||
/* The only case in which we do NOT have to close the file is when the file
|
||||
* was sealed for read-only
|
||||
*/
|
||||
if (seals != -1 && (seals & READONLY_SEALS) == READONLY_SEALS)
|
||||
return;
|
||||
#endif
|
||||
|
||||
close (fd);
|
||||
}
|
53
src/core/meta-anonymous-file.h
Normal file
53
src/core/meta-anonymous-file.h
Normal file
@ -0,0 +1,53 @@
|
||||
/*
|
||||
* Copyright (C) 2020 Sebastian Wick
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License as
|
||||
* published by the Free Software Foundation; either version 2 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
|
||||
* 02111-1307, USA.
|
||||
*
|
||||
* Author: Sebastian Wick <sebastian@sebastianwick.net>
|
||||
*/
|
||||
|
||||
#ifndef META_ANONYMOUS_FILE_H
|
||||
#define META_ANONYMOUS_FILE_H
|
||||
|
||||
#include "meta/common.h"
|
||||
#include "core/util-private.h"
|
||||
|
||||
typedef struct _MetaAnonymousFile MetaAnonymousFile;
|
||||
|
||||
typedef enum _MetaAnonymousFileMapmode
|
||||
{
|
||||
META_ANONYMOUS_FILE_MAPMODE_PRIVATE,
|
||||
META_ANONYMOUS_FILE_MAPMODE_SHARED,
|
||||
} MetaAnonymousFileMapmode;
|
||||
|
||||
META_EXPORT_TEST
|
||||
MetaAnonymousFile * meta_anonymous_file_new (size_t size,
|
||||
const uint8_t *data);
|
||||
|
||||
META_EXPORT_TEST
|
||||
void meta_anonymous_file_free (MetaAnonymousFile *file);
|
||||
|
||||
META_EXPORT_TEST
|
||||
size_t meta_anonymous_file_size (MetaAnonymousFile *file);
|
||||
|
||||
META_EXPORT_TEST
|
||||
int meta_anonymous_file_open_fd (MetaAnonymousFile *file,
|
||||
MetaAnonymousFileMapmode mapmode);
|
||||
|
||||
META_EXPORT_TEST
|
||||
void meta_anonymous_file_close_fd (int fd);
|
||||
|
||||
#endif /* META_ANONYMOUS_FILE_H */
|
@ -349,6 +349,8 @@ mutter_sources = [
|
||||
'core/main-private.h',
|
||||
'core/meta-accel-parse.c',
|
||||
'core/meta-accel-parse.h',
|
||||
'core/meta-anonymous-file.c',
|
||||
'core/meta-anonymous-file.h',
|
||||
'core/meta-border.c',
|
||||
'core/meta-border.h',
|
||||
'core/meta-clipboard-manager.c',
|
||||
|
Loading…
Reference in New Issue
Block a user