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)