diff options
Diffstat (limited to 'src/shared/specifier.c')
-rw-r--r-- | src/shared/specifier.c | 358 |
1 files changed, 358 insertions, 0 deletions
diff --git a/src/shared/specifier.c b/src/shared/specifier.c new file mode 100644 index 0000000..86731f8 --- /dev/null +++ b/src/shared/specifier.c @@ -0,0 +1,358 @@ +/* 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 "format-util.h" +#include "fs-util.h" +#include "hostname-util.h" +#include "macro.h" +#include "os-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, const Specifier table[], const void *userdata, char **_ret) { + size_t l, allocated = 0; + _cleanup_free_ char *ret = NULL; + char *t; + const char *f; + bool percent = false; + int r; + + assert(text); + assert(table); + + l = strlen(text); + if (!GREEDY_REALLOC(ret, allocated, l + 1)) + return -ENOMEM; + t = ret; + + for (f = text; *f; f++, l--) + if (percent) { + 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, userdata, &w); + if (r < 0) + return r; + + j = t - ret; + k = strlen(w); + + if (!GREEDY_REALLOC(ret, allocated, j + k + l + 1)) + return -ENOMEM; + memcpy(ret + j, w, k); + t = ret + j + k; + } else if (strchr(POSSIBLE_SPECIFIERS, *f)) + /* Oops, an unknown specifier. */ + return -EBADSLT; + else { + *(t++) = '%'; + *(t++) = *f; + } + } + + percent = false; + } else if (*f == '%') + percent = true; + else + *(t++) = *f; + + /* If string ended with a stray %, also end with % */ + if (percent) + *(t++) = '%'; + *(t++) = 0; + + /* Try to deallocate unused bytes, but don't sweat it too much */ + if ((size_t)(t - ret) < allocated) { + t = realloc(ret, t - ret); + if (t) + ret = t; + } + + *_ret = TAKE_PTR(ret); + return 0; +} + +/* Generic handler for simple string replacements */ + +int specifier_string(char specifier, const void *data, const void *userdata, char **ret) { + char *n; + + n = strdup(strempty(data)); + if (!n) + return -ENOMEM; + + *ret = n; + return 0; +} + +int specifier_machine_id(char specifier, const void *data, const void *userdata, char **ret) { + sd_id128_t id; + char *n; + int r; + + r = sd_id128_get_machine(&id); + if (r < 0) + return r; + + n = new(char, 33); + if (!n) + return -ENOMEM; + + *ret = sd_id128_to_string(id, n); + return 0; +} + +int specifier_boot_id(char specifier, const void *data, const void *userdata, char **ret) { + sd_id128_t id; + char *n; + int r; + + r = sd_id128_get_boot(&id); + if (r < 0) + return r; + + n = new(char, 33); + if (!n) + return -ENOMEM; + + *ret = sd_id128_to_string(id, n); + return 0; +} + +int specifier_host_name(char specifier, const void *data, const void *userdata, char **ret) { + char *n; + + n = gethostname_malloc(); + if (!n) + return -ENOMEM; + + *ret = n; + return 0; +} + +int specifier_short_host_name(char specifier, const void *data, const void *userdata, char **ret) { + char *n; + + n = gethostname_short_malloc(); + if (!n) + return -ENOMEM; + + *ret = n; + return 0; +} + +int specifier_kernel_release(char specifier, const void *data, const void *userdata, char **ret) { + struct utsname uts; + char *n; + int r; + + r = uname(&uts); + if (r < 0) + return -errno; + + n = strdup(uts.release); + if (!n) + return -ENOMEM; + + *ret = n; + return 0; +} + +int specifier_architecture(char specifier, const void *data, const void *userdata, char **ret) { + char *t; + + t = strdup(architecture_to_string(uname_architecture())); + if (!t) + return -ENOMEM; + + *ret = t; + return 0; +} + +static int specifier_os_release_common(const char *field, char **ret) { + char *t = NULL; + int r; + + r = parse_os_release(NULL, field, &t, NULL); + if (r < 0) + return r; + if (!t) { + /* fields in /etc/os-release might quite possibly be missing, even if everything is entirely + * valid otherwise. Let's hence return "" in that case. */ + t = strdup(""); + if (!t) + return -ENOMEM; + } + + *ret = t; + return 0; +} + +int specifier_os_id(char specifier, const void *data, const void *userdata, char **ret) { + return specifier_os_release_common("ID", ret); +} + +int specifier_os_version_id(char specifier, const void *data, const void *userdata, char **ret) { + return specifier_os_release_common("VERSION_ID", ret); +} + +int specifier_os_build_id(char specifier, const void *data, const void *userdata, char **ret) { + return specifier_os_release_common("BUILD_ID", ret); +} + +int specifier_os_variant_id(char specifier, const void *data, const void *userdata, char **ret) { + return specifier_os_release_common("VARIANT_ID", ret); +} + +int specifier_group_name(char specifier, const void *data, const void *userdata, char **ret) { + char *t; + + t = gid_to_name(getgid()); + if (!t) + return -ENOMEM; + + *ret = t; + return 0; +} + +int specifier_group_id(char specifier, const void *data, const void *userdata, char **ret) { + if (asprintf(ret, UID_FMT, getgid()) < 0) + return -ENOMEM; + + return 0; +} + +int specifier_user_name(char specifier, const void *data, const void *userdata, char **ret) { + char *t; + + /* 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(getuid()); + if (!t) + return -ENOMEM; + + *ret = t; + return 0; +} + +int specifier_user_id(char specifier, const void *data, const void *userdata, char **ret) { + + if (asprintf(ret, UID_FMT, getuid()) < 0) + return -ENOMEM; + + return 0; +} + +int specifier_user_home(char specifier, const void *data, const void *userdata, char **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 void *userdata, char **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 void *userdata, char **ret) { + const char *p; + char *copy; + int r; + + 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 void *userdata, char **ret) { + const char *p; + char *copy; + int r; + + 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; +} |