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 113 typedef struct { 114 int64_t tv_sec; 115 uint32_t tv_nsec; 116 int32_t _pad; 117 } stx_timestamp_t; 118 _Static_assert(sizeof (stx_timestamp_t) == 0x10, 119 "stx_timestamp_t not 16 bytes"); 120 121 typedef struct { 122 uint32_t stx_mask; 123 uint32_t stx_blksize; 124 uint64_t stx_attributes; 125 uint32_t stx_nlink; 126 uint32_t stx_uid; 127 uint32_t stx_gid; 128 uint16_t stx_mode; 129 uint16_t _pad1; 130 uint64_t stx_ino; 131 uint64_t stx_size; 132 uint64_t stx_blocks; 133 uint64_t stx_attributes_mask; 134 stx_timestamp_t stx_atime; 135 stx_timestamp_t stx_btime; 136 stx_timestamp_t stx_ctime; 137 stx_timestamp_t stx_mtime; 138 uint32_t stx_rdev_major; 139 uint32_t stx_rdev_minor; 140 uint32_t stx_dev_major; 141 uint32_t stx_dev_minor; 142 uint64_t stx_mnt_id; 143 uint32_t stx_dio_mem_align; 144 uint32_t stx_dio_offset_align; 145 uint64_t _pad2[12]; 146 } stx_t; 147 _Static_assert(sizeof (stx_t) == 0x100, "stx_t not 256 bytes"); 148 149 typedef struct { 150 const char *name; 151 unsigned int mask; 152 } stx_field_t; 153 154 stx_field_t fields[] = { 155 { "type", STATX_TYPE }, 156 { "mode", STATX_MODE }, 157 { "nlink", STATX_NLINK }, 158 { "uid", STATX_UID }, 159 { "gid", STATX_GID }, 160 { "atime", STATX_ATIME }, 161 { "mtime", STATX_MTIME }, 162 { "ctime", STATX_CTIME }, 163 { "ino", STATX_INO }, 164 { "size", STATX_SIZE }, 165 { "blocks", STATX_BLOCKS }, 166 { "btime", STATX_BTIME }, 167 { "mnt_id", STATX_MNT_ID }, 168 { "dioalign", STATX_DIOALIGN }, 169 { NULL }, 170 }; 171 172 static int 173 usage(void) 174 { 175 printf( 176 "usage: statx <field[,field,field]> <file>\n" 177 "available fields:\n"); 178 179 int w = 0; 180 for (stx_field_t *f = fields; f->name != NULL; f++) { 181 if (w > 0 && (w + strlen(f->name) + 1) > 60) { 182 fputc('\n', stdout); 183 w = 0; 184 } 185 if (w == 0) 186 fputc(' ', stdout); 187 w += printf(" %s", f->name); 188 } 189 if (w > 0) 190 fputc('\n', stdout); 191 return (1); 192 } 193 194 int 195 main(int argc, char **argv) 196 { 197 if (argc < 3) 198 return (usage()); 199 200 unsigned int mask = 0; 201 202 char *name; 203 while ((name = strsep(&argv[1], ",")) != NULL) { 204 stx_field_t *f; 205 for (f = fields; f->name != NULL; f++) { 206 if (strcmp(name, f->name) == 0) { 207 mask |= f->mask; 208 break; 209 } 210 } 211 if (f->name == NULL) { 212 fprintf(stderr, "unknown field name: %s\n", name); 213 return (usage()); 214 } 215 } 216 217 int fd = open(argv[2], O_PATH); 218 if (fd < 0) { 219 fprintf(stderr, "open: %s: %s\n", argv[2], strerror(errno)); 220 return (1); 221 } 222 223 stx_t stx = {}; 224 225 if (_statx(fd, "", 226 AT_EMPTY_PATH | AT_SYMLINK_NOFOLLOW, mask, &stx) < 0) { 227 fprintf(stderr, "statx: %s: %s\n", argv[2], strerror(errno)); 228 close(fd); 229 return (1); 230 } 231 232 int rc = 0; 233 234 for (stx_field_t *f = fields; f->name != NULL; f++) { 235 if (!(mask & f->mask)) 236 continue; 237 if (!(stx.stx_mask & f->mask)) { 238 printf("statx: kernel did not return field: %s\n", 239 f->name); 240 rc = 2; 241 continue; 242 } 243 } 244 245 if (rc > 0) 246 return (rc); 247 248 for (stx_field_t *f = fields; f->name != NULL; f++) { 249 if (!(mask & f->mask)) 250 continue; 251 252 switch (f->mask) { 253 case STATX_TYPE: 254 printf("type: %u\n", stx.stx_mode & S_IFMT); 255 break; 256 case STATX_MODE: 257 printf("mode: %u\n", stx.stx_mode & ~S_IFMT); 258 break; 259 case STATX_NLINK: 260 printf("nlink: %u\n", stx.stx_nlink); 261 break; 262 case STATX_UID: 263 printf("uid: %u\n", stx.stx_uid); 264 break; 265 case STATX_GID: 266 printf("gid: %u\n", stx.stx_gid); 267 break; 268 case STATX_ATIME: 269 printf("atime: %ld.%u\n", 270 stx.stx_atime.tv_sec, stx.stx_atime.tv_nsec); 271 break; 272 case STATX_MTIME: 273 printf("mtime: %ld.%u\n", 274 stx.stx_mtime.tv_sec, stx.stx_mtime.tv_nsec); 275 break; 276 case STATX_CTIME: 277 printf("ctime: %ld.%u\n", 278 stx.stx_ctime.tv_sec, stx.stx_ctime.tv_nsec); 279 break; 280 case STATX_INO: 281 printf("ino: %lu\n", stx.stx_ino); 282 break; 283 case STATX_SIZE: 284 printf("size: %lu\n", stx.stx_size); 285 break; 286 case STATX_BLOCKS: 287 printf("blocks: %lu\n", stx.stx_blocks); 288 break; 289 case STATX_BTIME: 290 printf("btime: %ld.%u\n", 291 stx.stx_btime.tv_sec, stx.stx_btime.tv_nsec); 292 break; 293 case STATX_MNT_ID: 294 printf("mnt_id: %lu\n", stx.stx_mnt_id); 295 break; 296 case STATX_DIOALIGN: 297 printf("dioalign: %u %u\n", 298 stx.stx_dio_mem_align, stx.stx_dio_offset_align); 299 break; 300 } 301 } 302 303 return (rc); 304 } 305