nspawn: add hotkey for rebooting/powering off container (#36583)

This commit is contained in:
Luca Boccassi
2025-03-05 01:55:11 +00:00
committed by GitHub
7 changed files with 131 additions and 25 deletions
+29
View File
@@ -1847,6 +1847,35 @@ After=sys-subsystem-net-devices-ens1.device</programlisting>
</refsect2>
</refsect1>
<refsect1>
<title>Hotkeys</title>
<para>When invoked in interactive mode (i.e. the default <option>--console=interactive</option>), a few
special keyboard shortcuts are understood that control the container runtime. These shortcuts need to be
typed within 1s to have effect, otherwise they will be forwarded to the container as regular
keypresses.</para>
<variablelist>
<varlistentry>
<term>Ctrl-] Ctrl-] Ctrl-]</term>
<listitem><para>Immediately terminate the container, killing all processes.</para></listitem>
</varlistentry>
<varlistentry>
<term>Ctrl-] Ctrl-] r</term>
<listitem><para>Issue a reboot request to the container.</para>
<xi:include href="version-info.xml" xpointer="v258"/></listitem>
</varlistentry>
<varlistentry>
<term>Ctrl-] Ctrl-] p</term>
<listitem><para>Issue a shutdown request to the container.</para>
<xi:include href="version-info.xml" xpointer="v258"/></listitem>
</varlistentry>
</variablelist>
</refsect1>
<xi:include href="common-variables.xml" />
<refsect1>
+36 -1
View File
@@ -4692,6 +4692,37 @@ static void set_window_title(PTYForward *f) {
(void) pty_forward_set_title_prefix(f, dot);
}
static int ptyfwd_hotkey(PTYForward *f, char c, void *userdata) {
pid_t pid = PTR_TO_PID(userdata);
const char *word;
int sig = 0;
assert(f);
switch (c) {
case 'p':
sig = SIGRTMIN+4;
word = "power off";
break;
case 'r':
sig = SIGRTMIN+5;
word = "reboot";
break;
default:
log_info("Unknown hotkey sequence ^]^]%c, ignoring.", c);
return 0;
}
if (kill(pid, sig) < 0)
log_error_errno(errno, "Failed to send %s (%s request) to PID 1 of container: %m", signal_to_string(sig), word);
else
log_info("Sent %s (%s request) to PID 1 of container.", signal_to_string(sig), word);
return 0;
}
static int merge_settings(Settings *settings, const char *path) {
int rl;
@@ -5687,6 +5718,8 @@ static int run_container(
(void) pty_forward_set_background_color(forward, arg_background);
set_window_title(forward);
pty_forward_set_hotkey_handler(forward, ptyfwd_hotkey, PID_TO_PTR(*pid));
break;
default:
@@ -6364,7 +6397,9 @@ static int run(int argc, char *argv[]) {
special_glyph(SPECIAL_GLYPH_LIGHT_SHADE), ansi_grey(), arg_machine, u ?: t, ansi_normal());
if (arg_console_mode == CONSOLE_INTERACTIVE)
log_info("%s %sPress %sCtrl-]%s three times within 1s to kill container.%s",
log_info("%s %sPress %sCtrl-]%s three times within 1s to kill container; two times followed by %sr%s\n"
"%s %sto reboot container; two times followed by %sp%s to poweroff container.%s",
special_glyph(SPECIAL_GLYPH_LIGHT_SHADE), ansi_grey(), ansi_highlight(), ansi_grey(), ansi_highlight(), ansi_normal(),
special_glyph(SPECIAL_GLYPH_LIGHT_SHADE), ansi_grey(), ansi_highlight(), ansi_grey(), ansi_normal());
}
+1 -1
View File
@@ -192,7 +192,7 @@ static int run(int argc, char *argv[]) {
return log_error_errno(r, "Failed to set title: %m");
}
pty_forward_set_handler(forward, pty_forward_handler, event);
pty_forward_set_hangup_handler(forward, pty_forward_handler, event);
r = pidref_safe_fork_full(
"(sd-ptyfwd)",
+1 -1
View File
@@ -2220,7 +2220,7 @@ static int start_transient_service(sd_bus *bus) {
if (r < 0)
return log_error_errno(r, "Failed to create PTY forwarder: %m");
pty_forward_set_handler(c.forward, pty_forward_handler, &c);
pty_forward_set_hangup_handler(c.forward, pty_forward_handler, &c);
/* Make sure to process any TTY events before we process bus events */
(void) pty_forward_set_priority(c.forward, SD_EVENT_PRIORITY_IMPORTANT);
+59 -20
View File
@@ -101,8 +101,10 @@ struct PTYForward {
usec_t escape_timestamp;
unsigned escape_counter;
PTYForwardHandler handler;
void *userdata;
PTYForwardHangupHandler hangup_handler;
void *hangup_userdata;
PTYForwardHotkeyHandler hotkey_handler;
void *hotkey_userdata;
char *background_color;
AnsiColorState ansi_color_state;
@@ -192,23 +194,32 @@ static int pty_forward_done(PTYForward *f, int rcode) {
f->done = true;
pty_forward_disconnect(f);
if (f->handler)
return f->handler(f, rcode, f->userdata);
else
return sd_event_exit(e, rcode < 0 ? EXIT_FAILURE : rcode);
if (f->hangup_handler)
return f->hangup_handler(f, rcode, f->hangup_userdata);
return sd_event_exit(e, rcode < 0 ? EXIT_FAILURE : rcode);
}
static bool look_for_escape(PTYForward *f, const char *buffer, size_t n) {
const char *p;
typedef enum RequestOperation {
REQUEST_NOP,
REQUEST_EXIT,
REQUEST_HOTKEY_BASE,
REQUEST_HOTKEY_A = REQUEST_HOTKEY_BASE + 'a',
REQUEST_HOTKEY_Z = REQUEST_HOTKEY_BASE + 'z',
_REQUEST_OPERATION_MAX,
_REQUEST_OPERATION_INVALID = -EINVAL,
} RequestOperation;
static RequestOperation look_for_escape(PTYForward *f, const char *buffer, size_t n) {
assert(f);
assert(buffer);
assert(n > 0);
for (p = buffer; p < buffer + n; p++) {
for (const char *p = buffer; p < buffer + n; p++) {
/* Check for ^] */
if (*p == 0x1D) {
switch (*p) {
case 0x1D: { /* Check for ^] */
usec_t nw = now(CLOCK_MONOTONIC);
if (f->escape_counter == 0 || nw > f->escape_timestamp + ESCAPE_USEC) {
@@ -218,15 +229,29 @@ static bool look_for_escape(PTYForward *f, const char *buffer, size_t n) {
(f->escape_counter)++;
if (f->escape_counter >= 3)
return true;
return REQUEST_EXIT;
}
} else {
break;
}
case 'a'...'z':
if (f->escape_counter == 2 &&
now(CLOCK_MONOTONIC) <= f->escape_timestamp + ESCAPE_USEC) {
f->escape_timestamp = 0;
f->escape_counter = 0;
return REQUEST_HOTKEY_BASE + *p;
}
_fallthrough_;
default:
f->escape_timestamp = 0;
f->escape_counter = 0;
}
}
return false;
return REQUEST_NOP;
}
static bool ignore_vhangup(PTYForward *f) {
@@ -654,10 +679,17 @@ static int do_shovel(PTYForward *f) {
} else {
/* Check if ^] has been pressed three times within one second. If we get this we quite
* immediately. */
if (look_for_escape(f, f->in_buffer + f->in_buffer_full, k))
return -ECANCELED;
RequestOperation q = look_for_escape(f, f->in_buffer + f->in_buffer_full, k);
f->in_buffer_full += (size_t) k;
if (q < 0)
return q;
if (q == REQUEST_EXIT)
return -ECANCELED;
if (q >= REQUEST_HOTKEY_A && q <= REQUEST_HOTKEY_Z && f->hotkey_handler) {
r = f->hotkey_handler(f, q - REQUEST_HOTKEY_BASE, f->hotkey_userdata);
if (r < 0)
return r;
}
}
did_something = true;
@@ -1084,11 +1116,18 @@ bool pty_forward_get_ignore_vhangup(PTYForward *f) {
return FLAGS_SET(f->flags, PTY_FORWARD_IGNORE_VHANGUP);
}
void pty_forward_set_handler(PTYForward *f, PTYForwardHandler cb, void *userdata) {
void pty_forward_set_hangup_handler(PTYForward *f, PTYForwardHangupHandler cb, void *userdata) {
assert(f);
f->handler = cb;
f->userdata = userdata;
f->hangup_handler = cb;
f->hangup_userdata = userdata;
}
void pty_forward_set_hotkey_handler(PTYForward *f, PTYForwardHotkeyHandler cb, void *userdata) {
assert(f);
f->hotkey_handler = cb;
f->hotkey_userdata = userdata;
}
bool pty_forward_drain(PTYForward *f) {
+4 -2
View File
@@ -23,7 +23,8 @@ typedef enum PTYForwardFlags {
PTY_FORWARD_DUMB_TERMINAL = 1 << 3,
} PTYForwardFlags;
typedef int (*PTYForwardHandler)(PTYForward *f, int rcode, void *userdata);
typedef int (*PTYForwardHangupHandler)(PTYForward *f, int rcode, void *userdata);
typedef int (*PTYForwardHotkeyHandler)(PTYForward *f, char key, void *userdata);
#define N_PTY_FORWARD_SIGNALS 7
extern const int pty_forward_signals[N_PTY_FORWARD_SIGNALS];
@@ -34,7 +35,8 @@ PTYForward* pty_forward_free(PTYForward *f);
int pty_forward_set_ignore_vhangup(PTYForward *f, bool ignore_vhangup);
bool pty_forward_get_ignore_vhangup(PTYForward *f);
void pty_forward_set_handler(PTYForward *f, PTYForwardHandler handler, void *userdata);
void pty_forward_set_hangup_handler(PTYForward *f, PTYForwardHangupHandler handler, void *userdata);
void pty_forward_set_hotkey_handler(PTYForward *f, PTYForwardHotkeyHandler handler, void *userdata);
bool pty_forward_drain(PTYForward *f);
+1
View File
@@ -576,3 +576,4 @@ file-hierarchy.xml /refsect1[title="Home Directory"]/variablelist/varlistentry[t
file-hierarchy.xml /refsect1[title="Home Directory"]/variablelist/varlistentry[term="~/.local/share/"]
file-hierarchy.xml /refsect1[title="Home Directory"]/variablelist/varlistentry[term="~/.local/state/"]
systemd-measure.xml /refsect1[title="Options"]/variablelist/varlistentry[term="--linux=PATH"]
systemd-nspawn.xml /refsect1[title="Hotkeys"]/variablelist/varlistentry[term="Ctrl-] Ctrl-] Ctrl-]"]