/* SPDX-License-Identifier: LGPL-2.1-or-later */ #include #include #include #include "alloc-util.h" #include "fs-util.h" #include "log.h" #include "macro.h" #include "nulstr-util.h" #include "path-lookup.h" #include "path-util.h" #include "stat-util.h" #include "string-util.h" #include "strv.h" #include "tmpfile-util.h" #include "user-util.h" int xdg_user_runtime_dir(char **ret, const char *suffix) { const char *e; char *j; assert(ret); assert(suffix); e = getenv("XDG_RUNTIME_DIR"); if (!e) return -ENXIO; j = path_join(e, suffix); if (!j) return -ENOMEM; *ret = j; return 0; } int xdg_user_config_dir(char **ret, const char *suffix) { _cleanup_free_ char *j = NULL; const char *e; int r; assert(ret); e = getenv("XDG_CONFIG_HOME"); if (e) { j = path_join(e, suffix); if (!j) return -ENOMEM; } else { r = get_home_dir(&j); if (r < 0) return r; if (!path_extend(&j, "/.config", suffix)) return -ENOMEM; } *ret = TAKE_PTR(j); return 0; } int xdg_user_data_dir(char **ret, const char *suffix) { _cleanup_free_ char *j = NULL; const char *e; int r; assert(ret); assert(suffix); /* We don't treat /etc/xdg/systemd here as the spec * suggests because we assume that is a link to * /etc/systemd/ anyway. */ e = getenv("XDG_DATA_HOME"); if (e) { j = path_join(e, suffix); if (!j) return -ENOMEM; } else { r = get_home_dir(&j); if (r < 0) return r; if (!path_extend(&j, "/.local/share", suffix)) return -ENOMEM; } *ret = TAKE_PTR(j); return 1; } static const char* const user_data_unit_paths[] = { "/usr/local/lib/systemd/user", "/usr/local/share/systemd/user", USER_DATA_UNIT_DIR, "/usr/lib/systemd/user", "/usr/share/systemd/user", NULL }; static const char* const user_config_unit_paths[] = { USER_CONFIG_UNIT_DIR, "/etc/systemd/user", NULL }; int xdg_user_dirs(char ***ret_config_dirs, char ***ret_data_dirs) { /* Implement the mechanisms defined in * * http://standards.freedesktop.org/basedir-spec/basedir-spec-0.6.html * * We look in both the config and the data dirs because we * want to encourage that distributors ship their unit files * as data, and allow overriding as configuration. */ const char *e; _cleanup_strv_free_ char **config_dirs = NULL, **data_dirs = NULL; e = getenv("XDG_CONFIG_DIRS"); if (e) config_dirs = strv_split(e, ":"); else config_dirs = strv_new("/etc/xdg"); if (!config_dirs) return -ENOMEM; e = getenv("XDG_DATA_DIRS"); if (e) data_dirs = strv_split(e, ":"); else data_dirs = strv_new("/usr/local/share", "/usr/share"); if (!data_dirs) return -ENOMEM; *ret_config_dirs = TAKE_PTR(config_dirs); *ret_data_dirs = TAKE_PTR(data_dirs); return 0; } static char** user_dirs( const char *persistent_config, const char *runtime_config, const char *global_persistent_config, const char *global_runtime_config, const char *generator, const char *generator_early, const char *generator_late, const char *transient, const char *persistent_control, const char *runtime_control) { _cleanup_strv_free_ char **config_dirs = NULL, **data_dirs = NULL; _cleanup_free_ char *data_home = NULL; _cleanup_strv_free_ char **res = NULL; int r; r = xdg_user_dirs(&config_dirs, &data_dirs); if (r < 0) return NULL; r = xdg_user_data_dir(&data_home, "/systemd/user"); if (r < 0 && r != -ENXIO) return NULL; /* Now merge everything we found. */ if (strv_extend(&res, persistent_control) < 0) return NULL; if (strv_extend(&res, runtime_control) < 0) return NULL; if (strv_extend(&res, transient) < 0) return NULL; if (strv_extend(&res, generator_early) < 0) return NULL; if (strv_extend(&res, persistent_config) < 0) return NULL; if (strv_extend_strv_concat(&res, config_dirs, "/systemd/user") < 0) return NULL; /* global config has lower priority than the user config of the same type */ if (strv_extend(&res, global_persistent_config) < 0) return NULL; if (strv_extend_strv(&res, (char**) user_config_unit_paths, false) < 0) return NULL; if (strv_extend(&res, runtime_config) < 0) return NULL; if (strv_extend(&res, global_runtime_config) < 0) return NULL; if (strv_extend(&res, generator) < 0) return NULL; if (strv_extend(&res, data_home) < 0) return NULL; if (strv_extend_strv_concat(&res, data_dirs, "/systemd/user") < 0) return NULL; if (strv_extend_strv(&res, (char**) user_data_unit_paths, false) < 0) return NULL; if (strv_extend(&res, generator_late) < 0) return NULL; if (path_strv_make_absolute_cwd(res) < 0) return NULL; return TAKE_PTR(res); } bool path_is_user_data_dir(const char *path) { assert(path); return strv_contains((char**) user_data_unit_paths, path); } bool path_is_user_config_dir(const char *path) { assert(path); return strv_contains((char**) user_config_unit_paths, path); } static int acquire_generator_dirs( LookupScope scope, const char *tempdir, char **generator, char **generator_early, char **generator_late) { _cleanup_free_ char *x = NULL, *y = NULL, *z = NULL, *p = NULL; const char *prefix; assert(generator); assert(generator_early); assert(generator_late); assert(IN_SET(scope, LOOKUP_SCOPE_SYSTEM, LOOKUP_SCOPE_USER, LOOKUP_SCOPE_GLOBAL)); if (scope == LOOKUP_SCOPE_GLOBAL) return -EOPNOTSUPP; if (tempdir) prefix = tempdir; else if (scope == LOOKUP_SCOPE_SYSTEM) prefix = "/run/systemd"; else { /* LOOKUP_SCOPE_USER */ const char *e; e = getenv("XDG_RUNTIME_DIR"); if (!e) return -ENXIO; p = path_join(e, "/systemd"); if (!p) return -ENOMEM; prefix = p; } x = path_join(prefix, "generator"); if (!x) return -ENOMEM; y = path_join(prefix, "generator.early"); if (!y) return -ENOMEM; z = path_join(prefix, "generator.late"); if (!z) return -ENOMEM; *generator = TAKE_PTR(x); *generator_early = TAKE_PTR(y); *generator_late = TAKE_PTR(z); return 0; } static int acquire_transient_dir( LookupScope scope, const char *tempdir, char **ret) { char *transient; assert(ret); assert(IN_SET(scope, LOOKUP_SCOPE_SYSTEM, LOOKUP_SCOPE_USER, LOOKUP_SCOPE_GLOBAL)); if (scope == LOOKUP_SCOPE_GLOBAL) return -EOPNOTSUPP; if (tempdir) transient = path_join(tempdir, "transient"); else if (scope == LOOKUP_SCOPE_SYSTEM) transient = strdup("/run/systemd/transient"); else return xdg_user_runtime_dir(ret, "/systemd/transient"); if (!transient) return -ENOMEM; *ret = transient; return 0; } static int acquire_config_dirs(LookupScope scope, char **persistent, char **runtime) { _cleanup_free_ char *a = NULL, *b = NULL; int r; assert(persistent); assert(runtime); switch (scope) { case LOOKUP_SCOPE_SYSTEM: a = strdup(SYSTEM_CONFIG_UNIT_DIR); b = strdup("/run/systemd/system"); break; case LOOKUP_SCOPE_GLOBAL: a = strdup(USER_CONFIG_UNIT_DIR); b = strdup("/run/systemd/user"); break; case LOOKUP_SCOPE_USER: r = xdg_user_config_dir(&a, "/systemd/user"); if (r < 0 && r != -ENXIO) return r; r = xdg_user_runtime_dir(runtime, "/systemd/user"); if (r < 0) { if (r != -ENXIO) return r; /* If XDG_RUNTIME_DIR is not set, don't consider that fatal, simply initialize the runtime * directory to NULL */ *runtime = NULL; } *persistent = TAKE_PTR(a); return 0; default: assert_not_reached(); } if (!a || !b) return -ENOMEM; *persistent = TAKE_PTR(a); *runtime = TAKE_PTR(b); return 0; } static int acquire_control_dirs(LookupScope scope, char **persistent, char **runtime) { _cleanup_free_ char *a = NULL; int r; assert(persistent); assert(runtime); switch (scope) { case LOOKUP_SCOPE_SYSTEM: { _cleanup_free_ char *b = NULL; a = strdup("/etc/systemd/system.control"); if (!a) return -ENOMEM; b = strdup("/run/systemd/system.control"); if (!b) return -ENOMEM; *runtime = TAKE_PTR(b); break; } case LOOKUP_SCOPE_USER: r = xdg_user_config_dir(&a, "/systemd/user.control"); if (r < 0 && r != -ENXIO) return r; r = xdg_user_runtime_dir(runtime, "/systemd/user.control"); if (r < 0) { if (r != -ENXIO) return r; /* If XDG_RUNTIME_DIR is not set, don't consider this fatal, simply initialize the directory to * NULL */ *runtime = NULL; } break; case LOOKUP_SCOPE_GLOBAL: return -EOPNOTSUPP; default: assert_not_reached(); } *persistent = TAKE_PTR(a); return 0; } static int acquire_attached_dirs( LookupScope scope, char **ret_persistent, char **ret_runtime) { _cleanup_free_ char *a = NULL, *b = NULL; assert(ret_persistent); assert(ret_runtime); /* Portable services are not available to regular users for now. */ if (scope != LOOKUP_SCOPE_SYSTEM) return -EOPNOTSUPP; a = strdup("/etc/systemd/system.attached"); if (!a) return -ENOMEM; b = strdup("/run/systemd/system.attached"); if (!b) return -ENOMEM; *ret_persistent = TAKE_PTR(a); *ret_runtime = TAKE_PTR(b); return 0; } static int patch_root_prefix(char **p, const char *root_dir) { char *c; assert(p); if (!*p) return 0; c = path_join(root_dir, *p); if (!c) return -ENOMEM; free_and_replace(*p, c); return 0; } static int patch_root_prefix_strv(char **l, const char *root_dir) { int r; if (!root_dir) return 0; STRV_FOREACH(i, l) { r = patch_root_prefix(i, root_dir); if (r < 0) return r; } return 0; } static int get_paths_from_environ(const char *var, char ***paths, bool *append) { const char *e; int r; assert(var); assert(paths); assert(append); *append = false; e = getenv(var); if (e) { const char *k; k = endswith(e, ":"); if (k) { e = strndupa_safe(e, k - e); *append = true; } /* FIXME: empty components in other places should be rejected. */ r = path_split_and_make_absolute(e, paths); if (r < 0) return r; } return 0; } int lookup_paths_init( LookupPaths *lp, LookupScope scope, LookupPathsFlags flags, const char *root_dir) { _cleanup_(rmdir_and_freep) char *tempdir = NULL; _cleanup_free_ char *root = NULL, *persistent_config = NULL, *runtime_config = NULL, *global_persistent_config = NULL, *global_runtime_config = NULL, *generator = NULL, *generator_early = NULL, *generator_late = NULL, *transient = NULL, *persistent_control = NULL, *runtime_control = NULL, *persistent_attached = NULL, *runtime_attached = NULL; bool append = false; /* Add items from SYSTEMD_UNIT_PATH before normal directories */ _cleanup_strv_free_ char **paths = NULL; int r; assert(lp); assert(scope >= 0); assert(scope < _LOOKUP_SCOPE_MAX); #if HAVE_SPLIT_USR flags |= LOOKUP_PATHS_SPLIT_USR; #endif if (!empty_or_root(root_dir)) { if (scope == LOOKUP_SCOPE_USER) return -EINVAL; r = is_dir(root_dir, true); if (r < 0) return r; if (r == 0) return -ENOTDIR; root = strdup(root_dir); if (!root) return -ENOMEM; } if (flags & LOOKUP_PATHS_TEMPORARY_GENERATED) { r = mkdtemp_malloc("/tmp/systemd-temporary-XXXXXX", &tempdir); if (r < 0) return log_debug_errno(r, "Failed to create temporary directory: %m"); } /* Note: when XDG_RUNTIME_DIR is not set this will not return -ENXIO, but simply set runtime_config to NULL */ r = acquire_config_dirs(scope, &persistent_config, &runtime_config); if (r < 0) return r; if (scope == LOOKUP_SCOPE_USER) { r = acquire_config_dirs(LOOKUP_SCOPE_GLOBAL, &global_persistent_config, &global_runtime_config); if (r < 0) return r; } if ((flags & LOOKUP_PATHS_EXCLUDE_GENERATED) == 0) { /* Note: if XDG_RUNTIME_DIR is not set, this will fail completely with ENXIO */ r = acquire_generator_dirs(scope, tempdir, &generator, &generator_early, &generator_late); if (r < 0 && !IN_SET(r, -EOPNOTSUPP, -ENXIO)) return r; } /* Note: if XDG_RUNTIME_DIR is not set, this will fail completely with ENXIO */ r = acquire_transient_dir(scope, tempdir, &transient); if (r < 0 && !IN_SET(r, -EOPNOTSUPP, -ENXIO)) return r; /* Note: when XDG_RUNTIME_DIR is not set this will not return -ENXIO, but simply set runtime_control to NULL */ r = acquire_control_dirs(scope, &persistent_control, &runtime_control); if (r < 0 && r != -EOPNOTSUPP) return r; r = acquire_attached_dirs(scope, &persistent_attached, &runtime_attached); if (r < 0 && r != -EOPNOTSUPP) return r; /* First priority is whatever has been passed to us via env vars */ r = get_paths_from_environ("SYSTEMD_UNIT_PATH", &paths, &append); if (r < 0) return r; if (!paths || append) { /* Let's figure something out. */ _cleanup_strv_free_ char **add = NULL; /* For the user units we include share/ in the search * path in order to comply with the XDG basedir spec. * For the system stuff we avoid such nonsense. OTOH * we include /lib in the search path for the system * stuff but avoid it for user stuff. */ switch (scope) { case LOOKUP_SCOPE_SYSTEM: add = strv_new( /* If you modify this you also want to modify * systemdsystemunitpath= in systemd.pc.in! */ STRV_IFNOTNULL(persistent_control), STRV_IFNOTNULL(runtime_control), STRV_IFNOTNULL(transient), STRV_IFNOTNULL(generator_early), persistent_config, SYSTEM_CONFIG_UNIT_DIR, "/etc/systemd/system", STRV_IFNOTNULL(persistent_attached), runtime_config, "/run/systemd/system", STRV_IFNOTNULL(runtime_attached), STRV_IFNOTNULL(generator), "/usr/local/lib/systemd/system", SYSTEM_DATA_UNIT_DIR, "/usr/lib/systemd/system", STRV_IFNOTNULL(flags & LOOKUP_PATHS_SPLIT_USR ? "/lib/systemd/system" : NULL), STRV_IFNOTNULL(generator_late)); break; case LOOKUP_SCOPE_GLOBAL: add = strv_new( /* If you modify this you also want to modify * systemduserunitpath= in systemd.pc.in, and * the arrays in user_dirs() above! */ STRV_IFNOTNULL(persistent_control), STRV_IFNOTNULL(runtime_control), STRV_IFNOTNULL(transient), STRV_IFNOTNULL(generator_early), persistent_config, USER_CONFIG_UNIT_DIR, "/etc/systemd/user", runtime_config, "/run/systemd/user", STRV_IFNOTNULL(generator), "/usr/local/share/systemd/user", "/usr/share/systemd/user", "/usr/local/lib/systemd/user", USER_DATA_UNIT_DIR, "/usr/lib/systemd/user", STRV_IFNOTNULL(generator_late)); break; case LOOKUP_SCOPE_USER: add = user_dirs(persistent_config, runtime_config, global_persistent_config, global_runtime_config, generator, generator_early, generator_late, transient, persistent_control, runtime_control); break; default: assert_not_reached(); } if (!add) return -ENOMEM; if (paths) { r = strv_extend_strv(&paths, add, true); if (r < 0) return r; } else /* Small optimization: if paths is NULL (and it usually is), we can simply assign 'add' to it, * and don't have to copy anything */ paths = TAKE_PTR(add); } r = patch_root_prefix(&persistent_config, root); if (r < 0) return r; r = patch_root_prefix(&runtime_config, root); if (r < 0) return r; r = patch_root_prefix(&generator, root); if (r < 0) return r; r = patch_root_prefix(&generator_early, root); if (r < 0) return r; r = patch_root_prefix(&generator_late, root); if (r < 0) return r; r = patch_root_prefix(&transient, root); if (r < 0) return r; r = patch_root_prefix(&persistent_control, root); if (r < 0) return r; r = patch_root_prefix(&runtime_control, root); if (r < 0) return r; r = patch_root_prefix(&persistent_attached, root); if (r < 0) return r; r = patch_root_prefix(&runtime_attached, root); if (r < 0) return r; r = patch_root_prefix_strv(paths, root); if (r < 0) return -ENOMEM; *lp = (LookupPaths) { .search_path = strv_uniq(TAKE_PTR(paths)), .persistent_config = TAKE_PTR(persistent_config), .runtime_config = TAKE_PTR(runtime_config), .generator = TAKE_PTR(generator), .generator_early = TAKE_PTR(generator_early), .generator_late = TAKE_PTR(generator_late), .transient = TAKE_PTR(transient), .persistent_control = TAKE_PTR(persistent_control), .runtime_control = TAKE_PTR(runtime_control), .persistent_attached = TAKE_PTR(persistent_attached), .runtime_attached = TAKE_PTR(runtime_attached), .root_dir = TAKE_PTR(root), .temporary_dir = TAKE_PTR(tempdir), }; return 0; } int lookup_paths_init_or_warn(LookupPaths *lp, LookupScope scope, LookupPathsFlags flags, const char *root_dir) { int r; r = lookup_paths_init(lp, scope, flags, root_dir); if (r < 0) return log_error_errno(r, "Failed to initialize unit search paths%s%s: %m", isempty(root_dir) ? "" : " for root directory ", strempty(root_dir)); return r; } void lookup_paths_free(LookupPaths *lp) { if (!lp) return; lp->search_path = strv_free(lp->search_path); lp->persistent_config = mfree(lp->persistent_config); lp->runtime_config = mfree(lp->runtime_config); lp->persistent_attached = mfree(lp->persistent_attached); lp->runtime_attached = mfree(lp->runtime_attached); lp->generator = mfree(lp->generator); lp->generator_early = mfree(lp->generator_early); lp->generator_late = mfree(lp->generator_late); lp->transient = mfree(lp->transient); lp->persistent_control = mfree(lp->persistent_control); lp->runtime_control = mfree(lp->runtime_control); lp->root_dir = mfree(lp->root_dir); lp->temporary_dir = mfree(lp->temporary_dir); } void lookup_paths_log(LookupPaths *lp) { assert(lp); if (strv_isempty(lp->search_path)) { log_debug("Ignoring unit files."); lp->search_path = strv_free(lp->search_path); } else { _cleanup_free_ char *t = NULL; t = strv_join(lp->search_path, "\n\t"); log_debug("Looking for unit files in (higher priority first):\n\t%s", strna(t)); } } char **generator_binary_paths(LookupScope scope) { bool append = false; /* Add items from SYSTEMD_GENERATOR_PATH before normal directories */ _cleanup_strv_free_ char **paths = NULL; int r; /* First priority is whatever has been passed to us via env vars */ r = get_paths_from_environ("SYSTEMD_GENERATOR_PATH", &paths, &append); if (r < 0) return NULL; if (!paths || append) { _cleanup_strv_free_ char **add = NULL; switch (scope) { case LOOKUP_SCOPE_SYSTEM: add = strv_new("/run/systemd/system-generators", "/etc/systemd/system-generators", "/usr/local/lib/systemd/system-generators", SYSTEM_GENERATOR_DIR); break; case LOOKUP_SCOPE_GLOBAL: case LOOKUP_SCOPE_USER: add = strv_new("/run/systemd/user-generators", "/etc/systemd/user-generators", "/usr/local/lib/systemd/user-generators", USER_GENERATOR_DIR); break; default: assert_not_reached(); } if (!add) return NULL; if (paths) { r = strv_extend_strv(&paths, add, true); if (r < 0) return NULL; } else /* Small optimization: if paths is NULL (and it usually is), we can simply assign 'add' to it, * and don't have to copy anything */ paths = TAKE_PTR(add); } return TAKE_PTR(paths); } char **env_generator_binary_paths(bool is_system) { bool append = false; /* Add items from SYSTEMD_ENVIRONMENT_GENERATOR_PATH before normal directories */ _cleanup_strv_free_ char **paths = NULL; _cleanup_strv_free_ char **add = NULL; int r; /* First priority is whatever has been passed to us via env vars */ r = get_paths_from_environ("SYSTEMD_ENVIRONMENT_GENERATOR_PATH", &paths, &append); if (r < 0) return NULL; if (!paths || append) { if (is_system) add = strv_new("/run/systemd/system-environment-generators", "/etc/systemd/system-environment-generators", "/usr/local/lib/systemd/system-environment-generators", SYSTEM_ENV_GENERATOR_DIR); else add = strv_new("/run/systemd/user-environment-generators", "/etc/systemd/user-environment-generators", "/usr/local/lib/systemd/user-environment-generators", USER_ENV_GENERATOR_DIR); if (!add) return NULL; } if (paths) { r = strv_extend_strv(&paths, add, true); if (r < 0) return NULL; } else /* Small optimization: if paths is NULL (and it usually is), we can simply assign 'add' to it, * and don't have to copy anything */ paths = TAKE_PTR(add); return TAKE_PTR(paths); } int find_portable_profile(const char *name, const char *unit, char **ret_path) { const char *p, *dot; assert(name); assert(ret_path); assert_se(dot = strrchr(unit, '.')); NULSTR_FOREACH(p, PORTABLE_PROFILE_DIRS) { _cleanup_free_ char *joined = NULL; joined = strjoin(p, "/", name, "/", dot + 1, ".conf"); if (!joined) return -ENOMEM; if (laccess(joined, F_OK) >= 0) { *ret_path = TAKE_PTR(joined); return 0; } if (errno != ENOENT) return -errno; } return -ENOENT; }