We often have a pattern where the same verb function is used for multiple actions. This leads to an antipattern where we figure out what action needs to be taken from argv[0] multiple times: often once in parse_argv() to figure out what options are allowed, then once again implicitly in dispatch_verb(), and then again in the action verb itself. Let's allow passing a parameter into the verb to simplify this. This matches a pattern we have in conf-parser.h, where we have both void *userdata (more global) and void *data (per-config item). Here, I opted for uintptr_t userdata. It seems that most of the time we'll want to just pass an enum value. This works OK with no casts. I also tried a void* and union. In both cases, much more boilerplate is needed: either a cast or a macro to help avoid compiler warnings. uintptr_t seems generic enough to cover foreseeable usecases with no fuss. This is a noop refactoring. See next commit for an example.
124 lines
4.7 KiB
C
124 lines
4.7 KiB
C
/* SPDX-License-Identifier: LGPL-2.1-or-later */
|
|
|
|
#include <stdlib.h>
|
|
#include <unistd.h>
|
|
|
|
#include "sd-bus.h"
|
|
|
|
#include "alloc-util.h"
|
|
#include "analyze.h"
|
|
#include "analyze-unit-shell.h"
|
|
#include "bus-error.h"
|
|
#include "bus-util.h"
|
|
#include "fd-util.h"
|
|
#include "log.h"
|
|
#include "namespace-util.h"
|
|
#include "pidref.h"
|
|
#include "process-util.h"
|
|
#include "runtime-scope.h"
|
|
#include "strv.h"
|
|
#include "unit-def.h"
|
|
#include "unit-name.h"
|
|
|
|
int verb_unit_shell(int argc, char *argv[], uintptr_t _data, void *userdata) {
|
|
_cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL;
|
|
_cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL;
|
|
_cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
|
|
_cleanup_free_ char *unit = NULL;
|
|
int r;
|
|
|
|
if (arg_transport != BUS_TRANSPORT_LOCAL)
|
|
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Cannot spawn a unit shell for a remote service.");
|
|
|
|
r = unit_name_mangle_with_suffix(argv[1], "as unit", UNIT_NAME_MANGLE_WARN, ".service", &unit);
|
|
if (r < 0)
|
|
return log_error_errno(r, "Failed to mangle name '%s': %m", argv[1]);
|
|
|
|
r = acquire_bus(&bus, /* use_full_bus= */ NULL);
|
|
if (r < 0)
|
|
return bus_log_connect_error(r, arg_transport, arg_runtime_scope);
|
|
|
|
_cleanup_free_ char *object = unit_dbus_path_from_name(unit);
|
|
if (!object)
|
|
return log_oom();
|
|
|
|
r = sd_bus_get_property(
|
|
bus,
|
|
"org.freedesktop.systemd1",
|
|
object,
|
|
"org.freedesktop.systemd1.Service",
|
|
"MainPID",
|
|
&error,
|
|
&reply,
|
|
"u");
|
|
if (r < 0)
|
|
return log_error_errno(r, "Failed to get the main PID of %s: %s", unit, bus_error_message(&error, r));
|
|
|
|
pid_t pid;
|
|
r = sd_bus_message_read(reply, "u", &pid);
|
|
if (r < 0)
|
|
return log_error_errno(r, "Failed to read the main PID of %s from reply: %m", unit);
|
|
if (pid == 0)
|
|
return log_error_errno(SYNTHETIC_ERRNO(ESRCH), "Unit %s has no MainPID (hint: inactive?)", unit);
|
|
|
|
_cleanup_close_ int mntns_fd = -EBADF, root_fd = -EBADF, pidns_fd = -EBADF, netns_fd = -EBADF, userns_fd = -EBADF;
|
|
r = namespace_open(
|
|
pid,
|
|
&pidns_fd,
|
|
&mntns_fd,
|
|
&netns_fd,
|
|
&userns_fd,
|
|
&root_fd);
|
|
if (r < 0)
|
|
return log_error_errno(r, "Failed to retrieve FDs of namespaces of %s: %m", unit);
|
|
|
|
_cleanup_strv_free_ char **args = NULL;
|
|
if (argc > 2) {
|
|
args = strv_copy(strv_skip(argv, 2));
|
|
if (!args)
|
|
return log_oom();
|
|
}
|
|
|
|
_cleanup_(pidref_done) PidRef child = PIDREF_NULL;
|
|
r = namespace_fork(
|
|
"(unit-shell-ns)",
|
|
"(unit-shell)",
|
|
FORK_RESET_SIGNALS|FORK_DEATHSIG_SIGKILL,
|
|
pidns_fd,
|
|
mntns_fd,
|
|
netns_fd,
|
|
userns_fd,
|
|
root_fd,
|
|
&child);
|
|
if (r < 0)
|
|
return log_error_errno(r, "Failed to fork and enter the namespace of %s: %m", unit);
|
|
|
|
if (r == 0) {
|
|
if (args) {
|
|
(void) execvp(args[0], args);
|
|
log_error_errno(errno, "Failed to execute '%s': %m", *args);
|
|
} else {
|
|
(void) execl(DEFAULT_USER_SHELL, "-" DEFAULT_USER_SHELL_NAME, NULL);
|
|
log_debug_errno(errno, "Failed to execute '" DEFAULT_USER_SHELL "', ignoring: %m");
|
|
|
|
if (!streq(DEFAULT_USER_SHELL, "/bin/bash")) {
|
|
(void) execl("/bin/bash", "-bash", NULL);
|
|
log_debug_errno(errno, "Failed to execute '/bin/bash', ignoring: %m");
|
|
}
|
|
|
|
if (!streq(DEFAULT_USER_SHELL, "/bin/sh")) {
|
|
(void) execl("/bin/sh", "-sh", NULL);
|
|
log_debug_errno(errno, "Failed to execute '/bin/sh', ignoring: %m");
|
|
}
|
|
|
|
log_error_errno(errno, "Failed to execute '" DEFAULT_USER_SHELL "', '/bin/bash', and '/bin/sh': %m");
|
|
}
|
|
_exit(EXIT_FAILURE);
|
|
}
|
|
|
|
return pidref_wait_for_terminate_and_check(
|
|
"(unit-shell)",
|
|
&child,
|
|
WAIT_LOG_ABNORMAL|WAIT_LOG_NON_ZERO_EXIT_STATUS);
|
|
}
|