On Fri, May 09, 2025 at 07:00:32PM +0200, Amir Goldstein wrote: > Test for kernel and filesystem support for conenctable file handles. > > With -N flag, encode connectable file handles and fail the test if the > kernel or filesystem do not support conenctable file handles. > > Verify that decoding connectable file handles always results in a non > empty path of the fd. > > Signed-off-by: Amir Goldstein <amir73il@xxxxxxxxx> > --- Reviewed-by: Zorro Lang <zlang@xxxxxxxxxx> > common/rc | 16 ++++++++++--- > src/open_by_handle.c | 53 ++++++++++++++++++++++++++++++++++++-------- > 2 files changed, 57 insertions(+), 12 deletions(-) > > diff --git a/common/rc b/common/rc > index 6592c835..6407b744 100644 > --- a/common/rc > +++ b/common/rc > @@ -3829,8 +3829,14 @@ _require_freeze() > } > > # Does NFS export work on this fs? > -_require_exportfs() > +_require_open_by_handle() > { > + local what="NFS export" > + local opts="$1" > + if [ "$1" == "-N" ]; then > + what="connectable file handles" > + fi > + > _require_test_program "open_by_handle" > > # virtiofs doesn't support open_by_handle_at(2) yet, though the syscall > @@ -3841,10 +3847,14 @@ _require_exportfs() > _notrun "$FSTYP doesn't support open_by_handle_at(2)" > > mkdir -p "$TEST_DIR"/exportfs_test > - $here/src/open_by_handle -c "$TEST_DIR"/exportfs_test 2>&1 \ > - || _notrun "$FSTYP does not support NFS export" > + $here/src/open_by_handle $opts -c "$TEST_DIR"/exportfs_test 2>&1 \ > + || _notrun "$FSTYP does not support $what" > } > > +_require_exportfs() > +{ > + _require_open_by_handle > +} > > # Does shutdown work on this fs? > _require_scratch_shutdown() > diff --git a/src/open_by_handle.c b/src/open_by_handle.c > index a99cce4b..7b664201 100644 > --- a/src/open_by_handle.c > +++ b/src/open_by_handle.c > @@ -96,6 +96,9 @@ Examples: > #ifndef AT_HANDLE_MNT_ID_UNIQUE > # define AT_HANDLE_MNT_ID_UNIQUE 0x001 > #endif > +#ifndef AT_HANDLE_CONNECTABLE > +# define AT_HANDLE_CONNECTABLE 0x002 > +#endif > > #define MAXFILES 1024 > > @@ -121,6 +124,7 @@ void usage(void) > fprintf(stderr, "open_by_handle -d <test_dir> [N] - unlink test files and hardlinks, drop caches and try to open by handle\n"); > fprintf(stderr, "open_by_handle -m <test_dir> [N] - rename test files, drop caches and try to open by handle\n"); > fprintf(stderr, "open_by_handle -M <test_dir> [N] - do not silently skip the mount ID verifications\n"); > + fprintf(stderr, "open_by_handle -N <test_dir> [N] - encode connectable file handles\n"); > fprintf(stderr, "open_by_handle -p <test_dir> - create/delete and try to open by handle also test_dir itself\n"); > fprintf(stderr, "open_by_handle -i <handles_file> <test_dir> [N] - read test files handles from file and try to open by handle\n"); > fprintf(stderr, "open_by_handle -o <handles_file> <test_dir> [N] - get file handles of test files and write handles to file\n"); > @@ -130,14 +134,16 @@ void usage(void) > fprintf(stderr, "open_by_handle -C <feature> - check if <feature> is supported by the kernel.\n"); > fprintf(stderr, " <feature> can be any of the following values:\n"); > fprintf(stderr, " - AT_HANDLE_MNT_ID_UNIQUE\n"); > + fprintf(stderr, " - AT_HANDLE_CONNECTABLE\n"); > exit(EXIT_FAILURE); > } > > -static int do_name_to_handle_at(const char *fname, struct file_handle *fh, > - int bufsz, bool force_check_mountid) > +static int do_name_to_handle_at(const char *fname, struct file_handle *fh, int bufsz, > + bool force_check_mountid, bool connectable) > { > int ret; > int mntid_short; > + int at_flags; > > static bool skip_mntid, skip_mntid_unique; > > @@ -181,18 +187,24 @@ static int do_name_to_handle_at(const char *fname, struct file_handle *fh, > } > } > > + at_flags = connectable ? AT_HANDLE_CONNECTABLE : 0; > fh->handle_bytes = bufsz; > - ret = name_to_handle_at(AT_FDCWD, fname, fh, &mntid_short, 0); > + ret = name_to_handle_at(AT_FDCWD, fname, fh, &mntid_short, at_flags); > if (bufsz < fh->handle_bytes) { > /* Query the filesystem required bufsz and the file handle */ > if (ret != -1 || errno != EOVERFLOW) { > fprintf(stderr, "%s: unexpected result from name_to_handle_at: %d (%m)\n", fname, ret); > return EXIT_FAILURE; > } > - ret = name_to_handle_at(AT_FDCWD, fname, fh, &mntid_short, 0); > + ret = name_to_handle_at(AT_FDCWD, fname, fh, &mntid_short, at_flags); > } > if (ret < 0) { > - fprintf(stderr, "%s: name_to_handle: %m\n", fname); > + /* No filesystem support for encoding connectable file handles (e.g. overlayfs)? */ > + if (connectable) > + fprintf(stderr, "%s: name_to_handle_at(AT_HANDLE_CONNECTABLE) not supported by %s\n", > + fname, errno == EINVAL ? "kernel" : "filesystem"); > + else > + fprintf(stderr, "%s: name_to_handle: %m\n", fname); > return EXIT_FAILURE; > } > > @@ -245,8 +257,17 @@ static int check_feature(const char *feature) > return EXIT_FAILURE; > } > return 0; > + } else if (!strcmp(feature, "AT_HANDLE_CONNECTABLE")) { > + int ret = name_to_handle_at(AT_FDCWD, ".", NULL, NULL, AT_HANDLE_CONNECTABLE); > + /* If AT_HANDLE_CONNECTABLE is supported, we get EFAULT. */ > + if (ret < 0 && errno == EINVAL) { > + fprintf(stderr, "name_to_handle_at(AT_HANDLE_CONNECTABLE) not supported by running kernel\n"); > + return EXIT_FAILURE; > + } > + return 0; > } > > + > fprintf(stderr, "unknown feature name '%s'\n", feature); > return EXIT_FAILURE; > } > @@ -270,13 +291,13 @@ int main(int argc, char **argv) > int create = 0, delete = 0, nlink = 1, move = 0; > int rd = 0, wr = 0, wrafter = 0, parent = 0; > int keepopen = 0, drop_caches = 1, sleep_loop = 0; > - int force_check_mountid = 0; > + bool force_check_mountid = 0, connectable = 0; > int bufsz = MAX_HANDLE_SZ; > > if (argc < 2) > usage(); > > - while ((c = getopt(argc, argv, "cC:ludmMrwapknhi:o:sz")) != -1) { > + while ((c = getopt(argc, argv, "cC:ludmMNrwapknhi:o:sz")) != -1) { > switch (c) { > case 'c': > create = 1; > @@ -313,6 +334,9 @@ int main(int argc, char **argv) > case 'M': > force_check_mountid = 1; > break; > + case 'N': > + connectable = 1; > + break; > case 'p': > parent = 1; > break; > @@ -445,7 +469,8 @@ int main(int argc, char **argv) > return EXIT_FAILURE; > } > } else { > - ret = do_name_to_handle_at(fname, &handle[i].fh, bufsz, force_check_mountid); > + ret = do_name_to_handle_at(fname, &handle[i].fh, bufsz, > + force_check_mountid, connectable); > if (ret) > return EXIT_FAILURE; > } > @@ -475,7 +500,8 @@ int main(int argc, char **argv) > return EXIT_FAILURE; > } > } else { > - ret = do_name_to_handle_at(test_dir, &dir_handle.fh, bufsz, force_check_mountid); > + ret = do_name_to_handle_at(test_dir, &dir_handle.fh, bufsz, > + force_check_mountid, connectable); > if (ret) > return EXIT_FAILURE; > } > @@ -589,6 +615,15 @@ int main(int argc, char **argv) > errno = 0; > fd = open_by_handle_at(mount_fd, &handle[i].fh, wrafter ? O_RDWR : O_RDONLY); > if ((nlink || keepopen) && fd >= 0) { > + char linkname[PATH_MAX]; > + char procname[64]; > + sprintf(procname, "/proc/self/fd/%i", fd); > + int n = readlink(procname, linkname, PATH_MAX); > + > + /* check that fd is "connected" - that is has a non empty path */ > + if (connectable && n <= 1) { > + printf("open_by_handle(%s) returned a disconnected fd!\n", fname); > + } > if (rd) { > char buf[4] = {0}; > int size = read(fd, buf, 4); > -- > 2.34.1 >