diff --git a/direct.c b/direct.c index 272631f7..cb42c51e 100644 --- a/direct.c +++ b/direct.c @@ -165,10 +165,80 @@ direct_readlinkat (struct ovl_layer *l, const char *path, char *buf, size_t bufs return TEMP_FAILURE_RETRY (readlinkat (l->fd, path, buf, bufsiz)); } +static ino_t +direct_get_nfs_filehandle (const struct ovl_layer *l, const char *path) +{ + int mount_id; + int ret = name_to_handle_at (l->fd, path, l->fh, &mount_id, 0); + if (ret == -1) + return 0; + + ino_t h = 0xcbf29ce484222325ULL; + for (size_t i = 0; i < l->fh->handle_bytes; i++) + { + h ^= l->fh->f_handle[i]; + h *= 0x100000001b3ULL; + } + + return h; +} + +/* Returns: + -1 - Error + 0 - NFS filehandles not supported + 1 - Can use NFS filehandles */ +static int +has_nfs_filehandles (struct ovl_layer *l, const char *path) +{ + struct file_handle *tmp_fh; + int mount_id; + int ret; + + tmp_fh = malloc (sizeof (*tmp_fh)); + if (tmp_fh == NULL) + return -1; + + tmp_fh->handle_bytes = 0; + + ret = name_to_handle_at (AT_FDCWD, path, tmp_fh, &mount_id, 0); + if (ret == -1 && errno == ENOTSUP) + { + free (tmp_fh); + return 0; + } + /* previous call should fail with EOVERFLOW and handle_bytes replaced with + * the size of the handle. EOVERFLOW can also occur if no filehandle is + * available in a system that does support file-handle lookup. + */ + if (ret != -1 || errno != EOVERFLOW || tmp_fh->handle_bytes == 0) + { + free (tmp_fh); + return -1; + } + + l->fh = realloc (tmp_fh, tmp_fh->handle_bytes + sizeof (*l->fh)); + if (! l->fh) + { + free (tmp_fh); + return -1; + } + + ret = name_to_handle_at (AT_FDCWD, path, l->fh, &mount_id, 0); + + if (ret == -1) + { + free (l->fh); + l->fh = NULL; + return 0; + } + return 1; +} + static int direct_load_data_source (struct ovl_layer *l, const char *opaque, const char *path, int n_layer) { char tmp[64]; + struct stat st; l->path = realpath (path, NULL); if (l->path == NULL) { @@ -184,6 +254,18 @@ direct_load_data_source (struct ovl_layer *l, const char *opaque, const char *pa return l->fd; } + if (fstat (l->fd, &st) == -1) + { + close (l->fd); + free (l->path); + l->path = NULL; + return -1; + } + else + l->st_dev = st.st_dev; + + l->nfs_filehandles = has_nfs_filehandles (l, l->path); + if (fgetxattr (l->fd, XATTR_PRIVILEGED_OVERRIDE_STAT, tmp, sizeof (tmp)) >= 0) l->stat_override_mode = STAT_OVERRIDE_PRIVILEGED; else if (fgetxattr (l->fd, XATTR_OVERRIDE_CONTAINERS_STAT, tmp, sizeof (tmp)) >= 0) @@ -230,4 +312,5 @@ struct data_source direct_access_ds = { .listxattr = direct_listxattr, .readlinkat = direct_readlinkat, .support_acls = direct_support_acls, + .get_nfs_filehandle = direct_get_nfs_filehandle, }; diff --git a/fuse-overlayfs.1 b/fuse-overlayfs.1 index 63b24b5c..c0d9bace 100644 --- a/fuse-overlayfs.1 +++ b/fuse-overlayfs.1 @@ -9,11 +9,11 @@ fuse-overlayfs - overlayfs FUSE implementation .SH SYNOPSIS .PP mounting - fuse-overlayfs [-f] [--debug] [-o OPTS] MOUNT_TARGET + fuse-overlayfs [\-f] [\-\-debug] [\-o OPTS] MOUNT_TARGET .PP unmounting - fusermount -u mountpoint + fusermount \-u mountpoint .SH DESCRIPTION @@ -25,26 +25,26 @@ namespace. .SH OPTIONS .PP -\fB--debug\fP +\fB\-\-debug\fP Enable debugging mode, can be very noisy. .PP -\fB-o lowerdir=low1[:low2...]\fP +\fB\-o lowerdir=low1[:low2...]\fP A list of directories separated by \fB\fC:\fR\&. Their content is merged. .PP -\fB-o upperdir=upperdir\fP +\fB\-o upperdir=upperdir\fP A directory merged on top of all the lowerdirs where all the changes done to the file system will be written. .PP -\fB-o workdir=workdir\fP +\fB\-o workdir=workdir\fP A directory used internally by fuse-overlays, must be on the same file system as the upper dir. .PP -\fB-o uidmapping=UID:MAPPED-UID:LEN[,UID2:MAPPED-UID2:LEN2]\fP -\fB-o gidmapping=GID:MAPPED-GID:LEN[,GID2:MAPPED-GID2:LEN2]\fP +\fB\-o uidmapping=UID:MAPPED-UID:LEN[,UID2:MAPPED-UID2:LEN2]\fP +\fB\-o gidmapping=GID:MAPPED-GID:LEN[,GID2:MAPPED-GID2:LEN2]\fP Specifies the dynamic UID/GID mapping used by fuse-overlayfs when reading/writing files to the system. @@ -62,7 +62,7 @@ without requiring to chown the files. For example, given on the host two files like: .PP -$ stat -c %u:%g lower/a lower/b +$ stat \-c %u:%g lower/a lower/b 0:0 1:1 @@ -76,7 +76,7 @@ $ cat /proc/self/uid_map We would see: .PP -$ stat -c %u:%g merged/a merged/b +$ stat \-c %u:%g merged/a merged/b 65534:65534 65534:65534 @@ -87,12 +87,12 @@ mapped. .PP In the above example, if we mount the fuse-overlayfs file system using: -\fB\fC-ouidmapping=0:1000:1:1:110000:65536,gidmapping=0:1000:1:1:110000:65536\fR, +\fB\fC\-ouidmapping=0:1000:1:1:110000:65536,gidmapping=0:1000:1:1:110000:65536\fR, which is the namespace configuration specified on a single line, we'd see from the same user namespace: .PP -$ stat -c %u:%g merged/a merged/b +$ stat \-c %u:%g merged/a merged/b 0:0 1:1 @@ -100,19 +100,19 @@ $ stat -c %u:%g merged/a merged/b Those are the same IDs visible from outside the user namespace. .PP -\fB-o squash_to_root\fP +\fB\-o squash_to_root\fP Every file and directory is owned by the root user (0:0). .PP -\fB-o squash_to_uid=uid\fP -\fB-o squash_to_gid=gid\fP +\fB\-o squash_to_uid=uid\fP +\fB\-o squash_to_gid=gid\fP Every file and directory is owned by the specified uid or gid. .PP It has higher precedence over \fBsquash_to_root\fP\&. .PP -\fB-o static_nlink\fP +\fB\-o static_nlink\fP Set st_nlink to the static value 1 for all directories. .PP @@ -122,9 +122,51 @@ be a slow operation. With this option enabled, the number of hard links reported when running stat for any directory is 1. .PP -\fB-o noacl\fP +\fB\-o noacl\fP Disable ACL support in the FUSE file system. +.PP +\fB\-o xino=off|auto|on\fP +Controls how \fIst_ino\fP values are generated for files exposed by fuse-overlayfs. + +When all lower and upper layers reside on the same underlying device, +fuse-overlayfs exposes the real inode number from the underlying filesystem. +When layers span multiple devices, an opaque inode number is generated; by +default this value is not stable across mounts. + +The \fBxino\fP option modifies this behavior: + +.PP +\fB\-o xino=off\fP +Disables extended inode generation. This matches the default behavior: +when all layers are on the same device, the underlying inode number is used; +otherwise an opaque, non‑stable inode number is returned. + +.PP +\fB\-o xino=auto\fP +Attempts to generate stable inode numbers across mounts by hashing the file +handle returned by \fIname_to_handle_at\fP(2). +This mode is used only if all layers support \fIname_to_handle_at\fP(2); if any +layer does not, behavior falls back to \fBxino=off\fP. +If all layers are on the same device, the underlying inode number is still +used, regardless of this setting. + +.PP +\fB\-o xino=on\fP +Requires that all layers support \fIname_to_handle_at\fP(2). If they do, inode +numbers are derived from a hash of the file handle and remain stable across +mounts. +If any layer does not support \fIname_to_handle_at\fP(2), the mount fails. +As with other modes, when all layers are on the same device, the underlying +inode number always takes precedence. + +.PP +\fB\-o ino32_t\fP +Forces all returned \fIst_ino\fP values to be truncated to 32 bits. + +This option exists solely for compatibility with older 32‑bit userspaces that +cannot correctly handle 64‑bit inode numbers. It has no functional benefit on +modern systems and should not be used unless required for legacy compatibility. .SH SEE ALSO .PP diff --git a/fuse-overlayfs.1.md b/fuse-overlayfs.1.md index c4022092..e0b4090d 100644 --- a/fuse-overlayfs.1.md +++ b/fuse-overlayfs.1.md @@ -100,6 +100,44 @@ links reported when running stat for any directory is 1. **-o noacl** Disable ACL support in the FUSE file system. +**-o xino=off|auto|on** +Controls how `st_ino` values are generated for files returned by fuse-overlayfs. + +When all lower and upper layers reside on the same underlying device, +fuse-overlayfs exposes the real inode number from the underlying filesystem. +When layers span multiple devices, an opaque inode number is generated; by +default this value is not stable across mounts. + +The `xino` option modifies this behavior: + +**xino=off** +Disables extended inode generation. This matches the default behavior: +when all layers are on the same device, the underlying inode number is used; +otherwise an opaque, non‑stable inode number is returned. + +**xino=auto** +Attempts to generate stable inode numbers across mounts by hashing the file +handle returned by `name_to_handle_at(2)`. +This mode is used only if all layers support `name_to_handle_at(2)`; if any +layer does not, behavior falls back to `xino=off`. +If all layers are on the same device, the underlying inode number is still +used, regardless of this setting. + +**xino=on** +Requires that all layers support `name_to_handle_at(2)`. If they do, inode +numbers are derived from a hash of the file handle and remain stable across +mounts. +If any layer does not support `name_to_handle_at(2)`, the mount fails. +As with other modes, when all layers are on the same device, the underlying +inode number always takes precedence. + +**-o ino32_t** +Forces all returned `st_ino` values to be truncated to 32 bits. + +This option exists solely for compatibility with older 32‑bit userspaces that +cannot correctly handle 64‑bit inode numbers. It has no functional benefit on +modern systems and should not be used unless required for legacy compatibility. + # SEE ALSO **fuse**(8), **mount**(8), **user_namespaces**(7) diff --git a/fuse-overlayfs.h b/fuse-overlayfs.h index 39274f47..fcd2c6c3 100644 --- a/fuse-overlayfs.h +++ b/fuse-overlayfs.h @@ -105,6 +105,9 @@ struct ovl_data int squash_to_uid; int squash_to_gid; int static_nlink; + int ino_passthrough; + int nfs_filehandles; + int ino_t_32; int volatile_mode; @@ -137,6 +140,10 @@ struct ovl_layer void *data_source_private_data; int stat_override_mode; + + dev_t st_dev; + int nfs_filehandles; + struct file_handle *fh; }; /* a data_source defines the methods for accessing a lower layer. */ @@ -156,6 +163,7 @@ struct data_source 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); bool (*support_acls) (struct ovl_layer *l); + ino_t (*get_nfs_filehandle) (const struct ovl_layer *l, const char *path); }; /* passthrough to the file system. */ diff --git a/main.c b/main.c index 4df64ecd..e5ae0d90 100644 --- a/main.c +++ b/main.c @@ -32,8 +32,8 @@ #include #include #include +#include #include -#include #include #include #include @@ -69,9 +69,9 @@ #ifndef TEMP_FAILURE_RETRY # define TEMP_FAILURE_RETRY(expression) \ - (__extension__ ({ long int __result; \ - do __result = (long int) (expression); \ - while (__result == -1L && errno == EINTR); \ + (__extension__({ long int __result; \ + do __result = (long int) (expression); \ + while (__result == -1L && errno == EINTR); \ __result; })) #endif @@ -156,8 +156,8 @@ open_by_handle_at (int mount_fd, struct file_handle *handle, int flags) #endif #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!"); +_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 { @@ -230,12 +230,17 @@ static const struct fuse_opt ovl_opts[] = { offsetof (struct ovl_data, squash_to_uid), 1 }, { "squash_to_gid=%d", offsetof (struct ovl_data, squash_to_gid), 1 }, + { "ino32_t", + offsetof (struct ovl_data, ino_t_32), 1 }, { "static_nlink", offsetof (struct ovl_data, static_nlink), 1 }, { "volatile", /* native overlay supports "volatile" to mean fsync=0. */ offsetof (struct ovl_data, volatile_mode), 1 }, { "noacl", offsetof (struct ovl_data, noacl), 1 }, + FUSE_OPT_KEY ("xino=off", 1), + FUSE_OPT_KEY ("xino=auto", 2), + FUSE_OPT_KEY ("xino=on", 3), FUSE_OPT_END }; @@ -276,7 +281,7 @@ get_next_wd_counter () } static ino_t -node_to_inode (struct ovl_node *n) +node_to_inode (const struct ovl_node *n) { return (ino_t) n->ino; } @@ -308,7 +313,7 @@ check_writeable_proc () if (svfs.f_type != PROC_SUPER_MAGIC) { - fprintf (stderr, "invalid file system type found on /proc: %d, expected %d\n", svfs.f_fsid, PROC_SUPER_MAGIC); + fprintf (stderr, "invalid file system type found on /proc: %ld, expected %d\n", (long) svfs.f_type, PROC_SUPER_MAGIC); return; } @@ -468,6 +473,10 @@ ovl_init (void *userdata, struct fuse_conn_info *conn) conn->want |= FUSE_CAP_DONT_MASK | FUSE_CAP_SPLICE_READ | FUSE_CAP_SPLICE_WRITE | FUSE_CAP_SPLICE_MOVE; if (lo->writeback) conn->want |= FUSE_CAP_WRITEBACK_CACHE; +#ifdef FUSE_CAP_NO_EXPORT_SUPPORT + if (! lo->ino_passthrough && ! lo->nfs_filehandles && (conn->capable & FUSE_CAP_NO_EXPORT_SUPPORT)) + conn->want |= FUSE_CAP_NO_EXPORT_SUPPORT; +#endif } static struct ovl_layer * @@ -939,6 +948,26 @@ get_gid (struct ovl_data *data, gid_t id) return find_mapping (id, data, false, false); } +static ino_t +get_st_ino (const struct ovl_node *node, const struct ovl_data *data) +{ + const struct ovl_layer *l = node->layer; + + uint64_t ino; + + if (data->ino_passthrough) + ino = node->tmp_ino; + else if (data->nfs_filehandles) + ino = l->ds->get_nfs_filehandle (l, node->path); + else + ino = node_to_inode (node) >> __builtin_ctz (alignof (max_align_t)); + + if (data->ino_t_32) + return (uint32_t) ino; + + return ino; +} + static int rpl_stat (fuse_req_t req, struct ovl_node *node, int fd, const char *path, struct stat *st_in, struct stat *st) { @@ -956,9 +985,7 @@ rpl_stat (fuse_req_t req, struct ovl_node *node, int fd, const char *path, struc st->st_uid = find_mapping (st->st_uid, data, true, true); st->st_gid = find_mapping (st->st_gid, data, true, false); - - st->st_ino = node->tmp_ino; - st->st_dev = node->tmp_dev; + st->st_ino = get_st_ino (node, data); if (node->loaded && node->n_links > 0) st->st_nlink = node->n_links; @@ -2082,6 +2109,12 @@ read_dirs (struct ovl_data *lo, char *path, bool low, struct ovl_layer *layers) l->path = NULL; l->fd = -1; + /* If the plugin doesn't override it, we set st_dev to a hash of the + * text configuring the plugin. This means that different layers + * will be deemed to be on different devices. + */ + l->st_dev = hash_string (it, -1); + if (l->ds->load_data_source (l, data, path, i) < 0) { fprintf (stderr, "cannot load store %s at %s\n", data, path); @@ -2554,8 +2587,7 @@ ovl_do_readdir (fuse_req_t req, fuse_ino_t ino, size_t size, /* From the 'stbuf' argument the st_ino field and bits 12-15 of the * st_mode field are used. The other fields are ignored. */ - st->st_ino = node->tmp_ino; - st->st_dev = node->tmp_dev; + st->st_ino = get_st_ino (node, lo); st->st_mode = node->ino->mode; entsize = fuse_add_direntry (req, p, remaining, name, st, offset + 1); @@ -5621,6 +5653,12 @@ fuse_opt_proc (void *data, const char *arg, int key, struct fuse_args *outargs) if (strcmp (arg, "ro") == 0) return 1; + if (key >= 1 && key <= 3) + { + ovl_data->nfs_filehandles = key - 1; + return 0; + } + if (key == FUSE_OPT_KEY_NONOPT) { if (ovl_data->mountpoint) @@ -5731,6 +5769,9 @@ main (int argc, char *argv[]) .squash_to_uid = -1, .squash_to_gid = -1, .static_nlink = 0, + .ino_passthrough = 1, + .nfs_filehandles = 0, + .ino_t_32 = 0, .xattr_permissions = 0, .euid = geteuid (), .timeout = 1000000000.0, @@ -5758,7 +5799,7 @@ main (int argc, char *argv[]) read_overflowids (); - pthread_mutex_init (&lock, PTHREAD_MUTEX_DEFAULT); + pthread_mutex_init (&lock, NULL); if (opts.show_help) { @@ -5837,8 +5878,24 @@ main (int argc, char *argv[]) lo.layers = layers; - for (tmp_layer = layers; ! lo.noacl && tmp_layer; tmp_layer = tmp_layer->next) + for (tmp_layer = layers; tmp_layer; tmp_layer = tmp_layer->next) { + if (tmp_layer->st_dev != layers->st_dev) + lo.ino_passthrough = 0; + if (tmp_layer->nfs_filehandles == -1) + { + if (lo.nfs_filehandles == 2) + error (EXIT_FAILURE, 0, "Unexpected error on %s while checking for nfs filehandle support", tmp_layer->path); + else + warn ("Unexpected error on %s while checking for nfs filehandle support", tmp_layer->path); + lo.nfs_filehandles = 0; + } + else if (tmp_layer->nfs_filehandles == 0) + { + if (lo.nfs_filehandles == 2) + error (EXIT_FAILURE, 0, "xino=on but %s does not support nfs filehandles", tmp_layer->path); + lo.nfs_filehandles = 0; + } if (! tmp_layer->ds->support_acls (tmp_layer)) lo.noacl = 1; } diff --git a/plugin-manager.h b/plugin-manager.h index 81d6a052..1e578491 100644 --- a/plugin-manager.h +++ b/plugin-manager.h @@ -37,7 +37,7 @@ struct ovl_plugin_context *load_plugins (const char *plugins); /* taken from glibc unistd.h and fixes musl */ #ifndef TEMP_FAILURE_RETRY #define TEMP_FAILURE_RETRY(expression) \ - (__extension__ ({ long int __result; \ + (__extension__({ long int __result; \ do __result = (long int) (expression); \ while (__result == -1L && errno == EINTR); \ __result; })) diff --git a/tests/test-mmap.sh b/tests/test-mmap.sh new file mode 100755 index 00000000..8bde455e --- /dev/null +++ b/tests/test-mmap.sh @@ -0,0 +1,70 @@ +#!/bin/bash + +set -e + +BIN=fuse-overlayfs +BIN=../fuse-overlayfs + +cleanup() +{ + umount mnt || true + umount ext1 ext2 || true + rm -fr mnt img1 img2 ext1 ext2 mupper mlower a.out test.c + + echo + echo "FAILED" + echo +} + +trap cleanup EXIT + +rm -fr mnt img1 img2 ext1 ext2 + +# override any other settings. +export LC_ALL=C + +echo +echo "Testing mmap where files have the same inodes across layers." + +rm -f img1 +truncate -s 10M img1 +/sbin/mke2fs -t ext2 img1 >& /dev/null +rm -f img2 +truncate -s 10M img2 +/sbin/mke2fs -t ext2 img2 >& /dev/null +mkdir -p ext1 +mkdir -p ext2 +fuse2fs -o fakeroot,uid=$(id -u),gid=$(id -g) img1 ext1 +fuse2fs -o fakeroot,uid=$(id -u),gid=$(id -g) img2 ext2 + +# We assume that the files will get the same inode number on both layers, this is checked in test-passthrough.sh for a single file. +dst=ext1 +for i in $( ldd $( gcc -print-prog-name=cc1 ) | awk '{print $3}' ); do + cp $i $dst + if [[ $dst == ext1 ]]; then + dst=ext2 + else + dst=ext1 + fi +done + +mkdir mnt +$BIN -o lowerdir=ext2:ext1 mnt + +echo "int main(void) { return 0; }" >test.c + +LD_LIBRARY_PATH=mnt/ gcc test.c + +echo "PASSED" + +umount mnt + +umount ext1 +umount ext2 + +rm -fr mnt img1 img2 ext1 ext2 a.out test.c + +trap - EXIT +echo +echo "FINISHED" +echo diff --git a/tests/test-passthrough.sh b/tests/test-passthrough.sh new file mode 100755 index 00000000..69bdab08 --- /dev/null +++ b/tests/test-passthrough.sh @@ -0,0 +1,180 @@ +#!/bin/bash + +set -e + +BIN=fuse-overlayfs +BIN=../fuse-overlayfs + +cleanup() +{ + umount mnt || true + umount mupper || true + umount mlower || true + umount ext1 ext2 || true + rm -fr lower upper mnt img1 img2 ext1 ext2 mupper mlower + + echo + echo "FAILED" + echo +} + +fail() +{ + echo "$1" + return 1 +} + +trap cleanup EXIT + +rm -fr lower upper mnt img1 img2 ext1 ext2 mupper mlower + +mkdir -p lower/dir/dirl +mkdir -p upper/dir/diru +mkdir -p mnt + +touch lower/dir/lower +touch upper/dir/upper + +# override any other settings. +export LC_ALL=C + +test_d_ino() +{ + local inodes=() + mapfile -t inodes < <( ls -li mnt/dir | awk '!/^total / { print $1}' ) + local dl=$( stat -c %i mnt/dir/dirl ) + local du=$( stat -c %i mnt/dir/diru ) + local fl=$( stat -c %i mnt/dir/lower ) + local fu=$( stat -c %i mnt/dir/upper ) + + [[ $dl == ${inodes[0]} ]] || fail "Expected st_ino $dl == d_ino ${inodes[0]}" + [[ $du == ${inodes[1]} ]] || fail "Expected st_ino $du == d_ino ${inodes[1]}" + [[ $fl == ${inodes[2]} ]] || fail "Expected st_ino $fl == d_ino ${inodes[2]}" + [[ $fu == ${inodes[3]} ]] || fail "Expected st_ino $fu == d_ino ${inodes[3]}" +} + +$BIN -o lowerdir=lower:upper mnt + +echo +echo "All on same fs - ino passthrough - stable ino" + +ldl=$( stat -c %i lower/dir/dirl ) +lfl=$( stat -c %i lower/dir/lower ) +udu=$( stat -c %i upper/dir/diru ) +ufu=$( stat -c %i upper/dir/upper ) +mdl=$( stat -c %i mnt/dir/dirl ) +mfl=$( stat -c %i mnt/dir/lower ) +mdu=$( stat -c %i mnt/dir/diru ) +mfu=$( stat -c %i mnt/dir/upper ) + +[[ $lfl == $mfl ]] || fail "In passthrough mode expected file lower to have the same st_ino via direct access and via $BIN Expected $lfl == $mfl " +[[ $ufu == $mfu ]] || fail "In passthrough mode expected file upper to have the same st_ino via direct access and via $BIN Expected $ufu == $mfu " +[[ $ldl == $mdl ]] || fail "In passthrough mode expected directory dirl to have the same st_ino via direct access and via $BIN Expected $ldl == $mdl " +[[ $udu == $mdu ]] || fail "In passthrough mode expected directory diru to have the same st_ino via direct access and via $BIN Expected $udu == $mdu " + +test_d_ino +echo "PASSED" + +umount mnt + +echo +echo "Checking that same inode on multiple layers maps to unique inodes on fuse" + +rm -f img1 +truncate -s 10M img1 +/sbin/mke2fs -t ext2 img1 >& /dev/null +rm -f img2 +truncate -s 10M img2 +/sbin/mke2fs -t ext2 img2 >& /dev/null +mkdir -p ext1 +mkdir -p ext2 +fuse2fs -o fakeroot,uid=$(id -u),gid=$(id -g) img1 ext1 +fuse2fs -o fakeroot,uid=$(id -u),gid=$(id -g) img2 ext2 + +touch ext1/lower +touch ext2/upper +$BIN -o lowerdir=ext2:ext1 mnt + +el=$( stat -c %i ext1/lower ) +eu=$( stat -c %i ext2/upper ) +ml=$( stat -c %i mnt/lower ) +mu=$( stat -c %i mnt/upper ) + +# Would be weird for this to fail but lets confirm that the first file has the same st_ino on both devices. +[[ $el == $eu ]] || fail "Expected first file on separate ext2 file systems to have the same st_ino. This requires a fix to the test to ensure this happens" + +# Confirm when looking at the files via the mount that they have different st_ino +[[ $ml != $mu ]] || fail "Duplicated st_ino on $BIN - this will cause issues" + +echo "PASSED" + +umount mnt + +umount ext1 +rmdir ext1 +rm ext2/upper + +mkdir -p ext2 + +echo +echo "Upper on different fs - non-stable ino across mounts" +$BIN -o lowerdir=lower:upper:ext2 mnt +test_d_ino +umount mnt +$BIN -o lowerdir=lower:upper:ext2 mnt +test_d_ino +# No need to check anything here - we expect it to be different but it's not an error if they're not +echo "PASSED" +umount mnt + +echo +echo "Upper on different fs xino - stable ino across mounts" +$BIN -o xino=auto,lowerdir=lower:upper:ext2 mnt +test_d_ino +mdl=$( stat -c %i mnt/dir/dirl ) +mfl=$( stat -c %i mnt/dir/lower ) +mdu=$( stat -c %i mnt/dir/diru ) +mfu=$( stat -c %i mnt/dir/upper ) +umount mnt +$BIN -o xino=auto,lowerdir=lower:upper:ext2 mnt +test_d_ino +mdl2=$( stat -c %i mnt/dir/dirl ) +mfl2=$( stat -c %i mnt/dir/lower ) +mdu2=$( stat -c %i mnt/dir/diru ) +mfu2=$( stat -c %i mnt/dir/upper ) + +[[ $mdl == $mdl2 ]] || fail "Expected inode to be the same across mounts: xino failure Expected $mdl == $mdl2" +[[ $mfl == $mfl2 ]] || fail "Expected inode to be the same across mounts: xino failure Expected $mfl == $mfl2" +[[ $mdu == $mdu2 ]] || fail "Expected inode to be the same across mounts: xino failure Expected $mdu == $mdu2" +[[ $mfu == $mfu2 ]] || fail "Expected inode to be the same across mounts: xino failure Expected $mfu == $mfu2" + +echo "PASSED" +umount mnt + +echo +echo "double fuse - disables xino - non-stable ino across mounts (assumes FUSE_CAP_NO_EXPORT_SUPPORT supported)" +mkdir -p mlower +$BIN -o xino=off,lowerdir=lower:ext2 mlower +mkdir -p mupper +$BIN -o xino=off,lowerdir=upper:ext2 mupper + +$BIN -o xino=auto,lowerdir=mlower:mupper mnt +test_d_ino +umount mnt +$BIN -o xino=auto,lowerdir=mlower:mupper mnt +test_d_ino +# No need to check anything here - we expect it to be different but it's not an error if they're not (and they will be the same if FUSE_CAP_NO_EXPORT_SUPPORT is not supported. + +echo "PASSED" +umount mnt + +umount mupper +umount mlower +umount ext2 + +rm -fr lower upper mnt img1 img2 ext1 ext2 mupper mlower + +trap - EXIT +echo +echo "FINISHED" +echo diff --git a/utils.c b/utils.c index b9317bb1..97b70c27 100644 --- a/utils.c +++ b/utils.c @@ -33,7 +33,7 @@ #ifndef TEMP_FAILURE_RETRY # define TEMP_FAILURE_RETRY(expression) \ - (__extension__ ({ long int __result; \ + (__extension__({ long int __result; \ do __result = (long int) (expression); \ while (__result == -1L && errno == EINTR); \ __result; }))