]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
core,systemctl: add new "systemctl revert" command
authorLennart Poettering <lennart@poettering.net>
Fri, 8 Apr 2016 16:00:36 +0000 (18:00 +0200)
committerLennart Poettering <lennart@poettering.net>
Tue, 12 Apr 2016 11:43:32 +0000 (13:43 +0200)
This allows dropping all user configuration and reverting back to the vendor
default of a unit file. It basically undoes what "systemctl edit", "systemctl
set-property" and "systemctl mask" do.

man/systemctl.xml
src/core/dbus-manager.c
src/core/org.freedesktop.systemd1.conf
src/shared/install.c
src/shared/install.h
src/systemctl/systemctl.c
src/test/test-install-root.c

index 064777810f7fb41bc53a1d390e7e99f4968f7c3d..5f624243f7427beaad923b7944a45e4c930d9536 100644 (file)
@@ -1244,6 +1244,28 @@ kobject-uevent 1 systemd-udevd-kernel.socket systemd-udevd.service
           </listitem>
         </varlistentry>
 
+        <varlistentry>
+          <term><command>revert <replaceable>NAME</replaceable>...</command></term>
+
+          <listitem>
+            <para>Revert one or more unit files to their vendor versions. This command removes drop-in configuration
+            files that modify the specified units, as well as any user-configured unit file that overrides a matching
+            vendor supplied unit file. Specifically, for a unit <literal>foo.service</literal> the matching directories
+            <literal>foo.service.d/</literal> with all their contained files are removed, both below the persistent and
+            runtime configuration directories (i.e. below <filename>/etc/systemd/system</filename> and
+            <filename>/run/systemd/system</filename>); if the unit file has a vendor-supplied version (i.e. a unit file
+            located below <filename>/usr</filename>) any matching peristent or runtime unit file that overrides it is
+            removed, too. Note that if a unit file has no vendor-supplied version (i.e. is only defined below
+            <filename>/etc/systemd/system</filename> or <filename>/run/systemd/system</filename>, but not in a unit
+            file stored below <filename>/usr</filename>), then it is not removed. Also, if a unit is masked, it is
+            unmasked.</para>
+
+            <para>Effectively, this command may be used to undo all changes made with <command>systemctl
+            edit</command>, <command>systemctl set-property</command> and <command>systemctl mask</command> and puts
+            the original unit file with its settings back in effect.</para>
+          </listitem>
+        </varlistentry>
+
         <varlistentry>
           <term><command>add-wants <replaceable>TARGET</replaceable>
           <replaceable>NAME</replaceable>...</command></term>
index f0ee2fcb36222879db1232ee4cbda9f509f7f820..41f27ec26bb1e6fe85dab1631278c586b3d3ade7 100644 (file)
@@ -1758,6 +1758,33 @@ static int method_unmask_unit_files(sd_bus_message *message, void *userdata, sd_
         return method_disable_unit_files_generic(message, userdata, "enable", unit_file_unmask, error);
 }
 
+static int method_revert_unit_files(sd_bus_message *message, void *userdata, sd_bus_error *error) {
+        _cleanup_strv_free_ char **l = NULL;
+        UnitFileChange *changes = NULL;
+        unsigned n_changes = 0;
+        Manager *m = userdata;
+        int r;
+
+        assert(message);
+        assert(m);
+
+        r = sd_bus_message_read_strv(message, &l);
+        if (r < 0)
+                return r;
+
+        r = bus_verify_manage_unit_files_async(m, message, error);
+        if (r < 0)
+                return r;
+        if (r == 0)
+                return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */
+
+        r = unit_file_revert(m->unit_file_scope, NULL, l, &changes, &n_changes);
+        if (r < 0)
+                return r;
+
+        return reply_unit_file_changes_and_free(m, message, -1, changes, n_changes);
+}
+
 static int method_set_default_target(sd_bus_message *message, void *userdata, sd_bus_error *error) {
         UnitFileChange *changes = NULL;
         unsigned n_changes = 0;
@@ -2005,6 +2032,7 @@ const sd_bus_vtable bus_manager_vtable[] = {
         SD_BUS_METHOD("PresetUnitFilesWithMode", "assbb", "ba(sss)", method_preset_unit_files_with_mode, SD_BUS_VTABLE_UNPRIVILEGED),
         SD_BUS_METHOD("MaskUnitFiles", "asbb", "a(sss)", method_mask_unit_files, SD_BUS_VTABLE_UNPRIVILEGED),
         SD_BUS_METHOD("UnmaskUnitFiles", "asb", "a(sss)", method_unmask_unit_files, SD_BUS_VTABLE_UNPRIVILEGED),
+        SD_BUS_METHOD("RevertUnitFiles", "as", "a(sss)", method_revert_unit_files, SD_BUS_VTABLE_UNPRIVILEGED),
         SD_BUS_METHOD("SetDefaultTarget", "sb", "a(sss)", method_set_default_target, SD_BUS_VTABLE_UNPRIVILEGED),
         SD_BUS_METHOD("GetDefaultTarget", NULL, "s", method_get_default_target, SD_BUS_VTABLE_UNPRIVILEGED),
         SD_BUS_METHOD("PresetAllUnitFiles", "sbb", "a(sss)", method_preset_all_unit_files, SD_BUS_VTABLE_UNPRIVILEGED),
index 6a7a37ee924f57d11fa92739ad84c98580cb6888..f78eedbd6e097d961dcdea25a9e9a11f00f04d44 100644 (file)
                        send_interface="org.freedesktop.systemd1.Manager"
                        send_member="LinkUnitFiles"/>
 
+                <allow send_destination="org.freedesktop.systemd1"
+                       send_interface="org.freedesktop.systemd1.Manager"
+                       send_member="RevertUnitFiles"/>
+
                 <allow send_destination="org.freedesktop.systemd1"
                        send_interface="org.freedesktop.systemd1.Manager"
                        send_member="PresetUnitFiles"/>
index 8fc9205107c473277b62937868ed0e12245843aa..8d9f96553b3885d3fd9f82c7e2a4c68369181885 100644 (file)
@@ -45,6 +45,7 @@
 #include "mkdir.h"
 #include "path-lookup.h"
 #include "path-util.h"
+#include "rm-rf.h"
 #include "set.h"
 #include "special.h"
 #include "stat-util.h"
@@ -136,27 +137,35 @@ static int path_is_transient(const LookupPaths *p, const char *path) {
         return path_equal(p->transient, parent);
 }
 
-static int path_is_config(const LookupPaths *p, const char *path) {
+static int path_is_control(const LookupPaths *p, const char *path) {
         _cleanup_free_ char *parent = NULL;
-        const char *rpath;
 
         assert(p);
         assert(path);
 
-        /* Checks whether the specified path is intended for configuration or is outside of it. We check both the
-         * top-level directory and the one actually configured. The latter is particularly relevant for cases where we
-         * operate on a root directory. */
+        parent = dirname_malloc(path);
+        if (!parent)
+                return -ENOMEM;
 
-        rpath = skip_root(p, path);
-        if (rpath && (path_startswith(rpath, "/etc") || path_startswith(rpath, "/run")))
-                return true;
+        return path_equal(parent, p->persistent_control) ||
+                path_equal(parent, p->runtime_control);
+}
+
+static int path_is_config(const LookupPaths *p, const char *path) {
+        _cleanup_free_ char *parent = NULL;
+
+        assert(p);
+        assert(path);
+
+        /* Note that we do *not* have generic checks for /etc or /run in place, since with them we couldn't discern
+         * configuration from transient or generated units */
 
         parent = dirname_malloc(path);
         if (!parent)
                 return -ENOMEM;
 
         return path_equal(parent, p->persistent_config) ||
-                path_equal(parent, p->runtime_config);
+               path_equal(parent, p->runtime_config);
 }
 
 static int path_is_runtime(const LookupPaths *p, const char *path) {
@@ -166,6 +175,9 @@ static int path_is_runtime(const LookupPaths *p, const char *path) {
         assert(p);
         assert(path);
 
+        /* Everything in /run is considered runtime. On top of that we also add explicit checks for the various runtime
+         * directories, as safety net. */
+
         rpath = skip_root(p, path);
         if (rpath && path_startswith(rpath, "/run"))
                 return true;
@@ -174,7 +186,33 @@ static int path_is_runtime(const LookupPaths *p, const char *path) {
         if (!parent)
                 return -ENOMEM;
 
-        return path_equal(parent, p->runtime_config);
+        return path_equal(parent, p->runtime_config) ||
+               path_equal(parent, p->generator) ||
+               path_equal(parent, p->generator_early) ||
+               path_equal(parent, p->generator_late) ||
+               path_equal(parent, p->transient) ||
+               path_equal(parent, p->runtime_control);
+}
+
+static int path_is_vendor(const LookupPaths *p, const char *path) {
+        const char *rpath;
+
+        assert(p);
+        assert(path);
+
+        rpath = skip_root(p, path);
+        if (!rpath)
+                return 0;
+
+        if (path_startswith(rpath, "/usr"))
+                return true;
+
+#ifdef HAVE_SPLIT_USR
+        if (path_startswith(rpath, "/lib"))
+                return true;
+#endif
+
+        return path_equal(rpath, SYSTEM_DATA_UNIT_PATH);
 }
 
 int unit_file_changes_add(
@@ -1703,6 +1741,182 @@ int unit_file_link(
         return r;
 }
 
+static int path_shall_revert(const LookupPaths *paths, const char *path) {
+        int r;
+
+        assert(paths);
+        assert(path);
+
+        /* Checks whether the path is one where the drop-in directories shall be removed. */
+
+        r = path_is_config(paths, path);
+        if (r != 0)
+                return r;
+
+        r = path_is_control(paths, path);
+        if (r != 0)
+                return r;
+
+        return path_is_transient(paths, path);
+}
+
+int unit_file_revert(
+                UnitFileScope scope,
+                const char *root_dir,
+                char **files,
+                UnitFileChange **changes,
+                unsigned *n_changes) {
+
+        _cleanup_set_free_free_ Set *remove_symlinks_to = NULL;
+        /* _cleanup_(install_context_done) InstallContext c = {}; */
+        _cleanup_lookup_paths_free_ LookupPaths paths = {};
+        _cleanup_strv_free_ char **todo = NULL;
+        size_t n_todo = 0, n_allocated = 0;
+        char **i;
+        int r, q;
+
+        /* Puts a unit file back into vendor state. This means:
+         *
+         * a) we remove all drop-in snippets added by the user ("config"), add to transient units ("transient"), and
+         *    added via "systemctl set-property" ("control"), but not if the drop-in is generated ("generated").
+         *
+         * c) if there's a vendor unit file (i.e. one in /usr) we remove any configured overriding unit files (i.e. in
+         *    "config", but not in "transient" or "control" or even "generated").
+         *
+         * We remove all that in both the runtime and the persistant directories, if that applies.
+         */
+
+        r = lookup_paths_init(&paths, scope, 0, root_dir);
+        if (r < 0)
+                return r;
+
+        STRV_FOREACH(i, files) {
+                bool has_vendor = false;
+                char **p;
+
+                if (!unit_name_is_valid(*i, UNIT_NAME_ANY))
+                        return -EINVAL;
+
+                STRV_FOREACH(p, paths.search_path) {
+                        _cleanup_free_ char *path = NULL, *dropin = NULL;
+                        struct stat st;
+
+                        path = path_make_absolute(*i, *p);
+                        if (!path)
+                                return -ENOMEM;
+
+                        r = lstat(path, &st);
+                        if (r < 0) {
+                                if (errno != ENOENT)
+                                        return -errno;
+                        } else if (S_ISREG(st.st_mode)) {
+                                /* Check if there's a vendor version */
+                                r = path_is_vendor(&paths, path);
+                                if (r < 0)
+                                        return r;
+                                if (r > 0)
+                                        has_vendor = true;
+                        }
+
+                        dropin = strappend(path, ".d");
+                        if (!dropin)
+                                return -ENOMEM;
+
+                        r = lstat(dropin, &st);
+                        if (r < 0) {
+                                if (errno != ENOENT)
+                                        return -errno;
+                        } else if (S_ISDIR(st.st_mode)) {
+                                /* Remove the drop-ins */
+                                r = path_shall_revert(&paths, dropin);
+                                if (r < 0)
+                                        return r;
+                                if (r > 0) {
+                                        if (!GREEDY_REALLOC0(todo, n_allocated, n_todo + 2))
+                                                return -ENOMEM;
+
+                                        todo[n_todo++] = dropin;
+                                        dropin = NULL;
+                                }
+                        }
+                }
+
+                if (!has_vendor)
+                        continue;
+
+                /* OK, there's a vendor version, hence drop all configuration versions */
+                STRV_FOREACH(p, paths.search_path) {
+                        _cleanup_free_ char *path = NULL;
+                        struct stat st;
+
+                        path = path_make_absolute(*i, *p);
+                        if (!path)
+                                return -ENOMEM;
+
+                        r = lstat(path, &st);
+                        if (r < 0) {
+                                if (errno != ENOENT)
+                                        return -errno;
+                        } else if (S_ISREG(st.st_mode) || S_ISLNK(st.st_mode)) {
+                                r = path_is_config(&paths, path);
+                                if (r < 0)
+                                        return r;
+                                if (r > 0) {
+                                        if (!GREEDY_REALLOC0(todo, n_allocated, n_todo + 2))
+                                                return -ENOMEM;
+
+                                        todo[n_todo++] = path;
+                                        path = NULL;
+                                }
+                        }
+                }
+        }
+
+        strv_uniq(todo);
+
+        r = 0;
+        STRV_FOREACH(i, todo) {
+                _cleanup_strv_free_ char **fs = NULL;
+                const char *rp;
+                char **j;
+
+                (void) get_files_in_directory(*i, &fs);
+
+                q = rm_rf(*i, REMOVE_ROOT|REMOVE_PHYSICAL);
+                if (q < 0 && q != -ENOENT && r >= 0) {
+                        r = q;
+                        continue;
+                }
+
+                STRV_FOREACH(j, fs) {
+                        _cleanup_free_ char *t = NULL;
+
+                        t = strjoin(*i, "/", *j, NULL);
+                        if (!t)
+                                return -ENOMEM;
+
+                        unit_file_changes_add(changes, n_changes, UNIT_FILE_UNLINK, t, NULL);
+                }
+
+                unit_file_changes_add(changes, n_changes, UNIT_FILE_UNLINK, *i, NULL);
+
+                rp = skip_root(&paths, *i);
+                q = mark_symlink_for_removal(&remove_symlinks_to, rp ?: *i);
+                if (q < 0)
+                        return q;
+        }
+
+        q = remove_marked_symlinks(remove_symlinks_to, paths.runtime_config, &paths, changes, n_changes);
+        if (r >= 0)
+                r = q;
+
+        q = remove_marked_symlinks(remove_symlinks_to, paths.persistent_config, &paths, changes, n_changes);
+        if (r >= 0)
+                r = q;
+
+        return r;
+}
+
 int unit_file_add_dependency(
                 UnitFileScope scope,
                 bool runtime,
index 8d54fd14ad7b698df2e97802870bea8783293328..6f8b45d4f6c094b245f3c90302770744350ab84d 100644 (file)
@@ -128,11 +128,12 @@ static inline bool UNIT_FILE_INSTALL_INFO_HAS_ALSO(UnitFileInstallInfo *i) {
 int unit_file_enable(UnitFileScope scope, bool runtime, const char *root_dir, char **files, bool force, UnitFileChange **changes, unsigned *n_changes);
 int unit_file_disable(UnitFileScope scope, bool runtime, const char *root_dir, char **files, UnitFileChange **changes, unsigned *n_changes);
 int unit_file_reenable(UnitFileScope scope, bool runtime, const char *root_dir, char **files, bool force, UnitFileChange **changes, unsigned *n_changes);
-int unit_file_link(UnitFileScope scope, bool runtime, const char *root_dir, char **files, bool force, UnitFileChange **changes, unsigned *n_changes);
 int unit_file_preset(UnitFileScope scope, bool runtime, const char *root_dir, char **files, UnitFilePresetMode mode, bool force, UnitFileChange **changes, unsigned *n_changes);
 int unit_file_preset_all(UnitFileScope scope, bool runtime, const char *root_dir, UnitFilePresetMode mode, bool force, UnitFileChange **changes, unsigned *n_changes);
 int unit_file_mask(UnitFileScope scope, bool runtime, const char *root_dir, char **files, bool force, UnitFileChange **changes, unsigned *n_changes);
 int unit_file_unmask(UnitFileScope scope, bool runtime, const char *root_dir, char **files, UnitFileChange **changes, unsigned *n_changes);
+int unit_file_link(UnitFileScope scope, bool runtime, const char *root_dir, char **files, bool force, UnitFileChange **changes, unsigned *n_changes);
+int unit_file_revert(UnitFileScope scope, const char *root_dir, char **files, UnitFileChange **changes, unsigned *n_changes);
 int unit_file_set_default(UnitFileScope scope, const char *root_dir, const char *file, bool force, UnitFileChange **changes, unsigned *n_changes);
 int unit_file_get_default(UnitFileScope scope, const char *root_dir, char **name);
 int unit_file_add_dependency(UnitFileScope scope, bool runtime, const char *root_dir, char **files, const char *target, UnitDependency dep, bool force, UnitFileChange **changes, unsigned *n_changes);
index 3344d25f7724692a3e7c5080dd29b5f062dadca9..b94af9cf084209092718d7a6b453a7d8221c68e9 100644 (file)
@@ -1993,7 +1993,7 @@ static void dump_unit_file_changes(const UnitFileChange *changes, unsigned n_cha
                 if (changes[i].type == UNIT_FILE_SYMLINK)
                         log_info("Created symlink %s, pointing to %s.", changes[i].path, changes[i].source);
                 else
-                        log_info("Removed symlink %s.", changes[i].path);
+                        log_info("Removed %s.", changes[i].path);
         }
 }
 
@@ -5437,6 +5437,8 @@ static int enable_unit(int argc, char *argv[], void *userdata) {
                         r = unit_file_mask(arg_scope, arg_runtime, arg_root, names, arg_force, &changes, &n_changes);
                 else if (streq(verb, "unmask"))
                         r = unit_file_unmask(arg_scope, arg_runtime, arg_root, names, &changes, &n_changes);
+                else if (streq(verb, "revert"))
+                        r = unit_file_revert(arg_scope, arg_root, names, &changes, &n_changes);
                 else
                         assert_not_reached("Unknown verb");
 
@@ -5455,7 +5457,7 @@ static int enable_unit(int argc, char *argv[], void *userdata) {
                 _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL, *m = NULL;
                 _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
                 int expect_carries_install_info = false;
-                bool send_force = true, send_preset_mode = false;
+                bool send_runtime = true, send_force = true, send_preset_mode = false;
                 const char *method;
                 sd_bus *bus;
 
@@ -5490,6 +5492,9 @@ static int enable_unit(int argc, char *argv[], void *userdata) {
                 else if (streq(verb, "unmask")) {
                         method = "UnmaskUnitFiles";
                         send_force = false;
+                } else if (streq(verb, "revert")) {
+                        method = "RevertUnitFiles";
+                        send_runtime = send_force = false;
                 } else
                         assert_not_reached("Unknown verb");
 
@@ -5513,9 +5518,11 @@ static int enable_unit(int argc, char *argv[], void *userdata) {
                                 return bus_log_create_error(r);
                 }
 
-                r = sd_bus_message_append(m, "b", arg_runtime);
-                if (r < 0)
-                        return bus_log_create_error(r);
+                if (send_runtime) {
+                        r = sd_bus_message_append(m, "b", arg_runtime);
+                        if (r < 0)
+                                return bus_log_create_error(r);
+                }
 
                 if (send_force) {
                         r = sd_bus_message_append(m, "b", arg_force);
@@ -6280,6 +6287,8 @@ static void systemctl_help(void) {
                "  unmask NAME...                  Unmask one or more units\n"
                "  link PATH...                    Link one or more units files into\n"
                "                                  the search path\n"
+               "  revert NAME...                  Revert one or more unit files to vendor\n"
+               "                                  version\n"
                "  add-wants TARGET NAME...        Add 'Wants' dependency for the target\n"
                "                                  on specified one or more units\n"
                "  add-requires TARGET NAME...     Add 'Requires' dependency for the target\n"
@@ -7403,6 +7412,7 @@ static int systemctl_main(int argc, char *argv[]) {
                 { "mask",                  2,        VERB_ANY, 0,             enable_unit       },
                 { "unmask",                2,        VERB_ANY, 0,             enable_unit       },
                 { "link",                  2,        VERB_ANY, 0,             enable_unit       },
+                { "revert",                2,        VERB_ANY, 0,             enable_unit       },
                 { "switch-root",           2,        VERB_ANY, VERB_NOCHROOT, switch_root       },
                 { "list-dependencies",     VERB_ANY, 2,        VERB_NOCHROOT, list_dependencies },
                 { "set-default",           2,        2,        0,             set_default       },
index bc4206a1b0d543a53c21fb5f54d004997046c6e5..2138655e291589f5014ce59eac82aa515c8f2984 100644 (file)
@@ -630,6 +630,57 @@ static void test_preset_and_list(const char *root) {
         assert_se(got_yes && got_no);
 }
 
+static void test_revert(const char *root) {
+        const char *p;
+        UnitFileState state;
+        UnitFileChange *changes = NULL;
+        unsigned n_changes = 0;
+
+        assert(root);
+
+        assert_se(unit_file_get_state(UNIT_FILE_SYSTEM, root, "xx.service", NULL) == -ENOENT);
+        assert_se(unit_file_get_state(UNIT_FILE_SYSTEM, root, "yy.service", NULL) == -ENOENT);
+
+        p = strjoina(root, "/usr/lib/systemd/system/xx.service");
+        assert_se(write_string_file(p, "# Empty\n", WRITE_STRING_FILE_CREATE) >= 0);
+
+        assert_se(unit_file_get_state(UNIT_FILE_SYSTEM, root, "xx.service", NULL) >= 0);
+        assert_se(unit_file_get_state(UNIT_FILE_SYSTEM, root, "xx.service", &state) >= 0 && state == UNIT_FILE_STATIC);
+
+        /* Initially there's nothing to revert */
+        assert_se(unit_file_revert(UNIT_FILE_SYSTEM, root, STRV_MAKE("xx.service"), &changes, &n_changes) >= 0);
+        assert_se(n_changes == 0);
+        unit_file_changes_free(changes, n_changes);
+        changes = NULL; n_changes = 0;
+
+        p = strjoina(root, SYSTEM_CONFIG_UNIT_PATH"/xx.service");
+        assert_se(write_string_file(p, "# Empty override\n", WRITE_STRING_FILE_CREATE) >= 0);
+
+        /* Revert the override file */
+        assert_se(unit_file_revert(UNIT_FILE_SYSTEM, root, STRV_MAKE("xx.service"), &changes, &n_changes) >= 0);
+        assert_se(n_changes == 1);
+        assert_se(changes[0].type == UNIT_FILE_UNLINK);
+        assert_se(streq(changes[0].path, p));
+        unit_file_changes_free(changes, n_changes);
+        changes = NULL; n_changes = 0;
+
+        p = strjoina(root, SYSTEM_CONFIG_UNIT_PATH"/xx.service.d/dropin.conf");
+        assert_se(mkdir_parents(p, 0755) >= 0);
+        assert_se(write_string_file(p, "# Empty dropin\n", WRITE_STRING_FILE_CREATE) >= 0);
+
+        /* Revert the dropin file */
+        assert_se(unit_file_revert(UNIT_FILE_SYSTEM, root, STRV_MAKE("xx.service"), &changes, &n_changes) >= 0);
+        assert_se(n_changes == 2);
+        assert_se(changes[0].type == UNIT_FILE_UNLINK);
+        assert_se(streq(changes[0].path, p));
+
+        p = strjoina(root, SYSTEM_CONFIG_UNIT_PATH"/xx.service.d");
+        assert_se(changes[1].type == UNIT_FILE_UNLINK);
+        assert_se(streq(changes[1].path, p));
+        unit_file_changes_free(changes, n_changes);
+        changes = NULL; n_changes = 0;
+}
+
 int main(int argc, char *argv[]) {
         char root[] = "/tmp/rootXXXXXX";
         const char *p;
@@ -658,6 +709,7 @@ int main(int argc, char *argv[]) {
         test_template_enable(root);
         test_indirect(root);
         test_preset_and_list(root);
+        test_revert(root);
 
         assert_se(rm_rf(root, REMOVE_ROOT|REMOVE_PHYSICAL) >= 0);