mirror of
https://github.com/Dasharo/systemd.git
synced 2026-03-06 15:02:31 -08:00
Merge pull request #31458 from poettering/vmspawn-ptyfwd
vmspawn: implement TTY logic via ptyfwd
This commit is contained in:
@@ -205,14 +205,6 @@
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry>
|
||||
<term><option>--qemu-gui</option></term>
|
||||
|
||||
<listitem><para>Start QEMU in graphical mode.</para>
|
||||
|
||||
<xi:include href="version-info.xml" xpointer="v255"/></listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry>
|
||||
<term><option>-n</option></term>
|
||||
<term><option>--network-tap</option></term>
|
||||
@@ -361,6 +353,42 @@
|
||||
</variablelist>
|
||||
</refsect2>
|
||||
|
||||
<refsect2>
|
||||
<title>Input/Output Options</title>
|
||||
|
||||
<variablelist>
|
||||
<varlistentry>
|
||||
<term><option>--console=</option><replaceable>MODE</replaceable></term>
|
||||
|
||||
<listitem><para>Configures how to set up the console of the VM. Takes one of
|
||||
<literal>interactive</literal>, <literal>read-only</literal>, <literal>native</literal>,
|
||||
<literal>gui</literal>. Defaults to <literal>interactive</literal>. <literal>interactive</literal>
|
||||
provides an interactive terminal interface to the VM. <literal>read-only</literal> is similar, but
|
||||
is strictly read-only, i.e. does not accept any input from the user. <literal>native</literal> also
|
||||
provides a TTY-based interface, but uses qemu native implementation (which means the qemu monitor
|
||||
is available). <literal>gui</literal> shows the qemu graphical UI.</para>
|
||||
|
||||
<xi:include href="version-info.xml" xpointer="v256"/></listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry>
|
||||
<term><option>--background=<replaceable>COLOR</replaceable></option></term>
|
||||
|
||||
<listitem><para>Change the terminal background color to the specified ANSI color as long as the VM
|
||||
runs. The color specified should be an ANSI X3.64 SGR background color, i.e. strings such as
|
||||
<literal>40</literal>, <literal>41</literal>, …, <literal>47</literal>, <literal>48;2;…</literal>,
|
||||
<literal>48;5;…</literal>. See <ulink
|
||||
url="https://en.wikipedia.org/wiki/ANSI_escape_code#SGR_(Select_Graphic_Rendition)_parameters">ANSI
|
||||
Escape Code (Wikipedia)</ulink> for details. Assign an empty string to disable any coloring. This
|
||||
only has an effect in <option>--console=interactive</option> and
|
||||
<option>--console=read-only</option> modes.</para>
|
||||
|
||||
<xi:include href="version-info.xml" xpointer="v256"/>
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
</variablelist>
|
||||
</refsect2>
|
||||
|
||||
<refsect2>
|
||||
<title>Credentials</title>
|
||||
|
||||
|
||||
@@ -77,6 +77,7 @@ const char *special_glyph_full(SpecialGlyph code, bool force_utf) {
|
||||
[SPECIAL_GLYPH_RED_CIRCLE] = "o",
|
||||
[SPECIAL_GLYPH_YELLOW_CIRCLE] = "o",
|
||||
[SPECIAL_GLYPH_BLUE_CIRCLE] = "o",
|
||||
[SPECIAL_GLYPH_GREEN_CIRCLE] = "o",
|
||||
},
|
||||
|
||||
/* UTF-8 */
|
||||
@@ -143,6 +144,7 @@ const char *special_glyph_full(SpecialGlyph code, bool force_utf) {
|
||||
[SPECIAL_GLYPH_RED_CIRCLE] = u8"🔴",
|
||||
[SPECIAL_GLYPH_YELLOW_CIRCLE] = u8"🟡",
|
||||
[SPECIAL_GLYPH_BLUE_CIRCLE] = u8"🔵",
|
||||
[SPECIAL_GLYPH_GREEN_CIRCLE] = u8"🟢",
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
@@ -52,6 +52,7 @@ typedef enum SpecialGlyph {
|
||||
SPECIAL_GLYPH_RED_CIRCLE,
|
||||
SPECIAL_GLYPH_YELLOW_CIRCLE,
|
||||
SPECIAL_GLYPH_BLUE_CIRCLE,
|
||||
SPECIAL_GLYPH_GREEN_CIRCLE,
|
||||
_SPECIAL_GLYPH_MAX,
|
||||
_SPECIAL_GLYPH_INVALID = -EINVAL,
|
||||
} SpecialGlyph;
|
||||
|
||||
@@ -5991,10 +5991,12 @@ static int run(int argc, char *argv[]) {
|
||||
_cleanup_free_ char *u = NULL;
|
||||
(void) terminal_urlify_path(t, t, &u);
|
||||
|
||||
log_info("%s %sSpawning container %s on %s.%s\n"
|
||||
"%s %sPress %sCtrl-]%s three times within 1s to kill container.%s",
|
||||
special_glyph(SPECIAL_GLYPH_LIGHT_SHADE), ansi_grey(), arg_machine, u ?: t, ansi_normal(),
|
||||
special_glyph(SPECIAL_GLYPH_LIGHT_SHADE), ansi_grey(), ansi_highlight(), ansi_grey(), ansi_normal());
|
||||
log_info("%s %sSpawning container %s on %s.%s",
|
||||
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",
|
||||
special_glyph(SPECIAL_GLYPH_LIGHT_SHADE), ansi_grey(), ansi_highlight(), ansi_grey(), ansi_normal());
|
||||
}
|
||||
|
||||
assert_se(sigprocmask_many(SIG_BLOCK, NULL, SIGCHLD, SIGWINCH, SIGTERM, SIGINT, SIGRTMIN+18) >= 0);
|
||||
|
||||
@@ -1751,9 +1751,9 @@ static int start_transient_service(sd_bus *bus) {
|
||||
return log_error_errno(r, "Failed to get event loop: %m");
|
||||
|
||||
if (master >= 0) {
|
||||
assert_se(sigprocmask_many(SIG_BLOCK, NULL, SIGWINCH, SIGTERM, SIGINT) >= 0);
|
||||
(void) sd_event_add_signal(c.event, NULL, SIGINT, NULL, NULL);
|
||||
(void) sd_event_add_signal(c.event, NULL, SIGTERM, NULL, NULL);
|
||||
assert_se(sigprocmask_many(SIG_BLOCK, /* old_sigset=*/ NULL, SIGWINCH) >= 0);
|
||||
|
||||
(void) sd_event_set_signal_exit(c.event, true);
|
||||
|
||||
if (!arg_quiet)
|
||||
log_info("Press ^] three times within 1s to disconnect TTY.");
|
||||
|
||||
@@ -452,7 +452,7 @@ int terminal_tint_color(double hue, char **ret) {
|
||||
else /* otherwise pump it up */
|
||||
s = 75;
|
||||
|
||||
v = MAX(30, v); /* Make sure we don't hide the color in black */
|
||||
v = MAX(20, v); /* Make sure we don't hide the color in black */
|
||||
|
||||
uint8_t r8, g8, b8;
|
||||
hsv_to_rgb(hue, s, v, &r8, &g8, &b8);
|
||||
|
||||
@@ -82,7 +82,7 @@ TEST(keymaps) {
|
||||
|
||||
#define dump_glyph(x) log_info(STRINGIFY(x) ": %s", special_glyph(x))
|
||||
TEST(dump_special_glyphs) {
|
||||
assert_cc(SPECIAL_GLYPH_BLUE_CIRCLE + 1 == _SPECIAL_GLYPH_MAX);
|
||||
assert_cc(SPECIAL_GLYPH_GREEN_CIRCLE + 1 == _SPECIAL_GLYPH_MAX);
|
||||
|
||||
log_info("is_locale_utf8: %s", yes_no(is_locale_utf8()));
|
||||
|
||||
@@ -130,6 +130,7 @@ TEST(dump_special_glyphs) {
|
||||
dump_glyph(SPECIAL_GLYPH_RED_CIRCLE);
|
||||
dump_glyph(SPECIAL_GLYPH_YELLOW_CIRCLE);
|
||||
dump_glyph(SPECIAL_GLYPH_BLUE_CIRCLE);
|
||||
dump_glyph(SPECIAL_GLYPH_GREEN_CIRCLE);
|
||||
}
|
||||
|
||||
DEFINE_TEST_MAIN(LOG_INFO);
|
||||
|
||||
@@ -1,3 +1,13 @@
|
||||
/* SPDX-License-Identifier: LGPL-2.1-or-later */
|
||||
|
||||
#include "string-table.h"
|
||||
#include "vmspawn-settings.h"
|
||||
|
||||
static const char *const console_mode_table[_CONSOLE_MODE_MAX] = {
|
||||
[CONSOLE_INTERACTIVE] = "interactive",
|
||||
[CONSOLE_READ_ONLY] = "read-only",
|
||||
[CONSOLE_NATIVE] = "native",
|
||||
[CONSOLE_GUI] = "gui",
|
||||
};
|
||||
|
||||
DEFINE_STRING_TABLE_LOOKUP(console_mode, ConsoleMode);
|
||||
|
||||
@@ -1,8 +1,20 @@
|
||||
/* SPDX-License-Identifier: LGPL-2.1-or-later */
|
||||
#pragma once
|
||||
|
||||
#include <errno.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include "macro.h"
|
||||
|
||||
typedef enum ConsoleMode {
|
||||
CONSOLE_INTERACTIVE, /* ptyfwd */
|
||||
CONSOLE_READ_ONLY, /* ptyfwd, but in read-only mode */
|
||||
CONSOLE_NATIVE, /* qemu's native TTY handling */
|
||||
CONSOLE_GUI, /* qemu's graphical UI */
|
||||
_CONSOLE_MODE_MAX,
|
||||
_CONSOLE_MODE_INVALID = -EINVAL,
|
||||
} ConsoleMode;
|
||||
|
||||
typedef enum SettingsMask {
|
||||
SETTING_START_MODE = UINT64_C(1) << 0,
|
||||
SETTING_BIND_MOUNTS = UINT64_C(1) << 11,
|
||||
@@ -10,3 +22,6 @@ typedef enum SettingsMask {
|
||||
SETTING_CREDENTIALS = UINT64_C(1) << 30,
|
||||
_SETTING_FORCE_ENUM_WIDTH = UINT64_MAX
|
||||
} SettingsMask;
|
||||
|
||||
const char *console_mode_to_string(ConsoleMode m) _const_;
|
||||
ConsoleMode console_mode_from_string(const char *s) _pure_;
|
||||
|
||||
@@ -46,6 +46,7 @@
|
||||
#include "path-util.h"
|
||||
#include "pretty-print.h"
|
||||
#include "process-util.h"
|
||||
#include "ptyfwd.h"
|
||||
#include "random-util.h"
|
||||
#include "rm-rf.h"
|
||||
#include "signal-util.h"
|
||||
@@ -73,7 +74,7 @@ static unsigned arg_vsock_cid = VMADDR_CID_ANY;
|
||||
static int arg_tpm = -1;
|
||||
static char *arg_linux = NULL;
|
||||
static char **arg_initrds = NULL;
|
||||
static bool arg_qemu_gui = false;
|
||||
static ConsoleMode arg_console_mode = CONSOLE_INTERACTIVE;
|
||||
static NetworkStack arg_network_stack = NETWORK_STACK_NONE;
|
||||
static int arg_secure_boot = -1;
|
||||
static MachineCredentialContext arg_credentials = {};
|
||||
@@ -87,6 +88,7 @@ static bool arg_runtime_directory_created = false;
|
||||
static bool arg_privileged = false;
|
||||
static char **arg_kernel_cmdline_extra = NULL;
|
||||
static char **arg_extra_drives = NULL;
|
||||
static char *arg_background = NULL;
|
||||
|
||||
STATIC_DESTRUCTOR_REGISTER(arg_directory, freep);
|
||||
STATIC_DESTRUCTOR_REGISTER(arg_image, freep);
|
||||
@@ -101,6 +103,7 @@ STATIC_DESTRUCTOR_REGISTER(arg_runtime_mounts, runtime_mount_context_done);
|
||||
STATIC_DESTRUCTOR_REGISTER(arg_forward_journal, freep);
|
||||
STATIC_DESTRUCTOR_REGISTER(arg_kernel_cmdline_extra, strv_freep);
|
||||
STATIC_DESTRUCTOR_REGISTER(arg_extra_drives, strv_freep);
|
||||
STATIC_DESTRUCTOR_REGISTER(arg_background, freep);
|
||||
|
||||
static int help(void) {
|
||||
_cleanup_free_ char *link = NULL;
|
||||
@@ -130,7 +133,6 @@ static int help(void) {
|
||||
" --tpm=BOOL Enable use of a virtual TPM\n"
|
||||
" --linux=PATH Specify the linux kernel for direct kernel boot\n"
|
||||
" --initrd=PATH Specify the initrd for direct kernel boot\n"
|
||||
" --qemu-gui Start QEMU in graphical mode\n"
|
||||
" -n --network-tap Create a TAP device for networking\n"
|
||||
" --network-user-mode Use user mode networking\n"
|
||||
" --secure-boot=BOOL Enable searching for firmware supporting SecureBoot\n"
|
||||
@@ -150,6 +152,9 @@ static int help(void) {
|
||||
"\n%3$sIntegration:%4$s\n"
|
||||
" --forward-journal=FILE|DIR\n"
|
||||
" Forward the VM's journal to the host\n"
|
||||
"\n%3$sInput/Output:%4$s\n"
|
||||
" --console=MODE Console mode (interactive, native, gui)\n"
|
||||
" --background=COLOR Set ANSI color for background\n"
|
||||
"\n%3$sCredentials:%4$s\n"
|
||||
" --set-credential=ID:VALUE\n"
|
||||
" Pass a credential with literal value to the VM\n"
|
||||
@@ -190,6 +195,8 @@ static int parse_argv(int argc, char *argv[]) {
|
||||
ARG_SET_CREDENTIAL,
|
||||
ARG_LOAD_CREDENTIAL,
|
||||
ARG_FIRMWARE,
|
||||
ARG_CONSOLE,
|
||||
ARG_BACKGROUND,
|
||||
};
|
||||
|
||||
static const struct option options[] = {
|
||||
@@ -212,7 +219,8 @@ static int parse_argv(int argc, char *argv[]) {
|
||||
{ "tpm", required_argument, NULL, ARG_TPM },
|
||||
{ "linux", required_argument, NULL, ARG_LINUX },
|
||||
{ "initrd", required_argument, NULL, ARG_INITRD },
|
||||
{ "qemu-gui", no_argument, NULL, ARG_QEMU_GUI },
|
||||
{ "console", required_argument, NULL, ARG_CONSOLE },
|
||||
{ "qemu-gui", no_argument, NULL, ARG_QEMU_GUI }, /* compat option */
|
||||
{ "network-tap", no_argument, NULL, 'n' },
|
||||
{ "network-user-mode", no_argument, NULL, ARG_NETWORK_USER_MODE },
|
||||
{ "bind", required_argument, NULL, ARG_BIND },
|
||||
@@ -224,6 +232,7 @@ static int parse_argv(int argc, char *argv[]) {
|
||||
{ "set-credential", required_argument, NULL, ARG_SET_CREDENTIAL },
|
||||
{ "load-credential", required_argument, NULL, ARG_LOAD_CREDENTIAL },
|
||||
{ "firmware", required_argument, NULL, ARG_FIRMWARE },
|
||||
{ "background", required_argument, NULL, ARG_BACKGROUND },
|
||||
{}
|
||||
};
|
||||
|
||||
@@ -344,8 +353,15 @@ static int parse_argv(int argc, char *argv[]) {
|
||||
break;
|
||||
}
|
||||
|
||||
case ARG_CONSOLE:
|
||||
arg_console_mode = console_mode_from_string(optarg);
|
||||
if (arg_console_mode < 0)
|
||||
return log_error_errno(arg_console_mode, "Failed to parse specified console mode: %s", optarg);
|
||||
|
||||
break;
|
||||
|
||||
case ARG_QEMU_GUI:
|
||||
arg_qemu_gui = true;
|
||||
arg_console_mode = CONSOLE_GUI;
|
||||
break;
|
||||
|
||||
case 'n':
|
||||
@@ -438,6 +454,12 @@ static int parse_argv(int argc, char *argv[]) {
|
||||
|
||||
break;
|
||||
|
||||
case ARG_BACKGROUND:
|
||||
r = free_and_strdup_warn(&arg_background, optarg);
|
||||
if (r < 0)
|
||||
return r;
|
||||
break;
|
||||
|
||||
case '?':
|
||||
return -EINVAL;
|
||||
|
||||
@@ -1030,6 +1052,25 @@ static int merge_initrds(char **ret) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void set_window_title(PTYForward *f) {
|
||||
_cleanup_free_ char *hn = NULL, *dot = NULL;
|
||||
|
||||
assert(f);
|
||||
|
||||
(void) gethostname_strict(&hn);
|
||||
|
||||
if (emoji_enabled())
|
||||
dot = strjoin(special_glyph(SPECIAL_GLYPH_GREEN_CIRCLE), " ");
|
||||
|
||||
if (hn)
|
||||
(void) pty_forward_set_titlef(f, "%sVirtual Machine %s on %s", strempty(dot), arg_machine, hn);
|
||||
else
|
||||
(void) pty_forward_set_titlef(f, "%sVirtual Machine %s", strempty(dot), arg_machine);
|
||||
|
||||
if (dot)
|
||||
(void) pty_forward_set_title_prefix(f, dot);
|
||||
}
|
||||
|
||||
static int run_virtual_machine(int kvm_device_fd, int vhost_device_fd) {
|
||||
_cleanup_(ovmf_config_freep) OvmfConfig *ovmf_config = NULL;
|
||||
_cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL;
|
||||
@@ -1222,12 +1263,54 @@ static int run_virtual_machine(int kvm_device_fd, int vhost_device_fd) {
|
||||
if (r < 0)
|
||||
return log_oom();
|
||||
|
||||
if (arg_qemu_gui)
|
||||
_cleanup_close_ int master = -EBADF;
|
||||
PTYForwardFlags ptyfwd_flags = 0;
|
||||
switch (arg_console_mode) {
|
||||
|
||||
case CONSOLE_READ_ONLY:
|
||||
ptyfwd_flags |= PTY_FORWARD_READ_ONLY;
|
||||
|
||||
_fallthrough_;
|
||||
|
||||
case CONSOLE_INTERACTIVE: {
|
||||
_cleanup_free_ char *pty_path = NULL;
|
||||
|
||||
master = posix_openpt(O_RDWR|O_NOCTTY|O_CLOEXEC|O_NONBLOCK);
|
||||
if (master < 0)
|
||||
return log_error_errno(errno, "Failed to acquire pseudo tty: %m");
|
||||
|
||||
r = ptsname_malloc(master, &pty_path);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to determine tty name: %m");
|
||||
|
||||
if (unlockpt(master) < 0)
|
||||
return log_error_errno(errno, "Failed to unlock tty: %m");
|
||||
|
||||
if (strv_extend_many(
|
||||
&cmdline,
|
||||
"-nographic",
|
||||
"-nodefaults",
|
||||
"-chardev") < 0)
|
||||
return log_oom();
|
||||
|
||||
if (strv_extendf(&cmdline,
|
||||
"serial,id=console,path=%s", pty_path) < 0)
|
||||
return log_oom();
|
||||
|
||||
r = strv_extend_many(
|
||||
&cmdline,
|
||||
"-serial", "chardev:console");
|
||||
break;
|
||||
}
|
||||
|
||||
case CONSOLE_GUI:
|
||||
r = strv_extend_many(
|
||||
&cmdline,
|
||||
"-vga",
|
||||
"virtio");
|
||||
else
|
||||
break;
|
||||
|
||||
case CONSOLE_NATIVE:
|
||||
r = strv_extend_many(
|
||||
&cmdline,
|
||||
"-nographic",
|
||||
@@ -1235,6 +1318,11 @@ static int run_virtual_machine(int kvm_device_fd, int vhost_device_fd) {
|
||||
"-chardev", "stdio,mux=on,id=console,signal=off",
|
||||
"-serial", "chardev:console",
|
||||
"-mon", "console");
|
||||
break;
|
||||
|
||||
default:
|
||||
assert_not_reached();
|
||||
}
|
||||
if (r < 0)
|
||||
return log_oom();
|
||||
|
||||
@@ -1583,7 +1671,7 @@ static int run_virtual_machine(int kvm_device_fd, int vhost_device_fd) {
|
||||
log_debug("Executing: %s", joined);
|
||||
}
|
||||
|
||||
assert_se(sigprocmask_many(SIG_BLOCK, NULL, SIGCHLD) >= 0);
|
||||
assert_se(sigprocmask_many(SIG_BLOCK, /* old_sigset=*/ NULL, SIGCHLD, SIGWINCH) >= 0);
|
||||
|
||||
_cleanup_(sd_event_source_unrefp) sd_event_source *notify_event_source = NULL;
|
||||
_cleanup_(sd_event_unrefp) sd_event *event = NULL;
|
||||
@@ -1635,6 +1723,26 @@ static int run_virtual_machine(int kvm_device_fd, int vhost_device_fd) {
|
||||
/* Exit when the child exits */
|
||||
(void) event_add_child_pidref(event, NULL, &child_pidref, WEXITED, on_child_exit, NULL);
|
||||
|
||||
_cleanup_(pty_forward_freep) PTYForward *forward = NULL;
|
||||
if (master >= 0) {
|
||||
r = pty_forward_new(event, master, ptyfwd_flags, &forward);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to create PTY forwarder: %m");
|
||||
|
||||
if (!arg_background) {
|
||||
_cleanup_free_ char *bg = NULL;
|
||||
|
||||
r = terminal_tint_color(130 /* green */, &bg);
|
||||
if (r < 0)
|
||||
log_debug_errno(r, "Failed to determine terminal background color, not tinting.");
|
||||
else
|
||||
(void) pty_forward_set_background_color(forward, bg);
|
||||
} else if (!isempty(arg_background))
|
||||
(void) pty_forward_set_background_color(forward, arg_background);
|
||||
|
||||
set_window_title(forward);
|
||||
}
|
||||
|
||||
r = sd_event_loop(event);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to run event loop: %m");
|
||||
@@ -1740,15 +1848,20 @@ static int run(int argc, char *argv[]) {
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
if (!arg_quiet) {
|
||||
if (!arg_quiet && arg_console_mode != CONSOLE_GUI) {
|
||||
_cleanup_free_ char *u = NULL;
|
||||
const char *vm_path = arg_image ?: arg_directory;
|
||||
(void) terminal_urlify_path(vm_path, vm_path, &u);
|
||||
|
||||
log_info("%s %sSpawning VM %s on %s.%s\n"
|
||||
"%s %sPress %sCtrl-a x%s to kill VM.%s",
|
||||
special_glyph(SPECIAL_GLYPH_LIGHT_SHADE), ansi_grey(), arg_machine, u ?: vm_path, ansi_normal(),
|
||||
special_glyph(SPECIAL_GLYPH_LIGHT_SHADE), ansi_grey(), ansi_highlight(), ansi_grey(), ansi_normal());
|
||||
log_info("%s %sSpawning VM %s on %s.%s",
|
||||
special_glyph(SPECIAL_GLYPH_LIGHT_SHADE), ansi_grey(), arg_machine, u ?: vm_path, ansi_normal());
|
||||
|
||||
if (arg_console_mode == CONSOLE_INTERACTIVE)
|
||||
log_info("%s %sPress %sCtrl-]%s three times within 1s to kill VM.%s",
|
||||
special_glyph(SPECIAL_GLYPH_LIGHT_SHADE), ansi_grey(), ansi_highlight(), ansi_grey(), ansi_normal());
|
||||
else if (arg_console_mode == CONSOLE_NATIVE)
|
||||
log_info("%s %sPress %sCtrl-a x%s to kill VM.%s",
|
||||
special_glyph(SPECIAL_GLYPH_LIGHT_SHADE), ansi_grey(), ansi_highlight(), ansi_grey(), ansi_normal());
|
||||
}
|
||||
|
||||
r = sd_listen_fds_with_names(true, &names);
|
||||
|
||||
Reference in New Issue
Block a user