[mutter] Add read-only anonymous file abstraction MetaAnonymousFile



commit 551a57ed7f2b132a8dca0f1889a24196debb43cc
Author: Jonas Dreßler <verdre v0yd nl>
Date:   Fri Jan 17 23:43:24 2020 +0100

    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

 config.h.meson                 |   9 +
 meson.build                    |  14 ++
 src/core/meta-anonymous-file.c | 368 +++++++++++++++++++++++++++++++++++++++++
 src/core/meta-anonymous-file.h |  53 ++++++
 src/meson.build                |   2 +
 5 files changed, 446 insertions(+)
---
diff --git a/config.h.meson b/config.h.meson
index 0edff4d57..f5c0e71a6 100644
--- a/config.h.meson
+++ b/config.h.meson
@@ -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
diff --git a/meson.build b/meson.build
index ed83ef540..f08dcfecf 100644
--- a/meson.build
+++ b/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)
diff --git a/src/core/meta-anonymous-file.c b/src/core/meta-anonymous-file.c
new file mode 100644
index 000000000..95b63c9f0
--- /dev/null
+++ b/src/core/meta-anonymous-file.c
@@ -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);
+}
diff --git a/src/core/meta-anonymous-file.h b/src/core/meta-anonymous-file.h
new file mode 100644
index 000000000..5289c7193
--- /dev/null
+++ b/src/core/meta-anonymous-file.h
@@ -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 */
diff --git a/src/meson.build b/src/meson.build
index cee1e8565..ea337214c 100644
--- a/src/meson.build
+++ b/src/meson.build
@@ -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',


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]