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
_statx(int fd,const char * path,int flags,unsigned int mask,void * stx)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
usage(void)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
main(int argc,char ** argv)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