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