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 
getxattr_at_malloc(int fd,const char * path,const char * name,int flags,char ** ret)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 = -1;
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 
parse_crtime(le64_t le,usec_t * usec)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 
fd_getcrtime_at(int fd,const char * path,int flags,usec_t * ret)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 vaccuuming 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. Thius 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 
fd_setcrtime(int fd,usec_t usec)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 
listxattr_at_malloc(int fd,const char * path,int flags,char ** ret)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 = -1;
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