| // SPDX-License-Identifier: GPL-2.0 |
| /* |
| * Copyright (c) 2000-2003 Silicon Graphics, Inc. |
| * All Rights Reserved. |
| */ |
| |
| #include "global.h" |
| #include <xfs/jdm.h> |
| #include <pthread.h> |
| |
| |
| int debug; |
| int quiet; |
| int statit; |
| int verbose; |
| int threaded; |
| |
| /* |
| * libxfs is only available if a install-qa has been done from a local source |
| * tree. That's not always done, so add the necessary functions here when not |
| * detected by autoconf. |
| */ |
| #ifndef HAVE_XFS_LIBXFS_H |
| unsigned int |
| libxfs_log2_roundup(unsigned int i) |
| { |
| unsigned int rval; |
| |
| for (rval = 0; rval < NBBY * sizeof(i); rval++) { |
| if ((1 << rval) >= i) |
| break; |
| } |
| return rval; |
| } |
| |
| static inline int fls(int x) |
| { |
| int r = 32; |
| |
| if (!x) |
| return 0; |
| if (!(x & 0xffff0000u)) { |
| x <<= 16; |
| r -= 16; |
| } |
| if (!(x & 0xff000000u)) { |
| x <<= 8; |
| r -= 8; |
| } |
| if (!(x & 0xf0000000u)) { |
| x <<= 4; |
| r -= 4; |
| } |
| if (!(x & 0xc0000000u)) { |
| x <<= 2; |
| r -= 2; |
| } |
| if (!(x & 0x80000000u)) { |
| x <<= 1; |
| r -= 1; |
| } |
| return r; |
| } |
| |
| static inline int xfs_highbit32(__uint32_t v) |
| { |
| return fls(v) - 1; |
| } |
| #endif |
| |
| void |
| dotime(void *ti, char *s) |
| { |
| char *c; |
| xfs_bstime_t *t; |
| |
| t = (xfs_bstime_t *)ti; |
| |
| c = ctime(&t->tv_sec); |
| printf("\t%s %19.19s.%09d %s", s, c, t->tv_nsec, c + 20); |
| } |
| |
| void |
| printbstat(struct xfs_bstat *sp) |
| { |
| printf("ino %lld mode %#o nlink %d uid %d gid %d rdev %#x\n", |
| (long long)sp->bs_ino, sp->bs_mode, sp->bs_nlink, |
| sp->bs_uid, sp->bs_gid, sp->bs_rdev); |
| printf("\tblksize %d size %lld blocks %lld xflags %#x extsize %d\n", |
| sp->bs_blksize, (long long)sp->bs_size, (long long)sp->bs_blocks, |
| sp->bs_xflags, sp->bs_extsize); |
| dotime(&sp->bs_atime, "atime"); |
| dotime(&sp->bs_mtime, "mtime"); |
| dotime(&sp->bs_ctime, "ctime"); |
| printf( "\textents %d %d gen %d\n", |
| sp->bs_extents, sp->bs_aextents, sp->bs_gen); |
| printf( "\tDMI: event mask 0x%08x state 0x%04x\n", |
| sp->bs_dmevmask, sp->bs_dmstate); |
| } |
| |
| void |
| printstat(struct stat64 *sp) |
| { |
| printf("ino %lld mode %#o nlink %d uid %d gid %d rdev %#x\n", |
| (long long)sp->st_ino, (unsigned int)sp->st_mode, (int)sp->st_nlink, |
| (int)sp->st_uid, (int)sp->st_gid, (int)sp->st_rdev); |
| printf("\tblksize %llu size %lld blocks %lld\n", |
| (unsigned long long)sp->st_blksize, (long long)sp->st_size, (long long)sp->st_blocks); |
| dotime(&sp->st_atime, "atime"); |
| dotime(&sp->st_mtime, "mtime"); |
| dotime(&sp->st_ctime, "ctime"); |
| } |
| |
| static int |
| do_bstat( |
| int fsfd, |
| jdm_fshandle_t *fshandlep, |
| char *name, |
| int nent, |
| __u64 first, |
| __u64 last) |
| { |
| __s32 count; |
| int total = 0; |
| struct xfs_bstat *t; |
| int ret; |
| struct xfs_fsop_bulkreq bulkreq; |
| __u64 ino; |
| |
| t = malloc(nent * sizeof(*t)); |
| |
| if (verbose) |
| printf( |
| "XFS_IOC_FSBULKSTAT test: last=%lld nent=%d\n", |
| (long long)last, nent); |
| |
| ino = first; |
| |
| bulkreq.lastip = &ino; |
| bulkreq.icount = nent; |
| bulkreq.ubuffer = t; |
| bulkreq.ocount = &count; |
| |
| while ((ret = xfsctl(name, fsfd, XFS_IOC_FSBULKSTAT, &bulkreq)) == 0) { |
| int i; |
| |
| total += count; |
| |
| if (verbose) |
| printf( |
| "XFS_IOC_FSBULKSTAT: first/last/ino=%lld/%lld/%lld ret=%d count=%d total=%d\n", |
| (long long)first, (long long)last, |
| (long long)ino, ret, count, total); |
| if (count == 0) |
| break; |
| |
| if (quiet && !statit) |
| goto next; |
| |
| for (i = 0; i < count; i++) { |
| if (!quiet) |
| printbstat(&t[i]); |
| |
| if (statit) { |
| char *cc_readlinkbufp; |
| int cc_readlinkbufsz; |
| struct stat64 sb; |
| int nread; |
| int fd; |
| |
| switch(t[i].bs_mode & S_IFMT) { |
| case S_IFLNK: |
| cc_readlinkbufsz = MAXPATHLEN; |
| cc_readlinkbufp = (char *)calloc( |
| 1, |
| cc_readlinkbufsz); |
| nread = jdm_readlink( |
| fshandlep, |
| &t[i], |
| cc_readlinkbufp, |
| cc_readlinkbufsz); |
| if (verbose && nread > 0) |
| printf( |
| "readlink: ino %lld: <%*s>\n", |
| (long long)t[i].bs_ino, |
| nread, |
| cc_readlinkbufp); |
| free(cc_readlinkbufp); |
| if ( nread < 0 ) { |
| printf( |
| "could not read symlink ino %llu\n", |
| (unsigned long long)t[i].bs_ino ); |
| printbstat(&t[i]); |
| } |
| break; |
| |
| case S_IFCHR: |
| case S_IFBLK: |
| case S_IFIFO: |
| case S_IFSOCK: |
| break; |
| |
| case S_IFREG: |
| case S_IFDIR: |
| fd = jdm_open( fshandlep, &t[i], O_RDONLY); |
| if (fd < 0) { |
| printf( |
| "unable to open handle ino %lld: %s\n", |
| (long long)t[i].bs_ino, |
| strerror(errno)); |
| continue; |
| } |
| if (fstat64(fd, &sb) < 0) { |
| printf( |
| "unable to stat ino %lld: %s\n", |
| (long long)t[i].bs_ino, |
| strerror(errno)); |
| } |
| close(fd); |
| |
| /* |
| * Don't compare blksize or blocks, |
| * they are used differently by stat |
| * and bstat. |
| */ |
| if ( (t[i].bs_ino != sb.st_ino) || |
| (t[i].bs_mode != sb.st_mode) || |
| (t[i].bs_nlink != sb.st_nlink) || |
| (t[i].bs_uid != sb.st_uid) || |
| (t[i].bs_gid != sb.st_gid) || |
| (t[i].bs_rdev != sb.st_rdev) || |
| (t[i].bs_size != sb.st_size) || |
| /* (t[i].bs_blksize != sb.st_blksize) || */ |
| (t[i].bs_mtime.tv_sec != sb.st_mtime) || |
| (t[i].bs_ctime.tv_sec != sb.st_ctime) ) { |
| printf("\nstat/bstat missmatch\n"); |
| printbstat(&t[i]); |
| printstat(&sb); |
| } |
| } |
| } |
| } |
| next: |
| if (debug) |
| break; |
| |
| if (ino >= last) |
| break; |
| } |
| if (verbose) |
| printf( |
| "XFS_IOC_FSBULKSTAT test: last=%lld nent=%d ret=%d count=%d\n", |
| (long long)last, nent, ret, count); |
| |
| return total; |
| } |
| |
| struct thread_args { |
| pthread_t tid; |
| int fsfd; |
| jdm_fshandle_t *fshandlep; |
| char *name; |
| int nent; |
| __u64 first; |
| __u64 last; |
| int ret; |
| }; |
| |
| static void * |
| do_bstat_thread( |
| void *args) |
| { |
| struct thread_args *targs = args; |
| |
| targs->ret = do_bstat(targs->fsfd, targs->fshandlep, targs->name, |
| targs->nent, targs->first, targs->last); |
| return NULL; |
| } |
| |
| /* |
| * Always show full working: |
| * |
| * XFS_AGINO_TO_INO(mp,a,i) \ |
| * (((xfs_ino_t)(a) << XFS_INO_AGINO_BITS(mp)) | (i)) |
| * |
| * i always zero, so: |
| * a << XFS_INO_AGINO_BITS(mp) |
| * |
| * XFS_INO_AGINO_BITS(mp) (mp)->m_agino_log |
| * |
| * mp->m_agino_log = sbp->sb_inopblog + sbp->sb_agblklog |
| * |
| * sb_inopblog = fsgeom.blocksize / fsgeom.inodesize |
| * sb_agblklog = (__uint8_t)libxfs_log2_roundup((unsigned int)fsgeom.agblocks); |
| * |
| * a << (libxfs_highbit32(fsgeom.blocksize /fsgeom.inodesize) + |
| * libxfs_log2_roundup(fsgeom.agblocks)); |
| */ |
| #define FSGEOM_INOPBLOG(fsg) \ |
| (xfs_highbit32((fsg).blocksize / (fsg).inodesize)) |
| #define FSGEOM_AGINO_TO_INO(fsg, a, i) \ |
| (((__u64)(a) << (FSGEOM_INOPBLOG(fsg) + \ |
| libxfs_log2_roundup((fsg).agblocks))) | (i)) |
| |
| /* |
| * XFS_OFFBNO_TO_AGINO(mp,b,o) \ |
| * ((xfs_agino_t)(((b) << XFS_INO_OFFSET_BITS(mp)) | (o))) |
| * |
| * i always zero, so: |
| * b << XFS_INO_OFFSET_BITS(mp) |
| * |
| * XFS_INO_OFFSET_BITS(mp) (mp)->m_sb.sb_inopblog |
| */ |
| #define FSGEOM_OFFBNO_TO_AGINO(fsg, b, o) \ |
| ((__u32)(((b) << FSGEOM_INOPBLOG(fsg)) | (o))) |
| static int |
| do_threads( |
| int fsfd, |
| jdm_fshandle_t *fshandlep, |
| char *name, |
| int nent, |
| __u64 first, |
| int numthreads) |
| { |
| struct xfs_fsop_geom geom; |
| struct thread_args *targs; |
| int ntargs = 0; |
| int ret; |
| int i, j; |
| int total = 0; |
| |
| |
| /* get number of AGs */ |
| ret = ioctl(fsfd, XFS_IOC_FSGEOMETRY, &geom); |
| if (ret) { |
| perror("XFS_IOC_FSGEOMETRY"); |
| exit(1); |
| } |
| |
| /* allocate thread array */ |
| targs = malloc(geom.agcount * sizeof(*targs)); |
| if (ret) { |
| perror("malloc(targs)"); |
| exit(1); |
| } |
| |
| for (i = 0; i < geom.agcount; i++) { |
| __u64 last; |
| |
| last = FSGEOM_AGINO_TO_INO(geom, i, |
| FSGEOM_OFFBNO_TO_AGINO(geom, |
| geom.agblocks - 1, 0)); |
| |
| if (first > last) { |
| i--; |
| continue; |
| } |
| first = MAX(first, FSGEOM_AGINO_TO_INO(geom, i, 0)); |
| |
| targs[i].fsfd = fsfd; |
| targs[i].fshandlep = fshandlep; |
| targs[i].name = name; |
| targs[i].nent = nent; |
| targs[i].first = first; |
| targs[i].last = last; |
| targs[i].ret = 0; |
| } |
| ntargs = i; |
| |
| /* start threads */ |
| i = 0; |
| while (i < ntargs) { |
| for (j = 0; j < numthreads; j++) { |
| int tgt = i + j; |
| |
| if (tgt >= ntargs) |
| break; |
| |
| ret = pthread_create(&targs[tgt].tid, NULL, |
| do_bstat_thread, &targs[tgt]); |
| if (ret) { |
| perror("pthread-create"); |
| exit(1); |
| } |
| } |
| |
| |
| /* join threads */ |
| for (j--; j >= 0; j--) { |
| int tgt = i + j; |
| |
| if (targs[tgt].tid) { |
| pthread_join(targs[tgt].tid, NULL); |
| total += targs[tgt].ret; |
| } |
| } |
| i += numthreads; |
| } |
| |
| /* die */ |
| return total; |
| } |
| |
| void |
| usage(void) |
| { |
| printf("\n"\ |
| "Usage:\n"\ |
| "\n"\ |
| " xfs_bstat [-c] [-q] [-v] [-l <num>] [-t <num>] [ dir [ batch_size ]]\n"\ |
| "\n"\ |
| " -c Check the results against stat(3) output\n"\ |
| " -q Quiet\n"\ |
| " -v Verbose output\n"\ |
| " -l <num> Inode to start with\n"\ |
| " -t <num> Threads to run\n"); |
| |
| exit(1); |
| } |
| |
| |
| int |
| main(int argc, char **argv) |
| { |
| int fsfd; |
| __u64 first = 0; |
| char *name; |
| int nent; |
| int ret; |
| jdm_fshandle_t *fshandlep = NULL; |
| int c; |
| int numthreads = 0; |
| |
| while ((c = getopt(argc, argv, "cdl:qt:v")) != -1) { |
| switch (c) { |
| case 'q': |
| quiet = 1; |
| break; |
| case 'v': |
| verbose = 1; |
| break; |
| case 'c': |
| statit = 1; |
| break; |
| case 'd': |
| debug = 1; |
| break; |
| case 'l': |
| first = atoi(optarg); |
| break; |
| case 't': |
| threaded = 1; |
| numthreads = atoi(optarg); |
| break; |
| case '?': |
| default: |
| usage(); |
| } |
| } |
| argc -= optind; |
| argv += optind; |
| |
| if (argc < 1) |
| name = "."; |
| else |
| name = *argv; |
| |
| if (argc < 2) |
| nent = 4096; |
| else |
| nent = atoi(*++argv); |
| |
| fsfd = open(name, O_RDONLY); |
| if (fsfd < 0) { |
| perror(name); |
| exit(1); |
| } |
| |
| if (verbose) |
| printf("Bulkstat test on %s, batch size=%d statcheck=%d\n", |
| name, nent, statit); |
| |
| if (statit) { |
| fshandlep = jdm_getfshandle( name ); |
| if (! fshandlep) { |
| printf("unable to construct sys handle for %s: %s\n", |
| name, strerror(errno)); |
| return -1; |
| } |
| } |
| |
| if (threaded) |
| ret = do_threads(fsfd, fshandlep, name, nent, first, numthreads); |
| else |
| ret = do_bstat(fsfd, fshandlep, name, nent, first, -1LL); |
| |
| if (verbose) |
| printf("Bulkstat found %d inodes\n", ret); |
| |
| if (fsfd) |
| close(fsfd); |
| |
| if (ret < 0 ) |
| perror("xfsctl(XFS_IOC_FSBULKSTAT)"); |
| |
| return 0; |
| } |