1*61145dc2SMartin Matuska /* 2*61145dc2SMartin Matuska * SPDX-License-Identifier: MIT 3*61145dc2SMartin Matuska * 4*61145dc2SMartin Matuska * Copyright (c) 2025, Rob Norris <robn@despairlabs.com> 5*61145dc2SMartin Matuska * 6*61145dc2SMartin Matuska * Permission is hereby granted, free of charge, to any person obtaining a copy 7*61145dc2SMartin Matuska * of this software and associated documentation files (the "Software"), to 8*61145dc2SMartin Matuska * deal in the Software without restriction, including without limitation the 9*61145dc2SMartin Matuska * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 10*61145dc2SMartin Matuska * sell copies of the Software, and to permit persons to whom the Software is 11*61145dc2SMartin Matuska * furnished to do so, subject to the following conditions: 12*61145dc2SMartin Matuska * 13*61145dc2SMartin Matuska * The above copyright notice and this permission notice shall be included in 14*61145dc2SMartin Matuska * all copies or substantial portions of the Software. 15*61145dc2SMartin Matuska * 16*61145dc2SMartin Matuska * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17*61145dc2SMartin Matuska * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18*61145dc2SMartin Matuska * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19*61145dc2SMartin Matuska * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20*61145dc2SMartin Matuska * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 21*61145dc2SMartin Matuska * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 22*61145dc2SMartin Matuska * IN THE SOFTWARE. 23*61145dc2SMartin Matuska */ 24*61145dc2SMartin Matuska 25*61145dc2SMartin Matuska #include <stdint.h> 26*61145dc2SMartin Matuska #include <stdio.h> 27*61145dc2SMartin Matuska #include <string.h> 28*61145dc2SMartin Matuska #include <errno.h> 29*61145dc2SMartin Matuska #include <fcntl.h> 30*61145dc2SMartin Matuska #include <sys/syscall.h> 31*61145dc2SMartin Matuska #include <unistd.h> 32*61145dc2SMartin Matuska 33*61145dc2SMartin Matuska /* 34*61145dc2SMartin Matuska * statx() may be available in the kernel, but not in the libc, so we build 35*61145dc2SMartin Matuska * our own wrapper if we can't link one. 36*61145dc2SMartin Matuska */ 37*61145dc2SMartin Matuska 38*61145dc2SMartin Matuska #ifndef __NR_statx 39*61145dc2SMartin Matuska #if defined(__x86_64__) 40*61145dc2SMartin Matuska #define __NR_statx (332) 41*61145dc2SMartin Matuska #elif defined(__i386__) 42*61145dc2SMartin Matuska #define __NR_statx (383) 43*61145dc2SMartin Matuska #elif defined(__s390__) 44*61145dc2SMartin Matuska #define __NR_statx (379) 45*61145dc2SMartin Matuska #elif defined(__arm__) 46*61145dc2SMartin Matuska #define __NR_statx (397) 47*61145dc2SMartin Matuska #elif defined(__aarch64__) 48*61145dc2SMartin Matuska #define __NR_statx (291) 49*61145dc2SMartin Matuska #elif defined(__powerpc__) 50*61145dc2SMartin Matuska #define __NR_statx (383) 51*61145dc2SMartin Matuska #else 52*61145dc2SMartin Matuska #error "no definition of __NR_statx for this platform" 53*61145dc2SMartin Matuska #endif 54*61145dc2SMartin Matuska #endif /* __NR_statx */ 55*61145dc2SMartin Matuska 56*61145dc2SMartin Matuska 57*61145dc2SMartin Matuska int 58*61145dc2SMartin Matuska statx(int, const char *, int, unsigned int, void *) 59*61145dc2SMartin Matuska __attribute__((weak)); 60*61145dc2SMartin Matuska 61*61145dc2SMartin Matuska static inline int 62*61145dc2SMartin Matuska _statx(int fd, const char *path, int flags, unsigned int mask, void *stx) 63*61145dc2SMartin Matuska { 64*61145dc2SMartin Matuska if (statx) 65*61145dc2SMartin Matuska return (statx(fd, path, flags, mask, stx)); 66*61145dc2SMartin Matuska else 67*61145dc2SMartin Matuska return (syscall(__NR_statx, fd, path, flags, mask, stx)); 68*61145dc2SMartin Matuska } 69*61145dc2SMartin Matuska 70*61145dc2SMartin Matuska #ifndef STATX_TYPE 71*61145dc2SMartin Matuska #define STATX_TYPE (1<<0) 72*61145dc2SMartin Matuska #endif 73*61145dc2SMartin Matuska #ifndef STATX_MODE 74*61145dc2SMartin Matuska #define STATX_MODE (1<<1) 75*61145dc2SMartin Matuska #endif 76*61145dc2SMartin Matuska #ifndef STATX_NLINK 77*61145dc2SMartin Matuska #define STATX_NLINK (1<<2) 78*61145dc2SMartin Matuska #endif 79*61145dc2SMartin Matuska #ifndef STATX_UID 80*61145dc2SMartin Matuska #define STATX_UID (1<<3) 81*61145dc2SMartin Matuska #endif 82*61145dc2SMartin Matuska #ifndef STATX_GID 83*61145dc2SMartin Matuska #define STATX_GID (1<<4) 84*61145dc2SMartin Matuska #endif 85*61145dc2SMartin Matuska #ifndef STATX_ATIME 86*61145dc2SMartin Matuska #define STATX_ATIME (1<<5) 87*61145dc2SMartin Matuska #endif 88*61145dc2SMartin Matuska #ifndef STATX_MTIME 89*61145dc2SMartin Matuska #define STATX_MTIME (1<<6) 90*61145dc2SMartin Matuska #endif 91*61145dc2SMartin Matuska #ifndef STATX_CTIME 92*61145dc2SMartin Matuska #define STATX_CTIME (1<<7) 93*61145dc2SMartin Matuska #endif 94*61145dc2SMartin Matuska #ifndef STATX_INO 95*61145dc2SMartin Matuska #define STATX_INO (1<<8) 96*61145dc2SMartin Matuska #endif 97*61145dc2SMartin Matuska #ifndef STATX_SIZE 98*61145dc2SMartin Matuska #define STATX_SIZE (1<<9) 99*61145dc2SMartin Matuska #endif 100*61145dc2SMartin Matuska #ifndef STATX_BLOCKS 101*61145dc2SMartin Matuska #define STATX_BLOCKS (1<<10) 102*61145dc2SMartin Matuska #endif 103*61145dc2SMartin Matuska #ifndef STATX_BTIME 104*61145dc2SMartin Matuska #define STATX_BTIME (1<<11) 105*61145dc2SMartin Matuska #endif 106*61145dc2SMartin Matuska #ifndef STATX_MNT_ID 107*61145dc2SMartin Matuska #define STATX_MNT_ID (1<<12) 108*61145dc2SMartin Matuska #endif 109*61145dc2SMartin Matuska #ifndef STATX_DIOALIGN 110*61145dc2SMartin Matuska #define STATX_DIOALIGN (1<<13) 111*61145dc2SMartin Matuska #endif 112*61145dc2SMartin Matuska 113*61145dc2SMartin Matuska typedef struct { 114*61145dc2SMartin Matuska int64_t tv_sec; 115*61145dc2SMartin Matuska uint32_t tv_nsec; 116*61145dc2SMartin Matuska int32_t _pad; 117*61145dc2SMartin Matuska } stx_timestamp_t; 118*61145dc2SMartin Matuska _Static_assert(sizeof (stx_timestamp_t) == 0x10, 119*61145dc2SMartin Matuska "stx_timestamp_t not 16 bytes"); 120*61145dc2SMartin Matuska 121*61145dc2SMartin Matuska typedef struct { 122*61145dc2SMartin Matuska uint32_t stx_mask; 123*61145dc2SMartin Matuska uint32_t stx_blksize; 124*61145dc2SMartin Matuska uint64_t stx_attributes; 125*61145dc2SMartin Matuska uint32_t stx_nlink; 126*61145dc2SMartin Matuska uint32_t stx_uid; 127*61145dc2SMartin Matuska uint32_t stx_gid; 128*61145dc2SMartin Matuska uint16_t stx_mode; 129*61145dc2SMartin Matuska uint16_t _pad1; 130*61145dc2SMartin Matuska uint64_t stx_ino; 131*61145dc2SMartin Matuska uint64_t stx_size; 132*61145dc2SMartin Matuska uint64_t stx_blocks; 133*61145dc2SMartin Matuska uint64_t stx_attributes_mask; 134*61145dc2SMartin Matuska stx_timestamp_t stx_atime; 135*61145dc2SMartin Matuska stx_timestamp_t stx_btime; 136*61145dc2SMartin Matuska stx_timestamp_t stx_ctime; 137*61145dc2SMartin Matuska stx_timestamp_t stx_mtime; 138*61145dc2SMartin Matuska uint32_t stx_rdev_major; 139*61145dc2SMartin Matuska uint32_t stx_rdev_minor; 140*61145dc2SMartin Matuska uint32_t stx_dev_major; 141*61145dc2SMartin Matuska uint32_t stx_dev_minor; 142*61145dc2SMartin Matuska uint64_t stx_mnt_id; 143*61145dc2SMartin Matuska uint32_t stx_dio_mem_align; 144*61145dc2SMartin Matuska uint32_t stx_dio_offset_align; 145*61145dc2SMartin Matuska uint64_t _pad2[12]; 146*61145dc2SMartin Matuska } stx_t; 147*61145dc2SMartin Matuska _Static_assert(sizeof (stx_t) == 0x100, "stx_t not 256 bytes"); 148*61145dc2SMartin Matuska 149*61145dc2SMartin Matuska typedef struct { 150*61145dc2SMartin Matuska const char *name; 151*61145dc2SMartin Matuska unsigned int mask; 152*61145dc2SMartin Matuska } stx_field_t; 153*61145dc2SMartin Matuska 154*61145dc2SMartin Matuska stx_field_t fields[] = { 155*61145dc2SMartin Matuska { "type", STATX_TYPE }, 156*61145dc2SMartin Matuska { "mode", STATX_MODE }, 157*61145dc2SMartin Matuska { "nlink", STATX_NLINK }, 158*61145dc2SMartin Matuska { "uid", STATX_UID }, 159*61145dc2SMartin Matuska { "gid", STATX_GID }, 160*61145dc2SMartin Matuska { "atime", STATX_ATIME }, 161*61145dc2SMartin Matuska { "mtime", STATX_MTIME }, 162*61145dc2SMartin Matuska { "ctime", STATX_CTIME }, 163*61145dc2SMartin Matuska { "ino", STATX_INO }, 164*61145dc2SMartin Matuska { "size", STATX_SIZE }, 165*61145dc2SMartin Matuska { "blocks", STATX_BLOCKS }, 166*61145dc2SMartin Matuska { "btime", STATX_BTIME }, 167*61145dc2SMartin Matuska { "mnt_id", STATX_MNT_ID }, 168*61145dc2SMartin Matuska { "dioalign", STATX_DIOALIGN }, 169*61145dc2SMartin Matuska { NULL }, 170*61145dc2SMartin Matuska }; 171*61145dc2SMartin Matuska 172*61145dc2SMartin Matuska static int 173*61145dc2SMartin Matuska usage(void) 174*61145dc2SMartin Matuska { 175*61145dc2SMartin Matuska printf( 176*61145dc2SMartin Matuska "usage: statx <field[,field,field]> <file>\n" 177*61145dc2SMartin Matuska "available fields:\n"); 178*61145dc2SMartin Matuska 179*61145dc2SMartin Matuska int w = 0; 180*61145dc2SMartin Matuska for (stx_field_t *f = fields; f->name != NULL; f++) { 181*61145dc2SMartin Matuska if (w > 0 && (w + strlen(f->name) + 1) > 60) { 182*61145dc2SMartin Matuska fputc('\n', stdout); 183*61145dc2SMartin Matuska w = 0; 184*61145dc2SMartin Matuska } 185*61145dc2SMartin Matuska if (w == 0) 186*61145dc2SMartin Matuska fputc(' ', stdout); 187*61145dc2SMartin Matuska w += printf(" %s", f->name); 188*61145dc2SMartin Matuska } 189*61145dc2SMartin Matuska if (w > 0) 190*61145dc2SMartin Matuska fputc('\n', stdout); 191*61145dc2SMartin Matuska return (1); 192*61145dc2SMartin Matuska } 193*61145dc2SMartin Matuska 194*61145dc2SMartin Matuska int 195*61145dc2SMartin Matuska main(int argc, char **argv) 196*61145dc2SMartin Matuska { 197*61145dc2SMartin Matuska if (argc < 3) 198*61145dc2SMartin Matuska return (usage()); 199*61145dc2SMartin Matuska 200*61145dc2SMartin Matuska unsigned int mask = 0; 201*61145dc2SMartin Matuska 202*61145dc2SMartin Matuska char *name; 203*61145dc2SMartin Matuska while ((name = strsep(&argv[1], ",")) != NULL) { 204*61145dc2SMartin Matuska stx_field_t *f; 205*61145dc2SMartin Matuska for (f = fields; f->name != NULL; f++) { 206*61145dc2SMartin Matuska if (strcmp(name, f->name) == 0) { 207*61145dc2SMartin Matuska mask |= f->mask; 208*61145dc2SMartin Matuska break; 209*61145dc2SMartin Matuska } 210*61145dc2SMartin Matuska } 211*61145dc2SMartin Matuska if (f->name == NULL) { 212*61145dc2SMartin Matuska fprintf(stderr, "unknown field name: %s\n", name); 213*61145dc2SMartin Matuska return (usage()); 214*61145dc2SMartin Matuska } 215*61145dc2SMartin Matuska } 216*61145dc2SMartin Matuska 217*61145dc2SMartin Matuska int fd = open(argv[2], O_PATH); 218*61145dc2SMartin Matuska if (fd < 0) { 219*61145dc2SMartin Matuska fprintf(stderr, "open: %s: %s\n", argv[2], strerror(errno)); 220*61145dc2SMartin Matuska return (1); 221*61145dc2SMartin Matuska } 222*61145dc2SMartin Matuska 223*61145dc2SMartin Matuska stx_t stx = {}; 224*61145dc2SMartin Matuska 225*61145dc2SMartin Matuska if (_statx(fd, "", 226*61145dc2SMartin Matuska AT_EMPTY_PATH | AT_SYMLINK_NOFOLLOW, mask, &stx) < 0) { 227*61145dc2SMartin Matuska fprintf(stderr, "statx: %s: %s\n", argv[2], strerror(errno)); 228*61145dc2SMartin Matuska close(fd); 229*61145dc2SMartin Matuska return (1); 230*61145dc2SMartin Matuska } 231*61145dc2SMartin Matuska 232*61145dc2SMartin Matuska int rc = 0; 233*61145dc2SMartin Matuska 234*61145dc2SMartin Matuska for (stx_field_t *f = fields; f->name != NULL; f++) { 235*61145dc2SMartin Matuska if (!(mask & f->mask)) 236*61145dc2SMartin Matuska continue; 237*61145dc2SMartin Matuska if (!(stx.stx_mask & f->mask)) { 238*61145dc2SMartin Matuska printf("statx: kernel did not return field: %s\n", 239*61145dc2SMartin Matuska f->name); 240*61145dc2SMartin Matuska rc = 2; 241*61145dc2SMartin Matuska continue; 242*61145dc2SMartin Matuska } 243*61145dc2SMartin Matuska } 244*61145dc2SMartin Matuska 245*61145dc2SMartin Matuska if (rc > 0) 246*61145dc2SMartin Matuska return (rc); 247*61145dc2SMartin Matuska 248*61145dc2SMartin Matuska for (stx_field_t *f = fields; f->name != NULL; f++) { 249*61145dc2SMartin Matuska if (!(mask & f->mask)) 250*61145dc2SMartin Matuska continue; 251*61145dc2SMartin Matuska 252*61145dc2SMartin Matuska switch (f->mask) { 253*61145dc2SMartin Matuska case STATX_TYPE: 254*61145dc2SMartin Matuska printf("type: %u\n", stx.stx_mode & S_IFMT); 255*61145dc2SMartin Matuska break; 256*61145dc2SMartin Matuska case STATX_MODE: 257*61145dc2SMartin Matuska printf("mode: %u\n", stx.stx_mode & ~S_IFMT); 258*61145dc2SMartin Matuska break; 259*61145dc2SMartin Matuska case STATX_NLINK: 260*61145dc2SMartin Matuska printf("nlink: %u\n", stx.stx_nlink); 261*61145dc2SMartin Matuska break; 262*61145dc2SMartin Matuska case STATX_UID: 263*61145dc2SMartin Matuska printf("uid: %u\n", stx.stx_uid); 264*61145dc2SMartin Matuska break; 265*61145dc2SMartin Matuska case STATX_GID: 266*61145dc2SMartin Matuska printf("gid: %u\n", stx.stx_gid); 267*61145dc2SMartin Matuska break; 268*61145dc2SMartin Matuska case STATX_ATIME: 269*61145dc2SMartin Matuska printf("atime: %ld.%u\n", 270*61145dc2SMartin Matuska stx.stx_atime.tv_sec, stx.stx_atime.tv_nsec); 271*61145dc2SMartin Matuska break; 272*61145dc2SMartin Matuska case STATX_MTIME: 273*61145dc2SMartin Matuska printf("mtime: %ld.%u\n", 274*61145dc2SMartin Matuska stx.stx_mtime.tv_sec, stx.stx_mtime.tv_nsec); 275*61145dc2SMartin Matuska break; 276*61145dc2SMartin Matuska case STATX_CTIME: 277*61145dc2SMartin Matuska printf("ctime: %ld.%u\n", 278*61145dc2SMartin Matuska stx.stx_ctime.tv_sec, stx.stx_ctime.tv_nsec); 279*61145dc2SMartin Matuska break; 280*61145dc2SMartin Matuska case STATX_INO: 281*61145dc2SMartin Matuska printf("ino: %lu\n", stx.stx_ino); 282*61145dc2SMartin Matuska break; 283*61145dc2SMartin Matuska case STATX_SIZE: 284*61145dc2SMartin Matuska printf("size: %lu\n", stx.stx_size); 285*61145dc2SMartin Matuska break; 286*61145dc2SMartin Matuska case STATX_BLOCKS: 287*61145dc2SMartin Matuska printf("blocks: %lu\n", stx.stx_blocks); 288*61145dc2SMartin Matuska break; 289*61145dc2SMartin Matuska case STATX_BTIME: 290*61145dc2SMartin Matuska printf("btime: %ld.%u\n", 291*61145dc2SMartin Matuska stx.stx_btime.tv_sec, stx.stx_btime.tv_nsec); 292*61145dc2SMartin Matuska break; 293*61145dc2SMartin Matuska case STATX_MNT_ID: 294*61145dc2SMartin Matuska printf("mnt_id: %lu\n", stx.stx_mnt_id); 295*61145dc2SMartin Matuska break; 296*61145dc2SMartin Matuska case STATX_DIOALIGN: 297*61145dc2SMartin Matuska printf("dioalign: %u %u\n", 298*61145dc2SMartin Matuska stx.stx_dio_mem_align, stx.stx_dio_offset_align); 299*61145dc2SMartin Matuska break; 300*61145dc2SMartin Matuska } 301*61145dc2SMartin Matuska } 302*61145dc2SMartin Matuska 303*61145dc2SMartin Matuska return (rc); 304*61145dc2SMartin Matuska } 305