]>
Commit | Line | Data |
---|---|---|
db9ecf05 | 1 | /* SPDX-License-Identifier: LGPL-2.1-or-later */ |
89a5a90c | 2 | |
11c3a366 | 3 | #include <fcntl.h> |
89a5a90c | 4 | #include <sys/xattr.h> |
e7537295 | 5 | #include <threads.h> |
89a5a90c | 6 | |
b5efdb8a | 7 | #include "alloc-util.h" |
7c248223 | 8 | #include "errno-util.h" |
89a5a90c | 9 | #include "fd-util.h" |
0c15577a | 10 | #include "fs-util.h" |
f5947a5e | 11 | #include "missing_syscall.h" |
ed024aba | 12 | #include "nulstr-util.h" |
70554f7e | 13 | #include "parse-util.h" |
93cc7779 | 14 | #include "sparse-endian.h" |
7cd296c2 | 15 | #include "stat-util.h" |
1133dea4 | 16 | #include "string-util.h" |
ed024aba | 17 | #include "strv.h" |
93cc7779 | 18 | #include "time-util.h" |
89a5a90c LP |
19 | #include "xattr-util.h" |
20 | ||
b8df25dc MY |
21 | /* Use a single cache for all of *xattrat syscalls (added in kernel 6.13) */ |
22 | static thread_local bool have_xattrat = true; | |
23 | ||
24 | static int normalize_and_maybe_pin_inode( | |
25 | int *fd, | |
26 | const char **path, | |
27 | int *at_flags, | |
28 | int *ret_tfd, | |
29 | bool *ret_opath) { | |
30 | ||
31 | int r; | |
32 | ||
33 | assert(fd); | |
34 | assert(*fd >= 0 || *fd == AT_FDCWD); | |
35 | assert(path); | |
36 | assert(at_flags); | |
37 | assert(ret_tfd); | |
38 | assert(ret_opath); | |
39 | ||
40 | if (isempty(*path)) | |
41 | *path = NULL; /* Normalize "" to NULL */ | |
42 | ||
43 | if (*fd == AT_FDCWD) { | |
44 | if (!*path) /* Both unspecified? Then operate on current working directory */ | |
45 | *path = "."; | |
46 | ||
47 | *ret_tfd = -EBADF; | |
48 | *ret_opath = false; | |
49 | return 0; | |
50 | } | |
51 | ||
52 | *at_flags |= AT_EMPTY_PATH; | |
53 | ||
54 | if (!*path) { | |
55 | r = fd_is_opath(*fd); | |
56 | if (r < 0) | |
57 | return r; | |
58 | *ret_opath = r; | |
b8df25dc MY |
59 | *ret_tfd = -EBADF; |
60 | return 0; | |
61 | } | |
62 | ||
63 | /* If both have been specified, then we go via O_PATH */ | |
64 | ||
65 | int tfd = openat(*fd, *path, O_PATH|O_CLOEXEC|(FLAGS_SET(*at_flags, AT_SYMLINK_FOLLOW) ? 0 : O_NOFOLLOW)); | |
66 | if (tfd < 0) | |
67 | return -errno; | |
68 | ||
69 | *fd = *ret_tfd = tfd; | |
70 | *path = NULL; | |
71 | *ret_opath = true; | |
72 | ||
73 | return 0; | |
74 | } | |
75 | ||
33cbda04 | 76 | static ssize_t getxattr_pinned_internal( |
b8df25dc MY |
77 | int fd, |
78 | const char *path, | |
79 | int at_flags, | |
80 | bool by_procfs, | |
81 | const char *name, | |
82 | char *buf, | |
83 | size_t size) { | |
84 | ||
85 | ssize_t n; | |
86 | ||
87 | assert(!path || !isempty(path)); | |
88 | assert((fd >= 0) == !path); | |
b8df25dc MY |
89 | assert(path || FLAGS_SET(at_flags, AT_EMPTY_PATH)); |
90 | assert(name); | |
91 | assert(buf || size == 0); | |
92 | ||
93 | if (path) | |
78b47a4e MY |
94 | n = FLAGS_SET(at_flags, AT_SYMLINK_FOLLOW) ? getxattr(path, name, buf, size) |
95 | : lgetxattr(path, name, buf, size); | |
b8df25dc MY |
96 | else |
97 | n = by_procfs ? getxattr(FORMAT_PROC_FD_PATH(fd), name, buf, size) | |
98 | : fgetxattr(fd, name, buf, size); | |
99 | if (n < 0) | |
100 | return -errno; | |
101 | ||
86cfbfae | 102 | assert(size == 0 || (size_t) n <= size); |
33cbda04 | 103 | return n; |
b8df25dc MY |
104 | } |
105 | ||
c53e07e2 LP |
106 | int getxattr_at_malloc( |
107 | int fd, | |
6ac99d9d LP |
108 | const char *path, |
109 | const char *name, | |
b8df25dc | 110 | int at_flags, |
33cbda04 LP |
111 | char **ret, |
112 | size_t *ret_size) { | |
6ac99d9d | 113 | |
254d1313 | 114 | _cleanup_close_ int opened_fd = -EBADF; |
b8df25dc MY |
115 | bool by_procfs; |
116 | int r; | |
89a5a90c | 117 | |
c53e07e2 | 118 | assert(fd >= 0 || fd == AT_FDCWD); |
89a5a90c | 119 | assert(name); |
b8df25dc | 120 | assert((at_flags & ~(AT_SYMLINK_FOLLOW|AT_EMPTY_PATH)) == 0); |
6ac99d9d LP |
121 | assert(ret); |
122 | ||
c53e07e2 LP |
123 | /* So, this is single function that does what getxattr()/lgetxattr()/fgetxattr() does, but in one go, |
124 | * and with additional bells and whistles. Specifically: | |
125 | * | |
b8df25dc MY |
126 | * 1. This works on O_PATH fds (via /proc/self/fd/, since getxattrat() syscall refuses them...) |
127 | * 2. As extension to openat()-style semantics implies AT_EMPTY_PATH if path is empty | |
128 | * 3. Does a malloc() loop, automatically sizing the allocation | |
129 | * 4. NUL-terminates the returned buffer (for safety) | |
c53e07e2 LP |
130 | */ |
131 | ||
b8df25dc MY |
132 | r = normalize_and_maybe_pin_inode(&fd, &path, &at_flags, &opened_fd, &by_procfs); |
133 | if (r < 0) | |
134 | return r; | |
6ac99d9d | 135 | |
b8df25dc MY |
136 | size_t l = 100; |
137 | for (unsigned n_attempts = 7;;) { | |
6ac99d9d | 138 | _cleanup_free_ char *v = NULL; |
89a5a90c | 139 | |
c53e07e2 LP |
140 | if (n_attempts == 0) /* If someone is racing against us, give up eventually */ |
141 | return -EBUSY; | |
142 | n_attempts--; | |
143 | ||
b8df25dc | 144 | v = new(char, l+1); |
89a5a90c LP |
145 | if (!v) |
146 | return -ENOMEM; | |
147 | ||
c53e07e2 LP |
148 | l = MALLOC_ELEMENTSOF(v) - 1; |
149 | ||
33cbda04 LP |
150 | ssize_t n; |
151 | n = getxattr_pinned_internal(fd, path, at_flags, by_procfs, name, v, l); | |
152 | if (n >= 0) { | |
153 | /* Refuse extended attributes with embedded NUL bytes if the caller isn't interested | |
154 | * in the size. After all this must mean the caller assumes we return a NUL | |
155 | * terminated strings, but if there's a NUL byte embedded they are definitely not | |
156 | * regular strings */ | |
157 | if (!ret_size && n > 1 && memchr(v, 0, n - 1)) | |
158 | return -EBADMSG; | |
159 | ||
160 | v[n] = 0; /* NUL terminate */ | |
6ac99d9d | 161 | *ret = TAKE_PTR(v); |
33cbda04 LP |
162 | if (ret_size) |
163 | *ret_size = (size_t) n; | |
164 | ||
165 | return 0; | |
89a5a90c | 166 | } |
33cbda04 LP |
167 | if (n != -ERANGE) |
168 | return (int) n; | |
89a5a90c | 169 | |
33cbda04 LP |
170 | n = getxattr_pinned_internal(fd, path, at_flags, by_procfs, name, NULL, 0); |
171 | if (n < 0) | |
172 | return (int) n; | |
6ac99d9d | 173 | |
33cbda04 | 174 | l = (size_t) n; |
89a5a90c LP |
175 | } |
176 | } | |
177 | ||
b8df25dc | 178 | int getxattr_at_bool(int fd, const char *path, const char *name, int at_flags) { |
70554f7e LP |
179 | _cleanup_free_ char *v = NULL; |
180 | int r; | |
181 | ||
33cbda04 | 182 | r = getxattr_at_malloc(fd, path, name, at_flags, &v, /* ret_size= */ NULL); |
70554f7e LP |
183 | if (r < 0) |
184 | return r; | |
185 | ||
70554f7e LP |
186 | return parse_boolean(v); |
187 | } | |
188 | ||
ed024aba | 189 | int getxattr_at_strv(int fd, const char *path, const char *name, int at_flags, char ***ret_strv) { |
05587cec MY |
190 | _cleanup_free_ char *nulstr = NULL; |
191 | size_t nulstr_size; | |
ed024aba LP |
192 | int r; |
193 | ||
05587cec | 194 | assert(ret_strv); |
ed024aba LP |
195 | |
196 | r = getxattr_at_malloc(fd, path, name, at_flags, &nulstr, &nulstr_size); | |
197 | if (r < 0) | |
198 | return r; | |
199 | ||
200 | _cleanup_strv_free_ char **l = strv_parse_nulstr(nulstr, nulstr_size); | |
201 | if (!l) | |
202 | return -ENOMEM; | |
203 | ||
204 | *ret_strv = TAKE_PTR(l); | |
205 | return 0; | |
206 | } | |
207 | ||
b8df25dc | 208 | static int listxattr_pinned_internal( |
c53e07e2 LP |
209 | int fd, |
210 | const char *path, | |
b8df25dc MY |
211 | int at_flags, |
212 | bool by_procfs, | |
213 | char *buf, | |
214 | size_t size) { | |
215 | ||
216 | ssize_t n; | |
c53e07e2 | 217 | |
b8df25dc MY |
218 | assert(!path || !isempty(path)); |
219 | assert((fd >= 0) == !path); | |
b8df25dc MY |
220 | assert(path || FLAGS_SET(at_flags, AT_EMPTY_PATH)); |
221 | assert(buf || size == 0); | |
222 | ||
223 | if (path) | |
78b47a4e MY |
224 | n = FLAGS_SET(at_flags, AT_SYMLINK_FOLLOW) ? listxattr(path, buf, size) |
225 | : llistxattr(path, buf, size); | |
b8df25dc MY |
226 | else |
227 | n = by_procfs ? listxattr(FORMAT_PROC_FD_PATH(fd), buf, size) | |
228 | : flistxattr(fd, buf, size); | |
229 | if (n < 0) | |
230 | return -errno; | |
231 | ||
86cfbfae | 232 | assert(size == 0 || (size_t) n <= size); |
b8df25dc MY |
233 | |
234 | if (n > INT_MAX) /* We couldn't return this as 'int' anymore */ | |
235 | return -E2BIG; | |
236 | ||
237 | return (int) n; | |
238 | } | |
239 | ||
240 | int listxattr_at_malloc(int fd, const char *path, int at_flags, char **ret) { | |
254d1313 | 241 | _cleanup_close_ int opened_fd = -EBADF; |
b8df25dc MY |
242 | bool by_procfs; |
243 | int r; | |
7de2d2e1 | 244 | |
c53e07e2 | 245 | assert(fd >= 0 || fd == AT_FDCWD); |
b8df25dc | 246 | assert((at_flags & ~(AT_SYMLINK_FOLLOW|AT_EMPTY_PATH)) == 0); |
7de2d2e1 LP |
247 | assert(ret); |
248 | ||
c53e07e2 LP |
249 | /* This is to listxattr()/llistattr()/flistattr() what getxattr_at_malloc() is to getxattr()/… */ |
250 | ||
b8df25dc MY |
251 | r = normalize_and_maybe_pin_inode(&fd, &path, &at_flags, &opened_fd, &by_procfs); |
252 | if (r < 0) | |
253 | return r; | |
c53e07e2 | 254 | |
b8df25dc MY |
255 | size_t l = 100; |
256 | for (unsigned n_attempts = 7;;) { | |
7de2d2e1 | 257 | _cleanup_free_ char *v = NULL; |
7de2d2e1 | 258 | |
c53e07e2 LP |
259 | if (n_attempts == 0) /* If someone is racing against us, give up eventually */ |
260 | return -EBUSY; | |
261 | n_attempts--; | |
262 | ||
7de2d2e1 LP |
263 | v = new(char, l+1); |
264 | if (!v) | |
265 | return -ENOMEM; | |
266 | ||
c53e07e2 LP |
267 | l = MALLOC_ELEMENTSOF(v) - 1; |
268 | ||
b8df25dc MY |
269 | r = listxattr_pinned_internal(fd, path, at_flags, by_procfs, v, l); |
270 | if (r >= 0) { | |
271 | v[r] = 0; /* NUL terminate */ | |
7de2d2e1 | 272 | *ret = TAKE_PTR(v); |
b8df25dc | 273 | return r; |
7de2d2e1 | 274 | } |
b8df25dc MY |
275 | if (r != -ERANGE) |
276 | return r; | |
7de2d2e1 | 277 | |
b8df25dc MY |
278 | r = listxattr_pinned_internal(fd, path, at_flags, by_procfs, NULL, 0); |
279 | if (r < 0) | |
280 | return r; | |
7de2d2e1 | 281 | |
b8df25dc | 282 | l = (size_t) r; |
7de2d2e1 LP |
283 | } |
284 | } | |
a4d2461c | 285 | |
b8df25dc MY |
286 | int xsetxattr_full( |
287 | int fd, | |
288 | const char *path, | |
289 | int at_flags, | |
290 | const char *name, | |
291 | const char *value, | |
292 | size_t size, | |
293 | int xattr_flags) { | |
a4d2461c | 294 | |
a4d2461c DDM |
295 | int r; |
296 | ||
297 | assert(fd >= 0 || fd == AT_FDCWD); | |
b8df25dc | 298 | assert((at_flags & ~(AT_SYMLINK_FOLLOW|AT_EMPTY_PATH)) == 0); |
a4d2461c DDM |
299 | assert(name); |
300 | assert(value); | |
a4d2461c DDM |
301 | |
302 | if (size == SIZE_MAX) | |
303 | size = strlen(value); | |
304 | ||
b8df25dc MY |
305 | if (have_xattrat && !isempty(path)) { |
306 | struct xattr_args args = { | |
307 | .value = PTR_TO_UINT64(value), | |
308 | .size = size, | |
309 | .flags = xattr_flags, | |
310 | }; | |
311 | ||
312 | r = RET_NERRNO(setxattrat(fd, path, | |
313 | at_flags_normalize_nofollow(at_flags), | |
314 | name, | |
315 | &args, sizeof(args))); | |
316 | if (r != -ENOSYS) /* No ERRNO_IS_NOT_SUPPORTED here, as EOPNOTSUPP denotes the fs doesn't | |
317 | support xattr */ | |
318 | return r; | |
319 | ||
320 | have_xattrat = false; | |
321 | } | |
a4d2461c | 322 | |
b8df25dc MY |
323 | _cleanup_close_ int opened_fd = -EBADF; |
324 | bool by_procfs; | |
5f904eb7 | 325 | |
b8df25dc MY |
326 | r = normalize_and_maybe_pin_inode(&fd, &path, &at_flags, &opened_fd, &by_procfs); |
327 | if (r < 0) | |
328 | return r; | |
a4d2461c | 329 | |
b8df25dc MY |
330 | if (path) |
331 | r = FLAGS_SET(at_flags, AT_SYMLINK_FOLLOW) ? setxattr(path, name, value, size, xattr_flags) | |
332 | : lsetxattr(path, name, value, size, xattr_flags); | |
333 | else | |
334 | r = by_procfs ? setxattr(FORMAT_PROC_FD_PATH(fd), name, value, size, xattr_flags) | |
335 | : fsetxattr(fd, name, value, size, xattr_flags); | |
336 | if (r < 0) | |
337 | return -errno; | |
a4d2461c | 338 | |
b8df25dc MY |
339 | return 0; |
340 | } | |
341 | ||
05587cec MY |
342 | int xsetxattr_strv(int fd, const char *path, int at_flags, const char *name, char * const *l) { |
343 | _cleanup_free_ char *nulstr = NULL; | |
344 | size_t size; | |
9fbe26cf LP |
345 | int r; |
346 | ||
05587cec MY |
347 | assert(name); |
348 | ||
9fbe26cf LP |
349 | r = strv_make_nulstr(l, &nulstr, &size); |
350 | if (r < 0) | |
351 | return r; | |
352 | ||
353 | return xsetxattr_full(fd, path, at_flags, name, nulstr, size, /* xattr_flags= */ 0); | |
354 | } | |
355 | ||
b8df25dc MY |
356 | int xremovexattr(int fd, const char *path, int at_flags, const char *name) { |
357 | int r; | |
a4d2461c | 358 | |
b8df25dc MY |
359 | assert(fd >= 0 || fd == AT_FDCWD); |
360 | assert((at_flags & ~(AT_SYMLINK_FOLLOW|AT_EMPTY_PATH)) == 0); | |
361 | assert(name); | |
362 | ||
363 | if (have_xattrat && !isempty(path)) { | |
364 | r = RET_NERRNO(removexattrat(fd, path, | |
365 | at_flags_normalize_nofollow(at_flags), | |
366 | name)); | |
367 | if (r != -ENOSYS) /* No ERRNO_IS_NOT_SUPPORTED here, as EOPNOTSUPP denotes the fs doesn't | |
368 | support xattr */ | |
369 | return r; | |
370 | ||
371 | have_xattrat = false; | |
a4d2461c DDM |
372 | } |
373 | ||
b8df25dc MY |
374 | _cleanup_close_ int tfd = -EBADF; |
375 | bool by_procfs; | |
376 | ||
377 | r = normalize_and_maybe_pin_inode(&fd, &path, &at_flags, &tfd, &by_procfs); | |
378 | if (r < 0) | |
379 | return r; | |
380 | ||
5f904eb7 | 381 | if (path) |
b8df25dc MY |
382 | r = FLAGS_SET(at_flags, AT_SYMLINK_FOLLOW) ? removexattr(path, name) |
383 | : lremovexattr(path, name); | |
5f904eb7 | 384 | else |
b8df25dc MY |
385 | r = by_procfs ? removexattr(FORMAT_PROC_FD_PATH(fd), name) |
386 | : fremovexattr(fd, name); | |
5f904eb7 YW |
387 | if (r < 0) |
388 | return -errno; | |
a4d2461c | 389 | |
5f904eb7 | 390 | return 0; |
a4d2461c | 391 | } |
baca3581 MY |
392 | |
393 | static int parse_crtime(le64_t le, usec_t *ret) { | |
394 | usec_t u; | |
395 | ||
396 | assert(ret); | |
397 | ||
398 | assert_cc(sizeof(usec_t) == sizeof(uint64_t)); | |
399 | assert_cc((usec_t) UINT64_MAX == USEC_INFINITY); | |
400 | ||
401 | u = (usec_t) le64toh(le); | |
402 | if (!timestamp_is_set(u)) | |
403 | return -EIO; | |
404 | ||
405 | *ret = u; | |
406 | return 0; | |
407 | } | |
408 | ||
409 | int getcrtime_at( | |
410 | int fd, | |
411 | const char *path, | |
412 | int at_flags, | |
413 | usec_t *ret) { | |
414 | ||
415 | _cleanup_free_ le64_t *le = NULL; | |
4424e6c8 | 416 | struct statx sx; |
baca3581 MY |
417 | usec_t a, b; |
418 | int r; | |
419 | ||
420 | assert(fd >= 0 || fd == AT_FDCWD); | |
421 | assert((at_flags & ~(AT_SYMLINK_FOLLOW|AT_EMPTY_PATH)) == 0); | |
422 | ||
423 | if (isempty(path)) | |
424 | at_flags |= AT_EMPTY_PATH; | |
425 | ||
426 | /* So here's the deal: the creation/birth time (crtime/btime) of a file is a relatively newly supported concept | |
427 | * on Linux (or more strictly speaking: a concept that only recently got supported in the API, it was | |
428 | * implemented on various file systems on the lower level since a while, but never was accessible). However, we | |
429 | * needed a concept like that for vacuuming algorithms and such, hence we emulated it via a user xattr for a | |
430 | * long time. Starting with Linux 4.11 there's statx() which exposes the timestamp to userspace for the first | |
431 | * time, where it is available. This function will read it, but it tries to keep some compatibility with older | |
432 | * systems: we try to read both the crtime/btime and the xattr, and then use whatever is older. After all the | |
433 | * concept is useful for determining how "old" a file really is, and hence using the older of the two makes | |
434 | * most sense. */ | |
435 | ||
436 | if (statx(fd, strempty(path), | |
437 | at_flags_normalize_nofollow(at_flags)|AT_STATX_DONT_SYNC, | |
438 | STATX_BTIME, | |
439 | &sx) >= 0 && | |
440 | FLAGS_SET(sx.stx_mask, STATX_BTIME) && sx.stx_btime.tv_sec != 0) | |
441 | a = statx_timestamp_load(&sx.stx_btime); | |
442 | else | |
443 | a = USEC_INFINITY; | |
444 | ||
33cbda04 LP |
445 | size_t le_size; |
446 | r = getxattr_at_malloc(fd, path, "user.crtime_usec", at_flags, (char**) &le, &le_size); | |
baca3581 | 447 | if (r >= 0) { |
33cbda04 | 448 | if (le_size != sizeof(*le)) |
baca3581 MY |
449 | r = -EIO; |
450 | else | |
451 | r = parse_crtime(*le, &b); | |
452 | } | |
453 | if (r < 0) { | |
454 | if (a != USEC_INFINITY) { | |
455 | if (ret) | |
456 | *ret = a; | |
457 | return 0; | |
458 | } | |
459 | ||
460 | return r; | |
461 | } | |
462 | ||
463 | if (ret) | |
464 | *ret = MIN(a, b); | |
465 | return 0; | |
466 | } | |
467 | ||
468 | int fd_setcrtime(int fd, usec_t usec) { | |
469 | le64_t le; | |
470 | ||
471 | assert(fd >= 0); | |
472 | ||
473 | if (!timestamp_is_set(usec)) | |
474 | usec = now(CLOCK_REALTIME); | |
475 | ||
476 | le = htole64((uint64_t) usec); | |
b8df25dc MY |
477 | return xsetxattr_full(fd, /* path = */ NULL, AT_EMPTY_PATH, |
478 | "user.crtime_usec", (const char*) &le, sizeof(le), | |
479 | /* xattr_flags = */ 0); | |
baca3581 | 480 | } |