diff --git a/man/systemd-tmpfiles.xml b/man/systemd-tmpfiles.xml
index 9767aead851..c3942e5b6ee 100644
--- a/man/systemd-tmpfiles.xml
+++ b/man/systemd-tmpfiles.xml
@@ -151,19 +151,33 @@
- If this option is passed, all files and directories created by a
- tmpfiles.d/ entry will be deleted. Keep in mind that by default,
- /home is created by systemd-tmpfiles
- (see /usr/lib/tmpfiles.d/home.conf). Therefore it is recommended
- to first run systemd-tmpfiles --dry-run --purge to be certain which files
- and directories will be deleted.
+
+ If this option is passed, all files and directories marked for
+ creation by the tmpfiles.d/ files specified on the command
+ line will be deleted. Specifically, this acts on all files and directories
+ marked with f, F, d, D,
+ v, q, Q, p,
+ L, c, b, C,
+ w, e. If this switch is used at least one
+ tmpfiles.d/ file (or - for standard input) must be
+ specified on the command line or the invocation will be refused, for safety reasons (as otherwise
+ much of the installed system files might be removed).
+
+ The primary usecase for this option is to automatically remove files and directories that
+ originally have been created on behalf of an installed packaged at package removal time.
+
+ It is recommended to first run this command in combination with
+ (see below) to verify which files and directories will be deleted.
+
+ Warning! This is is usually not the command you want! In most cases
+ is what you are looking for.
- Execute "user" configuration, i.e. tmpfiles.d
+ Execute "user" configuration, i.e. tmpfiles.d/
files in user configuration directories.
diff --git a/src/tmpfiles/tmpfiles.c b/src/tmpfiles/tmpfiles.c
index 807925f199d..5841db293e7 100644
--- a/src/tmpfiles/tmpfiles.c
+++ b/src/tmpfiles/tmpfiles.c
@@ -204,7 +204,6 @@ static OperationMask arg_operation = 0;
static bool arg_boot = false;
static bool arg_graceful = false;
static PagerFlags arg_pager_flags = 0;
-
static char **arg_include_prefixes = NULL;
static char **arg_exclude_prefixes = NULL;
static char *arg_root = NULL;
@@ -396,20 +395,20 @@ static int user_config_paths(char*** ret) {
static bool needs_purge(ItemType t) {
return IN_SET(t,
- COPY_FILES,
- TRUNCATE_FILE,
CREATE_FILE,
- WRITE_FILE,
- EMPTY_DIRECTORY,
+ TRUNCATE_FILE,
+ CREATE_DIRECTORY,
+ TRUNCATE_DIRECTORY,
CREATE_SUBVOLUME,
CREATE_SUBVOLUME_INHERIT_QUOTA,
CREATE_SUBVOLUME_NEW_QUOTA,
+ CREATE_FIFO,
+ CREATE_SYMLINK,
CREATE_CHAR_DEVICE,
CREATE_BLOCK_DEVICE,
- CREATE_SYMLINK,
- CREATE_FIFO,
- CREATE_DIRECTORY,
- TRUNCATE_DIRECTORY);
+ COPY_FILES,
+ WRITE_FILE,
+ EMPTY_DIRECTORY);
}
static bool needs_glob(ItemType t) {
@@ -548,14 +547,13 @@ static DIR* xopendirat_nomod(int dirfd, const char *path) {
return dir;
if (!IN_SET(errno, ENOENT, ELOOP))
- log_debug_errno(errno, "Cannot open %sdirectory \"%s\": %m", dirfd == AT_FDCWD ? "" : "sub", path);
-
- if (errno != EPERM)
+ log_debug_errno(errno, "Cannot open %sdirectory \"%s\" with O_NOATIME: %m", dirfd == AT_FDCWD ? "" : "sub", path);
+ if (!ERRNO_IS_PRIVILEGE(errno))
return NULL;
dir = xopendirat(dirfd, path, O_NOFOLLOW);
if (!dir)
- log_debug_errno(errno, "Cannot open %sdirectory \"%s\": %m", dirfd == AT_FDCWD ? "" : "sub", path);
+ log_debug_errno(errno, "Cannot open %sdirectory \"%s\" with or without O_NOATIME: %m", dirfd == AT_FDCWD ? "" : "sub", path);
return dir;
}
@@ -3024,10 +3022,16 @@ static int remove_recursive(
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);
+ log_action("Would remove", "Removing", "%s directory \"%s\".", instance);
+ if (!arg_dry_run) {
+ r = RET_NERRNO(rmdir(instance));
+ if (r < 0) {
+ bool fatal = !IN_SET(r, -ENOENT, -ENOTEMPTY);
+ log_full_errno(fatal ? LOG_ERR : LOG_DEBUG, r, "Failed to remove %s: %m", instance);
+ if (fatal)
+ return r;
+ }
+ }
}
return 0;
}
@@ -4140,18 +4144,19 @@ static int help(void) {
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"
+ " --create Create and adjust files and directories\n"
" --clean Clean up files and directories\n"
- " --remove Remove files and directories\n"
+ " --remove Remove files and directories marked for removal\n"
+ " --purge Delete files and directories marked for creation in\n"
+ " specified configuration files (careful!)\n"
" -h --help Show this help\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"
+ " --tldr Show non-comment parts of configuration files\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"
@@ -4338,6 +4343,10 @@ static int parse_argv(int argc, char *argv[]) {
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
"You need to specify at least one of --clean, --create, --remove, or --purge.");
+ if (FLAGS_SET(arg_operation, OPERATION_PURGE) && optind >= argc)
+ return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
+ "Refusing --purge without specification of a configuration file.");
+
if (arg_replace && arg_cat_flags != CAT_CONFIG_OFF)
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
"Option --replace= is not supported with --cat-config/--tldr.");
@@ -4636,12 +4645,10 @@ static int run(int argc, char *argv[]) {
if (!c.items || !c.globs)
return log_oom();
- /* If command line arguments are specified along with --replace, read all
- * configuration files and insert the positional arguments at the specified
- * place. Otherwise, if command line arguments are specified, execute just
- * them, and finally, without --replace= or any positional arguments, just
- * read configuration and execute it.
- */
+ /* If command line arguments are specified along with --replace=, read all configuration files and
+ * insert the positional arguments at the specified place. Otherwise, if command line arguments are
+ * specified, execute just them, and finally, without --replace= or any positional arguments, just
+ * read configuration and execute it. */
if (arg_replace || optind >= argc)
r = read_config_files(&c, config_dirs, argv + optind, &invalid_config);
else