diff --git a/man/ptyctl.xml b/man/ptyctl.xml new file mode 100644 index 00000000000..e11eab6b36b --- /dev/null +++ b/man/ptyctl.xml @@ -0,0 +1,168 @@ + + + + + + + + ptyctl + systemd + + + + ptyctl + 1 + + + + ptyctl + List, monitor and hang up pseudo TTYs registered with the pseudo TTY broker + + + + + ptyctl + OPTIONS + COMMAND + NAME + + + + + Description + + ptyctl may be used to introspect and control the pseudo TTY allocations + registered with the pseudo TTY broker, + systemd-ptybrokerd.service8. + The broker keeps the frontend (i.e. the master) 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 slave) side. ptyctl permits listing these pseudo + TTYs, interactively connecting to them, and hanging them up. + + Pseudo TTYs are registered with the broker for example via of + systemd-pty-forward1, + systemd-nspawn1 and + systemd-vmspawn1, or + via StandardOutput=broker in service units, see + systemd.exec5. + + + + Commands + + The following commands are understood: + + + + list + + 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. + + + + + + monitor NAME + + 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 Ctrl] 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. + + + + + + hangup NAME… + + Hang up the pseudo TTYs registered under the specified names. This executes the + equivalent of + vhangup2 + on the pseudo TTY, disconnecting the session running on its backend side. + + + + + + + + Options + + The following options are understood: + + + + + + + Selects whether to talk to the system-wide instance or the per-user instance of + systemd-ptybrokerd.service8. + Defaults to . + + + + + + + + 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 + 40, 41, …, 47, 48;2;…, + 48;5;…. See ANSI + Escape Code (Wikipedia) for details. Only has an effect on the monitor + command. + + Example: --background=44 for a blue background. + + + + + + + + Change the terminal title to the specified string while monitoring a pseudo TTY. Only + has an effect on the monitor command. + + + + + + + + + Suppresses additional informational output while running. + + + + + + + + + + + + Exit status + + On success, 0 is returned, a non-zero failure code otherwise. + + + + See Also + + systemd1 + systemd-ptybrokerd.service8 + systemd-pty-forward1 + systemd-nspawn1 + systemd-vmspawn1 + systemd.exec5 + + + + diff --git a/man/rules/meson.build b/man/rules/meson.build index 3bf1c4ed842..4445ef296e9 100644 --- a/man/rules/meson.build +++ b/man/rules/meson.build @@ -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', diff --git a/shell-completion/bash/meson.build b/shell-completion/bash/meson.build index 98dd8d8a282..fc4c6052752 100644 --- a/shell-completion/bash/meson.build +++ b/shell-completion/bash/meson.build @@ -45,6 +45,7 @@ foreach item : [ ['networkctl', 'ENABLE_NETWORKD'], ['oomctl', 'ENABLE_OOMD'], ['portablectl', 'ENABLE_PORTABLED'], + ['ptyctl', ''], ['resolvectl', 'ENABLE_RESOLVE'], ['run0', ''], ['storagectl', ''], diff --git a/shell-completion/bash/ptyctl b/shell-completion/bash/ptyctl new file mode 100644 index 00000000000..8508466d603 --- /dev/null +++ b/shell-completion/bash/ptyctl @@ -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 . + +__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 diff --git a/shell-completion/zsh/_ptyctl b/shell-completion/zsh/_ptyctl new file mode 100644 index 00000000000..5126b3ced13 --- /dev/null +++ b/shell-completion/zsh/_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' diff --git a/shell-completion/zsh/meson.build b/shell-completion/zsh/meson.build index 6fe0d1e1830..a75896c8ebf 100644 --- a/shell-completion/zsh/meson.build +++ b/shell-completion/zsh/meson.build @@ -37,6 +37,7 @@ foreach item : [ ['_machinectl', 'ENABLE_MACHINED'], ['_networkctl', 'ENABLE_NETWORKD'], ['_oomctl', 'ENABLE_OOMD'], + ['_ptyctl', ''], ['_resolvectl', 'ENABLE_RESOLVE'], ['_run0', ''], ['_sd_bus_address', ''], diff --git a/src/ptybroker/meson.build b/src/ptybroker/meson.build index dee8b88c7ab..a7ba3617a05 100644 --- a/src/ptybroker/meson.build +++ b/src/ptybroker/meson.build @@ -10,6 +10,11 @@ executables += [ 'ptybroker-util.c', ), }, + executable_template + { + 'name' : 'ptyctl', + 'public' : true, + 'sources' : files('ptyctl.c'), + }, ] install_data('io.systemd.PTYBroker.policy', diff --git a/src/ptybroker/ptyctl.c b/src/ptybroker/ptyctl.c new file mode 100644 index 00000000000..30508a0e234 --- /dev/null +++ b/src/ptybroker/ptyctl.c @@ -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); diff --git a/test/units/TEST-74-AUX-UTILS.introspect-cli.sh b/test/units/TEST-74-AUX-UTILS.introspect-cli.sh index 3a791984088..aba41ca8516 100755 --- a/test/units/TEST-74-AUX-UTILS.introspect-cli.sh +++ b/test/units/TEST-74-AUX-UTILS.introspect-cli.sh @@ -48,6 +48,7 @@ INTROSPECTABLE=( oomctl portablectl poweroff + ptyctl reboot resolvectl run0