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/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..d434f8e850 100644
--- a/src/shared/ptyfwd.c
+++ b/src/shared/ptyfwd.c
@@ -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) {
diff --git a/src/shared/ptyfwd.h b/src/shared/ptyfwd.h
index bd33551a6d..b276cb15c5 100644
--- a/src/shared/ptyfwd.h
+++ b/src/shared/ptyfwd.h
@@ -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);
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-]"]