mirror of
https://github.com/systemd/systemd.git
synced 2026-11-06 05:17:24 +00:00
ptyctl: add new client tool for ptybrokerd
This commit is contained in:
1 parent
0cba4522db
commit
d166b4c123
9 files changed
+754
No files matched your search
+168
@@ -0,0 +1,168 @@
|
||||
<?xml version='1.0'?> <!--*-nxml-*-->
|
||||
<!DOCTYPE refentry PUBLIC "-//OASIS//DTD DocBook XML V4.5//EN"
|
||||
"http://www.oasis-open.org/docbook/xml/4.5/docbookx.dtd">
|
||||
<!-- SPDX-License-Identifier: LGPL-2.1-or-later -->
|
||||
|
||||
<refentry id="ptyctl"
|
||||
xmlns:xi="http://www.w3.org/2001/XInclude">
|
||||
|
||||
<refentryinfo>
|
||||
<title>ptyctl</title>
|
||||
<productname>systemd</productname>
|
||||
</refentryinfo>
|
||||
|
||||
<refmeta>
|
||||
<refentrytitle>ptyctl</refentrytitle>
|
||||
<manvolnum>1</manvolnum>
|
||||
</refmeta>
|
||||
|
||||
<refnamediv>
|
||||
<refname>ptyctl</refname>
|
||||
<refpurpose>List, monitor and hang up pseudo TTYs registered with the pseudo TTY broker</refpurpose>
|
||||
</refnamediv>
|
||||
|
||||
<refsynopsisdiv>
|
||||
<cmdsynopsis>
|
||||
<command>ptyctl</command>
|
||||
<arg choice="opt" rep="repeat">OPTIONS</arg>
|
||||
<arg choice="opt">COMMAND</arg>
|
||||
<arg choice="opt" rep="repeat">NAME</arg>
|
||||
</cmdsynopsis>
|
||||
</refsynopsisdiv>
|
||||
|
||||
<refsect1>
|
||||
<title>Description</title>
|
||||
|
||||
<para><command>ptyctl</command> may be used to introspect and control the pseudo TTY allocations
|
||||
registered with the pseudo TTY broker,
|
||||
<citerefentry><refentrytitle>systemd-ptybrokerd.service</refentrytitle><manvolnum>8</manvolnum></citerefentry>.
|
||||
The broker keeps the frontend (i.e. the <literal>master</literal>) side of registered pseudo TTYs open,
|
||||
while the payload — a service, a container, a virtual machine console, or similar — runs connected to the
|
||||
backend (i.e. the <literal>slave</literal>) side. <command>ptyctl</command> permits listing these pseudo
|
||||
TTYs, interactively connecting to them, and hanging them up.</para>
|
||||
|
||||
<para>Pseudo TTYs are registered with the broker for example via <option>--console=broker</option> of
|
||||
<citerefentry><refentrytitle>systemd-pty-forward</refentrytitle><manvolnum>1</manvolnum></citerefentry>,
|
||||
<citerefentry><refentrytitle>systemd-nspawn</refentrytitle><manvolnum>1</manvolnum></citerefentry> and
|
||||
<citerefentry><refentrytitle>systemd-vmspawn</refentrytitle><manvolnum>1</manvolnum></citerefentry>, or
|
||||
via <varname>StandardOutput=broker</varname> in service units, see
|
||||
<citerefentry><refentrytitle>systemd.exec</refentrytitle><manvolnum>5</manvolnum></citerefentry>.</para>
|
||||
</refsect1>
|
||||
|
||||
<refsect1>
|
||||
<title>Commands</title>
|
||||
|
||||
<para>The following commands are understood:</para>
|
||||
|
||||
<variablelist>
|
||||
<varlistentry>
|
||||
<term><command>list</command></term>
|
||||
|
||||
<listitem><para>List the pseudo TTYs currently registered with the broker, showing their name,
|
||||
description, log tag, frontend and backend types, and backend TTY path. This command is the default
|
||||
if no command is specified.</para>
|
||||
|
||||
<xi:include href="version-info.xml" xpointer="v263"/></listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry>
|
||||
<term><command>monitor</command> <replaceable>NAME</replaceable></term>
|
||||
|
||||
<listitem><para>Connect interactively to the pseudo TTY registered under the specified name: its
|
||||
output is forwarded to the local terminal, and local input is forwarded back to it. To disconnect,
|
||||
press <keycombo><keycap>Ctrl</keycap><keycap>]</keycap></keycombo> three times within one
|
||||
second. Disconnecting only ends the monitor connection, the pseudo TTY itself remains registered with
|
||||
the broker and the payload continues to run.</para>
|
||||
|
||||
<xi:include href="version-info.xml" xpointer="v263"/></listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry>
|
||||
<term><command>hangup</command> <replaceable>NAME</replaceable>…</term>
|
||||
|
||||
<listitem><para>Hang up the pseudo TTYs registered under the specified names. This executes the
|
||||
equivalent of
|
||||
<citerefentry project='man-pages'><refentrytitle>vhangup</refentrytitle><manvolnum>2</manvolnum></citerefentry>
|
||||
on the pseudo TTY, disconnecting the session running on its backend side.</para>
|
||||
|
||||
<xi:include href="version-info.xml" xpointer="v263"/></listitem>
|
||||
</varlistentry>
|
||||
</variablelist>
|
||||
</refsect1>
|
||||
|
||||
<refsect1>
|
||||
<title>Options</title>
|
||||
|
||||
<para>The following options are understood:</para>
|
||||
|
||||
<variablelist>
|
||||
<varlistentry>
|
||||
<term><option>--system</option></term>
|
||||
<term><option>--user</option></term>
|
||||
|
||||
<listitem><para>Selects whether to talk to the system-wide instance or the per-user instance of
|
||||
<citerefentry><refentrytitle>systemd-ptybrokerd.service</refentrytitle><manvolnum>8</manvolnum></citerefentry>.
|
||||
Defaults to <option>--system</option>.</para>
|
||||
|
||||
<xi:include href="version-info.xml" xpointer="v263"/></listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry>
|
||||
<term><option>--background=<replaceable>COLOR</replaceable></option></term>
|
||||
|
||||
<listitem><para>Change the terminal background color to the specified ANSI color while monitoring a
|
||||
pseudo TTY. 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. Only has an effect on the <command>monitor</command>
|
||||
command.</para>
|
||||
|
||||
<para>Example: <literal>--background=44</literal> for a blue background.</para>
|
||||
|
||||
<xi:include href="version-info.xml" xpointer="v263"/></listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry>
|
||||
<term><option>--title=<replaceable>TITLE</replaceable></option></term>
|
||||
|
||||
<listitem><para>Change the terminal title to the specified string while monitoring a pseudo TTY. Only
|
||||
has an effect on the <command>monitor</command> command.</para>
|
||||
|
||||
<xi:include href="version-info.xml" xpointer="v263"/></listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry>
|
||||
<term><option>--quiet</option></term>
|
||||
<term><option>-q</option></term>
|
||||
|
||||
<listitem><para>Suppresses additional informational output while running.</para>
|
||||
|
||||
<xi:include href="version-info.xml" xpointer="v263"/></listitem>
|
||||
</varlistentry>
|
||||
|
||||
<xi:include href="standard-options.xml" xpointer="no-ask-password"/>
|
||||
<xi:include href="standard-options.xml" xpointer="help"/>
|
||||
<xi:include href="standard-options.xml" xpointer="version"/>
|
||||
</variablelist>
|
||||
</refsect1>
|
||||
|
||||
<refsect1>
|
||||
<title>Exit status</title>
|
||||
|
||||
<para>On success, 0 is returned, a non-zero failure code otherwise.</para>
|
||||
</refsect1>
|
||||
|
||||
<refsect1>
|
||||
<title>See Also</title>
|
||||
<para><simplelist type="inline">
|
||||
<member><citerefentry><refentrytitle>systemd</refentrytitle><manvolnum>1</manvolnum></citerefentry></member>
|
||||
<member><citerefentry><refentrytitle>systemd-ptybrokerd.service</refentrytitle><manvolnum>8</manvolnum></citerefentry></member>
|
||||
<member><citerefentry><refentrytitle>systemd-pty-forward</refentrytitle><manvolnum>1</manvolnum></citerefentry></member>
|
||||
<member><citerefentry><refentrytitle>systemd-nspawn</refentrytitle><manvolnum>1</manvolnum></citerefentry></member>
|
||||
<member><citerefentry><refentrytitle>systemd-vmspawn</refentrytitle><manvolnum>1</manvolnum></citerefentry></member>
|
||||
<member><citerefentry><refentrytitle>systemd.exec</refentrytitle><manvolnum>5</manvolnum></citerefentry></member>
|
||||
</simplelist></para>
|
||||
</refsect1>
|
||||
|
||||
</refentry>
|
||||
@@ -82,6 +82,7 @@ manpages = [
|
||||
['portablectl', '1', [], 'ENABLE_PORTABLED'],
|
||||
['poweroff', '8', ['halt', 'reboot'], ''],
|
||||
['pstore.conf', '5', ['pstore.conf.d'], 'ENABLE_PSTORE'],
|
||||
['ptyctl', '1', [], ''],
|
||||
['repart.d', '5', [], 'ENABLE_REPART'],
|
||||
['report-geoip.conf',
|
||||
'5',
|
||||
|
||||
@@ -45,6 +45,7 @@ foreach item : [
|
||||
['networkctl', 'ENABLE_NETWORKD'],
|
||||
['oomctl', 'ENABLE_OOMD'],
|
||||
['portablectl', 'ENABLE_PORTABLED'],
|
||||
['ptyctl', ''],
|
||||
['resolvectl', 'ENABLE_RESOLVE'],
|
||||
['run0', ''],
|
||||
['storagectl', ''],
|
||||
|
||||
@@ -0,0 +1,84 @@
|
||||
# shellcheck shell=bash
|
||||
# ptyctl(1) completion -*- shell-script -*-
|
||||
# SPDX-License-Identifier: LGPL-2.1-or-later
|
||||
#
|
||||
# This file is part of systemd.
|
||||
#
|
||||
# systemd is free software; you can redistribute it and/or modify it
|
||||
# under the terms of the GNU Lesser General Public License as published by
|
||||
# the Free Software Foundation; either version 2.1 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# systemd is distributed in the hope that it will be useful, but
|
||||
# WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
# General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with systemd; If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
__contains_word () {
|
||||
local w word=$1; shift
|
||||
for w in "$@"; do
|
||||
[[ $w = "$word" ]] && return
|
||||
done
|
||||
}
|
||||
|
||||
__get_ptys() {
|
||||
local mode=$1
|
||||
ptyctl "$mode" --quiet list 2>/dev/null | awk 'NR > 1 { print $1 }'
|
||||
}
|
||||
|
||||
_ptyctl() {
|
||||
local cur=${COMP_WORDS[COMP_CWORD]} prev=${COMP_WORDS[COMP_CWORD-1]} words cword
|
||||
local i verb comps mode
|
||||
|
||||
local -A OPTS=(
|
||||
[STANDALONE]='-h --help --version -q --quiet --no-ask-password --system --user'
|
||||
[ARG]='--background --title'
|
||||
)
|
||||
|
||||
_init_completion || return
|
||||
|
||||
if __contains_word "--user" ${COMP_WORDS[*]}; then
|
||||
mode=--user
|
||||
else
|
||||
mode=--system
|
||||
fi
|
||||
|
||||
if __contains_word "$prev" ${OPTS[ARG]}; then
|
||||
comps=''
|
||||
COMPREPLY=( $(compgen -W '$comps' -- "$cur") )
|
||||
return 0
|
||||
fi
|
||||
|
||||
if [[ "$cur" = -* ]]; then
|
||||
COMPREPLY=( $(compgen -W '${OPTS[*]}' -- "$cur") )
|
||||
return 0
|
||||
fi
|
||||
|
||||
local -A VERBS=(
|
||||
[STANDALONE]='list'
|
||||
[PTYS]='monitor hangup'
|
||||
)
|
||||
|
||||
for ((i=0; i < COMP_CWORD; i++)); do
|
||||
if __contains_word "${COMP_WORDS[i]}" ${VERBS[*]}; then
|
||||
verb=${COMP_WORDS[i]}
|
||||
break
|
||||
fi
|
||||
done
|
||||
|
||||
if [[ -z ${verb-} ]]; then
|
||||
comps=${VERBS[*]}
|
||||
elif __contains_word "$verb" ${VERBS[STANDALONE]}; then
|
||||
comps=''
|
||||
elif __contains_word "$verb" ${VERBS[PTYS]}; then
|
||||
comps=$(__get_ptys $mode)
|
||||
fi
|
||||
|
||||
COMPREPLY=( $(compgen -W '$comps' -- "$cur") )
|
||||
return 0
|
||||
}
|
||||
|
||||
complete -F _ptyctl ptyctl
|
||||
@@ -0,0 +1,49 @@
|
||||
#compdef ptyctl
|
||||
# SPDX-License-Identifier: LGPL-2.1-or-later
|
||||
|
||||
(( $+functions[_ptyctl_ptys] )) || _ptyctl_ptys()
|
||||
{
|
||||
local mode
|
||||
if (( $words[(I)--user] )); then
|
||||
mode=--user
|
||||
else
|
||||
mode=--system
|
||||
fi
|
||||
|
||||
local -a _ptys
|
||||
_ptys=( ${(f)"$(ptyctl $mode --quiet list 2>/dev/null | awk 'NR > 1 { print $1 }')"} )
|
||||
_describe -t ptys 'pty' _ptys
|
||||
}
|
||||
|
||||
(( $+functions[_ptyctl_commands] )) || _ptyctl_commands()
|
||||
{
|
||||
local -a _ptyctl_cmds
|
||||
_ptyctl_cmds=(
|
||||
"list:List the PTYs registered with the broker"
|
||||
"monitor:Monitor the specified PTY"
|
||||
"hangup:Hang up the specified PTY(s)"
|
||||
)
|
||||
if (( CURRENT == 1 )); then
|
||||
_describe -t commands 'ptyctl command' _ptyctl_cmds
|
||||
else
|
||||
case "$words[1]" in
|
||||
monitor|hangup)
|
||||
_ptyctl_ptys
|
||||
;;
|
||||
*)
|
||||
_message "no more options"
|
||||
;;
|
||||
esac
|
||||
fi
|
||||
}
|
||||
|
||||
_arguments \
|
||||
'(- *)'{-h,--help}'[Show this help]' \
|
||||
'(- *)--version[Show package version]' \
|
||||
'(--quiet -q)'{-q,--quiet}'[Suppress information messages during runtime]' \
|
||||
'--no-ask-password[Do not prompt for password]' \
|
||||
'(--user)--system[Talk to system PTY broker]' \
|
||||
'(--system)--user[Talk to per-user PTY broker]' \
|
||||
'--background=[Set ANSI color for background (when monitoring)]: : _message "background color"' \
|
||||
'--title=[Set terminal title (when monitoring)]: : _message "terminal title"' \
|
||||
'*::ptyctl command:_ptyctl_commands'
|
||||
@@ -37,6 +37,7 @@ foreach item : [
|
||||
['_machinectl', 'ENABLE_MACHINED'],
|
||||
['_networkctl', 'ENABLE_NETWORKD'],
|
||||
['_oomctl', 'ENABLE_OOMD'],
|
||||
['_ptyctl', ''],
|
||||
['_resolvectl', 'ENABLE_RESOLVE'],
|
||||
['_run0', ''],
|
||||
['_sd_bus_address', ''],
|
||||
|
||||
@@ -10,6 +10,11 @@ executables += [
|
||||
'ptybroker-util.c',
|
||||
),
|
||||
},
|
||||
executable_template + {
|
||||
'name' : 'ptyctl',
|
||||
'public' : true,
|
||||
'sources' : files('ptyctl.c'),
|
||||
},
|
||||
]
|
||||
|
||||
install_data('io.systemd.PTYBroker.policy',
|
||||
|
||||
@@ -0,0 +1,444 @@
|
||||
/* SPDX-License-Identifier: LGPL-2.1-or-later */
|
||||
|
||||
#include "sd-event.h"
|
||||
#include "sd-json.h"
|
||||
#include "sd-varlink.h"
|
||||
|
||||
#include "alloc-util.h"
|
||||
#include "ansi-color.h"
|
||||
#include "build.h"
|
||||
#include "bus-util.h"
|
||||
#include "fd-util.h"
|
||||
#include "format-table.h"
|
||||
#include "json-util.h"
|
||||
#include "log.h"
|
||||
#include "main-func.h"
|
||||
#include "options.h"
|
||||
#include "parse-argument.h"
|
||||
#include "pidref.h"
|
||||
#include "polkit-agent.h"
|
||||
#include "ptybroker-client.h"
|
||||
#include "ptyfwd.h"
|
||||
#include "runtime-scope.h"
|
||||
#include "set.h"
|
||||
#include "string-util.h"
|
||||
#include "strv.h"
|
||||
#include "terminal-util.h"
|
||||
#include "varlink-util.h"
|
||||
#include "verbs.h"
|
||||
|
||||
static bool arg_quiet = false;
|
||||
static bool arg_ask_password = true;
|
||||
static RuntimeScope arg_runtime_scope = RUNTIME_SCOPE_SYSTEM;
|
||||
static char *arg_background = NULL;
|
||||
static char *arg_title = NULL;
|
||||
|
||||
STATIC_DESTRUCTOR_REGISTER(arg_background, freep);
|
||||
STATIC_DESTRUCTOR_REGISTER(arg_title, freep);
|
||||
|
||||
COMMAND(
|
||||
"ptyctl\0",
|
||||
"Interact with the pseudo TTY broker.",
|
||||
.man_pages = "ptyctl(1)\0",
|
||||
);
|
||||
|
||||
VERB_COMMON_HELP_AUTO_HIDDEN();
|
||||
|
||||
typedef struct ListPtyEntry {
|
||||
const char *name;
|
||||
const char *description;
|
||||
const char *tag;
|
||||
const char *frontend_type;
|
||||
const char *backend_type;
|
||||
const char *backend_path;
|
||||
PidRef owner;
|
||||
uid_t owner_uid;
|
||||
} ListPtyEntry;
|
||||
|
||||
static void list_pty_entry_done(ListPtyEntry *e) {
|
||||
assert(e);
|
||||
|
||||
pidref_done(&e->owner);
|
||||
}
|
||||
|
||||
VERB_DEFAULT_NOARG(verb_list, "list", "List the PTYs registered with the broker");
|
||||
static int verb_list(int argc, char *argv[], uintptr_t data, void *userdata) {
|
||||
int r;
|
||||
|
||||
(void) polkit_agent_open_if_enabled(BUS_TRANSPORT_LOCAL, arg_ask_password);
|
||||
|
||||
_cleanup_(sd_varlink_unrefp) sd_varlink *vl = NULL;
|
||||
r = pty_broker_connect(arg_runtime_scope, &vl);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
sd_json_variant *reply = NULL;
|
||||
const char *error_id = NULL;
|
||||
r = sd_varlink_collectbo(
|
||||
vl,
|
||||
"io.systemd.PTYBroker.ListPty",
|
||||
&reply,
|
||||
&error_id,
|
||||
SD_JSON_BUILD_PAIR_BOOLEAN("allowInteractiveAuthentication", arg_ask_password));
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to call ListPty(): %m");
|
||||
if (error_id && !streq(error_id, "io.systemd.PTYBroker.NoSuchPty"))
|
||||
return log_error_errno(sd_varlink_error_to_errno(error_id, reply),
|
||||
"Failed to list PTYs: %s", error_id);
|
||||
|
||||
_cleanup_(table_unrefp) Table *table = table_new("name", "description", "tag", "frontend", "backend", "backend path", "uid", "pid");
|
||||
if (!table)
|
||||
return log_oom();
|
||||
|
||||
table_set_ersatz_string(table, TABLE_ERSATZ_DASH);
|
||||
(void) table_set_sort(table, (size_t) 0);
|
||||
|
||||
sd_json_variant *i;
|
||||
JSON_VARIANT_ARRAY_FOREACH(i, reply) {
|
||||
static const sd_json_dispatch_field dispatch_table[] = {
|
||||
{ "name", SD_JSON_VARIANT_STRING, sd_json_dispatch_const_string, offsetof(ListPtyEntry, name), 0 },
|
||||
{ "description", SD_JSON_VARIANT_STRING, sd_json_dispatch_const_string, offsetof(ListPtyEntry, description), 0 },
|
||||
{ "tag", SD_JSON_VARIANT_STRING, sd_json_dispatch_const_string, offsetof(ListPtyEntry, tag), 0 },
|
||||
{ "frontendType", SD_JSON_VARIANT_STRING, sd_json_dispatch_const_string, offsetof(ListPtyEntry, frontend_type), 0 },
|
||||
{ "backendType", SD_JSON_VARIANT_STRING, sd_json_dispatch_const_string, offsetof(ListPtyEntry, backend_type), 0 },
|
||||
{ "backendPath", SD_JSON_VARIANT_STRING, sd_json_dispatch_const_string, offsetof(ListPtyEntry, backend_path), 0 },
|
||||
{ "owner", SD_JSON_VARIANT_OBJECT, json_dispatch_pidref, offsetof(ListPtyEntry, owner), 0 },
|
||||
{ "ownerUID", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uid_gid, offsetof(ListPtyEntry, owner_uid), 0 },
|
||||
{}
|
||||
};
|
||||
|
||||
_cleanup_(list_pty_entry_done) ListPtyEntry e = {
|
||||
.owner = PIDREF_NULL,
|
||||
.owner_uid = UID_INVALID,
|
||||
};
|
||||
|
||||
r = sd_json_dispatch(i, dispatch_table, SD_JSON_ALLOW_EXTENSIONS, &e);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to parse ListPty() reply: %m");
|
||||
|
||||
r = table_add_many(
|
||||
table,
|
||||
TABLE_STRING, e.name,
|
||||
TABLE_STRING, e.description,
|
||||
TABLE_STRING, e.tag,
|
||||
TABLE_STRING, e.frontend_type,
|
||||
TABLE_STRING, e.backend_type,
|
||||
TABLE_STRING, e.backend_path,
|
||||
TABLE_UID, e.owner_uid,
|
||||
TABLE_PID, e.owner.pid);
|
||||
if (r < 0)
|
||||
return table_log_add_error(r);
|
||||
}
|
||||
|
||||
if (table_isempty(table)) {
|
||||
if (!arg_quiet)
|
||||
log_info("No PTYs.");
|
||||
return 0;
|
||||
}
|
||||
|
||||
return table_print_or_warn(table);
|
||||
}
|
||||
|
||||
static int monitor_broker_pty(const char *name, int *ret_monitor_fd, char ***ret_track_buffer) {
|
||||
int r;
|
||||
|
||||
assert(name);
|
||||
assert(ret_monitor_fd);
|
||||
assert(ret_track_buffer);
|
||||
|
||||
/* Attach to an *existing* pseudo TTY allocation via ptybrokerd's MonitorPty(). We neither allocate a
|
||||
* new PTY nor spawn a child: MonitorPty() upgrades the connection to a bidirectional socket that
|
||||
* carries the monitored frontend's output to us and forwards our input back to it. We use it as the
|
||||
* PTY forwarder "master". */
|
||||
|
||||
(void) polkit_agent_open_if_enabled(BUS_TRANSPORT_LOCAL, arg_ask_password);
|
||||
|
||||
_cleanup_(sd_varlink_unrefp) sd_varlink *vl = NULL;
|
||||
r = pty_broker_connect(arg_runtime_scope, &vl);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
_cleanup_(sd_json_variant_unrefp) sd_json_variant *ts = NULL;
|
||||
r = pty_broker_terminal_settings_to_json(&ts);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to build terminal settings: %m");
|
||||
|
||||
_cleanup_close_ int input_fd = -EBADF, output_fd = -EBADF;
|
||||
sd_json_variant *reply = NULL;
|
||||
const char *error_id = NULL;
|
||||
r = sd_varlink_call_and_upgradebo(
|
||||
vl,
|
||||
"io.systemd.PTYBroker.MonitorPty",
|
||||
&reply,
|
||||
&error_id,
|
||||
&input_fd,
|
||||
&output_fd,
|
||||
SD_JSON_BUILD_PAIR_STRING("name", name),
|
||||
SD_JSON_BUILD_PAIR_UNSIGNED("trackBufferLines", MAX(5U, lines() / 2U)),
|
||||
SD_JSON_BUILD_PAIR_VARIANT("terminalSettings", ts),
|
||||
SD_JSON_BUILD_PAIR_BOOLEAN("allowInteractiveAuthentication", arg_ask_password));
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to call MonitorPty(): %m");
|
||||
if (error_id)
|
||||
return log_error_errno(sd_varlink_error_to_errno(error_id, reply),
|
||||
"Failed to monitor PTY via ptybrokerd: %s", error_id);
|
||||
|
||||
r = same_fd(input_fd, output_fd);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to check if input/output file descriptors match: %m");
|
||||
if (r == 0)
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EPROTO), "Input/output file descriptor of monitor connection do not match.");
|
||||
|
||||
_cleanup_(strv_freep) char **tb = NULL;
|
||||
static const sd_json_dispatch_field dispatch_table[] = {
|
||||
{ "trackBuffer", SD_JSON_VARIANT_ARRAY, sd_json_dispatch_strv, 0, 0 },
|
||||
{}
|
||||
};
|
||||
|
||||
r = sd_json_dispatch(reply, dispatch_table, SD_JSON_ALLOW_EXTENSIONS, &tb);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to parse MonitorPty() reply: %m");
|
||||
|
||||
/* The upgraded monitor connection is a single bidirectional socket, handed to us as two dup'ed fds.
|
||||
* The PTY forwarder reads and writes a single "master" fd, so we keep the input side for that and
|
||||
* drop the redundant output side. */
|
||||
*ret_monitor_fd = TAKE_FD(input_fd);
|
||||
*ret_track_buffer = TAKE_PTR(tb);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int pty_forward_handler(PTYForward *f, int rcode, void *userdata) {
|
||||
sd_event *e = ASSERT_PTR(userdata);
|
||||
|
||||
assert(f);
|
||||
|
||||
if (rcode == -ECANCELED) {
|
||||
log_debug_errno(rcode, "PTY forwarder disconnected.");
|
||||
return sd_event_exit(e, EXIT_SUCCESS);
|
||||
}
|
||||
if (rcode < 0) {
|
||||
(void) sd_event_exit(e, EXIT_FAILURE);
|
||||
return log_error_errno(rcode, "Error on PTY forwarding logic: %m");
|
||||
}
|
||||
|
||||
/* rcode == 0: the monitored PTY has been hung up or released. Unlike systemd-pty-forward we manage no
|
||||
* child process whose exit would terminate the event loop, hence exit on hangup here. */
|
||||
log_debug("Monitored PTY was released, exiting.");
|
||||
return sd_event_exit(e, EXIT_SUCCESS);
|
||||
}
|
||||
|
||||
VERB(verb_monitor, "monitor", "NAME\0", 2, 2, 0, "Monitor the specified PTY");
|
||||
static int verb_monitor(int argc, char *argv[], uintptr_t data, void *userdata) {
|
||||
_cleanup_(sd_event_unrefp) sd_event *event = NULL;
|
||||
_cleanup_close_ int monitor_fd = -EBADF;
|
||||
_cleanup_(pty_forward_freep) PTYForward *forward = NULL;
|
||||
int r;
|
||||
|
||||
_cleanup_(strv_freep) char **track_buffer = NULL;
|
||||
r = monitor_broker_pty(argv[1], &monitor_fd, &track_buffer);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
r = sd_event_default(&event);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to get event loop: %m");
|
||||
|
||||
if (!arg_quiet)
|
||||
log_info("Press ^] three times within 1s to disconnect TTY.");
|
||||
|
||||
STRV_FOREACH(i, track_buffer)
|
||||
printf("%s%s%s\n", ansi_grey(), *i, ansi_normal());
|
||||
fflush(stdout);
|
||||
|
||||
r = pty_forward_new(event, monitor_fd, /* flags= */ 0, &forward);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to create PTY forwarder: %m");
|
||||
|
||||
if (!isempty(arg_background)) {
|
||||
r = pty_forward_set_background_color(forward, arg_background);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to set background color: %m");
|
||||
}
|
||||
|
||||
if (shall_set_terminal_title() && !isempty(arg_title)) {
|
||||
r = pty_forward_set_title(forward, arg_title);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to set title: %m");
|
||||
}
|
||||
|
||||
pty_forward_set_hangup_handler(forward, pty_forward_handler, event);
|
||||
|
||||
return sd_event_loop(event);
|
||||
}
|
||||
|
||||
typedef struct HangUpContext {
|
||||
sd_event *event;
|
||||
Set *connections;
|
||||
int error;
|
||||
} HangUpContext;
|
||||
|
||||
static void hangup_context_done(HangUpContext *c) {
|
||||
assert(c);
|
||||
|
||||
/* Free the connections before the event loop, as they hold event sources attached to it. */
|
||||
set_free(c->connections);
|
||||
sd_event_unref(c->event);
|
||||
}
|
||||
|
||||
static int on_hangup_reply(
|
||||
sd_varlink *link,
|
||||
sd_json_variant *parameters,
|
||||
const char *error_id,
|
||||
sd_varlink_reply_flags_t flags,
|
||||
void *userdata) {
|
||||
|
||||
HangUpContext *context = ASSERT_PTR(userdata);
|
||||
|
||||
assert(link);
|
||||
|
||||
/* The name of the PTY this connection hangs up is stashed in the connection's description. */
|
||||
const char *name = sd_varlink_get_description(link);
|
||||
|
||||
if (error_id) {
|
||||
int e = sd_varlink_error_to_errno(error_id, parameters);
|
||||
log_error_errno(e, "Failed to hang up PTY '%s': %s", strna(name), error_id);
|
||||
if (context->error == 0)
|
||||
context->error = e;
|
||||
}
|
||||
|
||||
/* Drop the completed connection from the set. sd_varlink_process() holds a reference for the duration
|
||||
* of this callback, so unreffing here is safe. Once the set has drained we are done. */
|
||||
assert_se(set_remove(context->connections, link) == link);
|
||||
sd_varlink_unref(link);
|
||||
|
||||
if (set_isempty(context->connections))
|
||||
(void) sd_event_exit(context->event, 0); /* Errors are propagated via context->error instead */
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
VERB(verb_hangup, "hangup", "NAME…\0", 2, VERB_ANY, 0, "Hang up the specified PTY(s)");
|
||||
static int verb_hangup(int argc, char *argv[], uintptr_t data, void *userdata) {
|
||||
_cleanup_(hangup_context_done) HangUpContext context = {};
|
||||
int r;
|
||||
|
||||
(void) polkit_agent_open_if_enabled(BUS_TRANSPORT_LOCAL, arg_ask_password);
|
||||
|
||||
r = sd_event_default(&context.event);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to get event loop: %m");
|
||||
|
||||
/* Establish a separate connection to the broker for each PTY to hang up, keep all of them alive in a
|
||||
* Set, and enqueue the HangUpPty() calls in parallel. Each reply drops its connection from the set;
|
||||
* we only return once the set has drained. */
|
||||
|
||||
STRV_FOREACH(name, strv_skip(argv, 1)) {
|
||||
_cleanup_(sd_varlink_unrefp) sd_varlink *vl = NULL;
|
||||
r = pty_broker_connect(arg_runtime_scope, &vl);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
r = sd_varlink_set_description(vl, *name);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to set Varlink connection description: %m");
|
||||
|
||||
r = sd_varlink_attach_event(vl, context.event, SD_EVENT_PRIORITY_NORMAL);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to attach Varlink connection to event loop: %m");
|
||||
|
||||
sd_varlink_set_userdata(vl, &context);
|
||||
|
||||
r = sd_varlink_bind_reply(vl, on_hangup_reply);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to bind reply callback: %m");
|
||||
|
||||
r = sd_varlink_invokebo(
|
||||
vl,
|
||||
"io.systemd.PTYBroker.HangUpPty",
|
||||
SD_JSON_BUILD_PAIR_STRING("name", *name),
|
||||
SD_JSON_BUILD_PAIR_BOOLEAN("allowInteractiveAuthentication", arg_ask_password));
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to enqueue HangUpPty() call: %m");
|
||||
|
||||
r = set_ensure_put(&context.connections, &varlink_hash_ops, vl);
|
||||
if (r < 0)
|
||||
return log_oom();
|
||||
|
||||
TAKE_PTR(vl); /* The set owns the connection now. */
|
||||
}
|
||||
|
||||
r = sd_event_loop(context.event);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to run event loop: %m");
|
||||
|
||||
return context.error;
|
||||
}
|
||||
|
||||
static int parse_argv(int argc, char *argv[], char ***ret_args) {
|
||||
assert(argc >= 0);
|
||||
assert(argv);
|
||||
assert(ret_args);
|
||||
|
||||
OptionParser opts = { argc, argv };
|
||||
int r;
|
||||
|
||||
FOREACH_OPTION_OR_RETURN(c, &opts)
|
||||
switch (c) {
|
||||
|
||||
OPTION_COMMON_HELP:
|
||||
return command_print_help();
|
||||
|
||||
OPTION_COMMON_VERSION:
|
||||
return version();
|
||||
|
||||
OPTION('q', "quiet", NULL, "Suppress information messages during runtime"):
|
||||
arg_quiet = true;
|
||||
break;
|
||||
|
||||
OPTION_COMMON_NO_ASK_PASSWORD:
|
||||
arg_ask_password = false;
|
||||
break;
|
||||
|
||||
OPTION_LONG("system", NULL, "Talk to system PTY broker"):
|
||||
arg_runtime_scope = RUNTIME_SCOPE_SYSTEM;
|
||||
break;
|
||||
|
||||
OPTION_LONG("user", NULL, "Talk to per-user PTY broker"):
|
||||
arg_runtime_scope = RUNTIME_SCOPE_USER;
|
||||
break;
|
||||
|
||||
OPTION_LONG("background", "COLOR", "Set ANSI color for background (when monitoring)"):
|
||||
r = parse_background_argument(opts.arg, &arg_background);
|
||||
if (r < 0)
|
||||
return r;
|
||||
break;
|
||||
|
||||
OPTION_LONG("title", "TITLE", "Set terminal title (when monitoring)"):
|
||||
r = free_and_strdup_warn(&arg_title, opts.arg);
|
||||
if (r < 0)
|
||||
return r;
|
||||
break;
|
||||
|
||||
OPTION_COMMON_INTROSPECT_CLI:
|
||||
return introspect_cli(SD_JSON_FORMAT_OFF);
|
||||
}
|
||||
|
||||
*ret_args = option_parser_get_args(&opts);
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int run(int argc, char *argv[]) {
|
||||
int r;
|
||||
|
||||
log_setup();
|
||||
|
||||
char **args = NULL;
|
||||
r = parse_argv(argc, argv, &args);
|
||||
if (r <= 0)
|
||||
return r;
|
||||
|
||||
return dispatch_verb(args, NULL);
|
||||
}
|
||||
|
||||
DEFINE_MAIN_FUNCTION_WITH_POSITIVE_FAILURE(run);
|
||||
@@ -48,6 +48,7 @@ INTROSPECTABLE=(
|
||||
oomctl
|
||||
portablectl
|
||||
poweroff
|
||||
ptyctl
|
||||
reboot
|
||||
resolvectl
|
||||
run0
|
||||
|
||||
Reference in new issue
Block a user