diff options
Diffstat (limited to 'src/tmpfiles')
-rw-r--r-- | src/tmpfiles/meson.build | 1 | ||||
-rw-r--r-- | src/tmpfiles/tmpfiles.c | 1064 |
2 files changed, 603 insertions, 462 deletions
diff --git a/src/tmpfiles/meson.build b/src/tmpfiles/meson.build index 8a24a21..2e91850 100644 --- a/src/tmpfiles/meson.build +++ b/src/tmpfiles/meson.build @@ -21,7 +21,6 @@ executables += [ 'c_args' : '-DSTANDALONE', 'link_with' : [ libbasic, - libbasic_gcrypt, libshared_static, libsystemd_static, ], diff --git a/src/tmpfiles/tmpfiles.c b/src/tmpfiles/tmpfiles.c index 4919cb7..807925f 100644 --- a/src/tmpfiles/tmpfiles.c +++ b/src/tmpfiles/tmpfiles.c @@ -45,12 +45,10 @@ #include "log.h" #include "macro.h" #include "main-func.h" -#include "missing_stat.h" #include "missing_syscall.h" #include "mkdir-label.h" #include "mount-util.h" #include "mountpoint-util.h" -#include "nulstr-util.h" #include "offline-passwd.h" #include "pager.h" #include "parse-argument.h" @@ -64,10 +62,12 @@ #include "set.h" #include "sort-util.h" #include "specifier.h" +#include "stat-util.h" #include "stdio-util.h" #include "string-table.h" #include "string-util.h" #include "strv.h" +#include "sysctl-util.h" #include "terminal-util.h" #include "umask-util.h" #include "user-util.h" @@ -82,6 +82,7 @@ typedef enum OperationMask { OPERATION_CREATE = 1 << 0, OPERATION_REMOVE = 1 << 1, OPERATION_CLEAN = 1 << 2, + OPERATION_PURGE = 1 << 3, } OperationMask; typedef enum ItemType { @@ -197,6 +198,7 @@ typedef enum { } CreationMode; static CatFlags arg_cat_flags = CAT_CONFIG_OFF; +static bool arg_dry_run = false; static RuntimeScope arg_runtime_scope = RUNTIME_SCOPE_SYSTEM; static OperationMask arg_operation = 0; static bool arg_boot = false; @@ -213,12 +215,15 @@ static ImagePolicy *arg_image_policy = NULL; #define MAX_DEPTH 256 typedef struct Context { - OrderedHashmap *items, *globs; + OrderedHashmap *items; + OrderedHashmap *globs; Set *unix_sockets; + Hashmap *uid_cache; + Hashmap *gid_cache; } Context; -STATIC_DESTRUCTOR_REGISTER(arg_include_prefixes, freep); -STATIC_DESTRUCTOR_REGISTER(arg_exclude_prefixes, freep); +STATIC_DESTRUCTOR_REGISTER(arg_include_prefixes, strv_freep); +STATIC_DESTRUCTOR_REGISTER(arg_exclude_prefixes, strv_freep); STATIC_DESTRUCTOR_REGISTER(arg_root, freep); STATIC_DESTRUCTOR_REGISTER(arg_image, freep); STATIC_DESTRUCTOR_REGISTER(arg_image_policy, image_policy_freep); @@ -238,6 +243,9 @@ static void context_done(Context *c) { ordered_hashmap_free(c->globs); set_free(c->unix_sockets); + + hashmap_free(c->uid_cache); + hashmap_free(c->gid_cache); } /* Different kinds of errors that mean that information is not available in the environment. */ @@ -249,7 +257,13 @@ static bool ERRNO_IS_NOINFO(int r) { ENXIO); /* env var is unset */ } -static int specifier_directory(char specifier, const void *data, const char *root, const void *userdata, char **ret) { +static int specifier_directory( + char specifier, + const void *data, + const char *root, + const void *userdata, + char **ret) { + struct table_entry { uint64_t type; const char *suffix; @@ -328,6 +342,12 @@ static int log_unresolvable_specifier(const char *filename, unsigned line) { return 0; } +#define log_action(would, doing, fmt, ...) \ + log_full(arg_dry_run ? LOG_INFO : LOG_DEBUG, \ + fmt, \ + arg_dry_run ? (would) : (doing), \ + __VA_ARGS__) + static int user_config_paths(char*** ret) { _cleanup_strv_free_ char **config_dirs = NULL, **data_dirs = NULL; _cleanup_free_ char *persistent_config = NULL, *runtime_config = NULL, *data_home = NULL; @@ -350,23 +370,19 @@ static int user_config_paths(char*** ret) { if (r < 0 && !ERRNO_IS_NOINFO(r)) return r; - r = strv_extend_strv_concat(&res, config_dirs, "/user-tmpfiles.d"); - if (r < 0) - return r; - - r = strv_extend(&res, persistent_config); + r = strv_extend_strv_concat(&res, (const char* const*) config_dirs, "/user-tmpfiles.d"); if (r < 0) return r; - r = strv_extend(&res, runtime_config); + r = strv_extend_many( + &res, + persistent_config, + runtime_config, + data_home); if (r < 0) return r; - r = strv_extend(&res, data_home); - if (r < 0) - return r; - - r = strv_extend_strv_concat(&res, data_dirs, "/user-tmpfiles.d"); + r = strv_extend_strv_concat(&res, (const char* const*) data_dirs, "/user-tmpfiles.d"); if (r < 0) return r; @@ -378,23 +394,41 @@ static int user_config_paths(char*** ret) { return 0; } +static bool needs_purge(ItemType t) { + return IN_SET(t, + COPY_FILES, + TRUNCATE_FILE, + CREATE_FILE, + WRITE_FILE, + EMPTY_DIRECTORY, + CREATE_SUBVOLUME, + CREATE_SUBVOLUME_INHERIT_QUOTA, + CREATE_SUBVOLUME_NEW_QUOTA, + CREATE_CHAR_DEVICE, + CREATE_BLOCK_DEVICE, + CREATE_SYMLINK, + CREATE_FIFO, + CREATE_DIRECTORY, + TRUNCATE_DIRECTORY); +} + static bool needs_glob(ItemType t) { return IN_SET(t, WRITE_FILE, - IGNORE_PATH, - IGNORE_DIRECTORY_PATH, - REMOVE_PATH, - RECURSIVE_REMOVE_PATH, EMPTY_DIRECTORY, - ADJUST_MODE, - RELABEL_PATH, - RECURSIVE_RELABEL_PATH, SET_XATTR, RECURSIVE_SET_XATTR, SET_ACL, RECURSIVE_SET_ACL, SET_ATTRIBUTE, - RECURSIVE_SET_ATTRIBUTE); + RECURSIVE_SET_ATTRIBUTE, + IGNORE_PATH, + IGNORE_DIRECTORY_PATH, + REMOVE_PATH, + RECURSIVE_REMOVE_PATH, + RELABEL_PATH, + RECURSIVE_RELABEL_PATH, + ADJUST_MODE); } static bool takes_ownership(ItemType t) { @@ -402,7 +436,6 @@ static bool takes_ownership(ItemType t) { CREATE_FILE, TRUNCATE_FILE, CREATE_DIRECTORY, - EMPTY_DIRECTORY, TRUNCATE_DIRECTORY, CREATE_SUBVOLUME, CREATE_SUBVOLUME_INHERIT_QUOTA, @@ -413,6 +446,7 @@ static bool takes_ownership(ItemType t) { CREATE_BLOCK_DEVICE, COPY_FILES, WRITE_FILE, + EMPTY_DIRECTORY, IGNORE_PATH, IGNORE_DIRECTORY_PATH, REMOVE_PATH, @@ -422,17 +456,10 @@ static bool takes_ownership(ItemType t) { static struct Item* find_glob(OrderedHashmap *h, const char *match) { ItemArray *j; - ORDERED_HASHMAP_FOREACH(j, h) { - size_t n; - - for (n = 0; n < j->n_items; n++) { - Item *item = j->items + n; - + ORDERED_HASHMAP_FOREACH(j, h) + FOREACH_ARRAY(item, j->items, j->n_items) if (fnmatch(item->path, match, FNM_PATHNAME|FNM_PERIOD) == 0) return item; - } - } - return NULL; } @@ -537,16 +564,59 @@ static DIR* opendir_nomod(const char *path) { return xopendirat_nomod(AT_FDCWD, path); } -static nsec_t load_statx_timestamp_nsec(const struct statx_timestamp *ts) { - assert(ts); +static int opendir_and_stat( + const char *path, + DIR **ret, + struct statx *ret_sx, + bool *ret_mountpoint) { + + _cleanup_closedir_ DIR *d = NULL; + STRUCT_NEW_STATX_DEFINE(st1); + int r; + + assert(path); + assert(ret); + assert(ret_sx); + assert(ret_mountpoint); + + /* Do opendir() and statx() on the directory. + * Return 1 if successful, 0 if file doesn't exist or is not a directory, + * negative errno otherwise. + */ + + d = opendir_nomod(path); + if (!d) { + bool ignore = IN_SET(errno, ENOENT, ENOTDIR); + r = log_full_errno(ignore ? LOG_DEBUG : LOG_ERR, + errno, "Failed to open directory %s: %m", path); + if (!ignore) + return r; + + *ret = NULL; + *ret_sx = (struct statx) {}; + *ret_mountpoint = NULL; + return 0; + } - if (ts->tv_sec < 0) - return NSEC_INFINITY; + r = statx_fallback(dirfd(d), "", AT_EMPTY_PATH, STATX_MODE|STATX_INO|STATX_ATIME|STATX_MTIME, &st1.sx); + if (r < 0) + return log_error_errno(r, "statx(%s) failed: %m", path); + + if (FLAGS_SET(st1.sx.stx_attributes_mask, STATX_ATTR_MOUNT_ROOT)) + *ret_mountpoint = FLAGS_SET(st1.sx.stx_attributes, STATX_ATTR_MOUNT_ROOT); + else { + STRUCT_NEW_STATX_DEFINE(st2); + + r = statx_fallback(dirfd(d), "..", 0, STATX_INO, &st2.sx); + if (r < 0) + return log_error_errno(r, "statx(%s/..) failed: %m", path); - if ((nsec_t) ts->tv_sec >= (UINT64_MAX - ts->tv_nsec) / NSEC_PER_SEC) - return NSEC_INFINITY; + *ret_mountpoint = !statx_mount_same(&st1.nsx, &st2.nsx); + } - return ts->tv_sec * NSEC_PER_SEC + ts->tv_nsec; + *ret = TAKE_PTR(d); + *ret_sx = st1.sx; + return 1; } static bool needs_cleanup( @@ -655,8 +725,8 @@ static int dir_cleanup( continue; if (r < 0) { /* FUSE, NFS mounts, SELinux might return EACCES */ - r = log_full_errno(r == -EACCES ? LOG_DEBUG : LOG_ERR, r, - "statx(%s/%s) failed: %m", p, de->d_name); + log_full_errno(r == -EACCES ? LOG_DEBUG : LOG_ERR, r, + "statx(%s/%s) failed: %m", p, de->d_name); continue; } @@ -675,9 +745,9 @@ static int dir_cleanup( continue; } - /* Try to detect bind mounts of the same filesystem instance; they do not differ in device - * major/minors. This type of query is not supported on all kernels or filesystem types - * though. */ + /* Try to detect bind mounts of the same filesystem instance; they do not differ in + * device major/minors. This type of query is not supported on all kernels or + * filesystem types though. */ if (S_ISDIR(sx.stx_mode)) { int q; @@ -691,10 +761,10 @@ static int dir_cleanup( } } - 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; + atime_nsec = FLAGS_SET(sx.stx_mask, STATX_ATIME) ? statx_timestamp_load_nsec(&sx.stx_atime) : 0; + mtime_nsec = FLAGS_SET(sx.stx_mask, STATX_MTIME) ? statx_timestamp_load_nsec(&sx.stx_mtime) : 0; + ctime_nsec = FLAGS_SET(sx.stx_mask, STATX_CTIME) ? statx_timestamp_load_nsec(&sx.stx_ctime) : 0; + btime_nsec = FLAGS_SET(sx.stx_mask, STATX_BTIME) ? statx_timestamp_load_nsec(&sx.stx_btime) : 0; sub_path = path_join(p, de->d_name); if (!sub_path) { @@ -736,7 +806,8 @@ static int dir_cleanup( continue; } - if (flock(dirfd(sub_dir), LOCK_EX|LOCK_NB) < 0) { + if (!arg_dry_run && + flock(dirfd(sub_dir), LOCK_EX|LOCK_NB) < 0) { log_debug_errno(errno, "Couldn't acquire shared BSD lock on directory \"%s\", skipping: %m", sub_path); continue; } @@ -769,13 +840,16 @@ static int dir_cleanup( cutoff_nsec, sub_path, age_by_dir, true)) continue; - log_debug("Removing directory \"%s\".", sub_path); - if (unlinkat(dirfd(d), de->d_name, AT_REMOVEDIR) < 0) - if (!IN_SET(errno, ENOENT, ENOTEMPTY)) - r = log_warning_errno(errno, "Failed to remove directory \"%s\", ignoring: %m", sub_path); + log_action("Would remove", "Removing", "%s directory \"%s\"", sub_path); + if (!arg_dry_run && + unlinkat(dirfd(d), de->d_name, AT_REMOVEDIR) < 0 && + !IN_SET(errno, ENOENT, ENOTEMPTY)) + r = log_warning_errno(errno, "Failed to remove directory \"%s\", ignoring: %m", sub_path); } else { - _cleanup_close_ int fd = -EBADF; + _cleanup_close_ int fd = -EBADF; /* This file descriptor is defined here so that the + * lock that is taken below is only dropped _after_ + * the unlink operation has finished. */ /* 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. */ @@ -807,7 +881,7 @@ static int dir_cleanup( continue; } - /* Keep files on this level around if this is requested */ + /* Keep files on this level if this was requested */ if (keep_this_level) { log_debug("Keeping \"%s\".", sub_path); continue; @@ -817,78 +891,77 @@ static int dir_cleanup( cutoff_nsec, sub_path, age_by_file, false)) continue; - fd = xopenat_full(dirfd(d), - de->d_name, - O_RDONLY|O_CLOEXEC|O_NOFOLLOW|O_NOATIME|O_NONBLOCK, - /* xopen_flags = */ 0, - /* mode = */ 0); - if (fd < 0 && !IN_SET(fd, -ENOENT, -ELOOP)) - log_warning_errno(fd, "Opening file \"%s\" failed, ignoring: %m", sub_path); - if (fd >= 0 && flock(fd, LOCK_EX|LOCK_NB) < 0 && errno == EAGAIN) { - log_debug_errno(errno, "Couldn't acquire shared BSD lock on file \"%s\", skipping: %m", sub_path); - continue; + if (!arg_dry_run) { + fd = xopenat(dirfd(d), de->d_name, O_RDONLY|O_CLOEXEC|O_NOFOLLOW|O_NOATIME|O_NONBLOCK|O_NOCTTY); + if (fd < 0 && !IN_SET(fd, -ENOENT, -ELOOP)) + log_warning_errno(fd, "Opening file \"%s\" failed, proceeding without lock: %m", sub_path); + if (fd >= 0 && flock(fd, LOCK_EX|LOCK_NB) < 0 && errno == EAGAIN) { + log_debug_errno(errno, "Couldn't acquire shared BSD lock on file \"%s\", skipping: %m", sub_path); + continue; + } } - log_debug("Removing \"%s\".", sub_path); - if (unlinkat(dirfd(d), de->d_name, 0) < 0) - if (errno != ENOENT) - r = log_warning_errno(errno, "Failed to remove \"%s\", ignoring: %m", sub_path); + log_action("Would remove", "Removing", "%s \"%s\"", sub_path); + if (!arg_dry_run && + unlinkat(dirfd(d), de->d_name, 0) < 0 && + errno != ENOENT) + r = log_warning_errno(errno, "Failed to remove \"%s\", ignoring: %m", sub_path); deleted = true; } } finish: - if (deleted) { + if (deleted && (self_atime_nsec < NSEC_INFINITY || self_mtime_nsec < NSEC_INFINITY)) { struct timespec ts[2]; - log_debug("Restoring access and modification time on \"%s\": %s, %s", - p, - FORMAT_TIMESTAMP_STYLE(self_atime_nsec / NSEC_PER_USEC, TIMESTAMP_US), - FORMAT_TIMESTAMP_STYLE(self_mtime_nsec / NSEC_PER_USEC, TIMESTAMP_US)); + log_action("Would restore", "Restoring", + "%s access and modification time on \"%s\": %s, %s", + p, + FORMAT_TIMESTAMP_STYLE(self_atime_nsec / NSEC_PER_USEC, TIMESTAMP_US), + FORMAT_TIMESTAMP_STYLE(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 */ - if (futimens(dirfd(d), ts) < 0) + if (!arg_dry_run && + futimens(dirfd(d), ts) < 0) log_warning_errno(errno, "Failed to revert timestamps of '%s', ignoring: %m", p); } return r; } -static bool dangerous_hardlinks(void) { - _cleanup_free_ char *value = NULL; +static bool hardlinks_protected(void) { static int cached = -1; int r; - /* Check whether the fs.protected_hardlinks sysctl is on. If we can't determine it we assume its off, as that's - * what the upstream default is. */ + /* Check whether the fs.protected_hardlinks sysctl is on. If we can't determine it we assume its off, + * as that's what the kernel default is. + * Note that we ship 50-default.conf where it is enabled, but better be safe than sorry. */ if (cached >= 0) return cached; - r = read_one_line_file("/proc/sys/fs/protected_hardlinks", &value); - if (r < 0) { - log_debug_errno(r, "Failed to read fs.protected_hardlinks sysctl: %m"); - return true; - } + _cleanup_free_ char *value = NULL; - r = parse_boolean(value); + r = sysctl_read("fs/protected_hardlinks", &value); if (r < 0) { - log_debug_errno(r, "Failed to parse fs.protected_hardlinks sysctl: %m"); - return true; + log_debug_errno(r, "Failed to read fs.protected_hardlinks sysctl, assuming disabled: %m"); + return false; } - cached = r == 0; - return cached; + cached = parse_boolean(value); + if (cached < 0) + log_debug_errno(cached, "Failed to parse fs.protected_hardlinks sysctl, assuming disabled: %m"); + return cached > 0; } static bool hardlink_vulnerable(const struct stat *st) { assert(st); - return !S_ISDIR(st->st_mode) && st->st_nlink > 1 && dangerous_hardlinks(); + return !S_ISDIR(st->st_mode) && st->st_nlink > 1 && !hardlinks_protected(); } static mode_t process_mask_perms(mode_t mode, mode_t current) { @@ -965,18 +1038,23 @@ static int fd_set_perms( if (((m ^ st->st_mode) & 07777) == 0) log_debug("\"%s\" matches temporary mode %o already.", path, m); else { - log_debug("Temporarily changing \"%s\" to mode %o.", path, m); - r = fchmod_opath(fd, m); - if (r < 0) - return log_error_errno(r, "fchmod() of %s failed: %m", path); + log_action("Would temporarily change", "Temporarily changing", + "%s \"%s\" to mode %o", path, m); + if (!arg_dry_run) { + r = fchmod_opath(fd, m); + if (r < 0) + return log_error_errno(r, "fchmod() of %s failed: %m", path); + } } } } if (do_chown) { - log_debug("Changing \"%s\" to owner "UID_FMT":"GID_FMT, path, new_uid, new_gid); + log_action("Would change", "Changing", + "%s \"%s\" to owner "UID_FMT":"GID_FMT, path, new_uid, new_gid); - if (fchownat(fd, "", + if (!arg_dry_run && + fchownat(fd, "", new_uid != st->st_uid ? new_uid : UID_INVALID, new_gid != st->st_gid ? new_gid : GID_INVALID, AT_EMPTY_PATH) < 0) @@ -989,10 +1067,12 @@ static int fd_set_perms( if (S_ISLNK(st->st_mode)) log_debug("Skipping mode fix for symlink %s.", path); else { - log_debug("Changing \"%s\" to mode %o.", path, new_mode); - r = fchmod_opath(fd, new_mode); - if (r < 0) - return log_error_errno(r, "fchmod() of %s failed: %m", path); + log_action("Would change", "Changing", "%s \"%s\" to mode %o", path, new_mode); + if (!arg_dry_run) { + r = fchmod_opath(fd, new_mode); + if (r < 0) + return log_error_errno(r, "fchmod() of %s failed: %m", path); + } } } @@ -1122,8 +1202,11 @@ static int fd_set_xattrs( assert(path); STRV_FOREACH_PAIR(name, value, i->xattrs) { - log_debug("Setting extended attribute '%s=%s' on %s.", *name, *value, path); - if (setxattr(FORMAT_PROC_FD_PATH(fd), *name, *value, strlen(*value), 0) < 0) + log_action("Would set", "Setting", + "%s extended attribute '%s=%s' on %s", *name, *value, path); + + if (!arg_dry_run && + setxattr(FORMAT_PROC_FD_PATH(fd), *name, *value, strlen(*value), 0) < 0) return log_error_errno(errno, "Setting extended attribute %s=%s on %s failed: %m", *name, *value, path); } @@ -1327,12 +1410,13 @@ static int path_set_acl( return r; t = acl_to_any_text(dup, NULL, ',', TEXT_ABBREVIATE); - log_debug("Setting %s ACL %s on %s.", - type == ACL_TYPE_ACCESS ? "access" : "default", - strna(t), pretty); + log_action("Would set", "Setting", + "%s %s ACL %s on %s", + type == ACL_TYPE_ACCESS ? "access" : "default", + strna(t), pretty); - r = acl_set_file(path, type, dup); - if (r < 0) { + if (!arg_dry_run && + acl_set_file(path, type, dup) < 0) { if (ERRNO_IS_NOT_SUPPORTED(errno)) /* No error if filesystem doesn't support ACLs. Return negative. */ return -errno; @@ -1440,7 +1524,6 @@ static int path_set_acls( } static int parse_attribute_from_arg(Item *item) { - static const struct { char character; unsigned value; @@ -1501,8 +1584,7 @@ static int parse_attribute_from_arg(Item *item) { if (i >= ELEMENTSOF(attributes)) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), - "Unknown file attribute '%c' on '%s'.", - *p, item->path); + "Unknown file attribute '%c' on '%s'.", *p, item->path); v = attributes[i].value; @@ -1531,7 +1613,6 @@ static int fd_set_attribute( const struct stat *st, CreationMode creation) { - _cleanup_close_ int procfs_fd = -EBADF; struct stat stbuf; unsigned f; int r; @@ -1563,20 +1644,29 @@ static int fd_set_attribute( if (!S_ISDIR(st->st_mode)) f &= ~FS_DIRSYNC_FL; - procfs_fd = fd_reopen(fd, O_RDONLY|O_CLOEXEC|O_NOATIME); - if (procfs_fd < 0) - return log_error_errno(procfs_fd, "Failed to re-open '%s': %m", path); - - unsigned previous, current; - r = chattr_full(procfs_fd, NULL, f, item->attribute_mask, &previous, ¤t, CHATTR_FALLBACK_BITWISE); - if (r == -ENOANO) - log_warning("Cannot set file attributes for '%s', maybe due to incompatibility in specified attributes, " - "previous=0x%08x, current=0x%08x, expected=0x%08x, ignoring.", - path, previous, current, (previous & ~item->attribute_mask) | (f & item->attribute_mask)); - else if (r < 0) - log_full_errno(ERRNO_IS_NOT_SUPPORTED(r) ? LOG_DEBUG : LOG_WARNING, r, - "Cannot set file attributes for '%s', value=0x%08x, mask=0x%08x, ignoring: %m", - path, item->attribute_value, item->attribute_mask); + log_action("Would try to set", "Trying to set", + "%s file attributes 0x%08x on %s", + f & item->attribute_mask, + path); + + if (!arg_dry_run) { + _cleanup_close_ int procfs_fd = -EBADF; + + procfs_fd = fd_reopen(fd, O_RDONLY|O_CLOEXEC|O_NOATIME); + if (procfs_fd < 0) + return log_error_errno(procfs_fd, "Failed to reopen '%s': %m", path); + + unsigned previous, current; + r = chattr_full(procfs_fd, NULL, f, item->attribute_mask, &previous, ¤t, CHATTR_FALLBACK_BITWISE); + if (r == -ENOANO) + log_warning("Cannot set file attributes for '%s', maybe due to incompatibility in specified attributes, " + "previous=0x%08x, current=0x%08x, expected=0x%08x, ignoring.", + path, previous, current, (previous & ~item->attribute_mask) | (f & item->attribute_mask)); + else if (r < 0) + log_full_errno(ERRNO_IS_NOT_SUPPORTED(r) ? LOG_DEBUG : LOG_WARNING, r, + "Cannot set file attributes for '%s', value=0x%08x, mask=0x%08x, ignoring: %m", + path, item->attribute_value, item->attribute_mask); + } return 0; } @@ -1614,11 +1704,13 @@ static int write_argument_data(Item *i, int fd, const char *path) { assert(item_binary_argument(i)); - log_debug("Writing to \"%s\".", path); + log_action("Would write", "Writing", "%s to \"%s\"", path); - r = loop_write(fd, item_binary_argument(i), item_binary_argument_size(i)); - if (r < 0) - return log_error_errno(r, "Failed to write file \"%s\": %m", path); + if (!arg_dry_run) { + r = loop_write(fd, item_binary_argument(i), item_binary_argument_size(i)); + if (r < 0) + return log_error_errno(r, "Failed to write file \"%s\": %m", path); + } return 0; } @@ -1645,10 +1737,9 @@ static int write_one_file(Context *c, Item *i, const char *path, CreationMode cr if (dir_fd < 0) return dir_fd; - /* Follows symlinks */ - fd = openat(dir_fd, bn, - O_NONBLOCK|O_CLOEXEC|O_WRONLY|O_NOCTTY|(i->append_or_force ? O_APPEND : 0), - i->mode); + /* Follow symlinks. Open with O_PATH in dry-run mode to make sure we don't use the path inadvertently. */ + int flags = O_NONBLOCK | O_CLOEXEC | O_WRONLY | O_NOCTTY | i->append_or_force * O_APPEND | arg_dry_run * O_PATH; + fd = openat(dir_fd, bn, flags, i->mode); if (fd < 0) { if (errno == ENOENT) { log_debug_errno(errno, "Not writing missing file \"%s\": %m", path); @@ -1694,6 +1785,14 @@ static int create_file( if (r == O_DIRECTORY) return log_error_errno(SYNTHETIC_ERRNO(EISDIR), "Cannot open path '%s' for writing, is a directory.", path); + if (arg_dry_run) { + log_info("Would create file %s", path); + return 0; + + /* The opening of the directory below would fail if it doesn't exist, + * so log and exit before even trying to do that. */ + } + /* Validate the path and keep the fd on the directory for opening the file so we're sure that it * can't be changed behind our back. */ dir_fd = path_open_parent_safe(path, i->allow_failure); @@ -1717,7 +1816,7 @@ static int create_file( * fd_set_perms() report the error if the perms need to be modified. */ fd = openat(dir_fd, bn, O_NOFOLLOW|O_CLOEXEC|O_PATH, i->mode); if (fd < 0) - return log_error_errno(errno, "Failed to re-open file %s: %m", path); + return log_error_errno(errno, "Failed to reopen file %s: %m", path); if (fstat(fd, &stbuf) < 0) return log_error_errno(errno, "stat(%s) failed: %m", path); @@ -1773,6 +1872,11 @@ static int truncate_file( if (dir_fd < 0) return dir_fd; + if (arg_dry_run) { + log_info("Would truncate %s", path); + return 0; + } + creation = CREATION_EXISTING; fd = RET_NERRNO(openat(dir_fd, bn, O_NOFOLLOW|O_NONBLOCK|O_CLOEXEC|O_WRONLY|O_NOCTTY, i->mode)); if (fd == -ENOENT) { @@ -1800,7 +1904,7 @@ static int truncate_file( "Cannot create file %s on a read-only file system.", path); - return log_error_errno(errno, "Failed to re-open file %s: %m", path); + return log_error_errno(errno, "Failed to reopen file %s: %m", path); } erofs = true; @@ -1840,7 +1944,9 @@ static int copy_files(Context *c, Item *i) { struct stat st, a; int r; - log_debug("Copying tree \"%s\" to \"%s\".", i->argument, i->path); + log_action("Would copy", "Copying", "%s tree \"%s\" to \"%s\"", i->argument, i->path); + if (arg_dry_run) + return 0; r = path_extract_filename(i->path, &bn); if (r < 0) @@ -1919,18 +2025,27 @@ static int create_directory_or_subvolume( * heavy-weight). Thus, chroot() environments and suchlike will get a full brtfs * subvolume set up below their tree only if they specifically set up a btrfs * subvolume for the root dir too. */ - subvol = false; else { - WITH_UMASK((~mode) & 0777) - r = btrfs_subvol_make(pfd, bn); + log_action("Would create", "Creating", "%s btrfs subvolume %s", path); + if (!arg_dry_run) + WITH_UMASK((~mode) & 0777) + r = btrfs_subvol_make(pfd, bn); + else + r = 0; } } else r = 0; - if (!subvol || ERRNO_IS_NEG_NOT_SUPPORTED(r)) - WITH_UMASK(0000) - r = mkdirat_label(pfd, bn, mode); + if (!subvol || ERRNO_IS_NEG_NOT_SUPPORTED(r)) { + log_action("Would create", "Creating", "%s directory \"%s\"", path); + if (!arg_dry_run) + WITH_UMASK(0000) + r = mkdirat_label(pfd, bn, mode); + } + + if (arg_dry_run) + return 0; creation = r >= 0 ? CREATION_NORMAL : CREATION_EXISTING; @@ -1979,6 +2094,11 @@ static int create_directory( assert(i); assert(IN_SET(i->type, CREATE_DIRECTORY, TRUNCATE_DIRECTORY)); + if (arg_dry_run) { + log_info("Would create directory %s", path); + return 0; + } + fd = create_directory_or_subvolume(path, i->mode, /* subvol= */ false, i->allow_failure, &st, &creation); if (fd == -EEXIST) return 0; @@ -2002,6 +2122,11 @@ static int create_subvolume( assert(i); assert(IN_SET(i->type, CREATE_SUBVOLUME, CREATE_SUBVOLUME_NEW_QUOTA, CREATE_SUBVOLUME_INHERIT_QUOTA)); + if (arg_dry_run) { + log_info("Would create subvolume %s", path); + return 0; + } + fd = create_directory_or_subvolume(path, i->mode, /* subvol = */ true, i->allow_failure, &st, &creation); if (fd == -EEXIST) return 0; @@ -2088,7 +2213,13 @@ static int create_device( if (r < 0) return log_error_errno(r, "Failed to extract filename from path '%s': %m", i->path); if (r == O_DIRECTORY) - return log_error_errno(SYNTHETIC_ERRNO(EISDIR), "Cannot open path '%s' for creating device node, is a directory.", i->path); + return log_error_errno(SYNTHETIC_ERRNO(EISDIR), + "Cannot open path '%s' for creating device node, is a directory.", i->path); + + if (arg_dry_run) { + log_info("Would create device node %s", i->path); + return 0; + } /* Validate the path and use the returned directory fd for copying the target so we're sure that the * path can't be changed behind our back. */ @@ -2155,7 +2286,8 @@ static int create_device( return log_error_errno(errno, "Failed to fstat(%s): %m", i->path); if (((st.st_mode ^ file_type) & S_IFMT) != 0) - return log_error_errno(SYNTHETIC_ERRNO(EBADF), "Device node we just created is not a device node, refusing."); + return log_error_errno(SYNTHETIC_ERRNO(EBADF), + "Device node we just created is not a device node, refusing."); creation = CREATION_FORCE; } else { @@ -2193,7 +2325,13 @@ static int create_fifo(Context *c, Item *i) { if (r < 0) return log_error_errno(r, "Failed to extract filename from path '%s': %m", i->path); if (r == O_DIRECTORY) - return log_error_errno(SYNTHETIC_ERRNO(EISDIR), "Cannot open path '%s' for creating FIFO, is a directory.", i->path); + return log_error_errno(SYNTHETIC_ERRNO(EISDIR), + "Cannot open path '%s' for creating FIFO, is a directory.", i->path); + + if (arg_dry_run) { + log_info("Would create fifo %s", i->path); + return 0; + } pfd = path_open_parent_safe(i->path, i->allow_failure); if (pfd < 0) @@ -2250,7 +2388,8 @@ static int create_fifo(Context *c, Item *i) { return log_error_errno(errno, "Failed to fstat(%s): %m", i->path); if (!S_ISFIFO(st.st_mode)) - return log_error_errno(SYNTHETIC_ERRNO(EBADF), "FIFO inode we just created is not a FIFO, refusing."); + return log_error_errno(SYNTHETIC_ERRNO(EBADF), + "FIFO inode we just created is not a FIFO, refusing."); creation = CREATION_FORCE; } else { @@ -2279,7 +2418,13 @@ static int create_symlink(Context *c, Item *i) { if (r < 0) return log_error_errno(r, "Failed to extract filename from path '%s': %m", i->path); if (r == O_DIRECTORY) - return log_error_errno(SYNTHETIC_ERRNO(EISDIR), "Cannot open path '%s' for creating FIFO, is a directory.", i->path); + return log_error_errno(SYNTHETIC_ERRNO(EISDIR), + "Cannot open path '%s' for creating FIFO, is a directory.", i->path); + + if (arg_dry_run) { + log_info("Would create symlink %s -> %s", i->path, i->argument); + return 0; + } pfd = path_open_parent_safe(i->path, i->allow_failure); if (pfd < 0) @@ -2366,12 +2511,13 @@ static int item_do( fdaction_t action) { struct stat st; - int r = 0, q; + int r; assert(c); assert(i); - assert(path); assert(fd >= 0); + assert(path); + assert(action); if (fstat(fd, &st) < 0) { r = log_error_errno(errno, "fstat() on file failed: %m"); @@ -2388,36 +2534,35 @@ static int item_do( * reading the directory content. */ d = opendir(FORMAT_PROC_FD_PATH(fd)); if (!d) { - log_error_errno(errno, "Failed to opendir() '%s': %m", FORMAT_PROC_FD_PATH(fd)); - if (r == 0) - r = -errno; + RET_GATHER(r, log_error_errno(errno, "Failed to opendir() '%s': %m", FORMAT_PROC_FD_PATH(fd))); goto finish; } - FOREACH_DIRENT_ALL(de, d, q = -errno; goto finish) { - int de_fd; + FOREACH_DIRENT_ALL(de, d, RET_GATHER(r, -errno); goto finish) { + _cleanup_close_ int de_fd = -EBADF; + _cleanup_free_ char *de_path = NULL; if (dot_or_dot_dot(de->d_name)) continue; de_fd = openat(fd, de->d_name, O_NOFOLLOW|O_CLOEXEC|O_PATH); - if (de_fd < 0) - q = log_error_errno(errno, "Failed to open() file '%s': %m", de->d_name); - else { - _cleanup_free_ char *de_path = NULL; - - de_path = path_join(path, de->d_name); - if (!de_path) - q = log_oom(); - else - /* Pass ownership of dirent fd over */ - q = item_do(c, i, de_fd, de_path, CREATION_EXISTING, action); + if (de_fd < 0) { + if (errno != ENOENT) + RET_GATHER(r, log_error_errno(errno, "Failed to open file '%s': %m", de->d_name)); + continue; + } + + de_path = path_join(path, de->d_name); + if (!de_path) { + r = log_oom(); + goto finish; } - if (q < 0 && r == 0) - r = q; + /* Pass ownership of dirent fd over */ + RET_GATHER(r, item_do(c, i, TAKE_FD(de_fd), de_path, CREATION_EXISTING, action)); } } + finish: safe_close(fd); return r; @@ -2427,21 +2572,22 @@ static int glob_item(Context *c, Item *i, action_t action) { _cleanup_globfree_ glob_t g = { .gl_opendir = (void *(*)(const char *)) opendir_nomod, }; - int r = 0, k; + int r; assert(c); assert(i); + assert(action); - k = safe_glob(i->path, GLOB_NOSORT|GLOB_BRACE, &g); - if (k < 0 && k != -ENOENT) - return log_error_errno(k, "glob(%s) failed: %m", i->path); + r = safe_glob(i->path, GLOB_NOSORT|GLOB_BRACE, &g); + if (r == -ENOENT) + return 0; + if (r < 0) + return log_error_errno(r, "Failed to glob '%s': %m", i->path); - STRV_FOREACH(fn, g.gl_pathv) { + r = 0; + STRV_FOREACH(fn, g.gl_pathv) /* We pass CREATION_EXISTING here, since if we are globbing for it, it always has to exist */ - k = action(c, i, *fn, CREATION_EXISTING); - if (k < 0 && r == 0) - r = k; - } + RET_GATHER(r, action(c, i, *fn, CREATION_EXISTING)); return r; } @@ -2454,34 +2600,33 @@ static int glob_item_recursively( _cleanup_globfree_ glob_t g = { .gl_opendir = (void *(*)(const char *)) opendir_nomod, }; - int r = 0, k; + int r; - k = safe_glob(i->path, GLOB_NOSORT|GLOB_BRACE, &g); - if (k < 0 && k != -ENOENT) - return log_error_errno(k, "glob(%s) failed: %m", i->path); + assert(c); + assert(i); + assert(action); + r = safe_glob(i->path, GLOB_NOSORT|GLOB_BRACE, &g); + if (r == -ENOENT) + return 0; + if (r < 0) + return log_error_errno(r, "Failed to glob '%s': %m", i->path); + + r = 0; STRV_FOREACH(fn, g.gl_pathv) { _cleanup_close_ int fd = -EBADF; - /* Make sure we won't trigger/follow file object (such as - * device nodes, automounts, ...) pointed out by 'fn' with - * O_PATH. Note, when O_PATH is used, flags other than + /* Make sure we won't trigger/follow file object (such as device nodes, automounts, ...) + * pointed out by 'fn' with O_PATH. Note, when O_PATH is used, flags other than * O_CLOEXEC, O_DIRECTORY, and O_NOFOLLOW are ignored. */ fd = open(*fn, O_CLOEXEC|O_NOFOLLOW|O_PATH); if (fd < 0) { - log_error_errno(errno, "Opening '%s' failed: %m", *fn); - if (r == 0) - r = -errno; + RET_GATHER(r, log_error_errno(errno, "Failed to open '%s': %m", *fn)); continue; } - k = item_do(c, i, fd, *fn, CREATION_EXISTING, action); - if (k < 0 && r == 0) - r = k; - - /* we passed fd ownership to the previous call */ - fd = -EBADF; + RET_GATHER(r, item_do(c, i, TAKE_FD(fd), *fn, CREATION_EXISTING, action)); } return r; @@ -2503,65 +2648,71 @@ static int rm_if_wrong_type_safe( assert(!follow_links || parent_st); assert((flags & ~AT_SYMLINK_NOFOLLOW) == 0); + if (mode == 0) + return 0; + if (!filename_is_valid(name)) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "\"%s\" is not a valid filename.", name); r = fstatat_harder(parent_fd, name, &st, flags, REMOVE_CHMOD | REMOVE_CHMOD_RESTORE); if (r < 0) { (void) fd_get_path(parent_fd, &parent_name); - return log_full_errno(r == -ENOENT? LOG_DEBUG : LOG_ERR, r, - "Failed to stat \"%s\" at \"%s\": %m", name, strna(parent_name)); + return log_full_errno(r == -ENOENT ? LOG_DEBUG : LOG_ERR, r, + "Failed to stat \"%s/%s\": %m", parent_name ?: "...", name); } /* Fail before removing anything if this is an unsafe transition. */ if (follow_links && unsafe_transition(parent_st, &st)) { (void) fd_get_path(parent_fd, &parent_name); return log_error_errno(SYNTHETIC_ERRNO(ENOLINK), - "Unsafe transition from \"%s\" to \"%s\".", parent_name, name); + "Unsafe transition from \"%s\" to \"%s\".", parent_name ?: "...", name); } if ((st.st_mode & S_IFMT) == mode) return 0; (void) fd_get_path(parent_fd, &parent_name); - log_notice("Wrong file type 0o%o; rm -rf \"%s/%s\"", st.st_mode & S_IFMT, strna(parent_name), name); + log_notice("Wrong file type 0o%o; rm -rf \"%s/%s\"", st.st_mode & S_IFMT, parent_name ?: "...", name); /* If the target of the symlink was the wrong type, the link needs to be removed instead of the * target, so make sure it is identified as a link and not a directory. */ if (follow_links) { r = fstatat_harder(parent_fd, name, &st, AT_SYMLINK_NOFOLLOW, REMOVE_CHMOD | REMOVE_CHMOD_RESTORE); if (r < 0) - return log_error_errno(r, "Failed to stat \"%s\" at \"%s\": %m", name, strna(parent_name)); + return log_error_errno(r, "Failed to stat \"%s/%s\": %m", parent_name ?: "...", name); } /* Do not remove mount points. */ r = fd_is_mount_point(parent_fd, name, follow_links ? AT_SYMLINK_FOLLOW : 0); if (r < 0) - (void) log_warning_errno(r, "Failed to check if \"%s/%s\" is a mount point: %m; Continuing", - strna(parent_name), name); + (void) log_warning_errno(r, "Failed to check if \"%s/%s\" is a mount point: %m; continuing.", + parent_name ?: "...", name); else if (r > 0) return log_error_errno(SYNTHETIC_ERRNO(EBUSY), - "Not removing \"%s/%s\" because it is a mount point.", strna(parent_name), name); + "Not removing \"%s/%s\" because it is a mount point.", parent_name ?: "...", name); - if ((st.st_mode & S_IFMT) == S_IFDIR) { - _cleanup_close_ int child_fd = -EBADF; + log_action("Would remove", "Removing", "%s %s/%s", parent_name ?: "...", name); + if (!arg_dry_run) { + if ((st.st_mode & S_IFMT) == S_IFDIR) { + _cleanup_close_ int child_fd = -EBADF; - child_fd = openat(parent_fd, name, O_NOCTTY | O_CLOEXEC | O_DIRECTORY); - if (child_fd < 0) - return log_error_errno(errno, "Failed to open \"%s\" at \"%s\": %m", name, strna(parent_name)); + child_fd = openat(parent_fd, name, O_NOCTTY | O_CLOEXEC | O_DIRECTORY); + if (child_fd < 0) + return log_error_errno(errno, "Failed to open \"%s/%s\": %m", parent_name ?: "...", name); - r = rm_rf_children(TAKE_FD(child_fd), REMOVE_ROOT|REMOVE_SUBVOLUME|REMOVE_PHYSICAL, &st); - if (r < 0) - return log_error_errno(r, "Failed to remove contents of \"%s\" at \"%s\": %m", name, strna(parent_name)); + r = rm_rf_children(TAKE_FD(child_fd), REMOVE_ROOT|REMOVE_SUBVOLUME|REMOVE_PHYSICAL, &st); + if (r < 0) + return log_error_errno(r, "Failed to remove contents of \"%s/%s\": %m", parent_name ?: "...", name); - r = unlinkat_harder(parent_fd, name, AT_REMOVEDIR, REMOVE_CHMOD | REMOVE_CHMOD_RESTORE); - } else - r = unlinkat_harder(parent_fd, name, 0, REMOVE_CHMOD | REMOVE_CHMOD_RESTORE); - if (r < 0) - return log_error_errno(r, "Failed to remove \"%s\" at \"%s\": %m", name, strna(parent_name)); + r = unlinkat_harder(parent_fd, name, AT_REMOVEDIR, REMOVE_CHMOD | REMOVE_CHMOD_RESTORE); + } else + r = unlinkat_harder(parent_fd, name, 0, REMOVE_CHMOD | REMOVE_CHMOD_RESTORE); + if (r < 0) + return log_error_errno(r, "Failed to remove \"%s/%s\": %m", parent_name ?: "...", name); + } - /* This is covered by the log_notice "Wrong file type..." It is logged earlier because it gives - * context to other error messages that might follow. */ + /* This is covered by the log_notice "Wrong file type...". + * It is logged earlier because it gives context to other error messages that might follow. */ return -ENOENT; } @@ -2579,7 +2730,7 @@ static int mkdir_parents_rm_if_wrong_type(mode_t child_mode, const char *path) { if (!is_path(path)) /* rm_if_wrong_type_safe already logs errors. */ - return child_mode != 0 ? rm_if_wrong_type_safe(child_mode, AT_FDCWD, NULL, path, AT_SYMLINK_NOFOLLOW) : 0; + return rm_if_wrong_type_safe(child_mode, AT_FDCWD, NULL, path, AT_SYMLINK_NOFOLLOW); if (child_mode != 0 && endswith(path, "/")) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), @@ -2609,20 +2760,22 @@ static int mkdir_parents_rm_if_wrong_type(mode_t child_mode, const char *path) { /* Is this the last component? If so, then check the type */ if (*e == 0) - return child_mode != 0 ? rm_if_wrong_type_safe(child_mode, parent_fd, &parent_st, t, AT_SYMLINK_NOFOLLOW) : 0; + return rm_if_wrong_type_safe(child_mode, parent_fd, &parent_st, t, AT_SYMLINK_NOFOLLOW); r = rm_if_wrong_type_safe(S_IFDIR, parent_fd, &parent_st, t, 0); /* Remove dangling symlinks. */ if (r == -ENOENT) r = rm_if_wrong_type_safe(S_IFDIR, parent_fd, &parent_st, t, AT_SYMLINK_NOFOLLOW); if (r == -ENOENT) { - WITH_UMASK(0000) - r = mkdirat_label(parent_fd, t, 0755); - if (r < 0) { - _cleanup_free_ char *parent_name = NULL; - - (void) fd_get_path(parent_fd, &parent_name); - return log_error_errno(r, "Failed to mkdir \"%s\" at \"%s\": %m", t, strnull(parent_name)); + if (!arg_dry_run) { + WITH_UMASK(0000) + r = mkdirat_label(parent_fd, t, 0755); + if (r < 0) { + _cleanup_free_ char *parent_name = NULL; + + (void) fd_get_path(parent_fd, &parent_name); + return log_error_errno(r, "Failed to mkdir \"%s\" at \"%s\": %m", t, strnull(parent_name)); + } } } else if (r < 0) /* rm_if_wrong_type_safe already logs errors. */ @@ -2649,13 +2802,15 @@ static int mkdir_parents_rm_if_wrong_type(mode_t child_mode, const char *path) { static int mkdir_parents_item(Item *i, mode_t child_mode) { int r; + if (i->try_replace) { r = mkdir_parents_rm_if_wrong_type(child_mode, i->path); if (r < 0 && r != -ENOENT) return r; } else WITH_UMASK(0000) - (void) mkdir_parents_label(i->path, 0755); + if (!arg_dry_run) + (void) mkdir_parents_label(i->path, 0755); return 0; } @@ -2830,44 +2985,100 @@ static int create_item(Context *c, Item *i) { return 0; } -static int remove_item_instance( +static int remove_recursive( Context *c, Item *i, const char *instance, - CreationMode creation) { + bool remove_instance) { + _cleanup_closedir_ DIR *d = NULL; + STRUCT_STATX_DEFINE(sx); + bool mountpoint; int r; + r = opendir_and_stat(instance, &d, &sx, &mountpoint); + if (r < 0) + return r; + if (r == 0) { + if (remove_instance) { + log_action("Would remove", "Removing", "%s file \"%s\".", instance); + if (!arg_dry_run && + remove(instance) < 0 && + errno != ENOENT) + return log_error_errno(errno, "rm %s: %m", instance); + } + return 0; + } + + r = dir_cleanup(c, i, instance, d, + /* self_atime_nsec= */ NSEC_INFINITY, + /* self_mtime_nsec= */ NSEC_INFINITY, + /* cutoff_nsec= */ NSEC_INFINITY, + sx.stx_dev_major, sx.stx_dev_minor, + mountpoint, + MAX_DEPTH, + /* keep_this_level= */ false, + /* age_by_file= */ 0, + /* age_by_dir= */ 0); + if (r < 0) + return r; + + if (remove_instance) { + log_debug("Removing directory \"%s\".", instance); + r = RET_NERRNO(rmdir(instance)); + if (r < 0 && !IN_SET(r, -ENOENT, -ENOTEMPTY)) + return log_error_errno(r, "Failed to remove %s: %m", instance); + } + return 0; +} + +static int purge_item_instance(Context *c, Item *i, const char *instance, CreationMode creation) { + return remove_recursive(c, i, instance, /* remove_instance= */ true); +} + +static int purge_item(Context *c, Item *i) { + assert(i); + + if (!needs_purge(i->type)) + return 0; + + log_debug("Running purge action for entry %c %s", (char) i->type, i->path); + + if (needs_glob(i->type)) + return glob_item(c, i, purge_item_instance); + + return purge_item_instance(c, i, i->path, CREATION_EXISTING); +} + +static int remove_item_instance( + Context *c, + Item *i, + const char *instance, + CreationMode creation) { + assert(c); assert(i); switch (i->type) { case REMOVE_PATH: - if (remove(instance) < 0 && errno != ENOENT) - return log_error_errno(errno, "rm(%s): %m", instance); + log_action("Would remove", "Removing", "%s \"%s\".", instance); + if (!arg_dry_run && + remove(instance) < 0 && + errno != ENOENT) + return log_error_errno(errno, "rm %s: %m", instance); - break; + return 0; case RECURSIVE_REMOVE_PATH: - /* FIXME: we probably should use dir_cleanup() here instead of rm_rf() so that 'x' is honoured. */ - log_debug("rm -rf \"%s\"", instance); - r = rm_rf(instance, REMOVE_ROOT|REMOVE_SUBVOLUME|REMOVE_PHYSICAL); - if (r < 0 && r != -ENOENT) - return log_error_errno(r, "rm_rf(%s): %m", instance); - - break; + return remove_recursive(c, i, instance, /* remove_instance= */ true); default: assert_not_reached(); } - - return 0; } static int remove_item(Context *c, Item *i) { - int r; - assert(c); assert(i); @@ -2876,13 +3087,7 @@ static int remove_item(Context *c, Item *i) { switch (i->type) { case TRUNCATE_DIRECTORY: - /* FIXME: we probably should use dir_cleanup() here instead of rm_rf() so that 'x' is honoured. */ - log_debug("rm -rf \"%s\"", i->path); - r = rm_rf(i->path, REMOVE_PHYSICAL); - if (r < 0 && r != -ENOENT) - return log_error_errno(r, "rm_rf(%s): %m", i->path); - - return 0; + return remove_recursive(c, i, i->path, /* remove_instance= */ false); case REMOVE_PATH: case RECURSIVE_REMOVE_PATH: @@ -2916,49 +3121,25 @@ static int clean_item_instance( const char* instance, CreationMode creation) { - _cleanup_closedir_ DIR *d = NULL; - STRUCT_STATX_DEFINE(sx); - int mountpoint, r; - usec_t cutoff, n; - assert(i); if (!i->age_set) return 0; - n = now(CLOCK_REALTIME); + usec_t n = now(CLOCK_REALTIME); if (n < i->age) return 0; - cutoff = n - i->age; - - d = opendir_nomod(instance); - if (!d) { - if (IN_SET(errno, ENOENT, ENOTDIR)) { - log_debug_errno(errno, "Directory \"%s\": %m", instance); - return 0; - } - - return log_error_errno(errno, "Failed to open directory %s: %m", instance); - } + usec_t cutoff = n - i->age; - r = statx_fallback(dirfd(d), "", AT_EMPTY_PATH, STATX_MODE|STATX_INO|STATX_ATIME|STATX_MTIME, &sx); - if (r < 0) - return log_error_errno(r, "statx(%s) failed: %m", instance); - - if (FLAGS_SET(sx.stx_attributes_mask, STATX_ATTR_MOUNT_ROOT)) - mountpoint = FLAGS_SET(sx.stx_attributes, STATX_ATTR_MOUNT_ROOT); - else { - struct stat ps; - - if (fstatat(dirfd(d), "..", &ps, AT_SYMLINK_NOFOLLOW) != 0) - return log_error_errno(errno, "stat(%s/..) failed: %m", i->path); + _cleanup_closedir_ DIR *d = NULL; + STRUCT_STATX_DEFINE(sx); + bool mountpoint; + int r; - mountpoint = - sx.stx_dev_major != major(ps.st_dev) || - sx.stx_dev_minor != minor(ps.st_dev) || - sx.stx_ino != ps.st_ino; - } + r = opendir_and_stat(instance, &d, &sx, &mountpoint); + if (r <= 0) + return r; if (DEBUG_LOGGING) { _cleanup_free_ char *ab_f = NULL, *ab_d = NULL; @@ -2979,10 +3160,11 @@ static int clean_item_instance( } return dir_cleanup(c, i, instance, d, - load_statx_timestamp_nsec(&sx.stx_atime), - load_statx_timestamp_nsec(&sx.stx_mtime), + statx_timestamp_load_nsec(&sx.stx_atime), + statx_timestamp_load_nsec(&sx.stx_mtime), cutoff * NSEC_PER_USEC, - sx.stx_dev_major, sx.stx_dev_minor, mountpoint, + sx.stx_dev_major, sx.stx_dev_minor, + mountpoint, MAX_DEPTH, i->keep_first_level, i->age_by_file, i->age_by_dir); } @@ -2996,16 +3178,16 @@ static int clean_item(Context *c, Item *i) { switch (i->type) { case CREATE_DIRECTORY: + case TRUNCATE_DIRECTORY: case CREATE_SUBVOLUME: case CREATE_SUBVOLUME_INHERIT_QUOTA: case CREATE_SUBVOLUME_NEW_QUOTA: - case TRUNCATE_DIRECTORY: - case IGNORE_PATH: case COPY_FILES: clean_item_instance(c, i, i->path, CREATION_EXISTING); return 0; case EMPTY_DIRECTORY: + case IGNORE_PATH: case IGNORE_DIRECTORY_PATH: return glob_item(c, i, clean_item_instance); @@ -3022,7 +3204,7 @@ static int process_item( OperationMask todo; _cleanup_free_ char *_path = NULL; const char *path; - int r, q, p; + int r; assert(c); assert(i); @@ -3058,12 +3240,11 @@ static int process_item( if (i->allow_failure) r = 0; - q = FLAGS_SET(operation, OPERATION_REMOVE) ? remove_item(c, i) : 0; - p = FLAGS_SET(operation, OPERATION_CLEAN) ? clean_item(c, i) : 0; + RET_GATHER(r, FLAGS_SET(operation, OPERATION_REMOVE) ? remove_item(c, i) : 0); + RET_GATHER(r, FLAGS_SET(operation, OPERATION_CLEAN) ? clean_item(c, i) : 0); + RET_GATHER(r, FLAGS_SET(operation, OPERATION_PURGE) ? purge_item(c, i) : 0); - return r < 0 ? r : - q < 0 ? q : - p; + return r; } static int process_item_array( @@ -3072,7 +3253,6 @@ static int process_item_array( OperationMask operation) { int r = 0; - size_t n; assert(c); assert(array); @@ -3082,25 +3262,15 @@ static int process_item_array( r = process_item_array(c, array->parent, operation & OPERATION_CREATE); /* Clean up all children first */ - if ((operation & (OPERATION_REMOVE|OPERATION_CLEAN)) && !set_isempty(array->children)) { + if ((operation & (OPERATION_REMOVE|OPERATION_CLEAN|OPERATION_PURGE)) && !set_isempty(array->children)) { ItemArray *cc; - SET_FOREACH(cc, array->children) { - int k; - - k = process_item_array(c, cc, operation & (OPERATION_REMOVE|OPERATION_CLEAN)); - if (k < 0 && r == 0) - r = k; - } + SET_FOREACH(cc, array->children) + RET_GATHER(r, process_item_array(c, cc, operation & (OPERATION_REMOVE|OPERATION_CLEAN|OPERATION_PURGE))); } - for (n = 0; n < array->n_items; n++) { - int k; - - k = process_item(c, array->items + n, operation); - if (k < 0 && r == 0) - r = k; - } + FOREACH_ARRAY(item, array->items, array->n_items) + RET_GATHER(r, process_item(c, item, operation)); return r; } @@ -3125,13 +3295,11 @@ static void item_free_contents(Item *i) { } static ItemArray* item_array_free(ItemArray *a) { - size_t n; - if (!a) return NULL; - for (n = 0; n < a->n_items; n++) - item_free_contents(a->items + n); + FOREACH_ARRAY(item, a->items, a->n_items) + item_free_contents(item); set_free(a->children); free(a->items); @@ -3261,27 +3429,30 @@ static int patch_var_run(const char *fname, unsigned line, char **path) { assert(path); assert(*path); - /* Optionally rewrites lines referencing /var/run/, to use /run/ instead. Why bother? tmpfiles merges lines in - * some cases and detects conflicts in others. If files/directories are specified through two equivalent lines - * this is problematic as neither case will be detected. Ideally we'd detect these cases by resolving symlinks - * early, but that's precisely not what we can do here as this code very likely is running very early on, at a - * time where the paths in question are not available yet, or even more importantly, our own tmpfiles rules - * might create the paths that are intermediary to the listed paths. We can't really cover the generic case, - * but the least we can do is cover the specific case of /var/run vs. /run, as /var/run is a legacy name for - * /run only, and we explicitly document that and require that on systemd systems the former is a symlink to - * the latter. Moreover files below this path are by far the primary use case for tmpfiles.d/. */ + /* Optionally rewrites lines referencing /var/run/, to use /run/ instead. Why bother? tmpfiles merges + * lines in some cases and detects conflicts in others. If files/directories are specified through + * two equivalent lines this is problematic as neither case will be detected. Ideally we'd detect + * these cases by resolving symlinks early, but that's precisely not what we can do here as this code + * very likely is running very early on, at a time where the paths in question are not available yet, + * or even more importantly, our own tmpfiles rules might create the paths that are intermediary to + * the listed paths. We can't really cover the generic case, but the least we can do is cover the + * specific case of /var/run vs. /run, as /var/run is a legacy name for /run only, and we explicitly + * document that and require that on systemd systems the former is a symlink to the latter. Moreover + * files below this path are by far the primary use case for tmpfiles.d/. */ k = path_startswith(*path, "/var/run/"); - if (isempty(k)) /* Don't complain about other paths than /var/run, and not about /var/run itself either. */ + if (isempty(k)) /* Don't complain about paths other than under /var/run, + * and not about /var/run itself either. */ return 0; n = path_join("/run", k); if (!n) return log_oom(); - /* Also log about this briefly. We do so at LOG_NOTICE level, as we fixed up the situation automatically, hence - * there's no immediate need for action by the user. However, in the interest of making things less confusing - * to the user, let's still inform the user that these snippets should really be updated. */ + /* Also log about this briefly. We do so at LOG_NOTICE level, as we fixed up the situation + * automatically, hence there's no immediate need for action by the user. However, in the interest of + * making things less confusing to the user, let's still inform the user that these snippets should + * really be updated. */ log_syntax(NULL, LOG_NOTICE, fname, line, 0, "Line references path below legacy directory /var/run/, updating %s → %s; please update the tmpfiles.d/ drop-in file accordingly.", *path, n); @@ -3394,13 +3565,10 @@ static int parse_age_by_from_arg(const char *age_by_str, Item *item) { } static bool is_duplicated_item(ItemArray *existing, const Item *i) { - assert(existing); assert(i); - for (size_t n = 0; n < existing->n_items; n++) { - const Item *e = existing->items + n; - + FOREACH_ARRAY(e, existing->items, existing->n_items) { if (item_compatible(e, i)) continue; @@ -3414,14 +3582,13 @@ static bool is_duplicated_item(ItemArray *existing, const Item *i) { } static int parse_line( - Context *c, const char *fname, unsigned line, const char *buffer, bool *invalid_config, - Hashmap **uid_cache, - Hashmap **gid_cache) { + void *context) { + Context *c = ASSERT_PTR(context); _cleanup_free_ char *action = NULL, *mode = NULL, *user = NULL, *group = NULL, *age = NULL, *path = NULL; _cleanup_(item_free_contents) Item i = { /* The "age-by" argument considers all file timestamp types by default. */ @@ -3430,11 +3597,10 @@ static int parse_line( }; ItemArray *existing; OrderedHashmap *h; - int r, pos; bool append_or_force = false, boot = false, allow_failure = false, try_replace = false, unbase64 = false, from_cred = false, missing_user_or_group = false; + int r; - assert(c); assert(fname); assert(line >= 1); assert(buffer); @@ -3472,8 +3638,7 @@ static int parse_line( &mode, &user, &group, - &age, - NULL); + &age); if (r < 0) { if (IN_SET(r, -EINVAL, -EBADSLT)) /* invalid quoting and such or an unknown specifier */ @@ -3492,10 +3657,11 @@ static int parse_line( if (isempty(action)) { *invalid_config = true; - return log_syntax(NULL, LOG_ERR, fname, line, SYNTHETIC_ERRNO(EBADMSG), "Command too short '%s'.", action); + return log_syntax(NULL, LOG_ERR, fname, line, SYNTHETIC_ERRNO(EBADMSG), + "Command too short '%s'.", action); } - for (pos = 1; action[pos]; pos++) { + for (int pos = 1; action[pos]; pos++) if (action[pos] == '!' && !boot) boot = true; else if (action[pos] == '+' && !append_or_force) @@ -3510,12 +3676,13 @@ static int parse_line( from_cred = true; else { *invalid_config = true; - return log_syntax(NULL, LOG_ERR, fname, line, SYNTHETIC_ERRNO(EBADMSG), "Unknown modifiers in command '%s'", action); + return log_syntax(NULL, LOG_ERR, fname, line, SYNTHETIC_ERRNO(EBADMSG), + "Unknown modifiers in command '%s'.", action); } - } if (boot && !arg_boot) { - log_syntax(NULL, LOG_DEBUG, fname, line, 0, "Ignoring entry %s \"%s\" because --boot is not specified.", action, path); + log_syntax(NULL, LOG_DEBUG, fname, line, 0, + "Ignoring entry %s \"%s\" because --boot is not specified.", action, path); return 0; } @@ -3530,7 +3697,8 @@ static int parse_line( if (r < 0) { if (IN_SET(r, -EINVAL, -EBADSLT)) *invalid_config = true; - return log_syntax(NULL, LOG_ERR, fname, line, r, "Failed to replace specifiers in '%s': %m", path); + return log_syntax(NULL, LOG_ERR, fname, line, r, + "Failed to replace specifiers in '%s': %m", path); } r = patch_var_run(fname, line, &i.path); @@ -3562,14 +3730,8 @@ static int parse_line( case RELABEL_PATH: case RECURSIVE_RELABEL_PATH: if (i.argument) - log_syntax(NULL, - LOG_WARNING, - fname, - line, - 0, - "%c lines don't take argument fields, ignoring.", - (char) i.type); - + log_syntax(NULL, LOG_WARNING, fname, line, 0, + "%c lines don't take argument fields, ignoring.", (char) i.type); break; case CREATE_FILE: @@ -3579,21 +3741,24 @@ static int parse_line( case CREATE_SYMLINK: if (unbase64) { *invalid_config = true; - return log_syntax(NULL, LOG_ERR, fname, line, SYNTHETIC_ERRNO(EBADMSG), "base64 decoding not supported for symlink targets."); + return log_syntax(NULL, LOG_ERR, fname, line, SYNTHETIC_ERRNO(EBADMSG), + "base64 decoding not supported for symlink targets."); } break; case WRITE_FILE: if (!i.argument) { *invalid_config = true; - return log_syntax(NULL, LOG_ERR, fname, line, SYNTHETIC_ERRNO(EBADMSG), "Write file requires argument."); + return log_syntax(NULL, LOG_ERR, fname, line, SYNTHETIC_ERRNO(EBADMSG), + "Write file requires argument."); } break; case COPY_FILES: if (unbase64) { *invalid_config = true; - return log_syntax(NULL, LOG_ERR, fname, line, SYNTHETIC_ERRNO(EBADMSG), "base64 decoding not supported for copy sources."); + return log_syntax(NULL, LOG_ERR, fname, line, SYNTHETIC_ERRNO(EBADMSG), + "base64 decoding not supported for copy sources."); } break; @@ -3601,18 +3766,21 @@ static int parse_line( case CREATE_BLOCK_DEVICE: if (unbase64) { *invalid_config = true; - return log_syntax(NULL, LOG_ERR, fname, line, SYNTHETIC_ERRNO(EBADMSG), "base64 decoding not supported for device node creation."); + return log_syntax(NULL, LOG_ERR, fname, line, SYNTHETIC_ERRNO(EBADMSG), + "base64 decoding not supported for device node creation."); } if (!i.argument) { *invalid_config = true; - return log_syntax(NULL, LOG_ERR, fname, line, SYNTHETIC_ERRNO(EBADMSG), "Device file requires argument."); + return log_syntax(NULL, LOG_ERR, fname, line, SYNTHETIC_ERRNO(EBADMSG), + "Device file requires argument."); } r = parse_devnum(i.argument, &i.major_minor); if (r < 0) { *invalid_config = true; - return log_syntax(NULL, LOG_ERR, fname, line, r, "Can't parse device file major/minor '%s'.", i.argument); + return log_syntax(NULL, LOG_ERR, fname, line, r, + "Can't parse device file major/minor '%s'.", i.argument); } break; @@ -3621,7 +3789,8 @@ static int parse_line( case RECURSIVE_SET_XATTR: if (unbase64) { *invalid_config = true; - return log_syntax(NULL, LOG_ERR, fname, line, SYNTHETIC_ERRNO(EBADMSG), "base64 decoding not supported for extended attributes."); + return log_syntax(NULL, LOG_ERR, fname, line, SYNTHETIC_ERRNO(EBADMSG), + "base64 decoding not supported for extended attributes."); } if (!i.argument) { *invalid_config = true; @@ -3637,7 +3806,8 @@ static int parse_line( case RECURSIVE_SET_ACL: if (unbase64) { *invalid_config = true; - return log_syntax(NULL, LOG_ERR, fname, line, SYNTHETIC_ERRNO(EBADMSG), "base64 decoding not supported for ACLs."); + return log_syntax(NULL, LOG_ERR, fname, line, SYNTHETIC_ERRNO(EBADMSG), + "base64 decoding not supported for ACLs."); } if (!i.argument) { *invalid_config = true; @@ -3653,7 +3823,8 @@ static int parse_line( case RECURSIVE_SET_ATTRIBUTE: if (unbase64) { *invalid_config = true; - return log_syntax(NULL, LOG_ERR, fname, line, SYNTHETIC_ERRNO(EBADMSG), "base64 decoding not supported for file attributes."); + return log_syntax(NULL, LOG_ERR, fname, line, SYNTHETIC_ERRNO(EBADMSG), + "base64 decoding not supported for file attributes."); } if (!i.argument) { *invalid_config = true; @@ -3684,7 +3855,8 @@ static int parse_line( if (r < 0) { if (IN_SET(r, -EINVAL, -EBADSLT)) *invalid_config = true; - return log_syntax(NULL, LOG_ERR, fname, line, r, "Failed to substitute specifiers in argument: %m"); + return log_syntax(NULL, LOG_ERR, fname, line, r, + "Failed to substitute specifiers in argument: %m"); } } @@ -3704,7 +3876,8 @@ static int parse_line( return log_oom(); } else if (!path_is_absolute(i.argument)) { *invalid_config = true; - return log_syntax(NULL, LOG_ERR, fname, line, SYNTHETIC_ERRNO(EBADMSG), "Source path '%s' is not absolute.", i.argument); + return log_syntax(NULL, LOG_ERR, fname, line, SYNTHETIC_ERRNO(EBADMSG), + "Source path '%s' is not absolute.", i.argument); } @@ -3721,7 +3894,8 @@ static int parse_line( if (laccess(i.argument, F_OK) == -ENOENT) { /* Silently skip over lines where the source file is missing. */ - log_syntax(NULL, LOG_DEBUG, fname, line, 0, "Copy source path '%s' does not exist, skipping line.", i.argument); + log_syntax(NULL, LOG_DEBUG, fname, line, 0, + "Copy source path '%s' does not exist, skipping line.", i.argument); return 0; } @@ -3733,9 +3907,11 @@ static int parse_line( if (from_cred) { if (!i.argument) - return log_syntax(NULL, LOG_ERR, fname, line, SYNTHETIC_ERRNO(EINVAL), "Reading from credential requested, but no credential name specified."); + return log_syntax(NULL, LOG_ERR, fname, line, SYNTHETIC_ERRNO(EINVAL), + "Reading from credential requested, but no credential name specified."); if (!credential_name_valid(i.argument)) - return log_syntax(NULL, LOG_ERR, fname, line, SYNTHETIC_ERRNO(EINVAL), "Credential name not valid: %s", i.argument); + return log_syntax(NULL, LOG_ERR, fname, line, SYNTHETIC_ERRNO(EINVAL), + "Credential name not valid: %s", i.argument); r = read_credential(i.argument, &i.binary_argument, &i.binary_argument_size); if (IN_SET(r, -ENXIO, -ENOENT)) { @@ -3753,7 +3929,8 @@ static int parse_line( _cleanup_free_ void *data = NULL; size_t data_size = 0; - r = unbase64mem(item_binary_argument(&i), item_binary_argument_size(&i), &data, &data_size); + r = unbase64mem_full(item_binary_argument(&i), item_binary_argument_size(&i), /* secure = */ false, + &data, &data_size); if (r < 0) return log_syntax(NULL, LOG_ERR, fname, line, r, "Failed to base64 decode specified argument '%s': %m", i.argument); @@ -3779,7 +3956,7 @@ static int parse_line( else u = user; - r = find_uid(u, &i.uid, uid_cache); + r = find_uid(u, &i.uid, &c->uid_cache); if (r == -ESRCH && arg_graceful) { log_syntax(NULL, LOG_DEBUG, fname, line, r, "%s: user '%s' not found, not adjusting ownership.", i.path, u); @@ -3800,7 +3977,7 @@ static int parse_line( else g = group; - r = find_gid(g, &i.gid, gid_cache); + r = find_gid(g, &i.gid, &c->gid_cache); if (r == -ESRCH && arg_graceful) { log_syntax(NULL, LOG_DEBUG, fname, line, r, "%s: group '%s' not found, not adjusting ownership.", i.path, g); @@ -3816,14 +3993,13 @@ static int parse_line( const char *mm; unsigned m; - for (mm = mode;; mm++) { + for (mm = mode;; mm++) if (*mm == '~') i.mask_perms = true; else if (*mm == ':') i.mode_only_create = true; else break; - } r = parse_mode(mm, &m); if (r < 0) { @@ -3940,16 +4116,16 @@ static int exclude_default_prefixes(void) { * likely over-mounted if the root directory is actually used, and it wouldbe less than ideal to have * all kinds of files created/adjusted underneath these mount points. */ - r = strv_extend_strv( + r = strv_extend_many( &arg_exclude_prefixes, - STRV_MAKE("/dev", - "/proc", - "/run", - "/sys"), - true); + "/dev", + "/proc", + "/run", + "/sys"); if (r < 0) return log_oom(); + strv_uniq(arg_exclude_prefixes); return 0; } @@ -3961,18 +4137,21 @@ static int help(void) { if (r < 0) return log_oom(); - printf("%s [OPTIONS...] [CONFIGURATION FILE...]\n" - "\n%sCreates, deletes and cleans up volatile and temporary files and directories.%s\n\n" + printf("%1$s COMMAND [OPTIONS...] [CONFIGURATION FILE...]\n" + "\n%2$sCreate, delete, and clean up files and directories.%4$s\n" + "\n%3$sCommands:%4$s\n" + " --create Create files and directories\n" + " --clean Clean up files and directories\n" + " --remove Remove files and directories\n" " -h --help Show this help\n" - " --user Execute user configuration\n" " --version Show package version\n" + "\n%3$sOptions:%4$s\n" + " --user Execute user configuration\n" " --cat-config Show configuration files\n" " --tldr Show non-comment parts of configuration\n" - " --create Create marked files/directories\n" - " --clean Clean up marked directories\n" - " --remove Remove marked files/directories\n" " --boot Execute actions only safe at boot\n" " --graceful Quietly ignore unknown users or groups\n" + " --purge Delete all files owned by the configuration files\n" " --prefix=PATH Only apply rules with the specified prefix\n" " --exclude-prefix=PATH Ignore rules with the specified prefix\n" " -E Ignore rules prefixed with /dev, /proc, /run, /sys\n" @@ -3980,10 +4159,12 @@ static int help(void) { " --image=PATH Operate on disk image as filesystem root\n" " --image-policy=POLICY Specify disk image dissection policy\n" " --replace=PATH Treat arguments as replacement for PATH\n" + " --dry-run Just print what would be done\n" " --no-pager Do not pipe output into a pager\n" - "\nSee the %s for details.\n", + "\nSee the %5$s for details.\n", program_invocation_short_name, ansi_highlight(), + ansi_underline(), ansi_normal(), link); @@ -3991,7 +4172,6 @@ static int help(void) { } static int parse_argv(int argc, char *argv[]) { - enum { ARG_VERSION = 0x100, ARG_CAT_CONFIG, @@ -4000,6 +4180,7 @@ static int parse_argv(int argc, char *argv[]) { ARG_CREATE, ARG_CLEAN, ARG_REMOVE, + ARG_PURGE, ARG_BOOT, ARG_GRACEFUL, ARG_PREFIX, @@ -4008,6 +4189,7 @@ static int parse_argv(int argc, char *argv[]) { ARG_IMAGE, ARG_IMAGE_POLICY, ARG_REPLACE, + ARG_DRY_RUN, ARG_NO_PAGER, }; @@ -4020,6 +4202,7 @@ static int parse_argv(int argc, char *argv[]) { { "create", no_argument, NULL, ARG_CREATE }, { "clean", no_argument, NULL, ARG_CLEAN }, { "remove", no_argument, NULL, ARG_REMOVE }, + { "purge", no_argument, NULL, ARG_PURGE }, { "boot", no_argument, NULL, ARG_BOOT }, { "graceful", no_argument, NULL, ARG_GRACEFUL }, { "prefix", required_argument, NULL, ARG_PREFIX }, @@ -4028,6 +4211,7 @@ static int parse_argv(int argc, char *argv[]) { { "image", required_argument, NULL, ARG_IMAGE }, { "image-policy", required_argument, NULL, ARG_IMAGE_POLICY }, { "replace", required_argument, NULL, ARG_REPLACE }, + { "dry-run", no_argument, NULL, ARG_DRY_RUN }, { "no-pager", no_argument, NULL, ARG_NO_PAGER }, {} }; @@ -4075,17 +4259,21 @@ static int parse_argv(int argc, char *argv[]) { arg_boot = true; break; + case ARG_PURGE: + arg_operation |= OPERATION_PURGE; + break; + case ARG_GRACEFUL: arg_graceful = true; break; case ARG_PREFIX: - if (strv_push(&arg_include_prefixes, optarg) < 0) + if (strv_extend(&arg_include_prefixes, optarg) < 0) return log_oom(); break; case ARG_EXCLUDE_PREFIX: - if (strv_push(&arg_exclude_prefixes, optarg) < 0) + if (strv_extend(&arg_exclude_prefixes, optarg) < 0) return log_oom(); break; @@ -4131,6 +4319,10 @@ static int parse_argv(int argc, char *argv[]) { arg_replace = optarg; break; + case ARG_DRY_RUN: + arg_dry_run = true; + break; + case ARG_NO_PAGER: arg_pager_flags |= PAGER_DISABLE; break; @@ -4144,7 +4336,7 @@ static int parse_argv(int argc, char *argv[]) { if (arg_operation == 0 && arg_cat_flags == CAT_CONFIG_OFF) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), - "You need to specify at least one of --clean, --create, or --remove."); + "You need to specify at least one of --clean, --create, --remove, or --purge."); if (arg_replace && arg_cat_flags != CAT_CONFIG_OFF) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), @@ -4159,7 +4351,8 @@ static int parse_argv(int argc, char *argv[]) { "Combination of --user and --root= is not supported."); if (arg_image && arg_root) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Please specify either --root= or --image=, the combination of both is not supported."); + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "Please specify either --root= or --image=, the combination of both is not supported."); return 1; } @@ -4171,77 +4364,28 @@ static int read_config_file( bool ignore_enoent, bool *invalid_config) { - _cleanup_hashmap_free_ Hashmap *uid_cache = NULL, *gid_cache = NULL; - _cleanup_fclose_ FILE *_f = NULL; - _cleanup_free_ char *pp = NULL; - unsigned v = 0; - FILE *f; ItemArray *ia; int r = 0; assert(c); assert(fn); - if (streq(fn, "-")) { - log_debug("Reading config from stdin%s", special_glyph(SPECIAL_GLYPH_ELLIPSIS)); - fn = "<stdin>"; - f = stdin; - } else { - r = search_and_fopen(fn, "re", arg_root, (const char**) config_dirs, &_f, &pp); - if (r < 0) { - if (ignore_enoent && r == -ENOENT) { - log_debug_errno(r, "Failed to open \"%s\", ignoring: %m", fn); - return 0; - } - - return log_error_errno(r, "Failed to open '%s': %m", fn); - } - - log_debug("Reading config file \"%s\"%s", pp, special_glyph(SPECIAL_GLYPH_ELLIPSIS)); - fn = pp; - f = _f; - } - - for (;;) { - _cleanup_free_ char *line = NULL; - bool invalid_line = false; - int k; - - k = read_stripped_line(f, LONG_LINE_MAX, &line); - if (k < 0) - return log_error_errno(k, "Failed to read '%s': %m", fn); - if (k == 0) - break; - - v++; - - if (IN_SET(line[0], 0, '#')) - continue; - - k = parse_line(c, fn, v, line, &invalid_line, &uid_cache, &gid_cache); - if (k < 0) { - if (invalid_line) - /* Allow reporting with a special code if the caller requested this */ - *invalid_config = true; - else if (r == 0) - /* The first error becomes our return value */ - r = k; - } - } + r = conf_file_read(arg_root, (const char**) config_dirs, fn, + parse_line, c, ignore_enoent, invalid_config); + if (r <= 0) + return r; /* we have to determine age parameter for each entry of type X */ ORDERED_HASHMAP_FOREACH(ia, c->globs) - for (size_t ni = 0; ni < ia->n_items; ni++) { + FOREACH_ARRAY(i, ia->items, ia->n_items) { ItemArray *ja; - Item *i = ia->items + ni, *candidate_item = NULL; + Item *candidate_item = NULL; if (i->type != IGNORE_DIRECTORY_PATH) continue; ORDERED_HASHMAP_FOREACH(ja, c->items) - for (size_t nj = 0; nj < ja->n_items; nj++) { - Item *j = ja->items + nj; - + FOREACH_ARRAY(j, ja->items, ja->n_items) { if (!IN_SET(j->type, CREATE_DIRECTORY, TRUNCATE_DIRECTORY, CREATE_SUBVOLUME, @@ -4266,12 +4410,6 @@ static int read_config_file( } } - if (ferror(f)) { - log_error_errno(errno, "Failed to read from file %s: %m", fn); - if (r == 0) - r = -EIO; - } - return r; } @@ -4325,7 +4463,6 @@ static int read_config_files( } static int read_credential_lines(Context *c, bool *invalid_config) { - _cleanup_free_ char *j = NULL; const char *d; int r; @@ -4354,9 +4491,10 @@ static int link_parent(Context *c, ItemArray *a) { assert(c); assert(a); - /* Finds the closest "parent" item array for the specified item array. Then registers the specified item array - * as child of it, and fills the parent in, linking them both ways. This allows us to later create parents - * before their children, and clean up/remove children before their parents. */ + /* Finds the closest "parent" item array for the specified item array. Then registers the specified + * item array as child of it, and fills the parent in, linking them both ways. This allows us to + * later create parents before their children, and clean up/remove children before their parents. + */ if (a->n_items <= 0) return 0; @@ -4395,6 +4533,7 @@ static int run(int argc, char *argv[]) { bool invalid_config = false; ItemArray *a; enum { + PHASE_PURGE, PHASE_REMOVE_AND_CLEAN, PHASE_CREATE, _PHASE_MAX @@ -4429,7 +4568,7 @@ static int run(int argc, char *argv[]) { break; case RUNTIME_SCOPE_SYSTEM: - config_dirs = strv_split_nulstr(CONF_PATHS_NULSTR("tmpfiles.d")); + config_dirs = strv_new(CONF_PATHS("tmpfiles.d")); if (!config_dirs) return log_oom(); break; @@ -4476,7 +4615,8 @@ static int run(int argc, char *argv[]) { DISSECT_IMAGE_VALIDATE_OS | DISSECT_IMAGE_RELAX_VAR_CHECK | DISSECT_IMAGE_FSCK | - DISSECT_IMAGE_GROWFS, + DISSECT_IMAGE_GROWFS | + DISSECT_IMAGE_ALLOW_USERSPACE_VERITY, &mounted_dir, /* ret_dir_fd= */ NULL, &loop_device); @@ -4525,12 +4665,14 @@ static int run(int argc, char *argv[]) { return r; } - /* If multiple operations are requested, let's first run the remove/clean operations, and only then the create - * operations. i.e. that we first clean out the platform we then build on. */ + /* If multiple operations are requested, let's first run the remove/clean operations, and only then + * the create operations. i.e. that we first clean out the platform we then build on. */ for (phase = 0; phase < _PHASE_MAX; phase++) { OperationMask op; - if (phase == PHASE_REMOVE_AND_CLEAN) + if (phase == PHASE_PURGE) + op = arg_operation & OPERATION_PURGE; + else if (phase == PHASE_REMOVE_AND_CLEAN) op = arg_operation & (OPERATION_REMOVE|OPERATION_CLEAN); else if (phase == PHASE_CREATE) op = arg_operation & OPERATION_CREATE; |