diff --git a/fuse-overlayfs.h b/fuse-overlayfs.h new file mode 100644 index 0000000..226f30f --- /dev/null +++ b/fuse-overlayfs.h @@ -0,0 +1,127 @@ +/* fuse-overlayfs: Overlay Filesystem in Userspace + + Copyright (C) 2019 Red Hat Inc. + + 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 . +*/ +#ifndef FUSE_OVERLAYFS_H +# define FUSE_OVERLAYFS_H + +# include +# include + +struct ovl_ino +{ + struct ovl_node *node; + ino_t ino; + dev_t dev; + int lookups; + mode_t mode; + int nlinks; +}; + +struct ovl_node +{ + struct ovl_node *parent; + Hash_table *children; + struct ovl_layer *layer, *last_layer; + ino_t tmp_ino; + dev_t tmp_dev; + char *path; + char *name; + int hidden_dirfd; + int node_lookups; + size_t name_hash; + Hash_table *inodes; + struct ovl_ino *ino; + struct ovl_node *next_link; + + unsigned int do_unlink : 1; + unsigned int do_rmdir : 1; + unsigned int hidden : 1; + unsigned int whiteout : 1; + unsigned int loaded : 1; + unsigned int no_security_capability : 1; +}; + +struct ovl_mapping +{ + struct ovl_mapping *next; + unsigned int host; + unsigned int to; + unsigned int len; +}; + +struct ovl_data +{ + struct fuse_session *se; + 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; + int debug; + struct ovl_layer *layers; + + Hash_table *inodes; + + struct ovl_node *root; + char *timeout_str; + double timeout; + int threaded; + int fsync; + int fast_ino_check; + int writeback; + int disable_xattrs; + + /* current uid/gid*/ + uid_t uid; + uid_t gid; +}; + +struct ovl_layer +{ + struct ovl_layer *next; + struct data_source *ds; + struct ovl_data *ovl_data; + char *path; + int fd; + bool low; +}; + +/* a data_source defines the methods for accessing a lower layer. */ +struct data_source +{ + int (*file_exists)(struct ovl_layer *l, const char *pathname); + int (*statat)(struct ovl_layer *l, const char *path, struct stat *st, int flags); + int (*fstat)(struct ovl_layer *l, int fd, const char *path, struct stat *st); + void *(*opendir)(struct ovl_layer *l, const char *path); + struct dirent *(*readdir)(void *dirp); + int (*closedir)(void *dirp); + int (*openat)(struct ovl_layer *l, const char *path, int flags, mode_t mode); + int (*listxattr)(struct ovl_layer *l, const char *path, char *buf, size_t size); + int (*getxattr)(struct ovl_layer *l, const char *path, const char *name, char *buf, size_t size); + ssize_t (*readlinkat)(struct ovl_layer *l, const char *path, char *buf, size_t bufsiz); +}; + +/* passtrough to the file system. */ +struct data_source direct_access_ds; + +#endif diff --git a/main.c b/main.c index 0a207c1..b2e02ac 100644 --- a/main.c +++ b/main.c @@ -24,6 +24,8 @@ #include +#include "fuse-overlayfs.h" + #include #include #include @@ -182,89 +184,6 @@ static bool disable_ovl_whiteout; static uid_t overflow_uid; static gid_t overflow_gid; -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_ino -{ - struct ovl_node *node; - ino_t ino; - dev_t dev; - int lookups; - mode_t mode; - int nlinks; -}; - -struct ovl_node -{ - struct ovl_node *parent; - Hash_table *children; - struct ovl_layer *layer, *last_layer; - ino_t tmp_ino; - dev_t tmp_dev; - char *path; - char *name; - int hidden_dirfd; - int node_lookups; - size_t name_hash; - Hash_table *inodes; - struct ovl_ino *ino; - struct ovl_node *next_link; - - unsigned int do_unlink : 1; - unsigned int do_rmdir : 1; - unsigned int hidden : 1; - unsigned int whiteout : 1; - unsigned int loaded : 1; - unsigned int no_security_capability : 1; -}; - -struct ovl_data -{ - struct fuse_session *se; - 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; - int debug; - struct ovl_layer *layers; - - Hash_table *inodes; - - struct ovl_node *root; - char *timeout_str; - double timeout; - int threaded; - int fsync; - int fast_ino_check; - int writeback; - int disable_xattrs; - - /* current uid/gid*/ - uid_t uid; - uid_t gid; -}; - static double get_timeout (struct ovl_data *lo) { @@ -582,32 +501,6 @@ node_dirfd (struct ovl_node *n) return n->layer->fd; } -static void -get_node_path (struct ovl_data *lo, struct ovl_node *n, char *path) -{ - if (n->hidden) - strconcat3 (path, PATH_MAX, lo->workdir, "/", n->path); - else - strconcat3 (path, PATH_MAX, n->layer->path, "/", n->path); -} - -static int -open_fd_or_get_path (struct ovl_data *lo, struct ovl_node *n, char *path, int *fd, int mode) -{ - int ret; - - path[0] = '\0'; - - *fd = TEMP_FAILURE_RETRY (openat (node_dirfd (n), n->path, O_NONBLOCK|O_NOFOLLOW|mode)); - if (*fd < 0 && (errno == ELOOP || errno == EISDIR)) - { - get_node_path (lo, n, path); - return 0; - } - - return *fd; -} - static bool has_prefix (const char *str, const char *pref) { @@ -645,33 +538,24 @@ set_fd_opaque (int fd) } static int -is_directory_opaque (int dirfd, const char *path) +is_directory_opaque (struct ovl_layer *l, 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)); + s = l->ds->getxattr (l, path, PRIVILEGED_OPAQUE_XATTR, b, sizeof (b)); if (s < 0 && errno == ENODATA) - s = fgetxattr (fd, OPAQUE_XATTR, b, sizeof (b)); - - saved_errno = errno; - close (fd); + s = l->ds->getxattr (l, path, OPAQUE_XATTR, b, sizeof (b)); if (s < 0) { - if (saved_errno == ENOTSUP || saved_errno == ENODATA) + if (errno == ENOTSUP || errno == ENODATA) { char whiteout_opq_path[PATH_MAX]; strconcat3 (whiteout_opq_path, PATH_MAX, path, "/" OPAQUE_WHITEOUT, NULL); - if (file_exists_at (dirfd, whiteout_opq_path) == 0) + if (l->ds->file_exists (l, whiteout_opq_path) == 0) return 1; return (errno == ENOENT) ? 0 : -1; @@ -699,7 +583,7 @@ create_whiteout (struct ovl_data *lo, struct ovl_node *parent, const char *name, for (l = get_lower_layers (lo); l; l = l->next) { - ret = file_exists_at (l->fd, path); + ret = l->ds->file_exists (l, path); if (ret < 0 && errno == ENOENT) continue; @@ -730,7 +614,8 @@ create_whiteout (struct ovl_data *lo, struct ovl_node *parent, const char *name, strconcat3 (whiteout_wh_path, PATH_MAX, parent->path, "/.wh.", name); - fd = TEMP_FAILURE_RETRY (openat (get_upper_layer (lo)->fd, whiteout_wh_path, O_CREAT|O_WRONLY|O_NONBLOCK, 0700)); + + fd = get_upper_layer (lo)->ds->openat (get_upper_layer (lo), whiteout_wh_path, O_CREAT|O_WRONLY|O_NONBLOCK, 0700); if (fd < 0 && errno != EEXIST) return -1; @@ -761,7 +646,7 @@ delete_whiteout (struct ovl_data *lo, int dirfd, struct ovl_node *parent, const strconcat3 (whiteout_path, PATH_MAX, parent->path, "/", name); - if (TEMP_FAILURE_RETRY (fstatat (get_upper_layer (lo)->fd, whiteout_path, &st, AT_SYMLINK_NOFOLLOW)) == 0 + if (get_upper_layer (lo)->ds->statat (get_upper_layer (lo), whiteout_path, &st, AT_SYMLINK_NOFOLLOW) == 0 && (st.st_mode & S_IFMT) == S_IFCHR && major (st.st_rdev) == 0 && minor (st.st_rdev) == 0) @@ -833,16 +718,20 @@ static int rpl_stat (fuse_req_t req, struct ovl_node *node, int fd, const char *path, struct stat *st_in, struct stat *st) { int ret = 0; + struct ovl_layer *l = node->layer; struct ovl_data *data = ovl_data (req); if (st_in) memcpy (st, st_in, sizeof (* st)); else if (fd >= 0) - ret = fstat (fd, st); + ret = l->ds->fstat (l, fd, path, st); else if (path != NULL) ret = stat (path, st); - else + else if (node->hidden) ret = fstatat (node_dirfd (node), node->path, st, AT_SYMLINK_NOFOLLOW); + else + ret = l->ds->statat (l, node->path, st, AT_SYMLINK_NOFOLLOW); + if (ret < 0) return ret; @@ -973,6 +862,13 @@ drop_node_from_ino (Hash_table *inodes, struct ovl_node *node) } } +static int +direct_renameat2 (struct ovl_layer *l, int olddirfd, const char *oldpath, + int newdirfd, const char *newpath, unsigned int flags) +{ + return syscall (SYS_renameat2, olddirfd, oldpath, newdirfd, newpath, flags); +} + static int hide_node (struct ovl_data *lo, struct ovl_node *node, bool unlink_src) { @@ -1350,10 +1246,13 @@ make_ovl_node (struct ovl_data *lo, const char *path, struct ovl_layer *layer, c bool stat_only = false; cleanup_free char *val = NULL; cleanup_free char *origin = NULL; - cleanup_close int fd = TEMP_FAILURE_RETRY (openat (it->fd, npath, O_RDONLY|O_NONBLOCK|O_NOFOLLOW)); + cleanup_close int fd = -1; + + fd = it->ds->openat (it, npath, O_RDONLY|O_NONBLOCK|O_NOFOLLOW, 0755); + if (fd < 0) { - if (errno != EPERM && fstatat (it->fd, npath, &st, AT_SYMLINK_NOFOLLOW) == 0) + if (errno != EPERM && it->ds->statat (it, npath, &st, AT_SYMLINK_NOFOLLOW) == 0) { ret->tmp_ino = st.st_ino; ret->tmp_dev = st.st_dev; @@ -1364,7 +1263,7 @@ make_ovl_node (struct ovl_data *lo, const char *path, struct ovl_layer *layer, c } /* It is an open FD, stat the file and read the origin xattrs. */ - if (fstat (fd, &st) == 0) + if (it->ds->fstat (it, fd, npath, &st) == 0) { ret->tmp_ino = st.st_ino; ret->tmp_dev = st.st_dev; @@ -1399,7 +1298,7 @@ make_ovl_node (struct ovl_data *lo, const char *path, struct ovl_layer *layer, c originfd = open_by_handle_at (AT_FDCWD, fh, O_RDONLY); if (originfd >= 0) { - if (fstat (originfd, &st) == 0) + if (it->ds->fstat (it, originfd, npath, &st) == 0) { ret->tmp_ino = st.st_ino; ret->tmp_dev = st.st_dev; @@ -1510,19 +1409,12 @@ load_dir (struct ovl_data *lo, struct ovl_node *n, struct ovl_layer *layer, char for (it = lo->layers; it; it = it->next) { - int fd; - cleanup_dir DIR *dp = NULL; - cleanup_close int cleanup_fd = TEMP_FAILURE_RETRY (openat (it->fd, path, O_DIRECTORY)); - if (cleanup_fd < 0) - continue; + DIR *dp = NULL; - dp = fdopendir (cleanup_fd); + dp = it->ds->opendir (it, path); if (dp == NULL) continue; - cleanup_fd = -1; /* It is now owned by dp. */ - - fd = dirfd (dp); for (;;) { int ret; @@ -1532,11 +1424,14 @@ load_dir (struct ovl_data *lo, struct ovl_node *n, struct ovl_layer *layer, char char whiteout_path[PATH_MAX]; errno = 0; - dent = readdir (dp); + dent = it->ds->readdir (dp); if (dent == NULL) { if (errno) - return NULL; + { + it->ds->closedir (dp); + return NULL; + } break; } @@ -1567,9 +1462,12 @@ load_dir (struct ovl_data *lo, struct ovl_node *n, struct ovl_layer *layer, char strconcat3 (node_path, PATH_MAX, n->path, "/", dent->d_name); - ret = file_exists_at (fd, whiteout_path); + ret = it->ds->file_exists (it, whiteout_path); if (ret < 0 && errno != ENOENT) - return NULL; + { + it->ds->closedir (dp); + return NULL; + } if (ret == 0) { @@ -1577,6 +1475,7 @@ load_dir (struct ovl_data *lo, struct ovl_node *n, struct ovl_layer *layer, char if (child == NULL) { errno = ENOMEM; + it->ds->closedir (dp); return NULL; } } @@ -1593,8 +1492,12 @@ load_dir (struct ovl_data *lo, struct ovl_node *n, struct ovl_layer *layer, char a whiteout file. */ struct stat st; - if (TEMP_FAILURE_RETRY (fstatat (fd, dent->d_name, &st, AT_SYMLINK_NOFOLLOW)) < 0) - return NULL; + ret = it->ds->statat (it, node_path, &st, AT_SYMLINK_NOFOLLOW); + if (ret < 0) + { + it->ds->closedir (dp); + return NULL; + } dirp = st.st_mode & S_IFDIR; wh = get_whiteout_name (dent->d_name, &st); @@ -1606,6 +1509,7 @@ load_dir (struct ovl_data *lo, struct ovl_node *n, struct ovl_layer *layer, char if (child == NULL) { errno = ENOMEM; + it->ds->closedir (dp); return NULL; } } @@ -1615,6 +1519,7 @@ load_dir (struct ovl_data *lo, struct ovl_node *n, struct ovl_layer *layer, char if (child == NULL) { errno = ENOMEM; + it->ds->closedir (dp); return NULL; } child->last_layer = it; @@ -1624,6 +1529,7 @@ load_dir (struct ovl_data *lo, struct ovl_node *n, struct ovl_layer *layer, char if (insert_node (n, child, false) == NULL) { errno = ENOMEM; + it->ds->closedir (dp); return NULL; } } @@ -1667,7 +1573,7 @@ cleanup_layerp (struct ovl_layer **p) #define cleanup_layer __attribute__((cleanup (cleanup_layerp))) static struct ovl_layer * -read_dirs (char *path, bool low, struct ovl_layer *layers) +read_dirs (struct ovl_data *lo, char *path, bool low, struct ovl_layer *layers) { char *saveptr = NULL, *it; struct ovl_layer *last; @@ -1691,6 +1597,11 @@ read_dirs (char *path, bool low, struct ovl_layer *layers) l = calloc (1, sizeof (*l)); if (l == NULL) return NULL; + + l->ds = &direct_access_ds; + + l->ovl_data = lo; + l->fd = -1; l->path = realpath (it, NULL); @@ -1760,7 +1671,7 @@ do_lookup_file (struct ovl_data *lo, fuse_ino_t parent, const char *name) strconcat3 (path, PATH_MAX, pnode->path, "/", name); - ret = TEMP_FAILURE_RETRY (fstatat (it->fd, path, &st, AT_SYMLINK_NOFOLLOW)); + ret = it->ds->statat (it, path, &st, AT_SYMLINK_NOFOLLOW); if (ret < 0) { int saved_errno = errno; @@ -1772,7 +1683,7 @@ do_lookup_file (struct ovl_data *lo, fuse_ino_t parent, const char *name) strconcat3 (whpath, PATH_MAX, pnode->path, "/.wh.", name); - ret = file_exists_at (it->fd, whpath); + ret = it->ds->file_exists (it, whpath); if (ret < 0 && errno != ENOENT && errno != ENOTDIR) return NULL; if (ret == 0) @@ -1801,7 +1712,7 @@ do_lookup_file (struct ovl_data *lo, fuse_ino_t parent, const char *name) } strconcat3 (whpath, PATH_MAX, pnode->path, "/.wh.", name); - ret = file_exists_at (it->fd, whpath); + ret = it->ds->file_exists (it, whpath); if (ret < 0 && errno != ENOENT) return NULL; if (ret == 0) @@ -1822,7 +1733,7 @@ do_lookup_file (struct ovl_data *lo, fuse_ino_t parent, const char *name) if (st.st_mode & S_IFDIR) { - ret = is_directory_opaque (it->fd, path); + ret = is_directory_opaque (it, path); if (ret < 0) { node_free (node); @@ -2007,7 +1918,7 @@ create_missing_whiteouts (struct ovl_data *lo, struct ovl_node *node, const char cleanup_dir DIR *dp = NULL; cleanup_close int cleanup_fd = -1; - cleanup_fd = TEMP_FAILURE_RETRY (openat (l->fd, from, O_DIRECTORY)); + cleanup_fd = l->ds->openat (l, from, O_DIRECTORY, 0755); if (cleanup_fd < 0) { if (errno == ENOENT) @@ -2225,8 +2136,6 @@ ovl_listxattr (fuse_req_t req, fuse_ino_t ino, size_t size) struct ovl_node *node; struct ovl_data *lo = ovl_data (req); cleanup_free char *buf = NULL; - cleanup_close int fd = -1; - char path[PATH_MAX]; int ret; if (UNLIKELY (ovl_debug (req))) @@ -2255,24 +2164,23 @@ ovl_listxattr (fuse_req_t req, fuse_ino_t ino, size_t size) } } - path[0] = '\0'; - ret = open_fd_or_get_path (lo, node, path, &fd, O_RDONLY); + if (! node->hidden) + ret = node->layer->ds->listxattr (node->layer, node->path, buf, size); + else + { + char path[PATH_MAX]; + strconcat3 (path, PATH_MAX, lo->workdir, "/", node->path); + ret = listxattr (path, buf, size); + } if (ret < 0) { fuse_reply_err (req, errno); return; } - l = release_big_lock (); + len = ret; - if (fd >= 0) - len = flistxattr (fd, buf, size); - else - len = llistxattr (path, buf, size); - - if (len < 0) - fuse_reply_err (req, errno); - else if (size == 0) + if (size == 0) fuse_reply_xattr (req, len); else if (len <= size) fuse_reply_buf (req, buf, len); @@ -2286,8 +2194,6 @@ ovl_getxattr (fuse_req_t req, fuse_ino_t ino, const char *name, size_t size) struct ovl_node *node; struct ovl_data *lo = ovl_data (req); cleanup_free char *buf = NULL; - cleanup_close int fd = -1; - char path[PATH_MAX]; bool is_security_capability = false; int ret; @@ -2326,29 +2232,29 @@ ovl_getxattr (fuse_req_t req, fuse_ino_t ino, const char *name, size_t size) } } - path[0] = '\0'; - ret = open_fd_or_get_path (lo, node, path, &fd, O_RDONLY); + if (! node->hidden) + ret = node->layer->ds->getxattr (node->layer, node->path, name, buf, size); + else + { + char path[PATH_MAX]; + strconcat3 (path, PATH_MAX, lo->workdir, "/", node->path); + ret = getxattr (path, name, buf, size); + } + + if (get_timeout (lo) > 0 && is_security_capability && ret < 0 && errno == ENODATA) + node->no_security_capability = 1; + if (ret < 0) { fuse_reply_err (req, errno); return; } - l = release_big_lock (); + len = ret; - if (fd >= 0) - len = fgetxattr (fd, name, buf, size); - else - len = lgetxattr (path, name, buf, size); - - if (get_timeout (lo) > 0 && is_security_capability && len < 0 && errno == ENODATA) - node->no_security_capability = 1; - - if (len < 0) - fuse_reply_err (req, errno); - else if (size == 0) + if (size == 0) fuse_reply_xattr (req, len); - else if (len <= size) + else fuse_reply_buf (req, buf, len); } @@ -2510,7 +2416,7 @@ create_node_directory (struct ovl_data *lo, struct ovl_node *src) if (src->layer == get_upper_layer (lo)) return 0; - ret = sfd = TEMP_FAILURE_RETRY (openat (node_dirfd (src), src->path, O_RDONLY|O_NONBLOCK)); + ret = sfd = src->layer->ds->openat (src->layer, src->path, O_RDONLY|O_NONBLOCK, 0755); if (ret < 0) return ret; @@ -2580,7 +2486,7 @@ copyup (struct ovl_data *lo, struct ovl_node *node) sprintf (wd_tmp_file_name, "%lu", get_next_wd_counter ()); - ret = TEMP_FAILURE_RETRY (fstatat (node_dirfd (node), node->path, &st, AT_SYMLINK_NOFOLLOW)); + ret = node->layer->ds->statat (node->layer, node->path, &st, AT_SYMLINK_NOFOLLOW); if (ret < 0) return ret; @@ -2608,7 +2514,7 @@ copyup (struct ovl_data *lo, struct ovl_node *node) { char *new; - ret = readlinkat (node_dirfd (node), node->path, p, current_size - 1); + ret = node->layer->ds->readlinkat (node->layer, node->path, p, current_size - 1); if (ret < 0) goto exit; if (ret < current_size - 1) @@ -2627,7 +2533,7 @@ copyup (struct ovl_data *lo, struct ovl_node *node) goto success; } - ret = sfd = TEMP_FAILURE_RETRY (openat (node_dirfd (node), node->path, O_RDONLY|O_NONBLOCK)); + ret = sfd = node->layer->ds->openat (node->layer, node->path, O_RDONLY|O_NONBLOCK, 0755); if (sfd < 0) goto exit; @@ -2867,14 +2773,14 @@ empty_dirfd (int fd) } static int -empty_dir (struct ovl_data *lo, struct ovl_node *node) +empty_dir (struct ovl_layer *l, const char *path) { cleanup_dir DIR *dp = NULL; cleanup_close int cleanup_fd = -1; struct dirent *dent; int ret; - cleanup_fd = TEMP_FAILURE_RETRY (openat (get_upper_layer (lo)->fd, node->path, O_DIRECTORY)); + cleanup_fd = TEMP_FAILURE_RETRY (openat (l->fd, path, O_DIRECTORY)); if (cleanup_fd < 0) return -1; @@ -2933,7 +2839,7 @@ do_rm (fuse_req_t req, fuse_ino_t parent, const char *name, bool dirp) { if (whiteouts > 0) { - if (empty_dir (lo, node) < 0) + if (empty_dir (get_upper_layer (lo), node->path) < 0) { fuse_reply_err (req, errno); return; @@ -3006,6 +2912,41 @@ ovl_rmdir (fuse_req_t req, fuse_ino_t parent, const char *name) do_rm (req, parent, name, true); } +static int +open_fd_or_get_path (struct ovl_layer *l, const char *path, char *out, int *fd, int flags) +{ + int ret; + + out[0] = '\0'; + + *fd = l->ds->openat (l, path, O_NONBLOCK|O_NOFOLLOW|flags, 0755); + if (*fd < 0 && (errno == ELOOP || errno == EISDIR)) + { + strconcat3 (out, PATH_MAX, l->path, "/", path); + return 0; + } + + return *fd; +} + +static int +direct_setxattr (struct ovl_layer *l, const char *path, const char *name, const char *buf, size_t size, int flags) +{ + cleanup_close int fd = -1; + char full_path[PATH_MAX]; + int ret; + + full_path[0] = '\0'; + ret = open_fd_or_get_path (l, path, full_path, &fd, O_WRONLY); + if (ret < 0) + return ret; + + if (fd >= 0) + return fsetxattr (fd, name, buf, size, flags); + + return setxattr (full_path, name, buf, size, flags); +} + static void ovl_setxattr (fuse_req_t req, fuse_ino_t ino, const char *name, const char *value, size_t size, int flags) @@ -3013,9 +2954,7 @@ ovl_setxattr (fuse_req_t req, fuse_ino_t ino, const char *name, cleanup_lock int l = enter_big_lock (); struct ovl_data *lo = ovl_data (req); bool is_security_capability = false; - cleanup_close int fd = -1; struct ovl_node *node; - char path[PATH_MAX]; int ret; if (UNLIKELY (ovl_debug (req))) @@ -3050,38 +2989,48 @@ ovl_setxattr (fuse_req_t req, fuse_ino_t ino, const char *name, return; } - path[0] = '\0'; - ret = open_fd_or_get_path (lo, node, path, &fd, O_WRONLY); - if (ret < 0) - { - fuse_reply_err (req, errno); - return; - } - - l = release_big_lock (); - - if (fd >= 0) - ret = fsetxattr (fd, name, value, size, flags); + if (! node->hidden) + ret = direct_setxattr (node->layer, node->path, name, value, size, flags); else - ret = setxattr (path, name, value, size, flags); - + { + char path[PATH_MAX]; + strconcat3 (path, PATH_MAX, lo->workdir, "/", node->path); + ret = setxattr (path, name, value, size, flags); + } if (ret < 0) { fuse_reply_err (req, errno); return; } + node->no_security_capability = 1; fuse_reply_err (req, 0); } +static int +direct_removexattr (struct ovl_layer *l, const char *path, const char *name) +{ + cleanup_close int fd = -1; + char full_path[PATH_MAX]; + int ret; + + full_path[0] = '\0'; + ret = open_fd_or_get_path (l, path, full_path, &fd, O_WRONLY); + if (ret < 0) + return ret; + + if (fd >= 0) + return fremovexattr (fd, name); + + return lremovexattr (full_path, name); +} + static void ovl_removexattr (fuse_req_t req, fuse_ino_t ino, const char *name) { cleanup_lock int l = enter_big_lock (); struct ovl_node *node; struct ovl_data *lo = ovl_data (req); - cleanup_close int fd = -1; - char path[PATH_MAX]; int ret; if (UNLIKELY (ovl_debug (req))) @@ -3101,20 +3050,14 @@ ovl_removexattr (fuse_req_t req, fuse_ino_t ino, const char *name) return; } - path[0] = '\0'; - ret = open_fd_or_get_path (lo, node, path, &fd, O_WRONLY); - if (ret < 0) - { - fuse_reply_err (req, errno); - return; - } - - l = release_big_lock (); - - if (fd >= 0) - ret = fremovexattr (fd, name); + if (! node->hidden) + ret = direct_removexattr (node->layer, node->path, name); else - ret = lremovexattr (path, name); + { + char path[PATH_MAX]; + strconcat3 (path, PATH_MAX, lo->workdir, "/", node->path); + ret = removexattr (path, name); + } if (ret < 0) { @@ -3126,8 +3069,9 @@ ovl_removexattr (fuse_req_t req, fuse_ino_t ino, const char *name) } static int -create_file (struct ovl_data *lo, int dirfd, const char *path, uid_t uid, gid_t gid, int flags, mode_t mode) +direct_create_file (struct ovl_layer *l, int dirfd, const char *path, uid_t uid, gid_t gid, int flags, mode_t mode) { + struct ovl_data *lo = l->ovl_data; cleanup_close int fd = -1; char wd_tmp_file_name[32]; int ret; @@ -3253,7 +3197,7 @@ ovl_do_open (fuse_req_t req, fuse_ino_t parent, const char *name, int flags, mod uid = get_uid (lo, ctx->uid); gid = get_gid (lo, ctx->gid); - fd = create_file (lo, get_upper_layer (lo)->fd, path, uid, gid, flags, mode & ~ctx->umask); + fd = direct_create_file (get_upper_layer (lo), get_upper_layer (lo)->fd, path, uid, gid, flags, mode & ~ctx->umask); if (fd < 0) return fd; @@ -3263,7 +3207,7 @@ ovl_do_open (fuse_req_t req, fuse_ino_t parent, const char *name, int flags, mod if (st == NULL) st = &st_tmp; - if (fstat (fd, st) < 0) + if (get_upper_layer (lo)->ds->fstat (get_upper_layer (lo), fd, path, st) < 0) return -1; n = make_ovl_node (lo, path, get_upper_layer (lo), name, st->st_ino, st->st_dev, false, p, lo->fast_ino_check); @@ -3291,12 +3235,15 @@ ovl_do_open (fuse_req_t req, fuse_ino_t parent, const char *name, int flags, mod /* readonly, we can use both lowerdir and upperdir. */ if (readonly) { + struct ovl_layer *l = n->layer; if (retnode) *retnode = n; - return TEMP_FAILURE_RETRY (openat (node_dirfd (n), n->path, flags, mode)); + return l->ds->openat (l, n->path, flags, mode); } else { + struct ovl_layer *l; + n = get_node_up (lo, n); if (n == NULL) return -1; @@ -3304,7 +3251,9 @@ ovl_do_open (fuse_req_t req, fuse_ino_t parent, const char *name, int flags, mod if (retnode) *retnode = n; - return TEMP_FAILURE_RETRY (openat (node_dirfd (n), n->path, flags, mode)); + l = n->layer; + + return l->ds->openat (l, n->path, flags, mode); } } @@ -3518,6 +3467,13 @@ ovl_setattr (fuse_req_t req, fuse_ino_t ino, struct stat *attr, int to_set, stru /* Ignore request. */ } + uid = -1; + gid = -1; + 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 (fi != NULL) fd = fi->fh; // use existing fd if fuse_file_info is available else @@ -3632,13 +3588,6 @@ ovl_setattr (fuse_req_t req, fuse_ino_t ino, struct stat *attr, int to_set, stru } } - uid = -1; - gid = -1; - 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 != -1 || gid != -1) { if (fd >= 0) @@ -3661,6 +3610,12 @@ ovl_setattr (fuse_req_t req, fuse_ino_t ino, struct stat *attr, int to_set, stru fuse_reply_attr (req, &e.attr, get_timeout (lo)); } +static int +direct_linkat (struct ovl_layer *l, const char *oldpath, const char *newpath, int flags) +{ + return linkat (l->fd, oldpath, l->fd, newpath, 0); +} + static void ovl_link (fuse_req_t req, fuse_ino_t ino, fuse_ino_t newparent, const char *newname) { @@ -3725,13 +3680,8 @@ ovl_link (fuse_req_t req, fuse_ino_t ino, fuse_ino_t newparent, const char *newn return; } - if (linkat (node_dirfd (node), 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) + ret = direct_linkat (get_upper_layer (lo), node->path, path, 0); + if (ret < 0) { fuse_reply_err (req, errno); return; @@ -3769,20 +3719,52 @@ ovl_link (fuse_req_t req, fuse_ino_t ino, fuse_ino_t newparent, const char *newn fuse_reply_entry (req, &e); } +static int +direct_symlinkat (struct ovl_layer *l, const char *target, const char *linkpath, uid_t uid, gid_t gid) +{ + struct ovl_data *lo = l->ovl_data; + char wd_tmp_file_name[32]; + int ret; + + sprintf (wd_tmp_file_name, "%lu", get_next_wd_counter ()); + + unlinkat (lo->workdir_fd, wd_tmp_file_name, 0); + ret = symlinkat (linkpath, lo->workdir_fd, wd_tmp_file_name); + if (ret < 0) + return ret; + + if (uid != lo->uid || gid != lo->gid) + { + ret = fchownat (lo->workdir_fd, wd_tmp_file_name, uid, gid, AT_SYMLINK_NOFOLLOW); + if (ret < 0) + { + unlinkat (lo->workdir_fd, wd_tmp_file_name, 0); + return ret; + } + } + + ret = renameat (lo->workdir_fd, wd_tmp_file_name, get_upper_layer (lo)->fd, target); + if (ret < 0) + { + unlinkat (lo->workdir_fd, wd_tmp_file_name, 0); + return ret; + } + + return 0; +} + static void ovl_symlink (fuse_req_t req, const char *link, fuse_ino_t parent, const char *name) { cleanup_lock int l = enter_big_lock (); struct ovl_data *lo = ovl_data (req); struct ovl_node *pnode, *node; - cleanup_free char *path = NULL; int ret; struct fuse_entry_param e; const struct fuse_ctx *ctx = fuse_req_ctx (req); char wd_tmp_file_name[32]; bool need_delete_whiteout = true; - uid_t uid; - gid_t gid; + cleanup_free char *path = NULL; if (UNLIKELY (ovl_debug (req))) fprintf (stderr, "ovl_symlink(link=%s, ino=%" PRIu64 "s, name=%s)\n", link, parent, name); @@ -3811,26 +3793,18 @@ ovl_symlink (fuse_req_t req, const char *link, fuse_ino_t parent, const char *na if (pnode->loaded && node == NULL) need_delete_whiteout = false; - sprintf (wd_tmp_file_name, "%lu", get_next_wd_counter ()); - - unlinkat (lo->workdir_fd, wd_tmp_file_name, 0); - ret = symlinkat (link, lo->workdir_fd, wd_tmp_file_name); + ret = asprintf (&path, "%s/%s", pnode->path, name); if (ret < 0) { - fuse_reply_err (req, errno); + fuse_reply_err (req, ENOMEM); return; } - uid = get_uid (lo, ctx->uid); - gid = get_uid (lo, ctx->gid); - if (uid != lo->uid || gid != lo->gid) + ret = direct_symlinkat (get_upper_layer (lo), path, link, get_uid (lo, ctx->uid), get_gid (lo, ctx->gid)); + if (ret < 0) { - if (fchownat (lo->workdir_fd, wd_tmp_file_name, get_uid (lo, ctx->uid), get_gid (lo, ctx->gid), AT_SYMLINK_NOFOLLOW) < 0) - { - unlinkat (lo->workdir_fd, wd_tmp_file_name, 0); - fuse_reply_err (req, errno); - return; - } + fuse_reply_err (req, ENOMEM); + return; } if (need_delete_whiteout && delete_whiteout (lo, -1, pnode, name) < 0) @@ -3840,20 +3814,6 @@ ovl_symlink (fuse_req_t req, const char *link, fuse_ino_t parent, const char *na return; } - ret = asprintf (&path, "%s/%s", pnode->path, name); - if (ret < 0) - { - fuse_reply_err (req, errno); - return; - } - - ret = renameat (lo->workdir_fd, wd_tmp_file_name, get_upper_layer (lo)->fd, path); - if (ret < 0) - { - fuse_reply_err (req, errno); - return; - } - node = make_ovl_node (lo, path, get_upper_layer (lo), name, 0, 0, false, pnode, lo->fast_ino_check); if (node == NULL) { @@ -3964,7 +3924,7 @@ ovl_rename_exchange (fuse_req_t req, fuse_ino_t parent, const char *name, goto error; - ret = syscall (SYS_renameat2, srcfd, name, destfd, newname, flags); + ret = direct_renameat2 (node->layer, srcfd, name, destfd, newname, flags); if (ret < 0) goto error; @@ -4101,7 +4061,7 @@ ovl_rename_direct (fuse_req_t req, fuse_ino_t parent, const char *name, errno = ENOTEMPTY; goto error; } - if (destnode_whiteouts && empty_dir (lo, destnode) < 0) + if (destnode_whiteouts && empty_dir (get_upper_layer (lo), destnode->path) < 0) goto error; } @@ -4137,7 +4097,7 @@ ovl_rename_direct (fuse_req_t req, fuse_ino_t parent, const char *name, 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); + ret = direct_renameat2 (get_upper_layer (lo), srcfd, name, destfd, newname, flags|RENAME_EXCHANGE); if (ret == 0) goto done; @@ -4157,11 +4117,12 @@ ovl_rename_direct (fuse_req_t req, fuse_ino_t parent, const char *name, /* 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); + ret = direct_renameat2 (get_upper_layer (lo), srcfd, name, destfd, + newname, flags|RENAME_WHITEOUT); if (ret < 0) { - ret = syscall (SYS_renameat2, srcfd, name, destfd, newname, flags); + ret = direct_renameat2 (get_upper_layer (lo), srcfd, name, destfd, + newname, flags); if (ret < 0) goto error; @@ -4275,7 +4236,7 @@ ovl_readlink (fuse_req_t req, fuse_ino_t ino) { char *tmp; - ret = readlinkat (node_dirfd (node), node->path, buf, current_size - 1); + ret = node->layer->ds->readlinkat (node->layer, node->path, buf, current_size - 1); if (ret == -1) { fuse_reply_err (req, errno); @@ -4561,6 +4522,22 @@ ovl_mkdir (fuse_req_t req, fuse_ino_t parent, const char *name, mode_t mode) fuse_reply_entry (req, &e); } +static int +direct_fsync (struct ovl_layer *l, int fd, const char *path, int datasync) +{ + cleanup_close int cfd = -1; + + if (fd < 0) + { + cfd = openat (l->fd, path, O_NOFOLLOW|O_DIRECTORY); + if (cfd < 0) + return cfd; + fd = cfd; + } + + return datasync ? fdatasync (fd) : fsync (fd); +} + static void do_fsync (fuse_req_t req, fuse_ino_t ino, int datasync, int fd) { @@ -4569,7 +4546,6 @@ do_fsync (fuse_req_t req, fuse_ino_t ino, int datasync, int fd) struct ovl_node *node; struct ovl_data *lo = ovl_data (req); cleanup_lock int l = 0; - cleanup_close int cfd = -1; char path[PATH_MAX]; if (!lo->fsync) @@ -4588,27 +4564,15 @@ do_fsync (fuse_req_t req, fuse_ino_t ino, int datasync, int fd) if (fd < 0) strcpy (path, node->path); - l = release_big_lock (); - if (! do_fsync) { fuse_reply_err (req, 0); return; } - if (fd < 0) - { - cfd = openat (get_upper_layer (lo)->fd, path, O_NOFOLLOW|O_DIRECTORY); - if (cfd < 0) - { - fuse_reply_err (req, errno == ENOENT ? 0 : errno); - return; - } - fd = cfd; - } - if (do_fsync) - ret = datasync ? fdatasync (fd) : fsync (fd); + ret = direct_fsync (node->layer, fd, path, datasync); + fuse_reply_err (req, ret == 0 ? 0 : errno); } @@ -4632,6 +4596,12 @@ ovl_fsyncdir (fuse_req_t req, fuse_ino_t ino, int datasync, struct fuse_file_inf return do_fsync (req, ino, datasync, -1); } +static int +direct_ioctl (struct ovl_layer *l, int fd, int cmd, unsigned long *r) +{ + return ioctl (fd, cmd, &r); +} + static void ovl_ioctl (fuse_req_t req, fuse_ino_t ino, unsigned int cmd, void *arg, struct fuse_file_info *fi, unsigned int flags, @@ -4688,7 +4658,7 @@ ovl_ioctl (fuse_req_t req, fuse_ino_t ino, unsigned int cmd, void *arg, if (fd < 0) { - fd = cleaned_fd = TEMP_FAILURE_RETRY (openat (node_dirfd (node), node->path, O_RDONLY|O_NONBLOCK)); + fd = cleaned_fd = node->layer->ds->openat (node->layer, node->path, O_RDONLY|O_NONBLOCK, 0755); if (fd < 0) { fuse_reply_err (req, errno); @@ -4698,12 +4668,18 @@ ovl_ioctl (fuse_req_t req, fuse_ino_t ino, unsigned int cmd, void *arg, l = release_big_lock (); - if (ioctl (fd, cmd, &r) < 0) + if (direct_ioctl (node->layer, fd, cmd, &r) < 0) fuse_reply_err (req, errno); else fuse_reply_ioctl (req, 0, &r, out_bufsz ? sizeof (r) : 0); } +static int +direct_fallocate (struct ovl_layer *l, int fd, int mode, off_t offset, off_t len) +{ + return fallocate (fd, mode, offset, len); +} + static void ovl_fallocate (fuse_req_t req, fuse_ino_t ino, int mode, off_t offset, off_t length, struct fuse_file_info *fi) { @@ -4731,7 +4707,7 @@ ovl_fallocate (fuse_req_t req, fuse_ino_t ino, int mode, off_t offset, off_t len return; } - fd = TEMP_FAILURE_RETRY (openat (node_dirfd (node), node->path, O_NONBLOCK|O_NOFOLLOW|O_WRONLY)); + fd = node->layer->ds->openat (node->layer, node->path, O_NONBLOCK|O_NOFOLLOW|O_WRONLY, 0755); if (fd < 0) { fuse_reply_err (req, errno); @@ -4740,11 +4716,20 @@ ovl_fallocate (fuse_req_t req, fuse_ino_t ino, int mode, off_t offset, off_t len l = release_big_lock (); - ret = fallocate (fd, mode, offset, length); + ret = direct_fallocate (node->layer, fd, mode, offset, length); fuse_reply_err (req, ret < 0 ? errno : 0); } #ifdef HAVE_COPY_FILE_RANGE + +static ssize_t +direct_copy_file_range (struct ovl_layer *l, int fd_in, off_t *off_in, + int fd_out, off_t *off_out, + size_t len, unsigned int flags) +{ + return copy_file_range (fd_in, off_in, fd_out, off_out, len, flags); +} + static void ovl_copy_file_range (fuse_req_t req, fuse_ino_t ino_in, off_t off_in, struct fuse_file_info *fi_in, fuse_ino_t ino_out, off_t off_out, struct fuse_file_info *fi_out, size_t len, int flags) { @@ -4780,7 +4765,7 @@ ovl_copy_file_range (fuse_req_t req, fuse_ino_t ino_in, off_t off_in, struct fus return; } - fd = TEMP_FAILURE_RETRY (openat (node_dirfd (node), node->path, O_NONBLOCK|O_NOFOLLOW|O_RDONLY)); + fd = node->layer->ds->openat (node->layer, node->path, O_NONBLOCK|O_NOFOLLOW|O_RDONLY, 0755); if (fd < 0) { fuse_reply_err (req, errno); @@ -4796,7 +4781,7 @@ ovl_copy_file_range (fuse_req_t req, fuse_ino_t ino_in, off_t off_in, struct fus l = release_big_lock (); - ret = copy_file_range (fd, &off_in, fd_dest, &off_out, len, flags); + ret = direct_copy_file_range (node->layer, fd, &off_in, fd_dest, &off_out, len, flags); if (ret < 0) fuse_reply_err (req, errno); else @@ -4804,6 +4789,117 @@ ovl_copy_file_range (fuse_req_t req, fuse_ino_t ino_in, off_t off_in, struct fus } #endif +static int +direct_file_exists (struct ovl_layer *l, const char *pathname) +{ + return file_exists_at (l->fd, pathname); +} + +static int +direct_listxattr (struct ovl_layer *l, const char *path, char *buf, size_t size) +{ + cleanup_close int fd = -1; + char full_path[PATH_MAX]; + int ret; + + full_path[0] = '\0'; + ret = open_fd_or_get_path (l, path, full_path, &fd, O_RDONLY); + if (ret < 0) + return ret; + + if (fd >= 0) + return flistxattr (fd, buf, size); + + return llistxattr (full_path, buf, size); +} + +static int +direct_getxattr (struct ovl_layer *l, const char *path, const char *name, char *buf, size_t size) +{ + cleanup_close int fd = -1; + char full_path[PATH_MAX]; + int ret; + + full_path[0] = '\0'; + ret = open_fd_or_get_path (l, path, full_path, &fd, O_RDONLY); + if (ret < 0) + return ret; + + if (fd >= 0) + return fgetxattr (fd, name, buf, size); + + return lgetxattr (full_path, name, buf, size); +} + +static int +direct_fstat (struct ovl_layer *l, int fd, const char *path, struct stat *st) +{ + return fstat (fd, st); +} + +static int +direct_statat (struct ovl_layer *l, const char *path, struct stat *st, int flags) +{ + return TEMP_FAILURE_RETRY (fstatat (l->fd, path, st, flags)); +} + +static struct dirent * +direct_readdir (void *dirp) +{ + return readdir (dirp); +} + +static void * +direct_opendir (struct ovl_layer *l, const char *path) +{ + cleanup_close int cleanup_fd = -1; + DIR *dp = NULL; + + cleanup_fd = TEMP_FAILURE_RETRY (openat (l->fd, path, O_DIRECTORY)); + if (cleanup_fd < 0) + return NULL; + + dp = fdopendir (cleanup_fd); + if (dp == NULL) + return NULL; + + cleanup_fd = -1; + + return dp; +} + +static int +direct_closedir (void *dirp) +{ + return closedir (dirp); +} + +static int +direct_openat (struct ovl_layer *l, const char *path, int flags, mode_t mode) +{ + return TEMP_FAILURE_RETRY (openat (l->fd, path, flags, mode)); +} + +static ssize_t +direct_readlinkat (struct ovl_layer *l, const char *path, char *buf, size_t bufsiz) +{ + return TEMP_FAILURE_RETRY (readlinkat (l->fd, path, buf, bufsiz)); +} + +struct data_source direct_access_ds = + { + .file_exists = direct_file_exists, + .statat = direct_statat, + .fstat = direct_fstat, + .opendir = direct_opendir, + .readdir = direct_readdir, + .closedir = direct_closedir, + .openat = direct_openat, + .getxattr = direct_getxattr, + .listxattr = direct_listxattr, + .readlinkat = direct_readlinkat, + }; + static struct fuse_lowlevel_ops ovl_oper = { .statfs = ovl_statfs, @@ -5051,7 +5147,7 @@ main (int argc, char *argv[]) error (EXIT_FAILURE, errno, "cannot convert %s", lo.timeout_str); } - layers = read_dirs (lo.lowerdir, true, NULL); + layers = read_dirs (&lo, lo.lowerdir, true, NULL); if (layers == NULL) { error (EXIT_FAILURE, errno, "cannot read lower dirs"); @@ -5059,7 +5155,7 @@ main (int argc, char *argv[]) if (lo.upperdir != NULL) { - tmp_layer = read_dirs (lo.upperdir, false, layers); + tmp_layer = read_dirs (&lo, lo.upperdir, false, layers); if (tmp_layer == NULL) error (EXIT_FAILURE, errno, "cannot read upper dir"); layers = tmp_layer;