diff options
-rw-r--r-- | .gitignore | 1 | ||||
-rw-r--r-- | configure.ac | 5 | ||||
-rw-r--r-- | include/builddefs.in | 4 | ||||
-rw-r--r-- | m4/package_libcdev.m4 | 44 | ||||
-rw-r--r-- | m4/package_xfslibs.m4 | 15 | ||||
-rw-r--r-- | src/Makefile | 10 | ||||
-rw-r--r-- | src/xfsfind.c | 290 |
7 files changed, 369 insertions, 0 deletions
@@ -178,6 +178,7 @@ tags /src/writemod /src/writev_on_pagefault /src/xfsctl +/src/xfsfind /src/aio-dio-regress/aio-dio-append-write-fallocate-race /src/aio-dio-regress/aio-dio-append-write-read-race /src/aio-dio-regress/aio-dio-cow-race diff --git a/configure.ac b/configure.ac index cbf83779..e92bd6b2 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 6641209f..dab10c96 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 5c76c0f7..98572aec 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 0746cd1d..8ef58cc0 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 f270015c..a574f7bd 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 00000000..6b0a93e7 --- /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; +} |