mirror of
https://github.com/systemd/systemd
synced 2025-10-05 16:03:15 +02:00
systemd-analyze: added the verb unit-shell to spawn and attach shell
This commit is contained in:
@@ -70,6 +70,13 @@
|
||||
<arg choice="opt" rep="repeat">OPTIONS</arg>
|
||||
<arg choice="plain">unit-paths</arg>
|
||||
</cmdsynopsis>
|
||||
<cmdsynopsis>
|
||||
<command>systemd-analyze</command>
|
||||
<arg choice="opt" rep="repeat">OPTIONS</arg>
|
||||
<arg choice="plain">unit-shell</arg>
|
||||
<arg choice="plain"><replaceable>SERVICE</replaceable></arg>
|
||||
<arg choice="opt" rep="repeat"><replaceable>Command</replaceable></arg>
|
||||
</cmdsynopsis>
|
||||
<cmdsynopsis>
|
||||
<command>systemd-analyze</command>
|
||||
<arg choice="opt" rep="repeat">OPTIONS</arg>
|
||||
@@ -1160,6 +1167,22 @@ LEGEND: M → sys_vendor (LENOVO) ┄ F → product_family (ThinkPad X1 Carbon G
|
||||
|
||||
<xi:include href="version-info.xml" xpointer="v258"/>
|
||||
</refsect2>
|
||||
|
||||
<refsect2>
|
||||
<title><command>systemd-analyze unit-shell <replaceable>SERVICE</replaceable> <optional><replaceable>command</replaceable>...</optional></command></title>
|
||||
|
||||
<para>The given command runs on the namespace of the specified running service. If no command is given,
|
||||
spawn and attach a shell with the namespace to the service.</para>
|
||||
|
||||
<example>
|
||||
<title>Example output</title>
|
||||
<programlisting>$ systemd-analyze unit-shell systemd-resolved.service ls
|
||||
bin dev etc home lib lib64 lost+found mnt proc run srv tmp var vmlinuz.old
|
||||
boot efi exitrd init lib32 libx32 media opt root sbin sys usr vmlinuz work</programlisting>
|
||||
</example>
|
||||
|
||||
<xi:include href="version-info.xml" xpointer="v258"/>
|
||||
</refsect2>
|
||||
</refsect1>
|
||||
|
||||
<refsect1>
|
||||
|
@@ -82,6 +82,7 @@ _systemd_analyze() {
|
||||
[FDSTORE]='fdstore'
|
||||
[CAPABILITY]='capability'
|
||||
[TRANSIENT_SETTINGS]='transient-settings'
|
||||
[UNIT_SHELL]='unit-shell'
|
||||
)
|
||||
|
||||
local CONFIGS='locale.conf systemd/bootchart.conf systemd/coredump.conf systemd/journald.conf
|
||||
@@ -233,6 +234,13 @@ _systemd_analyze() {
|
||||
else
|
||||
comps="$(systemctl --no-legend --no-pager -t help)"
|
||||
fi
|
||||
|
||||
elif __contains_word "$verb" ${VERBS[UNIT_SHELL]}; then
|
||||
if [[ $cur = -* ]]; then
|
||||
comps='--help --version'
|
||||
else
|
||||
comps=$( __get_services $mode )
|
||||
fi
|
||||
fi
|
||||
|
||||
COMPREPLY=( $(compgen -W '$comps' -- "$cur") )
|
||||
|
123
src/analyze/analyze-unit-shell.c
Normal file
123
src/analyze/analyze-unit-shell.c
Normal file
@@ -0,0 +1,123 @@
|
||||
/* SPDX-License-Identifier: LGPL-2.1-or-later */
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.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 "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[], 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);
|
||||
|
||||
_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();
|
||||
}
|
||||
|
||||
pid_t child;
|
||||
r = namespace_fork(
|
||||
"(unit-shell-ns)",
|
||||
"(unit-shell)",
|
||||
/* except_fds= */ NULL,
|
||||
/* n_except_fds */ 0,
|
||||
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) {
|
||||
r = execvp(args[0], args);
|
||||
if (r < 0)
|
||||
log_error_errno(errno, "Failed to execute '%s': %m", *args);
|
||||
} else {
|
||||
r = execl(DEFAULT_USER_SHELL, "-" DEFAULT_USER_SHELL_NAME, NULL);
|
||||
if (r < 0)
|
||||
log_debug_errno(errno, "Failed to execute '" DEFAULT_USER_SHELL "', ignoring: %m");
|
||||
if (!streq(DEFAULT_USER_SHELL, "/bin/bash")) {
|
||||
r = execl("/bin/bash", "-bash", NULL);
|
||||
if (r < 0)
|
||||
log_debug_errno(errno, "Failed to execute '/bin/bash', ignoring: %m");
|
||||
}
|
||||
if (!streq(DEFAULT_USER_SHELL, "/bin/sh")) {
|
||||
r = execl("/bin/sh", "-sh", NULL);
|
||||
if (r < 0)
|
||||
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 wait_for_terminate_and_check(
|
||||
"(unit-shell)",
|
||||
child,
|
||||
WAIT_LOG_ABNORMAL|WAIT_LOG_NON_ZERO_EXIT_STATUS);
|
||||
}
|
4
src/analyze/analyze-unit-shell.h
Normal file
4
src/analyze/analyze-unit-shell.h
Normal file
@@ -0,0 +1,4 @@
|
||||
/* SPDX-License-Identifier: LGPL-2.1-or-later */
|
||||
#pragma once
|
||||
|
||||
int verb_unit_shell(int argc, char *argv[], void *userdata);
|
@@ -44,6 +44,7 @@
|
||||
#include "analyze-timestamp.h"
|
||||
#include "analyze-unit-files.h"
|
||||
#include "analyze-unit-paths.h"
|
||||
#include "analyze-unit-shell.h"
|
||||
#include "analyze-verify.h"
|
||||
#include "analyze-verify-util.h"
|
||||
#include "build.h"
|
||||
@@ -241,6 +242,8 @@ static int help(int argc, char *argv[], void *userdata) {
|
||||
" security [UNIT...] Analyze security of unit\n"
|
||||
" fdstore SERVICE... Show file descriptor store contents of service\n"
|
||||
" malloc [D-BUS SERVICE...] Dump malloc stats of a D-Bus service\n"
|
||||
" unit-shell SERVICE [Command]\n"
|
||||
" Run command on the namespace of the service\n"
|
||||
"\n%3$sExecutable Analysis:%4$s\n"
|
||||
" inspect-elf FILE... Parse and print ELF package metadata\n"
|
||||
"\n%3$sTPM Operations:%4$s\n"
|
||||
@@ -383,14 +386,68 @@ static int parse_argv(int argc, char *argv[]) {
|
||||
{}
|
||||
};
|
||||
|
||||
int r, c;
|
||||
bool reorder = false;
|
||||
int r, c, unit_shell = -1;
|
||||
|
||||
assert(argc >= 0);
|
||||
assert(argv);
|
||||
|
||||
while ((c = getopt_long(argc, argv, "hqH:M:U:m", options, NULL)) >= 0)
|
||||
/* Resetting to 0 forces the invocation of an internal initialization routine of getopt_long()
|
||||
* that checks for GNU extensions in optstring ('-' or '+; at the beginning). */
|
||||
optind = 0;
|
||||
|
||||
for (;;) {
|
||||
static const char option_string[] = "-hqH:M:U:m";
|
||||
|
||||
c = getopt_long(argc, argv, option_string + reorder, options, NULL);
|
||||
if (c < 0)
|
||||
break;
|
||||
|
||||
switch (c) {
|
||||
|
||||
case 1: /* getopt_long() returns 1 if "-" was the first character of the option string, and a
|
||||
* non-option argument was discovered. */
|
||||
|
||||
assert(!reorder);
|
||||
|
||||
/* We generally are fine with the fact that getopt_long() reorders the command line, and looks
|
||||
* for switches after the main verb. However, for "unit-shell" we really don't want that, since we
|
||||
* want that switches specified after the service name are passed to the program to execute,
|
||||
* and not processed by us. To make this possible, we'll first invoke getopt_long() with
|
||||
* reordering disabled (i.e. with the "-" prefix in the option string), looking for the first
|
||||
* non-option parameter. If it's the verb "unit-shell" we remember its position and continue
|
||||
* processing options. In this case, as soon as we hit the next non-option argument we found
|
||||
* the service name, and stop further processing. If the first non-option argument is any other
|
||||
* verb than "unit-shell" we switch to normal reordering mode and continue processing arguments
|
||||
* normally. */
|
||||
|
||||
if (unit_shell >= 0) {
|
||||
optind--; /* don't processs this argument, go one step back */
|
||||
goto done;
|
||||
}
|
||||
if (streq(optarg, "unit-shell"))
|
||||
/* Remember the position of the "unit_shell" verb, and continue processing normally. */
|
||||
unit_shell = optind - 1;
|
||||
else {
|
||||
int saved_optind;
|
||||
|
||||
/* Ok, this is some other verb. In this case, turn on reordering again, and continue
|
||||
* processing normally. */
|
||||
reorder = true;
|
||||
|
||||
/* We changed the option string. getopt_long() only looks at it again if we invoke it
|
||||
* at least once with a reset option index. Hence, let's reset the option index here,
|
||||
* then invoke getopt_long() again (ignoring what it has to say, after all we most
|
||||
* likely already processed it), and the bump the option index so that we read the
|
||||
* intended argument again. */
|
||||
saved_optind = optind;
|
||||
optind = 0;
|
||||
(void) getopt_long(argc, argv, option_string + reorder, options, NULL);
|
||||
optind = saved_optind - 1; /* go one step back, process this argument again */
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case 'h':
|
||||
return help(0, NULL, NULL);
|
||||
|
||||
@@ -602,6 +659,22 @@ static int parse_argv(int argc, char *argv[]) {
|
||||
default:
|
||||
assert_not_reached();
|
||||
}
|
||||
}
|
||||
|
||||
done:
|
||||
if (unit_shell >= 0) {
|
||||
char *t;
|
||||
|
||||
/* We found the "unit-shell" verb while processing the argument list. Since we turned off reordering of the
|
||||
* argument list initially let's readjust it now, and move the "unit-shell" verb to the back. */
|
||||
|
||||
optind -= 1; /* place the option index where the "unit-shell" verb will be placed */
|
||||
|
||||
t = argv[unit_shell];
|
||||
for (int i = unit_shell; i < optind; i++)
|
||||
argv[i] = argv[i+1];
|
||||
argv[optind] = t;
|
||||
}
|
||||
|
||||
if (arg_offline && !streq_ptr(argv[optind], "security"))
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
|
||||
@@ -684,6 +757,7 @@ static int run(int argc, char *argv[]) {
|
||||
{ "cat-config", 2, VERB_ANY, 0, verb_cat_config },
|
||||
{ "unit-files", VERB_ANY, VERB_ANY, 0, verb_unit_files },
|
||||
{ "unit-paths", 1, 1, 0, verb_unit_paths },
|
||||
{ "unit-shell", 2, VERB_ANY, 0, verb_unit_shell },
|
||||
{ "exit-status", VERB_ANY, VERB_ANY, 0, verb_exit_status },
|
||||
{ "syscall-filter", VERB_ANY, VERB_ANY, 0, verb_syscall_filters },
|
||||
{ "capability", VERB_ANY, VERB_ANY, 0, verb_capabilities },
|
||||
|
@@ -33,6 +33,7 @@ systemd_analyze_sources = files(
|
||||
'analyze-timestamp.c',
|
||||
'analyze-unit-files.c',
|
||||
'analyze-unit-paths.c',
|
||||
'analyze-unit-shell.c',
|
||||
'analyze-verify.c',
|
||||
'analyze.c',
|
||||
)
|
||||
|
@@ -1116,6 +1116,29 @@ systemd-analyze transient-settings mount | grep CPUQuotaPeriodSec
|
||||
(! systemd-analyze transient-settings service | grep ConditionKernelVersion )
|
||||
(! systemd-analyze transient-settings service | grep AssertKernelVersion )
|
||||
|
||||
# check systemd-analyze unit-shell with a namespaced unit
|
||||
UNIT_NAME="test-unit-shell.service"
|
||||
UNIT_FILE="/run/systemd/system/$UNIT_NAME"
|
||||
cat >"$UNIT_FILE" <<EOF
|
||||
[Unit]
|
||||
Description=Test unit for systemd-analyze unit-shell
|
||||
[Service]
|
||||
Type=notify
|
||||
NotifyAccess=all
|
||||
ExecStart=/bin/sh -c "echo 'Hello from test unit' >/tmp/testfile; systemd-notify --ready; sleep infinity"
|
||||
PrivateTmp=disconnected
|
||||
EOF
|
||||
# Start the service
|
||||
systemctl start "$UNIT_NAME"
|
||||
# Wait for the service to be active
|
||||
systemctl is-active --quiet "$UNIT_NAME"
|
||||
# Verify the service is active and has a MainPID
|
||||
MAIN_PID=$(systemctl show -p MainPID --value "$UNIT_NAME")
|
||||
[ "$MAIN_PID" -gt 0 ]
|
||||
# Test systemd-analyze unit-shell with a command (cat /tmp/testfile)
|
||||
OUTPUT=$(systemd-analyze unit-shell "$UNIT_NAME" cat /tmp/testfile)
|
||||
assert_in "Hello from test unit" "$OUTPUT"
|
||||
|
||||
systemd-analyze log-level info
|
||||
|
||||
touch /testok
|
||||
|
Reference in New Issue
Block a user