On Tue, Feb 07, 2023 at 09:01:41AM -0800, Darrick J. Wong wrote: > From: Darrick J. Wong <djwong@xxxxxxxxxx> > > Create a new find(1) like utility that doesn't crash on directory tree > changes (like find does due to bugs in its loop detector) and actually > implements the custom xfs attribute predicates that we need for scrub > stress tests. This program will be needed for a future patch where we > add stress tests for scrub and repair of file metadata. > > Signed-off-by: Darrick J. Wong <djwong@xxxxxxxxxx> > --- > v24.1: apply some autoupdate love to the m4 macros > --- This version looks better to me, I'll merge it with a little change on .gitignore file to filter src/xfsfind. Reviewed-by: Zorro Lang <zlang@xxxxxxxxxx> > configure.ac | 5 + > include/builddefs.in | 4 + > m4/package_libcdev.m4 | 44 +++++++ > m4/package_xfslibs.m4 | 15 +++ > src/Makefile | 10 ++ > src/xfsfind.c | 290 +++++++++++++++++++++++++++++++++++++++++++++++++ > 6 files changed, 368 insertions(+) > create mode 100644 src/xfsfind.c > > diff --git a/configure.ac b/configure.ac > index cbf8377988..e92bd6b26d 100644 > --- a/configure.ac > +++ b/configure.ac > @@ -66,6 +66,11 @@ AC_PACKAGE_WANT_LINUX_FS_H > AC_PACKAGE_WANT_LIBBTRFSUTIL > > AC_HAVE_COPY_FILE_RANGE > +AC_HAVE_SEEK_DATA > +AC_HAVE_BMV_OF_SHARED > +AC_HAVE_NFTW > +AC_HAVE_RLIMIT_NOFILE > + > AC_CHECK_FUNCS([renameat2]) > AC_CHECK_FUNCS([reallocarray]) > AC_CHECK_TYPES([struct mount_attr], [], [], [[#include <linux/mount.h>]]) > diff --git a/include/builddefs.in b/include/builddefs.in > index 6641209f81..dab10c968f 100644 > --- a/include/builddefs.in > +++ b/include/builddefs.in > @@ -68,6 +68,10 @@ HAVE_FIEMAP = @have_fiemap@ > HAVE_FALLOCATE = @have_fallocate@ > HAVE_COPY_FILE_RANGE = @have_copy_file_range@ > HAVE_LIBBTRFSUTIL = @have_libbtrfsutil@ > +HAVE_SEEK_DATA = @have_seek_data@ > +HAVE_NFTW = @have_nftw@ > +HAVE_BMV_OF_SHARED = @have_bmv_of_shared@ > +HAVE_RLIMIT_NOFILE = @have_rlimit_nofile@ > > GCCFLAGS = -funsigned-char -fno-strict-aliasing -Wall > > diff --git a/m4/package_libcdev.m4 b/m4/package_libcdev.m4 > index 5c76c0f73e..98572aecd9 100644 > --- a/m4/package_libcdev.m4 > +++ b/m4/package_libcdev.m4 > @@ -110,3 +110,47 @@ AC_DEFUN([AC_HAVE_COPY_FILE_RANGE], > AC_SUBST(have_copy_file_range) > ]) > > +# Check if we have SEEK_DATA > +AC_DEFUN([AC_HAVE_SEEK_DATA], > + [ AC_MSG_CHECKING([for SEEK_DATA]) > + AC_LINK_IFELSE([AC_LANG_PROGRAM([[ > +#define _GNU_SOURCE > +#include <sys/types.h> > +#include <unistd.h> > + ]], [[ > + lseek(-1, 0, SEEK_DATA); > + ]])],[have_seek_data=yes > + AC_MSG_RESULT(yes)],[AC_MSG_RESULT(no)]) > + AC_SUBST(have_seek_data) > + ]) > + > +# Check if we have nftw > +AC_DEFUN([AC_HAVE_NFTW], > + [ AC_MSG_CHECKING([for nftw]) > + AC_LINK_IFELSE([AC_LANG_PROGRAM([[ > +#define _GNU_SOURCE > +#include <stddef.h> > +#include <ftw.h> > + ]], [[ > + nftw("/", (int (*)(const char *, const struct stat *, int, struct FTW *))1, 0, 0); > + ]])],[have_nftw=yes > + AC_MSG_RESULT(yes)],[AC_MSG_RESULT(no)]) > + AC_SUBST(have_nftw) > + ]) > + > +# Check if we have RLIMIT_NOFILE > +AC_DEFUN([AC_HAVE_RLIMIT_NOFILE], > + [ AC_MSG_CHECKING([for RLIMIT_NOFILE]) > + AC_LINK_IFELSE([AC_LANG_PROGRAM([[ > +#define _GNU_SOURCE > +#include <sys/time.h> > +#include <sys/resource.h> > + ]], [[ > + struct rlimit rlimit; > + > + rlimit.rlim_cur = 0; > + getrlimit(RLIMIT_NOFILE, &rlimit); > + ]])],[have_rlimit_nofile=yes > + AC_MSG_RESULT(yes)],[AC_MSG_RESULT(no)]) > + AC_SUBST(have_rlimit_nofile) > + ]) > diff --git a/m4/package_xfslibs.m4 b/m4/package_xfslibs.m4 > index 0746cd1dc5..8ef58cc064 100644 > --- a/m4/package_xfslibs.m4 > +++ b/m4/package_xfslibs.m4 > @@ -104,3 +104,18 @@ AC_DEFUN([AC_PACKAGE_NEED_XFSCTL_MACRO], > exit 1 > ]) > ]) > + > +# Check if we have BMV_OF_SHARED from the GETBMAPX ioctl > +AC_DEFUN([AC_HAVE_BMV_OF_SHARED], > + [ AC_MSG_CHECKING([for BMV_OF_SHARED]) > + AC_LINK_IFELSE([AC_LANG_PROGRAM([[ > +#define _GNU_SOURCE > +#include <xfs/xfs.h> > + ]], [[ > + struct getbmapx obj; > + ioctl(-1, XFS_IOC_GETBMAPX, &obj); > + obj.bmv_oflags |= BMV_OF_SHARED; > + ]])],[have_bmv_of_shared=yes > + AC_MSG_RESULT(yes)],[AC_MSG_RESULT(no)]) > + AC_SUBST(have_bmv_of_shared) > + ]) > diff --git a/src/Makefile b/src/Makefile > index f270015ce8..a574f7bd03 100644 > --- a/src/Makefile > +++ b/src/Makefile > @@ -83,6 +83,16 @@ ifeq ($(HAVE_LIBCAP), true) > LLDLIBS += -lcap > endif > > +ifeq ($(HAVE_SEEK_DATA), yes) > + ifeq ($(HAVE_NFTW), yes) > + ifeq ($(HAVE_BMV_OF_SHARED), yes) > + ifeq ($(HAVE_RLIMIT_NOFILE), yes) > + TARGETS += xfsfind > + endif > + endif > + endif > +endif > + > CFILES = $(TARGETS:=.c) > LDIRT = $(TARGETS) fssum > > diff --git a/src/xfsfind.c b/src/xfsfind.c > new file mode 100644 > index 0000000000..6b0a93e793 > --- /dev/null > +++ b/src/xfsfind.c > @@ -0,0 +1,290 @@ > +// SPDX-License-Identifier: GPL-2.0 > +/* > + * find(1) but with special predicates for finding XFS attributes. > + * Copyright (C) 2022 Oracle. > + */ > +#include <sys/time.h> > +#include <sys/resource.h> > +#include <sys/types.h> > +#include <stdio.h> > +#include <unistd.h> > +#include <ftw.h> > +#include <linux/fs.h> > +#include <xfs/xfs.h> > + > +#include "global.h" > + > +static int want_anyfile; > +static int want_datafile; > +static int want_attrfile; > +static int want_dir; > +static int want_regfile; > +static int want_sharedfile; > +static int report_errors = 1; > + > +static int > +check_datafile( > + const char *path, > + int fd) > +{ > + off_t off; > + > + off = lseek(fd, 0, SEEK_DATA); > + if (off >= 0) > + return 1; > + > + if (errno == ENXIO) > + return 0; > + > + if (report_errors) > + perror(path); > + > + return -1; > +} > + > +static int > +check_attrfile( > + const char *path, > + int fd) > +{ > + struct fsxattr fsx; > + int ret; > + > + ret = ioctl(fd, XFS_IOC_FSGETXATTR, &fsx); > + if (ret) { > + if (report_errors) > + perror(path); > + return -1; > + } > + > + if (want_attrfile && (fsx.fsx_xflags & XFS_XFLAG_HASATTR)) > + return 1; > + return 0; > +} > + > +#define BMAP_NR 33 > +static struct getbmapx bmaps[BMAP_NR]; > + > +static int > +check_sharedfile( > + const char *path, > + int fd) > +{ > + struct getbmapx *key = &bmaps[0]; > + unsigned int i; > + int ret; > + > + memset(key, 0, sizeof(struct getbmapx)); > + key->bmv_length = ULLONG_MAX; > + /* no holes and don't flush dirty pages */ > + key->bmv_iflags = BMV_IF_DELALLOC | BMV_IF_NO_HOLES; > + key->bmv_count = BMAP_NR; > + > + while ((ret = ioctl(fd, XFS_IOC_GETBMAPX, bmaps)) == 0) { > + struct getbmapx *p = &bmaps[1]; > + xfs_off_t new_off; > + > + for (i = 0; i < key->bmv_entries; i++, p++) { > + if (p->bmv_oflags & BMV_OF_SHARED) > + return 1; > + } > + > + if (key->bmv_entries == 0) > + break; > + p = key + key->bmv_entries; > + if (p->bmv_oflags & BMV_OF_LAST) > + return 0; > + > + new_off = p->bmv_offset + p->bmv_length; > + key->bmv_length -= new_off - key->bmv_offset; > + key->bmv_offset = new_off; > + } > + if (ret < 0) { > + if (report_errors) > + perror(path); > + return -1; > + } > + > + return 0; > +} > + > +static void > +print_help( > + const char *name) > +{ > + printf("Usage: %s [OPTIONS] path\n", name); > + printf("\n"); > + printf("Print all file paths matching any of the given predicates.\n"); > + printf("\n"); > + printf("-a Match files with xattrs.\n"); > + printf("-b Match files with data blocks.\n"); > + printf("-d Match directories.\n"); > + printf("-q Ignore errors while walking directory tree.\n"); > + printf("-r Match regular files.\n"); > + printf("-s Match files with shared blocks.\n"); > + printf("\n"); > + printf("If no matching options are given, match all files found.\n"); > +} > + > +static int > +visit( > + const char *path, > + const struct stat *sb, > + int typeflag, > + struct FTW *ftwbuf) > +{ > + int printme = 1; > + int fd = -1; > + int retval = FTW_CONTINUE; > + > + if (want_anyfile) > + goto out; > + if (want_regfile && typeflag == FTW_F) > + goto out; > + if (want_dir && typeflag == FTW_D) > + goto out; > + > + /* > + * We can only open directories and files; screen out everything else. > + * Note that nftw lies and reports FTW_F for device files, so check the > + * statbuf mode too. > + */ > + if (typeflag != FTW_F && typeflag != FTW_D) { > + printme = 0; > + goto out; > + } > + > + if (!S_ISREG(sb->st_mode) && !S_ISDIR(sb->st_mode)) { > + printme = 0; > + goto out; > + } > + > + fd = open(path, O_RDONLY); > + if (fd < 0) { > + if (report_errors) { > + perror(path); > + return FTW_STOP; > + } > + > + return FTW_CONTINUE; > + } > + > + if (want_datafile && typeflag == FTW_F) { > + int ret = check_datafile(path, fd); > + if (ret < 0 && report_errors) { > + printme = 0; > + retval = FTW_STOP; > + goto out_fd; > + } > + > + if (ret == 1) > + goto out_fd; > + } > + > + if (want_attrfile) { > + int ret = check_attrfile(path, fd); > + if (ret < 0 && report_errors) { > + printme = 0; > + retval = FTW_STOP; > + goto out_fd; > + } > + > + if (ret == 1) > + goto out_fd; > + } > + > + if (want_sharedfile) { > + int ret = check_sharedfile(path, fd); > + if (ret < 0 && report_errors) { > + printme = 0; > + retval = FTW_STOP; > + goto out_fd; > + } > + > + if (ret == 1) > + goto out_fd; > + } > + > + printme = 0; > +out_fd: > + close(fd); > +out: > + if (printme) > + printf("%s\n", path); > + return retval; > +} > + > +static void > +handle_sigabrt( > + int signal, > + siginfo_t *info, > + void *ucontext) > +{ > + fprintf(stderr, "Signal %u, exiting.\n", signal); > + exit(2); > +} > + > +int > +main( > + int argc, > + char *argv[]) > +{ > + struct rlimit rlimit; > + struct sigaction abrt = { > + .sa_sigaction = handle_sigabrt, > + .sa_flags = SA_SIGINFO, > + }; > + int c; > + int ret; > + > + while ((c = getopt(argc, argv, "abdqrs")) >= 0) { > + switch (c) { > + case 'a': want_attrfile = 1; break; > + case 'b': want_datafile = 1; break; > + case 'd': want_dir = 1; break; > + case 'q': report_errors = 0; break; > + case 'r': want_regfile = 1; break; > + case 's': want_sharedfile = 1; break; > + default: > + print_help(argv[0]); > + return 1; > + } > + } > + > + ret = getrlimit(RLIMIT_NOFILE, &rlimit); > + if (ret) { > + perror("RLIMIT_NOFILE"); > + return 1; > + } > + > + if (!want_attrfile && !want_datafile && !want_dir && !want_regfile && > + !want_sharedfile) > + want_anyfile = 1; > + > + /* > + * nftw is known to abort() if a directory it is walking disappears out > + * from under it. Handle this with grace if the caller wants us to run > + * quietly. > + */ > + if (!report_errors) { > + ret = sigaction(SIGABRT, &abrt, NULL); > + if (ret) { > + perror("SIGABRT handler"); > + return 1; > + } > + } > + > + for (c = optind; c < argc; c++) { > + ret = nftw(argv[c], visit, rlimit.rlim_cur - 5, > + FTW_ACTIONRETVAL | FTW_CHDIR | FTW_MOUNT | > + FTW_PHYS); > + if (ret && report_errors) { > + perror(argv[c]); > + break; > + } > + } > + > + if (ret) > + return 1; > + return 0; > +} >