1
0
mirror of https://github.com/systemd/systemd.git synced 2025-02-25 21:57:32 +03:00

Merge pull request #31411 from poettering/build-path

userdbd,homed,importd,sysupdate,pid1: make it easier to run daemons that have callouts from build dir
This commit is contained in:
Luca Boccassi 2024-02-23 11:57:42 +00:00 committed by GitHub
commit e0699e067e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
13 changed files with 355 additions and 115 deletions

267
src/basic/build-path.c Normal file
View File

@ -0,0 +1,267 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#include <elf.h>
#include <link.h>
#include <sys/auxv.h>
#include "build-path.h"
#include "errno-list.h"
#include "errno-util.h"
#include "macro.h"
#include "path-util.h"
#include "process-util.h"
#include "unistd.h"
static int get_runpath_from_dynamic(const ElfW(Dyn) *d, const char **ret) {
size_t runpath_index = SIZE_MAX, rpath_index = SIZE_MAX;
const char *strtab = NULL;
assert(d);
/* Iterates through the PT_DYNAMIC section to find the DT_RUNPATH/DT_RPATH entries */
for (; d->d_tag != DT_NULL; d++) {
switch (d->d_tag) {
case DT_RUNPATH:
runpath_index = (size_t) d->d_un.d_val;
break;
case DT_RPATH:
rpath_index = (size_t) d->d_un.d_val;
break;
case DT_STRTAB:
strtab = (const char *) d->d_un.d_val;
break;
}
/* runpath wins, hence if we have the table and runpath we can exit the loop early */
if (strtab && runpath_index != SIZE_MAX)
break;
}
if (!strtab)
return -ENOTRECOVERABLE;
/* According to dl.so runpath wins of both runpath and rpath are defined. */
if (runpath_index != SIZE_MAX) {
if (ret)
*ret = strtab + runpath_index;
return 1;
}
if (rpath_index != SIZE_MAX) {
if (ret)
*ret = strtab + rpath_index;
return 1;
}
if (ret)
*ret = NULL;
return 0;
}
static int get_runpath(const char **ret) {
unsigned long phdr, phent, phnum;
/* Finds the rpath/runpath in the program headers of the main executable we are running in */
phdr = getauxval(AT_PHDR); /* Start offset of phdr */
if (phdr == 0)
return -ENOTRECOVERABLE;
phnum = getauxval(AT_PHNUM); /* Number of entries in phdr */
if (phnum == 0)
return -ENOTRECOVERABLE;
phent = getauxval(AT_PHENT); /* Size of entries in phdr */
if (phent < sizeof(ElfW(Phdr))) /* Safety check, that our idea of the structure matches the file */
return -ENOTRECOVERABLE;
ElfW(Addr) bias = 0, dyn = 0;
bool found_bias = false, found_dyn = false;
/* Iterate through the Phdr structures to find the PT_PHDR and PT_DYNAMIC sections */
for (unsigned long i = 0; i < phnum; i++) {
const ElfW(Phdr) *p = (const ElfW(Phdr)*) (phdr + (i * phent));
switch (p->p_type) {
case PT_PHDR:
if (p->p_vaddr > phdr) /* safety overflow check */
return -ENOTRECOVERABLE;
bias = (ElfW(Addr)) phdr - p->p_vaddr;
found_bias = true;
break;
case PT_DYNAMIC:
dyn = p->p_vaddr;
found_dyn = true;
break;
}
if (found_bias && found_dyn)
break;
}
if (!found_dyn)
return -ENOTRECOVERABLE;
return get_runpath_from_dynamic((const ElfW(Dyn)*) (bias + dyn), ret);
}
int get_build_exec_dir(char **ret) {
int r;
/* Returns the build execution directory if we are invoked in a build environment. Specifically, this
* checks if the main program binary has an rpath/runpath set (i.e. an explicit directory where to
* look for shared libraries) to $ORIGIN. If so we know that this is not a regular installed binary,
* but one which shall acquire its libraries from below a directory it is located in, i.e. a build
* directory or similar. In that case it typically makes sense to also search for our auxiliary
* executables we fork() off in a directory close to our main program binary, rather than in the
* system.
*
* This function is supposed to be used when looking for "callout" binaries that are closely related
* to the main program (i.e. speak a specific protocol between each other). And where it's generally
* a good idea to use the binary from the build tree (if there is one) instead of the system.
*
* Note that this does *not* actually return the rpath/runpath but the instead the directory the main
* executable was found in. This follows the logic that the result is supposed to be used for
* executable binaries (i.e. stuff in bindir), not for shared libraries (i.e. stuff in libdir), and
* hence the literal shared library path would just be wrong.
*
* TLDR: if we look for callouts in this dir first, running binaries from the meson build tree
* automatically uses the right callout.
*
* Returns:
* -ENOEXEC We are not running in an rpath/runpath $ORIGIN environment
* -ENOENT We don't know our own binary path
* -NOTRECOVERABLE Dynamic binary information missing?
*/
static int runpath_cached = -ERRNO_MAX-1;
if (runpath_cached == -ERRNO_MAX-1) {
const char *runpath = NULL;
runpath_cached = get_runpath(&runpath);
/* We only care if the runpath starts with $ORIGIN/ */
if (runpath_cached > 0 && !startswith(runpath, "$ORIGIN/"))
runpath_cached = 0;
}
if (runpath_cached < 0)
return runpath_cached;
if (runpath_cached == 0)
return -ENOEXEC;
_cleanup_free_ char *exe = NULL;
r = get_process_exe(0, &exe);
if (r < 0)
return runpath_cached = r;
return path_extract_directory(exe, ret);
}
static int find_build_dir_binary(const char *fn, char **ret) {
int r;
assert(fn);
assert(ret);
_cleanup_free_ char *build_dir = NULL;
r = get_build_exec_dir(&build_dir);
if (r < 0)
return r;
_cleanup_free_ char *np = path_join(build_dir, fn);
if (!np)
return -ENOMEM;
*ret = TAKE_PTR(np);
return 0;
}
static int find_environment_binary(const char *fn, const char **ret) {
/* If a path such as /usr/lib/systemd/systemd-foobar is specified, then this will check for an
* environment variable SYSTEMD_FOOBAR_PATH and return it if set. */
_cleanup_free_ char *s = strdup(fn);
if (!s)
return -ENOMEM;
ascii_strupper(s);
string_replace_char(s, '-', '_');
if (!strextend(&s, "_PATH"))
return -ENOMEM;
const char *e;
e = secure_getenv(s);
if (!e)
return -ENXIO;
*ret = e;
return 0;
}
int invoke_callout_binary(const char *path, char *const argv[]) {
int r;
assert(path);
/* Just like execv(), but tries to execute the specified binary in the build dir instead, if known */
_cleanup_free_ char *fn = NULL;
r = path_extract_filename(path, &fn);
if (r < 0)
return r;
if (r == O_DIRECTORY) /* Uh? */
return -EISDIR;
const char *e;
if (find_environment_binary(fn, &e) >= 0) {
/* If there's an explicit environment variable set for this binary, prefer it */
execv(e, argv);
return -errno; /* The environment variable counts, let's fail otherwise */
}
_cleanup_free_ char *np = NULL;
if (find_build_dir_binary(fn, &np) >= 0)
execv(np, argv);
execv(path, argv);
return -errno;
}
int pin_callout_binary(const char *path) {
int r;
assert(path);
/* Similar to invoke_callout_binary(), but pins (i.e. O_PATH opens) the binary instead of executing it. */
_cleanup_free_ char *fn = NULL;
r = path_extract_filename(path, &fn);
if (r < 0)
return r;
if (r == O_DIRECTORY) /* Uh? */
return -EISDIR;
const char *e;
if (find_environment_binary(fn, &e) >= 0)
return RET_NERRNO(open(e, O_CLOEXEC|O_PATH));
_cleanup_free_ char *np = NULL;
if (find_build_dir_binary(fn, &np) >= 0) {
r = RET_NERRNO(open(np, O_CLOEXEC|O_PATH));
if (r >= 0)
return r;
}
return RET_NERRNO(open(path, O_CLOEXEC|O_PATH));
}

8
src/basic/build-path.h Normal file
View File

@ -0,0 +1,8 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#pragma once
int get_build_exec_dir(char **ret);
int invoke_callout_binary(const char *path, char *const argv[]);
int pin_callout_binary(const char *path);

View File

@ -10,6 +10,7 @@ basic_sources = files(
'audit-util.c',
'btrfs.c',
'build.c',
'build-path.c',
'bus-label.c',
'cap-list.c',
'capability-util.c',

View File

@ -510,7 +510,7 @@ static int get_process_link_contents(pid_t pid, const char *proc_file, char **re
p = procfs_file_alloca(pid, proc_file);
r = readlink_malloc(p, ret);
return r == -ENOENT ? -ESRCH : r;
return (r == -ENOENT && proc_mounted() > 0) ? -ESRCH : r;
}
int get_process_exe(pid_t pid, char **ret) {

View File

@ -25,6 +25,7 @@
#include "alloc-util.h"
#include "audit-fd.h"
#include "boot-timestamps.h"
#include "build-path.h"
#include "bus-common-errors.h"
#include "bus-error.h"
#include "bus-kernel.h"
@ -1024,42 +1025,19 @@ int manager_new(RuntimeScope runtime_scope, ManagerTestRunFlags test_run_flags,
if (r < 0 && r != -EEXIST)
return r;
}
m->executor_fd = open(SYSTEMD_EXECUTOR_BINARY_PATH, O_CLOEXEC|O_PATH);
if (!FLAGS_SET(test_run_flags, MANAGER_TEST_DONT_OPEN_EXECUTOR)) {
m->executor_fd = pin_callout_binary(SYSTEMD_EXECUTOR_BINARY_PATH);
if (m->executor_fd < 0)
return log_emergency_errno(errno,
"Failed to open executor binary '%s': %m",
SYSTEMD_EXECUTOR_BINARY_PATH);
} else if (!FLAGS_SET(test_run_flags, MANAGER_TEST_DONT_OPEN_EXECUTOR)) {
_cleanup_free_ char *self_exe = NULL, *executor_path = NULL;
_cleanup_close_ int self_dir_fd = -EBADF;
int level = LOG_DEBUG;
/* Prefer sd-executor from the same directory as the test, e.g.: when running unit tests from the
* build directory. Fallback to working directory and then the installation path. */
r = readlink_and_make_absolute("/proc/self/exe", &self_exe);
if (r < 0)
return r;
self_dir_fd = open_parent(self_exe, O_CLOEXEC|O_PATH|O_DIRECTORY, 0);
if (self_dir_fd < 0)
return self_dir_fd;
m->executor_fd = RET_NERRNO(openat(self_dir_fd, "systemd-executor", O_CLOEXEC|O_PATH));
if (m->executor_fd == -ENOENT)
m->executor_fd = RET_NERRNO(openat(AT_FDCWD, "systemd-executor", O_CLOEXEC|O_PATH));
if (m->executor_fd == -ENOENT) {
m->executor_fd = RET_NERRNO(open(SYSTEMD_EXECUTOR_BINARY_PATH, O_CLOEXEC|O_PATH));
level = LOG_WARNING; /* Tests should normally use local builds */
}
if (m->executor_fd < 0)
return m->executor_fd;
return log_debug_errno(m->executor_fd, "Failed to pin executor binary: %m");
_cleanup_free_ char *executor_path = NULL;
r = fd_get_path(m->executor_fd, &executor_path);
if (r < 0)
return r;
log_full(level, "Using systemd-executor binary from '%s'.", executor_path);
log_debug("Using systemd-executor binary from '%s'.", executor_path);
}
/* Note that we do not set up the notify fd here. We do that after deserialization,

View File

@ -10,6 +10,7 @@
#include "blockdev-util.h"
#include "btrfs-util.h"
#include "build-path.h"
#include "bus-common-errors.h"
#include "bus-locator.h"
#include "data-fd-util.h"
@ -1276,7 +1277,7 @@ static int home_start_work(
return r;
if (r == 0) {
_cleanup_free_ char *joined = NULL;
const char *homework, *suffix, *unix_path;
const char *suffix, *unix_path;
/* Child */
@ -1320,12 +1321,8 @@ static int home_start_work(
if (r < 0)
log_warning_errno(r, "Failed to update $SYSTEMD_LOG_LEVEL, ignoring: %m");
/* Allow overriding the homework path via an environment variable, to make debugging
* easier. */
homework = getenv("SYSTEMD_HOMEWORK_PATH") ?: SYSTEMD_HOMEWORK_PATH;
execl(homework, homework, verb, NULL);
log_error_errno(errno, "Failed to invoke %s: %m", homework);
r = invoke_callout_binary(SYSTEMD_HOMEWORK_PATH, STRV_MAKE(SYSTEMD_HOMEWORK_PATH, verb));
log_error_errno(r, "Failed to invoke %s: %m", SYSTEMD_HOMEWORK_PATH);
_exit(EXIT_FAILURE);
}

View File

@ -6,6 +6,7 @@
#include "sd-bus.h"
#include "alloc-util.h"
#include "build-path.h"
#include "bus-common-errors.h"
#include "bus-get-properties.h"
#include "bus-log-control-api.h"
@ -475,8 +476,10 @@ static int transfer_start(Transfer *t) {
cmd[k++] = t->local;
cmd[k] = NULL;
execv(cmd[0], (char * const *) cmd);
log_error_errno(errno, "Failed to execute %s tool: %m", cmd[0]);
assert(k < ELEMENTSOF(cmd));
r = invoke_callout_binary(cmd[0], (char * const *) cmd);
log_error_errno(r, "Failed to execute %s tool: %m", cmd[0]);
_exit(EXIT_FAILURE);
}

View File

@ -6,6 +6,7 @@
#include "alloc-util.h"
#include "blockdev-util.h"
#include "build-path.h"
#include "chase.h"
#include "device-util.h"
#include "devnum-util.h"
@ -300,7 +301,7 @@ static int download_manifest(
/* Child */
const char *cmdline[] = {
"systemd-pull",
SYSTEMD_PULL_PATH,
"raw",
"--direct", /* just download the specified URL, don't download anything else */
"--verify", verify_signature ? "signature" : "no", /* verify the manifest file */
@ -309,8 +310,8 @@ static int download_manifest(
NULL
};
execv(pull_binary_path(), (char *const*) cmdline);
log_error_errno(errno, "Failed to execute %s tool: %m", pull_binary_path());
r = invoke_callout_binary(SYSTEMD_PULL_PATH, (char *const*) cmdline);
log_error_errno(r, "Failed to execute %s tool: %m", SYSTEMD_PULL_PATH);
_exit(EXIT_FAILURE);
};

View File

@ -4,6 +4,7 @@
#include "alloc-util.h"
#include "blockdev-util.h"
#include "build-path.h"
#include "chase.h"
#include "conf-parser.h"
#include "dirent-util.h"
@ -782,25 +783,23 @@ static void compile_pattern_fields(
memcpy(ret->sha256sum, i->metadata.sha256sum, sizeof(ret->sha256sum));
}
static int run_helper(
static int run_callout(
const char *name,
const char *path,
const char * const cmdline[]) {
char *cmdline[]) {
int r;
assert(name);
assert(path);
assert(cmdline);
assert(cmdline[0]);
r = safe_fork(name, FORK_RESET_SIGNALS|FORK_DEATHSIG_SIGTERM|FORK_LOG|FORK_WAIT, NULL);
if (r < 0)
return r;
if (r == 0) {
/* Child */
execv(path, (char *const*) cmdline);
log_error_errno(errno, "Failed to execute %s tool: %m", path);
r = invoke_callout_binary(cmdline[0], (char *const*) cmdline);
log_error_errno(r, "Failed to execute %s tool: %m", cmdline[0]);
_exit(EXIT_FAILURE);
}
@ -907,36 +906,30 @@ int transfer_acquire_instance(Transfer *t, Instance *i) {
* importer has some tricks up its sleeve, such as sparse file generation, which we
* want to take benefit of, too.) */
r = run_helper("(sd-import-raw)",
import_binary_path(),
(const char* const[]) {
"systemd-import",
r = run_callout("(sd-import-raw)",
STRV_MAKE(
SYSTEMD_IMPORT_PATH,
"raw",
"--direct", /* just copy/unpack the specified file, don't do anything else */
arg_sync ? "--sync=yes" : "--sync=no",
i->path,
t->temporary_path,
NULL
});
t->temporary_path));
break;
case RESOURCE_PARTITION:
/* regular file → partition */
r = run_helper("(sd-import-raw)",
import_binary_path(),
(const char* const[]) {
"systemd-import",
r = run_callout("(sd-import-raw)",
STRV_MAKE(
SYSTEMD_IMPORT_PATH,
"raw",
"--direct", /* just copy/unpack the specified file, don't do anything else */
"--offset", offset,
"--size-max", max_size,
arg_sync ? "--sync=yes" : "--sync=no",
i->path,
t->target.path,
NULL
});
t->target.path));
break;
default:
@ -951,18 +944,15 @@ int transfer_acquire_instance(Transfer *t, Instance *i) {
/* directory/subvolume → directory/subvolume */
r = run_helper("(sd-import-fs)",
import_fs_binary_path(),
(const char* const[]) {
"systemd-import-fs",
r = run_callout("(sd-import-fs)",
STRV_MAKE(
SYSTEMD_IMPORT_FS_PATH,
"run",
"--direct", /* just untar the specified file, don't do anything else */
arg_sync ? "--sync=yes" : "--sync=no",
t->target.type == RESOURCE_SUBVOLUME ? "--btrfs-subvol=yes" : "--btrfs-subvol=no",
i->path,
t->temporary_path,
NULL
});
t->temporary_path));
break;
case RESOURCE_TAR:
@ -970,18 +960,15 @@ int transfer_acquire_instance(Transfer *t, Instance *i) {
/* tar → directory/subvolume */
r = run_helper("(sd-import-tar)",
import_binary_path(),
(const char* const[]) {
"systemd-import",
r = run_callout("(sd-import-tar)",
STRV_MAKE(
SYSTEMD_IMPORT_PATH,
"tar",
"--direct", /* just untar the specified file, don't do anything else */
arg_sync ? "--sync=yes" : "--sync=no",
t->target.type == RESOURCE_SUBVOLUME ? "--btrfs-subvol=yes" : "--btrfs-subvol=no",
i->path,
t->temporary_path,
NULL
});
t->temporary_path));
break;
case RESOURCE_URL_FILE:
@ -992,28 +979,24 @@ int transfer_acquire_instance(Transfer *t, Instance *i) {
/* url file → regular file */
r = run_helper("(sd-pull-raw)",
pull_binary_path(),
(const char* const[]) {
"systemd-pull",
r = run_callout("(sd-pull-raw)",
STRV_MAKE(
SYSTEMD_PULL_PATH,
"raw",
"--direct", /* just download the specified URL, don't download anything else */
"--verify", digest, /* validate by explicit SHA256 sum */
arg_sync ? "--sync=yes" : "--sync=no",
i->path,
t->temporary_path,
NULL
});
t->temporary_path));
break;
case RESOURCE_PARTITION:
/* url file → partition */
r = run_helper("(sd-pull-raw)",
pull_binary_path(),
(const char* const[]) {
"systemd-pull",
r = run_callout("(sd-pull-raw)",
STRV_MAKE(
SYSTEMD_PULL_PATH,
"raw",
"--direct", /* just download the specified URL, don't download anything else */
"--verify", digest, /* validate by explicit SHA256 sum */
@ -1021,9 +1004,7 @@ int transfer_acquire_instance(Transfer *t, Instance *i) {
"--size-max", max_size,
arg_sync ? "--sync=yes" : "--sync=no",
i->path,
t->target.path,
NULL
});
t->target.path));
break;
default:
@ -1035,19 +1016,16 @@ int transfer_acquire_instance(Transfer *t, Instance *i) {
case RESOURCE_URL_TAR:
assert(IN_SET(t->target.type, RESOURCE_DIRECTORY, RESOURCE_SUBVOLUME));
r = run_helper("(sd-pull-tar)",
pull_binary_path(),
(const char*const[]) {
"systemd-pull",
r = run_callout("(sd-pull-tar)",
STRV_MAKE(
SYSTEMD_PULL_PATH,
"tar",
"--direct", /* just download the specified URL, don't download anything else */
"--verify", digest, /* validate by explicit SHA256 sum */
t->target.type == RESOURCE_SUBVOLUME ? "--btrfs-subvol=yes" : "--btrfs-subvol=no",
arg_sync ? "--sync=yes" : "--sync=no",
i->path,
t->temporary_path,
NULL
});
t->temporary_path));
break;
default:

View File

@ -7,15 +7,3 @@
extern bool arg_sync;
extern uint64_t arg_instances_max;
extern char *arg_root;
static inline const char* import_binary_path(void) {
return secure_getenv("SYSTEMD_IMPORT_PATH") ?: SYSTEMD_IMPORT_PATH;
}
static inline const char* import_fs_binary_path(void) {
return secure_getenv("SYSTEMD_IMPORT_FS_PATH") ?: SYSTEMD_IMPORT_FS_PATH;
}
static inline const char *pull_binary_path(void) {
return secure_getenv("SYSTEMD_PULL_PATH") ?: SYSTEMD_PULL_PATH;
}

View File

@ -51,6 +51,7 @@ simple_tests += files(
'test-bitmap.c',
'test-blockdev-util.c',
'test-bootspec.c',
'test-build-path.c',
'test-bus-util.c',
'test-calendarspec.c',
'test-cgroup-setup.c',

View File

@ -0,0 +1,20 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#include "build-path.h"
#include "log.h"
#include "string-util.h"
int main(int argc, char* argv[]) {
_cleanup_free_ char *p = NULL;
int r;
r = get_build_exec_dir(&p);
if (r == -ENOEXEC)
log_info("Not run from build dir.");
else if (r < 0)
log_error_errno(r, "Failed to find build dir: %m");
else
log_info("%s", strna(p));
return 0;
}

View File

@ -4,6 +4,7 @@
#include "sd-daemon.h"
#include "build-path.h"
#include "common-signal.h"
#include "env-util.h"
#include "fd-util.h"
@ -191,11 +192,8 @@ static int start_one_worker(Manager *m) {
_exit(EXIT_FAILURE);
}
/* execl("/home/lennart/projects/systemd/build/systemd-userwork", "systemd-userwork", "xxxxxxxxxxxxxxxx", NULL); /\* With some extra space rename_process() can make use of *\/ */
/* execl("/usr/bin/valgrind", "valgrind", "/home/lennart/projects/systemd/build/systemd-userwork", "systemd-userwork", "xxxxxxxxxxxxxxxx", NULL); /\* With some extra space rename_process() can make use of *\/ */
execl(SYSTEMD_USERWORK_PATH, "systemd-userwork", "xxxxxxxxxxxxxxxx", NULL); /* With some extra space rename_process() can make use of */
log_error_errno(errno, "Failed start worker process: %m");
r = invoke_callout_binary(SYSTEMD_USERWORK_PATH, STRV_MAKE(SYSTEMD_USERWORK_PATH, "xxxxxxxxxxxxxxxx")); /* With some extra space rename_process() can make use of */
log_error_errno(r, "Failed start worker process: %m");
_exit(EXIT_FAILURE);
}