From decae96905dfe0619536214502333b62fb0583ba Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Sun, 2 Mar 2025 07:51:34 +0100 Subject: [PATCH 1/3] ptyfwd: rename handler to hangup_handler We'll add another type of handler callback in the next commit, hence rename the existing handler to be more precise what it is about: handling hangups (either inline via tty, or explicit via user request) --- src/ptyfwd/ptyfwd-tool.c | 2 +- src/run/run.c | 2 +- src/shared/ptyfwd.c | 18 +++++++++--------- src/shared/ptyfwd.h | 4 ++-- 4 files changed, 13 insertions(+), 13 deletions(-) diff --git a/src/ptyfwd/ptyfwd-tool.c b/src/ptyfwd/ptyfwd-tool.c index f62a4dc767..c59ea34079 100644 --- a/src/ptyfwd/ptyfwd-tool.c +++ b/src/ptyfwd/ptyfwd-tool.c @@ -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)", diff --git a/src/run/run.c b/src/run/run.c index 5dd1e9f9b6..a42dd8c33b 100644 --- a/src/run/run.c +++ b/src/run/run.c @@ -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); diff --git a/src/shared/ptyfwd.c b/src/shared/ptyfwd.c index c5814ad3c7..8c3ea6b5ae 100644 --- a/src/shared/ptyfwd.c +++ b/src/shared/ptyfwd.c @@ -101,8 +101,8 @@ struct PTYForward { usec_t escape_timestamp; unsigned escape_counter; - PTYForwardHandler handler; - void *userdata; + PTYForwardHangupHandler hangup_handler; + void *hangup_userdata; char *background_color; AnsiColorState ansi_color_state; @@ -192,10 +192,10 @@ 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) { @@ -1084,11 +1084,11 @@ 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; } bool pty_forward_drain(PTYForward *f) { diff --git a/src/shared/ptyfwd.h b/src/shared/ptyfwd.h index bd33551a6d..8f096e42cb 100644 --- a/src/shared/ptyfwd.h +++ b/src/shared/ptyfwd.h @@ -23,7 +23,7 @@ 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); #define N_PTY_FORWARD_SIGNALS 7 extern const int pty_forward_signals[N_PTY_FORWARD_SIGNALS]; @@ -34,7 +34,7 @@ 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); bool pty_forward_drain(PTYForward *f); From b1f9d0e46bbb0f84b0c150af907e257e341baf73 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Sun, 2 Mar 2025 21:40:50 +0100 Subject: [PATCH 2/3] ptyfwd: add support for additional out-of-band hotkeys in ptyfwd Let's add the ability that ptyfwd tools can register additional hotkeys that they then can handle. So far the only hotkey we support is ^]^]^] to exit the ptyfwd session abruptly. Staying close to this let's add ^]^] for additional commands. --- src/shared/ptyfwd.c | 61 +++++++++++++++++++++++++++++++++++++-------- src/shared/ptyfwd.h | 2 ++ 2 files changed, 52 insertions(+), 11 deletions(-) diff --git a/src/shared/ptyfwd.c b/src/shared/ptyfwd.c index 8c3ea6b5ae..d434f8e850 100644 --- a/src/shared/ptyfwd.c +++ b/src/shared/ptyfwd.c @@ -103,6 +103,8 @@ struct PTYForward { PTYForwardHangupHandler hangup_handler; void *hangup_userdata; + PTYForwardHotkeyHandler hotkey_handler; + void *hotkey_userdata; char *background_color; AnsiColorState ansi_color_state; @@ -198,17 +200,26 @@ static int pty_forward_done(PTYForward *f, int rcode) { 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; @@ -1091,6 +1123,13 @@ void pty_forward_set_hangup_handler(PTYForward *f, PTYForwardHangupHandler cb, v 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) { assert(f); diff --git a/src/shared/ptyfwd.h b/src/shared/ptyfwd.h index 8f096e42cb..b276cb15c5 100644 --- a/src/shared/ptyfwd.h +++ b/src/shared/ptyfwd.h @@ -24,6 +24,7 @@ typedef enum PTYForwardFlags { } PTYForwardFlags; 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]; @@ -35,6 +36,7 @@ int pty_forward_set_ignore_vhangup(PTYForward *f, bool ignore_vhangup); bool pty_forward_get_ignore_vhangup(PTYForward *f); 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); From f0f5d5420219bed6716d003b01399e6ca3da1b1d Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Sun, 2 Mar 2025 21:43:12 +0100 Subject: [PATCH 3/3] nspawn: add ability to poweroff container cleanly with ^]^]p It's sometimes very useful to be able to terminate a container quickly but cleanly while talking to it. Introduce a hotkey for that: ^]^]p for powering it off. In similar style add ^]^]r for rebooting it. --- man/systemd-nspawn.xml | 29 +++++++++++++++++++++++++++++ src/nspawn/nspawn.c | 37 ++++++++++++++++++++++++++++++++++++- tools/command_ignorelist | 1 + 3 files changed, 66 insertions(+), 1 deletion(-) diff --git a/man/systemd-nspawn.xml b/man/systemd-nspawn.xml index 4484e87831..213f434fe7 100644 --- a/man/systemd-nspawn.xml +++ b/man/systemd-nspawn.xml @@ -1847,6 +1847,35 @@ After=sys-subsystem-net-devices-ens1.device + + Hotkeys + + When invoked in interactive mode (i.e. the default ), 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. + + + + Ctrl-] Ctrl-] Ctrl-] + Immediately terminate the container, killing all processes. + + + + Ctrl-] Ctrl-] r + Issue a reboot request to the container. + + + + + Ctrl-] Ctrl-] p + Issue a shutdown request to the container. + + + + + + diff --git a/src/nspawn/nspawn.c b/src/nspawn/nspawn.c index 1737d1ef0a..952516bc78 100644 --- a/src/nspawn/nspawn.c +++ b/src/nspawn/nspawn.c @@ -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()); } diff --git a/tools/command_ignorelist b/tools/command_ignorelist index aca6b8c93b..65602aab8a 100644 --- a/tools/command_ignorelist +++ b/tools/command_ignorelist @@ -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-]"]