xref: /freebsd/sys/contrib/openzfs/tests/zfs-tests/cmd/statx.c (revision b670c9bafc0e31c7609969bf374b2e80bdc00211)
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