| // 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; |
| } |