ptyctl: add new client tool for ptybrokerd

This commit is contained in:
Lennart Poettering committed 2026-10-21 13:19:49 +02:00
1 parent 0cba4522db
commit d166b4c123
9 files changed
+754

No files matched your search

+168
View File
@@ -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>
+1
View File
@@ -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',
+1
View File
@@ -45,6 +45,7 @@ foreach item : [
['networkctl', 'ENABLE_NETWORKD'],
['oomctl', 'ENABLE_OOMD'],
['portablectl', 'ENABLE_PORTABLED'],
['ptyctl', ''],
['resolvectl', 'ENABLE_RESOLVE'],
['run0', ''],
['storagectl', ''],
+84
View File
@@ -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
+49
View File
@@ -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'
+1
View File
@@ -37,6 +37,7 @@ foreach item : [
['_machinectl', 'ENABLE_MACHINED'],
['_networkctl', 'ENABLE_NETWORKD'],
['_oomctl', 'ENABLE_OOMD'],
['_ptyctl', ''],
['_resolvectl', 'ENABLE_RESOLVE'],
['_run0', ''],
['_sd_bus_address', ''],
+5
View File
@@ -10,6 +10,11 @@ executables += [
'ptybroker-util.c',
),
},
executable_template + {
'name' : 'ptyctl',
'public' : true,
'sources' : files('ptyctl.c'),
},
]
install_data('io.systemd.PTYBroker.policy',
+444
View File
@@ -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