mirror of
https://github.com/systemd/systemd.git
synced 2025-01-09 01:18:19 +03:00
core,systemctl: add new "systemctl revert" command
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.
This commit is contained in:
parent
d096025b81
commit
344ca7556b
@ -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>
|
||||
|
@ -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),
|
||||
|
@ -174,6 +174,10 @@
|
||||
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"/>
|
||||
|
@ -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,
|
||||
|
@ -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);
|
||||
|
@ -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 },
|
||||
|
@ -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);
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user