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