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