summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitignore1
-rw-r--r--configure.ac5
-rw-r--r--include/builddefs.in4
-rw-r--r--m4/package_libcdev.m444
-rw-r--r--m4/package_xfslibs.m415
-rw-r--r--src/Makefile10
-rw-r--r--src/xfsfind.c290
7 files changed, 369 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore
index a6f433f1..cfff8f85 100644
--- a/.gitignore
+++ b/.gitignore
@@ -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;
+}