From c6878637502b1717a110a9a7e8bba32a8583fcdf Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Sat, 4 Apr 2015 11:52:57 +0200 Subject: [PATCH] util: rework rm_rf() logic - Move to its own file rm-rf.c - Change parameters into a single flags parameter - Remove "honour sticky" logic, it's unused these days --- Makefile.am | 2 + src/boot/bootctl.c | 3 +- src/core/execute.c | 5 +- src/core/machine-id-setup.c | 2 +- src/core/manager.c | 3 +- src/import/aufs-util.c | 5 +- src/import/import-raw.c | 3 +- src/import/import-tar.c | 5 +- src/import/pull-common.c | 5 +- src/import/pull-dkr.c | 3 +- src/import/pull-raw.c | 3 +- src/import/pull-tar.c | 3 +- src/journal/journald-server.c | 3 +- src/journal/test-journal-init.c | 3 +- src/journal/test-journal-interleaving.c | 8 +- src/journal/test-journal-stream.c | 10 +- src/journal/test-journal-verify.c | 3 +- src/journal/test-journal.c | 6 +- src/login/logind-user.c | 5 +- src/nspawn/nspawn.c | 3 +- src/shared/machine-image.c | 3 +- src/shared/rm-rf.c | 173 ++++++++++++++++++++ src/shared/rm-rf.h | 33 ++++ src/shared/switch-root.c | 5 +- src/shared/util.c | 199 +----------------------- src/shared/util.h | 9 +- src/test/test-conf-files.c | 4 +- src/test/test-copy.c | 9 +- src/test/test-execute.c | 3 +- src/test/test-path-lookup.c | 3 +- src/test/test-path-util.c | 3 +- src/test/test-path.c | 12 +- src/test/test-util.c | 7 +- src/tmpfiles/tmpfiles.c | 3 +- 34 files changed, 294 insertions(+), 255 deletions(-) create mode 100644 src/shared/rm-rf.c create mode 100644 src/shared/rm-rf.h diff --git a/Makefile.am b/Makefile.am index 1b64b6232ea..4973d8418c8 100644 --- a/Makefile.am +++ b/Makefile.am @@ -771,6 +771,8 @@ libsystemd_shared_la_SOURCES = \ src/shared/device-nodes.h \ src/shared/util.c \ src/shared/util.h \ + src/shared/rm-rf.c \ + src/shared/rm-rf.h \ src/shared/virt.c \ src/shared/virt.h \ src/shared/architecture.c \ diff --git a/src/boot/bootctl.c b/src/boot/bootctl.c index bc1405145e5..001cfa76fd7 100644 --- a/src/boot/bootctl.c +++ b/src/boot/bootctl.c @@ -41,6 +41,7 @@ #include "efivars.h" #include "build.h" #include "util.h" +#include "rm-rf.h" static int verify_esp(const char *p, uint32_t *part, uint64_t *pstart, uint64_t *psize, sd_id128_t *uuid) { struct statfs sfs; @@ -1095,7 +1096,7 @@ static int remove_binaries(const char *esp_path) { return -ENOMEM; } - r = rm_rf(p, false, false, false); + r = rm_rf(p, REMOVE_ROOT|REMOVE_PHYSICAL); free(p); q = remove_boot_efi(esp_path); diff --git a/src/core/execute.c b/src/core/execute.c index 02df51b5bb2..768a32b31f7 100644 --- a/src/core/execute.c +++ b/src/core/execute.c @@ -49,6 +49,7 @@ #include #endif +#include "rm-rf.h" #include "execute.h" #include "strv.h" #include "macro.h" @@ -2020,7 +2021,7 @@ int exec_context_destroy_runtime_directory(ExecContext *c, const char *runtime_p /* We execute this synchronously, since we need to be * sure this is gone when we start the service * next. */ - rm_rf(p, false, true, false); + (void) rm_rf(p, REMOVE_ROOT); } return 0; @@ -2846,7 +2847,7 @@ int exec_runtime_deserialize_item(ExecRuntime **rt, Unit *u, const char *key, co static void *remove_tmpdir_thread(void *p) { _cleanup_free_ char *path = p; - rm_rf_dangerous(path, false, true, false); + (void) rm_rf(path, REMOVE_ROOT|REMOVE_PHYSICAL); return NULL; } diff --git a/src/core/machine-id-setup.c b/src/core/machine-id-setup.c index 7505dcbcaa1..2ffb2a7aa6f 100644 --- a/src/core/machine-id-setup.c +++ b/src/core/machine-id-setup.c @@ -313,7 +313,7 @@ int machine_id_commit(const char *root) { if (r < 0) return log_error_errno(r, "We didn't find a valid machine ID in %s.", etc_machine_id); - r = is_fd_on_temporary_fs(fd); + r = fd_is_temporary_fs(fd); if (r < 0) return log_error_errno(r, "Failed to determine whether %s is on a temporary file system: %m", etc_machine_id); if (r == 0) { diff --git a/src/core/manager.c b/src/core/manager.c index 1afd359df25..73417ab1a86 100644 --- a/src/core/manager.c +++ b/src/core/manager.c @@ -52,6 +52,7 @@ #include "locale-setup.h" #include "unit-name.h" #include "missing.h" +#include "rm-rf.h" #include "path-lookup.h" #include "special.h" #include "exit-status.h" @@ -2881,7 +2882,7 @@ static void remove_generator_dir(Manager *m, char **generator) { return; strv_remove(m->lookup_paths.unit_path, *generator); - rm_rf(*generator, false, true, false); + (void) rm_rf(*generator, REMOVE_ROOT); free(*generator); *generator = NULL; diff --git a/src/import/aufs-util.c b/src/import/aufs-util.c index c1301cdb4ab..18c42b8b6d1 100644 --- a/src/import/aufs-util.c +++ b/src/import/aufs-util.c @@ -22,6 +22,7 @@ #include #include "util.h" +#include "rm-rf.h" #include "aufs-util.h" static int nftw_cb( @@ -43,7 +44,7 @@ static int nftw_cb( return FTW_CONTINUE; log_debug("Removing whiteout indicator %s.", fpath); - r = rm_rf_dangerous(fpath, false, true, false); + r = rm_rf(fpath, REMOVE_ROOT|REMOVE_PHYSICAL); if (r < 0) return FTW_STOP; @@ -53,7 +54,7 @@ static int nftw_cb( strcpy(mempcpy(p, fpath, ftwbuf->base), original); log_debug("Removing deleted file %s.", p); - r = rm_rf_dangerous(p, false, true, false); + r = rm_rf(p, REMOVE_ROOT|REMOVE_PHYSICAL); if (r < 0) return FTW_STOP; } diff --git a/src/import/import-raw.c b/src/import/import-raw.c index 7d1ac2afd70..3a315139e59 100644 --- a/src/import/import-raw.c +++ b/src/import/import-raw.c @@ -28,6 +28,7 @@ #include "btrfs-util.h" #include "copy.h" #include "mkdir.h" +#include "rm-rf.h" #include "ratelimit.h" #include "machine-pool.h" #include "qcow2-util.h" @@ -242,7 +243,7 @@ static int raw_import_finish(RawImport *i) { if (i->force_local) { (void) btrfs_subvol_remove(i->final_path); - (void) rm_rf_dangerous(i->final_path, false, true, false); + (void) rm_rf(i->final_path, REMOVE_ROOT|REMOVE_PHYSICAL); } r = rename_noreplace(AT_FDCWD, i->temp_path, AT_FDCWD, i->final_path); diff --git a/src/import/import-tar.c b/src/import/import-tar.c index ef2345c7b93..c5346ca2b04 100644 --- a/src/import/import-tar.c +++ b/src/import/import-tar.c @@ -28,6 +28,7 @@ #include "btrfs-util.h" #include "copy.h" #include "mkdir.h" +#include "rm-rf.h" #include "ratelimit.h" #include "machine-pool.h" #include "qcow2-util.h" @@ -87,7 +88,7 @@ TarImport* tar_import_unref(TarImport *i) { if (i->temp_path) { (void) btrfs_subvol_remove(i->temp_path); - (void) rm_rf_dangerous(i->temp_path, false, true, false); + (void) rm_rf(i->temp_path, REMOVE_ROOT|REMOVE_PHYSICAL); free(i->temp_path); } @@ -198,7 +199,7 @@ static int tar_import_finish(TarImport *i) { if (i->force_local) { (void) btrfs_subvol_remove(i->final_path); - (void) rm_rf_dangerous(i->final_path, false, true, false); + (void) rm_rf(i->final_path, REMOVE_ROOT|REMOVE_PHYSICAL); } r = rename_noreplace(AT_FDCWD, i->temp_path, AT_FDCWD, i->final_path); diff --git a/src/import/pull-common.c b/src/import/pull-common.c index 94dd54bd57a..d4cebe205ef 100644 --- a/src/import/pull-common.c +++ b/src/import/pull-common.c @@ -24,6 +24,7 @@ #include "util.h" #include "strv.h" #include "copy.h" +#include "rm-rf.h" #include "btrfs-util.h" #include "capability.h" #include "pull-job.h" @@ -125,7 +126,7 @@ int pull_make_local_copy(const char *final, const char *image_root, const char * if (force_local) { (void) btrfs_subvol_remove(p); - (void) rm_rf_dangerous(p, false, true, false); + (void) rm_rf(p, REMOVE_ROOT|REMOVE_PHYSICAL); } r = btrfs_subvol_snapshot(final, p, false, false); @@ -418,7 +419,7 @@ finish: unlink(sig_file_path); if (gpg_home_created) - rm_rf_dangerous(gpg_home, false, true, false); + (void) rm_rf(gpg_home, REMOVE_ROOT|REMOVE_PHYSICAL); return r; } diff --git a/src/import/pull-dkr.c b/src/import/pull-dkr.c index 1a7dc310cba..a1e6fe8ff71 100644 --- a/src/import/pull-dkr.c +++ b/src/import/pull-dkr.c @@ -28,6 +28,7 @@ #include "btrfs-util.h" #include "utf8.h" #include "mkdir.h" +#include "rm-rf.h" #include "path-util.h" #include "import-util.h" #include "curl-util.h" @@ -111,7 +112,7 @@ DkrPull* dkr_pull_unref(DkrPull *i) { if (i->temp_path) { (void) btrfs_subvol_remove(i->temp_path); - (void) rm_rf_dangerous(i->temp_path, false, true, false); + (void) rm_rf(i->temp_path, REMOVE_ROOT|REMOVE_PHYSICAL); free(i->temp_path); } diff --git a/src/import/pull-raw.c b/src/import/pull-raw.c index c0c6d57eade..e10b280b660 100644 --- a/src/import/pull-raw.c +++ b/src/import/pull-raw.c @@ -31,6 +31,7 @@ #include "util.h" #include "macro.h" #include "mkdir.h" +#include "rm-rf.h" #include "path-util.h" #include "import-util.h" #include "import-common.h" @@ -278,7 +279,7 @@ static int raw_pull_make_local_copy(RawPull *i) { if (i->force_local) { (void) btrfs_subvol_remove(p); - (void) rm_rf_dangerous(p, false, true, false); + (void) rm_rf(p, REMOVE_ROOT|REMOVE_PHYSICAL); } r = tempfn_random(p, &tp); diff --git a/src/import/pull-tar.c b/src/import/pull-tar.c index 58cafdd7891..60dc22fdfa2 100644 --- a/src/import/pull-tar.c +++ b/src/import/pull-tar.c @@ -30,6 +30,7 @@ #include "util.h" #include "macro.h" #include "mkdir.h" +#include "rm-rf.h" #include "path-util.h" #include "import-util.h" #include "import-common.h" @@ -88,7 +89,7 @@ TarPull* tar_pull_unref(TarPull *i) { if (i->temp_path) { (void) btrfs_subvol_remove(i->temp_path); - (void) rm_rf_dangerous(i->temp_path, false, true, false); + (void) rm_rf(i->temp_path, REMOVE_ROOT|REMOVE_PHYSICAL); free(i->temp_path); } diff --git a/src/journal/journald-server.c b/src/journal/journald-server.c index 2488884634d..78d5b22ae12 100644 --- a/src/journal/journald-server.c +++ b/src/journal/journald-server.c @@ -31,6 +31,7 @@ #include "sd-messages.h" #include "sd-daemon.h" #include "mkdir.h" +#include "rm-rf.h" #include "hashmap.h" #include "journal-file.h" #include "socket-util.h" @@ -1088,7 +1089,7 @@ finish: s->runtime_journal = NULL; if (r >= 0) - rm_rf("/run/log/journal", false, true, false); + (void) rm_rf("/run/log/journal", REMOVE_ROOT); sd_journal_close(j); diff --git a/src/journal/test-journal-init.c b/src/journal/test-journal-init.c index 11fb150fe83..e6599f366d9 100644 --- a/src/journal/test-journal-init.c +++ b/src/journal/test-journal-init.c @@ -23,6 +23,7 @@ #include "log.h" #include "util.h" +#include "rm-rf.h" int main(int argc, char *argv[]) { sd_journal *j; @@ -58,7 +59,7 @@ int main(int argc, char *argv[]) { assert_se(j == NULL); } - assert_se(rm_rf_dangerous(t, false, true, false) >= 0); + assert_se(rm_rf(t, REMOVE_ROOT|REMOVE_PHYSICAL) >= 0); return 0; } diff --git a/src/journal/test-journal-interleaving.c b/src/journal/test-journal-interleaving.c index 3e6141771c6..c2fc123e420 100644 --- a/src/journal/test-journal-interleaving.c +++ b/src/journal/test-journal-interleaving.c @@ -23,12 +23,12 @@ #include #include -#include "systemd/sd-journal.h" - +#include "sd-journal.h" #include "journal-file.h" #include "journal-vacuum.h" #include "util.h" #include "log.h" +#include "rm-rf.h" /* This program tests skipping around in a multi-file journal. */ @@ -190,7 +190,7 @@ static void test_skip(void (*setup)(void)) { else { journal_directory_vacuum(".", 3000000, 0, NULL, true); - assert_se(rm_rf_dangerous(t, false, true, false) >= 0); + assert_se(rm_rf(t, REMOVE_ROOT|REMOVE_PHYSICAL) >= 0); } puts("------------------------------------------------------------"); @@ -275,7 +275,7 @@ static void test_sequence_numbers(void) { else { journal_directory_vacuum(".", 3000000, 0, NULL, true); - assert_se(rm_rf_dangerous(t, false, true, false) >= 0); + assert_se(rm_rf(t, REMOVE_ROOT|REMOVE_PHYSICAL) >= 0); } } diff --git a/src/journal/test-journal-stream.c b/src/journal/test-journal-stream.c index b8caeb3d41f..e1146c692d3 100644 --- a/src/journal/test-journal-stream.c +++ b/src/journal/test-journal-stream.c @@ -22,13 +22,13 @@ #include #include -#include "systemd/sd-journal.h" - -#include "journal-file.h" -#include "journal-internal.h" +#include "sd-journal.h" #include "util.h" #include "log.h" #include "macro.h" +#include "rm-rf.h" +#include "journal-file.h" +#include "journal-internal.h" #define N_ENTRIES 200 @@ -180,7 +180,7 @@ int main(int argc, char *argv[]) { SD_JOURNAL_FOREACH_UNIQUE(j, data, l) printf("%.*s\n", (int) l, (const char*) data); - assert_se(rm_rf_dangerous(t, false, true, false) >= 0); + assert_se(rm_rf(t, REMOVE_ROOT|REMOVE_PHYSICAL) >= 0); return 0; } diff --git a/src/journal/test-journal-verify.c b/src/journal/test-journal-verify.c index de9cd9c8b8c..8008f7455e7 100644 --- a/src/journal/test-journal-verify.c +++ b/src/journal/test-journal-verify.c @@ -25,6 +25,7 @@ #include "util.h" #include "log.h" +#include "rm-rf.h" #include "journal-file.h" #include "journal-verify.h" @@ -144,7 +145,7 @@ int main(int argc, char *argv[]) { log_info("Exiting..."); - assert_se(rm_rf_dangerous(t, false, true, false) >= 0); + assert_se(rm_rf(t, REMOVE_ROOT|REMOVE_PHYSICAL) >= 0); return 0; } diff --git a/src/journal/test-journal.c b/src/journal/test-journal.c index f7cc75b3d5d..caaab258c9b 100644 --- a/src/journal/test-journal.c +++ b/src/journal/test-journal.c @@ -22,8 +22,8 @@ #include #include - #include "log.h" +#include "rm-rf.h" #include "journal-file.h" #include "journal-authenticate.h" #include "journal-vacuum.h" @@ -118,7 +118,7 @@ static void test_non_empty(void) { else { journal_directory_vacuum(".", 3000000, 0, NULL, true); - assert_se(rm_rf_dangerous(t, false, true, false) >= 0); + assert_se(rm_rf(t, REMOVE_ROOT|REMOVE_PHYSICAL) >= 0); } puts("------------------------------------------------------------"); @@ -157,7 +157,7 @@ static void test_empty(void) { else { journal_directory_vacuum(".", 3000000, 0, NULL, true); - assert_se(rm_rf_dangerous(t, false, true, false) >= 0); + assert_se(rm_rf(t, REMOVE_ROOT|REMOVE_PHYSICAL) >= 0); } journal_file_close(f1); diff --git a/src/login/logind-user.c b/src/login/logind-user.c index 294c1e799cd..cba9cb60aa2 100644 --- a/src/login/logind-user.c +++ b/src/login/logind-user.c @@ -26,6 +26,7 @@ #include "util.h" #include "mkdir.h" +#include "rm-rf.h" #include "hashmap.h" #include "fileio.h" #include "path-util.h" @@ -521,7 +522,7 @@ static int user_remove_runtime_path(User *u) { if (!u->runtime_path) return 0; - r = rm_rf(u->runtime_path, false, false, false); + r = rm_rf(u->runtime_path, 0); if (r < 0) log_error_errno(r, "Failed to remove runtime directory %s: %m", u->runtime_path); @@ -532,7 +533,7 @@ static int user_remove_runtime_path(User *u) { if (r < 0 && errno != EINVAL && errno != ENOENT) log_error_errno(errno, "Failed to unmount user runtime directory %s: %m", u->runtime_path); - r = rm_rf(u->runtime_path, false, true, false); + r = rm_rf(u->runtime_path, REMOVE_ROOT); if (r < 0) log_error_errno(r, "Failed to remove runtime directory %s: %m", u->runtime_path); diff --git a/src/nspawn/nspawn.c b/src/nspawn/nspawn.c index 7e56cf2056c..730541af88c 100644 --- a/src/nspawn/nspawn.c +++ b/src/nspawn/nspawn.c @@ -59,6 +59,7 @@ #include "log.h" #include "util.h" #include "mkdir.h" +#include "rm-rf.h" #include "macro.h" #include "missing.h" #include "cgroup-util.h" @@ -4467,7 +4468,7 @@ finish: const char *p; p = strjoina("/run/systemd/nspawn/propagate/", arg_machine); - (void) rm_rf(p, false, true, false); + (void) rm_rf(p, REMOVE_ROOT); } free(arg_directory); diff --git a/src/shared/machine-image.c b/src/shared/machine-image.c index edf986d4db1..c5808af81ef 100644 --- a/src/shared/machine-image.c +++ b/src/shared/machine-image.c @@ -28,6 +28,7 @@ #include "path-util.h" #include "copy.h" #include "mkdir.h" +#include "rm-rf.h" #include "machine-image.h" static const char image_search_path[] = @@ -366,7 +367,7 @@ int image_remove(Image *i) { /* fall through */ case IMAGE_RAW: - return rm_rf_dangerous(i->path, false, true, false); + return rm_rf(i->path, REMOVE_ROOT|REMOVE_PHYSICAL); default: return -EOPNOTSUPP; diff --git a/src/shared/rm-rf.c b/src/shared/rm-rf.c new file mode 100644 index 00000000000..99d12b11c6d --- /dev/null +++ b/src/shared/rm-rf.c @@ -0,0 +1,173 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +/*** + This file is part of systemd. + + Copyright 2015 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see . +***/ + +#include "util.h" +#include "path-util.h" +#include "rm-rf.h" + +int rm_rf_children(int fd, RemoveFlags flags, struct stat *root_dev) { + _cleanup_closedir_ DIR *d = NULL; + int ret = 0, r; + + assert(fd >= 0); + + /* This returns the first error we run into, but nevertheless + * tries to go on. This closes the passed fd. */ + + if (!(flags & REMOVE_PHYSICAL)) { + + r = fd_is_temporary_fs(fd); + if (r < 0) { + safe_close(fd); + return r; + } + + if (!r) { + /* We refuse to clean physical file systems + * with this call, unless explicitly + * requested. This is extra paranoia just to + * be sure we never ever remove non-state + * data */ + + log_error("Attempted to remove disk file system, and we can't allow that."); + safe_close(fd); + return -EPERM; + } + } + + d = fdopendir(fd); + if (!d) { + safe_close(fd); + return errno == ENOENT ? 0 : -errno; + } + + for (;;) { + struct dirent *de; + bool is_dir; + struct stat st; + + errno = 0; + de = readdir(d); + if (!de) { + if (errno != 0 && ret == 0) + ret = -errno; + return ret; + } + + if (streq(de->d_name, ".") || streq(de->d_name, "..")) + continue; + + if (de->d_type == DT_UNKNOWN || (de->d_type == DT_DIR && root_dev)) { + if (fstatat(fd, de->d_name, &st, AT_SYMLINK_NOFOLLOW) < 0) { + if (ret == 0 && errno != ENOENT) + ret = -errno; + continue; + } + + is_dir = S_ISDIR(st.st_mode); + } else + is_dir = de->d_type == DT_DIR; + + if (is_dir) { + int subdir_fd; + + /* if root_dev is set, remove subdirectories only, if device is same as dir */ + if (root_dev && st.st_dev != root_dev->st_dev) + continue; + + subdir_fd = openat(fd, de->d_name, O_RDONLY|O_NONBLOCK|O_DIRECTORY|O_CLOEXEC|O_NOFOLLOW|O_NOATIME); + if (subdir_fd < 0) { + if (ret == 0 && errno != ENOENT) + ret = -errno; + continue; + } + + /* We pass REMOVE_PHYSICAL here, to avoid + * doing the fstatfs() to check the file + * system type again for each directory */ + r = rm_rf_children(subdir_fd, flags | REMOVE_PHYSICAL, root_dev); + if (r < 0 && ret == 0) + ret = r; + + if (unlinkat(fd, de->d_name, AT_REMOVEDIR) < 0) { + if (ret == 0 && errno != ENOENT) + ret = -errno; + } + + } else if (!(flags & REMOVE_ONLY_DIRECTORIES)) { + + if (unlinkat(fd, de->d_name, 0) < 0) { + if (ret == 0 && errno != ENOENT) + ret = -errno; + } + } + } +} + +int rm_rf(const char *path, RemoveFlags flags) { + int fd, r; + struct statfs s; + + assert(path); + + /* We refuse to clean the root file system with this + * call. This is extra paranoia to never cause a really + * seriously broken system. */ + if (path_equal(path, "/")) { + log_error("Attempted to remove entire root file system, and we can't allow that."); + return -EPERM; + } + + fd = open(path, O_RDONLY|O_NONBLOCK|O_DIRECTORY|O_CLOEXEC|O_NOFOLLOW|O_NOATIME); + if (fd < 0) { + + if (errno != ENOTDIR && errno != ELOOP) + return -errno; + + if (!(flags & REMOVE_PHYSICAL)) { + if (statfs(path, &s) < 0) + return -errno; + + if (!is_temporary_fs(&s)) { + log_error("Attempted to remove disk file system, and we can't allow that."); + return -EPERM; + } + } + + if ((flags & REMOVE_ROOT) && !(flags & REMOVE_ONLY_DIRECTORIES)) + if (unlink(path) < 0 && errno != ENOENT) + return -errno; + + return 0; + } + + r = rm_rf_children(fd, flags, NULL); + + if (flags & REMOVE_ROOT) { + + if (rmdir(path) < 0 && errno != ENOENT) { + if (r == 0) + r = -errno; + } + } + + return r; +} diff --git a/src/shared/rm-rf.h b/src/shared/rm-rf.h new file mode 100644 index 00000000000..769bbc853dd --- /dev/null +++ b/src/shared/rm-rf.h @@ -0,0 +1,33 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +#pragma once + +/*** + This file is part of systemd. + + Copyright 2015 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see . +***/ + +#include + +typedef enum RemoveFlags { + REMOVE_ONLY_DIRECTORIES = 1, + REMOVE_ROOT = 2, + REMOVE_PHYSICAL = 4, /* if not set, only removes files on tmpfs, never physical file systems */ +} RemoveFlags; + +int rm_rf_children(int fd, RemoveFlags flags, struct stat *root_dev); +int rm_rf(const char *path, RemoveFlags flags); diff --git a/src/shared/switch-root.c b/src/shared/switch-root.c index 813641ad44d..ae3839de162 100644 --- a/src/shared/switch-root.c +++ b/src/shared/switch-root.c @@ -29,10 +29,11 @@ #include "util.h" #include "path-util.h" -#include "switch-root.h" #include "mkdir.h" +#include "rm-rf.h" #include "base-filesystem.h" #include "missing.h" +#include "switch-root.h" int switch_root(const char *new_root, const char *oldroot, bool detach_oldroot, unsigned long mountflags) { @@ -142,7 +143,7 @@ int switch_root(const char *new_root, const char *oldroot, bool detach_oldroot, if (fstat(old_root_fd, &rb) < 0) log_warning_errno(errno, "Failed to stat old root directory, leaving: %m"); else { - rm_rf_children(old_root_fd, false, false, &rb); + (void) rm_rf_children(old_root_fd, 0, &rb); old_root_fd = -1; } } diff --git a/src/shared/util.c b/src/shared/util.c index 605fffcb7a7..8b011ccfa1d 100644 --- a/src/shared/util.c +++ b/src/shared/util.c @@ -2988,101 +2988,14 @@ int get_ctty(pid_t pid, dev_t *_devnr, char **r) { return 0; } -int rm_rf_children_dangerous(int fd, bool only_dirs, bool honour_sticky, struct stat *root_dev) { - _cleanup_closedir_ DIR *d = NULL; - int ret = 0; - - assert(fd >= 0); - - /* This returns the first error we run into, but nevertheless - * tries to go on. This closes the passed fd. */ - - d = fdopendir(fd); - if (!d) { - safe_close(fd); - - return errno == ENOENT ? 0 : -errno; - } - - for (;;) { - struct dirent *de; - bool is_dir, keep_around; - struct stat st; - int r; - - errno = 0; - de = readdir(d); - if (!de) { - if (errno != 0 && ret == 0) - ret = -errno; - return ret; - } - - if (streq(de->d_name, ".") || streq(de->d_name, "..")) - continue; - - if (de->d_type == DT_UNKNOWN || - honour_sticky || - (de->d_type == DT_DIR && root_dev)) { - if (fstatat(fd, de->d_name, &st, AT_SYMLINK_NOFOLLOW) < 0) { - if (ret == 0 && errno != ENOENT) - ret = -errno; - continue; - } - - is_dir = S_ISDIR(st.st_mode); - keep_around = - honour_sticky && - (st.st_uid == 0 || st.st_uid == getuid()) && - (st.st_mode & S_ISVTX); - } else { - is_dir = de->d_type == DT_DIR; - keep_around = false; - } - - if (is_dir) { - int subdir_fd; - - /* if root_dev is set, remove subdirectories only, if device is same as dir */ - if (root_dev && st.st_dev != root_dev->st_dev) - continue; - - subdir_fd = openat(fd, de->d_name, - O_RDONLY|O_NONBLOCK|O_DIRECTORY|O_CLOEXEC|O_NOFOLLOW|O_NOATIME); - if (subdir_fd < 0) { - if (ret == 0 && errno != ENOENT) - ret = -errno; - continue; - } - - r = rm_rf_children_dangerous(subdir_fd, only_dirs, honour_sticky, root_dev); - if (r < 0 && ret == 0) - ret = r; - - if (!keep_around) - if (unlinkat(fd, de->d_name, AT_REMOVEDIR) < 0) { - if (ret == 0 && errno != ENOENT) - ret = -errno; - } - - } else if (!only_dirs && !keep_around) { - - if (unlinkat(fd, de->d_name, 0) < 0) { - if (ret == 0 && errno != ENOENT) - ret = -errno; - } - } - } -} - -_pure_ static int is_temporary_fs(struct statfs *s) { +bool is_temporary_fs(const struct statfs *s) { assert(s); return F_TYPE_EQUAL(s->f_type, TMPFS_MAGIC) || F_TYPE_EQUAL(s->f_type, RAMFS_MAGIC); } -int is_fd_on_temporary_fs(int fd) { +int fd_is_temporary_fs(int fd) { struct statfs s; if (fstatfs(fd, &s) < 0) @@ -3091,114 +3004,6 @@ int is_fd_on_temporary_fs(int fd) { return is_temporary_fs(&s); } -int rm_rf_children(int fd, bool only_dirs, bool honour_sticky, struct stat *root_dev) { - struct statfs s; - - assert(fd >= 0); - - if (fstatfs(fd, &s) < 0) { - safe_close(fd); - return -errno; - } - - /* We refuse to clean disk file systems with this call. This - * is extra paranoia just to be sure we never ever remove - * non-state data */ - if (!is_temporary_fs(&s)) { - log_error("Attempted to remove disk file system, and we can't allow that."); - safe_close(fd); - return -EPERM; - } - - return rm_rf_children_dangerous(fd, only_dirs, honour_sticky, root_dev); -} - -static int file_is_priv_sticky(const char *p) { - struct stat st; - - assert(p); - - if (lstat(p, &st) < 0) - return -errno; - - return - (st.st_uid == 0 || st.st_uid == getuid()) && - (st.st_mode & S_ISVTX); -} - -static int rm_rf_internal(const char *path, bool only_dirs, bool delete_root, bool honour_sticky, bool dangerous) { - int fd, r; - struct statfs s; - - assert(path); - - /* We refuse to clean the root file system with this - * call. This is extra paranoia to never cause a really - * seriously broken system. */ - if (path_equal(path, "/")) { - log_error("Attempted to remove entire root file system, and we can't allow that."); - return -EPERM; - } - - fd = open(path, O_RDONLY|O_NONBLOCK|O_DIRECTORY|O_CLOEXEC|O_NOFOLLOW|O_NOATIME); - if (fd < 0) { - - if (errno != ENOTDIR && errno != ELOOP) - return -errno; - - if (!dangerous) { - if (statfs(path, &s) < 0) - return -errno; - - if (!is_temporary_fs(&s)) { - log_error("Attempted to remove disk file system, and we can't allow that."); - return -EPERM; - } - } - - if (delete_root && !only_dirs) - if (unlink(path) < 0 && errno != ENOENT) - return -errno; - - return 0; - } - - if (!dangerous) { - if (fstatfs(fd, &s) < 0) { - safe_close(fd); - return -errno; - } - - if (!is_temporary_fs(&s)) { - log_error("Attempted to remove disk file system, and we can't allow that."); - safe_close(fd); - return -EPERM; - } - } - - r = rm_rf_children_dangerous(fd, only_dirs, honour_sticky, NULL); - if (delete_root) { - - if (honour_sticky && file_is_priv_sticky(path) > 0) - return r; - - if (rmdir(path) < 0 && errno != ENOENT) { - if (r == 0) - r = -errno; - } - } - - return r; -} - -int rm_rf(const char *path, bool only_dirs, bool delete_root, bool honour_sticky) { - return rm_rf_internal(path, only_dirs, delete_root, honour_sticky, false); -} - -int rm_rf_dangerous(const char *path, bool only_dirs, bool delete_root, bool honour_sticky) { - return rm_rf_internal(path, only_dirs, delete_root, honour_sticky, true); -} - int chmod_and_chown(const char *path, mode_t mode, uid_t uid, gid_t gid) { assert(path); diff --git a/src/shared/util.h b/src/shared/util.h index 124c7c06d49..882355665cf 100644 --- a/src/shared/util.h +++ b/src/shared/util.h @@ -41,6 +41,7 @@ #include #include #include +#include #if SIZEOF_PID_T == 4 # define PID_PRI PRIi32 @@ -461,12 +462,8 @@ int get_ctty(pid_t, dev_t *_devnr, char **r); int chmod_and_chown(const char *path, mode_t mode, uid_t uid, gid_t gid); int fchmod_and_fchown(int fd, mode_t mode, uid_t uid, gid_t gid); -int is_fd_on_temporary_fs(int fd); - -int rm_rf_children(int fd, bool only_dirs, bool honour_sticky, struct stat *root_dev); -int rm_rf_children_dangerous(int fd, bool only_dirs, bool honour_sticky, struct stat *root_dev); -int rm_rf(const char *path, bool only_dirs, bool delete_root, bool honour_sticky); -int rm_rf_dangerous(const char *path, bool only_dirs, bool delete_root, bool honour_sticky); +bool is_temporary_fs(const struct statfs *s) _pure_; +int fd_is_temporary_fs(int fd); int pipe_eof(int fd); diff --git a/src/test/test-conf-files.c b/src/test/test-conf-files.c index 894c7f742ff..01ece022c12 100644 --- a/src/test/test-conf-files.c +++ b/src/test/test-conf-files.c @@ -26,7 +26,7 @@ #include "macro.h" #include "strv.h" #include "util.h" - +#include "rm-rf.h" static void setup_test_dir(char *tmp_dir, const char *files, ...) { va_list ap; @@ -74,7 +74,7 @@ static void test_conf_files_list(bool use_root) { assert_se(streq_ptr(found_files[1], expect_b)); assert_se(found_files[2] == NULL); - assert_se(rm_rf_dangerous(tmp_dir, false, true, false) == 0); + assert_se(rm_rf(tmp_dir, REMOVE_ROOT|REMOVE_PHYSICAL) == 0); } int main(int argc, char **argv) { diff --git a/src/test/test-copy.c b/src/test/test-copy.c index 5c96f610053..403d85bff0f 100644 --- a/src/test/test-copy.c +++ b/src/test/test-copy.c @@ -26,6 +26,7 @@ #include "strv.h" #include "macro.h" #include "util.h" +#include "rm-rf.h" static void test_copy_file(void) { _cleanup_free_ char *buf = NULL; @@ -86,8 +87,8 @@ static void test_copy_tree(void) { "link2", "dir1/file"); char **p, **link; - rm_rf_dangerous(copy_dir, false, true, false); - rm_rf_dangerous(original_dir, false, true, false); + (void) rm_rf(copy_dir, REMOVE_ROOT|REMOVE_PHYSICAL); + (void) rm_rf(original_dir, REMOVE_ROOT|REMOVE_PHYSICAL); STRV_FOREACH(p, files) { char *f = strjoina(original_dir, *p); @@ -128,8 +129,8 @@ static void test_copy_tree(void) { assert_se(copy_tree(original_dir, copy_dir, false) < 0); assert_se(copy_tree("/tmp/inexistent/foo/bar/fsdoi", copy_dir, false) < 0); - rm_rf_dangerous(copy_dir, false, true, false); - rm_rf_dangerous(original_dir, false, true, false); + (void) rm_rf(copy_dir, REMOVE_ROOT|REMOVE_PHYSICAL); + (void) rm_rf(original_dir, REMOVE_ROOT|REMOVE_PHYSICAL); } int main(int argc, char *argv[]) { diff --git a/src/test/test-execute.c b/src/test/test-execute.c index 428fd326003..c6210aaa87b 100644 --- a/src/test/test-execute.c +++ b/src/test/test-execute.c @@ -24,6 +24,7 @@ #include "util.h" #include "macro.h" #include "mkdir.h" +#include "rm-rf.h" typedef void (*test_function_t)(Manager *m); @@ -72,7 +73,7 @@ static void test_exec_workingdirectory(Manager *m) { test(m, "exec-workingdirectory.service", 0, CLD_EXITED); - rm_rf_dangerous("/tmp/test-exec_workingdirectory", false, true, false); + (void) rm_rf("/tmp/test-exec_workingdirectory", REMOVE_ROOT|REMOVE_PHYSICAL); } static void test_exec_personality(Manager *m) { diff --git a/src/test/test-path-lookup.c b/src/test/test-path-lookup.c index a951b01b975..66b0b99741b 100644 --- a/src/test/test-path-lookup.c +++ b/src/test/test-path-lookup.c @@ -24,6 +24,7 @@ #include "path-lookup.h" #include "log.h" #include "strv.h" +#include "rm-rf.h" static void test_paths(SystemdRunningAs running_as, bool personal) { char template[] = "/tmp/test-path-lookup.XXXXXXX"; @@ -42,7 +43,7 @@ static void test_paths(SystemdRunningAs running_as, bool personal) { assert_se(strv_contains(lp.unit_path, exists)); assert_se(strv_contains(lp.unit_path, not)); - assert_se(rm_rf_dangerous(template, false, true, false) >= 0); + assert_se(rm_rf(template, REMOVE_ROOT|REMOVE_PHYSICAL) >= 0); } static void print_generator_paths(SystemdRunningAs running_as) { diff --git a/src/test/test-path-util.c b/src/test/test-path-util.c index 6396fcb3980..759515e5648 100644 --- a/src/test/test-path-util.c +++ b/src/test/test-path-util.c @@ -26,6 +26,7 @@ #include "util.h" #include "macro.h" #include "strv.h" +#include "rm-rf.h" #define test_path_compare(a, b, result) { \ assert_se(path_compare(a, b) == result); \ @@ -256,7 +257,7 @@ static void test_strv_resolve(void) { assert_se(streq(search_dirs[1], "/dir2")); assert_se(streq(search_dirs[2], "/dir2")); - assert_se(rm_rf_dangerous(tmp_dir, false, true, false) == 0); + assert_se(rm_rf(tmp_dir, REMOVE_ROOT|REMOVE_PHYSICAL) == 0); } static void test_path_startswith(void) { diff --git a/src/test/test-path.c b/src/test/test-path.c index a3295aa997d..219b585b7c6 100644 --- a/src/test/test-path.c +++ b/src/test/test-path.c @@ -26,6 +26,7 @@ #include "macro.h" #include "strv.h" #include "mkdir.h" +#include "rm-rf.h" typedef void (*test_function_t)(Manager *m); @@ -47,7 +48,12 @@ static int setup_test(Manager **m) { assert_se(manager_startup(tmp, NULL, NULL) >= 0); STRV_FOREACH(test_path, tests_path) { - rm_rf_dangerous(strjoina("/tmp/test-path_", *test_path), false, true, false); + _cleanup_free_ char *p = NULL; + + p = strjoin("/tmp/test-path_", *test_path, NULL); + assert_se(p); + + (void) rm_rf(p, REMOVE_ROOT|REMOVE_PHYSICAL); } *m = tmp; @@ -104,7 +110,7 @@ static void check_stop_unlink(Manager *m, Unit *unit, const char *test_path, con } assert_se(UNIT_VTABLE(unit)->stop(unit) >= 0); - rm_rf_dangerous(test_path, false, true, false); + (void) rm_rf(test_path, REMOVE_ROOT|REMOVE_PHYSICAL); } static void test_path_exists(Manager *m) { @@ -228,7 +234,7 @@ static void test_path_makedirectory_directorymode(Manager *m) { assert_se((s.st_mode & S_IRWXO) == 0004); assert_se(UNIT_VTABLE(unit)->stop(unit) >= 0); - rm_rf_dangerous(test_path, false, true, false); + (void) rm_rf(test_path, REMOVE_ROOT|REMOVE_PHYSICAL); } int main(int argc, char *argv[]) { diff --git a/src/test/test-util.c b/src/test/test-util.c index d32ddd3a681..e9d1522a65e 100644 --- a/src/test/test-util.c +++ b/src/test/test-util.c @@ -31,6 +31,7 @@ #include "util.h" #include "mkdir.h" +#include "rm-rf.h" #include "strv.h" #include "def.h" #include "fileio.h" @@ -1017,7 +1018,7 @@ static void test_readlink_and_make_absolute(void) { free(r); assert_se(unlink(name_alias) >= 0); - assert_se(rm_rf_dangerous(tempdir, false, true, false) >= 0); + assert_se(rm_rf(tempdir, REMOVE_ROOT|REMOVE_PHYSICAL) >= 0); } static void test_read_one_char(void) { @@ -1274,8 +1275,8 @@ static void test_execute_directory(void) { assert_se(access("it_works2", F_OK) >= 0); assert_se(access("failed", F_OK) < 0); - rm_rf_dangerous(template_lo, false, true, false); - rm_rf_dangerous(template_hi, false, true, false); + (void) rm_rf(template_lo, REMOVE_ROOT|REMOVE_PHYSICAL); + (void) rm_rf(template_hi, REMOVE_ROOT|REMOVE_PHYSICAL); } static void test_unquote_first_word(void) { diff --git a/src/tmpfiles/tmpfiles.c b/src/tmpfiles/tmpfiles.c index 494fd1ac212..20972f63103 100644 --- a/src/tmpfiles/tmpfiles.c +++ b/src/tmpfiles/tmpfiles.c @@ -52,6 +52,7 @@ #include "specifier.h" #include "build.h" #include "copy.h" +#include "rm-rf.h" #include "selinux-util.h" #include "btrfs-util.h" #include "acl-util.h" @@ -1359,7 +1360,7 @@ static int remove_item_instance(Item *i, const char *instance) { /* 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_dangerous(instance, false, i->type == RECURSIVE_REMOVE_PATH, false); + r = rm_rf(instance, (i->type == RECURSIVE_REMOVE_PATH ? REMOVE_ROOT : 0) | REMOVE_PHYSICAL); if (r < 0 && r != -ENOENT) return log_error_errno(r, "rm_rf(%s): %m", instance);