diff --git a/man/tmpfiles.d.xml b/man/tmpfiles.d.xml
index 3f2ef7ee97..e5ea2bec8b 100644
--- a/man/tmpfiles.d.xml
+++ b/man/tmpfiles.d.xml
@@ -545,6 +545,14 @@ w- /proc/sys/vm/swappiness - - - - 10
(ctime). Any of these three (or two) values will prevent cleanup
if it is more recent than the current time minus the age
field.
+
+ Note that while the aging algorithm is run a 'shared' BSD file lock (see flock2) is
+ taken on each directory the algorithm descends into (and each directory below that, and so on). If the
+ aging algorithm finds a lock is already taken on some directory, it (and everything below it) is
+ skipped. Applications may use this to temporarily exclude certain directory subtrees from the aging
+ algorithm: the applications can take a BSD file lock themselves, and as long as they keep it aging of
+ the directory and everything below it is disabled.
diff --git a/src/tmpfiles/tmpfiles.c b/src/tmpfiles/tmpfiles.c
index b66765b407..6296a8acdf 100644
--- a/src/tmpfiles/tmpfiles.c
+++ b/src/tmpfiles/tmpfiles.c
@@ -12,6 +12,7 @@
#include
#include
#include
+#include
#include
#include
#include
@@ -48,6 +49,7 @@
#include "path-lookup.h"
#include "path-util.h"
#include "pretty-print.h"
+#include "rlimit-util.h"
#include "rm-rf.h"
#include "selinux-util.h"
#include "set.h"
@@ -525,7 +527,6 @@ static int dir_cleanup(
bool keep_this_level) {
struct dirent *dent;
- struct timespec times[2];
bool deleted = false;
int r = 0;
@@ -580,42 +581,45 @@ static int dir_cleanup(
}
if (S_ISDIR(s.st_mode)) {
+ _cleanup_closedir_ DIR *sub_dir = NULL;
if (mountpoint &&
streq(dent->d_name, "lost+found") &&
s.st_uid == 0) {
- log_debug("Ignoring \"%s\".", sub_path);
+ log_debug("Ignoring directory \"%s\".", sub_path);
continue;
}
if (maxdepth <= 0)
log_warning("Reached max depth on \"%s\".", sub_path);
else {
- _cleanup_closedir_ DIR *sub_dir;
int q;
sub_dir = xopendirat_nomod(dirfd(d), dent->d_name);
if (!sub_dir) {
if (errno != ENOENT)
- r = log_error_errno(errno, "opendir(%s) failed: %m", sub_path);
+ r = log_warning_errno(errno, "Opening directory \"%s\" failed, ignoring: %m", sub_path);
continue;
}
+ if (flock(dirfd(sub_dir), LOCK_EX|LOCK_NB) < 0) {
+ log_debug_errno(errno, "Couldn't acquire shared BSD lock on directory \"%s\", skipping: %m", p);
+ continue;
+ }
+
q = dir_cleanup(i, sub_path, sub_dir, &s, cutoff, rootdev, false, maxdepth-1, false);
if (q < 0)
r = q;
}
- /* Note: if you are wondering why we don't
- * support the sticky bit for excluding
- * directories from cleaning like we do it for
- * other file system objects: well, the sticky
- * bit already has a meaning for directories,
- * so we don't want to overload that. */
+ /* Note: if you are wondering why we don't support the sticky bit for excluding
+ * directories from cleaning like we do it for other file system objects: well, the
+ * sticky bit already has a meaning for directories, so we don't want to overload
+ * that. */
if (keep_this_level) {
- log_debug("Keeping \"%s\".", sub_path);
+ log_debug("Keeping directory \"%s\".", sub_path);
continue;
}
@@ -642,13 +646,11 @@ static int dir_cleanup(
log_debug("Removing directory \"%s\".", sub_path);
if (unlinkat(dirfd(d), dent->d_name, AT_REMOVEDIR) < 0)
if (!IN_SET(errno, ENOENT, ENOTEMPTY))
- r = log_error_errno(errno, "rmdir(%s): %m", sub_path);
+ r = log_warning_errno(errno, "Failed to remove directory \"%s\", ignoring: %m", sub_path);
} else {
- /* 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. */
+ /* 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. */
if (s.st_mode & S_ISVTX) {
log_debug("Skipping \"%s\": sticky bit set.", sub_path);
continue;
@@ -675,8 +677,7 @@ static int dir_cleanup(
continue;
}
- /* Keep files on this level around if this is
- * requested */
+ /* Keep files on this level around if this is requested */
if (keep_this_level) {
log_debug("Keeping \"%s\".", sub_path);
continue;
@@ -710,11 +711,10 @@ static int dir_cleanup(
continue;
}
- log_debug("unlink \"%s\"", sub_path);
-
+ log_debug("Removing \"%s\".", sub_path);
if (unlinkat(dirfd(d), dent->d_name, 0) < 0)
if (errno != ENOENT)
- r = log_error_errno(errno, "unlink(%s): %m", sub_path);
+ r = log_warning_errno(errno, "Failed to remove \"%s\", ignoring: %m", sub_path);
deleted = true;
}
@@ -722,21 +722,22 @@ static int dir_cleanup(
finish:
if (deleted) {
- usec_t age1, age2;
char a[FORMAT_TIMESTAMP_MAX], b[FORMAT_TIMESTAMP_MAX];
-
- /* Restore original directory timestamps */
- times[0] = ds->st_atim;
- times[1] = ds->st_mtim;
+ usec_t age1, age2;
age1 = timespec_load(&ds->st_atim);
age2 = timespec_load(&ds->st_mtim);
+
log_debug("Restoring access and modification time on \"%s\": %s, %s",
p,
format_timestamp_us(a, sizeof(a), age1),
format_timestamp_us(b, sizeof(b), age2));
- if (futimens(dirfd(d), times) < 0)
- log_error_errno(errno, "utimensat(%s): %m", p);
+
+ /* Restore original directory timestamps */
+ if (futimens(dirfd(d), (struct timespec[]) {
+ ds->st_atim,
+ ds->st_mtim }) < 0)
+ log_warning_errno(errno, "Failed to revert timestamps of '%s', ignoring: %m", p);
}
return r;
@@ -3175,6 +3176,9 @@ static int run(int argc, char *argv[]) {
log_setup_service();
+ /* Descending down file system trees might take a lot of fds */
+ (void) rlimit_nofile_bump(HIGH_RLIMIT_NOFILE);
+
if (arg_user) {
r = user_config_paths(&config_dirs);
if (r < 0)