summaryrefslogtreecommitdiff
path: root/src/tmpfiles/tmpfiles.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/tmpfiles/tmpfiles.c')
-rw-r--r--src/tmpfiles/tmpfiles.c265
1 files changed, 201 insertions, 64 deletions
diff --git a/src/tmpfiles/tmpfiles.c b/src/tmpfiles/tmpfiles.c
index 8f4ceee037..197bb5d223 100644
--- a/src/tmpfiles/tmpfiles.c
+++ b/src/tmpfiles/tmpfiles.c
@@ -110,6 +110,17 @@ typedef enum ItemType {
ADJUST_MODE = 'm', /* legacy, 'z' is identical to this */
} ItemType;
+typedef enum AgeBy {
+ AGE_BY_ATIME = 1 << 0,
+ AGE_BY_BTIME = 1 << 1,
+ AGE_BY_CTIME = 1 << 2,
+ AGE_BY_MTIME = 1 << 3,
+
+ /* All file timestamp types are checked by default. */
+ AGE_BY_DEFAULT_FILE = AGE_BY_ATIME | AGE_BY_BTIME | AGE_BY_CTIME | AGE_BY_MTIME,
+ AGE_BY_DEFAULT_DIR = AGE_BY_ATIME | AGE_BY_BTIME | AGE_BY_MTIME
+} AgeBy;
+
typedef struct Item {
ItemType type;
@@ -124,6 +135,7 @@ typedef struct Item {
gid_t gid;
mode_t mode;
usec_t age;
+ AgeBy age_by_file, age_by_dir;
dev_t major_minor;
unsigned attribute_value;
@@ -505,6 +517,64 @@ static inline nsec_t load_statx_timestamp_nsec(const struct statx_timestamp *ts)
return ts->tv_sec * NSEC_PER_SEC + ts->tv_nsec;
}
+static bool needs_cleanup(
+ nsec_t atime,
+ nsec_t btime,
+ nsec_t ctime,
+ nsec_t mtime,
+ nsec_t cutoff,
+ const char *sub_path,
+ AgeBy age_by,
+ bool is_dir) {
+
+ if (FLAGS_SET(age_by, AGE_BY_MTIME) && mtime != NSEC_INFINITY && mtime >= cutoff) {
+ char a[FORMAT_TIMESTAMP_MAX];
+ /* Follows spelling in stat(1). */
+ log_debug("%s \"%s\": modify time %s is too new.",
+ is_dir ? "Directory" : "File",
+ sub_path,
+ format_timestamp_style(a, sizeof(a), mtime / NSEC_PER_USEC, TIMESTAMP_US));
+
+ return false;
+ }
+
+ if (FLAGS_SET(age_by, AGE_BY_ATIME) && atime != NSEC_INFINITY && atime >= cutoff) {
+ char a[FORMAT_TIMESTAMP_MAX];
+ log_debug("%s \"%s\": access time %s is too new.",
+ is_dir ? "Directory" : "File",
+ sub_path,
+ format_timestamp_style(a, sizeof(a), atime / NSEC_PER_USEC, TIMESTAMP_US));
+
+ return false;
+ }
+
+ /*
+ * Note: Unless explicitly specified by the user, "ctime" is ignored
+ * by default for directories, because we change it when deleting.
+ */
+ if (FLAGS_SET(age_by, AGE_BY_CTIME) && ctime != NSEC_INFINITY && ctime >= cutoff) {
+ char a[FORMAT_TIMESTAMP_MAX];
+ log_debug("%s \"%s\": change time %s is too new.",
+ is_dir ? "Directory" : "File",
+ sub_path,
+ format_timestamp_style(a, sizeof(a), ctime / NSEC_PER_USEC, TIMESTAMP_US));
+
+ return false;
+ }
+
+ if (FLAGS_SET(age_by, AGE_BY_BTIME) && btime != NSEC_INFINITY && btime >= cutoff) {
+ char a[FORMAT_TIMESTAMP_MAX];
+ log_debug("%s \"%s\": birth time %s is too new.",
+ is_dir ? "Directory" : "File",
+ sub_path,
+ format_timestamp_style(a, sizeof(a), btime / NSEC_PER_USEC, TIMESTAMP_US));
+
+ return false;
+ }
+
+ return true;
+}
+
static int dir_cleanup(
Item *i,
const char *p,
@@ -516,7 +586,9 @@ static int dir_cleanup(
dev_t rootdev_minor,
bool mountpoint,
int maxdepth,
- bool keep_this_level) {
+ bool keep_this_level,
+ AgeBy age_by_file,
+ AgeBy age_by_dir) {
bool deleted = false;
struct dirent *dent;
@@ -641,7 +713,8 @@ static int dir_cleanup(
sub_path, sub_dir,
atime_nsec, mtime_nsec, cutoff_nsec,
rootdev_major, rootdev_minor,
- false, maxdepth-1, false);
+ false, maxdepth-1, false,
+ age_by_file, age_by_dir);
if (q < 0)
r = q;
}
@@ -656,31 +729,13 @@ static int dir_cleanup(
continue;
}
- /* Ignore ctime, we change it when deleting */
- if (mtime_nsec != NSEC_INFINITY && mtime_nsec >= cutoff_nsec) {
- char a[FORMAT_TIMESTAMP_MAX];
- /* Follows spelling in stat(1). */
- log_debug("Directory \"%s\": modify time %s is too new.",
- sub_path,
- format_timestamp_style(a, sizeof(a), mtime_nsec / NSEC_PER_USEC, TIMESTAMP_US));
+ /*
+ * Check the file timestamps of an entry against the
+ * given cutoff time; delete if it is older.
+ */
+ if (!needs_cleanup(atime_nsec, btime_nsec, ctime_nsec, mtime_nsec,
+ cutoff_nsec, sub_path, age_by_dir, true))
continue;
- }
-
- if (atime_nsec != NSEC_INFINITY && atime_nsec >= cutoff_nsec) {
- char a[FORMAT_TIMESTAMP_MAX];
- log_debug("Directory \"%s\": access time %s is too new.",
- sub_path,
- 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;
- }
log_debug("Removing directory \"%s\".", sub_path);
if (unlinkat(dirfd(d), dent->d_name, AT_REMOVEDIR) < 0)
@@ -724,38 +779,9 @@ static int dir_cleanup(
continue;
}
- if (mtime_nsec != NSEC_INFINITY && mtime_nsec >= cutoff_nsec) {
- char a[FORMAT_TIMESTAMP_MAX];
- /* Follows spelling in stat(1). */
- log_debug("File \"%s\": modify time %s is too new.",
- sub_path,
- format_timestamp_style(a, sizeof(a), mtime_nsec / NSEC_PER_USEC, TIMESTAMP_US));
- continue;
- }
-
- if (atime_nsec != NSEC_INFINITY && atime_nsec >= cutoff_nsec) {
- char a[FORMAT_TIMESTAMP_MAX];
- log_debug("File \"%s\": access time %s is too new.",
- sub_path,
- format_timestamp_style(a, sizeof(a), atime_nsec / NSEC_PER_USEC, TIMESTAMP_US));
- continue;
- }
-
- if (ctime_nsec != NSEC_INFINITY && ctime_nsec >= cutoff_nsec) {
- char a[FORMAT_TIMESTAMP_MAX];
- log_debug("File \"%s\": change time %s is too new.",
- sub_path,
- format_timestamp_style(a, sizeof(a), ctime_nsec / NSEC_PER_USEC, TIMESTAMP_US));
+ if (!needs_cleanup(atime_nsec, btime_nsec, ctime_nsec, mtime_nsec,
+ cutoff_nsec, sub_path, age_by_file, false))
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;
- }
log_debug("Removing \"%s\".", sub_path);
if (unlinkat(dirfd(d), dent->d_name, 0) < 0)
@@ -2443,6 +2469,23 @@ static int remove_item(Item *i) {
}
}
+static char *age_by_to_string(AgeBy ab, bool is_dir) {
+ static const char ab_map[] = { 'a', 'b', 'c', 'm' };
+ size_t j = 0;
+ char *ret;
+
+ ret = new(char, ELEMENTSOF(ab_map) + 1);
+ if (!ret)
+ return NULL;
+
+ for (size_t i = 0; i < ELEMENTSOF(ab_map); i++)
+ if (FLAGS_SET(ab, 1U << i))
+ ret[j++] = is_dir ? ascii_toupper(ab_map[i]) : ab_map[i];
+
+ ret[j] = 0;
+ return ret;
+}
+
static int clean_item_instance(Item *i, const char* instance) {
char timestamp[FORMAT_TIMESTAMP_MAX];
_cleanup_closedir_ DIR *d = NULL;
@@ -2489,17 +2532,31 @@ static int clean_item_instance(Item *i, const char* instance) {
sx.stx_ino != ps.st_ino;
}
- log_debug("Cleanup threshold for %s \"%s\" is %s",
- mountpoint ? "mount point" : "directory",
- instance,
- format_timestamp_style(timestamp, sizeof(timestamp), cutoff, TIMESTAMP_US));
+ if (DEBUG_LOGGING) {
+ _cleanup_free_ char *ab_f = NULL, *ab_d = NULL;
+
+ ab_f = age_by_to_string(i->age_by_file, false);
+ if (!ab_f)
+ return log_oom();
+
+ ab_d = age_by_to_string(i->age_by_dir, true);
+ if (!ab_d)
+ return log_oom();
+
+ log_debug("Cleanup threshold for %s \"%s\" is %s; age-by: %s%s",
+ mountpoint ? "mount point" : "directory",
+ instance,
+ format_timestamp_style(timestamp, sizeof(timestamp), cutoff, TIMESTAMP_US),
+ ab_f, ab_d);
+ }
return dir_cleanup(i, instance, d,
load_statx_timestamp_nsec(&sx.stx_atime),
load_statx_timestamp_nsec(&sx.stx_mtime),
cutoff * NSEC_PER_USEC,
sx.stx_dev_major, sx.stx_dev_minor, mountpoint,
- MAX_DEPTH, i->keep_first_level);
+ MAX_DEPTH, i->keep_first_level,
+ i->age_by_file, i->age_by_dir);
}
static int clean_item(Item *i) {
@@ -2665,6 +2722,9 @@ static bool item_compatible(Item *a, Item *b) {
a->age_set == b->age_set &&
a->age == b->age &&
+ a->age_by_file == b->age_by_file &&
+ a->age_by_dir == b->age_by_dir &&
+
a->mask_perms == b->mask_perms &&
a->keep_first_level == b->keep_first_level &&
@@ -2829,6 +2889,58 @@ static int find_gid(const char *group, gid_t *ret_gid, Hashmap **cache) {
return name_to_gid_offline(arg_root, group, ret_gid, cache);
}
+static int parse_age_by_from_arg(const char *age_by_str, Item *item) {
+ AgeBy ab_f = 0, ab_d = 0;
+
+ static const struct {
+ char age_by_chr;
+ AgeBy age_by_flag;
+ } age_by_types[] = {
+ { 'a', AGE_BY_ATIME },
+ { 'b', AGE_BY_BTIME },
+ { 'c', AGE_BY_CTIME },
+ { 'm', AGE_BY_MTIME },
+ };
+
+ assert(age_by_str);
+ assert(item);
+
+ if (isempty(age_by_str))
+ return -EINVAL;
+
+ for (const char *s = age_by_str; *s != 0; s++) {
+ size_t i;
+
+ /* Ignore whitespace. */
+ if (strchr(WHITESPACE, *s))
+ continue;
+
+ for (i = 0; i < ELEMENTSOF(age_by_types); i++) {
+ /* Check lower-case for files, upper-case for directories. */
+ if (*s == age_by_types[i].age_by_chr) {
+ ab_f |= age_by_types[i].age_by_flag;
+ break;
+ } else if (*s == ascii_toupper(age_by_types[i].age_by_chr)) {
+ ab_d |= age_by_types[i].age_by_flag;
+ break;
+ }
+ }
+
+ /* Invalid character. */
+ if (i >= ELEMENTSOF(age_by_types))
+ return -EINVAL;
+ }
+
+ /* No match. */
+ if (ab_f == 0 && ab_d == 0)
+ return -EINVAL;
+
+ item->age_by_file = ab_f > 0 ? ab_f : AGE_BY_DEFAULT_FILE;
+ item->age_by_dir = ab_d > 0 ? ab_d : AGE_BY_DEFAULT_DIR;
+
+ return 0;
+}
+
static int parse_line(
const char *fname,
unsigned line,
@@ -2838,7 +2950,11 @@ static int parse_line(
Hashmap **gid_cache) {
_cleanup_free_ char *action = NULL, *mode = NULL, *user = NULL, *group = NULL, *age = NULL, *path = NULL;
- _cleanup_(item_free_contents) Item i = {};
+ _cleanup_(item_free_contents) Item i = {
+ /* The "age-by" argument considers all file timestamp types by default. */
+ .age_by_file = AGE_BY_DEFAULT_FILE,
+ .age_by_dir = AGE_BY_DEFAULT_DIR,
+ };
ItemArray *existing;
OrderedHashmap *h;
int r, pos;
@@ -3112,16 +3228,37 @@ static int parse_line(
if (!empty_or_dash(age)) {
const char *a = age;
+ _cleanup_free_ char *seconds = NULL, *age_by = NULL;
if (*a == '~') {
i.keep_first_level = true;
a++;
}
+ /* Format: "age-by:age"; where age-by is "[abcmABCM]+". */
+ r = split_pair(a, ":", &age_by, &seconds);
+ if (r == -ENOMEM)
+ return log_oom();
+ if (r < 0 && r != -EINVAL)
+ return log_error_errno(r, "Failed to parse age-by for '%s': %m", age);
+ if (r >= 0) {
+ /* We found a ":", parse the "age-by" part. */
+ r = parse_age_by_from_arg(age_by, &i);
+ if (r == -ENOMEM)
+ return log_oom();
+ if (r < 0) {
+ *invalid_config = true;
+ return log_syntax(NULL, LOG_ERR, fname, line, r, "Invalid age-by '%s'.", age_by);
+ }
+
+ /* For parsing the "age" part, after the ":". */
+ a = seconds;
+ }
+
r = parse_sec(a, &i.age);
if (r < 0) {
*invalid_config = true;
- return log_syntax(NULL, LOG_ERR, fname, line, r, "Invalid age '%s'.", age);
+ return log_syntax(NULL, LOG_ERR, fname, line, r, "Invalid age '%s'.", a);
}
i.age_set = true;