]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/basic/xattr-util.c
tree-wide: fix a couple of typos
[thirdparty/systemd.git] / src / basic / xattr-util.c
1 /* SPDX-License-Identifier: LGPL-2.1-or-later */
2
3 #include <errno.h>
4 #include <fcntl.h>
5 #include <stdint.h>
6 #include <stdlib.h>
7 #include <sys/time.h>
8 #include <sys/xattr.h>
9
10 #include "alloc-util.h"
11 #include "errno-util.h"
12 #include "fd-util.h"
13 #include "macro.h"
14 #include "missing_syscall.h"
15 #include "sparse-endian.h"
16 #include "stat-util.h"
17 #include "stdio-util.h"
18 #include "string-util.h"
19 #include "time-util.h"
20 #include "xattr-util.h"
21
22 int getxattr_at_malloc(
23 int fd,
24 const char *path,
25 const char *name,
26 int flags,
27 char **ret) {
28
29 _cleanup_close_ int opened_fd = -EBADF;
30 unsigned n_attempts = 7;
31 bool by_procfs = false;
32 size_t l = 100;
33
34 assert(fd >= 0 || fd == AT_FDCWD);
35 assert(name);
36 assert((flags & ~(AT_SYMLINK_FOLLOW|AT_EMPTY_PATH)) == 0);
37 assert(ret);
38
39 /* So, this is single function that does what getxattr()/lgetxattr()/fgetxattr() does, but in one go,
40 * and with additional bells and whistles. Specifically:
41 *
42 * 1. This works on O_PATH fds (which fgetxattr() does not)
43 * 2. Provides full openat()-style semantics, i.e. by-fd, by-path and combination thereof
44 * 3. As extension to openat()-style semantics implies AT_EMPTY_PATH if path is NULL.
45 * 4. Does a malloc() loop, automatically sizing the allocation
46 * 5. NUL-terminates the returned buffer (for safety)
47 */
48
49 if (!path) /* If path is NULL, imply AT_EMPTY_PATH. – But if it's "", don't — for safety reasons. */
50 flags |= AT_EMPTY_PATH;
51
52 if (isempty(path)) {
53 if (!FLAGS_SET(flags, AT_EMPTY_PATH))
54 return -EINVAL;
55
56 if (fd == AT_FDCWD) /* Both unspecified? Then operate on current working directory */
57 path = ".";
58 else
59 path = NULL;
60
61 } else if (fd != AT_FDCWD) {
62
63 /* If both have been specified, then we go via O_PATH */
64 opened_fd = openat(fd, path, O_PATH|O_CLOEXEC|(FLAGS_SET(flags, AT_SYMLINK_FOLLOW) ? 0 : O_NOFOLLOW));
65 if (opened_fd < 0)
66 return -errno;
67
68 fd = opened_fd;
69 path = NULL;
70 by_procfs = true; /* fgetxattr() is not going to work, go via /proc/ link right-away */
71 }
72
73 for (;;) {
74 _cleanup_free_ char *v = NULL;
75 ssize_t n;
76
77 if (n_attempts == 0) /* If someone is racing against us, give up eventually */
78 return -EBUSY;
79 n_attempts--;
80
81 v = new0(char, l+1);
82 if (!v)
83 return -ENOMEM;
84
85 l = MALLOC_ELEMENTSOF(v) - 1;
86
87 if (path)
88 n = FLAGS_SET(flags, AT_SYMLINK_FOLLOW) ? getxattr(path, name, v, l) : lgetxattr(path, name, v, l);
89 else
90 n = by_procfs ? getxattr(FORMAT_PROC_FD_PATH(fd), name, v, l) : fgetxattr(fd, name, v, l);
91 if (n < 0) {
92 if (errno == EBADF) {
93 if (by_procfs || path)
94 return -EBADF;
95
96 by_procfs = true; /* Might be an O_PATH fd, try again via /proc/ link */
97 continue;
98 }
99
100 if (errno != ERANGE)
101 return -errno;
102 } else {
103 v[n] = 0; /* NUL terminate */
104 *ret = TAKE_PTR(v);
105 return (int) n;
106 }
107
108 if (path)
109 n = FLAGS_SET(flags, AT_SYMLINK_FOLLOW) ? getxattr(path, name, NULL, 0) : lgetxattr(path, name, NULL, 0);
110 else
111 n = by_procfs ? getxattr(FORMAT_PROC_FD_PATH(fd), name, NULL, 0) : fgetxattr(fd, name, NULL, 0);
112 if (n < 0)
113 return -errno;
114 if (n > INT_MAX) /* We couldn't return this as 'int' anymore */
115 return -E2BIG;
116
117 l = (size_t) n;
118 }
119 }
120
121 static int parse_crtime(le64_t le, usec_t *usec) {
122 uint64_t u;
123
124 assert(usec);
125
126 u = le64toh(le);
127 if (IN_SET(u, 0, UINT64_MAX))
128 return -EIO;
129
130 *usec = (usec_t) u;
131 return 0;
132 }
133
134 int fd_getcrtime_at(
135 int fd,
136 const char *path,
137 int flags,
138 usec_t *ret) {
139
140 _cleanup_free_ le64_t *le = NULL;
141 STRUCT_STATX_DEFINE(sx);
142 usec_t a, b;
143 int r;
144
145 assert(fd >= 0 || fd == AT_FDCWD);
146 assert((flags & ~(AT_SYMLINK_FOLLOW|AT_EMPTY_PATH)) == 0);
147 assert(ret);
148
149 if (!path)
150 flags |= AT_EMPTY_PATH;
151
152 /* So here's the deal: the creation/birth time (crtime/btime) of a file is a relatively newly supported concept
153 * on Linux (or more strictly speaking: a concept that only recently got supported in the API, it was
154 * implemented on various file systems on the lower level since a while, but never was accessible). However, we
155 * needed a concept like that for vacuuming algorithms and such, hence we emulated it via a user xattr for a
156 * long time. Starting with Linux 4.11 there's statx() which exposes the timestamp to userspace for the first
157 * time, where it is available. This function will read it, but it tries to keep some compatibility with older
158 * systems: we try to read both the crtime/btime and the xattr, and then use whatever is older. After all the
159 * concept is useful for determining how "old" a file really is, and hence using the older of the two makes
160 * most sense. */
161
162 if (statx(fd, strempty(path),
163 (flags & ~AT_SYMLINK_FOLLOW)|(FLAGS_SET(flags, AT_SYMLINK_FOLLOW) ? 0 : AT_SYMLINK_NOFOLLOW)|AT_STATX_DONT_SYNC,
164 STATX_BTIME,
165 &sx) >= 0 &&
166 (sx.stx_mask & STATX_BTIME) &&
167 sx.stx_btime.tv_sec != 0)
168 a = (usec_t) sx.stx_btime.tv_sec * USEC_PER_SEC +
169 (usec_t) sx.stx_btime.tv_nsec / NSEC_PER_USEC;
170 else
171 a = USEC_INFINITY;
172
173 r = getxattr_at_malloc(fd, path, "user.crtime_usec", flags, (char**) &le);
174 if (r >= 0) {
175 if (r != sizeof(*le))
176 r = -EIO;
177 else
178 r = parse_crtime(*le, &b);
179 }
180 if (r < 0) {
181 if (a != USEC_INFINITY) {
182 *ret = a;
183 return 0;
184 }
185
186 return r;
187 }
188
189 if (a != USEC_INFINITY)
190 *ret = MIN(a, b);
191 else
192 *ret = b;
193
194 return 0;
195 }
196
197 int fd_setcrtime(int fd, usec_t usec) {
198 le64_t le;
199
200 assert(fd >= 0);
201
202 if (!timestamp_is_set(usec))
203 usec = now(CLOCK_REALTIME);
204
205 le = htole64((uint64_t) usec);
206 return RET_NERRNO(fsetxattr(fd, "user.crtime_usec", &le, sizeof(le), 0));
207 }
208
209 int listxattr_at_malloc(
210 int fd,
211 const char *path,
212 int flags,
213 char **ret) {
214
215 _cleanup_close_ int opened_fd = -EBADF;
216 bool by_procfs = false;
217 unsigned n_attempts = 7;
218 size_t l = 100;
219
220 assert(fd >= 0 || fd == AT_FDCWD);
221 assert((flags & ~(AT_SYMLINK_FOLLOW|AT_EMPTY_PATH)) == 0);
222 assert(ret);
223
224 /* This is to listxattr()/llistattr()/flistattr() what getxattr_at_malloc() is to getxattr()/… */
225
226 if (!path) /* If path is NULL, imply AT_EMPTY_PATH. – But if it's "", don't. */
227 flags |= AT_EMPTY_PATH;
228
229 if (isempty(path)) {
230 if (!FLAGS_SET(flags, AT_EMPTY_PATH))
231 return -EINVAL;
232
233 if (fd == AT_FDCWD) /* Both unspecified? Then operate on current working directory */
234 path = ".";
235 else
236 path = NULL;
237
238 } else if (fd != AT_FDCWD) {
239 /* If both have been specified, then we go via O_PATH */
240 opened_fd = openat(fd, path, O_PATH|O_CLOEXEC|(FLAGS_SET(flags, AT_SYMLINK_FOLLOW) ? 0 : O_NOFOLLOW));
241 if (opened_fd < 0)
242 return -errno;
243
244 fd = opened_fd;
245 path = NULL;
246 by_procfs = true;
247 }
248
249 for (;;) {
250 _cleanup_free_ char *v = NULL;
251 ssize_t n;
252
253 if (n_attempts == 0) /* If someone is racing against us, give up eventually */
254 return -EBUSY;
255 n_attempts--;
256
257 v = new(char, l+1);
258 if (!v)
259 return -ENOMEM;
260
261 l = MALLOC_ELEMENTSOF(v) - 1;
262
263 if (path)
264 n = FLAGS_SET(flags, AT_SYMLINK_FOLLOW) ? listxattr(path, v, l) : llistxattr(path, v, l);
265 else
266 n = by_procfs ? listxattr(FORMAT_PROC_FD_PATH(fd), v, l) : flistxattr(fd, v, l);
267 if (n < 0) {
268 if (errno == EBADF) {
269 if (by_procfs || path)
270 return -EBADF;
271
272 by_procfs = true; /* Might be an O_PATH fd, try again via /proc/ link */
273 continue;
274 }
275
276 if (errno != ERANGE)
277 return -errno;
278 } else {
279 v[n] = 0; /* NUL terminate */
280 *ret = TAKE_PTR(v);
281 return (int) n;
282 }
283
284 if (path)
285 n = FLAGS_SET(flags, AT_SYMLINK_FOLLOW) ? listxattr(path, NULL, 0) : llistxattr(path, NULL, 0);
286 else
287 n = by_procfs ? listxattr(FORMAT_PROC_FD_PATH(fd), NULL, 0) : flistxattr(fd, NULL, 0);
288 if (n < 0)
289 return -errno;
290 if (n > INT_MAX) /* We couldn't return this as 'int' anymore */
291 return -E2BIG;
292
293 l = (size_t) n;
294 }
295 }
296
297 int xsetxattr(int fd,
298 const char *path,
299 const char *name,
300 const char *value,
301 size_t size,
302 int flags) {
303
304 _cleanup_close_ int opened_fd = -EBADF;
305 bool by_procfs = false;
306 int r;
307
308 assert(fd >= 0 || fd == AT_FDCWD);
309 assert(name);
310 assert(value);
311 assert((flags & ~(AT_SYMLINK_FOLLOW|AT_EMPTY_PATH)) == 0);
312
313 /* So, this is a single function that does what setxattr()/lsetxattr()/fsetxattr() do, but in one go,
314 * and with additional bells and whistles. Specifically:
315 *
316 * 1. This works on O_PATH fds (which fsetxattr() does not)
317 * 2. Provides full openat()-style semantics, i.e. by-fd, by-path and combination thereof
318 * 3. As extension to openat()-style semantics implies AT_EMPTY_PATH if path is NULL.
319 */
320
321 if (!path) /* If path is NULL, imply AT_EMPTY_PATH. – But if it's "", don't — for safety reasons. */
322 flags |= AT_EMPTY_PATH;
323
324 if (size == SIZE_MAX)
325 size = strlen(value);
326
327 if (isempty(path)) {
328 if (!FLAGS_SET(flags, AT_EMPTY_PATH))
329 return -EINVAL;
330
331 if (fd == AT_FDCWD) /* Both unspecified? Then operate on current working directory */
332 path = ".";
333 else {
334 r = fd_is_opath(fd);
335 if (r < 0)
336 return r;
337
338 by_procfs = r;
339 path = NULL;
340 }
341
342 } else if (fd != AT_FDCWD) {
343
344 /* If both have been specified, then we go via O_PATH */
345 opened_fd = openat(fd, path, O_PATH|O_CLOEXEC|(FLAGS_SET(flags, AT_SYMLINK_FOLLOW) ? 0 : O_NOFOLLOW));
346 if (opened_fd < 0)
347 return -errno;
348
349 fd = opened_fd;
350 path = NULL;
351 by_procfs = true; /* fsetxattr() is not going to work, go via /proc/ link right-away */
352 }
353
354 if (path)
355 r = FLAGS_SET(flags, AT_SYMLINK_FOLLOW) ? setxattr(path, name, value, size, 0)
356 : lsetxattr(path, name, value, size, 0);
357 else
358 r = by_procfs ? setxattr(FORMAT_PROC_FD_PATH(fd), name, value, size, 0)
359 : fsetxattr(fd, name, value, size, 0);
360 if (r < 0)
361 return -errno;
362
363 return 0;
364 }