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