]> git.ipfire.org Git - thirdparty/systemd.git/blame_incremental - src/basic/xattr-util.c
Merge pull request #14081 from poettering/xattr-list-rework
[thirdparty/systemd.git] / src / basic / xattr-util.c
... / ...
CommitLineData
1/* SPDX-License-Identifier: LGPL-2.1+ */
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 "fd-util.h"
12#include "macro.h"
13#include "missing_syscall.h"
14#include "sparse-endian.h"
15#include "stdio-util.h"
16#include "string-util.h"
17#include "time-util.h"
18#include "xattr-util.h"
19
20int getxattr_malloc(
21 const char *path,
22 const char *name,
23 char **ret,
24 bool allow_symlink) {
25
26 size_t l = 100;
27
28 assert(path);
29 assert(name);
30 assert(ret);
31
32 for(;;) {
33 _cleanup_free_ char *v = NULL;
34 ssize_t n;
35
36 v = new0(char, l+1);
37 if (!v)
38 return -ENOMEM;
39
40 if (allow_symlink)
41 n = lgetxattr(path, name, v, l);
42 else
43 n = getxattr(path, name, v, l);
44 if (n < 0) {
45 if (errno != ERANGE)
46 return -errno;
47 } else {
48 v[n] = 0; /* NUL terminate */
49 *ret = TAKE_PTR(v);
50 return (int) n;
51 }
52
53 if (allow_symlink)
54 n = lgetxattr(path, name, NULL, 0);
55 else
56 n = getxattr(path, name, NULL, 0);
57 if (n < 0)
58 return -errno;
59 if (n > INT_MAX) /* We couldn't return this as 'int' anymore */
60 return -E2BIG;
61
62 l = (size_t) n;
63 }
64}
65
66int fgetxattr_malloc(
67 int fd,
68 const char *name,
69 char **ret) {
70
71 size_t l = 100;
72
73 assert(fd >= 0);
74 assert(name);
75 assert(ret);
76
77 for (;;) {
78 _cleanup_free_ char *v = NULL;
79 ssize_t n;
80
81 v = new(char, l+1);
82 if (!v)
83 return -ENOMEM;
84
85 n = fgetxattr(fd, name, v, l);
86 if (n < 0) {
87 if (errno != ERANGE)
88 return -errno;
89 } else {
90 v[n] = 0; /* NUL terminate */
91 *ret = TAKE_PTR(v);
92 return (int) n;
93 }
94
95 n = fgetxattr(fd, name, NULL, 0);
96 if (n < 0)
97 return -errno;
98 if (n > INT_MAX) /* We couldn't return this as 'int' anymore */
99 return -E2BIG;
100
101 l = (size_t) n;
102 }
103}
104
105int fgetxattrat_fake(
106 int dirfd,
107 const char *filename,
108 const char *attribute,
109 void *value, size_t size,
110 int flags,
111 size_t *ret_size) {
112
113 char fn[STRLEN("/proc/self/fd/") + DECIMAL_STR_MAX(int) + 1];
114 _cleanup_close_ int fd = -1;
115 ssize_t l;
116
117 /* The kernel doesn't have a fgetxattrat() command, hence let's emulate one */
118
119 if (flags & ~(AT_SYMLINK_NOFOLLOW|AT_EMPTY_PATH))
120 return -EINVAL;
121
122 if (isempty(filename)) {
123 if (!(flags & AT_EMPTY_PATH))
124 return -EINVAL;
125
126 xsprintf(fn, "/proc/self/fd/%i", dirfd);
127 } else {
128 fd = openat(dirfd, filename, O_CLOEXEC|O_PATH|(flags & AT_SYMLINK_NOFOLLOW ? O_NOFOLLOW : 0));
129 if (fd < 0)
130 return -errno;
131
132 xsprintf(fn, "/proc/self/fd/%i", fd);
133 }
134
135 l = getxattr(fn, attribute, value, size);
136 if (l < 0)
137 return -errno;
138
139 *ret_size = l;
140 return 0;
141}
142
143static int parse_crtime(le64_t le, usec_t *usec) {
144 uint64_t u;
145
146 assert(usec);
147
148 u = le64toh(le);
149 if (IN_SET(u, 0, (uint64_t) -1))
150 return -EIO;
151
152 *usec = (usec_t) u;
153 return 0;
154}
155
156int fd_getcrtime_at(int dirfd, const char *name, usec_t *ret, int flags) {
157 struct_statx sx
158#if HAS_FEATURE_MEMORY_SANITIZER
159 = {}
160# warning "Explicitly initializing struct statx, to work around msan limitation. Please remove as soon as msan has been updated to not require this."
161#endif
162 ;
163 usec_t a, b;
164 le64_t le;
165 size_t n;
166 int r;
167
168 assert(ret);
169
170 if (flags & ~(AT_EMPTY_PATH|AT_SYMLINK_NOFOLLOW))
171 return -EINVAL;
172
173 /* So here's the deal: the creation/birth time (crtime/btime) of a file is a relatively newly supported concept
174 * on Linux (or more strictly speaking: a concept that only recently got supported in the API, it was
175 * implemented on various file systems on the lower level since a while, but never was accessible). However, we
176 * needed a concept like that for vaccuuming algorithms and such, hence we emulated it via a user xattr for a
177 * long time. Starting with Linux 4.11 there's statx() which exposes the timestamp to userspace for the first
178 * time, where it is available. Thius function will read it, but it tries to keep some compatibility with older
179 * systems: we try to read both the crtime/btime and the xattr, and then use whatever is older. After all the
180 * concept is useful for determining how "old" a file really is, and hence using the older of the two makes
181 * most sense. */
182
183 if (statx(dirfd, strempty(name), flags|AT_STATX_DONT_SYNC, STATX_BTIME, &sx) >= 0 &&
184 (sx.stx_mask & STATX_BTIME) &&
185 sx.stx_btime.tv_sec != 0)
186 a = (usec_t) sx.stx_btime.tv_sec * USEC_PER_SEC +
187 (usec_t) sx.stx_btime.tv_nsec / NSEC_PER_USEC;
188 else
189 a = USEC_INFINITY;
190
191 r = fgetxattrat_fake(dirfd, name, "user.crtime_usec", &le, sizeof(le), flags, &n);
192 if (r >= 0) {
193 if (n != sizeof(le))
194 r = -EIO;
195 else
196 r = parse_crtime(le, &b);
197 }
198 if (r < 0) {
199 if (a != USEC_INFINITY) {
200 *ret = a;
201 return 0;
202 }
203
204 return r;
205 }
206
207 if (a != USEC_INFINITY)
208 *ret = MIN(a, b);
209 else
210 *ret = b;
211
212 return 0;
213}
214
215int fd_getcrtime(int fd, usec_t *ret) {
216 return fd_getcrtime_at(fd, NULL, ret, AT_EMPTY_PATH);
217}
218
219int path_getcrtime(const char *p, usec_t *ret) {
220 return fd_getcrtime_at(AT_FDCWD, p, ret, 0);
221}
222
223int fd_setcrtime(int fd, usec_t usec) {
224 le64_t le;
225
226 assert(fd >= 0);
227
228 if (IN_SET(usec, 0, USEC_INFINITY))
229 usec = now(CLOCK_REALTIME);
230
231 le = htole64((uint64_t) usec);
232 if (fsetxattr(fd, "user.crtime_usec", &le, sizeof(le), 0) < 0)
233 return -errno;
234
235 return 0;
236}
237
238int flistxattr_malloc(int fd, char **ret) {
239 size_t l = 100;
240
241 assert(fd >= 0);
242 assert(ret);
243
244 for (;;) {
245 _cleanup_free_ char *v = NULL;
246 ssize_t n;
247
248 v = new(char, l+1);
249 if (!v)
250 return -ENOMEM;
251
252 n = flistxattr(fd, v, l);
253 if (n < 0) {
254 if (errno != ERANGE)
255 return -errno;
256 } else {
257 v[n] = 0; /* NUL terminate */
258 *ret = TAKE_PTR(v);
259 return (int) n;
260 }
261
262 n = flistxattr(fd, NULL, 0);
263 if (n < 0)
264 return -errno;
265 if (n > INT_MAX) /* We couldn't return this as 'int' anymore */
266 return -E2BIG;
267
268 l = (size_t) n;
269 }
270}