summaryrefslogtreecommitdiff
path: root/main.c
diff options
context:
space:
mode:
authorReinhard Tartler <siretart@tauware.de>2018-12-31 07:03:25 -0500
committerReinhard Tartler <siretart@tauware.de>2018-12-31 07:03:25 -0500
commita1f62c561dcf0f21b9504367d559b11354dfbcb2 (patch)
tree7bac9aa59f1e3d218500c0b8da751950f5794375 /main.c
New upstream version 0.2
Diffstat (limited to 'main.c')
-rw-r--r--main.c3720
1 files changed, 3720 insertions, 0 deletions
diff --git a/main.c b/main.c
new file mode 100644
index 0000000..3cac1dc
--- /dev/null
+++ b/main.c
@@ -0,0 +1,3720 @@
+/* fuse-overlayfs: Overlay Filesystem in Userspace
+
+ Copyright (C) 2018 Giuseppe Scrivano <giuseppe@scrivano.org>
+ Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu>
+
+ 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 3 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, see <http://www.gnu.org/licenses/>.
+*/
+
+#define _GNU_SOURCE
+#define FUSE_USE_VERSION 31
+#define _FILE_OFFSET_BITS 64
+
+#include <config.h>
+
+#include <fuse_lowlevel.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <stddef.h>
+#include <stdbool.h>
+#include <string.h>
+#include <limits.h>
+#include <dirent.h>
+#include <assert.h>
+#include <errno.h>
+#include <err.h>
+#include <error.h>
+#include <inttypes.h>
+#include <fcntl.h>
+#include <hash.h>
+#include <sys/statvfs.h>
+#include <sys/file.h>
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/syscall.h>
+#include <sys/sysmacros.h>
+
+#include <sys/xattr.h>
+
+#include <linux/fs.h>
+
+#ifndef RENAME_EXCHANGE
+# define RENAME_EXCHANGE (1 << 1)
+# define RENAME_NOREPLACE (1 << 2)
+#endif
+
+#ifndef RENAME_WHITEOUT
+# define RENAME_WHITEOUT (1 << 2)
+#endif
+
+#define ATTR_TIMEOUT 1000000000.0
+#define ENTRY_TIMEOUT 1000000000.0
+
+#define XATTR_PREFIX "user.fuseoverlayfs."
+#define ORIGIN_XATTR "user.fuseoverlayfs.origin"
+#define OPAQUE_XATTR "user.fuseoverlayfs.opaque"
+#define PRIVILEGED_XATTR_PREFIX "trusted.overlay."
+#define PRIVILEGED_OPAQUE_XATTR "trusted.overlay.opaque"
+#define PRIVILEGED_ORIGIN_XATTR "trusted.overlay.origin"
+
+#define NODE_TO_INODE(x) ((fuse_ino_t) x)
+
+#if defined(__GNUC__) && (__GNUC__ > 4 || __GNUC__ == 4 && __GNUC_MINOR__ >= 6) && !defined __cplusplus
+_Static_assert (sizeof (fuse_ino_t) >= sizeof (uintptr_t),
+ "fuse_ino_t too small to hold uintptr_t values!");
+#else
+struct _uintptr_to_must_hold_fuse_ino_t_dummy_struct
+{
+ unsigned _uintptr_to_must_hold_fuse_ino_t:
+ ((sizeof (fuse_ino_t) >= sizeof (uintptr_t)) ? 1 : -1);
+};
+#endif
+
+static bool disable_ovl_whiteout;
+
+struct ovl_layer
+{
+ struct ovl_layer *next;
+ char *path;
+ int fd;
+ bool low;
+};
+
+struct ovl_mapping
+{
+ struct ovl_mapping *next;
+ unsigned int host;
+ unsigned int to;
+ unsigned int len;
+};
+
+struct ovl_node
+{
+ struct ovl_node *parent;
+ Hash_table *children;
+ struct ovl_layer *layer, *last_layer;
+ char *path;
+ char *name;
+ int lookups;
+ ino_t ino;
+ int hidden_dirfd;
+
+ unsigned int present_lowerdir : 1;
+ unsigned int do_unlink : 1;
+ unsigned int do_rmdir : 1;
+ unsigned int hidden : 1;
+ unsigned int whiteout : 1;
+};
+
+struct ovl_data
+{
+ struct fuse_session *se;
+ int debug;
+ char *uid_str;
+ char *gid_str;
+ struct ovl_mapping *uid_mappings;
+ struct ovl_mapping *gid_mappings;
+ char *mountpoint;
+ char *lowerdir;
+ char *context;
+ char *upperdir;
+ char *workdir;
+ char *redirect_dir;
+ int workdir_fd;
+ struct ovl_layer *layers;
+
+ struct ovl_node *root;
+};
+
+static const struct fuse_opt ovl_opts[] = {
+ {"redirect_dir=%s",
+ offsetof (struct ovl_data, redirect_dir), 0},
+ {"context=%s",
+ offsetof (struct ovl_data, context), 0},
+ {"lowerdir=%s",
+ offsetof (struct ovl_data, lowerdir), 0},
+ {"upperdir=%s",
+ offsetof (struct ovl_data, upperdir), 0},
+ {"workdir=%s",
+ offsetof (struct ovl_data, workdir), 0},
+ {"uidmapping=%s",
+ offsetof (struct ovl_data, uid_str), 0},
+ {"gidmapping=%s",
+ offsetof (struct ovl_data, gid_str), 0},
+ FUSE_OPT_END
+};
+
+/* Kernel definitions. */
+
+typedef unsigned char u8;
+typedef unsigned char uuid_t[16];
+
+/* The type returned by overlay exportfs ops when encoding an ovl_fh handle */
+#define OVL_FILEID 0xfb
+
+/* On-disk and in-memeory format for redirect by file handle */
+struct ovl_fh
+{
+ u8 version; /* 0 */
+ u8 magic; /* 0xfb */
+ u8 len; /* size of this header + size of fid */
+ u8 flags; /* OVL_FH_FLAG_* */
+ u8 type; /* fid_type of fid */
+ uuid_t uuid; /* uuid of filesystem */
+ u8 fid[0]; /* file identifier */
+} __packed;
+
+static struct ovl_data *
+ovl_data (fuse_req_t req)
+{
+ return (struct ovl_data *) fuse_req_userdata (req);
+}
+
+static unsigned long
+get_next_wd_counter ()
+{
+ static unsigned long counter = 1;
+ return counter++;
+}
+
+static struct ovl_mapping *
+read_mappings (const char *str)
+{
+ char *buf = NULL, *saveptr = NULL, *it, *endptr;
+ struct ovl_mapping *tmp, *ret = NULL;
+ unsigned int a, b, c;
+ int state = 0;
+
+ buf = alloca (strlen (str) + 1);
+ strcpy (buf, str);
+
+ for (it = strtok_r (buf, ":", &saveptr); it; it = strtok_r (NULL, ":", &saveptr))
+ {
+ switch (state)
+ {
+ case 0:
+ a = strtol (it, &endptr, 10);
+ if (*endptr != 0)
+ error (EXIT_FAILURE, 0, "invalid mapping specified: %s", str);
+ state++;
+ break;
+
+ case 1:
+ b = strtol (it, &endptr, 10);
+ if (*endptr != 0)
+ error (EXIT_FAILURE, 0, "invalid mapping specified: %s", str);
+ state++;
+ break;
+
+ case 2:
+ c = strtol (it, &endptr, 10);
+ if (*endptr != 0)
+ error (EXIT_FAILURE, 0, "invalid mapping specified: %s", str);
+ state = 0;
+
+ tmp = malloc (sizeof (*tmp));
+ if (tmp == NULL)
+ return NULL;
+ tmp->next = ret;
+ tmp->host = a;
+ tmp->to = b;
+ tmp->len = c;
+ ret = tmp;
+ break;
+ }
+ }
+
+ if (state != 0)
+ error (EXIT_FAILURE, 0, "invalid mapping specified: %s", str);
+
+ return ret;
+}
+
+static void
+free_mapping (struct ovl_mapping *it)
+{
+ struct ovl_mapping *next = NULL;
+ for (; it; it = next)
+ {
+ next = it->next;
+ free (it);
+ }
+}
+
+/* Useful in a gdb session. */
+void
+dump_directory (struct ovl_node *node)
+{
+ struct ovl_node *it;
+
+ if (node->children == NULL)
+ return;
+
+ for (it = hash_get_first (node->children); it; it = hash_get_next (node->children, it))
+ printf ("ENTRY: %s (%s)\n", it->name, it->path);
+}
+
+static bool
+ovl_debug (fuse_req_t req)
+{
+ return ovl_data (req)->debug != 0;
+}
+
+static void
+ovl_init (void *userdata, struct fuse_conn_info *conn)
+{
+ conn->want |= FUSE_CAP_DONT_MASK | FUSE_CAP_SPLICE_READ | FUSE_CAP_SPLICE_MOVE;
+ conn->want &= ~FUSE_CAP_PARALLEL_DIROPS;
+}
+
+static struct ovl_layer *
+get_upper_layer (struct ovl_data *lo)
+{
+ return lo->layers;
+}
+
+static struct ovl_layer *
+get_lower_layers (struct ovl_data *lo)
+{
+ return lo->layers->next;
+}
+
+static inline bool
+node_dirp (struct ovl_node *n)
+{
+ return n->children != NULL;
+}
+
+static int
+node_dirfd (struct ovl_node *n)
+{
+ if (n->hidden)
+ return n->hidden_dirfd;
+ return n->layer->fd;
+}
+
+static bool
+has_prefix (const char *str, const char *pref)
+{
+ while (1)
+ {
+ if (*pref == '\0')
+ return true;
+ if (*str == '\0')
+ return false;
+ if (*pref != *str)
+ return false;
+ str++;
+ pref++;
+ }
+ return false;
+}
+
+static int
+set_fd_opaque (int fd)
+{
+ if (fsetxattr (fd, PRIVILEGED_OPAQUE_XATTR, "y", 1, 0) < 0)
+ {
+ if (errno == ENOTSUP)
+ return 0;
+ if (errno != EPERM || fsetxattr (fd, OPAQUE_XATTR, "y", 1, 0) < 0)
+ return -1;
+ }
+
+ return 0;
+}
+
+static int
+is_directory_opaque (int dirfd, const char *path)
+{
+ int fd;
+ char b[16];
+ ssize_t s;
+ int saved_errno;
+
+ fd = TEMP_FAILURE_RETRY (openat (dirfd, path, O_NONBLOCK));
+ if (fd < 0)
+ return -1;
+
+ s = fgetxattr (fd, PRIVILEGED_OPAQUE_XATTR, b, sizeof (b));
+ if (s < 0 && errno == ENODATA)
+ s = fgetxattr (fd, OPAQUE_XATTR, b, sizeof (b));
+
+ saved_errno = errno;
+ close (fd);
+
+ if (s < 0)
+ {
+ if (saved_errno == ENOTSUP || saved_errno == ENODATA)
+ return 0;
+ return -1;
+ }
+
+ if (b[0] == 'y')
+ return 1;
+
+ return 0;
+}
+
+static int
+create_whiteout (struct ovl_data *lo, struct ovl_node *parent, const char *name, bool skip_mknod)
+{
+ char whiteout_path[PATH_MAX + 10];
+ int fd = -1;
+ static bool can_mknod = true;
+
+ if (!disable_ovl_whiteout && !skip_mknod && can_mknod)
+ {
+ int ret;
+
+ sprintf (whiteout_path, "%s/%s", parent->path, name);
+ ret = mknodat (get_upper_layer (lo)->fd, whiteout_path, S_IFCHR|0700, makedev (0, 0));
+ if (ret == 0)
+ return 0;
+
+ if (errno != EPERM && errno != ENOTSUP)
+ return -1;
+
+ /* if it fails with EPERM then do not attempt mknod again. */
+ can_mknod = false;
+ }
+
+ sprintf (whiteout_path, "%s/.wh.%s", parent->path, name);
+ fd = TEMP_FAILURE_RETRY (openat (get_upper_layer (lo)->fd, whiteout_path, O_CREAT|O_WRONLY|O_NONBLOCK, 0700));
+ if (fd < 0 && errno != EEXIST)
+ return -1;
+
+ close (fd);
+ return 0;
+}
+
+static int
+delete_whiteout (struct ovl_data *lo, int dirfd, struct ovl_node *parent, const char *name)
+{
+ struct stat st;
+ char whiteout_path[PATH_MAX + 10];
+
+ if (dirfd >= 0)
+ {
+ if (TEMP_FAILURE_RETRY (fstatat (dirfd, name, &st, AT_SYMLINK_NOFOLLOW)) == 0
+ && (st.st_mode & S_IFMT) == S_IFCHR
+ && major (st.st_rdev) == 0
+ && minor (st.st_rdev) == 0)
+ {
+ if (unlinkat (dirfd, name, 0) < 0)
+ return -1;
+ }
+ }
+ else
+ {
+ sprintf (whiteout_path, "%s/%s", parent->path, name);
+
+ if (TEMP_FAILURE_RETRY (fstatat (get_upper_layer (lo)->fd, whiteout_path, &st, AT_SYMLINK_NOFOLLOW)) == 0
+ && (st.st_mode & S_IFMT) == S_IFCHR
+ && major (st.st_rdev) == 0
+ && minor (st.st_rdev) == 0)
+ {
+ if (unlinkat (get_upper_layer (lo)->fd, whiteout_path, 0) < 0)
+ return -1;
+ }
+ }
+
+ /* Look for the .wh. alternative as well. */
+
+ if (dirfd >= 0)
+ {
+ sprintf (whiteout_path, ".wh.%s", name);
+ if (unlinkat (dirfd, whiteout_path, 0) < 0 && errno != ENOENT)
+ return -1;
+ }
+ else
+ {
+ sprintf (whiteout_path, "%s/.wh.%s", parent->path, name);
+ if (unlinkat (get_upper_layer (lo)->fd, whiteout_path, 0) < 0 && errno != ENOENT)
+ return -1;
+ }
+
+ return 0;
+}
+
+static int
+hide_node (struct ovl_data *lo, struct ovl_node *node, bool unlink_src)
+{
+ char dest[PATH_MAX];
+ char *newpath;
+
+ asprintf (&newpath, "%lu", get_next_wd_counter ());
+ if (newpath == NULL)
+ {
+ unlink (dest);
+ return -1;
+ }
+
+ assert (node->layer == get_upper_layer (lo));
+
+ /* Might be leftover from a previous run. */
+ unlinkat (lo->workdir_fd, newpath, 0);
+ unlinkat (lo->workdir_fd, newpath, AT_REMOVEDIR);
+
+ if (unlink_src)
+ {
+ /* If the atomic rename+mknod failed, then fallback into doing it in two steps. */
+ if (syscall (SYS_renameat2, node_dirfd (node), node->path, lo->workdir_fd,
+ newpath, RENAME_WHITEOUT) < 0)
+ {
+ if (renameat (node_dirfd (node), node->path, lo->workdir_fd, newpath) < 0)
+ {
+ free (newpath);
+ return -1;
+ }
+ if (node->parent)
+ {
+ if (create_whiteout (lo, node->parent, node->name, false) < 0)
+ return -1;
+ }
+ }
+ }
+ else
+ {
+ if (node_dirp (node))
+ {
+ if (mkdirat (lo->workdir_fd, newpath, 0700) < 0)
+ {
+ free (newpath);
+ return -1;
+ }
+ }
+ else
+ {
+ if (linkat (node_dirfd (node), node->path, lo->workdir_fd, newpath, 0) < 0)
+ {
+ free (newpath);
+ return -1;
+ }
+ }
+ }
+ node->hidden_dirfd = lo->workdir_fd;
+ free (node->path);
+ node->path = newpath;
+ node->hidden = 1;
+ node->parent = NULL;
+
+ if (node_dirp (node))
+ node->do_rmdir = 1;
+ else
+ node->do_unlink = 1;
+ return 0;
+}
+
+static unsigned int
+find_mapping (unsigned int id, struct ovl_mapping *mapping, bool direct)
+{
+ if (mapping == NULL)
+ return id;
+ for (; mapping; mapping = mapping->next)
+ {
+ if (direct)
+ {
+ if (id >= mapping->host && id < mapping->host + mapping->len)
+ return mapping->to + (id - mapping->host);
+ }
+ else
+ {
+ if (id >= mapping->to && id < mapping->to + mapping->len)
+ return mapping->host + (id - mapping->to);
+ }
+ }
+ return 65534;
+}
+
+static uid_t
+get_uid (struct ovl_data *data, uid_t id)
+{
+ return find_mapping (id, data->uid_mappings, false);
+}
+
+static uid_t
+get_gid (struct ovl_data *data, gid_t id)
+{
+ return find_mapping (id, data->gid_mappings, false);
+}
+
+static int
+rpl_stat (fuse_req_t req, struct ovl_node *node, struct stat *st)
+{
+ int ret;
+ struct ovl_data *data = ovl_data (req);
+
+ ret = TEMP_FAILURE_RETRY (fstatat (node_dirfd (node), node->path, st, AT_SYMLINK_NOFOLLOW));
+ if (ret < 0)
+ return ret;
+
+ st->st_uid = find_mapping (st->st_uid, data->uid_mappings, true);
+ st->st_gid = find_mapping (st->st_gid, data->gid_mappings, true);
+
+ st->st_ino = node->ino;
+ if (ret == 0 && node_dirp (node))
+ {
+ struct ovl_node *it;
+
+ st->st_nlink = 2;
+
+ for (it = hash_get_first (node->children); it; it = hash_get_next (node->children, it))
+ {
+ if (node_dirp (it))
+ st->st_nlink++;
+ }
+ }
+
+ return ret;
+}
+
+static void
+node_mark_all_free (void *p)
+{
+ struct ovl_node *it, *n = (struct ovl_node *) p;
+
+ n->lookups = 0;
+
+ if (n->children)
+ {
+ for (it = hash_get_first (n->children); it; it = hash_get_next (n->children, it))
+ node_mark_all_free (it);
+ }
+}
+
+static void
+node_free (void *p)
+{
+ struct ovl_node *n = (struct ovl_node *) p;
+ if (n->parent)
+ {
+ if (hash_lookup (n->parent->children, n) == n)
+ hash_delete (n->parent->children, n);
+ n->parent = NULL;
+ }
+
+ if (n->lookups > 0)
+ return;
+
+ if (n->children)
+ {
+ struct ovl_node *it;
+
+ for (it = hash_get_first (n->children); it; it = hash_get_next (n->children, it))
+ it->parent = NULL;
+
+ hash_free (n->children);
+ n->children = NULL;
+ }
+
+ if (n->do_unlink)
+ unlinkat (n->hidden_dirfd, n->path, 0);
+ if (n->do_rmdir)
+ unlinkat (n->hidden_dirfd, n->path, AT_REMOVEDIR);
+
+ free (n->name);
+ free (n->path);
+ free (n);
+ return;
+}
+
+static void
+do_forget (fuse_ino_t ino, uint64_t nlookup)
+{
+ struct ovl_node *n;
+
+ if (ino == FUSE_ROOT_ID)
+ return;
+
+ n = (struct ovl_node *) ino;
+
+ n->lookups -= nlookup;
+ if (n->lookups <= 0)
+ node_free (n);
+}
+
+static void
+ovl_forget (fuse_req_t req, fuse_ino_t ino, uint64_t nlookup)
+{
+ if (ovl_debug (req))
+ fprintf (stderr, "ovl_forget(ino=%" PRIu64 ", nlookup=%lu)\n",
+ ino, nlookup);
+ do_forget (ino, nlookup);
+ fuse_reply_none (req);
+}
+
+static size_t
+node_hasher (const void *p, size_t s)
+{
+ struct ovl_node *n = (struct ovl_node *) p;
+ return hash_string (n->name, s);
+}
+
+static bool
+node_compare (const void *n1, const void *n2)
+{
+ struct ovl_node *node1 = (struct ovl_node *) n1;
+ struct ovl_node *node2 = (struct ovl_node *) n2;
+
+ return strcmp (node1->name, node2->name) == 0 ? true : false;
+}
+
+static struct ovl_node *
+make_whiteout_node (const char *path, const char *name)
+{
+ struct ovl_node *ret = calloc (1, sizeof (*ret));
+ if (ret == NULL)
+ {
+ errno = ENOMEM;
+ return NULL;
+ }
+ ret->name = strdup (name);
+ if (ret->name == NULL)
+ {
+ free (ret);
+ errno = ENOMEM;
+ return NULL;
+ }
+ ret->path = strdup (path);
+ if (ret->path == NULL)
+ {
+ free (ret->name);
+ free (ret);
+ errno = ENOMEM;
+ return NULL;
+ }
+ ret->whiteout = 1;
+ return ret;
+}
+
+static struct ovl_node *
+make_ovl_node (const char *path, struct ovl_layer *layer, const char *name, ino_t ino, bool dir_p, struct ovl_node *parent)
+{
+ struct ovl_node *ret = malloc (sizeof (*ret));
+ if (ret == NULL)
+ {
+ errno = ENOMEM;
+ return NULL;
+ }
+
+ ret->last_layer = NULL;
+ ret->parent = parent;
+ ret->lookups = 0;
+ ret->do_unlink = 0;
+ ret->hidden = 0;
+ ret->do_rmdir = 0;
+ ret->whiteout = 0;
+ ret->layer = layer;
+ ret->ino = ino;
+ ret->present_lowerdir = 0;
+ ret->hidden_dirfd = -1;
+ ret->name = strdup (name);
+ if (ret->name == NULL)
+ {
+ free (ret);
+ errno = ENOMEM;
+ return NULL;
+ }
+
+ if (has_prefix (path, "./") && path[2])
+ path += 2;
+
+ ret->path = strdup (path);
+ if (ret->path == NULL)
+ {
+ free (ret->name);
+ free (ret);
+ errno = ENOMEM;
+ return NULL;
+ }
+
+ if (!dir_p)
+ ret->children = NULL;
+ else
+ {
+ ret->children = hash_initialize (10, NULL, node_hasher, node_compare, node_free);
+ if (ret->children == NULL)
+ {
+ free (ret->path);
+ free (ret->name);
+ free (ret);
+ errno = ENOMEM;
+ return NULL;
+ }
+ }
+
+ if (ret->ino == 0)
+ {
+ struct stat st;
+ struct ovl_layer *it;
+ char path[PATH_MAX];
+
+ strcpy (path, ret->path);
+ for (it = layer; it; it = it->next)
+ {
+ ssize_t s;
+ int fd = TEMP_FAILURE_RETRY (openat (it->fd, path, O_RDONLY|O_NONBLOCK|O_NOFOLLOW|O_PATH));
+ if (fd < 0)
+ continue;
+
+ if (fstat (fd, &st) == 0)
+ ret->ino = st.st_ino;
+
+ close (fd);
+
+ fd = TEMP_FAILURE_RETRY (openat (it->fd, path, O_RDONLY|O_NONBLOCK|O_NOFOLLOW));
+ if (fd < 0)
+ continue;
+ s = fgetxattr (fd, PRIVILEGED_ORIGIN_XATTR, path, sizeof (path) - 1);
+ if (s > 0)
+ {
+ char buf[512];
+ struct ovl_fh *ofh = (struct ovl_fh *) path;
+ size_t s = ofh->len - sizeof (*ofh);
+ struct file_handle *fh = (struct file_handle *) buf;
+
+ if (s < sizeof (buf) - sizeof(int) * 2)
+ {
+ int originfd;
+
+ /*
+ overlay in the kernel stores a file handle in the .origin xattr.
+ Honor it when present, but don't fail on errors as an unprivileged
+ user cannot open a file handle.
+ */
+ fh->handle_bytes = s;
+ fh->handle_type = ofh->type;
+ memcpy (fh->f_handle, ofh->fid, s);
+
+ originfd = open_by_handle_at (AT_FDCWD, fh, O_RDONLY);
+ if (originfd >= 0)
+ {
+ if (fstat (originfd, &st) == 0)
+ {
+ ret->ino = st.st_ino;
+ close (originfd);
+ close (fd);
+ break;
+ }
+ }
+ }
+ }
+
+ /* If an origin is specified, use it for the next layer lookup. */
+ s = fgetxattr (fd, ORIGIN_XATTR, path, sizeof (path) - 1);
+ if (s > 0)
+ path[s] = '\0';
+
+ close (fd);
+
+ if (parent && parent->last_layer == it)
+ break;
+ }
+ }
+
+ return ret;
+}
+
+static struct ovl_node *
+insert_node (struct ovl_node *parent, struct ovl_node *item, bool replace)
+{
+ struct ovl_node *old = NULL, *prev_parent = item->parent;
+ int ret;
+
+ if (prev_parent)
+ {
+ if (hash_lookup (prev_parent->children, item) == item)
+ hash_delete (prev_parent->children, item);
+ }
+
+ if (replace)
+ {
+ old = hash_delete (parent->children, item);
+ if (old)
+ node_free (old);
+ }
+
+ ret = hash_insert_if_absent (parent->children, item, (const void **) &old);
+ if (ret < 0)
+ {
+ node_free (item);
+ errno = ENOMEM;
+ return NULL;
+ }
+ if (ret == 0)
+ {
+ node_free (item);
+ return old;
+ }
+
+ item->parent = parent;
+
+ return item;
+}
+
+static const char *
+get_whiteout_name (const char *name, struct stat *st)
+{
+ if (has_prefix (name, ".wh."))
+ return name + 4;
+ if ((st->st_mode & S_IFMT) == S_IFCHR
+ && major (st->st_rdev) == 0
+ && minor (st->st_rdev) == 0)
+ return name;
+ return NULL;
+}
+
+static struct ovl_node *
+load_dir (struct ovl_data *lo, struct ovl_node *n, struct ovl_layer *layer, char *path, char *name)
+{
+ DIR *dp;
+ struct dirent *dent;
+ struct stat st, tmp_st;
+ struct ovl_layer *it, *upper_layer = get_upper_layer (lo);
+
+ if (!n)
+ {
+ n = make_ovl_node (path, layer, name, 0, true, NULL);
+ if (n == NULL)
+ return NULL;
+ }
+
+ for (it = lo->layers; it; it = it->next)
+ {
+ int fd = TEMP_FAILURE_RETRY (openat (it->fd, path, O_DIRECTORY));
+ if (fd < 0)
+ continue;
+
+ dp = fdopendir (fd);
+ if (dp == NULL)
+ {
+ close (fd);
+ continue;
+ }
+
+ for (;;)
+ {
+ int ret;
+ struct ovl_node key;
+ const char *wh;
+ struct ovl_node *child = NULL;
+ char node_path[PATH_MAX + 1];
+
+ errno = 0;
+ dent = readdir (dp);
+ if (dent == NULL)
+ {
+ if (errno)
+ {
+ int saved_errno = errno;
+ closedir (dp);
+ errno = saved_errno;
+ return NULL;
+ }
+
+ break;
+ }
+
+ key.name = dent->d_name;
+
+ if ((strcmp (dent->d_name, ".") == 0) || strcmp (dent->d_name, "..") == 0)
+ continue;
+
+ if (TEMP_FAILURE_RETRY (fstatat (fd, dent->d_name, &st, AT_SYMLINK_NOFOLLOW)) < 0)
+ {
+ closedir (dp);
+ return NULL;
+ }
+
+ child = hash_lookup (n->children, &key);
+ if (child)
+ {
+ if (child->whiteout && it == upper_layer)
+ {
+ hash_delete (n->children, child);
+ node_free (child);
+ child = NULL;
+ }
+ else
+ {
+ if (it->low)
+ child->present_lowerdir = 1;
+ continue;
+ }
+ }
+
+ sprintf (node_path, ".wh.%s", dent->d_name);
+ ret = TEMP_FAILURE_RETRY (fstatat (fd, node_path, &tmp_st, AT_SYMLINK_NOFOLLOW));
+ if (ret < 0 && errno != ENOENT)
+ {
+ closedir (dp);
+ return NULL;
+ }
+ sprintf (node_path, "%s/%s", n->path, dent->d_name);
+
+ if (ret == 0)
+ {
+ child = make_whiteout_node (node_path, dent->d_name);
+ if (child == NULL)
+ {
+ errno = ENOMEM;
+ closedir (dp);
+ return NULL;
+ }
+ }
+ else
+ {
+ wh = get_whiteout_name (dent->d_name, &st);
+ if (wh)
+ {
+ child = make_whiteout_node (node_path, wh);
+ if (child == NULL)
+ {
+ errno = ENOMEM;
+ closedir (dp);
+ return NULL;
+ }
+ }
+ else
+ {
+ bool dirp = st.st_mode & S_IFDIR;
+
+ child = make_ovl_node (node_path, it, dent->d_name, 0, dirp, n);
+ if (child == NULL)
+ {
+ errno = ENOMEM;
+ closedir (dp);
+ return NULL;
+ }
+ }
+ }
+
+ if (insert_node (n, child, false) == NULL)
+ {
+ errno = ENOMEM;
+ return NULL;
+ }
+ }
+ closedir (dp);
+
+ if (n->last_layer == it)
+ break;
+ }
+
+ return n;
+}
+
+static void
+free_layers (struct ovl_layer *layers)
+{
+ if (layers == NULL)
+ return;
+ free_layers (layers->next);
+ free (layers->path);
+ if (layers->fd >= 0)
+ close (layers->fd);
+ free (layers);
+}
+
+static struct ovl_layer *
+read_dirs (char *path, bool low, struct ovl_layer *layers)
+{
+ char *buf = NULL, *saveptr = NULL, *it;
+ struct ovl_layer *last;
+
+ if (path == NULL)
+ return NULL;
+
+ buf = strdup (path);
+ if (buf == NULL)
+ return NULL;
+
+ last = layers;
+ while (last && last->next)
+ last = last->next;
+
+ for (it = strtok_r (path, ":", &saveptr); it; it = strtok_r (NULL, ":", &saveptr))
+ {
+ char full_path[PATH_MAX + 1];
+ struct ovl_layer *l = NULL;
+
+ if (realpath (it, full_path) < 0)
+ return NULL;
+
+ l = malloc (sizeof (*l));
+ if (l == NULL)
+ {
+ free_layers (layers);
+ return NULL;
+ }
+
+ l->path = strdup (full_path);
+ if (l->path == NULL)
+ {
+ free (l);
+ free_layers (layers);
+ return NULL;
+ }
+
+ l->fd = open (l->path, O_DIRECTORY);
+ if (l->fd < 0)
+ {
+ free (l->path);
+ free (l);
+ free_layers (layers);
+ return NULL;
+ }
+
+ l->low = low;
+ if (low)
+ {
+ l->next = NULL;
+ if (last == NULL)
+ last = layers = l;
+ else
+ {
+ last->next = l;
+ last = l;
+ }
+ }
+ else
+ {
+ l->next = layers;
+ layers = l;
+ }
+ }
+ free (buf);
+ return layers;
+}
+
+static struct ovl_node *
+do_lookup_file (struct ovl_data *lo, fuse_ino_t parent, const char *name)
+{
+ struct ovl_node key;
+ struct ovl_node *node, *pnode;
+
+ if (parent == FUSE_ROOT_ID)
+ pnode = lo->root;
+ else
+ pnode = (struct ovl_node *) parent;
+
+ if (name == NULL)
+ return pnode;
+
+ if (has_prefix (name, ".wh."))
+ {
+ errno = EINVAL;
+ return NULL;
+ }
+
+ key.name = (char *) name;
+ node = hash_lookup (pnode->children, &key);
+ if (node == NULL)
+ {
+ int ret;
+ char path[PATH_MAX];
+ char whpath[PATH_MAX];
+ struct ovl_layer *it;
+ struct stat st, tmp_st;
+ struct ovl_layer *upper_layer = get_upper_layer (lo);
+
+ for (it = lo->layers; it; it = it->next)
+ {
+ const char *wh_name;
+
+ sprintf (path, "%s/%s", pnode->path, name);
+ ret = TEMP_FAILURE_RETRY (fstatat (it->fd, path, &st, AT_SYMLINK_NOFOLLOW));
+ if (ret < 0)
+ {
+ int saved_errno = errno;
+
+ if (errno == ENOENT)
+ {
+ sprintf (whpath, "%s/.wh.%s", pnode->path, name);
+ ret = TEMP_FAILURE_RETRY (fstatat (it->fd, whpath, &tmp_st, AT_SYMLINK_NOFOLLOW));
+ if (ret < 0 && errno != ENOENT)
+ return NULL;
+ if (ret == 0)
+ {
+ node = make_whiteout_node (path, name);
+ if (node == NULL)
+ {
+ errno = ENOMEM;
+ return NULL;
+ }
+ goto insert_node;
+ }
+ continue;
+ }
+
+ if (node)
+ node_free (node);
+
+ errno = saved_errno;
+ return NULL;
+ }
+
+ /* If we already know the node, simply update the ino. */
+ if (node)
+ {
+ if (node->whiteout && it == upper_layer)
+ {
+ hash_delete (pnode->children, node);
+ node_free (node);
+ node = NULL;
+ }
+ else
+ {
+ node->ino = st.st_ino;
+ if (it->low)
+ node->present_lowerdir = 1;
+ continue;
+ }
+ }
+
+ sprintf (whpath, "%s/.wh.%s", pnode->path, name);
+ ret = TEMP_FAILURE_RETRY (fstatat (it->fd, whpath, &tmp_st, AT_SYMLINK_NOFOLLOW));
+ if (ret < 0 && errno != ENOENT)
+ return NULL;
+ if (ret == 0)
+ node = make_whiteout_node (path, name);
+ else
+ {
+ wh_name = get_whiteout_name (name, &st);
+ if (wh_name)
+ node = make_whiteout_node (path, wh_name);
+ else
+ node = make_ovl_node (path, it, name, 0, st.st_mode & S_IFDIR, pnode);
+ }
+ if (node == NULL)
+ {
+ errno = ENOMEM;
+ return NULL;
+ }
+
+ if (st.st_mode & S_IFDIR)
+ {
+ ret = is_directory_opaque (it->fd, path);
+ if (ret < 0)
+ {
+ node_free (node);
+ return NULL;
+ }
+ if (ret > 0)
+ node->last_layer = it;
+ }
+insert_node:
+ if (insert_node (pnode, node, false) == NULL)
+ {
+ node_free (node);
+ errno = ENOMEM;
+ return NULL;
+ }
+ if (node->last_layer)
+ break;
+ if (pnode && pnode->last_layer == it)
+ break;
+ }
+ }
+
+ if (node == NULL || node->whiteout)
+ {
+ errno = ENOENT;
+ return NULL;
+ }
+ return node;
+}
+
+static void
+ovl_lookup (fuse_req_t req, fuse_ino_t parent, const char *name)
+{
+ struct fuse_entry_param e;
+ int err = 0;
+ struct ovl_data *lo = ovl_data (req);
+ struct ovl_node *node;
+
+ if (ovl_debug (req))
+ fprintf (stderr, "ovl_lookup(parent=%" PRIu64 ", name=%s)\n",
+ parent, name);
+
+ memset (&e, 0, sizeof (e));
+
+ node = do_lookup_file (lo, parent, name);
+ if (node == NULL)
+ {
+ fuse_reply_err (req, errno);
+ return;
+ }
+
+ err = rpl_stat (req, node, &e.attr);
+ if (err)
+ {
+ fuse_reply_err (req, errno);
+ return;
+ }
+
+ e.ino = NODE_TO_INODE (node);
+ node->lookups++;
+ e.attr_timeout = ATTR_TIMEOUT;
+ e.entry_timeout = ENTRY_TIMEOUT;
+ fuse_reply_entry (req, &e);
+}
+
+struct ovl_dirp
+{
+ struct ovl_data *lo;
+ struct ovl_node **tbl;
+ size_t tbl_size;
+ size_t offset;
+};
+
+static struct ovl_dirp *
+ovl_dirp (struct fuse_file_info *fi)
+{
+ return (struct ovl_dirp *) (uintptr_t) fi->fh;
+}
+
+static void
+ovl_opendir (fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi)
+{
+ size_t counter = 0;
+ struct ovl_node *node;
+ struct ovl_data *lo = ovl_data (req);
+ struct ovl_node *it;
+ struct ovl_dirp *d = calloc (1, sizeof (struct ovl_dirp));
+
+ if (d == NULL)
+ {
+ errno = ENOENT;
+ goto out_errno;
+ }
+
+ node = do_lookup_file (lo, ino, NULL);
+ if (node == NULL)
+ {
+ errno = ENOENT;
+ goto out_errno;
+ }
+
+ if (! node_dirp (node))
+ {
+ errno = ENOTDIR;
+ goto out_errno;
+ }
+
+ node = load_dir (lo, node, node->layer, node->path, node->name);
+ if (node == NULL)
+ goto out_errno;
+
+ d->offset = 0;
+ d->tbl_size = hash_get_n_entries (node->children) + 2;
+ d->tbl = malloc (sizeof (struct ovl_node *) * d->tbl_size);
+ if (d->tbl == NULL)
+ {
+ errno = ENOMEM;
+ goto out_errno;
+ }
+
+ d->tbl[counter++] = node;
+ d->tbl[counter++] = node->parent;
+
+ for (it = hash_get_first (node->children); it; it = hash_get_next (node->children, it))
+ {
+ it->lookups++;
+ d->tbl[counter++] = it;
+ }
+
+ fi->fh = (uintptr_t) d;
+
+ fuse_reply_open (req, fi);
+ return;
+
+out_errno:
+ if (d)
+ {
+ if (d->tbl)
+ free (d->tbl);
+ free (d);
+ }
+ fuse_reply_err (req, errno);
+}
+
+static int
+create_missing_whiteouts (struct ovl_data *lo, struct ovl_node *node, const char *from)
+{
+ struct ovl_layer *l;
+
+ if (! node_dirp (node))
+ return 0;
+
+ node = load_dir (lo, node, node->layer, node->path, node->name);
+ if (node == NULL)
+ return -1;
+
+ for (l = get_lower_layers (lo); l; l = l->next)
+ {
+ DIR *dp;
+ int fd;
+
+ fd = TEMP_FAILURE_RETRY (openat (l->fd, from, O_DIRECTORY));
+ if (fd < 0)
+ {
+ if (errno == ENOENT)
+ continue;
+ if (errno == ENOTDIR)
+ break;
+
+ return -1;
+ }
+
+ dp = fdopendir (fd);
+ if (dp == NULL)
+ {
+ close (fd);
+ return -1;
+ }
+ else
+ {
+ struct dirent *dent;
+
+ for (;;)
+ {
+ struct ovl_node key;
+ struct ovl_node *n;
+
+ errno = 0;
+ dent = readdir (dp);
+ if (dent == NULL)
+ {
+ if (errno)
+ {
+ int saved_errno = errno;
+ closedir (dp);
+ errno = saved_errno;
+ return -1;
+ }
+
+ break;
+ }
+
+ if (strcmp (dent->d_name, ".") == 0)
+ continue;
+ if (strcmp (dent->d_name, "..") == 0)
+ continue;
+
+ key.name = (char *) dent->d_name;
+
+ n = hash_lookup (node->children, &key);
+ if (n)
+ {
+ if (node_dirp (n))
+ {
+ char *c;
+ n = load_dir (lo, n, n->layer, n->path, n->name);
+ if (n == NULL)
+ {
+ closedir (dp);
+ return -1;
+ }
+ if (asprintf (&c, "%s/%s", from, n->name) < 0)
+ {
+ closedir (dp);
+ return -1;
+ }
+
+ if (create_missing_whiteouts (lo, n, c) < 0)
+ {
+ free (c);
+ closedir (dp);
+ return -1;
+ }
+ free (c);
+ }
+ continue;
+ }
+
+ if (create_whiteout (lo, node, dent->d_name, false) < 0)
+ {
+ closedir (dp);
+ return -1;
+ }
+ }
+
+ closedir (dp);
+ }
+ }
+ return 0;
+}
+
+static void
+ovl_do_readdir (fuse_req_t req, fuse_ino_t ino, size_t size,
+ off_t offset, struct fuse_file_info *fi, int plus)
+{
+ struct ovl_dirp *d = ovl_dirp (fi);
+ size_t remaining = size;
+ char *p, *buffer = calloc (size, 1);
+
+ if (buffer == NULL)
+ {
+ fuse_reply_err (req, ENOMEM);
+ return;
+ }
+ p = buffer;
+ for (; remaining > 0 && offset < d->tbl_size; offset++)
+ {
+ int ret;
+ size_t entsize;
+ struct stat st;
+ const char *name;
+ struct ovl_node *node = d->tbl[offset];
+
+ if (node == NULL || node->whiteout)
+ continue;
+
+ ret = rpl_stat (req, node, &st);
+ if (ret < 0)
+ {
+ fuse_reply_err (req, errno);
+ goto exit;
+ }
+
+ if (offset == 0)
+ name = ".";
+ else if (offset == 1)
+ name = "..";
+ else
+ name = node->name;
+
+ if (!plus)
+ entsize = fuse_add_direntry (req, p, remaining, name, &st, offset + 1);
+ else
+ {
+ struct fuse_entry_param e;
+
+ memset (&e, 0, sizeof (e));
+ e.attr_timeout = ATTR_TIMEOUT;
+ e.entry_timeout = ENTRY_TIMEOUT;
+ e.ino = NODE_TO_INODE (node);
+ memcpy (&e.attr, &st, sizeof (st));
+
+ entsize = fuse_add_direntry_plus (req, p, remaining, name, &e, offset + 1);
+ if (entsize <= remaining)
+ {
+ /* First two entries are . and .. */
+ if (offset >= 2)
+ node->lookups++;
+ }
+ }
+
+ if (entsize > remaining)
+ break;
+
+ p += entsize;
+ remaining -= entsize;
+ }
+ fuse_reply_buf (req, buffer, size - remaining);
+ exit:
+ free (buffer);
+}
+
+static void
+ovl_readdir (fuse_req_t req, fuse_ino_t ino, size_t size,
+ off_t offset, struct fuse_file_info *fi)
+{
+ ovl_do_readdir (req, ino, size, offset, fi, 0);
+}
+
+static void
+ovl_readdirplus (fuse_req_t req, fuse_ino_t ino, size_t size,
+ off_t offset, struct fuse_file_info *fi)
+{
+ ovl_do_readdir (req, ino, size, offset, fi, 1);
+}
+
+static void
+ovl_releasedir (fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi)
+{
+ size_t s;
+ struct ovl_dirp *d = ovl_dirp (fi);
+
+ for (s = 2; s < d->tbl_size; s++)
+ {
+ struct ovl_node *n = d->tbl[s];
+ do_forget (NODE_TO_INODE (n), 1);
+ }
+
+ free (d->tbl);
+ free (d);
+ fuse_reply_err (req, 0);
+}
+
+static void
+ovl_listxattr (fuse_req_t req, fuse_ino_t ino, size_t size)
+{
+ ssize_t len;
+ struct ovl_node *node;
+ struct ovl_data *lo = ovl_data (req);
+ char *buf = NULL;
+ int fd;
+
+ if (ovl_debug (req))
+ fprintf (stderr, "ovl_listxattr(ino=%" PRIu64 ", size=%zu)\n", ino, size);
+
+ node = do_lookup_file (lo, ino, NULL);
+ if (node == NULL)
+ {
+ fuse_reply_err (req, ENOENT);
+ return;
+ }
+
+ if (size > 0)
+ {
+ buf = malloc (size);
+ if (buf == NULL)
+ {
+ fuse_reply_err (req, ENOMEM);
+ return;
+ }
+ }
+
+ fd = TEMP_FAILURE_RETRY (openat (node_dirfd (node), node->path, O_RDONLY|O_NONBLOCK));
+ if (fd < 0)
+ {
+ free (buf);
+ fuse_reply_err (req, errno);
+ return;
+ }
+
+ len = flistxattr (fd, buf, size);
+ if (len < 0)
+ fuse_reply_err (req, errno);
+ else if (size == 0)
+ fuse_reply_xattr (req, len);
+ else if (len <= size)
+ fuse_reply_buf (req, buf, len);
+
+ free (buf);
+ close (fd);
+}
+
+static void
+ovl_getxattr (fuse_req_t req, fuse_ino_t ino, const char *name, size_t size)
+{
+ ssize_t len;
+ struct ovl_node *node;
+ struct ovl_data *lo = ovl_data (req);
+ char *buf = NULL;
+ int fd;
+
+ if (ovl_debug (req))
+ fprintf (stderr, "ovl_getxattr(ino=%" PRIu64 ", name=%s, size=%zu)\n", ino, name, size);
+
+ node = do_lookup_file (lo, ino, NULL);
+ if (node == NULL)
+ {
+ fuse_reply_err (req, ENOENT);
+ return;
+ }
+
+ if (size > 0)
+ {
+ buf = malloc (size);
+ if (buf == NULL)
+ {
+ fuse_reply_err (req, ENOMEM);
+ return;
+ }
+ }
+
+ fd = TEMP_FAILURE_RETRY (openat (node_dirfd (node), node->path, O_RDONLY|O_NONBLOCK));
+ if (fd < 0)
+ {
+ free (buf);
+ fuse_reply_err (req, errno);
+ return;
+ }
+
+ len = fgetxattr (fd, name, buf, size);
+ if (len < 0)
+ fuse_reply_err (req, errno);
+ else if (size == 0)
+ fuse_reply_xattr (req, len);
+ else if (len <= size)
+ fuse_reply_buf (req, buf, len);
+
+ free (buf);
+ close (fd);
+}
+
+static void
+ovl_access (fuse_req_t req, fuse_ino_t ino, int mask)
+{
+ int ret;
+ struct ovl_data *lo = ovl_data (req);
+ struct ovl_node *n = do_lookup_file (lo, ino, NULL);
+
+ if (ovl_debug (req))
+ fprintf (stderr, "ovl_access(ino=%" PRIu64 ", mask=%d)\n",
+ ino, mask);
+
+ ret = faccessat (node_dirfd (n), n->path, mask, AT_SYMLINK_NOFOLLOW);
+ fuse_reply_err (req, ret < 0 ? errno : 0);
+}
+
+static int
+copy_xattr (int sfd, int dfd, char *buf, size_t buf_size)
+{
+ size_t xattr_len;
+
+ xattr_len = flistxattr (sfd, buf, buf_size / 2);
+ if (xattr_len > 0)
+ {
+ char *it;
+ char *xattr_buf = buf + buf_size / 2;
+ for (it = buf; it - buf < xattr_len; it += strlen (it) + 1)
+ {
+ ssize_t s = fgetxattr (sfd, it, xattr_buf, buf_size / 2);
+ if (s < 0)
+ return -1;
+
+ if (fsetxattr (dfd, it, xattr_buf, s, 0) < 0)
+ return -1;
+ }
+ }
+ return 0;
+}
+
+static int create_node_directory (struct ovl_data *lo, struct ovl_node *src);
+
+static int
+create_directory (struct ovl_data *lo, int dirfd, const char *name, const struct timespec *times,
+ struct ovl_node *parent, int xattr_sfd, uid_t uid, gid_t gid, mode_t mode)
+{
+ int ret;
+ int dfd = -1;
+ char *buf = NULL;
+ char wd_tmp_file_name[32];
+
+ sprintf (wd_tmp_file_name, "%lu", get_next_wd_counter ());
+
+ ret = mkdirat (lo->workdir_fd, wd_tmp_file_name, mode);
+ if (ret < 0)
+ goto out;
+
+ ret = dfd = TEMP_FAILURE_RETRY (openat (lo->workdir_fd, wd_tmp_file_name, O_RDONLY));
+ if (ret < 0)
+ goto out;
+
+ ret = fchown (dfd, uid, gid);
+ if (ret < 0)
+ goto out;
+
+ if (times)
+ {
+ ret = futimens (dfd, times);
+ if (ret < 0)
+ goto out;
+ }
+
+ if (ret == 0 && xattr_sfd >= 0)
+ {
+ const size_t buf_size = 1 << 20;
+ char *buf = malloc (buf_size);
+ if (buf == NULL)
+ {
+ ret = -1;
+ goto out;
+ }
+
+ ret = copy_xattr (xattr_sfd, dfd, buf, buf_size);
+ if (ret < 0)
+ goto out;
+ }
+
+ unlinkat (dirfd, name, 0);
+
+ ret = renameat (lo->workdir_fd, wd_tmp_file_name, dirfd, name);
+ if (ret < 0)
+ {
+ if (errno == ENOENT && parent)
+ {
+ ret = create_node_directory (lo, parent);
+ if (ret != 0)
+ goto out;
+ }
+
+ ret = renameat (lo->workdir_fd, wd_tmp_file_name, dirfd, name);
+ }
+out:
+ if (dfd >= 0)
+ close (dfd);
+ if (buf)
+ free (buf);
+
+ if (ret < 0)
+ unlinkat (lo->workdir_fd, wd_tmp_file_name, AT_REMOVEDIR);
+
+ return ret;
+}
+
+static int
+create_node_directory (struct ovl_data *lo, struct ovl_node *src)
+{
+ int ret;
+ struct stat st;
+ int sfd = -1;
+ struct timespec times[2];
+
+ if (src == NULL)
+ return 0;
+
+ if (src->layer == get_upper_layer (lo))
+ return 0;
+
+ ret = sfd = TEMP_FAILURE_RETRY (openat (node_dirfd (src), src->path, O_RDONLY|O_NONBLOCK));
+ if (ret < 0)
+ return ret;
+
+ ret = TEMP_FAILURE_RETRY (fstat (sfd, &st));
+ if (ret < 0)
+ return ret;
+
+ times[0] = st.st_atim;
+ times[1] = st.st_mtim;
+
+ ret = create_directory (lo, get_upper_layer (lo)->fd, src->path, times, src->parent, sfd, st.st_uid, st.st_gid, st.st_mode);
+
+ close (sfd);
+
+ if (ret == 0)
+ {
+ src->layer = get_upper_layer (lo);
+
+ if (src->parent)
+ delete_whiteout (lo, -1, src->parent, src->name);
+ }
+
+ return ret;
+}
+
+static int
+copyup (struct ovl_data *lo, struct ovl_node *node)
+{
+ int saved_errno;
+ int ret = -1;
+ int dfd = -1, sfd = -1;
+ struct stat st;
+ const size_t buf_size = 1 << 20;
+ char *buf = NULL;
+ struct timespec times[2];
+ char wd_tmp_file_name[32];
+
+ sprintf (wd_tmp_file_name, "%lu", get_next_wd_counter ());
+
+ ret = TEMP_FAILURE_RETRY (fstatat (node_dirfd (node), node->path, &st, AT_SYMLINK_NOFOLLOW));
+ if (ret < 0)
+ return ret;
+
+ if (node->parent)
+ {
+ ret = create_node_directory (lo, node->parent);
+ if (ret < 0)
+ return ret;
+ }
+
+ if ((st.st_mode & S_IFMT) == S_IFDIR)
+ {
+ ret = create_node_directory (lo, node);
+ if (ret < 0)
+ goto exit;
+ goto success;
+ }
+
+ if ((st.st_mode & S_IFMT) == S_IFLNK)
+ {
+ char p[PATH_MAX + 1];
+ ret = readlinkat (node_dirfd (node), node->path, p, sizeof (p) - 1);
+ if (ret < 0)
+ goto exit;
+ p[ret] = '\0';
+ ret = symlinkat (p, get_upper_layer (lo)->fd, node->path);
+ if (ret < 0)
+ goto exit;
+ goto success;
+ }
+
+ sfd = TEMP_FAILURE_RETRY (openat (node_dirfd (node), node->path, O_RDONLY|O_NONBLOCK));
+ if (sfd < 0)
+ goto exit;
+
+ dfd = TEMP_FAILURE_RETRY (openat (lo->workdir_fd, wd_tmp_file_name, O_CREAT|O_WRONLY, st.st_mode));
+ if (dfd < 0)
+ goto exit;
+
+ ret = fchown (dfd, st.st_uid, st.st_gid);
+ if (ret < 0)
+ goto exit;
+
+ buf = malloc (buf_size);
+ if (buf == NULL)
+ goto exit;
+ for (;;)
+ {
+ int written;
+ int nread;
+
+ nread = TEMP_FAILURE_RETRY (read (sfd, buf, buf_size));
+ if (nread < 0)
+ goto exit;
+
+ if (nread == 0)
+ break;
+
+ written = 0;
+ {
+ ret = TEMP_FAILURE_RETRY (write (dfd, buf + written, nread));
+ if (ret < 0)
+ goto exit;
+ written += ret;
+ nread -= ret;
+ }
+ while (nread);
+ }
+
+ times[0] = st.st_atim;
+ times[1] = st.st_mtim;
+ ret = futimens (dfd, times);
+ if (ret < 0)
+ goto exit;
+
+ ret = copy_xattr (sfd, dfd, buf, buf_size);
+ if (ret < 0)
+ goto exit;
+
+ /* Finally, move the file to its destination. */
+ ret = renameat (lo->workdir_fd, wd_tmp_file_name, get_upper_layer (lo)->fd, node->path);
+ if (ret < 0)
+ goto exit;
+
+ if (node->parent)
+ {
+ char whpath[PATH_MAX + 10];
+ sprintf (whpath, "%s/.wh.%s", node->parent->path, node->name);
+ if (unlinkat (get_upper_layer (lo)->fd, whpath, 0) < 0 && errno != ENOENT)
+ goto exit;
+ }
+
+ success:
+ ret = 0;
+
+ node->layer = get_upper_layer (lo);
+
+ exit:
+ saved_errno = errno;
+ free (buf);
+ if (sfd >= 0)
+ close (sfd);
+ if (dfd >= 0)
+ close (dfd);
+ if (ret < 0)
+ unlinkat (lo->workdir_fd, wd_tmp_file_name, 0);
+ errno = saved_errno;
+
+ return ret;
+}
+
+static struct ovl_node *
+get_node_up (struct ovl_data *lo, struct ovl_node *node)
+{
+ int ret;
+
+ if (node->layer == get_upper_layer (lo))
+ return node;
+
+ ret = copyup (lo, node);
+ if (ret < 0)
+ return NULL;
+
+ assert (node->layer == get_upper_layer (lo));
+
+ return node;
+}
+
+static size_t
+count_dir_entries (struct ovl_node *node, size_t *whiteouts)
+{
+ size_t c = 0;
+ struct ovl_node *it;
+
+ if (whiteouts)
+ *whiteouts = 0;
+
+ for (it = hash_get_first (node->children); it; it = hash_get_next (node->children, it))
+ {
+ if (it->whiteout)
+ {
+ if (whiteouts)
+ (*whiteouts)++;
+ continue;
+ }
+ if (strcmp (it->name, ".") == 0)
+ continue;
+ if (strcmp (it->name, "..") == 0)
+ continue;
+ c++;
+ }
+ return c;
+}
+
+static int
+update_paths (struct ovl_node *node)
+{
+ struct ovl_node *it;
+
+ if (node == NULL)
+ return 0;
+
+ if (node->parent)
+ {
+ free (node->path);
+ if (asprintf (&node->path, "%s/%s", node->parent->path, node->name) < 0)
+ {
+ node->path = NULL;
+ return -1;
+ }
+ }
+
+ if (node->children)
+ {
+ for (it = hash_get_first (node->children); it; it = hash_get_next (node->children, it))
+ {
+ if (update_paths (it) < 0)
+ return -1;
+ }
+ }
+
+ return 0;
+}
+
+static int
+empty_dir (struct ovl_data *lo, struct ovl_node *node)
+{
+ DIR *dp;
+ int fd;
+ struct dirent *dent;
+
+ fd = TEMP_FAILURE_RETRY (openat (get_upper_layer (lo)->fd, node->path, O_DIRECTORY));
+ if (fd < 0)
+ return -1;
+
+ if (set_fd_opaque (fd) < 0)
+ {
+ close (fd);
+ return -1;
+ }
+
+ dp = fdopendir (fd);
+ if (dp == NULL)
+ {
+ close (fd);
+ return -1;
+ }
+
+ for (;;)
+ {
+ errno = 0;
+ dent = readdir (dp);
+ if (dent == NULL)
+ {
+ if (errno)
+ {
+ int saved_errno = errno;
+ closedir (dp);
+ errno = saved_errno;
+ return -1;
+ }
+
+ break;
+ }
+ if (strcmp (dent->d_name, ".") == 0)
+ continue;
+ if (strcmp (dent->d_name, "..") == 0)
+ continue;
+ if (unlinkat (dirfd (dp), dent->d_name, 0) < 0)
+ unlinkat (dirfd (dp), dent->d_name, AT_REMOVEDIR);
+ }
+
+ closedir (dp);
+
+ return 0;
+}
+
+static void
+do_rm (fuse_req_t req, fuse_ino_t parent, const char *name, bool dirp)
+{
+ struct ovl_node *node;
+ struct ovl_data *lo = ovl_data (req);
+ struct ovl_node *pnode;
+ int ret = 0;
+ struct ovl_node key, *rm;
+
+ node = do_lookup_file (lo, parent, name);
+ if (node == NULL)
+ {
+ fuse_reply_err (req, ENOENT);
+ return;
+ }
+
+ if (dirp)
+ {
+ size_t c;
+
+ /* Re-load the directory. */
+ node = load_dir (lo, node, node->layer, node->path, node->name);
+ if (node == NULL)
+ {
+ fuse_reply_err (req, errno);
+ return;
+ }
+
+ c = count_dir_entries (node, NULL);
+ if (c)
+ {
+ fuse_reply_err (req, ENOTEMPTY);
+ return;
+ }
+ }
+
+ if (node->layer == get_upper_layer (lo))
+ {
+ node->hidden_dirfd = node->layer->fd;
+
+ if (! dirp)
+ node->do_unlink = 1;
+ else
+ {
+ if (empty_dir (lo, node) < 0)
+ {
+ fuse_reply_err (req, errno);
+ return;
+ }
+
+ node->do_rmdir = 1;
+ }
+ }
+
+ pnode = do_lookup_file (lo, parent, NULL);
+ if (pnode == NULL)
+ {
+ fuse_reply_err (req, ENOENT);
+ return;
+ }
+
+ pnode = get_node_up (lo, pnode);
+ if (pnode == NULL)
+ {
+ fuse_reply_err (req, ENOENT);
+ return;
+ }
+
+ /* If the node is still accessible then be sure we,
+ can write to it. Fix it to be done when a write is
+ really done, not now. */
+ node = get_node_up (lo, node);
+ if (node == NULL)
+ {
+ fuse_reply_err (req, errno);
+ return;
+ }
+
+ key.name = (char *) name;
+ rm = hash_delete (pnode->children, &key);
+ if (rm)
+ {
+ ret = hide_node (lo, rm, true);
+ if (ret < 0)
+ {
+ fuse_reply_err (req, errno);
+ return;
+ }
+
+ node_free (rm);
+ }
+
+ fuse_reply_err (req, ret);
+}
+
+static void
+ovl_unlink (fuse_req_t req, fuse_ino_t parent, const char *name)
+{
+ if (ovl_debug (req))
+ fprintf (stderr, "ovl_unlink(parent=%" PRIu64 ", name=%s)\n",
+ parent, name);
+ do_rm (req, parent, name, false);
+}
+
+static void
+ovl_rmdir (fuse_req_t req, fuse_ino_t parent, const char *name)
+{
+ if (ovl_debug (req))
+ fprintf (stderr, "ovl_rmdir(parent=%" PRIu64 ", name=%s)\n",
+ parent, name);
+ do_rm (req, parent, name, true);
+}
+
+static void
+ovl_setxattr (fuse_req_t req, fuse_ino_t ino, const char *name,
+ const char *value, size_t size, int flags)
+{
+ struct ovl_data *lo = ovl_data (req);
+ struct ovl_node *node;
+ int fd;
+
+ if (ovl_debug (req))
+ fprintf (stderr, "ovl_setxattr(ino=%" PRIu64 "s, name=%s, value=%s, size=%zu, flags=%d)\n", ino, name,
+ value, size, flags);
+
+ if (has_prefix (name, PRIVILEGED_XATTR_PREFIX) || has_prefix (name, XATTR_PREFIX))
+ {
+ fuse_reply_err (req, EPERM);
+ return;
+ }
+
+ node = do_lookup_file (lo, ino, NULL);
+ if (node == NULL)
+ {
+ fuse_reply_err (req, ENOENT);
+ return;
+ }
+
+ node = get_node_up (lo, node);
+ if (node == NULL)
+ {
+ fuse_reply_err (req, errno);
+ return;
+ }
+
+ fd = TEMP_FAILURE_RETRY (openat (node_dirfd (node), node->path, O_NONBLOCK));
+ if (fd < 0)
+ {
+ fuse_reply_err (req, errno);
+ return;
+ }
+
+ if (fsetxattr (fd, name, value, size, flags) < 0)
+ {
+ fuse_reply_err (req, errno);
+ close (fd);
+ return;
+ }
+ close (fd);
+ fuse_reply_err (req, 0);
+}
+
+static void
+ovl_removexattr (fuse_req_t req, fuse_ino_t ino, const char *name)
+{
+ struct ovl_node *node;
+ struct ovl_data *lo = ovl_data (req);
+ int fd;
+
+ if (ovl_debug (req))
+ fprintf (stderr, "ovl_removexattr(ino=%" PRIu64 "s, name=%s)\n", ino, name);
+
+ node = do_lookup_file (lo, ino, NULL);
+ if (node == NULL)
+ {
+ fuse_reply_err (req, ENOENT);
+ return;
+ }
+
+ node = get_node_up (lo, node);
+ if (node == NULL)
+ {
+ fuse_reply_err (req, errno);
+ return;
+ }
+
+ fd = TEMP_FAILURE_RETRY (openat (node_dirfd (node), node->path, O_NONBLOCK));
+ if (fd < 0)
+ {
+ fuse_reply_err (req, errno);
+ return;
+ }
+
+ if (fremovexattr (fd, name) < 0)
+ {
+ close (fd);
+ fuse_reply_err (req, errno);
+ return;
+ }
+
+ close (fd);
+ fuse_reply_err (req, 0);
+}
+
+static int
+ovl_do_open (fuse_req_t req, fuse_ino_t parent, const char *name, int flags, mode_t mode)
+{
+ struct ovl_data *lo = ovl_data (req);
+ struct ovl_node *n;
+ bool readonly = (flags & (O_APPEND | O_RDWR | O_WRONLY | O_CREAT | O_TRUNC)) == 0;
+ char path[PATH_MAX + 10];
+ int fd;
+
+ flags |= O_NOFOLLOW;
+
+ if (name && has_prefix (name, ".wh."))
+ {
+ errno = EINVAL;
+ return - 1;
+ }
+
+ n = do_lookup_file (lo, parent, name);
+ if (n && n->hidden)
+ {
+ n = NULL;
+ }
+ if (n && !n->whiteout && (flags & O_CREAT))
+ {
+ errno = EEXIST;
+ return -1;
+ }
+
+ if (!n)
+ {
+ struct ovl_node *p;
+ const struct fuse_ctx *ctx = fuse_req_ctx (req);
+ char wd_tmp_file_name[32];
+
+ if ((flags & O_CREAT) == 0)
+ {
+ errno = ENOENT;
+ return -1;
+ }
+
+ p = do_lookup_file (lo, parent, NULL);
+ if (p == NULL)
+ {
+ errno = ENOENT;
+ return -1;
+ }
+
+ p = get_node_up (lo, p);
+ if (p == NULL)
+ return -1;
+
+ if (delete_whiteout (lo, -1, p, name) < 0)
+ return -1;
+
+ sprintf (path, "%s/%s", p->path, name);
+ if (unlinkat (get_upper_layer (lo)->fd, path, 0) < 0 && errno != ENOENT)
+ return -1;
+
+ sprintf (wd_tmp_file_name, "%lu", get_next_wd_counter ());
+
+ fd = TEMP_FAILURE_RETRY (openat (lo->workdir_fd, wd_tmp_file_name, flags, mode));
+ if (fd < 0)
+ return -1;
+
+ if (fchown (fd, get_uid (lo, ctx->uid), get_gid (lo, ctx->gid)) < 0)
+ {
+ unlinkat (lo->workdir_fd, wd_tmp_file_name, 0);
+ return -1;
+ }
+
+ if (renameat (lo->workdir_fd, wd_tmp_file_name, get_upper_layer (lo)->fd, path) < 0)
+ {
+ unlinkat (lo->workdir_fd, wd_tmp_file_name, 0);
+ return -1;
+ }
+
+ n = make_ovl_node (path, get_upper_layer (lo), name, 0, false, p);
+ if (n == NULL)
+ {
+ errno = ENOMEM;
+ close (fd);
+ return -1;
+ }
+ n = insert_node (p, n, true);
+ if (n == NULL)
+ {
+ errno = ENOMEM;
+ close (fd);
+ return -1;
+ }
+ return fd;
+ }
+
+ /* readonly, we can use both lowerdir and upperdir. */
+ if (readonly)
+ {
+ return TEMP_FAILURE_RETRY (openat (node_dirfd (n), n->path, flags, mode));
+ }
+ else
+ {
+ n = get_node_up (lo, n);
+ if (n == NULL)
+ return -1;
+
+ return TEMP_FAILURE_RETRY (openat (node_dirfd (n), n->path, flags, mode));
+ }
+}
+
+static void
+ovl_read (fuse_req_t req, fuse_ino_t ino, size_t size,
+ off_t offset, struct fuse_file_info *fi)
+{
+ struct fuse_bufvec buf = FUSE_BUFVEC_INIT (size);
+ if (ovl_debug (req))
+ fprintf (stderr, "ovl_read(ino=%" PRIu64 ", size=%zd, "
+ "off=%lu)\n", ino, size, (unsigned long) offset);
+ buf.buf[0].flags = FUSE_BUF_IS_FD | FUSE_BUF_FD_SEEK;
+ buf.buf[0].fd = fi->fh;
+ buf.buf[0].pos = offset;
+ fuse_reply_data (req, &buf, FUSE_BUF_SPLICE_MOVE);
+}
+
+static void
+ovl_write_buf (fuse_req_t req, fuse_ino_t ino,
+ struct fuse_bufvec *in_buf, off_t off,
+ struct fuse_file_info *fi)
+{
+ (void) ino;
+ ssize_t res;
+ struct fuse_bufvec out_buf = FUSE_BUFVEC_INIT (fuse_buf_size (in_buf));
+ out_buf.buf[0].flags = FUSE_BUF_IS_FD | FUSE_BUF_FD_SEEK;
+ out_buf.buf[0].fd = fi->fh;
+ out_buf.buf[0].pos = off;
+
+ if (ovl_debug (req))
+ fprintf (stderr, "ovl_write_buf(ino=%" PRIu64 ", size=%zd, off=%lu, fd=%d)\n",
+ ino, out_buf.buf[0].size, (unsigned long) off, (int) fi->fh);
+
+ errno = 0;
+ res = fuse_buf_copy (&out_buf, in_buf, 0);
+ if (res < 0)
+ fuse_reply_err (req, errno);
+ else
+ fuse_reply_write (req, (size_t) res);
+}
+
+static void
+ovl_release (fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi)
+{
+ (void) ino;
+ close (fi->fh);
+ fuse_reply_err (req, 0);
+}
+
+static int
+do_getattr (fuse_req_t req, struct fuse_entry_param *e, struct ovl_node *node)
+{
+ int err = 0;
+
+ memset (e, 0, sizeof (*e));
+
+ err = rpl_stat (req, node, &e->attr);
+ if (err < 0)
+ return err;
+
+ e->ino = (fuse_ino_t) node;
+ e->attr_timeout = ATTR_TIMEOUT;
+ e->entry_timeout = ENTRY_TIMEOUT;
+
+ return 0;
+}
+
+static void
+ovl_create (fuse_req_t req, fuse_ino_t parent, const char *name,
+ mode_t mode, struct fuse_file_info *fi)
+{
+ int fd;
+ struct fuse_entry_param e;
+ struct ovl_data *lo = ovl_data (req);
+ struct ovl_node *node;
+
+ if (ovl_debug (req))
+ fprintf (stderr, "ovl_create(parent=%" PRIu64 ", name=%s)\n",
+ parent, name);
+
+ fi->flags = fi->flags | O_CREAT;
+
+ fd = ovl_do_open (req, parent, name, fi->flags, mode);
+ if (fd < 0)
+ {
+ fuse_reply_err (req, errno);
+ return;
+ }
+
+ node = do_lookup_file (lo, parent, name);
+ if (node == NULL || do_getattr (req, &e, node) < 0)
+ {
+ close (fd);
+ fuse_reply_err (req, errno);
+ return;
+ }
+ fi->fh = fd;
+
+ node->lookups++;
+ fuse_reply_create (req, &e, fi);
+}
+
+static void
+ovl_open (fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi)
+{
+ int fd;
+
+ if (ovl_debug (req))
+ fprintf (stderr, "ovl_open(ino=%" PRIu64 "s)\n", ino);
+
+ fd = ovl_do_open (req, ino, NULL, fi->flags, 0700);
+ if (fd < 0)
+ {
+ fuse_reply_err (req, errno);
+ return;
+ }
+ fi->fh = fd;
+ fuse_reply_open (req, fi);
+}
+
+static void
+ovl_getattr (fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi)
+{
+ struct ovl_data *lo = ovl_data (req);
+ struct ovl_node *node;
+ struct fuse_entry_param e;
+
+ if (ovl_debug (req))
+ fprintf (stderr, "ovl_getattr(ino=%" PRIu64 "s)\n", ino);
+
+ node = do_lookup_file (lo, ino, NULL);
+ if (node == NULL)
+ {
+ fuse_reply_err (req, ENOENT);
+ return;
+ }
+
+ if (do_getattr (req, &e, node) < 0)
+ {
+ fuse_reply_err (req, errno);
+ return;
+ }
+
+ fuse_reply_attr (req, &e.attr, ENTRY_TIMEOUT);
+}
+
+static void
+ovl_setattr (fuse_req_t req, fuse_ino_t ino, struct stat *attr, int to_set, struct fuse_file_info *fi)
+{
+ struct ovl_data *lo = ovl_data (req);
+ struct ovl_node *node;
+ struct fuse_entry_param e;
+ struct stat old_st;
+ struct timespec times[2];
+ uid_t uid;
+ gid_t gid;
+ int dirfd;
+
+ if (ovl_debug (req))
+ fprintf (stderr, "ovl_setattr(ino=%" PRIu64 "s, to_set=%d)\n", ino, to_set);
+
+ node = do_lookup_file (lo, ino, NULL);
+ if (node == NULL)
+ {
+ fuse_reply_err (req, ENOENT);
+ return;
+ }
+
+ node = get_node_up (lo, node);
+ if (node == NULL)
+ {
+ fuse_reply_err (req, errno);
+ return;
+ }
+
+ dirfd = node_dirfd (node);
+
+ if (TEMP_FAILURE_RETRY (fstatat (dirfd, node->path, &old_st, AT_SYMLINK_NOFOLLOW)) < 0)
+ {
+ fuse_reply_err (req, errno);
+ return;
+ }
+
+ if (to_set & FUSE_SET_ATTR_CTIME)
+ {
+ fuse_reply_err (req, EPERM);
+ return;
+ }
+
+ memset (times, 0, sizeof (times));
+ times[0].tv_sec = UTIME_OMIT;
+ times[1].tv_sec = UTIME_OMIT;
+ if (to_set & FUSE_SET_ATTR_ATIME)
+ times[0] = attr->st_atim;
+ else if (to_set & FUSE_SET_ATTR_ATIME_NOW)
+ times[0].tv_sec = UTIME_NOW;
+
+ if (to_set & FUSE_SET_ATTR_MTIME)
+ times[1] = attr->st_mtim;
+ else if (to_set & FUSE_SET_ATTR_MTIME_NOW)
+ times[1].tv_sec = UTIME_NOW;
+
+ if (times[0].tv_sec != UTIME_OMIT || times[1].tv_sec != UTIME_OMIT)
+ {
+ if ((utimensat (dirfd, node->path, times, AT_SYMLINK_NOFOLLOW) < 0))
+ {
+ fuse_reply_err (req, errno);
+ return;
+ }
+ }
+
+ if ((to_set & FUSE_SET_ATTR_MODE) && fchmodat (dirfd, node->path, attr->st_mode, 0) < 0)
+ {
+ fuse_reply_err (req, errno);
+ return;
+ }
+ if ((to_set & FUSE_SET_ATTR_SIZE))
+ {
+ int fd = TEMP_FAILURE_RETRY (openat (dirfd, node->path, O_WRONLY|O_NONBLOCK));
+ if (fd < 0)
+ {
+ fuse_reply_err (req, errno);
+ return;
+ }
+
+ if (ftruncate (fd, attr->st_size) < 0)
+ {
+ close (fd);
+ fuse_reply_err (req, errno);
+ return;
+ }
+ close (fd);
+ }
+
+ uid = old_st.st_uid;
+ gid = old_st.st_gid;
+ if (to_set & FUSE_SET_ATTR_UID)
+ uid = get_uid (lo, attr->st_uid);
+ if (to_set & FUSE_SET_ATTR_GID)
+ gid = get_gid (lo, attr->st_gid);
+
+ if (uid != old_st.st_uid || gid != old_st.st_gid)
+ {
+ if (fchownat (dirfd, node->path, uid, gid, AT_SYMLINK_NOFOLLOW) < 0)
+ {
+ fuse_reply_err (req, errno);
+ return;
+ }
+ }
+
+ if (do_getattr (req, &e, node) < 0)
+ {
+ fuse_reply_err (req, errno);
+ return;
+ }
+
+ fuse_reply_attr (req, &e.attr, ENTRY_TIMEOUT);
+}
+
+static void
+ovl_link (fuse_req_t req, fuse_ino_t ino, fuse_ino_t newparent, const char *newname)
+{
+ struct ovl_data *lo = ovl_data (req);
+ struct ovl_node *node, *newparentnode, *destnode;
+ char path[PATH_MAX + 10];
+ int ret;
+ struct fuse_entry_param e;
+ char wd_tmp_file_name[32];
+
+ if (ovl_debug (req))
+ fprintf (stderr, "ovl_link(ino=%" PRIu64 "s, newparent=%" PRIu64 "s, newname=%s)\n", ino, newparent, newname);
+
+ node = do_lookup_file (lo, ino, NULL);
+ if (node == NULL)
+ {
+ fuse_reply_err (req, ENOENT);
+ return;
+ }
+
+ node = get_node_up (lo, node);
+ if (node == NULL)
+ {
+ fuse_reply_err (req, errno);
+ return;
+ }
+
+ newparentnode = do_lookup_file (lo, newparent, NULL);
+ if (newparentnode == NULL)
+ {
+ fuse_reply_err (req, ENOENT);
+ return;
+ }
+
+ destnode = do_lookup_file (lo, newparent, newname);
+ if (destnode && !destnode->whiteout)
+ {
+ fuse_reply_err (req, EEXIST);
+ return;
+ }
+
+ newparentnode = get_node_up (lo, newparentnode);
+ if (newparentnode == NULL)
+ {
+ fuse_reply_err (req, errno);
+ return;
+ }
+
+ if (delete_whiteout (lo, -1, newparentnode, newname) < 0)
+ {
+ fuse_reply_err (req, errno);
+ return;
+ }
+
+ sprintf (wd_tmp_file_name, "%lu", get_next_wd_counter ());
+
+ sprintf (path, "%s/%s", newparentnode->path, newname);
+
+ if (linkat (node_dirfd (newparentnode), node->path, lo->workdir_fd, wd_tmp_file_name, 0) < 0)
+ {
+ fuse_reply_err (req, errno);
+ return;
+ }
+
+ if (renameat (lo->workdir_fd, wd_tmp_file_name, node_dirfd (newparentnode), path) < 0)
+ {
+ fuse_reply_err (req, errno);
+ return;
+ }
+ else
+ {
+ int dfd = TEMP_FAILURE_RETRY (openat (node_dirfd (newparentnode), path, O_WRONLY|O_NONBLOCK));
+ if (dfd >= 0)
+ {
+ bool set = false;
+ int sfd = TEMP_FAILURE_RETRY (openat (node_dirfd (node), node->path, O_RDONLY|O_NONBLOCK));
+ if (sfd >= 0)
+ {
+ char origin_path[PATH_MAX + 10];
+ size_t s;
+
+ s = fgetxattr (sfd, PRIVILEGED_ORIGIN_XATTR, origin_path, sizeof (origin_path));
+ if (s > 0)
+ fsetxattr (dfd, PRIVILEGED_ORIGIN_XATTR, origin_path, s, 0);
+
+ s = fgetxattr (sfd, ORIGIN_XATTR, origin_path, sizeof (origin_path));
+ if (s > 0)
+ set = fsetxattr (dfd, ORIGIN_XATTR, origin_path, s, 0) == 0;
+
+ close (sfd);
+ }
+
+ if (! set)
+ fsetxattr (dfd, ORIGIN_XATTR, node->path, strlen (node->path), 0);
+ close (dfd);
+ }
+ }
+
+ node = make_ovl_node (path, get_upper_layer (lo), newname, node->ino, false, newparentnode);
+ if (node == NULL)
+ {
+ fuse_reply_err (req, ENOMEM);
+ return;
+ }
+
+ node = insert_node (newparentnode, node, true);
+ if (node == NULL)
+ {
+ fuse_reply_err (req, ENOMEM);
+ return;
+ }
+
+ memset (&e, 0, sizeof (e));
+
+ ret = rpl_stat (req, node, &e.attr);
+ if (ret)
+ {
+ fuse_reply_err (req, errno);
+ return;
+ }
+
+ e.ino = NODE_TO_INODE (node);
+ node->lookups++;
+ e.attr_timeout = ATTR_TIMEOUT;
+ e.entry_timeout = ENTRY_TIMEOUT;
+ fuse_reply_entry (req, &e);
+}
+
+static void
+ovl_symlink (fuse_req_t req, const char *link, fuse_ino_t parent, const char *name)
+{
+ struct ovl_data *lo = ovl_data (req);
+ struct ovl_node *pnode, *node;
+ char path[PATH_MAX + 10];
+ int ret;
+ struct fuse_entry_param e;
+
+ if (ovl_debug (req))
+ fprintf (stderr, "ovl_symlink(link=%s, ino=%" PRIu64 "s, name=%s)\n", link, parent, name);
+
+ pnode = do_lookup_file (lo, parent, NULL);
+ if (pnode == NULL)
+ {
+ fuse_reply_err (req, ENOENT);
+ return;
+ }
+
+ pnode = get_node_up (lo, pnode);
+ if (pnode == NULL)
+ {
+ fuse_reply_err (req, errno);
+ return;
+ }
+
+ node = do_lookup_file (lo, parent, name);
+ if (node != NULL && !node->whiteout)
+ {
+ fuse_reply_err (req, EEXIST);
+ return;
+ }
+
+ if (delete_whiteout (lo, -1, pnode, name) < 0)
+ {
+ fuse_reply_err (req, errno);
+ return;
+ }
+
+ sprintf (path, "%s/%s", pnode->path, name);
+ ret = symlinkat (link, get_upper_layer (lo)->fd, path);
+ if (ret < 0)
+ {
+ fuse_reply_err (req, errno);
+ return;
+ }
+
+ node = make_ovl_node (path, get_upper_layer (lo), name, 0, false, pnode);
+ if (node == NULL)
+ {
+ fuse_reply_err (req, ENOMEM);
+ return;
+ }
+
+ node = insert_node (pnode, node, true);
+ if (node == NULL)
+ {
+ fuse_reply_err (req, ENOMEM);
+ return;
+ }
+
+ memset (&e, 0, sizeof (e));
+
+ ret = rpl_stat (req, node, &e.attr);
+ if (ret)
+ {
+ fuse_reply_err (req, errno);
+ return;
+ }
+
+ e.ino = NODE_TO_INODE (node);
+ node->lookups++;
+ e.attr_timeout = ATTR_TIMEOUT;
+ e.entry_timeout = ENTRY_TIMEOUT;
+ fuse_reply_entry (req, &e);
+}
+
+static void
+ovl_flock (fuse_req_t req, fuse_ino_t ino,
+ struct fuse_file_info *fi, int op)
+{
+ int ret, fd;
+
+ if (ovl_debug (req))
+ fprintf (stderr, "ovl_flock(ino=%" PRIu64 "s, op=%d)\n", ino, op);
+
+ fd = fi->fh;
+
+ ret = flock (fd, op);
+
+ fuse_reply_err (req, ret == 0 ? 0 : errno);
+}
+
+static void
+ovl_rename_exchange (fuse_req_t req, fuse_ino_t parent, const char *name,
+ fuse_ino_t newparent, const char *newname,
+ unsigned int flags)
+{
+ struct ovl_node *pnode, *node, *destnode, *destpnode;
+ struct ovl_data *lo = ovl_data (req);
+ int ret;
+ int saved_errno;
+ int srcfd = -1;
+ int destfd = -1;
+ struct ovl_node *rm1, *rm2;
+ char *tmp;
+
+ node = do_lookup_file (lo, parent, name);
+ if (node == NULL)
+ {
+ fuse_reply_err (req, ENOENT);
+ return;
+ }
+
+ if (node_dirp (node))
+ {
+ node = load_dir (lo, node, node->layer, node->path, node->name);
+ if (node == NULL)
+ {
+ fuse_reply_err (req, errno);
+ return;
+ }
+
+ if (node->layer != get_upper_layer (lo) || node->present_lowerdir)
+ {
+ fuse_reply_err (req, EXDEV);
+ return;
+ }
+ }
+ pnode = node->parent;
+
+ destpnode = do_lookup_file (lo, newparent, NULL);
+ destnode = NULL;
+
+ pnode = get_node_up (lo, pnode);
+ if (pnode == NULL)
+ goto error;
+
+ ret = TEMP_FAILURE_RETRY (openat (node_dirfd (pnode), pnode->path, O_DIRECTORY));
+ if (ret < 0)
+ goto error;
+ srcfd = ret;
+
+ destpnode = get_node_up (lo, destpnode);
+ if (destpnode == NULL)
+ goto error;
+
+ ret = TEMP_FAILURE_RETRY (openat (node_dirfd (destpnode), destpnode->path, O_DIRECTORY));
+ if (ret < 0)
+ goto error;
+ destfd = ret;
+
+ destnode = do_lookup_file (lo, newparent, newname);
+
+ node = get_node_up (lo, node);
+ if (node == NULL)
+ goto error;
+
+ if (destnode == NULL)
+ {
+ errno = ENOENT;
+ goto error;
+ }
+ if (node_dirp (node) && destnode->present_lowerdir)
+ {
+ fuse_reply_err (req, EXDEV);
+ return;
+ }
+ destnode = get_node_up (lo, destnode);
+ if (destnode == NULL)
+ goto error;
+
+
+ ret = syscall (SYS_renameat2, srcfd, name, destfd, newname, flags);
+ if (ret < 0)
+ goto error;
+
+ rm1 = hash_delete (destpnode->children, destnode);
+ rm2 = hash_delete (pnode->children, node);
+
+ tmp = node->path;
+ node->path = destnode->path;
+ destnode->path = tmp;
+
+ tmp = node->name;
+ node->name = destnode->name;
+ destnode->name = tmp;
+
+ node = insert_node (destpnode, node, true);
+ if (node == NULL)
+ {
+ node_free (rm1);
+ node_free (rm2);
+ goto error;
+ }
+ destnode = insert_node (pnode, destnode, true);
+ if (destnode == NULL)
+ {
+ node_free (rm1);
+ node_free (rm2);
+ goto error;
+ }
+ if ((update_paths (node) < 0) || (update_paths (destnode) < 0))
+ goto error;
+
+ if (delete_whiteout (lo, destfd, NULL, newname) < 0)
+ goto error;
+
+ ret = 0;
+ goto cleanup;
+
+ error:
+ ret = -1;
+
+ cleanup:
+ saved_errno = errno;
+ if (srcfd >= 0)
+ close (srcfd);
+ if (destfd >= 0)
+ close (destfd);
+ errno = saved_errno;
+
+ fuse_reply_err (req, ret == 0 ? 0 : errno);
+}
+
+static void
+ovl_rename_direct (fuse_req_t req, fuse_ino_t parent, const char *name,
+ fuse_ino_t newparent, const char *newname,
+ unsigned int flags)
+{
+ struct ovl_node *pnode, *node, *destnode, *destpnode;
+ struct ovl_data *lo = ovl_data (req);
+ int ret;
+ int saved_errno;
+ int srcfd = -1;
+ int destfd = -1;
+ struct ovl_node key;
+ bool destnode_is_whiteout = false;
+
+ node = do_lookup_file (lo, parent, name);
+ if (node == NULL || node->whiteout)
+ {
+ fuse_reply_err (req, ENOENT);
+ return;
+ }
+
+ if (node_dirp (node))
+ {
+ node = load_dir (lo, node, node->layer, node->path, node->name);
+ if (node == NULL)
+ {
+ fuse_reply_err (req, errno);
+ return;
+ }
+
+ if (node->layer != get_upper_layer (lo) || node->present_lowerdir)
+ {
+ fuse_reply_err (req, EXDEV);
+ return;
+ }
+ }
+ pnode = node->parent;
+
+ destpnode = do_lookup_file (lo, newparent, NULL);
+ destnode = NULL;
+
+ pnode = get_node_up (lo, pnode);
+ if (pnode == NULL)
+ goto error;
+
+ ret = TEMP_FAILURE_RETRY (openat (node_dirfd (pnode), pnode->path, O_DIRECTORY));
+ if (ret < 0)
+ goto error;
+ srcfd = ret;
+
+ destpnode = get_node_up (lo, destpnode);
+ if (destpnode == NULL)
+ goto error;
+
+ ret = TEMP_FAILURE_RETRY (openat (node_dirfd (destpnode), destpnode->path, O_DIRECTORY));
+ if (ret < 0)
+ goto error;
+ destfd = ret;
+
+ key.name = (char *) newname;
+ destnode = hash_lookup (destpnode->children, &key);
+
+ node = get_node_up (lo, node);
+ if (node == NULL)
+ goto error;
+
+ if (flags & RENAME_NOREPLACE && destnode && !destnode->whiteout)
+ {
+ errno = EEXIST;
+ goto error;
+ }
+
+ if (destnode)
+ {
+ size_t destnode_whiteouts = 0;
+
+ if (!destnode->whiteout && destnode->ino == node->ino)
+ goto error;
+
+ destnode_is_whiteout = destnode->whiteout;
+
+ if (!destnode->whiteout && node_dirp (destnode))
+ {
+ destnode = load_dir (lo, destnode, destnode->layer, destnode->path, destnode->name);
+ if (destnode == NULL)
+ goto error;
+
+ if (count_dir_entries (destnode, &destnode_whiteouts) > 0)
+ {
+ errno = ENOTEMPTY;
+ goto error;
+ }
+ if (destnode_whiteouts && empty_dir (lo, destnode) < 0)
+ goto error;
+ }
+
+ if (node_dirp (node) && create_missing_whiteouts (lo, node, destnode->path) < 0)
+ goto error;
+
+ if (destnode->lookups > 0)
+ node_free (destnode);
+ else
+ {
+ node_free (destnode);
+ destnode = NULL;
+ }
+
+ if (destnode)
+ {
+ /* If the node is still accessible then be sure we
+ can write to it. Fix it to be done when a write is
+ really done, not now. */
+ destnode = get_node_up (lo, destnode);
+ if (destnode == NULL)
+ {
+ fuse_reply_err (req, errno);
+ return;
+ }
+
+ if (hide_node (lo, destnode, false) < 0)
+ goto error;
+ }
+ }
+
+ /* If the destnode is a whiteout, first attempt to EXCHANGE the source and the destination,
+ so that with one operation we get both the rename and the whiteout created. */
+ if (destnode_is_whiteout)
+ {
+ ret = syscall (SYS_renameat2, srcfd, name, destfd, newname, flags|RENAME_EXCHANGE);
+ if (ret == 0)
+ goto done;
+
+ /* If it fails for any reason, fallback to the more articulated method. */
+ }
+
+ /* If the node is a directory we must ensure there is no whiteout at the
+ destination, otherwise the renameat2 will fail. Create a .wh.$NAME style
+ whiteout file until the renameat2 is completed. */
+ if (node_dirp (node))
+ {
+ ret = create_whiteout (lo, destpnode, newname, true);
+ if (ret < 0)
+ goto error;
+ unlinkat (destfd, newname, 0);
+ }
+
+ /* Try to create the whiteout atomically, if it fails do the
+ rename+mknod separately. */
+ ret = syscall (SYS_renameat2, srcfd, name, destfd,
+ newname, flags|RENAME_WHITEOUT);
+ if (ret < 0)
+ {
+ ret = syscall (SYS_renameat2, srcfd, name, destfd, newname, flags);
+ if (ret < 0)
+ goto error;
+
+ ret = create_whiteout (lo, pnode, name, false);
+ if (ret < 0)
+ goto error;
+ }
+
+ if (delete_whiteout (lo, destfd, NULL, newname) < 0)
+ goto error;
+
+ done:
+ hash_delete (pnode->children, node);
+
+ free (node->name);
+ node->name = strdup (newname);
+ if (node->name == NULL)
+ {
+ ret = -1;
+ goto error;
+ }
+
+ node = insert_node (destpnode, node, true);
+ if (node == NULL)
+ goto error;
+ if (update_paths (node) < 0)
+ goto error;
+
+ ret = 0;
+ goto cleanup;
+
+ error:
+ ret = -1;
+
+ cleanup:
+ saved_errno = errno;
+ if (srcfd >= 0)
+ close (srcfd);
+ if (destfd >= 0)
+ close (destfd);
+ errno = saved_errno;
+
+ fuse_reply_err (req, ret == 0 ? 0 : errno);
+}
+
+static void
+ovl_rename (fuse_req_t req, fuse_ino_t parent, const char *name,
+ fuse_ino_t newparent, const char *newname,
+ unsigned int flags)
+{
+ if (ovl_debug (req))
+ fprintf (stderr, "ovl_rename(ino=%" PRIu64 "s, name=%s , ino=%" PRIu64 "s, name=%s)\n", parent, name, newparent, newname);
+
+ if (flags & RENAME_EXCHANGE)
+ ovl_rename_exchange (req, parent, name, newparent, newname, flags);
+ else
+ ovl_rename_direct (req, parent, name, newparent, newname, flags);
+}
+
+static void
+ovl_statfs (fuse_req_t req, fuse_ino_t ino)
+{
+ int ret;
+ struct statvfs sfs;
+ struct ovl_data *lo = ovl_data (req);
+
+ if (ovl_debug (req))
+ fprintf (stderr, "ovl_statfs(ino=%" PRIu64 "s)\n", ino);
+
+ ret = statvfs (lo->upperdir, &sfs);
+ if (ret < 0)
+ {
+ fuse_reply_err (req, errno);
+ return;
+ }
+ fuse_reply_statfs (req, &sfs);
+}
+
+static void
+ovl_readlink (fuse_req_t req, fuse_ino_t ino)
+{
+ struct ovl_node *node;
+ struct ovl_data *lo = ovl_data (req);
+ int ret = 0;
+ char buf[PATH_MAX + 1];
+
+ if (ovl_debug (req))
+ fprintf (stderr, "ovl_readlink(ino=%" PRIu64 "s)\n", ino);
+
+ node = do_lookup_file (lo, ino, NULL);
+ if (node == NULL)
+ {
+ fuse_reply_err (req, ENOENT);
+ return;
+ }
+
+ ret = readlinkat (node_dirfd (node), node->path, buf, sizeof (buf));
+ if (ret == -1)
+ {
+ fuse_reply_err (req, errno);
+ return;
+ }
+ if (ret == sizeof (buf))
+ {
+ fuse_reply_err (req, ENAMETOOLONG);
+ return;
+ }
+
+ buf[ret] = '\0';
+ fuse_reply_readlink (req, buf);
+}
+
+static int
+hide_all (struct ovl_data *lo, struct ovl_node *node)
+{
+ struct ovl_node **nodes;
+ size_t i, nodes_size;
+
+ node = load_dir (lo, node, node->layer, node->path, node->name);
+ if (node == NULL)
+ return -1;
+
+ nodes_size = hash_get_n_entries (node->children) + 2;
+ nodes = malloc (sizeof (struct ovl_node *) * nodes_size);
+ if (nodes == NULL)
+ return -1;
+
+ nodes_size = hash_get_entries (node->children, (void **) nodes, nodes_size);
+ for (i = 0; i < nodes_size; i++)
+ {
+ struct ovl_node *it;
+ int ret;
+
+ it = nodes[i];
+ ret = create_whiteout (lo, node, it->name, false);
+ node_free (it);
+
+ if (ret < 0)
+ {
+ free(nodes);
+ return ret;
+ }
+ }
+
+ free (nodes);
+ return 0;
+}
+
+static void
+ovl_mknod (fuse_req_t req, fuse_ino_t parent, const char *name, mode_t mode, dev_t rdev)
+{
+ struct ovl_node *node;
+ struct ovl_data *lo = ovl_data (req);
+ struct ovl_node *pnode;
+ int ret = 0;
+ char path[PATH_MAX + 10];
+ struct fuse_entry_param e;
+ const struct fuse_ctx *ctx = fuse_req_ctx (req);
+ char wd_tmp_file_name[32];
+
+ if (ovl_debug (req))
+ fprintf (stderr, "ovl_mknod(ino=%" PRIu64 ", name=%s, mode=%d, rdev=%lu)\n",
+ parent, name, mode, rdev);
+
+ node = do_lookup_file (lo, parent, name);
+ if (node != NULL && !node->whiteout)
+ {
+ fuse_reply_err (req, EEXIST);
+ return;
+ }
+
+ pnode = do_lookup_file (lo, parent, NULL);
+ if (pnode == NULL)
+ {
+ fuse_reply_err (req, ENOENT);
+ return;
+ }
+
+ pnode = get_node_up (lo, pnode);
+ if (pnode == NULL)
+ {
+ fuse_reply_err (req, errno);
+ return;
+ }
+ sprintf (wd_tmp_file_name, "%lu", get_next_wd_counter ());
+ ret = mknodat (lo->workdir_fd, wd_tmp_file_name, mode, rdev);
+ if (ret < 0)
+ {
+ fuse_reply_err (req, errno);
+ return;
+ }
+
+ if (fchownat (lo->workdir_fd, wd_tmp_file_name, get_uid (lo, ctx->uid), get_gid (lo, ctx->gid), 0) < 0)
+ {
+ unlinkat (lo->workdir_fd, wd_tmp_file_name, 0);
+ fuse_reply_err (req, errno);
+ return;
+ }
+
+ sprintf (path, "%s/%s", pnode->path, name);
+ ret = renameat (lo->workdir_fd, wd_tmp_file_name, get_upper_layer (lo)->fd, path);
+ if (ret < 0)
+ {
+ unlinkat (lo->workdir_fd, wd_tmp_file_name, 0);
+ fuse_reply_err (req, errno);
+ return;
+ }
+
+ node = make_ovl_node (path, get_upper_layer (lo), name, 0, false, pnode);
+ if (node == NULL)
+ {
+ fuse_reply_err (req, ENOMEM);
+ return;
+ }
+
+ node = insert_node (pnode, node, true);
+ if (node == NULL)
+ {
+ fuse_reply_err (req, ENOMEM);
+ return;
+ }
+
+ if (delete_whiteout (lo, -1, pnode, name) < 0)
+ {
+ fuse_reply_err (req, errno);
+ return;
+ }
+
+ memset (&e, 0, sizeof (e));
+
+ ret = rpl_stat (req, node, &e.attr);
+ if (ret)
+ {
+ fuse_reply_err (req, errno);
+ return;
+ }
+
+ e.ino = NODE_TO_INODE (node);
+ e.attr_timeout = ATTR_TIMEOUT;
+ e.entry_timeout = ENTRY_TIMEOUT;
+ node->lookups++;
+ fuse_reply_entry (req, &e);
+}
+
+static void
+ovl_mkdir (fuse_req_t req, fuse_ino_t parent, const char *name, mode_t mode)
+{
+ struct ovl_node *node;
+ struct ovl_data *lo = ovl_data (req);
+ struct ovl_node *pnode;
+ int ret = 0;
+ char path[PATH_MAX + 10];
+ struct fuse_entry_param e;
+ const struct fuse_ctx *ctx = fuse_req_ctx (req);
+
+ if (ovl_debug (req))
+ fprintf (stderr, "ovl_mkdir(ino=%" PRIu64 ", name=%s, mode=%d)\n",
+ parent, name, mode);
+
+ node = do_lookup_file (lo, parent, name);
+ if (node != NULL && !node->whiteout)
+ {
+ fuse_reply_err (req, EEXIST);
+ return;
+ }
+
+ pnode = do_lookup_file (lo, parent, NULL);
+ if (pnode == NULL)
+ {
+ fuse_reply_err (req, ENOENT);
+ return;
+ }
+
+ pnode = get_node_up (lo, pnode);
+ if (pnode == NULL)
+ {
+ fuse_reply_err (req, errno);
+ return;
+ }
+ sprintf (path, "%s/%s", pnode->path, name);
+
+ ret = create_directory (lo, get_upper_layer (lo)->fd, path, NULL, pnode, -1,
+ get_uid (lo, ctx->uid), get_gid (lo, ctx->gid), mode);
+ if (ret < 0)
+ {
+ fuse_reply_err (req, errno);
+ return;
+ }
+
+ node = make_ovl_node (path, get_upper_layer (lo), name, 0, true, pnode);
+ if (node == NULL)
+ {
+ fuse_reply_err (req, ENOMEM);
+ return;
+ }
+
+ node = insert_node (pnode, node, true);
+ if (node == NULL)
+ {
+ fuse_reply_err (req, ENOMEM);
+ return;
+ }
+ ret = hide_all (lo, node);
+ if (ret < 0)
+ {
+ fuse_reply_err (req, errno);
+ return;
+ }
+
+ if (delete_whiteout (lo, -1, pnode, name) < 0)
+ {
+ fuse_reply_err (req, errno);
+ return;
+ }
+
+ memset (&e, 0, sizeof (e));
+
+ ret = rpl_stat (req, node, &e.attr);
+ if (ret)
+ {
+ fuse_reply_err (req, errno);
+ return;
+ }
+
+ e.ino = NODE_TO_INODE (node);
+ e.attr_timeout = ATTR_TIMEOUT;
+ e.entry_timeout = ENTRY_TIMEOUT;
+ node->lookups++;
+ fuse_reply_entry (req, &e);
+}
+
+static void
+ovl_fsync (fuse_req_t req, fuse_ino_t ino, int datasync, struct fuse_file_info *fi)
+{
+ int ret, fd;
+
+ if (ovl_debug (req))
+ fprintf (stderr, "ovl_fsync(ino=%" PRIu64 ", datasync=%d, fi=%p)\n",
+ ino, datasync, fi);
+
+ fd = fi->fh;
+ ret = datasync ? fdatasync (fd) : fsync (fd);
+ fuse_reply_err (req, ret == 0 ? 0 : errno);
+}
+
+static struct fuse_lowlevel_ops ovl_oper =
+ {
+ .statfs = ovl_statfs,
+ .access = ovl_access,
+ .getxattr = ovl_getxattr,
+ .removexattr = ovl_removexattr,
+ .setxattr = ovl_setxattr,
+ .listxattr = ovl_listxattr,
+ .init = ovl_init,
+ .lookup = ovl_lookup,
+ .forget = ovl_forget,
+ .getattr = ovl_getattr,
+ .readlink = ovl_readlink,
+ .opendir = ovl_opendir,
+ .readdir = ovl_readdir,
+ .readdirplus = ovl_readdirplus,
+ .releasedir = ovl_releasedir,
+ .create = ovl_create,
+ .open = ovl_open,
+ .release = ovl_release,
+ .read = ovl_read,
+ .write_buf = ovl_write_buf,
+ .unlink = ovl_unlink,
+ .rmdir = ovl_rmdir,
+ .setattr = ovl_setattr,
+ .symlink = ovl_symlink,
+ .rename = ovl_rename,
+ .mkdir = ovl_mkdir,
+ .mknod = ovl_mknod,
+ .link = ovl_link,
+ .fsync = ovl_fsync,
+ .flock = ovl_flock,
+ };
+
+static int
+fuse_opt_proc (void *data, const char *arg, int key, struct fuse_args *outargs)
+{
+ struct ovl_data *ovl_data = data;
+
+ if (strcmp (arg, "-f") == 0)
+ return 1;
+ if (strcmp (arg, "--help") == 0)
+ return 1;
+ if (strcmp (arg, "-h") == 0)
+ return 1;
+ if (strcmp (arg, "--version") == 0)
+ return 1;
+ if (strcmp (arg, "-V") == 0)
+ return 1;
+ if (strcmp (arg, "--debug") == 0)
+ return 1;
+
+ if (strcmp (arg, "allow_root") == 0)
+ return 1;
+ if (strcmp (arg, "default_permissions") == 0)
+ return 1;
+ if (strcmp (arg, "allow_other") == 0)
+ return 1;
+
+ if (key == FUSE_OPT_KEY_NONOPT)
+ {
+ if (ovl_data->mountpoint)
+ free (ovl_data->mountpoint);
+
+ ovl_data->mountpoint = strdup (arg);
+ return 0;
+ }
+ /* Ignore unknown arguments. */
+ if (key == -1)
+ return 0;
+
+ return 1;
+}
+
+char **
+get_new_args (int *argc, char **argv)
+{
+ int i;
+ char **newargv = malloc (sizeof (char *) * (*argc + 2));
+ newargv[0] = argv[0];
+ newargv[1] = "-odefault_permissions,allow_other";
+ for (i = 1; i < *argc; i++)
+ newargv[i + 1] = argv[i];
+ (*argc)++;
+ return newargv;
+}
+
+int
+main (int argc, char *argv[])
+{
+ struct fuse_session *se;
+ struct fuse_cmdline_opts opts;
+ char **newargv = get_new_args (&argc, argv);
+ struct ovl_data lo = {.debug = 0,
+ .uid_mappings = NULL,
+ .gid_mappings = NULL,
+ .uid_str = NULL,
+ .gid_str = NULL,
+ .root = NULL,
+ .lowerdir = NULL,
+ .redirect_dir = NULL,
+ .mountpoint = NULL,
+ };
+ int ret = -1;
+ struct fuse_args args = FUSE_ARGS_INIT (argc, newargv);
+
+ if (getenv ("FUSE_OVERLAYFS_DISABLE_OVL_WHITEOUT"))
+ disable_ovl_whiteout = true;
+
+ memset (&opts, 0, sizeof (opts));
+ if (fuse_opt_parse (&args, &lo, ovl_opts, fuse_opt_proc) == -1)
+ error (EXIT_FAILURE, 0, "error parsing options");
+ if (fuse_parse_cmdline (&args, &opts) != 0)
+ error (EXIT_FAILURE, 0, "error parsing cmdline");
+
+ if (opts.mountpoint)
+ free (opts.mountpoint);
+
+ if (opts.show_help)
+ {
+ printf ("usage: %s [options] <mountpoint>\n\n", argv[0]);
+ fuse_cmdline_help ();
+ fuse_lowlevel_help ();
+ ret = 0;
+ exit (EXIT_SUCCESS);
+ }
+ else if (opts.show_version)
+ {
+ printf ("fuse-overlayfs: version %s\n", PACKAGE_VERSION);
+ printf ("FUSE library version %s\n", fuse_pkgversion ());
+ fuse_lowlevel_version ();
+ ret = 0;
+ exit (EXIT_SUCCESS);
+ }
+
+ lo.debug = opts.debug;
+
+ if (lo.redirect_dir && strcmp (lo.redirect_dir, "off"))
+ error (EXIT_FAILURE, 0, "fuse-overlayfs only supports redirect_dir=off");
+
+ if (lo.upperdir == NULL)
+ error (EXIT_FAILURE, 0, "upperdir not specified");
+ else
+ {
+ char full_path[PATH_MAX + 1];
+
+ if (realpath (lo.upperdir, full_path) < 0)
+ error (EXIT_FAILURE, errno, "cannot retrieve path for %s", lo.upperdir);
+
+ lo.upperdir = strdup (full_path);
+ if (lo.upperdir == NULL)
+ error (EXIT_FAILURE, errno, "cannot allocate memory");
+ }
+
+ printf ("UID=%s\n", lo.uid_str ? : "unchanged");
+ printf ("GID=%s\n", lo.gid_str ? : "unchanged");
+ printf ("UPPERDIR=%s\n", lo.upperdir);
+ printf ("WORKDIR=%s\n", lo.workdir);
+ printf ("LOWERDIR=%s\n", lo.lowerdir);
+ printf ("MOUNTPOINT=%s\n", lo.mountpoint);
+
+ lo.uid_mappings = lo.uid_str ? read_mappings (lo.uid_str) : NULL;
+ lo.gid_mappings = lo.gid_str ? read_mappings (lo.gid_str) : NULL;
+
+ lo.layers = read_dirs (lo.lowerdir, true, NULL);
+ if (lo.layers == NULL)
+ error (EXIT_FAILURE, errno, "cannot read lower dirs");
+
+ lo.layers = read_dirs (lo.upperdir, false, lo.layers);
+ if (lo.layers == NULL)
+ error (EXIT_FAILURE, errno, "cannot read upper dir");
+
+ lo.root = load_dir (&lo, NULL, get_upper_layer (&lo), ".", "");
+ if (lo.root == NULL)
+ error (EXIT_FAILURE, errno, "cannot read upper dir");
+ lo.root->lookups = 2;
+
+ if (lo.workdir == NULL)
+ error (EXIT_FAILURE, 0, "workdir not specified");
+ else
+ {
+ char path[PATH_MAX + 1];
+
+ if (realpath (lo.workdir, path) < 0)
+ goto err_out1;
+ mkdir (path, 0700);
+ strcat (path, "/work");
+ mkdir (path, 0700);
+ free (lo.workdir);
+ lo.workdir = strdup (path);
+
+ lo.workdir_fd = open (lo.workdir, O_DIRECTORY);
+ if (lo.workdir_fd < 0)
+ error (EXIT_FAILURE, errno, "cannot open workdir");
+ }
+
+ se = fuse_session_new (&args, &ovl_oper, sizeof (ovl_oper), &lo);
+ lo.se = se;
+ if (se == NULL)
+ {
+ error (0, errno, "cannot create FUSE session");
+ goto err_out1;
+ }
+ if (fuse_set_signal_handlers (se) != 0)
+ {
+ error (0, errno, "cannot set signal handler");
+ goto err_out2;
+ }
+ if (fuse_session_mount (se, lo.mountpoint) != 0)
+ {
+ error (0, errno, "cannot mount");
+ goto err_out3;
+ }
+ fuse_daemonize (opts.foreground);
+ ret = fuse_session_loop (se);
+ fuse_session_unmount (se);
+err_out3:
+ fuse_remove_signal_handlers (se);
+err_out2:
+ fuse_session_destroy (se);
+err_out1:
+
+ node_mark_all_free (lo.root);
+
+ node_free (lo.root);
+
+ free_mapping (lo.uid_mappings);
+ free_mapping (lo.gid_mappings);
+
+ close (lo.workdir_fd);
+
+ free_layers (lo.layers);
+ fuse_opt_free_args (&args);
+
+ return ret ? 1 : 0;
+}