| /* 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; |
| } |