| // SPDX-License-Identifier: GPL-2.0 | |
| /* | |
| * Copyright (c) 2000-2003 Silicon Graphics, Inc. | |
| * All Rights Reserved. | |
| */ | |
| /* | |
| * Test for filesystem features on given mount point or device | |
| * -c test for 32bit chown support (via libc) | |
| * -t test for working rlimit/ftruncate64 (via libc) | |
| * -q test for quota support (kernel compile option) | |
| * -u test for user quota enforcement support (mount option) | |
| * -p test for project quota enforcement support (mount option) | |
| * -g test for group quota enforcement support (mount option) | |
| * -U test for user quota accounting support (mount option) | |
| * -G test for group quota accounting support (mount option) | |
| * -P test for project quota accounting support (mount option) | |
| * Return code: 0 is true, anything else is error/not supported | |
| * | |
| * Test for machine features | |
| * -A test whether AIO syscalls are available | |
| * -r test whether mount_setattr syscall is supported | |
| * -R test whether IO_URING syscalls are available | |
| * -o report a number of online cpus | |
| * -s report pagesize | |
| * -w report bits per long | |
| */ | |
| #include "global.h" | |
| #include <sys/quota.h> | |
| #include <sys/resource.h> | |
| #include <signal.h> | |
| #include <syscall.h> | |
| #include <unistd.h> | |
| #ifdef HAVE_XFS_XQM_H | |
| #include <xfs/xqm.h> | |
| #endif | |
| #ifdef HAVE_LIBAIO_H | |
| #include <libaio.h> | |
| #endif | |
| #ifdef HAVE_LIBURING_H | |
| #include <liburing.h> | |
| #endif | |
| #include "vfs/missing.h" | |
| #ifndef USRQUOTA | |
| #define USRQUOTA 0 | |
| #endif | |
| #ifndef GRPQUOTA | |
| #define GRPQUOTA 1 | |
| #endif | |
| #ifndef PRJQUOTA | |
| #define PRJQUOTA 2 | |
| #endif | |
| int verbose = 0; | |
| void | |
| usage(void) | |
| { | |
| fprintf(stderr, "Usage: feature [-v] -<q|u|g|p|U|G|P> <filesystem>\n"); | |
| fprintf(stderr, " feature [-v] -c <file>\n"); | |
| fprintf(stderr, " feature [-v] -t <file>\n"); | |
| fprintf(stderr, " feature -A | -r | -R | -o | -s | -w\n"); | |
| exit(1); | |
| } | |
| int check_big_ID(char *filename) | |
| { | |
| struct stat64 sbuf; | |
| memset(&sbuf, 0, sizeof(struct stat64)); | |
| if (lstat64(filename, &sbuf) < 0) { | |
| fprintf(stderr, "lstat64 failed on "); | |
| perror(filename); | |
| return(1); | |
| } | |
| /* 98789 is greater than 2^16 (65536) */ | |
| if ((uint32_t)sbuf.st_uid == 98789 || (uint32_t)sbuf.st_gid == 98789) | |
| return(0); | |
| if (verbose) | |
| fprintf(stderr, "lstat64 on %s gave uid=%d, gid=%d\n", | |
| filename, (int)sbuf.st_uid, (int)sbuf.st_gid); | |
| return(1); | |
| } | |
| int | |
| haschown32(char *filename) | |
| { | |
| if (check_big_ID(filename) == 0) | |
| return(0); | |
| if (chown(filename, 98789, 98789) < 0) { | |
| fprintf(stderr, "chown failed on "); | |
| perror(filename); | |
| return(1); | |
| } | |
| if (check_big_ID(filename) == 0) | |
| return(0); | |
| return (1); | |
| } | |
| int | |
| hastruncate64(char *filename) | |
| { | |
| struct rlimit64 rlimit64; | |
| off64_t bigoff = 4294967307LL; /* > 2^32 */ | |
| struct stat64 bigst; | |
| int fd; | |
| getrlimit64(RLIMIT_FSIZE, &rlimit64); | |
| rlimit64.rlim_cur = RLIM64_INFINITY; | |
| setrlimit64(RLIMIT_FSIZE, &rlimit64); | |
| signal(SIGXFSZ, SIG_IGN); | |
| if ((fd = open(filename, O_CREAT | O_RDWR, S_IRUSR | S_IWUSR)) < 0) { | |
| fprintf(stderr, "open failed on "); | |
| perror(filename); | |
| return(1); | |
| } | |
| if (ftruncate64(fd, bigoff) < 0) | |
| return(1); | |
| if (fstat64(fd, &bigst) < 0) { | |
| fprintf(stderr, "fstat64 failed on "); | |
| perror(filename); | |
| return(1); | |
| } | |
| if (verbose) | |
| fprintf(stderr, "fstat64 on %s gave sz=%lld (truncsz=%lld)\n", | |
| filename, (long long)bigst.st_size, (long long)bigoff); | |
| if (bigst.st_size != bigoff) | |
| return(1); | |
| return(0); | |
| } | |
| int | |
| hasxfsquota(int type, int q, char *device) | |
| { | |
| fs_quota_stat_t qstat; | |
| int qcmd; | |
| memset(&qstat, 0, sizeof(fs_quota_stat_t)); | |
| #ifdef QCMD | |
| if (q == 0) { | |
| if (access("/proc/fs/xfs/xqm", F_OK) < 0) { | |
| if (verbose) { | |
| fprintf(stderr, "can't access /proc/fs/xfs/xqm\n"); | |
| } | |
| return 1; | |
| } | |
| return 0; | |
| } | |
| qcmd = QCMD(Q_XGETQSTAT, type); | |
| #else | |
| if (q == 0) { | |
| if (quotactl(Q_SYNC, device, 0, (caddr_t)&qstat) == ENOPKG) { | |
| if (verbose) { | |
| fprintf(stderr, "Q_SYNC not supported\n"); | |
| } | |
| return 1; | |
| } | |
| return 0; | |
| } | |
| qcmd = Q_GETQSTAT; | |
| #endif | |
| if (quotactl(qcmd, device, 0, (caddr_t)&qstat) < 0) { | |
| if (verbose) | |
| perror("quotactl"); | |
| return (1); | |
| } | |
| else if (q == XFS_QUOTA_UDQ_ENFD && qstat.qs_flags & XFS_QUOTA_UDQ_ENFD) | |
| return (0); | |
| else if (q == XFS_QUOTA_GDQ_ENFD && qstat.qs_flags & XFS_QUOTA_GDQ_ENFD) | |
| return (0); | |
| else if (q == XFS_QUOTA_PDQ_ENFD && qstat.qs_flags & XFS_QUOTA_PDQ_ENFD) | |
| return (0); | |
| else if (q == XFS_QUOTA_UDQ_ACCT && qstat.qs_flags & XFS_QUOTA_UDQ_ACCT) | |
| return (0); | |
| else if (q == XFS_QUOTA_GDQ_ACCT && qstat.qs_flags & XFS_QUOTA_GDQ_ACCT) | |
| return (0); | |
| else if (q == XFS_QUOTA_PDQ_ACCT && qstat.qs_flags & XFS_QUOTA_PDQ_ACCT) | |
| return (0); | |
| if (verbose) | |
| fprintf(stderr, "quota type (%d) not available\n", q); | |
| return (1); | |
| } | |
| static int | |
| check_aio_support(void) | |
| { | |
| #ifdef HAVE_LIBAIO_H | |
| struct io_context *ctx = NULL; | |
| int err; | |
| err = io_setup(1, &ctx); | |
| if (err == 0) | |
| return 0; | |
| if (err == -ENOSYS) /* CONFIG_AIO=n */ | |
| return 1; | |
| fprintf(stderr, "unexpected error from io_setup(): %s\n", | |
| strerror(-err)); | |
| return 2; | |
| #else | |
| /* libaio was unavailable at build time; assume AIO is unsupported */ | |
| return 1; | |
| #endif | |
| } | |
| static int | |
| check_uring_support(void) | |
| { | |
| #ifdef HAVE_LIBURING_H | |
| struct io_uring ring; | |
| int err; | |
| err = io_uring_queue_init(1, &ring, 0); | |
| if (err == 0) | |
| return 0; | |
| if (err == -ENOSYS) /* CONFIG_IO_URING=n */ | |
| return 1; | |
| fprintf(stderr, "unexpected error from io_uring_queue_init(): %s\n", | |
| strerror(-err)); | |
| return 2; | |
| #else | |
| /* liburing is unavailable, assume IO_URING is unsupported */ | |
| return 1; | |
| #endif | |
| } | |
| static int | |
| check_mount_setattr_support(void) | |
| { | |
| int err; | |
| struct mount_attr attr = { | |
| .attr_set = MOUNT_ATTR_IDMAP, | |
| .userns_fd = -EBADF, | |
| }; | |
| /* mount_setattr() syscall not supported. */ | |
| err = sys_mount_setattr(-EBADF, "", AT_EMPTY_PATH, NULL, 0); | |
| if (err && errno == ENOSYS) | |
| return 1; | |
| /* idmapped mounts not supported */ | |
| err = sys_mount_setattr(-EBADF, ".", AT_EMPTY_PATH, &attr, sizeof(attr)); | |
| if (err && errno == E2BIG) | |
| return 1; | |
| return 0; | |
| } | |
| int | |
| main(int argc, char **argv) | |
| { | |
| int c; | |
| int Aflag = 0; | |
| int cflag = 0; | |
| int tflag = 0; | |
| int gflag = 0; | |
| int Gflag = 0; | |
| int pflag = 0; | |
| int Pflag = 0; | |
| int qflag = 0; | |
| int rflag = 0; | |
| int Rflag = 0; | |
| int sflag = 0; | |
| int uflag = 0; | |
| int Uflag = 0; | |
| int wflag = 0; | |
| int oflag = 0; | |
| char *fs = NULL; | |
| while ((c = getopt(argc, argv, "ActgGopPqrRsuUvw")) != EOF) { | |
| switch (c) { | |
| case 'A': | |
| Aflag++; | |
| break; | |
| case 'c': | |
| cflag++; | |
| break; | |
| case 't': | |
| tflag++; | |
| break; | |
| case 'g': | |
| gflag++; | |
| break; | |
| case 'G': | |
| Gflag++; | |
| break; | |
| case 'o': | |
| oflag++; | |
| break; | |
| case 'p': | |
| pflag++; | |
| break; | |
| case 'P': | |
| Pflag++; | |
| break; | |
| case 'q': | |
| qflag++; | |
| break; | |
| case 'r': | |
| rflag++; | |
| break; | |
| case 'R': | |
| Rflag++; | |
| break; | |
| case 's': | |
| sflag++; | |
| break; | |
| case 'u': | |
| uflag++; | |
| break; | |
| case 'U': | |
| Uflag++; | |
| break; | |
| case 'w': | |
| wflag++; | |
| break; | |
| case 'v': | |
| verbose++; | |
| break; | |
| default: | |
| usage(); | |
| } | |
| } | |
| /* filesystem features */ | |
| if (cflag|tflag|uflag|gflag|pflag|qflag|Uflag|Gflag|Pflag) { | |
| if (optind != argc-1) /* need a device */ | |
| usage(); | |
| fs = argv[argc-1]; | |
| } else if (Aflag || rflag || Rflag || wflag || sflag || oflag) { | |
| if (optind != argc) | |
| usage(); | |
| } else | |
| usage(); | |
| if (cflag) | |
| return(haschown32(fs)); | |
| if (tflag) | |
| return(hastruncate64(fs)); | |
| if (qflag) | |
| return(hasxfsquota(0, 0, fs)); | |
| if (gflag) | |
| return(hasxfsquota(GRPQUOTA, XFS_QUOTA_GDQ_ENFD, fs)); | |
| if (pflag) | |
| return(hasxfsquota(PRJQUOTA, XFS_QUOTA_PDQ_ENFD, fs)); | |
| if (uflag) | |
| return(hasxfsquota(USRQUOTA, XFS_QUOTA_UDQ_ENFD, fs)); | |
| if (Gflag) | |
| return(hasxfsquota(GRPQUOTA, XFS_QUOTA_GDQ_ACCT, fs)); | |
| if (Pflag) | |
| return(hasxfsquota(PRJQUOTA, XFS_QUOTA_PDQ_ACCT, fs)); | |
| if (Uflag) | |
| return(hasxfsquota(USRQUOTA, XFS_QUOTA_UDQ_ACCT, fs)); | |
| if (Aflag) | |
| return(check_aio_support()); | |
| if (rflag) | |
| return(check_mount_setattr_support()); | |
| if (Rflag) | |
| return(check_uring_support()); | |
| if (sflag) { | |
| printf("%d\n", getpagesize()); | |
| exit(0); | |
| } | |
| if (wflag) { | |
| #ifdef BITS_PER_LONG | |
| printf("%d\n", BITS_PER_LONG); | |
| #else | |
| #ifdef NBBY | |
| /* This can change under IRIX depending on whether we compile | |
| * with -n32/-32 or -64 | |
| */ | |
| printf("%d\n", (int)(NBBY * sizeof(long))); | |
| #else | |
| bozo! | |
| #endif | |
| #endif | |
| exit(0); | |
| } | |
| if (oflag) { | |
| long ncpus = -1; | |
| #if defined(_SC_NPROCESSORS_ONLN) | |
| ncpus = sysconf(_SC_NPROCESSORS_ONLN); | |
| #elif defined(_SC_NPROC_ONLN) | |
| ncpus = sysconf(_SC_NPROC_ONLN); | |
| #endif | |
| if (ncpus == -1) | |
| ncpus = 1; | |
| printf("%ld\n", ncpus); | |
| exit(0); | |
| } | |
| fprintf(stderr, "feature: dunno what you're after.\n"); | |
| return(1); | |
| } |