[ostree] Import rofiles-fuse



commit e9ccdd2d007801ef25cc7283188942d791889c27
Author: Colin Walters <walters verbum org>
Date:   Wed Feb 10 12:42:54 2016 +0100

    Import rofiles-fuse
    
    While it's not strictly tied to OSTree, let's move
    https://github.com/cgwalters/rofiles-fuse in here because:
    
     - It's *very* useful in concert with OSTree
     - It's tiny
     - We can reuse OSTree's test, documentation, etc. infrastructure
    
    One thing to consider also is that at some point we could experiment
    with writing a FUSE filesystem for OSTree.  This could internalize a
    better equivalent of `--link-checkout-speedup`, but on the other hand,
    the cost of walking filesystem trees for these types of operations is
    really quite small.
    
    But if we did decide to do more FUSE things in OSTree, this is a step
    towards that too.

 Makefile-man.am                  |    4 +
 Makefile-tests.am                |    5 +
 Makefile.am                      |    1 +
 configure.ac                     |   13 +
 man/rofiles-fuse.xml             |  104 +++++++
 src/rofiles-fuse/Makefile-inc.am |   23 ++
 src/rofiles-fuse/README.md       |   48 +++
 src/rofiles-fuse/main.c          |  598 ++++++++++++++++++++++++++++++++++++++
 tests/test-rofiles-fuse.sh       |   74 +++++
 9 files changed, 870 insertions(+), 0 deletions(-)
---
diff --git a/Makefile-man.am b/Makefile-man.am
index f70a577..a6090bf 100644
--- a/Makefile-man.am
+++ b/Makefile-man.am
@@ -21,6 +21,10 @@ if ENABLE_MAN
 
 man1_files = ostree.1 ostree-admin-cleanup.1 ostree-admin-config-diff.1 ostree-admin-deploy.1 
ostree-admin-init-fs.1 ostree-admin-instutil.1 ostree-admin-os-init.1 ostree-admin-status.1 
ostree-admin-set-origin.1 ostree-admin-switch.1 ostree-admin-undeploy.1 ostree-admin-upgrade.1 ostree-admin.1 
ostree-cat.1 ostree-checkout.1 ostree-checksum.1 ostree-commit.1 ostree-gpg-sign.1 ostree-config.1 
ostree-diff.1 ostree-fsck.1 ostree-init.1 ostree-log.1 ostree-ls.1 ostree-prune.1 ostree-pull-local.1 
ostree-pull.1 ostree-refs.1 ostree-remote.1 ostree-reset.1 ostree-rev-parse.1 ostree-show.1 ostree-summary.1 
ostree-static-delta.1 ostree-trivial-httpd.1
 
+if BUILDOPT_FUSE
+man1_files += rofiles-fuse.1
+endif
+
 man5_files = ostree.repo.5 ostree.repo-config.5
 
 man1_MANS = $(addprefix man/,$(man1_files))
diff --git a/Makefile-tests.am b/Makefile-tests.am
index 9f3feef..b046684 100644
--- a/Makefile-tests.am
+++ b/Makefile-tests.am
@@ -61,6 +61,11 @@ testfiles = test-basic \
        test-auto-summary \
        test-prune \
        $(NULL)
+
+if BUILDOPT_FUSE
+testfiles += test-rofiles-fuse
+endif
+
 insttest_SCRIPTS = $(addprefix tests/,$(testfiles:=.sh))
 
 # This one uses corrupt-repo-ref.js
diff --git a/Makefile.am b/Makefile.am
index da904da..e1c942d 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -68,6 +68,7 @@ include Makefile-otutil.am
 include Makefile-libostree.am
 include Makefile-ostree.am
 include Makefile-switchroot.am
+include src/rofiles-fuse/Makefile-inc.am
 include Makefile-tests.am
 include Makefile-boot.am
 include Makefile-man.am
diff --git a/configure.ac b/configure.ac
index 03cc746..6a34c0d 100644
--- a/configure.ac
+++ b/configure.ac
@@ -117,6 +117,8 @@ AS_IF([ test x$have_gpgme = xno ], [
 OSTREE_FEATURES="$OSTREE_FEATURES +gpgme"
 
 LIBARCHIVE_DEPENDENCY="libarchive >= 2.8.0"
+# What's in RHEL7.2.
+FUSE_DEPENDENCY="fuse >= 2.9.2"
 
 # check for gtk-doc
 m4_ifdef([GTK_DOC_CHECK], [
@@ -194,6 +196,16 @@ AS_IF([ test x$with_selinux != xno ], [
 if test x$with_selinux != xno; then OSTREE_FEATURES="$OSTREE_FEATURES +selinux"; fi
 AM_CONDITIONAL(USE_SELINUX, test $with_selinux != no)
 
+# Enabled by default because I think people should use it.
+AC_ARG_ENABLE(rofiles-fuse,
+              [AS_HELP_STRING([--enable-rofiles-fuse],
+                              [generate rofiles-fuse helper [default=yes]])],,
+              enable_rofiles_fuse=yes)
+AS_IF([ test $enable_rofiles_fuse != xno ], [
+    PKG_CHECK_MODULES(BUILDOPT_FUSE, $FUSE_DEPENDENCY)
+], [enable_rofiles_fuse=no])
+AM_CONDITIONAL(BUILDOPT_FUSE, test x$enable_rofiles_fuse = xyes)
+
 AC_ARG_WITH(dracut,
             AS_HELP_STRING([--with-dracut],
                            [Install dracut module (default: no)]),,
@@ -248,6 +260,7 @@ echo "
 
 
     introspection:                                $found_introspection
+    rofiles-fuse:                                 $enable_rofiles_fuse
     libsoup (retrieve remote HTTP repositories):  $with_soup
     libsoup TLS client certs:                     $have_libsoup_client_certs
     SELinux:                                      $with_selinux
diff --git a/man/rofiles-fuse.xml b/man/rofiles-fuse.xml
new file mode 100644
index 0000000..3d282c0
--- /dev/null
+++ b/man/rofiles-fuse.xml
@@ -0,0 +1,104 @@
+<?xml version='1.0'?> <!--*-nxml-*-->
+<!DOCTYPE refentry PUBLIC "-//OASIS//DTD DocBook XML V4.2//EN"
+    "http://www.oasis-open.org/docbook/xml/4.2/docbookx.dtd";>
+
+<!--
+Copyright 2016 Colin Walters <walters verbum org>
+
+This library is free software; you can redistribute it and/or
+modify it under the terms of the GNU Lesser General Public
+License as published by the Free Software Foundation; either
+version 2 of the License, or (at your option) any later version.
+
+This library 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
+Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public
+License along with this library; if not, write to the
+Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+Boston, MA 02111-1307, USA.
+-->
+
+<refentry id="ostree">
+
+    <refentryinfo>
+        <title>rofiles-fuse</title>
+        <productname>rofiles-fuse</productname>
+
+        <authorgroup>
+            <author>
+                <contrib>Developer</contrib>
+                <firstname>Colin</firstname>
+                <surname>Walters</surname>
+                <email>walters verbum org</email>
+            </author>
+        </authorgroup>
+    </refentryinfo>
+
+    <refmeta>
+        <refentrytitle>rofiles-fuse</refentrytitle>
+        <manvolnum>1</manvolnum>
+    </refmeta>
+
+    <refnamediv>
+        <refname>rofiles-fuse</refname>
+        <refpurpose>Use FUSE to create a view where directories are writable, files are 
immutable</refpurpose>
+    </refnamediv>
+
+    <refsynopsisdiv>
+      <cmdsynopsis>
+        <command>rofiles-fuse SRCDIR MNTPOINT</command>
+      </cmdsynopsis>
+    </refsynopsisdiv>
+
+    <refsect1>
+        <title>Description</title>
+
+        <para>
+         Creating a checkout from an OSTree repository by default
+         uses hard links, which means an in-place mutation to any
+         file corrupts the repository and all checkouts.  This can be
+         problematic if one wishes to run arbitrary programs against
+         such a checkout.  For example, RPM <literal>%post</literal>
+         scripts or equivalent.
+       </para>
+
+       <para>
+         In the case where one wants to create a tree commit derived
+         from other content, using <command>rofiles-fuse</command> in
+         concert with <command>ostree commit
+         --link-checkout-speedup</command> (or the underlying API)
+         can ensure that only new files are checksummed.
+       </para>
+         
+    </refsect1>
+
+    <refsect1>
+        <title>Example: Update an OSTree commit</title>
+       <programlisting>
+# Initialize a checkout and mount
+$ ostree --repo=repo checkout somebranch branch-checkout
+$ mkdir mnt
+$ rofiles-fuse branch-checkout mnt
+
+# Now, arbitrary changes to mnt/ are reflected in branch-checkout
+$ echo somenewcontent > mnt/anewfile
+$ mkdir mnt/anewdir
+$ rm mnt/someoriginalcontent -rf
+
+# Commit and cleanup
+$ fusermount -u mnt
+$ ostree --repo=repo commit --link-checkout-speedup -b somebranch -s 'Commit new content' 
--tree=dir=branch-checkout
+$ rm mnt branch-checkout -rf
+       </programlisting>
+    </refsect1>
+
+    <refsect1>
+        <title>See Also</title>
+        <para>
+            <citerefentry><refentrytitle>ostree</refentrytitle><manvolnum>1</manvolnum></citerefentry>
+        </para>
+    </refsect1>
+</refentry>
diff --git a/src/rofiles-fuse/Makefile-inc.am b/src/rofiles-fuse/Makefile-inc.am
new file mode 100644
index 0000000..5510a2b
--- /dev/null
+++ b/src/rofiles-fuse/Makefile-inc.am
@@ -0,0 +1,23 @@
+# Copyright (C) 2016 Colin Walters <walters verbum org>
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2 of the License, or (at your option) any later version.
+#
+# This library 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
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this library; if not, write to the
+# Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+# Boston, MA 02111-1307, USA.
+
+bin_PROGRAMS += rofiles-fuse
+
+rofiles_fuse_SOURCES = src/rofiles-fuse/main.c
+
+rofiles_fuse_CFLAGS = $(AM_CFLAGS) -D_GNU_SOURCE -D_FILE_OFFSET_BITS=64 $(BUILDOPT_FUSE_CFLAGS) 
$(OT_INTERNAL_GIO_UNIX_CFLAGS) -I$(srcdir)/libglnx $(NULL)
+rofiles_fuse_LDADD = libglnx.la $(BUILDOPT_FUSE_LIBS) $(OT_INTERNAL_GIO_UNIX_LIBS)
diff --git a/src/rofiles-fuse/README.md b/src/rofiles-fuse/README.md
new file mode 100644
index 0000000..1f18afc
--- /dev/null
+++ b/src/rofiles-fuse/README.md
@@ -0,0 +1,48 @@
+rofiles-fuse
+============
+
+Create a mountpoint that represents an underlying directory hierarchy,
+but where non-directory inodes cannot have content or xattrs changed.
+Files can still be unlinked, and new ones created.
+
+This filesystem is designed for OSTree and other systems that create
+"hardlink farms", i.e. filesystem trees deduplicated via hardlinks.
+
+Normally with hard links, if you change one, you change them all.
+
+There are two approaches to dealing with that:
+ - Copy on write: implemented by BTRFS, overlayfs, and http://linux-vserver.org/util-vserver:Vhashify
+ - Make them read-only: what this FUSE mount does
+
+Usage
+=====
+
+Let's say that you have immutable data in `/srv/backups/20150410`, and
+you want to update it with a new version, storing the result in
+`/srv/backups/20150411`.  Further assume that all software operating
+on the directory does the "create tempfile and `rename()`" dance
+rather than in-place edits.
+
+    $ mkdir -p /srv/backups/mnt   # Ensure we have a mount point
+    $ cp -al /srv/backups/20150410 /srv/backups/20150411
+    $ rofiles-fuse /srv/backups/20150411 /srv/backups/mnt
+
+Now we have a "rofiles" mount at `/srv/backups/mnt`.  If we try this:
+
+    $ echo new doc content > /srv/backups/mnt/document
+    bash: /srv/backups/mnt/document: Read-only file system
+
+It failed because the `>` redirection operator will try to truncate
+the existing file.  If instead we create `document.tmp` and then
+rename it atomically over the old one, it will work:
+
+    $ echo new doc content > /srv/backups/mnt/document.tmp
+    $ mv /srv/backups/mnt/document.tmp /srv/backups/mnt/document
+
+Let's unmount:
+
+    $ fusermount -u /srv/backups/mnt
+
+Now we have two directories `/srv/backups/20150410`
+`/srv/backups/20150411` which share all file storage except for the
+new document.
diff --git a/src/rofiles-fuse/main.c b/src/rofiles-fuse/main.c
new file mode 100644
index 0000000..004ad3d
--- /dev/null
+++ b/src/rofiles-fuse/main.c
@@ -0,0 +1,598 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*-
+ *
+ * Copyright (C) 2015,2016 Colin Walters <walters verbum org>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#define FUSE_USE_VERSION 26
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/statvfs.h>
+#include <stdio.h>
+#include <strings.h>
+#include <stdlib.h>
+#include <string.h>
+#include <assert.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <sys/xattr.h>
+#include <dirent.h>
+#include <unistd.h>
+#include <fuse.h>
+
+#include <glib.h>
+
+#include "libglnx.h"
+
+// Global to store our read-write path
+static int basefd = -1;
+static GHashTable *created_devino_hash = NULL;
+
+static inline const char *
+ENSURE_RELPATH (const char *path)
+{
+  return path + strspn (path, "/");
+}
+
+typedef struct {
+  dev_t dev;
+  ino_t ino;
+} DevIno;
+
+static guint
+devino_hash (gconstpointer a)
+{
+  DevIno *a_i = (gpointer)a;
+  return (guint) (a_i->dev + a_i->ino);
+}
+
+static int
+devino_equal (gconstpointer   a,
+              gconstpointer   b)
+{
+  DevIno *a_i = (gpointer)a;
+  DevIno *b_i = (gpointer)b;
+  return a_i->dev == b_i->dev
+    && a_i->ino == b_i->ino;
+}
+
+static gboolean
+devino_set_contains (dev_t dev, ino_t ino)
+{
+  DevIno devino = { dev, ino };
+  return g_hash_table_contains (created_devino_hash, &devino);
+}
+
+static gboolean
+devino_set_insert (dev_t dev, ino_t ino)
+{
+  DevIno *devino = g_new (DevIno, 1);
+  devino->dev = dev;
+  devino->ino = ino;
+  return g_hash_table_add (created_devino_hash, devino);
+}
+
+static gboolean
+devino_set_remove (dev_t dev, ino_t ino)
+{
+  DevIno devino = { dev, ino };
+  return g_hash_table_remove (created_devino_hash, &devino);
+}
+
+static int
+callback_getattr (const char *path, struct stat *st_data)
+{
+  path = ENSURE_RELPATH (path);
+  if (!*path)
+    {
+      if (fstat (basefd, st_data) == -1)
+       return -errno;
+    }
+  else
+    {
+      if (fstatat (basefd, path, st_data, AT_SYMLINK_NOFOLLOW) == -1)
+       return -errno;
+    }
+  return 0;
+}
+
+static int
+callback_readlink (const char *path, char *buf, size_t size)
+{
+  int r;
+
+  path = ENSURE_RELPATH (path);
+
+  /* Note FUSE wants the string to be always nul-terminated, even if
+   * truncated.
+   */
+  r = readlinkat (basefd, path, buf, size - 1);
+  if (r == -1)
+    return -errno;
+  buf[r] = '\0';
+  return 0;
+}
+
+static int
+callback_readdir (const char *path, void *buf, fuse_fill_dir_t filler,
+                 off_t offset, struct fuse_file_info *fi)
+{
+  DIR *dp;
+  struct dirent *de;
+  int dfd;
+
+  path = ENSURE_RELPATH (path);
+
+  if (!*path)
+    {
+      dfd = fcntl (basefd, F_DUPFD_CLOEXEC, 3);
+      lseek (dfd, 0, SEEK_SET);
+    }
+  else
+    {
+      dfd = openat (basefd, path, O_RDONLY | O_NONBLOCK | O_DIRECTORY | O_CLOEXEC | O_NOCTTY);
+      if (dfd == -1)
+       return -errno;
+    }
+
+  /* Transfers ownership of fd */
+  dp = fdopendir (dfd);
+  if (dp == NULL)
+    return -errno;
+
+  while ((de = readdir (dp)) != NULL)
+    {
+      struct stat st;
+      memset (&st, 0, sizeof (st));
+      st.st_ino = de->d_ino;
+      st.st_mode = de->d_type << 12;
+      if (filler (buf, de->d_name, &st, 0))
+       break;
+    }
+
+  (void) closedir (dp);
+  return 0;
+}
+
+static int
+callback_mknod (const char *path, mode_t mode, dev_t rdev)
+{
+  return -EROFS;
+}
+
+static int
+callback_mkdir (const char *path, mode_t mode)
+{
+  path = ENSURE_RELPATH (path);
+  if (mkdirat (basefd, path, mode) == -1)
+    return -errno;
+  return 0;
+}
+
+static int
+callback_unlink (const char *path)
+{
+  struct stat stbuf;
+  path = ENSURE_RELPATH (path);
+
+  if (fstatat (basefd, path, &stbuf, AT_SYMLINK_NOFOLLOW) == 0)
+    {
+      if (!S_ISDIR (stbuf.st_mode))
+       devino_set_remove (stbuf.st_dev, stbuf.st_ino);
+    }
+
+  if (unlinkat (basefd, path, 0) == -1)
+    return -errno;
+  return 0;
+}
+
+static int
+callback_rmdir (const char *path)
+{
+  path = ENSURE_RELPATH (path);
+  if (unlinkat (basefd, path, AT_REMOVEDIR) == -1)
+    return -errno;
+  return 0;
+}
+
+static int
+callback_symlink (const char *from, const char *to)
+{
+  struct stat stbuf;
+
+  to = ENSURE_RELPATH (to);
+
+  if (symlinkat (from, basefd, to) == -1)
+    return -errno;
+
+  if (fstatat (basefd, to, &stbuf, AT_SYMLINK_NOFOLLOW) == -1)
+    {
+      fprintf (stderr, "Failed to find newly created symlink '%s': %s\n",
+              to, g_strerror (errno));
+      exit (1);
+    }
+  return 0;
+}
+
+static int
+callback_rename (const char *from, const char *to)
+{
+  from = ENSURE_RELPATH (from);
+  to = ENSURE_RELPATH (to);
+  if (renameat (basefd, from, basefd, to) == -1)
+    return -errno;
+  return 0;
+}
+
+static int
+callback_link (const char *from, const char *to)
+{
+  from = ENSURE_RELPATH (from);
+  to = ENSURE_RELPATH (to);
+  if (linkat (basefd, from, basefd, to, 0) == -1)
+    return -errno;
+  return 0;
+}
+
+static int
+can_write (const char *path)
+{
+  struct stat stbuf;
+  if (fstatat (basefd, path, &stbuf, 0) == -1)
+    {
+      if (errno == ENOENT)
+       return 0;
+      else
+       return -errno;
+    }
+  if (devino_set_contains (stbuf.st_dev, stbuf.st_ino))
+    return -EROFS;
+  return 0;
+}
+
+#define VERIFY_WRITE(path) do { \
+  int r = can_write (path); \
+  if (r != 0) \
+    return r; \
+  } while (0)
+
+static int
+callback_chmod (const char *path, mode_t mode)
+{
+  path = ENSURE_RELPATH (path);
+  VERIFY_WRITE(path);
+  if (fchmodat (basefd, path, mode, 0) != 0)
+    return -errno;
+  return 0;
+}
+
+static int
+callback_chown (const char *path, uid_t uid, gid_t gid)
+{
+  path = ENSURE_RELPATH (path);
+  VERIFY_WRITE(path);
+  if (fchownat (basefd, path, uid, gid, 0) != 0)
+    return -errno;
+  return 0;
+}
+
+static int
+callback_truncate (const char *path, off_t size)
+{
+  glnx_fd_close int fd = -1;
+
+  path = ENSURE_RELPATH (path);
+  VERIFY_WRITE(path);
+
+  fd = openat (basefd, path, O_RDWR | O_CREAT);
+  if (fd == -1)
+    return -errno;
+
+  if (ftruncate (fd, size) == -1)
+    return -errno;
+
+  return 0;
+}
+
+static int
+callback_utime (const char *path, struct utimbuf *buf)
+{
+  struct timespec ts[2];
+
+  path = ENSURE_RELPATH (path);
+
+  ts[0].tv_sec = buf->actime;
+  ts[0].tv_nsec = UTIME_OMIT;
+  ts[1].tv_sec = buf->modtime;
+  ts[1].tv_nsec = UTIME_OMIT;
+
+  if (utimensat (basefd, path, ts, AT_SYMLINK_NOFOLLOW) == -1)
+    return -errno;
+
+  return 0;
+}
+
+static int
+do_open (const char *path, mode_t mode, struct fuse_file_info *finfo)
+{
+  const int flags = finfo->flags & O_ACCMODE;
+  int fd;
+  struct stat stbuf;
+
+  /* Support read only opens */
+  G_STATIC_ASSERT (O_RDONLY == 0);
+
+  path = ENSURE_RELPATH (path);
+
+  if (flags == 0)
+    fd = openat (basefd, path, flags);
+  else
+    {
+      const int forced_excl_flags = flags | O_CREAT | O_EXCL;
+      /* Do an exclusive open, don't allow writable fds for existing
+        files */
+      fd = openat (basefd, path, forced_excl_flags, mode);
+      /* If they didn't specify O_EXCL, give them EROFS if the file
+       * exists.
+       */
+      if (fd == -1 && (flags & O_EXCL) == 0)
+       {
+         if (errno == EEXIST)
+           errno = EROFS;
+       }
+      else if (fd != -1)
+       {
+         if (fstat (fd, &stbuf) == -1)
+           return -errno;
+         devino_set_insert (stbuf.st_dev, stbuf.st_ino);
+       }
+    }
+
+  if (fd == -1)
+    return -errno;
+
+  finfo->fh = fd;
+
+  return 0;
+}
+
+static int
+callback_open (const char *path, struct fuse_file_info *finfo)
+{
+  return do_open (path, 0, finfo);
+}
+
+static int
+callback_create(const char *path, mode_t mode, struct fuse_file_info *finfo)
+{
+  return do_open (path, mode, finfo);
+}
+
+static int
+callback_read (const char *path, char *buf, size_t size, off_t offset,
+              struct fuse_file_info *finfo)
+{
+  int r;
+  r = pread (finfo->fh, buf, size, offset);
+  if (r == -1)
+    return -errno;
+  return r;
+}
+
+static int
+callback_write (const char *path, const char *buf, size_t size, off_t offset,
+               struct fuse_file_info *finfo)
+{
+  int r;
+  r = pwrite (finfo->fh, buf, size, offset);
+  if (r == -1)
+    return -errno;
+  return r;
+}
+
+static int
+callback_statfs (const char *path, struct statvfs *st_buf)
+{
+  if (fstatvfs (basefd, st_buf) == -1)
+    return -errno;
+  return 0;
+}
+
+static int
+callback_release (const char *path, struct fuse_file_info *finfo)
+{
+  (void) close (finfo->fh);
+  return 0;
+}
+
+static int
+callback_fsync (const char *path, int crap, struct fuse_file_info *finfo)
+{
+  if (fsync (finfo->fh) == -1)
+    return -errno;
+  return 0;
+}
+
+static int
+callback_access (const char *path, int mode)
+{
+  path = ENSURE_RELPATH (path);
+  
+  /* Apparently at least GNU coreutils rm calls `faccessat(W_OK)`
+   * before trying to do an unlink.  So...we'll just lie about
+   * writable access here.
+   */
+  if (faccessat (basefd, path, mode, 0) == -1)
+    return -errno;
+  return 0;
+}
+
+static int
+callback_setxattr (const char *path, const char *name, const char *value,
+                  size_t size, int flags)
+{
+  return -ENOTSUP;
+}
+
+static int
+callback_getxattr (const char *path, const char *name, char *value,
+                  size_t size)
+{
+  return -ENOTSUP;
+}
+
+/*
+ * List the supported extended attributes.
+ */
+static int
+callback_listxattr (const char *path, char *list, size_t size)
+{
+  return -ENOTSUP;
+
+}
+
+/*
+ * Remove an extended attribute.
+ */
+static int
+callback_removexattr (const char *path, const char *name)
+{
+  return -ENOTSUP;
+
+}
+
+struct fuse_operations callback_oper = {
+  .getattr = callback_getattr,
+  .readlink = callback_readlink,
+  .readdir = callback_readdir,
+  .mknod = callback_mknod,
+  .mkdir = callback_mkdir,
+  .symlink = callback_symlink,
+  .unlink = callback_unlink,
+  .rmdir = callback_rmdir,
+  .rename = callback_rename,
+  .link = callback_link,
+  .chmod = callback_chmod,
+  .chown = callback_chown,
+  .truncate = callback_truncate,
+  .utime = callback_utime,
+  .create = callback_create,
+  .open = callback_open,
+  .read = callback_read,
+  .write = callback_write,
+  .statfs = callback_statfs,
+  .release = callback_release,
+  .fsync = callback_fsync,
+  .access = callback_access,
+
+  /* Extended attributes support for userland interaction */
+  .setxattr = callback_setxattr,
+  .getxattr = callback_getxattr,
+  .listxattr = callback_listxattr,
+  .removexattr = callback_removexattr
+};
+
+enum
+{
+  KEY_HELP,
+  KEY_VERSION,
+};
+
+static void
+usage (const char *progname)
+{
+  fprintf (stdout,
+          "usage: %s basepath mountpoint [options]\n"
+          "\n"
+          "   Makes basepath visible at mountpoint such that files are read-only, directories are writable\n"
+          "\n"
+          "general options:\n"
+          "   -o opt,[opt...]     mount options\n"
+          "   -h  --help          print help\n"
+          "\n", progname);
+}
+
+static int
+rofs_parse_opt (void *data, const char *arg, int key,
+               struct fuse_args *outargs)
+{
+  (void) data;
+
+  switch (key)
+    {
+    case FUSE_OPT_KEY_NONOPT:
+      if (basefd == -1)
+       {
+         basefd = openat (AT_FDCWD, arg, O_RDONLY | O_NONBLOCK | O_DIRECTORY | O_CLOEXEC | O_NOCTTY);
+         if (basefd == -1)
+           {
+             perror ("openat");
+             exit (1);
+           }
+         return 0;
+       }
+      else
+       {
+         return 1;
+       }
+    case FUSE_OPT_KEY_OPT:
+      return 1;
+    case KEY_HELP:
+      usage (outargs->argv[0]);
+      exit (0);
+    default:
+      fprintf (stderr, "see `%s -h' for usage\n", outargs->argv[0]);
+      exit (1);
+    }
+  return 1;
+}
+
+static struct fuse_opt rofs_opts[] = {
+  FUSE_OPT_KEY ("-h", KEY_HELP),
+  FUSE_OPT_KEY ("--help", KEY_HELP),
+  FUSE_OPT_KEY ("-V", KEY_VERSION),
+  FUSE_OPT_KEY ("--version", KEY_VERSION),
+  FUSE_OPT_END
+};
+
+int
+main (int argc, char *argv[])
+{
+  struct fuse_args args = FUSE_ARGS_INIT (argc, argv);
+  int res;
+
+  res = fuse_opt_parse (&args, &basefd, rofs_opts, rofs_parse_opt);
+  if (res != 0)
+    {
+      fprintf (stderr, "Invalid arguments\n");
+      fprintf (stderr, "see `%s -h' for usage\n", argv[0]);
+      exit (1);
+    }
+  if (basefd == -1)
+    {
+      fprintf (stderr, "Missing basepath\n");
+      fprintf (stderr, "see `%s -h' for usage\n", argv[0]);
+      exit (1);
+    }
+
+  created_devino_hash = g_hash_table_new_full (devino_hash, devino_equal, g_free, NULL); 
+
+  fuse_main (args.argc, args.argv, &callback_oper, NULL);
+
+  return 0;
+}
diff --git a/tests/test-rofiles-fuse.sh b/tests/test-rofiles-fuse.sh
new file mode 100755
index 0000000..24ee264
--- /dev/null
+++ b/tests/test-rofiles-fuse.sh
@@ -0,0 +1,74 @@
+#!/bin/bash
+#
+# Copyright (C) 2016 Colin Walters <walters verbum org>
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2 of the License, or (at your option) any later version.
+#
+# This library 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
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this library; if not, write to the
+# Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+# Boston, MA 02111-1307, USA.
+
+set -euo pipefail
+
+echo "1..5"
+
+. $(dirname $0)/libtest.sh
+setup_test_repository "bare-user"
+
+mkdir mnt
+
+$OSTREE checkout test2 checkout-test2
+
+rofiles-fuse checkout-test2 mnt
+cleanup_fuse() {
+    fusermount -u ${test_tmpdir}/mnt || true
+}
+trap cleanup_fuse EXIT
+assert_file_has_content mnt/firstfile first
+echo "ok mount"
+
+if cp /dev/null mnt/firstfile 2>err.txt; then
+    assert_not_reached "inplace mutation"
+fi
+assert_file_has_content err.txt "Read-only file system"
+assert_file_has_content mnt/firstfile first
+assert_file_has_content checkout-test2/firstfile first
+
+echo "ok failed inplace mutation"
+
+echo anewfile-for-fuse > mnt/anewfile-for-fuse
+assert_file_has_content mnt/anewfile-for-fuse anewfile-for-fuse
+assert_file_has_content checkout-test2/anewfile-for-fuse anewfile-for-fuse
+
+mkdir mnt/newfusedir
+for i in $(seq 5); do
+    echo ${i}-morenewfuse-${i} > mnt/newfusedir/test-morenewfuse.${i}
+done
+assert_file_has_content checkout-test2/newfusedir/test-morenewfuse.3 3-morenewfuse-3
+
+echo "ok new content"
+
+rm mnt/baz/cow
+assert_not_has_file checkout-test2/baz/cow
+rm mnt/baz/another -rf
+assert_not_has_dir checkout-test2/baz/another
+
+echo "ok deletion"
+
+ostree --repo=repo commit -b test2 -s fromfuse --link-checkout-speedup --tree=dir=checkout-test2
+
+echo "ok commit"
+
+
+
+
+


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