Message ID | 167243874650.722028.10607547751700517177.stgit@magnolia (mailing list archive) |
---|---|
State | New, archived |
Headers | show |
Series | fstests: strengthen fuzz testing | expand |
On Fri, Dec 30, 2022 at 02:19:06PM -0800, Darrick J. Wong wrote: > From: Darrick J. Wong <djwong@kernel.org> > > 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@kernel.org> > --- > configure.ac | 5 + > include/builddefs.in | 4 + > m4/package_libcdev.m4 | 47 ++++++++ > m4/package_xfslibs.m4 | 16 +++ > src/Makefile | 10 ++ > src/xfsfind.c | 290 +++++++++++++++++++++++++++++++++++++++++++++++++ > 6 files changed, 372 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..e1b381c16f 100644 > --- a/m4/package_libcdev.m4 > +++ b/m4/package_libcdev.m4 > @@ -110,3 +110,50 @@ 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_TRY_LINK([ The AC_TRY_LINK is obsolete by autoconf, refer to: https://www.gnu.org/software/autoconf/manual/autoconf-2.69/html_node/Obsolete-Macros.html So as the suggestion of above link, we'd better to replace: Macro: AC_TRY_LINK (includes, function-body, [action-if-true], [action-if-false]) with: AC_LINK_IFELSE( [AC_LANG_PROGRAM([[includes]], [[function-body]])], [action-if-true], [action-if-false]) For example (hope it's right:) diff --git a/m4/package_libcdev.m4 b/m4/package_libcdev.m4 index e1b381c1..7f1767a4 100644 --- a/m4/package_libcdev.m4 +++ b/m4/package_libcdev.m4 @@ -113,13 +113,13 @@ AC_DEFUN([AC_HAVE_COPY_FILE_RANGE], # Check if we have SEEK_DATA AC_DEFUN([AC_HAVE_SEEK_DATA], [ AC_MSG_CHECKING([for SEEK_DATA]) - AC_TRY_LINK([ + 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 + ]])], have_seek_data=yes AC_MSG_RESULT(yes), AC_MSG_RESULT(no)) AC_SUBST(have_seek_data) > +#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_TRY_LINK([ Same as above > +#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_TRY_LINK([ Same as above > +#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..479f30a29b 100644 > --- a/m4/package_xfslibs.m4 > +++ b/m4/package_xfslibs.m4 > @@ -104,3 +104,19 @@ 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_TRY_LINK([ Same as above Thanks, Zorro > +#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 afdf6b30c5..7807ca89a5 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; > +} >
On Sun, Feb 05, 2023 at 08:57:47PM +0800, Zorro Lang wrote: > On Fri, Dec 30, 2022 at 02:19:06PM -0800, Darrick J. Wong wrote: > > From: Darrick J. Wong <djwong@kernel.org> > > > > 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@kernel.org> > > --- > > configure.ac | 5 + > > include/builddefs.in | 4 + > > m4/package_libcdev.m4 | 47 ++++++++ > > m4/package_xfslibs.m4 | 16 +++ > > src/Makefile | 10 ++ > > src/xfsfind.c | 290 +++++++++++++++++++++++++++++++++++++++++++++++++ > > 6 files changed, 372 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..e1b381c16f 100644 > > --- a/m4/package_libcdev.m4 > > +++ b/m4/package_libcdev.m4 > > @@ -110,3 +110,50 @@ 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_TRY_LINK([ > > The AC_TRY_LINK is obsolete by autoconf, refer to: > https://www.gnu.org/software/autoconf/manual/autoconf-2.69/html_node/Obsolete-Macros.html > > So as the suggestion of above link, we'd better to replace: > Macro: AC_TRY_LINK (includes, function-body, [action-if-true], [action-if-false]) > with: > AC_LINK_IFELSE( > [AC_LANG_PROGRAM([[includes]], > [[function-body]])], > [action-if-true], > [action-if-false]) > > For example (hope it's right:) Yeah... this patch was written so long ago I wasn't even aware of the deprecations. I've run autoupdate to fix the problems and will repost. --D > diff --git a/m4/package_libcdev.m4 b/m4/package_libcdev.m4 > index e1b381c1..7f1767a4 100644 > --- a/m4/package_libcdev.m4 > +++ b/m4/package_libcdev.m4 > @@ -113,13 +113,13 @@ AC_DEFUN([AC_HAVE_COPY_FILE_RANGE], > # Check if we have SEEK_DATA > AC_DEFUN([AC_HAVE_SEEK_DATA], > [ AC_MSG_CHECKING([for SEEK_DATA]) > - AC_TRY_LINK([ > + 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 > + ]])], have_seek_data=yes > AC_MSG_RESULT(yes), > AC_MSG_RESULT(no)) > AC_SUBST(have_seek_data) > > > +#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_TRY_LINK([ > > Same as above > > > +#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_TRY_LINK([ > > Same as above > > > +#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..479f30a29b 100644 > > --- a/m4/package_xfslibs.m4 > > +++ b/m4/package_xfslibs.m4 > > @@ -104,3 +104,19 @@ 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_TRY_LINK([ > > Same as above > > Thanks, > Zorro > > > +#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 afdf6b30c5..7807ca89a5 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; > > +} > > >
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..e1b381c16f 100644 --- a/m4/package_libcdev.m4 +++ b/m4/package_libcdev.m4 @@ -110,3 +110,50 @@ 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_TRY_LINK([ +#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_TRY_LINK([ +#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_TRY_LINK([ +#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..479f30a29b 100644 --- a/m4/package_xfslibs.m4 +++ b/m4/package_xfslibs.m4 @@ -104,3 +104,19 @@ 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_TRY_LINK([ +#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 afdf6b30c5..7807ca89a5 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; +}