mirror of
https://github.com/morgan9e/systemd
synced 2026-04-14 16:37:19 +09:00
-1 was used everywhere, but -EBADF or -EBADFD started being used in various places. Let's make things consistent in the new style. Note that there are two candidates: EBADF 9 Bad file descriptor EBADFD 77 File descriptor in bad state Since we're initializating the fd, we're just assigning a value that means "no fd yet", so it's just a bad file descriptor, and the first errno fits better. If instead we had a valid file descriptor that became invalid because of some operation or state change, the other errno would fit better. In some places, initialization is dropped if unnecessary.
510 lines
14 KiB
C
510 lines
14 KiB
C
/* 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 = -EBADF;
|
|
|
|
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) {
|
|
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 = 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,
|
|
{}
|
|
};
|