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.
This commit is contained in:
Colin Walters 2016-02-10 12:42:54 +01:00
parent 5adafd7674
commit e9ccdd2d00
9 changed files with 870 additions and 0 deletions

View File

@ -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))

View File

@ -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

View File

@ -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

View File

@ -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

104
man/rofiles-fuse.xml Normal file
View File

@ -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>

View File

@ -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)

View File

@ -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.

598
src/rofiles-fuse/main.c Normal file
View File

@ -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;
}

74
tests/test-rofiles-fuse.sh Executable file
View File

@ -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"