diff options
Diffstat (limited to 'src/shared/dropin.c')
-rw-r--r-- | src/shared/dropin.c | 279 |
1 files changed, 279 insertions, 0 deletions
diff --git a/src/shared/dropin.c b/src/shared/dropin.c new file mode 100644 index 0000000..89f4b8a --- /dev/null +++ b/src/shared/dropin.c @@ -0,0 +1,279 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include <errno.h> +#include <stdarg.h> +#include <stdio.h> +#include <stdlib.h> + +#include "alloc-util.h" +#include "conf-files.h" +#include "dirent-util.h" +#include "dropin.h" +#include "escape.h" +#include "fd-util.h" +#include "fileio-label.h" +#include "fs-util.h" +#include "hashmap.h" +#include "log.h" +#include "macro.h" +#include "mkdir.h" +#include "path-util.h" +#include "set.h" +#include "string-util.h" +#include "strv.h" +#include "unit-name.h" + +int drop_in_file(const char *dir, const char *unit, unsigned level, + const char *name, char **ret_p, char **ret_q) { + + char prefix[DECIMAL_STR_MAX(unsigned)]; + _cleanup_free_ char *b = NULL, *p = NULL, *q = NULL; + + assert(unit); + assert(name); + assert(ret_p); + assert(ret_q); + + sprintf(prefix, "%u", level); + + b = xescape(name, "/."); + if (!b) + return -ENOMEM; + + if (!filename_is_valid(b)) + return -EINVAL; + + p = strjoin(dir, "/", unit, ".d"); + q = strjoin(p, "/", prefix, "-", b, ".conf"); + if (!p || !q) + return -ENOMEM; + + *ret_p = TAKE_PTR(p); + *ret_q = TAKE_PTR(q); + return 0; +} + +int write_drop_in(const char *dir, const char *unit, unsigned level, + const char *name, const char *data) { + + _cleanup_free_ char *p = NULL, *q = NULL; + int r; + + assert(dir); + assert(unit); + assert(name); + assert(data); + + r = drop_in_file(dir, unit, level, name, &p, &q); + if (r < 0) + return r; + + (void) mkdir_p(p, 0755); + return write_string_file_atomic_label(q, data); +} + +int write_drop_in_format(const char *dir, const char *unit, unsigned level, + const char *name, const char *format, ...) { + _cleanup_free_ char *p = NULL; + va_list ap; + int r; + + assert(dir); + assert(unit); + assert(name); + assert(format); + + va_start(ap, format); + r = vasprintf(&p, format, ap); + va_end(ap); + + if (r < 0) + return -ENOMEM; + + return write_drop_in(dir, unit, level, name, p); +} + +static int unit_file_add_dir( + const char *original_root, + const char *path, + char ***dirs) { + + _cleanup_free_ char *chased = NULL; + int r; + + assert(path); + + /* This adds [original_root]/path to dirs, if it exists. */ + + r = chase_symlinks(path, original_root, 0, &chased, NULL); + if (r == -ENOENT) /* Ignore -ENOENT, after all most units won't have a drop-in dir. */ + return 0; + if (r == -ENAMETOOLONG) { + /* Also, ignore -ENAMETOOLONG but log about it. After all, users are not even able to create the + * drop-in dir in such case. This mostly happens for device units with an overly long /sys path. */ + log_debug_errno(r, "Path '%s' too long, couldn't canonicalize, ignoring.", path); + return 0; + } + if (r < 0) + return log_warning_errno(r, "Failed to canonicalize path '%s': %m", path); + + if (strv_consume(dirs, TAKE_PTR(chased)) < 0) + return log_oom(); + + return 0; +} + +static int unit_file_find_dirs( + const char *original_root, + Set *unit_path_cache, + const char *unit_path, + const char *name, + const char *suffix, + char ***dirs) { + + _cleanup_free_ char *prefix = NULL, *instance = NULL, *built = NULL; + bool is_instance, chopped; + const char *dash; + UnitType type; + char *path; + size_t n; + int r; + + assert(unit_path); + assert(name); + assert(suffix); + + path = strjoina(unit_path, "/", name, suffix); + if (!unit_path_cache || set_get(unit_path_cache, path)) { + r = unit_file_add_dir(original_root, path, dirs); + if (r < 0) + return r; + } + + is_instance = unit_name_is_valid(name, UNIT_NAME_INSTANCE); + if (is_instance) { /* Also try the template dir */ + _cleanup_free_ char *template = NULL; + + r = unit_name_template(name, &template); + if (r < 0) + return log_error_errno(r, "Failed to generate template from unit name: %m"); + + r = unit_file_find_dirs(original_root, unit_path_cache, unit_path, template, suffix, dirs); + if (r < 0) + return r; + } + + /* Return early for top level drop-ins. */ + if (unit_type_from_string(name) >= 0) + return 0; + + /* Let's see if there's a "-" prefix for this unit name. If so, let's invoke ourselves for it. This will then + * recursively do the same for all our prefixes. i.e. this means given "foo-bar-waldo.service" we'll also + * search "foo-bar-.service" and "foo-.service". + * + * Note the order in which we do it: we traverse up adding drop-ins on each step. This means the more specific + * drop-ins may override the more generic drop-ins, which is the intended behaviour. */ + + r = unit_name_to_prefix(name, &prefix); + if (r < 0) + return log_error_errno(r, "Failed to derive unit name prefix from unit name: %m"); + + chopped = false; + for (;;) { + dash = strrchr(prefix, '-'); + if (!dash) /* No dash? if so we are done */ + return 0; + + n = (size_t) (dash - prefix); + if (n == 0) /* Leading dash? If so, we are done */ + return 0; + + if (prefix[n+1] != 0 || chopped) { + prefix[n+1] = 0; + break; + } + + /* Trailing dash? If so, chop it off and try again, but not more than once. */ + prefix[n] = 0; + chopped = true; + } + + if (!unit_prefix_is_valid(prefix)) + return 0; + + type = unit_name_to_type(name); + if (type < 0) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "Failed to derive unit type from unit name: %s", + name); + + if (is_instance) { + r = unit_name_to_instance(name, &instance); + if (r < 0) + return log_error_errno(r, "Failed to derive unit name instance from unit name: %m"); + } + + r = unit_name_build_from_type(prefix, instance, type, &built); + if (r < 0) + return log_error_errno(r, "Failed to build prefix unit name: %m"); + + return unit_file_find_dirs(original_root, unit_path_cache, unit_path, built, suffix, dirs); +} + +int unit_file_find_dropin_paths( + const char *original_root, + char **lookup_path, + Set *unit_path_cache, + const char *dir_suffix, + const char *file_suffix, + const char *name, + const Set *aliases, + char ***ret) { + + _cleanup_strv_free_ char **dirs = NULL; + const char *n; + char **p; + int r; + + assert(ret); + + if (name) + STRV_FOREACH(p, lookup_path) + (void) unit_file_find_dirs(original_root, unit_path_cache, *p, name, dir_suffix, &dirs); + + SET_FOREACH(n, aliases) + STRV_FOREACH(p, lookup_path) + (void) unit_file_find_dirs(original_root, unit_path_cache, *p, n, dir_suffix, &dirs); + + /* All the names in the unit are of the same type so just grab one. */ + n = name ?: (const char*) set_first(aliases); + if (n) { + UnitType type = _UNIT_TYPE_INVALID; + + type = unit_name_to_type(n); + if (type < 0) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "Failed to derive unit type from unit name: %s", n); + + /* Special top level drop in for "<unit type>.<suffix>". Add this last as it's the most generic + * and should be able to be overridden by more specific drop-ins. */ + STRV_FOREACH(p, lookup_path) + (void) unit_file_find_dirs(original_root, + unit_path_cache, + *p, + unit_type_to_string(type), + dir_suffix, + &dirs); + } + + if (strv_isempty(dirs)) { + *ret = NULL; + return 0; + } + + r = conf_files_list_strv(ret, file_suffix, NULL, 0, (const char**) dirs); + if (r < 0) + return log_warning_errno(r, "Failed to create the list of configuration files: %m"); + + return 1; +} |