Compare commits

...

4 Commits

Author SHA1 Message Date
Zbigniew Jędrzejewski-Szmek f77d6ec953
Merge pull request #16877 from poettering/tmpfiles-statx
tmpfiles: use statx()
2020-09-01 16:32:50 +02:00
Lennart Poettering df2f58176d doc: cross link sd_listen_fd() docs a bit
Let's make sure the sd_listen_fd() docs are really found from the
.socket file documentation as well as the FileDescriptorStoreMax=
documentation.

Let's also emphasize that that's where the order in which the fds are
passed are documented.

Fixes: #16647
2020-09-01 16:20:34 +02:00
Lennart Poettering ddb439b8f9 tmpfiles: use statx() when aging files
This allows us to properly detect mount points, for free. (Also, allows
us to respect btimes that are newer than the cutoff, which should be
useful when people untar file trees in /var/tmp)

Fixes: #16848
2020-08-28 15:45:37 +02:00
Lennart Poettering cd1361e203 time-util: add timespec_store_nsec()
timespec_store_nsec() is to timespec_store() what timespec_load_nsec()
is to timespec_load(), i.e. the nsec version of the usual usec API
2020-08-28 14:22:43 +02:00
7 changed files with 298 additions and 108 deletions

View File

@ -54,24 +54,19 @@
(i.e. <constant>SD_LISTEN_FDS_START</constant>), the remaining (i.e. <constant>SD_LISTEN_FDS_START</constant>), the remaining
descriptors follow at 4, 5, 6, …, if any.</para> descriptors follow at 4, 5, 6, …, if any.</para>
<para>If a daemon receives more than one file descriptor, they <para>If a daemon receives more than one file descriptor, they will be passed in the same order as
will be passed in the same order as configured in the systemd configured in the systemd socket unit file (see
socket unit file (see <citerefentry><refentrytitle>systemd.socket</refentrytitle><manvolnum>5</manvolnum></citerefentry> for
<citerefentry><refentrytitle>systemd.socket</refentrytitle><manvolnum>5</manvolnum></citerefentry> details) — if there's only one such file (see below). Nonetheless, it is recommended to verify the
for details). Nonetheless, it is recommended to verify the correct correct socket types before using them. To simplify this checking, the functions
socket types before using them. To simplify this checking, the
functions
<citerefentry><refentrytitle>sd_is_fifo</refentrytitle><manvolnum>3</manvolnum></citerefentry>, <citerefentry><refentrytitle>sd_is_fifo</refentrytitle><manvolnum>3</manvolnum></citerefentry>,
<citerefentry><refentrytitle>sd_is_socket</refentrytitle><manvolnum>3</manvolnum></citerefentry>, <citerefentry><refentrytitle>sd_is_socket</refentrytitle><manvolnum>3</manvolnum></citerefentry>,
<citerefentry><refentrytitle>sd_is_socket_inet</refentrytitle><manvolnum>3</manvolnum></citerefentry>, <citerefentry><refentrytitle>sd_is_socket_inet</refentrytitle><manvolnum>3</manvolnum></citerefentry>,
<citerefentry><refentrytitle>sd_is_socket_unix</refentrytitle><manvolnum>3</manvolnum></citerefentry> <citerefentry><refentrytitle>sd_is_socket_unix</refentrytitle><manvolnum>3</manvolnum></citerefentry> are
are provided. In order to maximize flexibility, it is recommended provided. In order to maximize flexibility, it is recommended to make these checks as loose as possible
to make these checks as loose as possible without allowing without allowing incorrect setups. i.e. often, the actual port number a socket is bound to matters little
incorrect setups. i.e. often, the actual port number a socket is for the service to work, hence it should not be verified. On the other hand, whether a socket is a
bound to matters little for the service to work, hence it should datagram or stream socket matters a lot for the most common program logics and should be checked.</para>
not be verified. On the other hand, whether a socket is a datagram
or stream socket matters a lot for the most common program logics
and should be checked.</para>
<para>This function call will set the FD_CLOEXEC flag for all <para>This function call will set the FD_CLOEXEC flag for all
passed file descriptors to avoid further inheritance to children passed file descriptors to avoid further inheritance to children

View File

@ -1028,20 +1028,24 @@
<varlistentry> <varlistentry>
<term><varname>FileDescriptorStoreMax=</varname></term> <term><varname>FileDescriptorStoreMax=</varname></term>
<listitem><para>Configure how many file descriptors may be stored in the service manager for the service using <listitem><para>Configure how many file descriptors may be stored in the service manager for the
service using
<citerefentry><refentrytitle>sd_pid_notify_with_fds</refentrytitle><manvolnum>3</manvolnum></citerefentry>'s <citerefentry><refentrytitle>sd_pid_notify_with_fds</refentrytitle><manvolnum>3</manvolnum></citerefentry>'s
<literal>FDSTORE=1</literal> messages. This is useful for implementing services that can restart after an <literal>FDSTORE=1</literal> messages. This is useful for implementing services that can restart
explicit request or a crash without losing state. Any open sockets and other file descriptors which should not after an explicit request or a crash without losing state. Any open sockets and other file
be closed during the restart may be stored this way. Application state can either be serialized to a file in descriptors which should not be closed during the restart may be stored this way. Application state
<filename>/run</filename>, or better, stored in a can either be serialized to a file in <filename>/run</filename>, or better, stored in a
<citerefentry><refentrytitle>memfd_create</refentrytitle><manvolnum>2</manvolnum></citerefentry> memory file <citerefentry><refentrytitle>memfd_create</refentrytitle><manvolnum>2</manvolnum></citerefentry>
descriptor. Defaults to 0, i.e. no file descriptors may be stored in the service manager. All file descriptors memory file descriptor. Defaults to 0, i.e. no file descriptors may be stored in the service
passed to the service manager from a specific service are passed back to the service's main process on the next manager. All file descriptors passed to the service manager from a specific service are passed back
service restart. Any file descriptors passed to the service manager are automatically closed when to the service's main process on the next service restart (see
<constant>POLLHUP</constant> or <constant>POLLERR</constant> is seen on them, or when the service is fully <citerefentry><refentrytitle>sd_listen_fds</refentrytitle><manvolnum>3</manvolnum></citerefentry> for
stopped and no job is queued or being executed for it. If this option is used, <varname>NotifyAccess=</varname> details about the precise protocol used and the order in which the file descriptors are passed). Any
(see above) should be set to open access to the notification socket provided by systemd. If file descriptors passed to the service manager are automatically closed when
<varname>NotifyAccess=</varname> is not set, it will be implicitly set to <constant>POLLHUP</constant> or <constant>POLLERR</constant> is seen on them, or when the service is
fully stopped and no job is queued or being executed for it. If this option is used,
<varname>NotifyAccess=</varname> (see above) should be set to open access to the notification socket
provided by systemd. If <varname>NotifyAccess=</varname> is not set, it will be implicitly set to
<option>main</option>.</para></listitem> <option>main</option>.</para></listitem>
</varlistentry> </varlistentry>

View File

@ -81,16 +81,14 @@
services, as well as parallelized starting of services. See the services, as well as parallelized starting of services. See the
blog stories linked at the end for an introduction.</para> blog stories linked at the end for an introduction.</para>
<para>Note that the daemon software configured for socket <para>Note that the daemon software configured for socket activation with socket units needs to be able
activation with socket units needs to be able to accept sockets to accept sockets from systemd, either via systemd's native socket passing interface (see
from systemd, either via systemd's native socket passing interface <citerefentry><refentrytitle>sd_listen_fds</refentrytitle><manvolnum>3</manvolnum></citerefentry> for
(see details about the precise protocol used and the order in which the file descriptors are passed) or via
<citerefentry><refentrytitle>sd_listen_fds</refentrytitle><manvolnum>3</manvolnum></citerefentry> traditional <citerefentry
for details) or via the traditional project='freebsd'><refentrytitle>inetd</refentrytitle><manvolnum>8</manvolnum></citerefentry>-style
<citerefentry project='freebsd'><refentrytitle>inetd</refentrytitle><manvolnum>8</manvolnum></citerefentry>-style socket passing (i.e. sockets passed in via standard input and output, using
socket passing (i.e. sockets passed in via standard input and <varname>StandardInput=socket</varname> in the service file).</para>
output, using <varname>StandardInput=socket</varname> in the
service file).</para>
<para>All network sockets allocated through <filename>.socket</filename> units are allocated in the host's network <para>All network sockets allocated through <filename>.socket</filename> units are allocated in the host's network
namespace (see <citerefentry namespace (see <citerefentry

View File

@ -50,13 +50,48 @@ struct statx STATX_DEFINITION;
struct new_statx STATX_DEFINITION; struct new_statx STATX_DEFINITION;
/* a528d35e8bfcc521d7cb70aaf03e1bd296c8493f (4.11) */ /* a528d35e8bfcc521d7cb70aaf03e1bd296c8493f (4.11) */
#ifndef STATX_BTIME #ifndef AT_STATX_DONT_SYNC
#define STATX_BTIME 0x00000800U #define AT_STATX_DONT_SYNC 0x4000
#endif #endif
/* a528d35e8bfcc521d7cb70aaf03e1bd296c8493f (4.11) */ /* a528d35e8bfcc521d7cb70aaf03e1bd296c8493f (4.11) */
#ifndef AT_STATX_DONT_SYNC #ifndef STATX_TYPE
#define AT_STATX_DONT_SYNC 0x4000 #define STATX_TYPE 0x00000001U
#endif
/* a528d35e8bfcc521d7cb70aaf03e1bd296c8493f (4.11) */
#ifndef STATX_MODE
#define STATX_MODE 0x00000002U
#endif
/* a528d35e8bfcc521d7cb70aaf03e1bd296c8493f (4.11) */
#ifndef STATX_UID
#define STATX_UID 0x00000008U
#endif
/* a528d35e8bfcc521d7cb70aaf03e1bd296c8493f (4.11) */
#ifndef STATX_ATIME
#define STATX_ATIME 0x00000020U
#endif
/* a528d35e8bfcc521d7cb70aaf03e1bd296c8493f (4.11) */
#ifndef STATX_MTIME
#define STATX_MTIME 0x00000040U
#endif
/* a528d35e8bfcc521d7cb70aaf03e1bd296c8493f (4.11) */
#ifndef STATX_CTIME
#define STATX_CTIME 0x00000080U
#endif
/* a528d35e8bfcc521d7cb70aaf03e1bd296c8493f (4.11) */
#ifndef STATX_INO
#define STATX_INO 0x00000100U
#endif
/* a528d35e8bfcc521d7cb70aaf03e1bd296c8493f (4.11) */
#ifndef STATX_BTIME
#define STATX_BTIME 0x00000800U
#endif #endif
/* fa2fcf4f1df1559a0a4ee0f46915b496cc2ebf60 (5.8) */ /* fa2fcf4f1df1559a0a4ee0f46915b496cc2ebf60 (5.8) */

View File

@ -244,12 +244,28 @@ struct timespec *timespec_store(struct timespec *ts, usec_t u) {
if (u == USEC_INFINITY || if (u == USEC_INFINITY ||
u / USEC_PER_SEC >= TIME_T_MAX) { u / USEC_PER_SEC >= TIME_T_MAX) {
ts->tv_sec = (time_t) -1; ts->tv_sec = (time_t) -1;
ts->tv_nsec = (long) -1; ts->tv_nsec = -1L;
return ts; return ts;
} }
ts->tv_sec = (time_t) (u / USEC_PER_SEC); ts->tv_sec = (time_t) (u / USEC_PER_SEC);
ts->tv_nsec = (long int) ((u % USEC_PER_SEC) * NSEC_PER_USEC); ts->tv_nsec = (long) ((u % USEC_PER_SEC) * NSEC_PER_USEC);
return ts;
}
struct timespec *timespec_store_nsec(struct timespec *ts, nsec_t n) {
assert(ts);
if (n == NSEC_INFINITY ||
n / NSEC_PER_SEC >= TIME_T_MAX) {
ts->tv_sec = (time_t) -1;
ts->tv_nsec = -1L;
return ts;
}
ts->tv_sec = (time_t) (n / NSEC_PER_SEC);
ts->tv_nsec = (long) (n % NSEC_PER_SEC);
return ts; return ts;
} }

View File

@ -112,6 +112,7 @@ usec_t triple_timestamp_by_clock(triple_timestamp *ts, clockid_t clock);
usec_t timespec_load(const struct timespec *ts) _pure_; usec_t timespec_load(const struct timespec *ts) _pure_;
nsec_t timespec_load_nsec(const struct timespec *ts) _pure_; nsec_t timespec_load_nsec(const struct timespec *ts) _pure_;
struct timespec *timespec_store(struct timespec *ts, usec_t u); struct timespec *timespec_store(struct timespec *ts, usec_t u);
struct timespec *timespec_store_nsec(struct timespec *ts, nsec_t n);
usec_t timeval_load(const struct timeval *tv) _pure_; usec_t timeval_load(const struct timeval *tv) _pure_;
struct timeval *timeval_store(struct timeval *tv, usec_t u); struct timeval *timeval_store(struct timeval *tv, usec_t u);

View File

@ -38,6 +38,8 @@
#include "log.h" #include "log.h"
#include "macro.h" #include "macro.h"
#include "main-func.h" #include "main-func.h"
#include "missing_stat.h"
#include "missing_syscall.h"
#include "mkdir.h" #include "mkdir.h"
#include "mount-util.h" #include "mount-util.h"
#include "mountpoint-util.h" #include "mountpoint-util.h"
@ -490,49 +492,137 @@ static DIR* opendir_nomod(const char *path) {
return xopendirat_nomod(AT_FDCWD, path); return xopendirat_nomod(AT_FDCWD, path);
} }
static inline nsec_t load_statx_timestamp_nsec(const struct statx_timestamp *ts) {
assert(ts);
if (ts->tv_sec < 0)
return NSEC_INFINITY;
if ((nsec_t) ts->tv_sec >= (UINT64_MAX - ts->tv_nsec) / NSEC_PER_SEC)
return NSEC_INFINITY;
return ts->tv_sec * NSEC_PER_SEC + ts->tv_nsec;
}
static int dir_cleanup( static int dir_cleanup(
Item *i, Item *i,
const char *p, const char *p,
DIR *d, DIR *d,
const struct stat *ds, nsec_t self_atime_nsec,
usec_t cutoff, nsec_t self_mtime_nsec,
dev_t rootdev, nsec_t cutoff_nsec,
dev_t rootdev_major,
dev_t rootdev_minor,
bool mountpoint, bool mountpoint,
int maxdepth, int maxdepth,
bool keep_this_level) { bool keep_this_level) {
struct dirent *dent; static bool use_statx = true;
bool deleted = false; bool deleted = false;
struct dirent *dent;
int r = 0; int r = 0;
FOREACH_DIRENT_ALL(dent, d, break) { FOREACH_DIRENT_ALL(dent, d, break) {
struct stat s;
usec_t age;
_cleanup_free_ char *sub_path = NULL; _cleanup_free_ char *sub_path = NULL;
nsec_t atime_nsec, mtime_nsec, ctime_nsec, btime_nsec;
mode_t mode;
uid_t uid;
if (dot_or_dot_dot(dent->d_name)) if (dot_or_dot_dot(dent->d_name))
continue; continue;
if (fstatat(dirfd(d), dent->d_name, &s, AT_SYMLINK_NOFOLLOW) < 0) { if (use_statx) {
if (errno == ENOENT) /* If statx() is supported, use it. It's preferable over fstatat() since it tells us
continue; * explicitly where we are looking at a mount point, for free as side
* information. Determing the same information without statx() is hard, see the
* complexity of path_is_mount_point(), and also much slower as it requires a numbre
* of syscalls instead of just one. Hence, when we have modern statx() we use it
* instead of fstat() and do proper mount point checks, while on older kernels's well
* do traditional st_dev based detection of mount points.
*
* Using statx() for detecting mount points also has the benfit that we handle weird
* file systems such as overlayfs better where each file is originating from a
* different st_dev. */
/* FUSE, NFS mounts, SELinux might return EACCES */ struct statx sx
r = log_full_errno(errno == EACCES ? LOG_DEBUG : LOG_ERR, errno, #if HAS_FEATURE_MEMORY_SANITIZER
"stat(%s/%s) failed: %m", p, dent->d_name); = {}
continue; # warning "Explicitly initializing struct statx, to work around msan limitation. Please remove as soon as msan has been updated to not require this."
#endif
;
if (statx(dirfd(d), dent->d_name,
AT_SYMLINK_NOFOLLOW|AT_NO_AUTOMOUNT,
STATX_TYPE|STATX_MODE|STATX_UID|STATX_ATIME|STATX_MTIME|STATX_CTIME|STATX_BTIME,
&sx) < 0) {
if (errno == ENOENT)
continue;
if (ERRNO_IS_NOT_SUPPORTED(errno) || errno == EPERM)
use_statx = false; /* Not supported or blocked by seccomp or so */
else {
/* FUSE, NFS mounts, SELinux might return EACCES */
r = log_full_errno(errno == EACCES ? LOG_DEBUG : LOG_ERR, errno,
"statx(%s/%s) failed: %m", p, dent->d_name);
continue;
}
} else {
if (FLAGS_SET(sx.stx_attributes_mask, STATX_ATTR_MOUNT_ROOT)) {
/* Yay, we have the mount point API, use it */
if (FLAGS_SET(sx.stx_attributes, STATX_ATTR_MOUNT_ROOT)) {
log_debug("Ignoring \"%s/%s\": different mount points.", p, dent->d_name);
continue;
}
} else {
/* So we have statx() but the STATX_ATTR_MOUNT_ROOT flag is not
* supported, fall back to traditional stx_dev checking. */
if (sx.stx_dev_major != rootdev_major ||
sx.stx_dev_minor != rootdev_minor) {
log_debug("Ignoring \"%s/%s\": different filesystem.", p, dent->d_name);
continue;
}
}
mode = sx.stx_mode;
uid = sx.stx_uid;
atime_nsec = FLAGS_SET(sx.stx_mask, STATX_ATIME) ? load_statx_timestamp_nsec(&sx.stx_atime) : 0;
mtime_nsec = FLAGS_SET(sx.stx_mask, STATX_MTIME) ? load_statx_timestamp_nsec(&sx.stx_mtime) : 0;
ctime_nsec = FLAGS_SET(sx.stx_mask, STATX_CTIME) ? load_statx_timestamp_nsec(&sx.stx_ctime) : 0;
btime_nsec = FLAGS_SET(sx.stx_mask, STATX_BTIME) ? load_statx_timestamp_nsec(&sx.stx_btime) : 0;
}
} }
/* Stay on the same filesystem */ if (!use_statx) {
if (s.st_dev != rootdev) { struct stat s;
log_debug("Ignoring \"%s/%s\": different filesystem.", p, dent->d_name);
continue; if (fstatat(dirfd(d), dent->d_name, &s, AT_SYMLINK_NOFOLLOW) < 0) {
if (errno == ENOENT)
continue;
/* FUSE, NFS mounts, SELinux might return EACCES */
r = log_full_errno(errno == EACCES ? LOG_DEBUG : LOG_ERR, errno,
"stat(%s/%s) failed: %m", p, dent->d_name);
continue;
}
/* Stay on the same filesystem */
if (major(s.st_dev) != rootdev_major || minor(s.st_dev) != rootdev_minor) {
log_debug("Ignoring \"%s/%s\": different filesystem.", p, dent->d_name);
continue;
}
mode = s.st_mode;
uid = s.st_uid;
atime_nsec = timespec_load_nsec(&s.st_atim);
mtime_nsec = timespec_load_nsec(&s.st_mtim);
ctime_nsec = timespec_load_nsec(&s.st_ctim);
btime_nsec = 0;
} }
/* Try to detect bind mounts of the same filesystem instance; they /* Try to detect bind mounts of the same filesystem instance; they
* do not differ in device major/minors. This type of query is not * do not differ in device major/minors. This type of query is not
* supported on all kernels or filesystem types though. */ * supported on all kernels or filesystem types though. */
if (S_ISDIR(s.st_mode)) { if (S_ISDIR(mode)) {
int q; int q;
q = fd_is_mount_point(dirfd(d), dent->d_name, 0); q = fd_is_mount_point(dirfd(d), dent->d_name, 0);
@ -561,12 +651,12 @@ static int dir_cleanup(
continue; continue;
} }
if (S_ISDIR(s.st_mode)) { if (S_ISDIR(mode)) {
_cleanup_closedir_ DIR *sub_dir = NULL; _cleanup_closedir_ DIR *sub_dir = NULL;
if (mountpoint && if (mountpoint &&
streq(dent->d_name, "lost+found") && streq(dent->d_name, "lost+found") &&
s.st_uid == 0) { uid == 0) {
log_debug("Ignoring directory \"%s\".", sub_path); log_debug("Ignoring directory \"%s\".", sub_path);
continue; continue;
} }
@ -589,7 +679,11 @@ static int dir_cleanup(
continue; continue;
} }
q = dir_cleanup(i, sub_path, sub_dir, &s, cutoff, rootdev, false, maxdepth-1, false); q = dir_cleanup(i,
sub_path, sub_dir,
atime_nsec, mtime_nsec, cutoff_nsec,
rootdev_major, rootdev_minor,
false, maxdepth-1, false);
if (q < 0) if (q < 0)
r = q; r = q;
} }
@ -605,22 +699,28 @@ static int dir_cleanup(
} }
/* Ignore ctime, we change it when deleting */ /* Ignore ctime, we change it when deleting */
age = timespec_load(&s.st_mtim); if (mtime_nsec != NSEC_INFINITY && mtime_nsec >= cutoff_nsec) {
if (age >= cutoff) {
char a[FORMAT_TIMESTAMP_MAX]; char a[FORMAT_TIMESTAMP_MAX];
/* Follows spelling in stat(1). */ /* Follows spelling in stat(1). */
log_debug("Directory \"%s\": modify time %s is too new.", log_debug("Directory \"%s\": modify time %s is too new.",
sub_path, sub_path,
format_timestamp_style(a, sizeof(a), age, TIMESTAMP_US)); format_timestamp_style(a, sizeof(a), mtime_nsec / NSEC_PER_USEC, TIMESTAMP_US));
continue; continue;
} }
age = timespec_load(&s.st_atim); if (atime_nsec != NSEC_INFINITY && atime_nsec >= cutoff_nsec) {
if (age >= cutoff) {
char a[FORMAT_TIMESTAMP_MAX]; char a[FORMAT_TIMESTAMP_MAX];
log_debug("Directory \"%s\": access time %s is too new.", log_debug("Directory \"%s\": access time %s is too new.",
sub_path, sub_path,
format_timestamp_style(a, sizeof(a), age, TIMESTAMP_US)); format_timestamp_style(a, sizeof(a), atime_nsec / NSEC_PER_USEC, TIMESTAMP_US));
continue;
}
if (btime_nsec != NSEC_INFINITY && btime_nsec >= cutoff_nsec) {
char a[FORMAT_TIMESTAMP_MAX];
log_debug("Directory \"%s\": birth time %s is too new.",
sub_path,
format_timestamp_style(a, sizeof(a), btime_nsec / NSEC_PER_USEC, TIMESTAMP_US));
continue; continue;
} }
@ -632,14 +732,14 @@ static int dir_cleanup(
} else { } else {
/* Skip files for which the sticky bit is set. These are semantics we define, and are /* Skip files for which the sticky bit is set. These are semantics we define, and are
* unknown elsewhere. See XDG_RUNTIME_DIR specification for details. */ * unknown elsewhere. See XDG_RUNTIME_DIR specification for details. */
if (s.st_mode & S_ISVTX) { if (mode & S_ISVTX) {
log_debug("Skipping \"%s\": sticky bit set.", sub_path); log_debug("Skipping \"%s\": sticky bit set.", sub_path);
continue; continue;
} }
if (mountpoint && if (mountpoint &&
S_ISREG(s.st_mode) && S_ISREG(mode) &&
s.st_uid == 0 && uid == 0 &&
STR_IN_SET(dent->d_name, STR_IN_SET(dent->d_name,
".journal", ".journal",
"aquota.user", "aquota.user",
@ -649,13 +749,13 @@ static int dir_cleanup(
} }
/* Ignore sockets that are listed in /proc/net/unix */ /* Ignore sockets that are listed in /proc/net/unix */
if (S_ISSOCK(s.st_mode) && unix_socket_alive(sub_path)) { if (S_ISSOCK(mode) && unix_socket_alive(sub_path)) {
log_debug("Skipping \"%s\": live socket.", sub_path); log_debug("Skipping \"%s\": live socket.", sub_path);
continue; continue;
} }
/* Ignore device nodes */ /* Ignore device nodes */
if (S_ISCHR(s.st_mode) || S_ISBLK(s.st_mode)) { if (S_ISCHR(mode) || S_ISBLK(mode)) {
log_debug("Skipping \"%s\": a device.", sub_path); log_debug("Skipping \"%s\": a device.", sub_path);
continue; continue;
} }
@ -666,31 +766,36 @@ static int dir_cleanup(
continue; continue;
} }
age = timespec_load(&s.st_mtim); if (mtime_nsec != NSEC_INFINITY && mtime_nsec >= cutoff_nsec) {
if (age >= cutoff) {
char a[FORMAT_TIMESTAMP_MAX]; char a[FORMAT_TIMESTAMP_MAX];
/* Follows spelling in stat(1). */ /* Follows spelling in stat(1). */
log_debug("File \"%s\": modify time %s is too new.", log_debug("File \"%s\": modify time %s is too new.",
sub_path, sub_path,
format_timestamp_style(a, sizeof(a), age, TIMESTAMP_US)); format_timestamp_style(a, sizeof(a), mtime_nsec / NSEC_PER_USEC, TIMESTAMP_US));
continue; continue;
} }
age = timespec_load(&s.st_atim); if (atime_nsec != NSEC_INFINITY && atime_nsec >= cutoff_nsec) {
if (age >= cutoff) {
char a[FORMAT_TIMESTAMP_MAX]; char a[FORMAT_TIMESTAMP_MAX];
log_debug("File \"%s\": access time %s is too new.", log_debug("File \"%s\": access time %s is too new.",
sub_path, sub_path,
format_timestamp_style(a, sizeof(a), age, TIMESTAMP_US)); format_timestamp_style(a, sizeof(a), atime_nsec / NSEC_PER_USEC, TIMESTAMP_US));
continue; continue;
} }
age = timespec_load(&s.st_ctim); if (ctime_nsec != NSEC_INFINITY && ctime_nsec >= cutoff_nsec) {
if (age >= cutoff) {
char a[FORMAT_TIMESTAMP_MAX]; char a[FORMAT_TIMESTAMP_MAX];
log_debug("File \"%s\": change time %s is too new.", log_debug("File \"%s\": change time %s is too new.",
sub_path, sub_path,
format_timestamp_style(a, sizeof(a), age, TIMESTAMP_US)); format_timestamp_style(a, sizeof(a), ctime_nsec / NSEC_PER_USEC, TIMESTAMP_US));
continue;
}
if (btime_nsec != NSEC_INFINITY && btime_nsec >= cutoff_nsec) {
char a[FORMAT_TIMESTAMP_MAX];
log_debug("File \"%s\": birth time %s is too new.",
sub_path,
format_timestamp_style(a, sizeof(a), btime_nsec / NSEC_PER_USEC, TIMESTAMP_US));
continue; continue;
} }
@ -705,21 +810,19 @@ static int dir_cleanup(
finish: finish:
if (deleted) { if (deleted) {
char a[FORMAT_TIMESTAMP_MAX], b[FORMAT_TIMESTAMP_MAX]; char a[FORMAT_TIMESTAMP_MAX], m[FORMAT_TIMESTAMP_MAX];
usec_t age1, age2; struct timespec ts[2];
age1 = timespec_load(&ds->st_atim);
age2 = timespec_load(&ds->st_mtim);
log_debug("Restoring access and modification time on \"%s\": %s, %s", log_debug("Restoring access and modification time on \"%s\": %s, %s",
p, p,
format_timestamp_style(a, sizeof(a), age1, TIMESTAMP_US), format_timestamp_style(a, sizeof(a), self_atime_nsec / NSEC_PER_USEC, TIMESTAMP_US),
format_timestamp_style(b, sizeof(b), age2, TIMESTAMP_US)); format_timestamp_style(m, sizeof(m), self_mtime_nsec / NSEC_PER_USEC, TIMESTAMP_US));
timespec_store_nsec(ts + 0, self_atime_nsec);
timespec_store_nsec(ts + 1, self_mtime_nsec);
/* Restore original directory timestamps */ /* Restore original directory timestamps */
if (futimens(dirfd(d), (struct timespec[]) { if (futimens(dirfd(d), ts) < 0)
ds->st_atim,
ds->st_mtim }) < 0)
log_warning_errno(errno, "Failed to revert timestamps of '%s', ignoring: %m", p); log_warning_errno(errno, "Failed to revert timestamps of '%s', ignoring: %m", p);
} }
@ -2186,11 +2289,20 @@ static int remove_item(Item *i) {
} }
static int clean_item_instance(Item *i, const char* instance) { static int clean_item_instance(Item *i, const char* instance) {
_cleanup_closedir_ DIR *d = NULL;
struct stat s, ps;
bool mountpoint;
usec_t cutoff, n;
char timestamp[FORMAT_TIMESTAMP_MAX]; char timestamp[FORMAT_TIMESTAMP_MAX];
_cleanup_closedir_ DIR *d = NULL;
uint32_t dev_major, dev_minor;
nsec_t atime_nsec, mtime_nsec;
int mountpoint = -1;
usec_t cutoff, n;
uint64_t ino;
struct statx sx
#if HAS_FEATURE_MEMORY_SANITIZER
= {}
# warning "Explicitly initializing struct statx, to work around msan limitation. Please remove as soon as msan has been updated to not require this."
#endif
;
assert(i); assert(i);
@ -2213,24 +2325,53 @@ static int clean_item_instance(Item *i, const char* instance) {
return log_error_errno(errno, "Failed to open directory %s: %m", instance); return log_error_errno(errno, "Failed to open directory %s: %m", instance);
} }
if (fstat(dirfd(d), &s) < 0) if (statx(dirfd(d), "", AT_EMPTY_PATH, STATX_MODE|STATX_INO|STATX_ATIME|STATX_MTIME, &sx) < 0) {
return log_error_errno(errno, "stat(%s) failed: %m", i->path); struct stat s;
if (!S_ISDIR(s.st_mode)) if (!ERRNO_IS_NOT_SUPPORTED(errno) && !ERRNO_IS_PRIVILEGE(errno))
return log_error_errno(SYNTHETIC_ERRNO(ENOTDIR), return log_error_errno(errno, "statx(%s) failed: %m", i->path);
"%s is not a directory.", i->path);
if (fstatat(dirfd(d), "..", &ps, AT_SYMLINK_NOFOLLOW) != 0) if (fstat(dirfd(d), &s) < 0)
return log_error_errno(errno, "stat(%s/..) failed: %m", i->path); return log_error_errno(errno, "stat(%s) failed: %m", i->path);
dev_major = major(s.st_dev);
dev_minor = minor(s.st_dev);
ino = s.st_ino;
atime_nsec = timespec_load_nsec(&s.st_atim);
mtime_nsec = timespec_load_nsec(&s.st_mtim);
} else {
if (FLAGS_SET(sx.stx_attributes_mask, STATX_ATTR_MOUNT_ROOT))
mountpoint = FLAGS_SET(sx.stx_attributes, STATX_ATTR_MOUNT_ROOT);
dev_major = sx.stx_dev_major;
dev_minor = sx.stx_dev_minor;
ino = sx.stx_ino;
atime_nsec = load_statx_timestamp_nsec(&sx.stx_atime);
mtime_nsec = load_statx_timestamp_nsec(&sx.stx_mtime);
}
if (mountpoint < 0) {
struct stat ps;
if (fstatat(dirfd(d), "..", &ps, AT_SYMLINK_NOFOLLOW) != 0)
return log_error_errno(errno, "stat(%s/..) failed: %m", i->path);
mountpoint =
dev_major != major(ps.st_dev) ||
dev_minor != minor(ps.st_dev) ||
ino != ps.st_ino;
}
mountpoint = s.st_dev != ps.st_dev || s.st_ino == ps.st_ino;
log_debug("Cleanup threshold for %s \"%s\" is %s", log_debug("Cleanup threshold for %s \"%s\" is %s",
mountpoint ? "mount point" : "directory", mountpoint ? "mount point" : "directory",
instance, instance,
format_timestamp_style(timestamp, sizeof(timestamp), cutoff, TIMESTAMP_US)); format_timestamp_style(timestamp, sizeof(timestamp), cutoff, TIMESTAMP_US));
return dir_cleanup(i, instance, d, &s, cutoff, s.st_dev, mountpoint, return dir_cleanup(i, instance, d,
atime_nsec, mtime_nsec, cutoff * NSEC_PER_USEC,
dev_major, dev_minor, mountpoint,
MAX_DEPTH, i->keep_first_level); MAX_DEPTH, i->keep_first_level);
} }