]>
git.ipfire.org Git - thirdparty/systemd.git/blob - src/basic/xattr-util.c
1 /* SPDX-License-Identifier: LGPL-2.1-or-later */
10 #include "alloc-util.h"
11 #include "errno-util.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"
22 int getxattr_at_malloc(
29 _cleanup_close_
int opened_fd
= -EBADF
;
30 unsigned n_attempts
= 7;
31 bool by_procfs
= false;
34 assert(fd
>= 0 || fd
== AT_FDCWD
);
36 assert((flags
& ~(AT_SYMLINK_FOLLOW
|AT_EMPTY_PATH
)) == 0);
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:
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)
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
;
53 if (!FLAGS_SET(flags
, AT_EMPTY_PATH
))
56 if (fd
== AT_FDCWD
) /* Both unspecified? Then operate on current working directory */
61 } else if (fd
!= AT_FDCWD
) {
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
));
70 by_procfs
= true; /* fgetxattr() is not going to work, go via /proc/ link right-away */
74 _cleanup_free_
char *v
= NULL
;
77 if (n_attempts
== 0) /* If someone is racing against us, give up eventually */
85 l
= MALLOC_ELEMENTSOF(v
) - 1;
88 n
= FLAGS_SET(flags
, AT_SYMLINK_FOLLOW
) ? getxattr(path
, name
, v
, l
) : lgetxattr(path
, name
, v
, l
);
90 n
= by_procfs
? getxattr(FORMAT_PROC_FD_PATH(fd
), name
, v
, l
) : fgetxattr(fd
, name
, v
, l
);
93 if (by_procfs
|| path
)
96 by_procfs
= true; /* Might be an O_PATH fd, try again via /proc/ link */
103 v
[n
] = 0; /* NUL terminate */
109 n
= FLAGS_SET(flags
, AT_SYMLINK_FOLLOW
) ? getxattr(path
, name
, NULL
, 0) : lgetxattr(path
, name
, NULL
, 0);
111 n
= by_procfs
? getxattr(FORMAT_PROC_FD_PATH(fd
), name
, NULL
, 0) : fgetxattr(fd
, name
, NULL
, 0);
114 if (n
> INT_MAX
) /* We couldn't return this as 'int' anymore */
121 static int parse_crtime(le64_t le
, usec_t
*usec
) {
127 if (IN_SET(u
, 0, UINT64_MAX
))
140 _cleanup_free_ le64_t
*le
= NULL
;
141 STRUCT_STATX_DEFINE(sx
);
145 assert(fd
>= 0 || fd
== AT_FDCWD
);
146 assert((flags
& ~(AT_SYMLINK_FOLLOW
|AT_EMPTY_PATH
)) == 0);
150 flags
|= AT_EMPTY_PATH
;
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
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
,
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
;
173 r
= getxattr_at_malloc(fd
, path
, "user.crtime_usec", flags
, (char**) &le
);
175 if (r
!= sizeof(*le
))
178 r
= parse_crtime(*le
, &b
);
181 if (a
!= USEC_INFINITY
) {
189 if (a
!= USEC_INFINITY
)
197 int fd_setcrtime(int fd
, usec_t usec
) {
202 if (!timestamp_is_set(usec
))
203 usec
= now(CLOCK_REALTIME
);
205 le
= htole64((uint64_t) usec
);
206 return RET_NERRNO(fsetxattr(fd
, "user.crtime_usec", &le
, sizeof(le
), 0));
209 int listxattr_at_malloc(
215 _cleanup_close_
int opened_fd
= -EBADF
;
216 bool by_procfs
= false;
217 unsigned n_attempts
= 7;
220 assert(fd
>= 0 || fd
== AT_FDCWD
);
221 assert((flags
& ~(AT_SYMLINK_FOLLOW
|AT_EMPTY_PATH
)) == 0);
224 /* This is to listxattr()/llistattr()/flistattr() what getxattr_at_malloc() is to getxattr()/… */
226 if (!path
) /* If path is NULL, imply AT_EMPTY_PATH. – But if it's "", don't. */
227 flags
|= AT_EMPTY_PATH
;
230 if (!FLAGS_SET(flags
, AT_EMPTY_PATH
))
233 if (fd
== AT_FDCWD
) /* Both unspecified? Then operate on current working directory */
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
));
250 _cleanup_free_
char *v
= NULL
;
253 if (n_attempts
== 0) /* If someone is racing against us, give up eventually */
261 l
= MALLOC_ELEMENTSOF(v
) - 1;
264 n
= FLAGS_SET(flags
, AT_SYMLINK_FOLLOW
) ? listxattr(path
, v
, l
) : llistxattr(path
, v
, l
);
266 n
= by_procfs
? listxattr(FORMAT_PROC_FD_PATH(fd
), v
, l
) : flistxattr(fd
, v
, l
);
268 if (errno
== EBADF
) {
269 if (by_procfs
|| path
)
272 by_procfs
= true; /* Might be an O_PATH fd, try again via /proc/ link */
279 v
[n
] = 0; /* NUL terminate */
285 n
= FLAGS_SET(flags
, AT_SYMLINK_FOLLOW
) ? listxattr(path
, NULL
, 0) : llistxattr(path
, NULL
, 0);
287 n
= by_procfs
? listxattr(FORMAT_PROC_FD_PATH(fd
), NULL
, 0) : flistxattr(fd
, NULL
, 0);
290 if (n
> INT_MAX
) /* We couldn't return this as 'int' anymore */
297 int xsetxattr(int fd
,
304 _cleanup_close_
int opened_fd
= -EBADF
;
305 bool by_procfs
= false;
308 assert(fd
>= 0 || fd
== AT_FDCWD
);
311 assert((flags
& ~(AT_SYMLINK_FOLLOW
|AT_EMPTY_PATH
)) == 0);
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:
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.
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
;
324 if (size
== SIZE_MAX
)
325 size
= strlen(value
);
328 if (!FLAGS_SET(flags
, AT_EMPTY_PATH
))
331 if (fd
== AT_FDCWD
) /* Both unspecified? Then operate on current working directory */
342 } else if (fd
!= AT_FDCWD
) {
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
));
351 by_procfs
= true; /* fsetxattr() is not going to work, go via /proc/ link right-away */
355 r
= FLAGS_SET(flags
, AT_SYMLINK_FOLLOW
) ? setxattr(path
, name
, value
, size
, 0)
356 : lsetxattr(path
, name
, value
, size
, 0);
358 r
= by_procfs
? setxattr(FORMAT_PROC_FD_PATH(fd
), name
, value
, size
, 0)
359 : fsetxattr(fd
, name
, value
, size
, 0);