tmpfiles: honor age for r/R cleanup
Let r and R lines participate in --clean when they specify an age. The
target itself is removed only after its selected file or directory
timestamps have aged enough; --remove remains unconditional.
Follow-up for: beca6b6e6b
This commit is contained in:
@@ -76,8 +76,13 @@ CHANGES WITH 262:
|
||||
Changes in systemd-tmpfiles:
|
||||
|
||||
* systemd-tmpfiles now rejects non-empty argument fields for tmpfiles.d
|
||||
line types that do not use the argument field. Previously, such
|
||||
fields were silently ignored after a warning.
|
||||
line types that do not use the argument field. This is a compatibility
|
||||
change: fields that were previously ignored after a warning will now
|
||||
cause the tmpfiles.d configuration to be rejected, so downstreams
|
||||
should audit shipped tmpfiles.d snippets before upgrading.
|
||||
|
||||
* The r and R tmpfiles.d line types now honor the age field when
|
||||
systemd-tmpfiles is invoked with --clean.
|
||||
|
||||
Changes in the TPM Subsystem:
|
||||
|
||||
|
||||
@@ -2729,7 +2729,6 @@ SPDX-License-Identifier: LGPL-2.1-or-later
|
||||
DHCP/HTTP base EFI boot.
|
||||
|
||||
- **tmpfiles:**
|
||||
- allow time-based cleanup in r and R too
|
||||
- creating new directories/subvolumes/fifos/device nodes
|
||||
should not follow symlinks. None of the other adjustment or creation
|
||||
calls follow symlinks.
|
||||
|
||||
+9
-6
@@ -382,7 +382,8 @@ L /tmp/foobar - - - - /dev/null</programlisting>
|
||||
This may not be used to remove non-empty directories, use
|
||||
<varname>R</varname> for that. Lines of this type accept
|
||||
shell-style globs in place of normal path
|
||||
names. Does not follow symlinks.</para></listitem>
|
||||
names. Does not follow symlinks. When <option>--clean</option> is used, the file or directory is subject
|
||||
to time-based cleanup if the age argument is specified. This age handling was added in v262.</para></listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry>
|
||||
@@ -390,7 +391,8 @@ L /tmp/foobar - - - - /dev/null</programlisting>
|
||||
<listitem><para>Recursively remove a path and all its
|
||||
subdirectories (if it is a directory). Lines of this type
|
||||
accept shell-style globs in place of normal path
|
||||
names. Does not follow symlinks.</para></listitem>
|
||||
names. Does not follow symlinks. When <option>--clean</option> is used, the path is subject to
|
||||
time-based cleanup if the age argument is specified. This age handling was added in v262.</para></listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry>
|
||||
@@ -676,13 +678,14 @@ w- /proc/sys/vm/swappiness - - - - 10</programlisting></para>
|
||||
<para>The age field only applies to lines starting with
|
||||
<varname>d</varname>, <varname>D</varname>, <varname>e</varname>,
|
||||
<varname>v</varname>, <varname>q</varname>,
|
||||
<varname>Q</varname>, <varname>C</varname>, <varname>x</varname>
|
||||
and <varname>X</varname>. If omitted or set to
|
||||
<literal>-</literal>, no automatic clean-up is done.</para>
|
||||
<varname>Q</varname>, <varname>C</varname>, <varname>r</varname>,
|
||||
<varname>R</varname>, <varname>x</varname> and <varname>X</varname>.
|
||||
If omitted or set to <literal>-</literal>, no automatic clean-up is done.</para>
|
||||
|
||||
<para>If the age field starts with a tilde character <literal>~</literal>, clean-up is only applied to
|
||||
files and directories one level inside the directory specified, but not the files and directories
|
||||
immediately inside it.</para>
|
||||
immediately inside it. For <varname>R</varname> lines, this also skips removing the specified
|
||||
top-level directory itself. This modifier is not supported by <varname>r</varname> lines.</para>
|
||||
|
||||
<para>The age of a file system entry is determined from its last
|
||||
modification timestamp (mtime), its last access timestamp (atime),
|
||||
|
||||
+454
-17
@@ -3390,6 +3390,352 @@ static int remove_item_instance(
|
||||
}
|
||||
}
|
||||
|
||||
static bool item_instance_needs_cleanup(
|
||||
Item *i,
|
||||
const char *instance,
|
||||
const struct statx *sx) {
|
||||
|
||||
assert(i);
|
||||
assert(instance);
|
||||
assert(sx);
|
||||
|
||||
if (!i->age_set)
|
||||
return false;
|
||||
|
||||
usec_t n = now(CLOCK_REALTIME);
|
||||
if (n < i->age)
|
||||
return false;
|
||||
|
||||
usec_t cutoff = n - i->age;
|
||||
nsec_t atime_nsec = FLAGS_SET(sx->stx_mask, STATX_ATIME) ? statx_timestamp_load_nsec(&sx->stx_atime) : NSEC_INFINITY;
|
||||
nsec_t mtime_nsec = FLAGS_SET(sx->stx_mask, STATX_MTIME) ? statx_timestamp_load_nsec(&sx->stx_mtime) : NSEC_INFINITY;
|
||||
nsec_t ctime_nsec = FLAGS_SET(sx->stx_mask, STATX_CTIME) ? statx_timestamp_load_nsec(&sx->stx_ctime) : NSEC_INFINITY;
|
||||
nsec_t btime_nsec = FLAGS_SET(sx->stx_mask, STATX_BTIME) ? statx_timestamp_load_nsec(&sx->stx_btime) : NSEC_INFINITY;
|
||||
bool is_dir = S_ISDIR(sx->stx_mode);
|
||||
|
||||
return needs_cleanup(atime_nsec, btime_nsec, ctime_nsec, mtime_nsec,
|
||||
cutoff * NSEC_PER_USEC, instance,
|
||||
is_dir ? i->age_by_dir : i->age_by_file, is_dir);
|
||||
}
|
||||
|
||||
static int item_instance_open_parent(
|
||||
Item *i,
|
||||
const char *instance,
|
||||
char **ret_name,
|
||||
bool *ret_must_be_directory) {
|
||||
|
||||
int r;
|
||||
|
||||
assert(i);
|
||||
assert(instance);
|
||||
assert(ret_name);
|
||||
assert(ret_must_be_directory);
|
||||
|
||||
_cleanup_free_ char *name = NULL;
|
||||
r = path_extract_filename(instance, &name);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to extract filename from path '%s': %m", instance);
|
||||
|
||||
_cleanup_close_ int parent_fd = path_open_parent_safe(instance, i->allow_failure);
|
||||
if (parent_fd < 0)
|
||||
return parent_fd;
|
||||
|
||||
*ret_name = TAKE_PTR(name);
|
||||
*ret_must_be_directory = r == O_DIRECTORY;
|
||||
return TAKE_FD(parent_fd);
|
||||
}
|
||||
|
||||
static int item_instance_open_opath(
|
||||
int parent_fd,
|
||||
const char *name,
|
||||
const char *instance,
|
||||
int *ret_fd,
|
||||
struct statx *ret_sx) {
|
||||
|
||||
int r;
|
||||
|
||||
assert(parent_fd >= 0);
|
||||
assert(name);
|
||||
assert(instance);
|
||||
assert(ret_fd);
|
||||
assert(ret_sx);
|
||||
|
||||
_cleanup_close_ int fd = RET_NERRNO(openat(parent_fd, name, O_PATH|O_CLOEXEC|O_NOFOLLOW));
|
||||
if (IN_SET(fd, -ENOENT, -ENOTDIR))
|
||||
return 0;
|
||||
if (fd < 0)
|
||||
return log_error_errno(fd, "Failed to open \"%s\": %m", instance);
|
||||
|
||||
struct statx sx;
|
||||
r = xstatx_full(fd,
|
||||
/* path= */ NULL,
|
||||
AT_EMPTY_PATH,
|
||||
/* xstatx_flags= */ 0,
|
||||
STATX_TYPE|STATX_MODE,
|
||||
STATX_ATIME|STATX_MTIME|STATX_CTIME|STATX_BTIME,
|
||||
/* mandatory_attributes= */ 0,
|
||||
&sx);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "statx(%s) failed: %m", instance);
|
||||
|
||||
*ret_fd = TAKE_FD(fd);
|
||||
*ret_sx = sx;
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int item_instance_still_current(
|
||||
int fd,
|
||||
int parent_fd,
|
||||
const char *name,
|
||||
const char *instance) {
|
||||
|
||||
int r;
|
||||
|
||||
assert(fd >= 0);
|
||||
assert(parent_fd >= 0);
|
||||
assert(name);
|
||||
assert(instance);
|
||||
|
||||
r = inode_same_at(fd, /* filea= */ NULL,
|
||||
parent_fd, name,
|
||||
AT_EMPTY_PATH|AT_SYMLINK_NOFOLLOW|AT_NO_AUTOMOUNT);
|
||||
if (IN_SET(r, -ENOENT, -ENOTDIR))
|
||||
return 0;
|
||||
if (r < 0) {
|
||||
log_debug_errno(r, "Failed to verify that \"%s\" still refers to the same inode, skipping: %m", instance);
|
||||
return 0;
|
||||
}
|
||||
if (r == 0) {
|
||||
log_debug("Skipping \"%s\": inode changed.", instance);
|
||||
return 0;
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int item_instance_fd_still_current(
|
||||
int fd,
|
||||
int other_fd,
|
||||
const char *instance) {
|
||||
|
||||
int r;
|
||||
|
||||
assert(fd >= 0);
|
||||
assert(other_fd >= 0);
|
||||
assert(instance);
|
||||
|
||||
r = fd_inode_same(fd, other_fd);
|
||||
if (r < 0) {
|
||||
log_debug_errno(r, "Failed to verify that \"%s\" still refers to the same inode, skipping: %m", instance);
|
||||
return 0;
|
||||
}
|
||||
if (r == 0) {
|
||||
log_debug("Skipping \"%s\": inode changed.", instance);
|
||||
return 0;
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int item_instance_open_directory_and_lock(
|
||||
int parent_fd,
|
||||
const char *name,
|
||||
int fd,
|
||||
const char *instance,
|
||||
DIR **ret) {
|
||||
|
||||
_cleanup_closedir_ DIR *d = NULL;
|
||||
int r;
|
||||
|
||||
assert(parent_fd >= 0);
|
||||
assert(name);
|
||||
assert(fd >= 0);
|
||||
assert(instance);
|
||||
assert(ret);
|
||||
|
||||
d = xopendirat_nomod(parent_fd, name);
|
||||
if (!d) {
|
||||
if (IN_SET(errno, ENOENT, ENOTDIR, ELOOP))
|
||||
return 0;
|
||||
|
||||
return log_error_errno(errno, "Failed to open directory \"%s\": %m", instance);
|
||||
}
|
||||
|
||||
r = item_instance_fd_still_current(fd, dirfd(d), instance);
|
||||
if (r <= 0)
|
||||
return r;
|
||||
|
||||
if (!arg_dry_run &&
|
||||
flock(dirfd(d), LOCK_EX|LOCK_NB) < 0) {
|
||||
log_debug_errno(errno, "Couldn't acquire shared BSD lock on directory \"%s\", skipping: %m", instance);
|
||||
return 0;
|
||||
}
|
||||
|
||||
*ret = TAKE_PTR(d);
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int item_instance_remove_empty_directory(
|
||||
int parent_fd,
|
||||
const char *name,
|
||||
const char *instance) {
|
||||
|
||||
int r;
|
||||
|
||||
assert(parent_fd >= 0);
|
||||
assert(name);
|
||||
assert(instance);
|
||||
|
||||
if (arg_dry_run)
|
||||
return 0;
|
||||
|
||||
r = RET_NERRNO(unlinkat(parent_fd, name, AT_REMOVEDIR));
|
||||
if (r < 0) {
|
||||
bool fatal = !IN_SET(r, -ENOENT, -ENOTEMPTY, -EBUSY);
|
||||
|
||||
log_full_errno(fatal ? LOG_ERR : LOG_DEBUG, r, "Failed to remove %s: %m", instance);
|
||||
if (fatal)
|
||||
return r;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int item_instance_remove_directory_if_empty(
|
||||
int parent_fd,
|
||||
const char *name,
|
||||
DIR *d,
|
||||
const char *instance,
|
||||
bool assume_empty) {
|
||||
|
||||
int r;
|
||||
|
||||
assert(parent_fd >= 0);
|
||||
assert(name);
|
||||
assert(d);
|
||||
assert(instance);
|
||||
|
||||
if (!assume_empty) {
|
||||
r = dir_is_empty_at(dirfd(d), /* path= */ NULL, /* ignore_hidden_or_backup= */ false);
|
||||
if (IN_SET(r, -ENOENT, -ENOTDIR))
|
||||
return 0;
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to determine whether \"%s\" is empty: %m", instance);
|
||||
if (r == 0)
|
||||
return 0;
|
||||
}
|
||||
|
||||
r = item_instance_still_current(dirfd(d), parent_fd, name, instance);
|
||||
if (r <= 0)
|
||||
return r;
|
||||
|
||||
log_action("Would remove", "Removing", "%s directory \"%s\".", instance);
|
||||
r = item_instance_remove_empty_directory(parent_fd, name, instance);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int clean_remove_item_instance_at(
|
||||
Item *i,
|
||||
int parent_fd,
|
||||
const char *name,
|
||||
int fd,
|
||||
const char *instance,
|
||||
const struct statx *sx,
|
||||
bool must_be_directory) {
|
||||
|
||||
int r;
|
||||
|
||||
assert(i);
|
||||
assert(parent_fd >= 0);
|
||||
assert(name);
|
||||
assert(fd >= 0);
|
||||
assert(instance);
|
||||
assert(sx);
|
||||
|
||||
if (must_be_directory && !S_ISDIR(sx->stx_mode))
|
||||
return log_error_errno(SYNTHETIC_ERRNO(ENOTDIR), "\"%s\" is not a directory.", instance);
|
||||
|
||||
if (!item_instance_needs_cleanup(i, instance, sx))
|
||||
return 0;
|
||||
|
||||
if (S_ISDIR(sx->stx_mode)) {
|
||||
_cleanup_closedir_ DIR *d = NULL;
|
||||
|
||||
r = item_instance_open_directory_and_lock(parent_fd, name, fd, instance, &d);
|
||||
if (r <= 0)
|
||||
return r;
|
||||
|
||||
return item_instance_remove_directory_if_empty(parent_fd, name, d, instance, /* assume_empty= */ false);
|
||||
}
|
||||
|
||||
_cleanup_close_ int lock_fd = -EBADF;
|
||||
if (!arg_dry_run) {
|
||||
lock_fd = xopenat(parent_fd, name, O_RDONLY|O_CLOEXEC|O_NOFOLLOW|O_NOATIME|O_NONBLOCK|O_NOCTTY);
|
||||
if (lock_fd < 0 && !IN_SET(lock_fd, -ENOENT, -ELOOP))
|
||||
log_warning_errno(lock_fd, "Opening file \"%s\" failed, proceeding without lock: %m", instance);
|
||||
|
||||
if (lock_fd >= 0) {
|
||||
r = item_instance_fd_still_current(fd, lock_fd, instance);
|
||||
if (r <= 0)
|
||||
return r;
|
||||
|
||||
if (flock(lock_fd, LOCK_EX|LOCK_NB) < 0 && errno == EAGAIN) {
|
||||
log_debug_errno(errno, "Couldn't acquire shared BSD lock on file \"%s\", skipping: %m", instance);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
r = item_instance_still_current(fd, parent_fd, name, instance);
|
||||
if (r <= 0)
|
||||
return r;
|
||||
}
|
||||
|
||||
if (i->type == RECURSIVE_REMOVE_PATH)
|
||||
log_action("Would remove", "Removing", "%s file \"%s\".", instance);
|
||||
else
|
||||
log_action("Would remove", "Removing", "%s \"%s\".", instance);
|
||||
|
||||
if (!arg_dry_run &&
|
||||
unlinkat(parent_fd, name, 0) < 0 &&
|
||||
errno != ENOENT)
|
||||
log_warning_errno(errno, "Failed to remove \"%s\", ignoring: %m", instance);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int clean_remove_item_instance(
|
||||
Context *c,
|
||||
Item *i,
|
||||
const char *instance,
|
||||
CreationMode creation) {
|
||||
|
||||
int r;
|
||||
|
||||
assert(c);
|
||||
assert(i);
|
||||
assert(instance);
|
||||
|
||||
if (!i->age_set)
|
||||
return 0;
|
||||
|
||||
_cleanup_free_ char *name = NULL;
|
||||
bool must_be_directory;
|
||||
_cleanup_close_ int parent_fd = item_instance_open_parent(i, instance, &name, &must_be_directory);
|
||||
if (parent_fd < 0)
|
||||
return parent_fd;
|
||||
|
||||
_cleanup_close_ int fd = -EBADF;
|
||||
struct statx sx;
|
||||
r = item_instance_open_opath(parent_fd, name, instance, &fd, &sx);
|
||||
if (r <= 0)
|
||||
return r;
|
||||
|
||||
return clean_remove_item_instance_at(i, parent_fd, name, fd, instance, &sx, must_be_directory);
|
||||
}
|
||||
|
||||
static int remove_item(Context *c, Item *i) {
|
||||
assert(c);
|
||||
assert(i);
|
||||
@@ -3427,13 +3773,19 @@ static char *age_by_to_string(AgeBy ab, bool is_dir) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int clean_item_instance(
|
||||
static int clean_item_instance_from_dir(
|
||||
Context *c,
|
||||
Item *i,
|
||||
const char* instance,
|
||||
CreationMode creation) {
|
||||
const char *instance,
|
||||
DIR *d,
|
||||
const struct statx *sx,
|
||||
bool mountpoint) {
|
||||
|
||||
assert(c);
|
||||
assert(i);
|
||||
assert(instance);
|
||||
assert(d);
|
||||
assert(sx);
|
||||
|
||||
if (!i->age_set)
|
||||
return 0;
|
||||
@@ -3443,19 +3795,8 @@ static int clean_item_instance(
|
||||
return 0;
|
||||
|
||||
usec_t cutoff = n - i->age;
|
||||
nsec_t atime_nsec, mtime_nsec;
|
||||
|
||||
_cleanup_closedir_ DIR *d = NULL;
|
||||
struct statx sx;
|
||||
bool mountpoint;
|
||||
int r;
|
||||
|
||||
r = opendir_and_stat(instance, &d, &sx, &mountpoint);
|
||||
if (r <= 0)
|
||||
return r;
|
||||
|
||||
atime_nsec = FLAGS_SET(sx.stx_mask, STATX_ATIME) ? statx_timestamp_load_nsec(&sx.stx_atime) : NSEC_INFINITY;
|
||||
mtime_nsec = FLAGS_SET(sx.stx_mask, STATX_MTIME) ? statx_timestamp_load_nsec(&sx.stx_mtime) : NSEC_INFINITY;
|
||||
nsec_t atime_nsec = FLAGS_SET(sx->stx_mask, STATX_ATIME) ? statx_timestamp_load_nsec(&sx->stx_atime) : NSEC_INFINITY;
|
||||
nsec_t mtime_nsec = FLAGS_SET(sx->stx_mask, STATX_MTIME) ? statx_timestamp_load_nsec(&sx->stx_mtime) : NSEC_INFINITY;
|
||||
|
||||
if (DEBUG_LOGGING) {
|
||||
_cleanup_free_ char *ab_f = NULL, *ab_d = NULL;
|
||||
@@ -3479,12 +3820,96 @@ static int clean_item_instance(
|
||||
atime_nsec,
|
||||
mtime_nsec,
|
||||
cutoff * NSEC_PER_USEC,
|
||||
sx.stx_dev_major, sx.stx_dev_minor,
|
||||
sx->stx_dev_major, sx->stx_dev_minor,
|
||||
mountpoint,
|
||||
MAX_DEPTH, i->keep_first_level,
|
||||
i->age_by_file, i->age_by_dir);
|
||||
}
|
||||
|
||||
static int clean_item_instance(
|
||||
Context *c,
|
||||
Item *i,
|
||||
const char *instance,
|
||||
CreationMode creation) {
|
||||
|
||||
int r;
|
||||
|
||||
assert(c);
|
||||
assert(i);
|
||||
assert(instance);
|
||||
|
||||
_cleanup_closedir_ DIR *d = NULL;
|
||||
struct statx sx;
|
||||
bool mountpoint;
|
||||
|
||||
r = opendir_and_stat(instance, &d, &sx, &mountpoint);
|
||||
if (r <= 0)
|
||||
return r;
|
||||
|
||||
return clean_item_instance_from_dir(c, i, instance, d, &sx, mountpoint);
|
||||
}
|
||||
|
||||
static int clean_recursive_remove_item_instance(
|
||||
Context *c,
|
||||
Item *i,
|
||||
const char *instance,
|
||||
CreationMode creation) {
|
||||
|
||||
int r;
|
||||
|
||||
assert(c);
|
||||
assert(i);
|
||||
assert(instance);
|
||||
|
||||
if (!i->age_set)
|
||||
return 0;
|
||||
|
||||
_cleanup_free_ char *name = NULL;
|
||||
bool must_be_directory;
|
||||
_cleanup_close_ int parent_fd = item_instance_open_parent(i, instance, &name, &must_be_directory);
|
||||
if (parent_fd < 0)
|
||||
return parent_fd;
|
||||
|
||||
_cleanup_close_ int fd = -EBADF;
|
||||
struct statx sx;
|
||||
r = item_instance_open_opath(parent_fd, name, instance, &fd, &sx);
|
||||
if (r <= 0)
|
||||
return r;
|
||||
if (!S_ISDIR(sx.stx_mode))
|
||||
return clean_remove_item_instance_at(i, parent_fd, name, fd, instance, &sx, must_be_directory);
|
||||
|
||||
_cleanup_closedir_ DIR *d = NULL;
|
||||
r = item_instance_open_directory_and_lock(parent_fd, name, fd, instance, &d);
|
||||
if (r <= 0)
|
||||
return r;
|
||||
|
||||
struct statx dir_sx;
|
||||
r = xstatx_full(dirfd(d),
|
||||
/* path= */ NULL,
|
||||
AT_EMPTY_PATH,
|
||||
/* xstatx_flags= */ 0,
|
||||
STATX_TYPE|STATX_MODE|STATX_INO,
|
||||
STATX_ATIME|STATX_MTIME|STATX_CTIME|STATX_BTIME,
|
||||
STATX_ATTR_MOUNT_ROOT,
|
||||
&dir_sx);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "statx(%s) failed: %m", instance);
|
||||
|
||||
bool cleanup_needed = item_instance_needs_cleanup(i, instance, &dir_sx);
|
||||
|
||||
r = clean_item_instance_from_dir(c, i, instance, d, &dir_sx, FLAGS_SET(dir_sx.stx_attributes, STATX_ATTR_MOUNT_ROOT));
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
if (i->keep_first_level)
|
||||
return 0;
|
||||
|
||||
if (!cleanup_needed)
|
||||
return 0;
|
||||
|
||||
return item_instance_remove_directory_if_empty(parent_fd, name, d, instance, /* assume_empty= */ arg_dry_run);
|
||||
}
|
||||
|
||||
static int clean_item(Context *c, Item *i) {
|
||||
assert(c);
|
||||
assert(i);
|
||||
@@ -3506,6 +3931,12 @@ static int clean_item(Context *c, Item *i) {
|
||||
case IGNORE_DIRECTORY_PATH:
|
||||
return glob_item(c, i, clean_item_instance);
|
||||
|
||||
case REMOVE_PATH:
|
||||
return glob_item(c, i, clean_remove_item_instance);
|
||||
|
||||
case RECURSIVE_REMOVE_PATH:
|
||||
return glob_item(c, i, clean_recursive_remove_item_instance);
|
||||
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
@@ -4378,6 +4809,12 @@ static int parse_line(
|
||||
_cleanup_free_ char *seconds = NULL, *age_by = NULL;
|
||||
|
||||
if (*a == '~') {
|
||||
if (i.type == REMOVE_PATH) {
|
||||
*invalid_config = true;
|
||||
return log_syntax(NULL, LOG_ERR, fname, line, SYNTHETIC_ERRNO(EBADMSG),
|
||||
"Age modifier '~' is not supported for %c lines.", (char) i.type);
|
||||
}
|
||||
|
||||
i.keep_first_level = true;
|
||||
a++;
|
||||
}
|
||||
|
||||
@@ -220,6 +220,154 @@ test "$before" = "$after"
|
||||
test ! -e /tmp/ageby-mtime/old-child
|
||||
rmdir /tmp/ageby-mtime
|
||||
|
||||
# r/R entries honor age when cleaning.
|
||||
rm -rf /tmp/ageby-remove
|
||||
mkdir -p /tmp/ageby-remove/old-dir/child /tmp/ageby-remove/new-dir/child \
|
||||
/tmp/ageby-remove/old-empty-dir /tmp/ageby-remove/new-empty-dir
|
||||
touch /tmp/ageby-remove/old-file /tmp/ageby-remove/new-file \
|
||||
/tmp/ageby-remove/old-recursive-file /tmp/ageby-remove/new-recursive-file
|
||||
touch --date "3 minutes ago" \
|
||||
/tmp/ageby-remove/old-file \
|
||||
/tmp/ageby-remove/old-recursive-file \
|
||||
/tmp/ageby-remove/old-dir/child \
|
||||
/tmp/ageby-remove/old-dir \
|
||||
/tmp/ageby-remove/old-empty-dir
|
||||
|
||||
output="$(systemd-tmpfiles --dry-run --clean - 2>&1 <<-EOF
|
||||
r /tmp/ageby-remove/old-file - - - m:1m -
|
||||
r /tmp/ageby-remove/new-file - - - m:1m -
|
||||
R /tmp/ageby-remove/old-recursive-file - - - m:1m -
|
||||
R /tmp/ageby-remove/new-recursive-file - - - m:1m -
|
||||
R /tmp/ageby-remove/old-dir - - - M:1m -
|
||||
R /tmp/ageby-remove/new-dir - - - M:1m -
|
||||
R /tmp/ageby-remove/old-empty-dir - - - M:1m -
|
||||
R /tmp/ageby-remove/new-empty-dir - - - M:1m -
|
||||
EOF
|
||||
)"
|
||||
[[ "$output" == *"Would remove directory \"/tmp/ageby-remove/old-dir\""* ]]
|
||||
[[ "$output" != *"Would remove directory \"/tmp/ageby-remove/new-dir\""* ]]
|
||||
[[ "$output" == *"Would remove directory \"/tmp/ageby-remove/old-empty-dir\""* ]]
|
||||
[[ "$output" != *"Would remove directory \"/tmp/ageby-remove/new-empty-dir\""* ]]
|
||||
|
||||
test -e /tmp/ageby-remove/old-file
|
||||
test -e /tmp/ageby-remove/new-file
|
||||
test -e /tmp/ageby-remove/old-recursive-file
|
||||
test -e /tmp/ageby-remove/new-recursive-file
|
||||
test -d /tmp/ageby-remove/old-dir
|
||||
test -d /tmp/ageby-remove/old-dir/child
|
||||
test -d /tmp/ageby-remove/new-dir
|
||||
test -d /tmp/ageby-remove/new-dir/child
|
||||
test -d /tmp/ageby-remove/old-empty-dir
|
||||
test -d /tmp/ageby-remove/new-empty-dir
|
||||
|
||||
systemd-tmpfiles --clean - <<-EOF
|
||||
r /tmp/ageby-remove/old-file - - - m:1m -
|
||||
r /tmp/ageby-remove/new-file - - - m:1m -
|
||||
R /tmp/ageby-remove/old-recursive-file - - - m:1m -
|
||||
R /tmp/ageby-remove/new-recursive-file - - - m:1m -
|
||||
R /tmp/ageby-remove/old-dir - - - M:1m -
|
||||
R /tmp/ageby-remove/new-dir - - - M:1m -
|
||||
R /tmp/ageby-remove/old-empty-dir - - - M:1m -
|
||||
R /tmp/ageby-remove/new-empty-dir - - - M:1m -
|
||||
EOF
|
||||
|
||||
test ! -e /tmp/ageby-remove/old-file
|
||||
test -e /tmp/ageby-remove/new-file
|
||||
test ! -e /tmp/ageby-remove/old-recursive-file
|
||||
test -e /tmp/ageby-remove/new-recursive-file
|
||||
test ! -e /tmp/ageby-remove/old-dir
|
||||
test -d /tmp/ageby-remove/new-dir
|
||||
test -d /tmp/ageby-remove/new-dir/child
|
||||
test ! -e /tmp/ageby-remove/old-empty-dir
|
||||
test -d /tmp/ageby-remove/new-empty-dir
|
||||
|
||||
# Aged r cleanup removes empty directories but not non-empty or too-new directories.
|
||||
mkdir -p /tmp/ageby-remove/old-r-empty-dir \
|
||||
/tmp/ageby-remove/new-r-empty-dir \
|
||||
/tmp/ageby-remove/old-r-nonempty-dir/child
|
||||
touch --date "3 minutes ago" \
|
||||
/tmp/ageby-remove/old-r-empty-dir \
|
||||
/tmp/ageby-remove/old-r-nonempty-dir
|
||||
output="$(systemd-tmpfiles --dry-run --clean - 2>&1 <<-EOF
|
||||
r /tmp/ageby-remove/old-r-empty-dir - - - M:1m -
|
||||
r /tmp/ageby-remove/new-r-empty-dir - - - M:1m -
|
||||
r /tmp/ageby-remove/old-r-nonempty-dir - - - M:1m -
|
||||
EOF
|
||||
)"
|
||||
[[ "$output" == *"Would remove directory \"/tmp/ageby-remove/old-r-empty-dir\""* ]]
|
||||
[[ "$output" != *"Would remove directory \"/tmp/ageby-remove/new-r-empty-dir\""* ]]
|
||||
[[ "$output" != *"Would remove directory \"/tmp/ageby-remove/old-r-nonempty-dir\""* ]]
|
||||
|
||||
systemd-tmpfiles --clean - <<-EOF
|
||||
r /tmp/ageby-remove/old-r-empty-dir - - - M:1m -
|
||||
r /tmp/ageby-remove/new-r-empty-dir - - - M:1m -
|
||||
r /tmp/ageby-remove/old-r-nonempty-dir - - - M:1m -
|
||||
EOF
|
||||
test ! -e /tmp/ageby-remove/old-r-empty-dir
|
||||
test -d /tmp/ageby-remove/new-r-empty-dir
|
||||
test -d /tmp/ageby-remove/old-r-nonempty-dir/child
|
||||
|
||||
# Aged R cleanup keeps the top-level directory if a child is too new.
|
||||
mkdir -p /tmp/ageby-remove/old-dir-new-child/new-child
|
||||
touch --date "3 minutes ago" /tmp/ageby-remove/old-dir-new-child
|
||||
systemd-tmpfiles --clean --inline "R /tmp/ageby-remove/old-dir-new-child - - - M:1m -"
|
||||
test -d /tmp/ageby-remove/old-dir-new-child/new-child
|
||||
|
||||
(! systemd-tmpfiles --clean --inline "r /tmp/ageby-remove/new-file - - - ~1m -")
|
||||
|
||||
# The ~ age modifier on R keeps the target and its immediate children.
|
||||
mkdir -p /tmp/ageby-remove/keep-root/old-child/grandchild
|
||||
touch --date "3 minutes ago" \
|
||||
/tmp/ageby-remove/keep-root \
|
||||
/tmp/ageby-remove/keep-root/old-child \
|
||||
/tmp/ageby-remove/keep-root/old-child/grandchild
|
||||
systemd-tmpfiles --clean --inline "R /tmp/ageby-remove/keep-root - - - ~M:1m -"
|
||||
test -d /tmp/ageby-remove/keep-root
|
||||
test -d /tmp/ageby-remove/keep-root/old-child
|
||||
test ! -e /tmp/ageby-remove/keep-root/old-child/grandchild
|
||||
|
||||
# Aged r/R cleanup skips locked files.
|
||||
touch /tmp/ageby-remove/locked-file /tmp/ageby-remove/locked-recursive-file
|
||||
touch --date "3 minutes ago" /tmp/ageby-remove/locked-file /tmp/ageby-remove/locked-recursive-file
|
||||
exec {lock_fd}<>/tmp/ageby-remove/locked-file
|
||||
exec {recursive_lock_fd}<>/tmp/ageby-remove/locked-recursive-file
|
||||
flock --exclusive "$lock_fd"
|
||||
flock --exclusive "$recursive_lock_fd"
|
||||
systemd-tmpfiles --clean - <<-EOF
|
||||
r /tmp/ageby-remove/locked-file - - - m:1m -
|
||||
R /tmp/ageby-remove/locked-recursive-file - - - m:1m -
|
||||
EOF
|
||||
test -e /tmp/ageby-remove/locked-file
|
||||
test -e /tmp/ageby-remove/locked-recursive-file
|
||||
exec {lock_fd}>&-
|
||||
exec {recursive_lock_fd}>&-
|
||||
systemd-tmpfiles --clean - <<-EOF
|
||||
r /tmp/ageby-remove/locked-file - - - m:1m -
|
||||
R /tmp/ageby-remove/locked-recursive-file - - - m:1m -
|
||||
EOF
|
||||
test ! -e /tmp/ageby-remove/locked-file
|
||||
test ! -e /tmp/ageby-remove/locked-recursive-file
|
||||
|
||||
# Aged r cleanup skips locked directories.
|
||||
mkdir -p /tmp/ageby-remove/locked-r-dir
|
||||
touch --date "3 minutes ago" /tmp/ageby-remove/locked-r-dir
|
||||
flock --exclusive /tmp/ageby-remove/locked-r-dir systemd-tmpfiles --clean --inline \
|
||||
"r /tmp/ageby-remove/locked-r-dir - - - M:1m -"
|
||||
test -d /tmp/ageby-remove/locked-r-dir
|
||||
systemd-tmpfiles --clean --inline "r /tmp/ageby-remove/locked-r-dir - - - M:1m -"
|
||||
test ! -e /tmp/ageby-remove/locked-r-dir
|
||||
|
||||
# Aged R cleanup skips locked directories and everything below them.
|
||||
mkdir -p /tmp/ageby-remove/locked-dir/child
|
||||
touch --date "3 minutes ago" /tmp/ageby-remove/locked-dir /tmp/ageby-remove/locked-dir/child
|
||||
flock --exclusive /tmp/ageby-remove/locked-dir systemd-tmpfiles --clean --inline \
|
||||
"R /tmp/ageby-remove/locked-dir - - - M:1m -"
|
||||
test -d /tmp/ageby-remove/locked-dir/child
|
||||
systemd-tmpfiles --clean --inline "R /tmp/ageby-remove/locked-dir - - - M:1m -"
|
||||
test ! -e /tmp/ageby-remove/locked-dir
|
||||
|
||||
rm -rf /tmp/ageby-remove
|
||||
|
||||
# X entries inherit the parent cleanup age and its age-by fields.
|
||||
rm -rf /tmp/ageby-x-inherit
|
||||
mkdir -p /tmp/ageby-x-inherit/parent/child /tmp/ageby-x-inherit/parent/other
|
||||
|
||||
Reference in New Issue
Block a user