diff options
Diffstat (limited to 'src/libshared/install.c')
-rw-r--r-- | src/libshared/install.c | 1299 |
1 files changed, 889 insertions, 410 deletions
diff --git a/src/libshared/install.c b/src/libshared/install.c index ef8f485cae..64d66a45d3 100644 --- a/src/libshared/install.c +++ b/src/libshared/install.c @@ -40,11 +40,13 @@ #include "hashmap.h" #include "install-printf.h" #include "install.h" +#include "locale-util.h" #include "log.h" #include "macro.h" #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" @@ -65,7 +67,65 @@ typedef struct { OrderedHashmap *have_processed; } InstallContext; -static int in_search_path(const char *path, char **search) { +typedef enum { + PRESET_UNKNOWN, + PRESET_ENABLE, + PRESET_DISABLE, +} PresetAction; + +typedef struct { + char *pattern; + PresetAction action; +} PresetRule; + +typedef struct { + PresetRule *rules; + size_t n_rules; +} Presets; + +static inline void presets_freep(Presets *p) { + size_t i; + + if (!p) + return; + + for (i = 0; i < p->n_rules; i++) + free(p->rules[i].pattern); + + free(p->rules); + p->n_rules = 0; +} + +static int unit_file_lookup_state(UnitFileScope scope, const LookupPaths *paths, const char *name, UnitFileState *ret); + +bool unit_type_may_alias(UnitType type) { + return IN_SET(type, + UNIT_SERVICE, + UNIT_SOCKET, + UNIT_TARGET, + UNIT_DEVICE, + UNIT_TIMER, + UNIT_PATH); +} + +bool unit_type_may_template(UnitType type) { + return IN_SET(type, + UNIT_SERVICE, + UNIT_SOCKET, + UNIT_TARGET, + UNIT_TIMER, + UNIT_PATH); +} + +static const char *unit_file_type_table[_UNIT_FILE_TYPE_MAX] = { + [UNIT_FILE_TYPE_REGULAR] = "regular", + [UNIT_FILE_TYPE_SYMLINK] = "symlink", + [UNIT_FILE_TYPE_MASKED] = "masked", +}; + +DEFINE_PRIVATE_STRING_TABLE_LOOKUP_TO_STRING(unit_file_type, UnitFileType); + +static int in_search_path(const LookupPaths *p, const char *path) { _cleanup_free_ char *parent = NULL; char **i; @@ -75,141 +135,141 @@ static int in_search_path(const char *path, char **search) { if (!parent) return -ENOMEM; - STRV_FOREACH(i, search) + STRV_FOREACH(i, p->search_path) if (path_equal(parent, *i)) return true; return false; } -static int get_config_path(UnitFileScope scope, bool runtime, const char *root_dir, char **ret) { - char *p = NULL; - int r; - - assert(scope >= 0); - assert(scope < _UNIT_FILE_SCOPE_MAX); - assert(ret); +static const char* skip_root(const LookupPaths *p, const char *path) { + char *e; - /* This determines where we shall create or remove our - * installation ("configuration") symlinks */ + assert(p); + assert(path); - switch (scope) { + if (!p->root_dir) + return path; - case UNIT_FILE_SYSTEM: + e = path_startswith(path, p->root_dir); + if (!e) + return NULL; - if (runtime) - p = path_join(root_dir, "/run/systemd/system", NULL); - else - p = path_join(root_dir, SYSTEM_CONFIG_UNIT_PATH, NULL); - break; + /* Make sure the returned path starts with a slash */ + if (e[0] != '/') { + if (e == path || e[-1] != '/') + return NULL; - case UNIT_FILE_GLOBAL: + e--; + } - if (root_dir) - return -EINVAL; + return e; +} - if (runtime) - p = strdup("/run/systemd/user"); - else - p = strdup(USER_CONFIG_UNIT_PATH); - break; +static int path_is_generator(const LookupPaths *p, const char *path) { + _cleanup_free_ char *parent = NULL; - case UNIT_FILE_USER: + assert(p); + assert(path); - if (root_dir) - return -EINVAL; + parent = dirname_malloc(path); + if (!parent) + return -ENOMEM; - if (runtime) - r = user_runtime_dir(&p); - else - r = user_config_home(&p); - if (r < 0) - return r; - if (r == 0) - return -ENOENT; + return path_equal_ptr(parent, p->generator) || + path_equal_ptr(parent, p->generator_early) || + path_equal_ptr(parent, p->generator_late); +} - break; +static int path_is_transient(const LookupPaths *p, const char *path) { + _cleanup_free_ char *parent = NULL; - default: - assert_not_reached("Bad scope"); - } + assert(p); + assert(path); - if (!p) + parent = dirname_malloc(path); + if (!parent) return -ENOMEM; - *ret = p; - return 0; + return path_equal_ptr(parent, p->transient); } -static bool is_config_path(UnitFileScope scope, const char *path) { - int r; +static int path_is_control(const LookupPaths *p, const char *path) { + _cleanup_free_ char *parent = NULL; - assert(scope >= 0); - assert(scope < _UNIT_FILE_SCOPE_MAX); + assert(p); assert(path); - /* Checks whether the specified path is intended for - * configuration or is outside of it */ + parent = dirname_malloc(path); + if (!parent) + return -ENOMEM; - switch (scope) { + return path_equal_ptr(parent, p->persistent_control) || + path_equal_ptr(parent, p->runtime_control); +} - case UNIT_FILE_SYSTEM: - case UNIT_FILE_GLOBAL: - return path_startswith(path, "/etc") || - path_startswith(path, SYSTEM_CONFIG_UNIT_PATH) || - path_startswith(path, "/run"); +static int path_is_config(const LookupPaths *p, const char *path) { + _cleanup_free_ char *parent = NULL; + assert(p); + assert(path); - case UNIT_FILE_USER: { - _cleanup_free_ char *p = NULL; + /* 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 */ - r = user_config_home(&p); - if (r < 0) - return r; - if (r > 0 && path_startswith(path, p)) - return true; + parent = dirname_malloc(path); + if (!parent) + return -ENOMEM; - p = mfree(p); + return path_equal_ptr(parent, p->persistent_config) || + path_equal_ptr(parent, p->runtime_config); +} - r = user_runtime_dir(&p); - if (r < 0) - return r; - if (r > 0 && path_startswith(path, p)) - return true; +static int path_is_runtime(const LookupPaths *p, const char *path) { + _cleanup_free_ char *parent = NULL; + const char *rpath; - return false; - } + assert(p); + assert(path); - default: - assert_not_reached("Bad scope"); - } -} + /* 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; -static int verify_root_dir(UnitFileScope scope, const char **root_dir) { - int r; + parent = dirname_malloc(path); + if (!parent) + return -ENOMEM; + + return path_equal_ptr(parent, p->runtime_config) || + path_equal_ptr(parent, p->generator) || + path_equal_ptr(parent, p->generator_early) || + path_equal_ptr(parent, p->generator_late) || + path_equal_ptr(parent, p->transient) || + path_equal_ptr(parent, p->runtime_control); +} - assert(root_dir); +static int path_is_vendor(const LookupPaths *p, const char *path) { + const char *rpath; - /* Verifies that the specified root directory to operate on - * makes sense. Reset it to NULL if it is the root directory - * or set to empty */ + assert(p); + assert(path); - if (isempty(*root_dir) || path_equal(*root_dir, "/")) { - *root_dir = NULL; + rpath = skip_root(p, path); + if (!rpath) return 0; - } - if (scope != UNIT_FILE_SYSTEM) - return -EINVAL; + if (path_startswith(rpath, "/usr")) + return true; - r = is_dir(*root_dir, true); - if (r < 0) - return r; - if (r == 0) - return -ENOTDIR; +#ifdef HAVE_SPLIT_USR + if (path_startswith(rpath, "/lib")) + return true; +#endif - return 0; + return path_equal(rpath, SYSTEM_DATA_UNIT_PATH); } int unit_file_changes_add( @@ -219,8 +279,8 @@ int unit_file_changes_add( const char *path, const char *source) { + _cleanup_free_ char *p = NULL, *s = NULL; UnitFileChange *c; - unsigned i; assert(path); assert(!changes == !n_changes); @@ -231,29 +291,22 @@ int unit_file_changes_add( c = realloc(*changes, (*n_changes + 1) * sizeof(UnitFileChange)); if (!c) return -ENOMEM; - *changes = c; - i = *n_changes; - - c[i].type = type; - c[i].path = strdup(path); - if (!c[i].path) - return -ENOMEM; - path_kill_slashes(c[i].path); + p = strdup(path); + if (source) + s = strdup(source); - if (source) { - c[i].source = strdup(source); - if (!c[i].source) { - free(c[i].path); - return -ENOMEM; - } + if (!p || (source && !s)) + return -ENOMEM; - path_kill_slashes(c[i].path); - } else - c[i].source = NULL; + path_kill_slashes(p); + if (s) + path_kill_slashes(s); - *n_changes = i+1; + c[*n_changes] = (UnitFileChange) { type, p, s }; + p = s = NULL; + (*n_changes) ++; return 0; } @@ -262,9 +315,6 @@ void unit_file_changes_free(UnitFileChange *changes, unsigned n_changes) { assert(changes || n_changes == 0); - if (!changes) - return; - for (i = 0; i < n_changes; i++) { free(changes[i].path); free(changes[i].source); @@ -273,6 +323,76 @@ void unit_file_changes_free(UnitFileChange *changes, unsigned n_changes) { free(changes); } +void unit_file_dump_changes(int r, const char *verb, const UnitFileChange *changes, unsigned n_changes, bool quiet) { + unsigned i; + bool logged = false; + + assert(changes || n_changes == 0); + /* If verb is not specified, errors are not allowed! */ + assert(verb || r >= 0); + + for (i = 0; i < n_changes; i++) { + assert(verb || changes[i].type >= 0); + + switch(changes[i].type) { + case UNIT_FILE_SYMLINK: + if (!quiet) + log_info("Created symlink %s %s %s.", + changes[i].path, + special_glyph(ARROW), + changes[i].source); + break; + case UNIT_FILE_UNLINK: + if (!quiet) + log_info("Removed %s.", changes[i].path); + break; + case UNIT_FILE_IS_MASKED: + if (!quiet) + log_info("Unit %s is masked, ignoring.", changes[i].path); + break; + case UNIT_FILE_IS_DANGLING: + if (!quiet) + log_info("Unit %s is an alias to a unit that is not present, ignoring.", + changes[i].path); + break; + case -EEXIST: + if (changes[i].source) + log_error_errno(changes[i].type, + "Failed to %s unit, file %s already exists and is a symlink to %s.", + verb, changes[i].path, changes[i].source); + else + log_error_errno(changes[i].type, + "Failed to %s unit, file %s already exists.", + verb, changes[i].path); + logged = true; + break; + case -ERFKILL: + log_error_errno(changes[i].type, "Failed to %s unit, unit %s is masked.", + verb, changes[i].path); + logged = true; + break; + case -EADDRNOTAVAIL: + log_error_errno(changes[i].type, "Failed to %s unit, unit %s is transient or generated.", + verb, changes[i].path); + logged = true; + break; + case -ELOOP: + log_error_errno(changes[i].type, "Failed to %s unit, refusing to operate on linked unit file %s", + verb, changes[i].path); + logged = true; + break; + default: + assert(changes[i].type < 0); + log_error_errno(changes[i].type, "Failed to %s unit, file %s: %m.", + verb, changes[i].path); + logged = true; + } + } + + if (r < 0 && !logged) + log_error_errno(r, "Failed to %s: %m.", verb); +} + static int create_symlink( const char *old_path, const char *new_path, @@ -288,36 +408,52 @@ static int create_symlink( /* Actually create a symlink, and remember that we did. Is * smart enough to check if there's already a valid symlink in - * place. */ + * place. + * + * Returns 1 if a symlink was created or already exists and points to + * the right place, or negative on error. + */ mkdir_parents_label(new_path, 0755); if (symlink(old_path, new_path) >= 0) { unit_file_changes_add(changes, n_changes, UNIT_FILE_SYMLINK, new_path, old_path); - return 0; + return 1; } - if (errno != EEXIST) + if (errno != EEXIST) { + unit_file_changes_add(changes, n_changes, -errno, new_path, NULL); return -errno; + } r = readlink_malloc(new_path, &dest); - if (r < 0) + if (r < 0) { + /* translate EINVAL (non-symlink exists) to EEXIST */ + if (r == -EINVAL) + r = -EEXIST; + + unit_file_changes_add(changes, n_changes, r, new_path, NULL); return r; + } if (path_equal(dest, old_path)) - return 0; + return 1; - if (!force) + if (!force) { + unit_file_changes_add(changes, n_changes, -EEXIST, new_path, dest); return -EEXIST; + } r = symlink_atomic(old_path, new_path); - if (r < 0) + if (r < 0) { + unit_file_changes_add(changes, n_changes, r, new_path, NULL); return r; + } unit_file_changes_add(changes, n_changes, UNIT_FILE_UNLINK, new_path, NULL); unit_file_changes_add(changes, n_changes, UNIT_FILE_SYMLINK, new_path, old_path); - return 0; + return 1; } static int mark_symlink_for_removal( @@ -353,6 +489,7 @@ static int remove_marked_symlinks_fd( int fd, const char *path, const char *config_path, + const LookupPaths *lp, bool *restart, UnitFileChange **changes, unsigned *n_changes) { @@ -365,6 +502,7 @@ static int remove_marked_symlinks_fd( assert(fd >= 0); assert(path); assert(config_path); + assert(lp); assert(restart); d = fdopendir(fd); @@ -400,12 +538,13 @@ static int remove_marked_symlinks_fd( } /* This will close nfd, regardless whether it succeeds or not */ - q = remove_marked_symlinks_fd(remove_symlinks_to, nfd, p, config_path, restart, changes, n_changes); + q = remove_marked_symlinks_fd(remove_symlinks_to, nfd, p, config_path, lp, restart, changes, n_changes); if (q < 0 && r == 0) r = q; } else if (de->d_type == DT_LNK) { _cleanup_free_ char *p = NULL, *dest = NULL; + const char *rp; bool found; int q; @@ -415,42 +554,43 @@ static int remove_marked_symlinks_fd( p = path_make_absolute(de->d_name, path); if (!p) return -ENOMEM; + path_kill_slashes(p); q = readlink_malloc(p, &dest); + if (q == -ENOENT) + continue; if (q < 0) { - if (q == -ENOENT) - continue; - if (r == 0) r = q; continue; } - /* We remove all links pointing to a file or - * path that is marked, as well as all files - * sharing the same name as a file that is - * marked. */ + /* We remove all links pointing to a file or path that is marked, as well as all files sharing + * the same name as a file that is marked. */ - found = - set_contains(remove_symlinks_to, dest) || + found = set_contains(remove_symlinks_to, dest) || set_contains(remove_symlinks_to, basename(dest)) || set_contains(remove_symlinks_to, de->d_name); if (!found) continue; - if (unlink(p) < 0 && errno != ENOENT) { + if (unlinkat(fd, de->d_name, 0) < 0 && errno != ENOENT) { if (r == 0) r = -errno; + unit_file_changes_add(changes, n_changes, -errno, p, NULL); continue; } - path_kill_slashes(p); (void) rmdir_parents(p, config_path); unit_file_changes_add(changes, n_changes, UNIT_FILE_UNLINK, p, NULL); - q = mark_symlink_for_removal(&remove_symlinks_to, p); + /* Now, remember the full path (but with the root prefix removed) of + * the symlink we just removed, and remove any symlinks to it, too. */ + + rp = skip_root(lp, p); + q = mark_symlink_for_removal(&remove_symlinks_to, rp ?: p); if (q < 0) return q; if (q > 0) @@ -464,6 +604,7 @@ static int remove_marked_symlinks_fd( static int remove_marked_symlinks( Set *remove_symlinks_to, const char *config_path, + const LookupPaths *lp, UnitFileChange **changes, unsigned *n_changes) { @@ -472,6 +613,7 @@ static int remove_marked_symlinks( int r = 0; assert(config_path); + assert(lp); if (set_size(remove_symlinks_to) <= 0) return 0; @@ -489,7 +631,7 @@ static int remove_marked_symlinks( return -errno; /* This takes possession of cfd and closes it */ - q = remove_marked_symlinks_fd(remove_symlinks_to, cfd, config_path, config_path, &restart, changes, n_changes); + q = remove_marked_symlinks_fd(remove_symlinks_to, cfd, config_path, config_path, lp, &restart, changes, n_changes); if (r == 0) r = q; } while (restart); @@ -503,6 +645,7 @@ static int find_symlinks_fd( int fd, const char *path, const char *config_path, + const LookupPaths *lp, bool *same_name_link) { _cleanup_closedir_ DIR *d = NULL; @@ -513,6 +656,7 @@ static int find_symlinks_fd( assert(fd >= 0); assert(path); assert(config_path); + assert(lp); assert(same_name_link); d = fdopendir(fd); @@ -546,7 +690,7 @@ static int find_symlinks_fd( } /* This will close nfd, regardless whether it succeeds or not */ - q = find_symlinks_fd(root_dir, name, nfd, p, config_path, same_name_link); + q = find_symlinks_fd(root_dir, name, nfd, p, config_path, lp, same_name_link); if (q > 0) return 1; if (r == 0) @@ -624,6 +768,7 @@ static int find_symlinks( const char *root_dir, const char *name, const char *config_path, + const LookupPaths *lp, bool *same_name_link) { int fd; @@ -640,29 +785,25 @@ static int find_symlinks( } /* This takes possession of fd and closes it */ - return find_symlinks_fd(root_dir, name, fd, config_path, config_path, same_name_link); + return find_symlinks_fd(root_dir, name, fd, config_path, config_path, lp, same_name_link); } static int find_symlinks_in_scope( UnitFileScope scope, - const char *root_dir, + const LookupPaths *paths, const char *name, UnitFileState *state) { - _cleanup_free_ char *normal_path = NULL, *runtime_path = NULL; bool same_name_link_runtime = false, same_name_link = false; int r; assert(scope >= 0); assert(scope < _UNIT_FILE_SCOPE_MAX); + assert(paths); assert(name); - /* First look in the normal config path */ - r = get_config_path(scope, false, root_dir, &normal_path); - if (r < 0) - return r; - - r = find_symlinks(root_dir, name, normal_path, &same_name_link); + /* First look in the persistent config path */ + r = find_symlinks(paths->root_dir, name, paths->persistent_config, paths, &same_name_link); if (r < 0) return r; if (r > 0) { @@ -671,11 +812,7 @@ static int find_symlinks_in_scope( } /* Then look in runtime config path */ - r = get_config_path(scope, true, root_dir, &runtime_path); - if (r < 0) - return r; - - r = find_symlinks(root_dir, name, runtime_path, &same_name_link_runtime); + r = find_symlinks(paths->root_dir, name, paths->runtime_config, paths, &same_name_link_runtime); if (r < 0) return r; if (r > 0) { @@ -742,6 +879,30 @@ static UnitFileInstallInfo *install_info_find(InstallContext *c, const char *nam return ordered_hashmap_get(c->will_process, name); } +static int install_info_may_process( + UnitFileInstallInfo *i, + const LookupPaths *paths, + UnitFileChange **changes, + unsigned *n_changes) { + assert(i); + assert(paths); + + /* Checks whether the loaded unit file is one we should process, or is masked, transient or generated and thus + * not subject to enable/disable operations. */ + + if (i->type == UNIT_FILE_TYPE_MASKED) { + unit_file_changes_add(changes, n_changes, -ERFKILL, i->path, NULL); + return -ERFKILL; + } + if (path_is_generator(paths, i->path) || + path_is_transient(paths, i->path)) { + unit_file_changes_add(changes, n_changes, -EADDRNOTAVAIL, i->path, NULL); + return -EADDRNOTAVAIL; + } + + return 0; +} + static int install_info_add( InstallContext *c, const char *name, @@ -804,18 +965,34 @@ fail: return r; } -static int install_info_add_auto( - InstallContext *c, - const char *name_or_path, - UnitFileInstallInfo **ret) { +static int config_parse_alias( + const char *unit, + const char *filename, + unsigned line, + const char *section, + unsigned section_line, + const char *lvalue, + int ltype, + const char *rvalue, + void *data, + void *userdata) { - assert(c); - assert(name_or_path); + const char *name; + UnitType type; - if (path_is_absolute(name_or_path)) - return install_info_add(c, NULL, name_or_path, ret); - else - return install_info_add(c, name_or_path, NULL, ret); + assert(filename); + assert(lvalue); + assert(rvalue); + + name = basename(filename); + type = unit_name_to_type(name); + if (!unit_type_may_alias(type)) + return log_syntax(unit, LOG_WARNING, filename, line, 0, + "Aliases are not allowed for %s units, ignoring.", + unit_type_to_string(type)); + + return config_parse_strv(unit, filename, line, section, section_line, + lvalue, ltype, rvalue, data, userdata); } static int config_parse_also( @@ -874,6 +1051,7 @@ static int config_parse_default_instance( void *userdata) { UnitFileInstallInfo *i = data; + const char *name; char *printed; int r; @@ -881,6 +1059,15 @@ static int config_parse_default_instance( assert(lvalue); assert(rvalue); + name = basename(filename); + if (unit_name_is_valid(name, UNIT_NAME_INSTANCE)) + /* When enabling an instance, we might be using a template unit file, + * but we should ignore DefaultInstance silently. */ + return 0; + if (!unit_name_is_valid(name, UNIT_NAME_TEMPLATE)) + return log_syntax(unit, LOG_WARNING, filename, line, 0, + "DefaultInstance only makes sense for template units, ignoring."); + r = install_full_printf(i, rvalue, &printed); if (r < 0) return r; @@ -900,11 +1087,10 @@ static int unit_file_load( InstallContext *c, UnitFileInstallInfo *info, const char *path, - const char *root_dir, SearchFlags flags) { const ConfigTableItem items[] = { - { "Install", "Alias", config_parse_strv, 0, &info->aliases }, + { "Install", "Alias", config_parse_alias, 0, &info->aliases }, { "Install", "WantedBy", config_parse_strv, 0, &info->wanted_by }, { "Install", "RequiredBy", config_parse_strv, 0, &info->required_by }, { "Install", "DefaultInstance", config_parse_default_instance, 0, info }, @@ -912,6 +1098,8 @@ static int unit_file_load( {} }; + const char *name; + UnitType type; _cleanup_fclose_ FILE *f = NULL; _cleanup_close_ int fd = -1; struct stat st; @@ -921,7 +1109,11 @@ static int unit_file_load( assert(info); assert(path); - path = prefix_roota(root_dir, path); + name = basename(path); + type = unit_name_to_type(name); + if (unit_name_is_valid(name, UNIT_NAME_TEMPLATE|UNIT_NAME_INSTANCE) && + !unit_type_may_template(type)) + return log_error_errno(EINVAL, "Unit type %s cannot be templated.", unit_type_to_string(type)); if (!(flags & SEARCH_LOAD)) { r = lstat(path, &st); @@ -983,26 +1175,26 @@ static int unit_file_load_or_readlink( const char *root_dir, SearchFlags flags) { - _cleanup_free_ char *np = NULL; + _cleanup_free_ char *target = NULL; int r; - r = unit_file_load(c, info, path, root_dir, flags); + r = unit_file_load(c, info, path, flags); if (r != -ELOOP) return r; /* This is a symlink, let's read it. */ - r = readlink_and_make_absolute_root(root_dir, path, &np); + r = readlink_malloc(path, &target); if (r < 0) return r; - if (path_equal(np, "/dev/null")) + if (path_equal(target, "/dev/null")) info->type = UNIT_FILE_TYPE_MASKED; else { const char *bn; UnitType a, b; - bn = basename(np); + bn = basename(target); if (unit_name_is_valid(info->name, UNIT_NAME_PLAIN)) { @@ -1029,9 +1221,16 @@ static int unit_file_load_or_readlink( if (a < 0 || b < 0 || a != b) return -EINVAL; + if (path_is_absolute(target)) + /* This is an absolute path, prefix the root so that we always deal with fully qualified paths */ + info->symlink_target = prefix_root(root_dir, target); + else + /* This is a relative path, take it relative to the dir the symlink is located in. */ + info->symlink_target = file_in_same_dir(path, target); + if (!info->symlink_target) + return -ENOMEM; + info->type = UNIT_FILE_TYPE_SYMLINK; - info->symlink_target = np; - np = NULL; } return 0; @@ -1041,9 +1240,9 @@ static int unit_file_search( InstallContext *c, UnitFileInstallInfo *info, const LookupPaths *paths, - const char *root_dir, SearchFlags flags) { + _cleanup_free_ char *template = NULL; char **p; int r; @@ -1056,59 +1255,53 @@ static int unit_file_search( return 0; if (info->path) - return unit_file_load_or_readlink(c, info, info->path, root_dir, flags); + return unit_file_load_or_readlink(c, info, info->path, paths->root_dir, flags); assert(info->name); - STRV_FOREACH(p, paths->unit_path) { + STRV_FOREACH(p, paths->search_path) { _cleanup_free_ char *path = NULL; path = strjoin(*p, "/", info->name, NULL); if (!path) return -ENOMEM; - r = unit_file_load_or_readlink(c, info, path, root_dir, flags); - if (r < 0) { - if (r != -ENOENT) - return r; - } else { + r = unit_file_load_or_readlink(c, info, path, paths->root_dir, flags); + if (r >= 0) { info->path = path; path = NULL; return r; - } + } else if (r != -ENOENT) + return r; } if (unit_name_is_valid(info->name, UNIT_NAME_INSTANCE)) { - /* Unit file doesn't exist, however instance * enablement was requested. We will check if it is * possible to load template unit file. */ - _cleanup_free_ char *template = NULL; - r = unit_name_template(info->name, &template); if (r < 0) return r; - STRV_FOREACH(p, paths->unit_path) { + STRV_FOREACH(p, paths->search_path) { _cleanup_free_ char *path = NULL; path = strjoin(*p, "/", template, NULL); if (!path) return -ENOMEM; - r = unit_file_load_or_readlink(c, info, path, root_dir, flags); - if (r < 0) { - if (r != -ENOENT) - return r; - } else { + r = unit_file_load_or_readlink(c, info, path, paths->root_dir, flags); + if (r >= 0) { info->path = path; path = NULL; return r; - } + } else if (r != -ENOENT) + return r; } } + log_debug("Cannot find unit %s%s%s.", info->name, template ? " or " : "", strempty(template)); return -ENOENT; } @@ -1140,10 +1333,14 @@ static int install_info_follow( return unit_file_load_or_readlink(c, i, i->path, root_dir, flags); } +/** + * Search for the unit file. If the unit name is a symlink, + * follow the symlink to the target, maybe more than once. + * Propagate the instance name if present. + */ static int install_info_traverse( UnitFileScope scope, InstallContext *c, - const char *root_dir, const LookupPaths *paths, UnitFileInstallInfo *start, SearchFlags flags, @@ -1157,7 +1354,7 @@ static int install_info_traverse( assert(start); assert(c); - r = unit_file_search(c, start, paths, root_dir, flags); + r = unit_file_search(c, start, paths, flags); if (r < 0) return r; @@ -1168,17 +1365,19 @@ static int install_info_traverse( if (++k > UNIT_FILE_FOLLOW_SYMLINK_MAX) return -ELOOP; - if (!(flags & SEARCH_FOLLOW_CONFIG_SYMLINKS) && is_config_path(scope, i->path)) - return -ELOOP; + if (!(flags & SEARCH_FOLLOW_CONFIG_SYMLINKS)) { + r = path_is_config(paths, i->path); + if (r < 0) + return r; + if (r > 0) + return -ELOOP; + } - r = install_info_follow(c, i, root_dir, flags); - if (r < 0) { + r = install_info_follow(c, i, paths->root_dir, flags); + if (r == -EXDEV) { _cleanup_free_ char *buffer = NULL; const char *bn; - if (r != -EXDEV) - return r; - /* Target has a different name, create a new * install info object for that, and continue * with that. */ @@ -1205,12 +1404,15 @@ static int install_info_traverse( if (r < 0) return r; - r = unit_file_search(c, i, paths, root_dir, flags); - if (r < 0) - return r; + /* Try again, with the new target we found. */ + r = unit_file_search(c, i, paths, flags); + if (r == -ENOENT) + /* Translate error code to highlight this specific case */ + return -ENOLINK; } - /* Try again, with the new target we found. */ + if (r < 0) + return r; } if (ret) @@ -1219,10 +1421,28 @@ static int install_info_traverse( return 0; } +static int install_info_add_auto( + InstallContext *c, + const LookupPaths *paths, + const char *name_or_path, + UnitFileInstallInfo **ret) { + + assert(c); + assert(name_or_path); + + if (path_is_absolute(name_or_path)) { + const char *pp; + + pp = prefix_roota(paths->root_dir, name_or_path); + + return install_info_add(c, NULL, pp, ret); + } else + return install_info_add(c, name_or_path, NULL, ret); +} + static int install_info_discover( UnitFileScope scope, InstallContext *c, - const char *root_dir, const LookupPaths *paths, const char *name, SearchFlags flags, @@ -1235,15 +1455,16 @@ static int install_info_discover( assert(paths); assert(name); - r = install_info_add_auto(c, name, &i); + r = install_info_add_auto(c, paths, name, &i); if (r < 0) return r; - return install_info_traverse(scope, c, root_dir, paths, i, flags, ret); + return install_info_traverse(scope, c, paths, i, flags, ret); } static int install_info_symlink_alias( UnitFileInstallInfo *i, + const LookupPaths *paths, const char *config_path, bool force, UnitFileChange **changes, @@ -1253,10 +1474,12 @@ static int install_info_symlink_alias( int r = 0, q; assert(i); + assert(paths); assert(config_path); STRV_FOREACH(s, i->aliases) { _cleanup_free_ char *alias_path = NULL, *dst = NULL; + const char *rp; q = install_full_printf(i, *s, &dst); if (q < 0) @@ -1266,7 +1489,9 @@ static int install_info_symlink_alias( if (!alias_path) return -ENOMEM; - q = create_symlink(i->path, alias_path, force, changes, n_changes); + rp = skip_root(paths, i->path); + + q = create_symlink(rp ?: i->path, alias_path, force, changes, n_changes); if (r == 0) r = q; } @@ -1276,10 +1501,10 @@ static int install_info_symlink_alias( static int install_info_symlink_wants( UnitFileInstallInfo *i, + const LookupPaths *paths, const char *config_path, char **list, const char *suffix, - bool force, UnitFileChange **changes, unsigned *n_changes) { @@ -1289,6 +1514,7 @@ static int install_info_symlink_wants( int r = 0, q; assert(i); + assert(paths); assert(config_path); if (unit_name_is_valid(i->name, UNIT_NAME_TEMPLATE)) { @@ -1309,6 +1535,7 @@ static int install_info_symlink_wants( STRV_FOREACH(s, list) { _cleanup_free_ char *path = NULL, *dst = NULL; + const char *rp; q = install_full_printf(i, *s, &dst); if (q < 0) @@ -1323,7 +1550,9 @@ static int install_info_symlink_wants( if (!path) return -ENOMEM; - q = create_symlink(i->path, path, force, changes, n_changes); + rp = skip_root(paths, i->path); + + q = create_symlink(rp ?: i->path, path, true, changes, n_changes); if (r == 0) r = q; } @@ -1335,12 +1564,12 @@ static int install_info_symlink_link( UnitFileInstallInfo *i, const LookupPaths *paths, const char *config_path, - const char *root_dir, bool force, UnitFileChange **changes, unsigned *n_changes) { _cleanup_free_ char *path = NULL; + const char *rp; int r; assert(i); @@ -1348,22 +1577,25 @@ static int install_info_symlink_link( assert(config_path); assert(i->path); - r = in_search_path(i->path, paths->unit_path); - if (r != 0) + r = in_search_path(paths, i->path); + if (r < 0) return r; + if (r > 0) + return 0; path = strjoin(config_path, "/", i->name, NULL); if (!path) return -ENOMEM; - return create_symlink(i->path, path, force, changes, n_changes); + rp = skip_root(paths, i->path); + + return create_symlink(rp ?: i->path, path, force, changes, n_changes); } static int install_info_apply( UnitFileInstallInfo *i, const LookupPaths *paths, const char *config_path, - const char *root_dir, bool force, UnitFileChange **changes, unsigned *n_changes) { @@ -1377,18 +1609,19 @@ static int install_info_apply( if (i->type != UNIT_FILE_TYPE_REGULAR) return 0; - r = install_info_symlink_alias(i, config_path, force, changes, n_changes); + r = install_info_symlink_alias(i, paths, config_path, force, changes, n_changes); - q = install_info_symlink_wants(i, config_path, i->wanted_by, ".wants/", force, changes, n_changes); + q = install_info_symlink_wants(i, paths, config_path, i->wanted_by, ".wants/", changes, n_changes); if (r == 0) r = q; - q = install_info_symlink_wants(i, config_path, i->required_by, ".requires/", force, changes, n_changes); + q = install_info_symlink_wants(i, paths, config_path, i->required_by, ".requires/", changes, n_changes); if (r == 0) r = q; - q = install_info_symlink_link(i, paths, config_path, root_dir, force, changes, n_changes); - if (r == 0) + q = install_info_symlink_link(i, paths, config_path, force, changes, n_changes); + /* Do not count links to the unit file towards the "carries_install_info" count */ + if (r == 0 && q < 0) r = q; return r; @@ -1399,7 +1632,6 @@ static int install_context_apply( InstallContext *c, const LookupPaths *paths, const char *config_path, - const char *root_dir, bool force, SearchFlags flags, UnitFileChange **changes, @@ -1427,19 +1659,19 @@ static int install_context_apply( if (q < 0) return q; - r = install_info_traverse(scope, c, root_dir, paths, i, flags, NULL); + r = install_info_traverse(scope, c, paths, i, flags, NULL); if (r < 0) return r; if (i->type != UNIT_FILE_TYPE_REGULAR) continue; - q = install_info_apply(i, paths, config_path, root_dir, force, changes, n_changes); + q = install_info_apply(i, paths, config_path, force, changes, n_changes); if (r >= 0) { if (q < 0) r = q; else - r+= q; + r += q; } } @@ -1451,8 +1683,7 @@ static int install_context_mark_for_removal( InstallContext *c, const LookupPaths *paths, Set **remove_symlinks_to, - const char *config_path, - const char *root_dir) { + const char *config_path) { UnitFileInstallInfo *i; int r; @@ -1476,12 +1707,18 @@ static int install_context_mark_for_removal( if (r < 0) return r; - r = install_info_traverse(scope, c, root_dir, paths, i, SEARCH_LOAD|SEARCH_FOLLOW_CONFIG_SYMLINKS, NULL); - if (r < 0) + r = install_info_traverse(scope, c, paths, i, SEARCH_LOAD|SEARCH_FOLLOW_CONFIG_SYMLINKS, NULL); + if (r == -ENOLINK) + return 0; + else if (r < 0) return r; - if (i->type != UNIT_FILE_TYPE_REGULAR) + if (i->type != UNIT_FILE_TYPE_REGULAR) { + log_debug("Unit %s has type %s, ignoring.", + i->name, + unit_file_type_to_string(i->type) ?: "invalid"); continue; + } r = mark_symlink_for_removal(remove_symlinks_to, i->name); if (r < 0) @@ -1500,20 +1737,19 @@ int unit_file_mask( UnitFileChange **changes, unsigned *n_changes) { - _cleanup_free_ char *prefix = NULL; + _cleanup_lookup_paths_free_ LookupPaths paths = {}; + const char *config_path; char **i; int r; assert(scope >= 0); assert(scope < _UNIT_FILE_SCOPE_MAX); - r = verify_root_dir(scope, &root_dir); + r = lookup_paths_init(&paths, scope, 0, root_dir); if (r < 0) return r; - r = get_config_path(scope, runtime, root_dir, &prefix); - if (r < 0) - return r; + config_path = runtime ? paths.runtime_config : paths.persistent_config; STRV_FOREACH(i, files) { _cleanup_free_ char *path = NULL; @@ -1525,7 +1761,7 @@ int unit_file_mask( continue; } - path = path_make_absolute(*i, prefix); + path = path_make_absolute(*i, config_path); if (!path) return -ENOMEM; @@ -1545,23 +1781,22 @@ int unit_file_unmask( UnitFileChange **changes, unsigned *n_changes) { + _cleanup_lookup_paths_free_ LookupPaths paths = {}; _cleanup_set_free_free_ Set *remove_symlinks_to = NULL; - _cleanup_free_ char *config_path = NULL; _cleanup_free_ char **todo = NULL; size_t n_todo = 0, n_allocated = 0; + const char *config_path; char **i; int r, q; assert(scope >= 0); assert(scope < _UNIT_FILE_SCOPE_MAX); - r = verify_root_dir(scope, &root_dir); + r = lookup_paths_init(&paths, scope, 0, root_dir); if (r < 0) return r; - r = get_config_path(scope, runtime, root_dir, &config_path); - if (r < 0) - return r; + config_path = runtime ? paths.runtime_config : paths.persistent_config; STRV_FOREACH(i, files) { _cleanup_free_ char *path = NULL; @@ -1592,24 +1827,31 @@ int unit_file_unmask( r = 0; STRV_FOREACH(i, todo) { _cleanup_free_ char *path = NULL; + const char *rp; path = path_make_absolute(*i, config_path); if (!path) return -ENOMEM; if (unlink(path) < 0) { - if (errno != -ENOENT && r >= 0) - r = -errno; - } else { - q = mark_symlink_for_removal(&remove_symlinks_to, path); - if (q < 0) - return q; + if (errno != ENOENT) { + if (r >= 0) + r = -errno; + unit_file_changes_add(changes, n_changes, -errno, path, NULL); + } - unit_file_changes_add(changes, n_changes, UNIT_FILE_UNLINK, path, NULL); + continue; } + + unit_file_changes_add(changes, n_changes, UNIT_FILE_UNLINK, path, NULL); + + rp = skip_root(&paths, path); + q = mark_symlink_for_removal(&remove_symlinks_to, rp ?: path); + if (q < 0) + return q; } - q = remove_marked_symlinks(remove_symlinks_to, config_path, changes, n_changes); + q = remove_marked_symlinks(remove_symlinks_to, config_path, &paths, changes, n_changes); if (r >= 0) r = q; @@ -1626,26 +1868,20 @@ int unit_file_link( unsigned *n_changes) { _cleanup_lookup_paths_free_ LookupPaths paths = {}; - _cleanup_free_ char *config_path = NULL; _cleanup_free_ char **todo = NULL; size_t n_todo = 0, n_allocated = 0; + const char *config_path; char **i; int r, q; assert(scope >= 0); assert(scope < _UNIT_FILE_SCOPE_MAX); - r = verify_root_dir(scope, &root_dir); - if (r < 0) - return r; - - r = lookup_paths_init_from_scope(&paths, scope, root_dir); + r = lookup_paths_init(&paths, scope, 0, root_dir); if (r < 0) return r; - r = get_config_path(scope, runtime, root_dir, &config_path); - if (r < 0) - return r; + config_path = runtime ? paths.runtime_config : paths.persistent_config; STRV_FOREACH(i, files) { _cleanup_free_ char *full = NULL; @@ -1659,7 +1895,7 @@ int unit_file_link( if (!unit_name_is_valid(fn, UNIT_NAME_ANY)) return -EINVAL; - full = prefix_root(root_dir, *i); + full = prefix_root(paths.root_dir, *i); if (!full) return -ENOMEM; @@ -1672,7 +1908,7 @@ int unit_file_link( if (!S_ISREG(st.st_mode)) return -ENOTTY; - q = in_search_path(*i, paths.unit_path); + q = in_search_path(&paths, *i); if (q < 0) return q; if (q > 0) @@ -1688,13 +1924,15 @@ int unit_file_link( r = 0; STRV_FOREACH(i, todo) { - _cleanup_free_ char *path = NULL; + _cleanup_free_ char *new_path = NULL; + const char *old_path; - path = path_make_absolute(basename(*i), config_path); - if (!path) + old_path = skip_root(&paths, *i); + new_path = path_make_absolute(basename(*i), config_path); + if (!new_path) return -ENOMEM; - q = create_symlink(*i, path, force, changes, n_changes); + q = create_symlink(old_path ?: *i, new_path, force, changes, n_changes); if (q < 0 && r >= 0) r = q; } @@ -1702,6 +1940,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 persistent 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, @@ -1715,8 +2129,8 @@ int unit_file_add_dependency( _cleanup_lookup_paths_free_ LookupPaths paths = {}; _cleanup_(install_context_done) InstallContext c = {}; - _cleanup_free_ char *config_path = NULL; UnitFileInstallInfo *i, *target_info; + const char *config_path; char **f; int r; @@ -1730,34 +2144,30 @@ int unit_file_add_dependency( if (!unit_name_is_valid(target, UNIT_NAME_ANY)) return -EINVAL; - r = verify_root_dir(scope, &root_dir); + r = lookup_paths_init(&paths, scope, 0, root_dir); if (r < 0) return r; - r = lookup_paths_init_from_scope(&paths, scope, root_dir); - if (r < 0) - return r; + config_path = runtime ? paths.runtime_config : paths.persistent_config; - r = get_config_path(scope, runtime, root_dir, &config_path); + r = install_info_discover(scope, &c, &paths, target, SEARCH_FOLLOW_CONFIG_SYMLINKS, &target_info); if (r < 0) return r; - - r = install_info_discover(scope, &c, root_dir, &paths, target, SEARCH_FOLLOW_CONFIG_SYMLINKS, &target_info); + r = install_info_may_process(target_info, &paths, changes, n_changes); if (r < 0) return r; - if (target_info->type == UNIT_FILE_TYPE_MASKED) - return -ESHUTDOWN; assert(target_info->type == UNIT_FILE_TYPE_REGULAR); STRV_FOREACH(f, files) { char ***l; - r = install_info_discover(scope, &c, root_dir, &paths, *f, SEARCH_FOLLOW_CONFIG_SYMLINKS, &i); + r = install_info_discover(scope, &c, &paths, *f, SEARCH_FOLLOW_CONFIG_SYMLINKS, &i); + if (r < 0) + return r; + r = install_info_may_process(i, &paths, changes, n_changes); if (r < 0) return r; - if (i->type == UNIT_FILE_TYPE_MASKED) - return -ESHUTDOWN; assert(i->type == UNIT_FILE_TYPE_REGULAR); @@ -1776,7 +2186,7 @@ int unit_file_add_dependency( return -ENOMEM; } - return install_context_apply(scope, &c, &paths, config_path, root_dir, force, SEARCH_FOLLOW_CONFIG_SYMLINKS, changes, n_changes); + return install_context_apply(scope, &c, &paths, config_path, force, SEARCH_FOLLOW_CONFIG_SYMLINKS, changes, n_changes); } int unit_file_enable( @@ -1790,7 +2200,7 @@ int unit_file_enable( _cleanup_lookup_paths_free_ LookupPaths paths = {}; _cleanup_(install_context_done) InstallContext c = {}; - _cleanup_free_ char *config_path = NULL; + const char *config_path; UnitFileInstallInfo *i; char **f; int r; @@ -1798,24 +2208,19 @@ int unit_file_enable( assert(scope >= 0); assert(scope < _UNIT_FILE_SCOPE_MAX); - r = verify_root_dir(scope, &root_dir); - if (r < 0) - return r; - - r = lookup_paths_init_from_scope(&paths, scope, root_dir); + r = lookup_paths_init(&paths, scope, 0, root_dir); if (r < 0) return r; - r = get_config_path(scope, runtime, root_dir, &config_path); - if (r < 0) - return r; + config_path = runtime ? paths.runtime_config : paths.persistent_config; STRV_FOREACH(f, files) { - r = install_info_discover(scope, &c, root_dir, &paths, *f, SEARCH_LOAD, &i); + r = install_info_discover(scope, &c, &paths, *f, SEARCH_LOAD, &i); + if (r < 0) + return r; + r = install_info_may_process(i, &paths, changes, n_changes); if (r < 0) return r; - if (i->type == UNIT_FILE_TYPE_MASKED) - return -ESHUTDOWN; assert(i->type == UNIT_FILE_TYPE_REGULAR); } @@ -1825,7 +2230,7 @@ int unit_file_enable( is useful to determine whether the passed files had any installation data at all. */ - return install_context_apply(scope, &c, &paths, config_path, root_dir, force, SEARCH_LOAD, changes, n_changes); + return install_context_apply(scope, &c, &paths, config_path, force, SEARCH_LOAD, changes, n_changes); } int unit_file_disable( @@ -1838,25 +2243,19 @@ int unit_file_disable( _cleanup_lookup_paths_free_ LookupPaths paths = {}; _cleanup_(install_context_done) InstallContext c = {}; - _cleanup_free_ char *config_path = NULL; _cleanup_set_free_free_ Set *remove_symlinks_to = NULL; + const char *config_path; char **i; int r; assert(scope >= 0); assert(scope < _UNIT_FILE_SCOPE_MAX); - r = verify_root_dir(scope, &root_dir); - if (r < 0) - return r; - - r = lookup_paths_init_from_scope(&paths, scope, root_dir); + r = lookup_paths_init(&paths, scope, 0, root_dir); if (r < 0) return r; - r = get_config_path(scope, runtime, root_dir, &config_path); - if (r < 0) - return r; + config_path = runtime ? paths.runtime_config : paths.persistent_config; STRV_FOREACH(i, files) { if (!unit_name_is_valid(*i, UNIT_NAME_ANY)) @@ -1867,11 +2266,11 @@ int unit_file_disable( return r; } - r = install_context_mark_for_removal(scope, &c, &paths, &remove_symlinks_to, config_path, root_dir); + r = install_context_mark_for_removal(scope, &c, &paths, &remove_symlinks_to, config_path); if (r < 0) return r; - return remove_marked_symlinks(remove_symlinks_to, config_path, changes, n_changes); + return remove_marked_symlinks(remove_symlinks_to, config_path, &paths, changes, n_changes); } int unit_file_reenable( @@ -1912,41 +2311,34 @@ int unit_file_set_default( _cleanup_lookup_paths_free_ LookupPaths paths = {}; _cleanup_(install_context_done) InstallContext c = {}; - _cleanup_free_ char *config_path = NULL; UnitFileInstallInfo *i; - const char *path; + const char *new_path, *old_path; int r; assert(scope >= 0); assert(scope < _UNIT_FILE_SCOPE_MAX); assert(name); - if (unit_name_to_type(name) != UNIT_TARGET) + if (unit_name_to_type(name) != UNIT_TARGET) /* this also validates the name */ return -EINVAL; if (streq(name, SPECIAL_DEFAULT_TARGET)) return -EINVAL; - r = verify_root_dir(scope, &root_dir); - if (r < 0) - return r; - - r = lookup_paths_init_from_scope(&paths, scope, root_dir); + r = lookup_paths_init(&paths, scope, 0, root_dir); if (r < 0) return r; - r = get_config_path(scope, false, root_dir, &config_path); + r = install_info_discover(scope, &c, &paths, name, 0, &i); if (r < 0) return r; - - r = install_info_discover(scope, &c, root_dir, &paths, name, 0, &i); + r = install_info_may_process(i, &paths, changes, n_changes); if (r < 0) return r; - if (i->type == UNIT_FILE_TYPE_MASKED) - return -ESHUTDOWN; - path = strjoina(config_path, "/" SPECIAL_DEFAULT_TARGET); + old_path = skip_root(&paths, i->path); + new_path = strjoina(paths.persistent_config, "/" SPECIAL_DEFAULT_TARGET); - return create_symlink(i->path, path, force, changes, n_changes); + return create_symlink(old_path ?: i->path, new_path, force, changes, n_changes); } int unit_file_get_default( @@ -1964,19 +2356,16 @@ int unit_file_get_default( assert(scope < _UNIT_FILE_SCOPE_MAX); assert(name); - r = verify_root_dir(scope, &root_dir); + r = lookup_paths_init(&paths, scope, 0, root_dir); if (r < 0) return r; - r = lookup_paths_init_from_scope(&paths, scope, root_dir); + r = install_info_discover(scope, &c, &paths, SPECIAL_DEFAULT_TARGET, SEARCH_FOLLOW_CONFIG_SYMLINKS, &i); if (r < 0) return r; - - r = install_info_discover(scope, &c, root_dir, &paths, SPECIAL_DEFAULT_TARGET, SEARCH_FOLLOW_CONFIG_SYMLINKS, &i); + r = install_info_may_process(i, &paths, NULL, 0); if (r < 0) return r; - if (i->type == UNIT_FILE_TYPE_MASKED) - return -ESHUTDOWN; n = strdup(i->name); if (!n) @@ -1986,9 +2375,8 @@ int unit_file_get_default( return 0; } -int unit_file_lookup_state( +static int unit_file_lookup_state( UnitFileScope scope, - const char *root_dir, const LookupPaths *paths, const char *name, UnitFileState *ret) { @@ -2004,11 +2392,7 @@ int unit_file_lookup_state( if (!unit_name_is_valid(name, UNIT_NAME_ANY)) return -EINVAL; - r = verify_root_dir(scope, &root_dir); - if (r < 0) - return r; - - r = install_info_discover(scope, &c, root_dir, paths, name, SEARCH_LOAD|SEARCH_FOLLOW_CONFIG_SYMLINKS, &i); + r = install_info_discover(scope, &c, paths, name, SEARCH_LOAD|SEARCH_FOLLOW_CONFIG_SYMLINKS, &i); if (r < 0) return r; @@ -2019,11 +2403,31 @@ int unit_file_lookup_state( switch (i->type) { case UNIT_FILE_TYPE_MASKED: - state = path_startswith(i->path, "/run") ? UNIT_FILE_MASKED_RUNTIME : UNIT_FILE_MASKED; + r = path_is_runtime(paths, i->path); + if (r < 0) + return r; + + state = r > 0 ? UNIT_FILE_MASKED_RUNTIME : UNIT_FILE_MASKED; break; case UNIT_FILE_TYPE_REGULAR: - r = find_symlinks_in_scope(scope, root_dir, i->name, &state); + r = path_is_generator(paths, i->path); + if (r < 0) + return r; + if (r > 0) { + state = UNIT_FILE_GENERATED; + break; + } + + r = path_is_transient(paths, i->path); + if (r < 0) + return r; + if (r > 0) { + state = UNIT_FILE_TRANSIENT; + break; + } + + r = find_symlinks_in_scope(scope, paths, i->name, &state); if (r < 0) return r; if (r == 0) { @@ -2058,32 +2462,42 @@ int unit_file_get_state( assert(scope < _UNIT_FILE_SCOPE_MAX); assert(name); - r = verify_root_dir(scope, &root_dir); + r = lookup_paths_init(&paths, scope, 0, root_dir); if (r < 0) return r; - r = lookup_paths_init_from_scope(&paths, scope, root_dir); + return unit_file_lookup_state(scope, &paths, name, ret); +} + +int unit_file_exists(UnitFileScope scope, const LookupPaths *paths, const char *name) { + _cleanup_(install_context_done) InstallContext c = {}; + int r; + + assert(paths); + assert(name); + + if (!unit_name_is_valid(name, UNIT_NAME_ANY)) + return -EINVAL; + + r = install_info_discover(scope, &c, paths, name, 0, NULL); + if (r == -ENOENT) + return 0; if (r < 0) return r; - return unit_file_lookup_state(scope, root_dir, &paths, name, ret); + return 1; } -int unit_file_query_preset(UnitFileScope scope, const char *root_dir, const char *name) { +static int read_presets(UnitFileScope scope, const char *root_dir, Presets *presets) { + _cleanup_(presets_freep) Presets ps = {}; + size_t n_allocated = 0; _cleanup_strv_free_ char **files = NULL; char **p; int r; assert(scope >= 0); assert(scope < _UNIT_FILE_SCOPE_MAX); - assert(name); - - r = verify_root_dir(scope, &root_dir); - if (r < 0) - return r; - - if (!unit_name_is_valid(name, UNIT_NAME_ANY)) - return -EINVAL; + assert(presets); if (scope == UNIT_FILE_SYSTEM) r = conf_files_list(&files, ".preset", root_dir, @@ -2100,8 +2514,11 @@ int unit_file_query_preset(UnitFileScope scope, const char *root_dir, const char "/usr/local/lib/systemd/user-preset", "/usr/lib/systemd/user-preset", NULL); - else - return 1; /* Default is "enable" */ + else { + *presets = (Presets){}; + + return 0; + } if (r < 0) return r; @@ -2109,6 +2526,7 @@ int unit_file_query_preset(UnitFileScope scope, const char *root_dir, const char STRV_FOREACH(p, files) { _cleanup_fclose_ FILE *f; char line[LINE_MAX]; + int n = 0; f = fopen(*p, "re"); if (!f) { @@ -2119,10 +2537,12 @@ int unit_file_query_preset(UnitFileScope scope, const char *root_dir, const char } FOREACH_LINE(line, f, return -errno) { + PresetRule rule = {}; const char *parameter; char *l; l = strstrip(line); + n++; if (isempty(l)) continue; @@ -2131,31 +2551,87 @@ int unit_file_query_preset(UnitFileScope scope, const char *root_dir, const char parameter = first_word(l, "enable"); if (parameter) { - if (fnmatch(parameter, name, FNM_NOESCAPE) == 0) { - log_debug("Preset file says enable %s.", name); - return 1; - } + char *pattern; - continue; + pattern = strdup(parameter); + if (!pattern) + return -ENOMEM; + + rule = (PresetRule) { + .pattern = pattern, + .action = PRESET_ENABLE, + }; } parameter = first_word(l, "disable"); if (parameter) { - if (fnmatch(parameter, name, FNM_NOESCAPE) == 0) { - log_debug("Preset file says disable %s.", name); - return 0; - } + char *pattern; + + pattern = strdup(parameter); + if (!pattern) + return -ENOMEM; + + rule = (PresetRule) { + .pattern = pattern, + .action = PRESET_DISABLE, + }; + } + + if (rule.action) { + if (!GREEDY_REALLOC(ps.rules, n_allocated, ps.n_rules + 1)) + return -ENOMEM; + ps.rules[ps.n_rules++] = rule; continue; } - log_debug("Couldn't parse line '%s'", l); + log_syntax(NULL, LOG_WARNING, *p, n, 0, "Couldn't parse line '%s'. Ignoring.", line); } } - /* Default is "enable" */ - log_debug("Preset file doesn't say anything about %s, enabling.", name); - return 1; + *presets = ps; + ps = (Presets){}; + + return 0; +} + +static int query_presets(const char *name, const Presets presets) { + PresetAction action = PRESET_UNKNOWN; + size_t i; + + if (!unit_name_is_valid(name, UNIT_NAME_ANY)) + return -EINVAL; + + for (i = 0; i < presets.n_rules; i++) + if (fnmatch(presets.rules[i].pattern, name, FNM_NOESCAPE) == 0) { + action = presets.rules[i].action; + break; + } + + switch (action) { + case PRESET_UNKNOWN: + log_debug("Preset files don't specify rule for %s. Enabling.", name); + return 1; + case PRESET_ENABLE: + log_debug("Preset files say enable %s.", name); + return 1; + case PRESET_DISABLE: + log_debug("Preset files say disable %s.", name); + return 0; + default: + assert_not_reached("invalid preset action"); + } +} + +int unit_file_query_preset(UnitFileScope scope, const char *root_dir, const char *name) { + _cleanup_(presets_freep) Presets presets = {}; + int r; + + r = read_presets(scope, root_dir, &presets); + if (r < 0) + return r; + + return query_presets(name, presets); } static int execute_preset( @@ -2164,7 +2640,6 @@ static int execute_preset( InstallContext *minus, const LookupPaths *paths, const char *config_path, - const char *root_dir, char **files, UnitFilePresetMode mode, bool force, @@ -2181,11 +2656,11 @@ static int execute_preset( if (mode != UNIT_FILE_PRESET_ENABLE_ONLY) { _cleanup_set_free_free_ Set *remove_symlinks_to = NULL; - r = install_context_mark_for_removal(scope, minus, paths, &remove_symlinks_to, config_path, root_dir); + r = install_context_mark_for_removal(scope, minus, paths, &remove_symlinks_to, config_path); if (r < 0) return r; - r = remove_marked_symlinks(remove_symlinks_to, config_path, changes, n_changes); + r = remove_marked_symlinks(remove_symlinks_to, config_path, paths, changes, n_changes); } else r = 0; @@ -2193,12 +2668,12 @@ static int execute_preset( int q; /* Returns number of symlinks that where supposed to be installed. */ - q = install_context_apply(scope, plus, paths, config_path, root_dir, force, SEARCH_LOAD, changes, n_changes); + q = install_context_apply(scope, plus, paths, config_path, force, SEARCH_LOAD, changes, n_changes); if (r >= 0) { if (q < 0) r = q; else - r+= q; + r += q; } } @@ -2210,9 +2685,11 @@ static int preset_prepare_one( InstallContext *plus, InstallContext *minus, LookupPaths *paths, - const char *root_dir, UnitFilePresetMode mode, - const char *name) { + const char *name, + Presets presets, + UnitFileChange **changes, + unsigned *n_changes) { UnitFileInstallInfo *i; int r; @@ -2221,19 +2698,20 @@ static int preset_prepare_one( install_info_find(minus, name)) return 0; - r = unit_file_query_preset(scope, root_dir, name); + r = query_presets(name, presets); if (r < 0) return r; if (r > 0) { - r = install_info_discover(scope, plus, root_dir, paths, name, SEARCH_LOAD|SEARCH_FOLLOW_CONFIG_SYMLINKS, &i); + r = install_info_discover(scope, plus, paths, name, SEARCH_LOAD|SEARCH_FOLLOW_CONFIG_SYMLINKS, &i); if (r < 0) return r; - if (i->type == UNIT_FILE_TYPE_MASKED) - return -ESHUTDOWN; + r = install_info_may_process(i, paths, changes, n_changes); + if (r < 0) + return r; } else - r = install_info_discover(scope, minus, root_dir, paths, name, SEARCH_FOLLOW_CONFIG_SYMLINKS, &i); + r = install_info_discover(scope, minus, paths, name, SEARCH_FOLLOW_CONFIG_SYMLINKS, &i); return r; } @@ -2250,7 +2728,8 @@ int unit_file_preset( _cleanup_(install_context_done) InstallContext plus = {}, minus = {}; _cleanup_lookup_paths_free_ LookupPaths paths = {}; - _cleanup_free_ char *config_path = NULL; + _cleanup_(presets_freep) Presets presets = {}; + const char *config_path; char **i; int r; @@ -2258,28 +2737,23 @@ int unit_file_preset( assert(scope < _UNIT_FILE_SCOPE_MAX); assert(mode < _UNIT_FILE_PRESET_MAX); - r = verify_root_dir(scope, &root_dir); + r = lookup_paths_init(&paths, scope, 0, root_dir); if (r < 0) return r; - r = lookup_paths_init_from_scope(&paths, scope, root_dir); - if (r < 0) - return r; + config_path = runtime ? paths.runtime_config : paths.persistent_config; - r = get_config_path(scope, runtime, root_dir, &config_path); + r = read_presets(scope, root_dir, &presets); if (r < 0) return r; STRV_FOREACH(i, files) { - if (!unit_name_is_valid(*i, UNIT_NAME_ANY)) - return -EINVAL; - - r = preset_prepare_one(scope, &plus, &minus, &paths, root_dir, mode, *i); + r = preset_prepare_one(scope, &plus, &minus, &paths, mode, *i, presets, changes, n_changes); if (r < 0) return r; } - return execute_preset(scope, &plus, &minus, &paths, config_path, root_dir, files, mode, force, changes, n_changes); + return execute_preset(scope, &plus, &minus, &paths, config_path, files, mode, force, changes, n_changes); } int unit_file_preset_all( @@ -2293,7 +2767,8 @@ int unit_file_preset_all( _cleanup_(install_context_done) InstallContext plus = {}, minus = {}; _cleanup_lookup_paths_free_ LookupPaths paths = {}; - _cleanup_free_ char *config_path = NULL; + _cleanup_(presets_freep) Presets presets = {}; + const char *config_path = NULL; char **i; int r; @@ -2301,28 +2776,21 @@ int unit_file_preset_all( assert(scope < _UNIT_FILE_SCOPE_MAX); assert(mode < _UNIT_FILE_PRESET_MAX); - r = verify_root_dir(scope, &root_dir); + r = lookup_paths_init(&paths, scope, 0, root_dir); if (r < 0) return r; - r = lookup_paths_init_from_scope(&paths, scope, root_dir); - if (r < 0) - return r; + config_path = runtime ? paths.runtime_config : paths.persistent_config; - r = get_config_path(scope, runtime, root_dir, &config_path); + r = read_presets(scope, root_dir, &presets); if (r < 0) return r; - STRV_FOREACH(i, paths.unit_path) { + STRV_FOREACH(i, paths.search_path) { _cleanup_closedir_ DIR *d = NULL; - _cleanup_free_ char *units_dir; struct dirent *de; - units_dir = path_join(root_dir, *i, NULL); - if (!units_dir) - return -ENOMEM; - - d = opendir(units_dir); + d = opendir(*i); if (!d) { if (errno == ENOENT) continue; @@ -2340,13 +2808,20 @@ int unit_file_preset_all( if (!IN_SET(de->d_type, DT_LNK, DT_REG)) continue; - r = preset_prepare_one(scope, &plus, &minus, &paths, root_dir, mode, de->d_name); + /* we don't pass changes[] in, because we want to handle errors on our own */ + r = preset_prepare_one(scope, &plus, &minus, &paths, mode, de->d_name, presets, NULL, 0); + if (r == -ERFKILL) + r = unit_file_changes_add(changes, n_changes, + UNIT_FILE_IS_MASKED, de->d_name, NULL); + else if (r == -ENOLINK) + r = unit_file_changes_add(changes, n_changes, + UNIT_FILE_IS_DANGLING, de->d_name, NULL); if (r < 0) return r; } } - return execute_preset(scope, &plus, &minus, &paths, config_path, root_dir, NULL, mode, force, changes, n_changes); + return execute_preset(scope, &plus, &minus, &paths, config_path, NULL, mode, force, changes, n_changes); } static void unit_file_list_free_one(UnitFileList *f) { @@ -2371,7 +2846,9 @@ DEFINE_TRIVIAL_CLEANUP_FUNC(UnitFileList*, unit_file_list_free_one); int unit_file_get_list( UnitFileScope scope, const char *root_dir, - Hashmap *h) { + Hashmap *h, + char **states, + char **patterns) { _cleanup_lookup_paths_free_ LookupPaths paths = {}; char **i; @@ -2381,24 +2858,15 @@ int unit_file_get_list( assert(scope < _UNIT_FILE_SCOPE_MAX); assert(h); - r = verify_root_dir(scope, &root_dir); + r = lookup_paths_init(&paths, scope, 0, root_dir); if (r < 0) return r; - r = lookup_paths_init_from_scope(&paths, scope, root_dir); - if (r < 0) - return r; - - STRV_FOREACH(i, paths.unit_path) { + STRV_FOREACH(i, paths.search_path) { _cleanup_closedir_ DIR *d = NULL; - _cleanup_free_ char *units_dir; struct dirent *de; - units_dir = path_join(root_dir, *i, NULL); - if (!units_dir) - return -ENOMEM; - - d = opendir(units_dir); + d = opendir(*i); if (!d) { if (errno == ENOENT) continue; @@ -2412,6 +2880,9 @@ int unit_file_get_list( if (!unit_name_is_valid(de->d_name, UNIT_NAME_ANY)) continue; + if (!strv_fnmatch_or_empty(patterns, de->d_name, FNM_NOESCAPE)) + continue; + if (hashmap_get(h, de->d_name)) continue; @@ -2424,14 +2895,18 @@ int unit_file_get_list( if (!f) return -ENOMEM; - f->path = path_make_absolute(de->d_name, units_dir); + f->path = path_make_absolute(de->d_name, *i); if (!f->path) return -ENOMEM; - r = unit_file_lookup_state(scope, root_dir, &paths, basename(f->path), &f->state); + r = unit_file_lookup_state(scope, &paths, de->d_name, &f->state); if (r < 0) f->state = UNIT_FILE_BAD; + if (!strv_isempty(states) && + !strv_contains(states, unit_file_state_to_string(f->state))) + continue; + r = hashmap_put(h, basename(f->path), f); if (r < 0) return r; @@ -2453,6 +2928,8 @@ static const char* const unit_file_state_table[_UNIT_FILE_STATE_MAX] = { [UNIT_FILE_STATIC] = "static", [UNIT_FILE_DISABLED] = "disabled", [UNIT_FILE_INDIRECT] = "indirect", + [UNIT_FILE_GENERATED] = "generated", + [UNIT_FILE_TRANSIENT] = "transient", [UNIT_FILE_BAD] = "bad", }; @@ -2461,6 +2938,8 @@ DEFINE_STRING_TABLE_LOOKUP(unit_file_state, UnitFileState); static const char* const unit_file_change_type_table[_UNIT_FILE_CHANGE_TYPE_MAX] = { [UNIT_FILE_SYMLINK] = "symlink", [UNIT_FILE_UNLINK] = "unlink", + [UNIT_FILE_IS_MASKED] = "masked", + [UNIT_FILE_IS_DANGLING] = "dangling", }; DEFINE_STRING_TABLE_LOOKUP(unit_file_change_type, UnitFileChangeType); |