diff options
Diffstat (limited to 'src/shared/specifier.c')
-rw-r--r-- | src/shared/specifier.c | 509 |
1 files changed, 509 insertions, 0 deletions
diff --git a/src/shared/specifier.c b/src/shared/specifier.c new file mode 100644 index 0000000..911bff2 --- /dev/null +++ b/src/shared/specifier.c @@ -0,0 +1,509 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include <errno.h> +#include <stdbool.h> +#include <stddef.h> +#include <stdlib.h> +#include <sys/utsname.h> + +#include "sd-id128.h" + +#include "alloc-util.h" +#include "architecture.h" +#include "chase-symlinks.h" +#include "fd-util.h" +#include "format-util.h" +#include "fs-util.h" +#include "hostname-util.h" +#include "id128-util.h" +#include "macro.h" +#include "os-util.h" +#include "path-lookup.h" +#include "path-util.h" +#include "specifier.h" +#include "string-util.h" +#include "strv.h" +#include "user-util.h" + +/* + * Generic infrastructure for replacing %x style specifiers in + * strings. Will call a callback for each replacement. + */ + +/* Any ASCII character or digit: our pool of potential specifiers, + * and "%" used for escaping. */ +#define POSSIBLE_SPECIFIERS ALPHANUMERICAL "%" + +int specifier_printf(const char *text, size_t max_length, const Specifier table[], const char *root, const void *userdata, char **ret) { + _cleanup_free_ char *result = NULL; + bool percent = false; + size_t l; + char *t; + int r; + + assert(ret); + assert(text); + assert(table); + + l = strlen(text); + if (!GREEDY_REALLOC(result, l + 1)) + return -ENOMEM; + t = result; + + for (const char *f = text; *f != '\0'; f++, l--) { + if (percent) { + percent = false; + + if (*f == '%') + *(t++) = '%'; + else { + const Specifier *i; + + for (i = table; i->specifier; i++) + if (i->specifier == *f) + break; + + if (i->lookup) { + _cleanup_free_ char *w = NULL; + size_t k, j; + + r = i->lookup(i->specifier, i->data, root, userdata, &w); + if (r < 0) + return r; + if (isempty(w)) + continue; + + j = t - result; + k = strlen(w); + + if (!GREEDY_REALLOC(result, j + k + l + 1)) + return -ENOMEM; + memcpy(result + j, w, k); + t = result + j + k; + } else if (strchr(POSSIBLE_SPECIFIERS, *f)) + /* Oops, an unknown specifier. */ + return -EBADSLT; + else { + *(t++) = '%'; + *(t++) = *f; + } + } + } else if (*f == '%') + percent = true; + else + *(t++) = *f; + + if ((size_t) (t - result) > max_length) + return -ENAMETOOLONG; + } + + /* If string ended with a stray %, also end with % */ + if (percent) { + *(t++) = '%'; + if ((size_t) (t - result) > max_length) + return -ENAMETOOLONG; + } + *(t++) = 0; + + *ret = TAKE_PTR(result); + return 0; +} + +/* Generic handler for simple string replacements */ + +int specifier_string(char specifier, const void *data, const char *root, const void *userdata, char **ret) { + char *n = NULL; + + assert(ret); + + if (!isempty(data)) { + n = strdup(data); + if (!n) + return -ENOMEM; + } + + *ret = n; + return 0; +} + +int specifier_real_path(char specifier, const void *data, const char *root, const void *userdata, char **ret) { + const char *path = data; + + assert(ret); + + if (!path) + return -ENOENT; + + return chase_symlinks(path, root, 0, ret, NULL); +} + +int specifier_real_directory(char specifier, const void *data, const char *root, const void *userdata, char **ret) { + _cleanup_free_ char *path = NULL; + int r; + + assert(ret); + + r = specifier_real_path(specifier, data, root, userdata, &path); + if (r < 0) + return r; + + assert(path); + return path_extract_directory(path, ret); +} + +int specifier_id128(char specifier, const void *data, const char *root, const void *userdata, char **ret) { + const sd_id128_t *id = ASSERT_PTR(data); + char *n; + + n = new(char, SD_ID128_STRING_MAX); + if (!n) + return -ENOMEM; + + *ret = sd_id128_to_string(*id, n); + return 0; +} + +int specifier_uuid(char specifier, const void *data, const char *root, const void *userdata, char **ret) { + const sd_id128_t *id = ASSERT_PTR(data); + char *n; + + n = new(char, SD_ID128_UUID_STRING_MAX); + if (!n) + return -ENOMEM; + + *ret = sd_id128_to_uuid_string(*id, n); + return 0; +} + +int specifier_uint64(char specifier, const void *data, const char *root, const void *userdata, char **ret) { + const uint64_t *n = ASSERT_PTR(data); + + return asprintf(ret, "%" PRIu64, *n) < 0 ? -ENOMEM : 0; +} + +int specifier_machine_id(char specifier, const void *data, const char *root, const void *userdata, char **ret) { + sd_id128_t id; + int r; + + assert(ret); + + if (root) { + _cleanup_close_ int fd = -1; + + fd = chase_symlinks_and_open("/etc/machine-id", root, CHASE_PREFIX_ROOT, O_RDONLY|O_CLOEXEC|O_NOCTTY, NULL); + if (fd < 0) + /* Translate error for missing os-release file to EUNATCH. */ + return fd == -ENOENT ? -EUNATCH : fd; + + r = id128_read_fd(fd, ID128_FORMAT_PLAIN, &id); + } else + r = sd_id128_get_machine(&id); + if (r < 0) + return r; + + return specifier_id128(specifier, &id, root, userdata, ret); +} + +int specifier_boot_id(char specifier, const void *data, const char *root, const void *userdata, char **ret) { + sd_id128_t id; + int r; + + assert(ret); + + r = sd_id128_get_boot(&id); + if (r < 0) + return r; + + return specifier_id128(specifier, &id, root, userdata, ret); +} + +int specifier_hostname(char specifier, const void *data, const char *root, const void *userdata, char **ret) { + char *n; + + assert(ret); + + n = gethostname_malloc(); + if (!n) + return -ENOMEM; + + *ret = n; + return 0; +} + +int specifier_short_hostname(char specifier, const void *data, const char *root, const void *userdata, char **ret) { + char *n; + + assert(ret); + + n = gethostname_short_malloc(); + if (!n) + return -ENOMEM; + + *ret = n; + return 0; +} + +int specifier_pretty_hostname(char specifier, const void *data, const char *root, const void *userdata, char **ret) { + char *n = NULL; + + assert(ret); + + if (get_pretty_hostname(&n) < 0) { + n = gethostname_short_malloc(); + if (!n) + return -ENOMEM; + } + + *ret = n; + return 0; +} + +int specifier_kernel_release(char specifier, const void *data, const char *root, const void *userdata, char **ret) { + struct utsname uts; + char *n; + + assert(ret); + + if (uname(&uts) < 0) + return -errno; + + n = strdup(uts.release); + if (!n) + return -ENOMEM; + + *ret = n; + return 0; +} + +int specifier_architecture(char specifier, const void *data, const char *root, const void *userdata, char **ret) { + char *t; + + assert(ret); + + t = strdup(architecture_to_string(uname_architecture())); + if (!t) + return -ENOMEM; + + *ret = t; + return 0; +} + +/* Note: fields in /etc/os-release might quite possibly be missing, even if everything is entirely valid + * otherwise. We'll return an empty value or NULL in that case from the functions below. But if the + * os-release file is missing, we'll return -EUNATCH. This means that something is seriously wrong with the + * installation. */ + +static int parse_os_release_specifier(const char *root, const char *id, char **ret) { + _cleanup_free_ char *v = NULL; + int r; + + assert(ret); + + r = parse_os_release(root, id, &v); + if (r >= 0) + /* parse_os_release() calls parse_env_file() which only sets the return value for + * entries found. Let's make sure we set the return value in all cases. */ + *ret = TAKE_PTR(v); + + /* Translate error for missing os-release file to EUNATCH. */ + return r == -ENOENT ? -EUNATCH : r; +} + +int specifier_os_id(char specifier, const void *data, const char *root, const void *userdata, char **ret) { + return parse_os_release_specifier(root, "ID", ret); +} + +int specifier_os_version_id(char specifier, const void *data, const char *root, const void *userdata, char **ret) { + return parse_os_release_specifier(root, "VERSION_ID", ret); +} + +int specifier_os_build_id(char specifier, const void *data, const char *root, const void *userdata, char **ret) { + return parse_os_release_specifier(root, "BUILD_ID", ret); +} + +int specifier_os_variant_id(char specifier, const void *data, const char *root, const void *userdata, char **ret) { + return parse_os_release_specifier(root, "VARIANT_ID", ret); +} + +int specifier_os_image_id(char specifier, const void *data, const char *root, const void *userdata, char **ret) { + return parse_os_release_specifier(root, "IMAGE_ID", ret); +} + +int specifier_os_image_version(char specifier, const void *data, const char *root, const void *userdata, char **ret) { + return parse_os_release_specifier(root, "IMAGE_VERSION", ret); +} + +int specifier_group_name(char specifier, const void *data, const char *root, const void *userdata, char **ret) { + LookupScope scope = PTR_TO_INT(data); + char *t; + + assert(ret); + + if (scope == LOOKUP_SCOPE_GLOBAL) + return -EINVAL; + + t = gid_to_name(scope == LOOKUP_SCOPE_USER ? getgid() : 0); + if (!t) + return -ENOMEM; + + *ret = t; + return 0; +} + +int specifier_group_id(char specifier, const void *data, const char *root, const void *userdata, char **ret) { + LookupScope scope = PTR_TO_INT(data); + gid_t gid; + + assert(ret); + + if (scope == LOOKUP_SCOPE_GLOBAL) + return -EINVAL; + + gid = scope == LOOKUP_SCOPE_USER ? getgid() : 0; + + if (asprintf(ret, UID_FMT, gid) < 0) + return -ENOMEM; + + return 0; +} + +int specifier_user_name(char specifier, const void *data, const char *root, const void *userdata, char **ret) { + LookupScope scope = PTR_TO_INT(data); + uid_t uid; + char *t; + + assert(ret); + + if (scope == LOOKUP_SCOPE_GLOBAL) + return -EINVAL; + + uid = scope == LOOKUP_SCOPE_USER ? getuid() : 0; + + /* If we are UID 0 (root), this will not result in NSS, otherwise it might. This is good, as we want + * to be able to run this in PID 1, where our user ID is 0, but where NSS lookups are not allowed. + + * We don't use getusername_malloc() here, because we don't want to look at $USER, to remain + * consistent with specifer_user_id() below. + */ + + t = uid_to_name(uid); + if (!t) + return -ENOMEM; + + *ret = t; + return 0; +} + +int specifier_user_id(char specifier, const void *data, const char *root, const void *userdata, char **ret) { + LookupScope scope = PTR_TO_INT(data); + uid_t uid; + + assert(ret); + + if (scope == LOOKUP_SCOPE_GLOBAL) + return -EINVAL; + + uid = scope == LOOKUP_SCOPE_USER ? getuid() : 0; + + if (asprintf(ret, UID_FMT, uid) < 0) + return -ENOMEM; + + return 0; +} + +int specifier_user_home(char specifier, const void *data, const char *root, const void *userdata, char **ret) { + assert(ret); + + /* On PID 1 (which runs as root) this will not result in NSS, + * which is good. See above */ + + return get_home_dir(ret); +} + +int specifier_user_shell(char specifier, const void *data, const char *root, const void *userdata, char **ret) { + assert(ret); + + /* On PID 1 (which runs as root) this will not result in NSS, + * which is good. See above */ + + return get_shell(ret); +} + +int specifier_tmp_dir(char specifier, const void *data, const char *root, const void *userdata, char **ret) { + const char *p; + char *copy; + int r; + + assert(ret); + + if (root) /* If root dir is set, don't honour $TMP or similar */ + p = "/tmp"; + else { + r = tmp_dir(&p); + if (r < 0) + return r; + } + copy = strdup(p); + if (!copy) + return -ENOMEM; + + *ret = copy; + return 0; +} + +int specifier_var_tmp_dir(char specifier, const void *data, const char *root, const void *userdata, char **ret) { + const char *p; + char *copy; + int r; + + assert(ret); + + if (root) + p = "/var/tmp"; + else { + r = var_tmp_dir(&p); + if (r < 0) + return r; + } + copy = strdup(p); + if (!copy) + return -ENOMEM; + + *ret = copy; + return 0; +} + +int specifier_escape_strv(char **l, char ***ret) { + char **z, **p, **q; + + assert(ret); + + if (strv_isempty(l)) { + *ret = NULL; + return 0; + } + + z = new(char*, strv_length(l)+1); + if (!z) + return -ENOMEM; + + for (p = l, q = z; *p; p++, q++) { + + *q = specifier_escape(*p); + if (!*q) { + strv_free(z); + return -ENOMEM; + } + } + + *q = NULL; + *ret = z; + + return 0; +} + +const Specifier system_and_tmp_specifier_table[] = { + COMMON_SYSTEM_SPECIFIERS, + COMMON_TMP_SPECIFIERS, + {} +}; |