]> git.ipfire.org Git - thirdparty/systemd.git/blame - src/basic/xattr-util.c
man/systemd-sysext: list ephemeral/ephemeral-import in the list of options
[thirdparty/systemd.git] / src / basic / xattr-util.c
CommitLineData
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) */
22static thread_local bool have_xattrat = true;
23
24static 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 76static 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
106int 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 178int 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 189int 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 208static 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
240int 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
286int 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
342int 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
356int 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
393static 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
409int 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
468int 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}