mirror of
https://github.com/systemd/systemd.git
synced 2026-11-06 05:17:24 +00:00
ptybroker: add new component that allocates pty on client request
This commit is contained in:
1 parent
633b5d0a16
commit
492a35af37
25 files changed
+4100
No files matched your search
@@ -1213,6 +1213,7 @@ manpages = [
|
||||
''],
|
||||
['systemd-pstore.service', '8', ['systemd-pstore'], 'ENABLE_PSTORE'],
|
||||
['systemd-pty-forward', '1', [], ''],
|
||||
['systemd-ptybrokerd.service', '8', ['systemd-ptybrokerd'], ''],
|
||||
['systemd-quotacheck@.service',
|
||||
'8',
|
||||
['systemd-quotacheck', 'systemd-quotacheck-root.service'],
|
||||
|
||||
@@ -0,0 +1,105 @@
|
||||
<?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="systemd-ptybrokerd.service">
|
||||
|
||||
<refentryinfo>
|
||||
<title>systemd-ptybrokerd.service</title>
|
||||
<productname>systemd</productname>
|
||||
</refentryinfo>
|
||||
|
||||
<refmeta>
|
||||
<refentrytitle>systemd-ptybrokerd.service</refentrytitle>
|
||||
<manvolnum>8</manvolnum>
|
||||
</refmeta>
|
||||
|
||||
<refnamediv>
|
||||
<refname>systemd-ptybrokerd.service</refname>
|
||||
<refname>systemd-ptybrokerd</refname>
|
||||
<refpurpose>Pseudo TTY Broker</refpurpose>
|
||||
</refnamediv>
|
||||
|
||||
<refsynopsisdiv>
|
||||
<para><filename>systemd-ptybrokerd.service</filename></para>
|
||||
<para><filename>systemd-ptybrokerd.socket</filename></para>
|
||||
<para><filename>/usr/lib/systemd/systemd-ptybrokerd</filename></para>
|
||||
</refsynopsisdiv>
|
||||
|
||||
<refsect1>
|
||||
<title>Description</title>
|
||||
|
||||
<para><command>systemd-ptybrokerd</command> is a service that allocates and manages pseudo terminals
|
||||
(PTYs) on behalf of clients. It allocates a PTY pair on client request — or takes possession of the
|
||||
frontend (i.e. <literal>master</literal>) side of a PTY the client already allocated — and then takes
|
||||
care of the frontend side of the PTY: whatever is written to the PTY is either discarded or forwarded to
|
||||
the log, and may at any time be monitored and interactively attached to. The backend (i.e.
|
||||
<literal>slave</literal>) side of the PTY is handed back to the client, which typically invokes some
|
||||
payload with its standard input/output/error connected to it. At the client's request the broker may also
|
||||
invoke a shell on the backend side itself, as a transient service unit. When running as part of the
|
||||
system service manager, such shell sessions run through the <literal>systemd-ptybroker-shell</literal>
|
||||
PAM stack.</para>
|
||||
|
||||
<para>This mechanism decouples the lifetime and management of a PTY from the process that requested it:
|
||||
the terminal of a service, container, virtual machine or shell session managed by the broker exists
|
||||
independently of any interactive client, may be monitored or attached to at any time (also by multiple
|
||||
clients simultaneously), and continues to operate — logging output, if configured — when no client is
|
||||
attached at all. It hence provides terminal semantics for otherwise non-interactive contexts, for example
|
||||
fully headless systems.</para>
|
||||
|
||||
<para>The service is used by various other components:</para>
|
||||
|
||||
<itemizedlist>
|
||||
<listitem><para>The service manager connects the standard input/output/error of services to a
|
||||
broker-provided PTY if <varname>StandardInput=broker</varname>,
|
||||
<varname>StandardOutput=broker</varname>, <varname>StandardError=broker</varname> (or their
|
||||
<option>broker-log</option> flavours) are used, see
|
||||
<citerefentry><refentrytitle>systemd.exec</refentrytitle><manvolnum>5</manvolnum></citerefentry>.</para></listitem>
|
||||
|
||||
<listitem><para><citerefentry><refentrytitle>systemd-pty-forward</refentrytitle><manvolnum>1</manvolnum></citerefentry>
|
||||
invokes arbitrary commands on a broker-provided PTY via
|
||||
<option>--console=broker</option>/<option>--console=broker-log</option>.</para></listitem>
|
||||
|
||||
<listitem><para><citerefentry><refentrytitle>systemd-nspawn</refentrytitle><manvolnum>1</manvolnum></citerefentry>
|
||||
and
|
||||
<citerefentry><refentrytitle>systemd-vmspawn</refentrytitle><manvolnum>1</manvolnum></citerefentry>
|
||||
register the console of a container or virtual machine with the broker via their
|
||||
<option>--console=broker</option>/<option>--console=broker-log</option> switches.</para></listitem>
|
||||
|
||||
<listitem><para><citerefentry><refentrytitle>run0</refentrytitle><manvolnum>1</manvolnum></citerefentry>
|
||||
requests interactive privileged shell sessions from the broker.</para></listitem>
|
||||
</itemizedlist>
|
||||
|
||||
<para>PTYs managed by the broker may be listed, monitored, interactively attached to, and hung up with
|
||||
the <citerefentry><refentrytitle>ptyctl</refentrytitle><manvolnum>1</manvolnum></citerefentry>
|
||||
tool.</para>
|
||||
|
||||
<para>The service is available both as a system service and as a per-user service. The system instance
|
||||
brokers PTYs for system services and privileged clients, the user instances do the same for the
|
||||
respective user's service manager and clients. Both instances are socket activated (via
|
||||
<filename>systemd-ptybrokerd.socket</filename>) and exit automatically again once they are idle, i.e.
|
||||
when no client is connected and no PTY is registered anymore.</para>
|
||||
|
||||
<para>The service provides the <constant>io.systemd.PTYBroker</constant> <ulink
|
||||
url="https://varlink.org/">Varlink</ulink> IPC interface on <filename>/run/systemd/io.systemd.PTYBroker</filename>
|
||||
(for the system instance) and <filename>$XDG_RUNTIME_DIR/systemd/io.systemd.PTYBroker</filename> (for
|
||||
the user instances). Access to the operations of the system instance is gated via <ulink
|
||||
url="https://www.freedesktop.org/software/polkit/docs/latest/">polkit</ulink>.</para>
|
||||
</refsect1>
|
||||
|
||||
<refsect1>
|
||||
<title>See Also</title>
|
||||
|
||||
<para><simplelist type="inline">
|
||||
<member><citerefentry><refentrytitle>systemd</refentrytitle><manvolnum>1</manvolnum></citerefentry></member>
|
||||
<member><citerefentry><refentrytitle>ptyctl</refentrytitle><manvolnum>1</manvolnum></citerefentry></member>
|
||||
<member><citerefentry><refentrytitle>systemd-pty-forward</refentrytitle><manvolnum>1</manvolnum></citerefentry></member>
|
||||
<member><citerefentry><refentrytitle>systemd.exec</refentrytitle><manvolnum>5</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>run0</refentrytitle><manvolnum>1</manvolnum></citerefentry></member>
|
||||
</simplelist></para>
|
||||
</refsect1>
|
||||
|
||||
</refentry>
|
||||
@@ -2262,6 +2262,7 @@ subdir('src/pcrextend')
|
||||
subdir('src/pcrlock')
|
||||
subdir('src/portable')
|
||||
subdir('src/pstore')
|
||||
subdir('src/ptybroker')
|
||||
subdir('src/ptyfwd')
|
||||
subdir('src/quotacheck')
|
||||
subdir('src/random-seed')
|
||||
|
||||
@@ -35,6 +35,7 @@ enable systemd-networkd.service
|
||||
enable systemd-networkd-wait-online.service
|
||||
enable systemd-nsresourced.socket
|
||||
enable systemd-pstore.service
|
||||
enable systemd-ptybrokerd.socket
|
||||
enable systemd-report-basic.socket
|
||||
enable systemd-report-cgroup.socket
|
||||
enable systemd-report-diskhealth.socket
|
||||
|
||||
@@ -43,6 +43,7 @@
|
||||
#include "varlink-io.systemd.Network.Link.h"
|
||||
#include "varlink-io.systemd.PCRExtend.h"
|
||||
#include "varlink-io.systemd.PCRLock.h"
|
||||
#include "varlink-io.systemd.PTYBroker.h"
|
||||
#include "varlink-io.systemd.Repart.h"
|
||||
#include "varlink-io.systemd.Report.h"
|
||||
#include "varlink-io.systemd.Report.Signer.h"
|
||||
@@ -258,6 +259,7 @@ TEST(parse_format) {
|
||||
&vl_interface_io_systemd_Network_Link,
|
||||
&vl_interface_io_systemd_PCRExtend,
|
||||
&vl_interface_io_systemd_PCRLock,
|
||||
&vl_interface_io_systemd_PTYBroker,
|
||||
&vl_interface_io_systemd_Repart,
|
||||
&vl_interface_io_systemd_Report,
|
||||
&vl_interface_io_systemd_Report_Signer,
|
||||
|
||||
@@ -0,0 +1,92 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?> <!--*-nxml-*-->
|
||||
<!DOCTYPE policyconfig PUBLIC "-//freedesktop//DTD PolicyKit Policy Configuration 1.0//EN"
|
||||
"https://www.freedesktop.org/standards/PolicyKit/1/policyconfig.dtd">
|
||||
|
||||
<!--
|
||||
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.
|
||||
-->
|
||||
|
||||
<policyconfig>
|
||||
|
||||
<vendor>The systemd Project</vendor>
|
||||
<vendor_url>https://systemd.io</vendor_url>
|
||||
|
||||
<action id="io.systemd.PTYBroker.acquire-pty">
|
||||
<description gettext-domain="systemd">Acquire a pseudo-terminal</description>
|
||||
<message gettext-domain="systemd">Authentication is required to acquire a pseudo-terminal.</message>
|
||||
<defaults>
|
||||
<allow_any>auth_admin_keep</allow_any>
|
||||
<allow_inactive>auth_admin_keep</allow_inactive>
|
||||
<allow_active>auth_admin_keep</allow_active>
|
||||
</defaults>
|
||||
</action>
|
||||
|
||||
<action id="io.systemd.PTYBroker.acquire-pty-with-shell">
|
||||
<description gettext-domain="systemd">Acquire a pseudo-terminal with a shell</description>
|
||||
<message gettext-domain="systemd">Authentication is required to acquire a pseudo-terminal running '$(command_line)' as user '$(user)' as a transient service unit.</message>
|
||||
<defaults>
|
||||
<allow_any>auth_admin_keep</allow_any>
|
||||
<allow_inactive>auth_admin_keep</allow_inactive>
|
||||
<allow_active>auth_admin_keep</allow_active>
|
||||
</defaults>
|
||||
<annotate key="org.freedesktop.policykit.imply">io.systemd.PTYBroker.acquire-pty</annotate>
|
||||
</action>
|
||||
|
||||
<action id="io.systemd.PTYBroker.enroll-pty">
|
||||
<description gettext-domain="systemd">Enroll a pre-allocated pseudo-terminal</description>
|
||||
<message gettext-domain="systemd">Authentication is required to enroll a pre-allocated pseudo-terminal with the PTY broker.</message>
|
||||
<defaults>
|
||||
<allow_any>auth_admin_keep</allow_any>
|
||||
<allow_inactive>auth_admin_keep</allow_inactive>
|
||||
<allow_active>auth_admin_keep</allow_active>
|
||||
</defaults>
|
||||
</action>
|
||||
|
||||
<action id="io.systemd.PTYBroker.list-ptys">
|
||||
<description gettext-domain="systemd">List tracked pseudo-terminals</description>
|
||||
<message gettext-domain="systemd">Authentication is required to list tracked pseudo-terminals of the PTY broker.</message>
|
||||
<defaults>
|
||||
<allow_any>yes</allow_any>
|
||||
<allow_inactive>yes</allow_inactive>
|
||||
<allow_active>yes</allow_active>
|
||||
</defaults>
|
||||
</action>
|
||||
|
||||
<action id="io.systemd.PTYBroker.monitor-pty">
|
||||
<description gettext-domain="systemd">Monitor a pseudo-terminal</description>
|
||||
<message gettext-domain="systemd">Authentication is required to monitor a pseudo-terminal managed by the PTY broker.</message>
|
||||
<defaults>
|
||||
<allow_any>auth_admin_keep</allow_any>
|
||||
<allow_inactive>auth_admin_keep</allow_inactive>
|
||||
<allow_active>auth_admin_keep</allow_active>
|
||||
</defaults>
|
||||
</action>
|
||||
|
||||
<action id="io.systemd.PTYBroker.configure-pty">
|
||||
<description gettext-domain="systemd">Reconfigure a pseudo-terminal</description>
|
||||
<message gettext-domain="systemd">Authentication is required to reconfigure a pseudo-terminal managed by the PTY broker.</message>
|
||||
<defaults>
|
||||
<allow_any>auth_admin_keep</allow_any>
|
||||
<allow_inactive>auth_admin_keep</allow_inactive>
|
||||
<allow_active>auth_admin_keep</allow_active>
|
||||
</defaults>
|
||||
</action>
|
||||
|
||||
<action id="io.systemd.PTYBroker.hang-up-pty">
|
||||
<description gettext-domain="systemd">Hang up a pseudo-terminal</description>
|
||||
<message gettext-domain="systemd">Authentication is required to hang up a pseudo-terminal managed by the PTY broker.</message>
|
||||
<defaults>
|
||||
<allow_any>auth_admin_keep</allow_any>
|
||||
<allow_inactive>auth_admin_keep</allow_inactive>
|
||||
<allow_active>auth_admin_keep</allow_active>
|
||||
</defaults>
|
||||
</action>
|
||||
|
||||
</policyconfig>
|
||||
@@ -0,0 +1,23 @@
|
||||
# SPDX-License-Identifier: LGPL-2.1-or-later
|
||||
|
||||
executables += [
|
||||
libexec_template + {
|
||||
'name' : 'systemd-ptybrokerd',
|
||||
'sources' : files(
|
||||
'ptybroker.c',
|
||||
'ptybroker-monitor.c',
|
||||
'ptybroker-pty.c',
|
||||
'ptybroker-util.c',
|
||||
),
|
||||
},
|
||||
]
|
||||
|
||||
install_data('io.systemd.PTYBroker.policy',
|
||||
install_dir : polkitpolicydir)
|
||||
|
||||
# Shells invoked by the broker get their own PAM stack name, but by default it is the same as run0's
|
||||
if pamconfdir != 'no'
|
||||
install_symlink('systemd-ptybroker-shell',
|
||||
pointing_to : 'systemd-run0',
|
||||
install_dir : pamconfdir)
|
||||
endif
|
||||
@@ -0,0 +1,13 @@
|
||||
/* SPDX-License-Identifier: LGPL-2.1-or-later */
|
||||
#pragma once
|
||||
|
||||
/* IWYU pragma: always_keep */
|
||||
|
||||
#include "forward.h" /* IWYU pragma: export */
|
||||
|
||||
typedef enum BackendType BackendType;
|
||||
typedef enum EndOfLine EndOfLine;
|
||||
typedef enum FrontendType FrontendType;
|
||||
typedef struct Manager Manager;
|
||||
typedef struct PseudoTTY PseudoTTY;
|
||||
typedef struct PseudoTTYMonitor PseudoTTYMonitor;
|
||||
@@ -0,0 +1,363 @@
|
||||
/* SPDX-License-Identifier: LGPL-2.1-or-later */
|
||||
|
||||
#include <unistd.h>
|
||||
|
||||
#include "alloc-util.h"
|
||||
#include "errno-util.h"
|
||||
#include "fd-util.h"
|
||||
#include "fileio.h"
|
||||
#include "iovec-util.h"
|
||||
#include "log.h"
|
||||
#include "ptybroker.h"
|
||||
#include "ptybroker-monitor.h"
|
||||
#include "ptybroker-pty.h"
|
||||
|
||||
PseudoTTYMonitor* pseudo_tty_monitor_free(PseudoTTYMonitor *monitor) {
|
||||
if (!monitor)
|
||||
return NULL;
|
||||
|
||||
if (monitor->pty) {
|
||||
LIST_REMOVE(monitors, monitor->pty->monitors, monitor);
|
||||
monitor->pty->n_monitors--;
|
||||
}
|
||||
|
||||
sd_event_source_disable_unref(monitor->io_event_source);
|
||||
sd_event_source_disable_unref(monitor->disconnect_event_source);
|
||||
|
||||
if (monitor->link) {
|
||||
/* If the link is still around here the connection never completed its upgrade: close it, so
|
||||
* that the client doesn't hang on to a connection nobody will ever service. */
|
||||
sd_varlink_set_userdata(monitor->link, NULL);
|
||||
sd_varlink_close_unref(monitor->link);
|
||||
}
|
||||
|
||||
iovec_done(&monitor->buffer);
|
||||
|
||||
return mfree(monitor);
|
||||
}
|
||||
|
||||
void pseudo_tty_monitor_disconnect(PseudoTTYMonitor *monitor) {
|
||||
assert(monitor);
|
||||
|
||||
/* Frees the monitor because its connection went away, and then either hangs up the pty (if requested)
|
||||
* or re-evaluates the pty's event mask: this monitor's buffer might have been what throttled reading
|
||||
* from the pty. */
|
||||
|
||||
PseudoTTY *pty = monitor->pty;
|
||||
bool hang_up = monitor->hang_up_on_disconnect;
|
||||
|
||||
pseudo_tty_monitor_free(monitor);
|
||||
|
||||
if (!pty)
|
||||
return;
|
||||
|
||||
if (hang_up)
|
||||
(void) pseudo_tty_vhangup(pty);
|
||||
|
||||
(void) pseudo_tty_set_events(pty);
|
||||
}
|
||||
|
||||
static int on_disconnect_defer(sd_event_source *s, void *userdata) {
|
||||
PseudoTTYMonitor *monitor = ASSERT_PTR(userdata);
|
||||
|
||||
assert(s);
|
||||
|
||||
/* If the link is still set the connection upgrade never completed, but the connection is gone
|
||||
* nonetheless. Nobody will ever service the monitor hence, drop it. */
|
||||
if (!monitor->link)
|
||||
return 0;
|
||||
|
||||
log_debug("Monitor connection went away before its upgrade completed, dropping monitor.");
|
||||
pseudo_tty_monitor_disconnect(monitor);
|
||||
return 0;
|
||||
}
|
||||
|
||||
void pseudo_tty_monitor_on_link_disconnect(PseudoTTYMonitor *monitor) {
|
||||
int r;
|
||||
|
||||
assert(monitor);
|
||||
|
||||
/* Called whenever the Varlink connection of the monitor is disconnected. Note that this also happens
|
||||
* right before the upgrade callback is invoked, hence we can't free the monitor right-away. Instead,
|
||||
* check from a deferred event source whether the upgrade happened in the meantime. */
|
||||
|
||||
if (!monitor->link || !monitor->pty || monitor->disconnect_event_source)
|
||||
return;
|
||||
|
||||
r = sd_event_add_defer(monitor->pty->manager->event, &monitor->disconnect_event_source, on_disconnect_defer, monitor);
|
||||
if (r < 0) {
|
||||
log_warning_errno(r, "Failed to allocate deferred event source for monitor disconnect, dropping monitor right-away: %m");
|
||||
/* Make sure the upgrade callback doesn't reference the monitor anymore, then drop it */
|
||||
pseudo_tty_monitor_disconnect(monitor);
|
||||
return;
|
||||
}
|
||||
|
||||
(void) sd_event_source_set_description(monitor->disconnect_event_source, "monitor-disconnect");
|
||||
}
|
||||
|
||||
static int pseudo_tty_monitor_write(PseudoTTYMonitor *monitor, int fd) {
|
||||
assert(monitor);
|
||||
assert(fd >= 0);
|
||||
|
||||
if (monitor->buffer.iov_len <= 0)
|
||||
return 0;
|
||||
|
||||
ssize_t n = write(fd, monitor->buffer.iov_base, monitor->buffer.iov_len);
|
||||
if (n < 0) {
|
||||
if (ERRNO_IS_TRANSIENT(errno))
|
||||
return 0;
|
||||
|
||||
return log_error_errno(errno, "Failed to write to monitor socket: %m");
|
||||
}
|
||||
|
||||
assert_se(iovec_consume(&monitor->buffer, n));
|
||||
return n > 0;
|
||||
}
|
||||
|
||||
int pseudo_tty_monitor_flush(PseudoTTYMonitor *monitor) {
|
||||
assert(monitor);
|
||||
|
||||
/* Writes out as much of the buffered output as the connection takes right now, without blocking */
|
||||
|
||||
if (!monitor->io_event_source) /* The connection upgrade hasn't completed yet */
|
||||
return 0;
|
||||
|
||||
int fd = sd_event_source_get_io_fd(monitor->io_event_source);
|
||||
if (fd < 0)
|
||||
return fd;
|
||||
|
||||
return pseudo_tty_monitor_write(monitor, fd);
|
||||
}
|
||||
|
||||
static int pseudo_tty_monitor_push_output(PseudoTTYMonitor *monitor, const void *data, size_t n) {
|
||||
assert(monitor);
|
||||
assert(data || n == 0);
|
||||
|
||||
if (n == SIZE_MAX)
|
||||
n = strlen(data);
|
||||
if (n == 0)
|
||||
return 0;
|
||||
|
||||
size_t m;
|
||||
if (!ADD_SAFE(&m, monitor->buffer.iov_len, n))
|
||||
return -ENOMEM;
|
||||
|
||||
if (!greedy_realloc(&monitor->buffer.iov_base, m, 1))
|
||||
return -ENOMEM;
|
||||
|
||||
memcpy((uint8_t*) monitor->buffer.iov_base + monitor->buffer.iov_len, data, n);
|
||||
monitor->buffer.iov_len = m;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int pseudo_tty_monitor_push_pending(PseudoTTYMonitor *monitor) {
|
||||
assert(monitor);
|
||||
assert(monitor->pty);
|
||||
|
||||
/* If we are setting up a new monitor, let's immediately enqueue any incomplete lines still queued in the pty */
|
||||
|
||||
struct iovec v = monitor->pty->frontend_read_buffer;
|
||||
|
||||
/* Skip over initial newlines */
|
||||
for (;;) {
|
||||
if (!iovec_is_set(&v))
|
||||
return 0;
|
||||
|
||||
if (end_of_line_from_char(*(char*) v.iov_base) == EOL_NONE)
|
||||
break;
|
||||
|
||||
iovec_inc(&v, 1);
|
||||
}
|
||||
|
||||
return pseudo_tty_monitor_push_output(monitor, v.iov_base, v.iov_len);
|
||||
}
|
||||
|
||||
static int pseudo_tty_monitor_read(PseudoTTYMonitor *monitor, int fd) {
|
||||
assert(monitor);
|
||||
assert(fd >= 0);
|
||||
|
||||
if (monitor->pty->frontend_write_buffer.iov_len >= BUFFER_MAX)
|
||||
return 0;
|
||||
|
||||
size_t left = BUFFER_MAX - monitor->pty->frontend_write_buffer.iov_len;
|
||||
size_t add = MIN(left, LONG_LINE_MAX);
|
||||
|
||||
if (!greedy_realloc(&monitor->pty->frontend_write_buffer.iov_base, monitor->pty->frontend_write_buffer.iov_len + add, 1))
|
||||
return log_oom();
|
||||
|
||||
void *p = (uint8_t*) monitor->pty->frontend_write_buffer.iov_base + monitor->pty->frontend_write_buffer.iov_len;
|
||||
ssize_t n = read(fd, p, add);
|
||||
if (n < 0) {
|
||||
if (ERRNO_IS_TRANSIENT(errno))
|
||||
return 0;
|
||||
|
||||
return log_error_errno(errno, "Failed to read from monitor socket: %m");
|
||||
}
|
||||
if (n == 0) /* Propagate EOF as proper error. This is a normal lifecycle event, hence log about it
|
||||
* only at debug level. */
|
||||
return log_debug_errno(SYNTHETIC_ERRNO(ECONNRESET), "Monitor disconnected.");
|
||||
|
||||
assert((size_t) n <= add);
|
||||
monitor->pty->frontend_write_buffer.iov_len += n;
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
int pseudo_tty_monitor_set_events(PseudoTTYMonitor *monitor) {
|
||||
int r;
|
||||
|
||||
assert(monitor);
|
||||
|
||||
if (!monitor->io_event_source) /* The connection upgrade hasn't completed yet, nothing to watch so far */
|
||||
return 0;
|
||||
|
||||
uint32_t events = 0;
|
||||
if (monitor->buffer.iov_len > 0)
|
||||
events |= EPOLLOUT;
|
||||
if (monitor->pty->frontend_write_buffer.iov_len < BUFFER_MAX)
|
||||
events |= EPOLLIN;
|
||||
|
||||
r = sd_event_source_set_io_events(monitor->io_event_source, events);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to adjust I/O event mask for monitor socket: %m");
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int on_monitor_io(sd_event_source *s, int fd, uint32_t revents, void *userdata) {
|
||||
PseudoTTYMonitor *monitor = ASSERT_PTR(userdata);
|
||||
int r;
|
||||
|
||||
assert(s);
|
||||
assert(fd >= 0);
|
||||
|
||||
r = 0;
|
||||
RET_GATHER(r, pseudo_tty_monitor_write(monitor, fd));
|
||||
RET_GATHER(r, pseudo_tty_monitor_read(monitor, fd));
|
||||
if (r < 0)
|
||||
goto fail;
|
||||
|
||||
/* Handle hangups only after processing any data still queued. Note that we might not see EOF on
|
||||
* read() if we didn't read due to a full buffer, and epoll will keep reporting EPOLLHUP/EPOLLERR
|
||||
* even with an empty event mask, hence handle them explicitly. */
|
||||
if (revents & (EPOLLHUP|EPOLLERR)) {
|
||||
r = log_debug_errno(SYNTHETIC_ERRNO(ECONNRESET), "Monitor disconnected.");
|
||||
goto fail;
|
||||
}
|
||||
|
||||
/* NB: We don't call the monitor-specific pseudo_tty_monitor_set_events() call here, but the
|
||||
* pseudo_tty_set_events() call that applies to the whole pty. That's because having progressed here
|
||||
* might allow progress on the pty too. Note that pseudo_tty_set_events() will call back into
|
||||
* pseudo_tty_monitor_set_events() */
|
||||
r = pseudo_tty_set_events(monitor->pty);
|
||||
if (r < 0)
|
||||
goto fail;
|
||||
|
||||
return 0;
|
||||
|
||||
fail:
|
||||
pseudo_tty_monitor_disconnect(monitor);
|
||||
return r;
|
||||
}
|
||||
|
||||
static int on_upgrade(sd_varlink *vl, int _input_fd, int _output_fd, void *userdata) {
|
||||
/* The fds are donated to us, no matter what, hence take possession of them right-away */
|
||||
_cleanup_close_ int input_fd = TAKE_FD(_input_fd), output_fd = TAKE_FD(_output_fd);
|
||||
PseudoTTYMonitor *monitor = userdata;
|
||||
int r;
|
||||
|
||||
assert(vl);
|
||||
assert(input_fd >= 0);
|
||||
assert(output_fd >= 0);
|
||||
|
||||
/* The upgrade only completes once the client consumed the upgrade reply, which might take a while
|
||||
* (in particular if the client is slow to read a large track buffer). If the monitor went away in
|
||||
* the meantime (because its pty was hung up), nobody is left to take the connection: drop it. */
|
||||
if (!monitor)
|
||||
return log_debug_errno(SYNTHETIC_ERRNO(ESTALE), "Monitor went away before its connection upgrade completed, closing connection.");
|
||||
|
||||
r = same_fd(input_fd, output_fd);
|
||||
if (r < 0) {
|
||||
log_error_errno(r, "Failed to detect if input/output fds are the same: %m");
|
||||
goto fail;
|
||||
}
|
||||
if (r == 0) {
|
||||
r = log_error_errno(SYNTHETIC_ERRNO(EBADF), "Cannot operate with distinct input/output fds.");
|
||||
goto fail;
|
||||
}
|
||||
|
||||
r = fd_nonblock(input_fd, true);
|
||||
if (r < 0) {
|
||||
log_error_errno(r, "Failed to enable non-blocking mode on upgraded socket: %m");
|
||||
goto fail;
|
||||
}
|
||||
|
||||
assert(monitor->link == vl);
|
||||
monitor->link = sd_varlink_unref(monitor->link);
|
||||
|
||||
assert(!monitor->io_event_source);
|
||||
r = sd_event_add_io(monitor->pty->manager->event, &monitor->io_event_source, input_fd, /* events= */ 0, on_monitor_io, monitor);
|
||||
if (r < 0) {
|
||||
log_error_errno(r, "Failed to allocate monitor IO event: %m");
|
||||
goto fail;
|
||||
}
|
||||
|
||||
r = sd_event_source_set_io_fd_own(monitor->io_event_source, true);
|
||||
if (r < 0) {
|
||||
log_error_errno(r, "Failed to pass ownership of monitor fd to IO event: %m");
|
||||
goto fail;
|
||||
}
|
||||
|
||||
TAKE_FD(input_fd); /* ownership is now passed */
|
||||
|
||||
r = pseudo_tty_monitor_set_events(monitor);
|
||||
if (r < 0)
|
||||
goto fail;
|
||||
|
||||
return 0;
|
||||
|
||||
fail:
|
||||
pseudo_tty_monitor_disconnect(monitor);
|
||||
return r;
|
||||
}
|
||||
|
||||
int pseudo_tty_monitor_new(sd_varlink *link, PseudoTTYMonitor **ret) {
|
||||
int r;
|
||||
|
||||
assert(link);
|
||||
assert(ret);
|
||||
|
||||
_cleanup_(pseudo_tty_monitor_freep) PseudoTTYMonitor *monitor = new(PseudoTTYMonitor, 1);
|
||||
if (!monitor)
|
||||
return -ENOMEM;
|
||||
|
||||
*monitor = (PseudoTTYMonitor) {
|
||||
.link = sd_varlink_ref(link),
|
||||
};
|
||||
|
||||
r = sd_varlink_bind_upgrade(link, on_upgrade);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
*ret = TAKE_PTR(monitor);
|
||||
return 0;
|
||||
}
|
||||
|
||||
void pseudo_tty_monitor_link(PseudoTTYMonitor *monitor, PseudoTTY *pty) {
|
||||
assert(monitor);
|
||||
assert(pty);
|
||||
|
||||
LIST_PREPEND(monitors, pty->monitors, monitor);
|
||||
pty->n_monitors++;
|
||||
monitor->pty = pty;
|
||||
|
||||
sd_varlink_set_userdata(monitor->link, monitor);
|
||||
}
|
||||
|
||||
size_t pseudo_tty_monitor_space(PseudoTTYMonitor *monitor) {
|
||||
assert(monitor);
|
||||
|
||||
assert(monitor->buffer.iov_len <= BUFFER_MAX);
|
||||
return BUFFER_MAX - monitor->buffer.iov_len;
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
/* SPDX-License-Identifier: LGPL-2.1-or-later */
|
||||
#pragma once
|
||||
|
||||
#include "sd-event.h"
|
||||
#include "sd-varlink.h"
|
||||
|
||||
#include "list.h"
|
||||
#include "ptybroker-forward.h"
|
||||
|
||||
struct PseudoTTYMonitor {
|
||||
/* A structure kept for every monitor connection. A single pseudo TTY may have multiple monitors associated. */
|
||||
PseudoTTY *pty;
|
||||
sd_varlink *link;
|
||||
sd_event_source *io_event_source;
|
||||
sd_event_source *disconnect_event_source; /* Deferred check whether the upgrade ever completed */
|
||||
struct iovec buffer; /* Data still to be written to the monitor connection */
|
||||
bool hang_up_on_disconnect; /* If true, hang up the pty once this monitor is disconnected */
|
||||
LIST_FIELDS(PseudoTTYMonitor, monitors);
|
||||
};
|
||||
|
||||
PseudoTTYMonitor* pseudo_tty_monitor_free(PseudoTTYMonitor *monitor);
|
||||
DEFINE_TRIVIAL_CLEANUP_FUNC(PseudoTTYMonitor*, pseudo_tty_monitor_free);
|
||||
|
||||
void pseudo_tty_monitor_disconnect(PseudoTTYMonitor *monitor);
|
||||
void pseudo_tty_monitor_on_link_disconnect(PseudoTTYMonitor *monitor);
|
||||
|
||||
int pseudo_tty_monitor_new(sd_varlink *link, PseudoTTYMonitor **ret);
|
||||
void pseudo_tty_monitor_link(PseudoTTYMonitor *monitor, PseudoTTY *pty);
|
||||
|
||||
int pseudo_tty_monitor_push_pending(PseudoTTYMonitor *monitor);
|
||||
|
||||
int pseudo_tty_monitor_set_events(PseudoTTYMonitor *monitor);
|
||||
int pseudo_tty_monitor_flush(PseudoTTYMonitor *monitor);
|
||||
|
||||
size_t pseudo_tty_monitor_space(PseudoTTYMonitor *monitor);
|
||||
@@ -0,0 +1,900 @@
|
||||
/* SPDX-License-Identifier: LGPL-2.1-or-later */
|
||||
|
||||
#include <stdio.h>
|
||||
#include <sys/ioctl.h>
|
||||
#include <sys/stat.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "alloc-util.h"
|
||||
#include "errno-util.h"
|
||||
#include "event-util.h"
|
||||
#include "fd-util.h"
|
||||
#include "fileio.h"
|
||||
#include "format-util.h"
|
||||
#include "hashmap.h"
|
||||
#include "inotify-util.h"
|
||||
#include "iovec-util.h"
|
||||
#include "iovec-wrapper.h"
|
||||
#include "list.h"
|
||||
#include "log.h"
|
||||
#include "pidref.h"
|
||||
#include "process-util.h"
|
||||
#include "ptybroker-monitor.h"
|
||||
#include "ptybroker-pty.h"
|
||||
#include "ptybroker.h"
|
||||
#include "set.h"
|
||||
#include "string-table.h"
|
||||
#include "string-util.h"
|
||||
#include "terminal-util.h"
|
||||
#include "utf8.h"
|
||||
#include "varlink-util.h"
|
||||
|
||||
#define TRACK_BUFFER_LINES_MAX (4U * 1024U)
|
||||
#define TRACK_BUFFER_BYTES_MAX (4U * U64_MB)
|
||||
|
||||
/* The minimum number of bytes we read from the frontend after the backend hung up, before freeing the pty.
|
||||
*
|
||||
* Normally we bound each read cycle by what FIONREAD (aka TIOCINQ) reports as queued. However, that only
|
||||
* covers what is sitting in the line discipline's read buffer (N_TTY_BUF_SIZE, i.e. 4K), not what is still
|
||||
* queued in the tty's flip buffers (up to 64K) waiting to be pushed into it. A budget derived from FIONREAD
|
||||
* alone would hence lose the tail end of the output of a backend that wrote a burst of data right before
|
||||
* exiting. 128K comfortably covers both the flip buffers and the line discipline's read buffer.
|
||||
*
|
||||
* We do not drain without any limit though: EPOLLHUP only tells us that currently nobody has the backend
|
||||
* open. Anyone with access to the pts device node may open it again and keep writing to it, in which case
|
||||
* we'd never see EIO or EAGAIN, and our event loop (and with it all other ptys and clients we manage) would
|
||||
* be stalled indefinitely. If the backend is truly gone, the kernel flushes the flip buffers before
|
||||
* returning EIO, hence the drain typically ends long before this budget is exhausted anyway. */
|
||||
#define PSEUDO_TTY_HANGUP_DRAIN_MIN (128U * U64_KB)
|
||||
|
||||
/* We force a line out after LONG_LINE_MAX, hence make sure we can read as much into our buffers */
|
||||
assert_cc(LONG_LINE_MAX < BUFFER_MAX);
|
||||
|
||||
/* Enforce that the track buffer definitely fits at least one line (as read from the pty, i.e. before TAB
|
||||
* expansion, which might make it larger, see below) */
|
||||
assert_cc(LONG_LINE_MAX < TRACK_BUFFER_BYTES_MAX);
|
||||
|
||||
static int pseudo_tty_track_buffer_add_line(PseudoTTY *pty, char *line) {
|
||||
assert(pty);
|
||||
assert(line);
|
||||
|
||||
/* Appends the specified line to the track buffer ring, dropping the oldest line once the ring is at
|
||||
* capacity, and as many further old lines as necessary to stay below the byte budget. Consumes the
|
||||
* line on success. Returns -E2BIG (without consuming the line) if the line is too large to ever fit
|
||||
* into the track buffer. */
|
||||
|
||||
/* Lines are cut at LONG_LINE_MAX when read from the pty, but strip_tab_ansi() expands each TAB to
|
||||
* eight spaces, hence a line might have grown considerably since. Don't even try to store it if it
|
||||
* cannot fit. */
|
||||
size_t l = strlen(line);
|
||||
if (l >= TRACK_BUFFER_BYTES_MAX)
|
||||
return -E2BIG;
|
||||
|
||||
size_t allocate = MIN(pty->track_buffer_allocated_lines + 1, TRACK_BUFFER_LINES_MAX);
|
||||
if (allocate > pty->track_buffer_allocated_lines) {
|
||||
if (!GREEDY_REALLOC0(pty->track_buffer, allocate))
|
||||
return -ENOMEM;
|
||||
|
||||
pty->track_buffer_allocated_lines = allocate;
|
||||
}
|
||||
|
||||
pty->track_buffer_next_line %= pty->track_buffer_allocated_lines;
|
||||
|
||||
/* Delete old lines, starting with the oldest one (which is the slot we are about to overwrite,
|
||||
* hence always drop it), continuing with the next oldest ones for as long as the buffer would
|
||||
* otherwise exceed the byte limit. */
|
||||
for (size_t i = 0; i < pty->track_buffer_allocated_lines; i++) {
|
||||
if (i > 0 && pty->track_buffer_n_bytes + l < TRACK_BUFFER_BYTES_MAX)
|
||||
break;
|
||||
|
||||
char **slot = pty->track_buffer + (pty->track_buffer_next_line + i) % pty->track_buffer_allocated_lines;
|
||||
|
||||
/* Free unless already NULLed out on an earlier iteration */
|
||||
if (*slot) {
|
||||
size_t k = strlen(*slot);
|
||||
assert(pty->track_buffer_n_bytes >= k);
|
||||
pty->track_buffer_n_bytes -= k;
|
||||
*slot = mfree(*slot);
|
||||
}
|
||||
}
|
||||
|
||||
/* insert new line */
|
||||
assert(pty->track_buffer_n_bytes + l < TRACK_BUFFER_BYTES_MAX);
|
||||
pty->track_buffer_n_bytes += l;
|
||||
pty->track_buffer[pty->track_buffer_next_line] = line;
|
||||
pty->track_buffer_next_line++;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void pseudo_tty_fail_acquire(PseudoTTY *pty, const char *error_id, sd_json_variant *parameters) {
|
||||
int r;
|
||||
|
||||
assert(pty);
|
||||
|
||||
/* Fails the AcquirePty() call still waiting for its reply, if there is one. If no error ID is
|
||||
* specified a generic ECONNRESET error is returned. */
|
||||
|
||||
if (!pty->acquire_link)
|
||||
return;
|
||||
|
||||
_cleanup_(sd_varlink_unrefp) sd_varlink *client = TAKE_PTR(pty->acquire_link);
|
||||
pty->acquire_reply = sd_json_variant_unref(pty->acquire_reply);
|
||||
|
||||
/* If a monitor was requested it shares the connection with the AcquirePty() call, and would close
|
||||
* it when destroyed, before the error reply is flushed. Hence detach the connection from the monitor
|
||||
* first, and dispose of the monitor right-away, it will never get its connection upgraded. */
|
||||
LIST_FOREACH(monitors, monitor, pty->monitors) {
|
||||
if (monitor->link != client)
|
||||
continue;
|
||||
|
||||
/* The error reply below returns the connection to idle state, i.e. the client may issue
|
||||
* further calls on it, hence hand it back to the Manager */
|
||||
sd_varlink_set_userdata(monitor->link, pty->manager);
|
||||
monitor->link = sd_varlink_unref(monitor->link);
|
||||
pseudo_tty_monitor_free(monitor);
|
||||
}
|
||||
|
||||
if (error_id)
|
||||
r = sd_varlink_error(client, error_id, parameters);
|
||||
else
|
||||
r = sd_varlink_error_errno(client, -ECONNRESET);
|
||||
if (r < 0)
|
||||
log_debug_errno(r, "Failed to send error reply to client, ignoring: %m");
|
||||
}
|
||||
|
||||
PseudoTTY* pseudo_tty_free(PseudoTTY *pty) {
|
||||
int r;
|
||||
|
||||
if (!pty)
|
||||
return NULL;
|
||||
|
||||
/* Make sure no client is left waiting for a reply that would never come */
|
||||
pseudo_tty_fail_acquire(pty, /* error_id= */ NULL, /* parameters= */ NULL);
|
||||
|
||||
/* A pending hang-up is complete once the pty is gone */
|
||||
r = varlink_many_reply(pty->vhangup_links, /* parameters= */ NULL);
|
||||
if (r < 0)
|
||||
log_debug_errno(r, "Failed to issue hangup method call replies, ignoring: %m");
|
||||
|
||||
while (pty->monitors)
|
||||
pseudo_tty_monitor_free(pty->monitors);
|
||||
|
||||
if (pty->in_free_queue) {
|
||||
assert(pty->manager);
|
||||
LIST_REMOVE(free_queue, pty->manager->ptys_free_queue, pty);
|
||||
}
|
||||
|
||||
if (pty->manager) {
|
||||
assert(pty->name);
|
||||
hashmap_remove(pty->manager->ptys, pty->name);
|
||||
}
|
||||
|
||||
pty->name = mfree(pty->name);
|
||||
pty->description = mfree(pty->description);
|
||||
pty->tag = mfree(pty->tag);
|
||||
|
||||
terminal_settings_done(&pty->terminal_settings);
|
||||
|
||||
sd_event_source_disable_unref(pty->io_event_source);
|
||||
|
||||
pty->frontend_fd = safe_close(pty->frontend_fd);
|
||||
pty->backend_fd = safe_close(pty->backend_fd);
|
||||
pty->pin_fd = safe_close(pty->pin_fd);
|
||||
|
||||
free(pty->backend_path);
|
||||
free(pty->unit);
|
||||
|
||||
sd_varlink_close_unref(pty->start_shell_link);
|
||||
|
||||
sd_varlink_unref(pty->acquire_link);
|
||||
sd_json_variant_unref(pty->acquire_reply);
|
||||
|
||||
free_many_charp(pty->track_buffer, pty->track_buffer_allocated_lines);
|
||||
free(pty->track_buffer);
|
||||
|
||||
iovec_done(&pty->frontend_write_buffer);
|
||||
iovec_done(&pty->frontend_read_buffer);
|
||||
|
||||
sd_event_source_disable_unref(pty->backend_inotify_event_source);
|
||||
|
||||
set_free(pty->vhangup_links);
|
||||
sd_event_source_disable_unref(pty->vhangup_event_source);
|
||||
|
||||
pidref_done(&pty->owner_pidref);
|
||||
|
||||
return mfree(pty);
|
||||
}
|
||||
|
||||
static int pseudo_tty_write(PseudoTTY *pty) {
|
||||
assert(pty);
|
||||
assert(pty->frontend_fd >= 0);
|
||||
|
||||
if (pty->frontend_write_buffer.iov_len <= 0)
|
||||
return 0;
|
||||
|
||||
ssize_t n = write(pty->frontend_fd, pty->frontend_write_buffer.iov_base, pty->frontend_write_buffer.iov_len);
|
||||
if (n < 0) {
|
||||
if (ERRNO_IS_TRANSIENT(errno))
|
||||
return 0;
|
||||
if (errno == EIO && pty->ignore_hangup) /* Backend currently hung up, keep the data queued */
|
||||
return 0;
|
||||
|
||||
return log_error_errno(errno, "Failed to write to pseudo TTY: %m");
|
||||
}
|
||||
|
||||
assert_se(iovec_consume(&pty->frontend_write_buffer, n));
|
||||
return 1;
|
||||
}
|
||||
|
||||
static size_t pseudo_tty_read_space(PseudoTTY *pty) {
|
||||
assert(pty);
|
||||
|
||||
/* Determines how much we can read. That's the minimum of our own scan buffer, and each monitor's
|
||||
* buffer */
|
||||
assert(pty->frontend_read_buffer.iov_len <= BUFFER_MAX);
|
||||
size_t left = BUFFER_MAX - pty->frontend_read_buffer.iov_len;
|
||||
LIST_FOREACH(monitors, monitor, pty->monitors) {
|
||||
if (left <= 0)
|
||||
break;
|
||||
|
||||
left = MIN(left, pseudo_tty_monitor_space(monitor));
|
||||
}
|
||||
|
||||
return left;
|
||||
}
|
||||
|
||||
static int pseudo_tty_read(PseudoTTY *pty) {
|
||||
assert(pty);
|
||||
assert(pty->frontend_fd >= 0);
|
||||
|
||||
/* Returns the number of bytes read, or 0 if there's nothing to read right now, or no space to read
|
||||
* it into. */
|
||||
|
||||
size_t left = pseudo_tty_read_space(pty);
|
||||
if (left <= 0)
|
||||
return 0;
|
||||
/* Cap a single read at LONG_LINE_MAX so we don't allocate/read huge chunks at once, but never read
|
||||
* more than the smallest consumer's headroom, so that BUFFER_MAX stays a hard cap for every buffer. */
|
||||
size_t add = MIN(left, (size_t) LONG_LINE_MAX);
|
||||
|
||||
/* Now extend the buffers according to our determination */
|
||||
if (!greedy_realloc(&pty->frontend_read_buffer.iov_base, pty->frontend_read_buffer.iov_len + add, 1))
|
||||
return log_oom();
|
||||
LIST_FOREACH(monitors, monitor, pty->monitors)
|
||||
if (!greedy_realloc(&monitor->buffer.iov_base, monitor->buffer.iov_len + add, 1))
|
||||
return log_oom();
|
||||
|
||||
/* And then read into our scan buffer first */
|
||||
void *p = (uint8_t*) pty->frontend_read_buffer.iov_base + pty->frontend_read_buffer.iov_len;
|
||||
ssize_t n = read(pty->frontend_fd, p, add);
|
||||
if (n < 0) {
|
||||
if (ERRNO_IS_TRANSIENT(errno))
|
||||
return 0;
|
||||
if (errno == EIO && pty->ignore_hangup) {
|
||||
log_debug("Backend of pseudo TTY '%s' hung up, waiting for it to be reopened.", strna(pty->name));
|
||||
return 0;
|
||||
}
|
||||
if (errno == EIO) /* The kernel reports EIO on the frontend once the backend has been hung up
|
||||
* or closed. This is a normal lifecycle event, hence log about it only at
|
||||
* debug level. */
|
||||
return log_debug_errno(SYNTHETIC_ERRNO(ECONNRESET), "Hangup on pseudo TTY.");
|
||||
|
||||
return log_error_errno(errno, "Failed to read from pseudo TTY: %m");
|
||||
}
|
||||
if (n == 0 && pty->ignore_hangup)
|
||||
return 0;
|
||||
if (n == 0) /* Propagate EOF as proper error. This is a normal lifecycle event, hence log about it
|
||||
* only at debug level. */
|
||||
return log_debug_errno(SYNTHETIC_ERRNO(ECONNRESET), "Hangup on pseudo TTY.");
|
||||
|
||||
assert((size_t) n <= add);
|
||||
pty->frontend_read_buffer.iov_len += n;
|
||||
|
||||
/* Copy the data we received to all monitors */
|
||||
LIST_FOREACH(monitors, m, pty->monitors) {
|
||||
memcpy((uint8_t*) m->buffer.iov_base + m->buffer.iov_len, p, n);
|
||||
m->buffer.iov_len += n;
|
||||
}
|
||||
|
||||
return (int) n;
|
||||
}
|
||||
|
||||
static int pseudo_tty_set_events_full(PseudoTTY *pty, bool rearm) {
|
||||
int r;
|
||||
|
||||
assert(pty);
|
||||
|
||||
uint32_t events = 0;
|
||||
if (pty->frontend_write_buffer.iov_len > 0)
|
||||
events |= EPOLLOUT;
|
||||
|
||||
bool readable = pty->frontend_read_buffer.iov_len < BUFFER_MAX;
|
||||
LIST_FOREACH(monitors, monitor, pty->monitors) {
|
||||
readable = readable && pseudo_tty_monitor_space(monitor) > 0;
|
||||
|
||||
/* Also refresh the monitors, since they read directly into our write buffer */
|
||||
r = pseudo_tty_monitor_set_events(monitor);
|
||||
if (r < 0)
|
||||
return r;
|
||||
}
|
||||
|
||||
if (readable)
|
||||
events |= EPOLLIN;
|
||||
|
||||
if (!pty->io_event_source) /* Might already be gone, e.g. while a vhangup() is in progress */
|
||||
return 0;
|
||||
|
||||
/* We watch the frontend edge-triggered, since in ignore-hangup mode EPOLLHUP would otherwise be
|
||||
* reported continuously while the backend is closed. sd-event re-registers edge-triggered event
|
||||
* sources on every call, and the kernel then immediately reports any pending EPOLLHUP again, hence
|
||||
* only re-arm if the mask actually changed, or if we stopped reading before hitting EAGAIN, and thus
|
||||
* need to be woken up again for the remaining data. */
|
||||
events |= EPOLLET;
|
||||
if (!rearm || !FLAGS_SET(events, EPOLLIN)) {
|
||||
uint32_t current;
|
||||
r = sd_event_source_get_io_events(pty->io_event_source, ¤t);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to get I/O event mask for pseudo TTY: %m");
|
||||
if (current == events)
|
||||
return 0;
|
||||
}
|
||||
|
||||
r = sd_event_source_set_io_events(pty->io_event_source, events);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to adjust I/O event mask for pseudo TTY: %m");
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int pseudo_tty_set_events(PseudoTTY *pty) {
|
||||
return pseudo_tty_set_events_full(pty, /* rearm= */ false);
|
||||
}
|
||||
|
||||
static int pseudo_tty_log_line(PseudoTTY *pty, const char *line) {
|
||||
assert(pty);
|
||||
assert(line);
|
||||
|
||||
if (pty->frontend_type != FRONTEND_LOG)
|
||||
return 0;
|
||||
|
||||
_cleanup_free_ char *j = NULL;
|
||||
if (pty->tag) {
|
||||
j = strjoin(pty->tag, ": ", line);
|
||||
if (!j)
|
||||
return log_oom();
|
||||
|
||||
line = j;
|
||||
}
|
||||
|
||||
/* Only add the fields that are actually set: an empty item would be serialized as a bare newline,
|
||||
* which the native journal protocol interprets as entry separator. */
|
||||
_cleanup_(iovw_done_free) struct iovec_wrapper iovw = {};
|
||||
if (iovw_put_string_field(&iovw, "MESSAGE=", line) < 0 ||
|
||||
iovw_put_string_field(&iovw, "SYSLOG_IDENTIFIER=", pty->tag ?: pty->name) < 0 ||
|
||||
iovw_put_string_field(&iovw, "PTY=", pty->name) < 0 ||
|
||||
iovw_put_string_fieldf(&iovw, "PTY_OWNER_PID=", PID_FMT, pty->owner_pidref.pid) < 0 ||
|
||||
iovw_put_string_fieldf(&iovw, "PTY_OWNER_UID=", UID_FMT, pty->owner_uid) < 0 ||
|
||||
iovw_put_string_fieldf(&iovw, "PTY_OWNER_GID=", GID_FMT, pty->owner_gid) < 0)
|
||||
return log_oom();
|
||||
|
||||
/* The pidfd inode number is only available on kernel 6.9+, hence drop the field if we couldn't
|
||||
* acquire it. */
|
||||
if (pty->owner_pidref.fd_id > 0 &&
|
||||
iovw_put_string_fieldf(&iovw, "PTY_OWNER_PIDFDID=", "%" PRIu64, pty->owner_pidref.fd_id) < 0)
|
||||
return log_oom();
|
||||
|
||||
/* If we started a shell unit on the pty, associate the output with it, so that it shows up in
|
||||
* 'journalctl -u' for the unit */
|
||||
if (pty->unit &&
|
||||
iovw_put_string_field(&iovw,
|
||||
pty->manager && pty->manager->scope == RUNTIME_SCOPE_USER ? "OBJECT_SYSTEMD_USER_UNIT=" : "OBJECT_SYSTEMD_UNIT=",
|
||||
pty->unit) < 0)
|
||||
return log_oom();
|
||||
|
||||
log_struct_iovec(LOG_INFO, iovw.iovec, iovw.count);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int pseudo_tty_dispatch_line(PseudoTTY *pty, const char *p, size_t n) {
|
||||
int r;
|
||||
|
||||
assert(pty);
|
||||
|
||||
_cleanup_free_ char *line = memdup_suffix0(p, n);
|
||||
if (!line)
|
||||
return log_oom();
|
||||
|
||||
if (!strip_tab_ansi(&line, /* _isz= */ NULL, /* highlight= */ NULL))
|
||||
return log_oom();
|
||||
|
||||
/* Suppress empty lines */
|
||||
if (isempty(line))
|
||||
return 0;
|
||||
|
||||
/* The track buffer is returned as JSON strings, which must be valid UTF-8. Hence replace invalid
|
||||
* sequences (i.e. binary output, or a multi-byte character we split at LONG_LINE_MAX) */
|
||||
if (!utf8_is_valid(line)) {
|
||||
char *escaped = utf8_escape_invalid(line);
|
||||
if (!escaped)
|
||||
return log_oom();
|
||||
|
||||
free_and_replace(line, escaped);
|
||||
}
|
||||
|
||||
/* If logging is enabled, log to the journal */
|
||||
r = pseudo_tty_log_line(pty, line);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
/* Append it to the track buffer */
|
||||
r = pseudo_tty_track_buffer_add_line(pty, line);
|
||||
if (r == -E2BIG) {
|
||||
log_debug("Line of pseudo TTY '%s' too long for the track buffer, not tracking it.", strna(pty->name));
|
||||
return 0;
|
||||
}
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to add line to track buffer: %m");
|
||||
|
||||
TAKE_PTR(line);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int pseudo_tty_process_line(PseudoTTY *pty, bool force_flush) {
|
||||
int r;
|
||||
|
||||
assert(pty);
|
||||
|
||||
char *begin = NULL, *e = pty->frontend_read_buffer.iov_base;
|
||||
size_t l = pty->frontend_read_buffer.iov_len;
|
||||
EndOfLine eol_mask = pty->eol_mask;
|
||||
|
||||
while (l > 0) {
|
||||
size_t n_drop_extra;
|
||||
EndOfLine eol_found = end_of_line_from_char(*e);
|
||||
if (eol_found == EOL_NONE) {
|
||||
/* A regular character */
|
||||
if (!begin)
|
||||
begin = e;
|
||||
|
||||
/* Check if this is maybe the last character in the buffer, and a flush is forced. If
|
||||
* so, this is also an EOL marker. Also, imply a forced flush if we hit more than
|
||||
* LONG_LINE_MAX characters */
|
||||
|
||||
if (e - begin >= (ptrdiff_t) LONG_LINE_MAX)
|
||||
eol_found |= EOL_TOO_LONG;
|
||||
if (force_flush && l <= 1)
|
||||
eol_found |= EOL_FORCE_FLUSH;
|
||||
|
||||
if (eol_found == EOL_NONE) {
|
||||
/* This is just a regular character. Yay. Let's now look for all EOL types again */
|
||||
eol_mask = _EOL_MASK_ALL;
|
||||
e++;
|
||||
l--;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (FLAGS_SET(eol_found, EOL_FORCE_FLUSH)) {
|
||||
/* This is a forced flush: include the final character in the line and consume
|
||||
* it, so that we are guaranteed to make progress and terminate. */
|
||||
e++;
|
||||
l--;
|
||||
}
|
||||
/* Otherwise this is an overly long line: break before the current character, it shall
|
||||
* start the next line. */
|
||||
|
||||
n_drop_extra = 0; /* There's no actual EOL character to eat up */
|
||||
} else
|
||||
n_drop_extra = 1;
|
||||
|
||||
/* Check if this is an EOL marker we are looking for */
|
||||
if (FLAGS_SET(eol_mask, eol_found)) {
|
||||
/* We hit a new EOL marker. Let's generate a line from this */
|
||||
if (begin)
|
||||
r = pseudo_tty_dispatch_line(pty, begin, e - begin);
|
||||
else
|
||||
r = pseudo_tty_dispatch_line(pty, /* p= */ NULL, /* n= */ 0);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
/* Let's drop what we processed from the buffer */
|
||||
assert_se(iovec_consume(&pty->frontend_read_buffer, (e - (char*) pty->frontend_read_buffer.iov_base) + n_drop_extra));
|
||||
|
||||
/* Restart with an empty line indicator. */
|
||||
begin = NULL;
|
||||
e = pty->frontend_read_buffer.iov_base;
|
||||
l = pty->frontend_read_buffer.iov_len;
|
||||
|
||||
/* None of the other EOL types shall be recognized as line breaks until we hit another one of the one we just hit. */
|
||||
pty->eol_mask = eol_mask = (eol_found & (EOL_TOO_LONG|EOL_FORCE_FLUSH)) != 0 ? _EOL_MASK_ALL : (eol_found|EOL_TOO_LONG|EOL_FORCE_FLUSH);
|
||||
continue;
|
||||
}
|
||||
|
||||
/* If we hit this EOL marker, but ignored it because it was used in conjunction with another
|
||||
* one, then do honour it if it shows up the next time. */
|
||||
eol_mask |= eol_found;
|
||||
|
||||
e++;
|
||||
l--;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void pseudo_tty_flush_monitors(PseudoTTY *pty) {
|
||||
assert(pty);
|
||||
|
||||
LIST_FOREACH(monitors, monitor, pty->monitors)
|
||||
(void) pseudo_tty_monitor_flush(monitor);
|
||||
}
|
||||
|
||||
static int pseudo_tty_read_queued(PseudoTTY *pty, size_t min_budget) {
|
||||
int r;
|
||||
|
||||
assert(pty);
|
||||
assert(pty->frontend_fd >= 0);
|
||||
|
||||
/* Reads whatever is currently queued in the kernel and passes it on to the log, the track buffer and
|
||||
* the monitors – the latter on a best-effort basis, i.e. only as much as their connections take
|
||||
* without blocking. We read no more than (twice) what was queued when we started, but at least
|
||||
* min_budget bytes, so that a backend that keeps writing cannot stall our event loop. Returns 0 if we
|
||||
* hit EAGAIN (or EIO), > 0 if we stopped early, i.e. if there might be more left to read. Note that
|
||||
* FIONREAD undercounts, see PSEUDO_TTY_HANGUP_DRAIN_MIN for details. */
|
||||
|
||||
int queued = 0;
|
||||
if (ioctl(pty->frontend_fd, FIONREAD, &queued) < 0)
|
||||
log_debug_errno(errno, "Failed to query number of queued bytes on pseudo TTY, ignoring: %m");
|
||||
|
||||
/* Double the value, to leave some leeway */
|
||||
size_t budget = MAX((size_t) MAX(queued, 0) * 2, min_budget), total = 0;
|
||||
|
||||
for (;;) {
|
||||
r = pseudo_tty_process_line(pty, /* force_flush= */ false);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
pseudo_tty_flush_monitors(pty);
|
||||
|
||||
if (pseudo_tty_read_space(pty) <= 0)
|
||||
return 1;
|
||||
|
||||
r = pseudo_tty_read(pty);
|
||||
if (r <= 0)
|
||||
return r;
|
||||
|
||||
total += r;
|
||||
if (total >= budget)
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
static int pseudo_tty_process_hangup(PseudoTTY *pty, uint32_t revents) {
|
||||
assert(pty);
|
||||
|
||||
if (pty->frontend_fd < 0)
|
||||
return -ECONNRESET;
|
||||
|
||||
/* The backend might be reopened later, hence keep the pty around */
|
||||
if (pty->ignore_hangup)
|
||||
return 0;
|
||||
|
||||
if (FLAGS_SET(revents, EPOLLHUP)) {
|
||||
/* Before giving up on the pty, read whatever output is still queued in the kernel, with a
|
||||
* minimum budget that covers the flip buffers too (see PSEUDO_TTY_HANGUP_DRAIN_MIN). */
|
||||
(void) pseudo_tty_read_queued(pty, PSEUDO_TTY_HANGUP_DRAIN_MIN);
|
||||
return -ECONNRESET;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void pseudo_tty_add_to_free_queue(PseudoTTY *pty) {
|
||||
assert(pty);
|
||||
|
||||
if (pty->in_free_queue)
|
||||
return;
|
||||
|
||||
log_debug("Queuing PTY '%s' for removal.", strna(pty->name));
|
||||
|
||||
LIST_PREPEND(free_queue, pty->manager->ptys_free_queue, pty);
|
||||
pty->in_free_queue = true;
|
||||
|
||||
/* Trigger the deferred drain of the free queue. We don't free right here since we may be called from
|
||||
* within one of this PTY's own I/O callbacks. */
|
||||
(void) sd_event_source_set_enabled(pty->manager->ptys_free_queue_event_source, SD_EVENT_ONESHOT);
|
||||
}
|
||||
|
||||
static int io_callback(sd_event_source *s, int fd, uint32_t revents, void *userdata) {
|
||||
PseudoTTY *pty = ASSERT_PTR(userdata);
|
||||
int r;
|
||||
|
||||
assert(s);
|
||||
|
||||
/* We watch the frontend edge-triggered, hence we have to write and read until EAGAIN, since we won't
|
||||
* be woken up again otherwise. If we stop reading early, explicitly re-arm the event source below. */
|
||||
int k;
|
||||
do
|
||||
k = pseudo_tty_write(pty);
|
||||
while (k > 0);
|
||||
r = k;
|
||||
|
||||
k = pseudo_tty_read_queued(pty, /* min_budget= */ 0);
|
||||
bool rearm = k > 0;
|
||||
RET_GATHER(r, k);
|
||||
|
||||
RET_GATHER(r, pseudo_tty_process_hangup(pty, revents));
|
||||
RET_GATHER(r, pseudo_tty_process_line(pty, r < 0));
|
||||
if (r >= 0)
|
||||
r = pseudo_tty_set_events_full(pty, rearm);
|
||||
if (r < 0) {
|
||||
pseudo_tty_add_to_free_queue(pty);
|
||||
|
||||
if (pty->io_event_source)
|
||||
(void) sd_event_source_set_io_events(pty->io_event_source, 0);
|
||||
|
||||
return r;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int pseudo_tty_new(PseudoTTY **ret) {
|
||||
assert(ret);
|
||||
|
||||
_cleanup_(pseudo_tty_freep) PseudoTTY *pty = new(PseudoTTY, 1);
|
||||
if (!pty)
|
||||
return -ENOMEM;
|
||||
|
||||
*pty = (PseudoTTY) {
|
||||
.frontend_type = _FRONTEND_TYPE_INVALID,
|
||||
.backend_type = _BACKEND_TYPE_INVALID,
|
||||
.frontend_fd = -EBADF,
|
||||
.backend_fd = -EBADF,
|
||||
.pin_fd = -EBADF,
|
||||
.eol_mask = _EOL_MASK_ALL,
|
||||
.owner_pidref = PIDREF_NULL,
|
||||
.owner_uid = UID_INVALID,
|
||||
.owner_gid = GID_INVALID,
|
||||
};
|
||||
|
||||
*ret = TAKE_PTR(pty);
|
||||
return 0;
|
||||
}
|
||||
|
||||
DEFINE_PRIVATE_HASH_OPS_WITH_VALUE_DESTRUCTOR(
|
||||
pseudo_tty_hashops,
|
||||
char,
|
||||
string_hash_func,
|
||||
string_compare_func,
|
||||
PseudoTTY,
|
||||
pseudo_tty_free);
|
||||
|
||||
int pseudo_tty_link(PseudoTTY *pty, Manager *m) {
|
||||
int r;
|
||||
|
||||
assert(pty);
|
||||
assert(m);
|
||||
assert(pty->name);
|
||||
|
||||
r = hashmap_ensure_put(&m->ptys, &pseudo_tty_hashops, pty->name, pty);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
pty->manager = m;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int pseudo_tty_watch_frontend_fd(PseudoTTY *pty, sd_event *event) {
|
||||
int r;
|
||||
assert(pty);
|
||||
|
||||
if (pty->frontend_fd < 0)
|
||||
return 0;
|
||||
|
||||
r = fd_nonblock(pty->frontend_fd, true);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to mark pty fd is non-blocking: %m");
|
||||
|
||||
r = sd_event_add_io(event, &pty->io_event_source, pty->frontend_fd, /* events= */ 0, io_callback, pty);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to allocate IO event source: %m");
|
||||
|
||||
r = pseudo_tty_set_events(pty);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to set IO events to watch for: %m");
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int backend_node_inotify_callback(sd_event_source *s, const struct inotify_event *event, void *userdata) {
|
||||
PseudoTTY *pty = ASSERT_PTR(userdata);
|
||||
|
||||
assert(s);
|
||||
|
||||
/* The backend ('slave') device node was removed from /dev/pts/, which the kernel does once the last
|
||||
* frontend ('master') reference is gone, i.e. the PTY has been destroyed. This is our hang-up signal
|
||||
* for the FRONTEND_TAKE case: there we handed the frontend fd to the client and closed our own copy,
|
||||
* so there is no fd left on which we'd see EPOLLHUP. Tear the PTY object down, just like io_callback()
|
||||
* does when it sees a hangup on a frontend fd we do keep.
|
||||
*
|
||||
* Verify the node is actually gone before acting. */
|
||||
|
||||
struct stat st;
|
||||
if (fstat(pty->pin_fd, &st) < 0)
|
||||
log_debug_errno(errno, "Failed to fstat() backend inode, assuming pty is dead: %m");
|
||||
else if (st.st_nlink > 0) {
|
||||
log_debug("Got IN_DELETE_SELF event, but inode is still there, ignoring.");
|
||||
return 0;
|
||||
}
|
||||
|
||||
pty->backend_inotify_event_source = sd_event_source_disable_unref(pty->backend_inotify_event_source);
|
||||
|
||||
/* If a vhangup is queued, then let the child process exit cause the clean-up */
|
||||
if (pty->vhangup_event_source)
|
||||
return 0;
|
||||
|
||||
pseudo_tty_add_to_free_queue(pty);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int pseudo_tty_watch_backend_node(PseudoTTY *pty, sd_event *event) {
|
||||
int r;
|
||||
|
||||
assert(pty);
|
||||
assert(event);
|
||||
|
||||
if (pty->pin_fd < 0)
|
||||
return 0;
|
||||
|
||||
/* Watch the backend device node for removal via the pinning O_PATH fd we keep around. */
|
||||
r = sd_event_add_inotify_fd(event, &pty->backend_inotify_event_source, pty->pin_fd, IN_DELETE_SELF, backend_node_inotify_callback, pty);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to watch PTY backend node for removal: %m");
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int pseudo_tty_track_buffer_to_json(PseudoTTY *pty, size_t n_lines, sd_json_variant **ret) {
|
||||
int r;
|
||||
|
||||
assert(pty);
|
||||
assert(ret);
|
||||
|
||||
size_t n = MIN(n_lines, pty->track_buffer_allocated_lines);
|
||||
if (n <= 0) {
|
||||
*ret = NULL;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* NB: new0() so that the array is NUL terminated even if every slot in the window turns out to
|
||||
* be empty and the loop below never writes anything */
|
||||
_cleanup_free_ const char **array = new0(const char*, n + 1);
|
||||
if (!array)
|
||||
return -ENOMEM;
|
||||
|
||||
size_t start = pty->track_buffer_next_line + pty->track_buffer_allocated_lines - n;
|
||||
|
||||
/* track_buffer_next_line points at the oldest line of the (always fully populated) ring; return the
|
||||
* most recent n lines, in the order they were logged */
|
||||
for (size_t i = 0, j = 0; i < n; i++) {
|
||||
char *line = pty->track_buffer[(start + i) % pty->track_buffer_allocated_lines];
|
||||
if (!line) /* when the buffer size is hit we might NULL out some older lines, skip over them
|
||||
* here */
|
||||
continue;
|
||||
|
||||
array[j++] = line;
|
||||
array[j] = NULL;
|
||||
}
|
||||
|
||||
_cleanup_(sd_json_variant_unrefp) sd_json_variant *l = NULL;
|
||||
r = sd_json_variant_new_array_strv(&l, (char**) array);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
*ret = TAKE_PTR(l);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int vhangup_callback(sd_event_source *s, const siginfo_t *si, void *userdata) {
|
||||
PseudoTTY *pty = ASSERT_PTR(userdata);
|
||||
int r;
|
||||
|
||||
r = varlink_many_reply(pty->vhangup_links, NULL);
|
||||
if (r < 0)
|
||||
log_debug_errno(r, "Failed to issue hangup method call replies, ignoring: %m");
|
||||
|
||||
/* These calls are answered now, make sure we don't answer them again when the pty is freed */
|
||||
pty->vhangup_links = set_free(pty->vhangup_links);
|
||||
|
||||
pseudo_tty_add_to_free_queue(pty);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int pseudo_tty_vhangup(PseudoTTY *pty) {
|
||||
int r;
|
||||
|
||||
assert(pty);
|
||||
|
||||
if (pty->vhangup_event_source)
|
||||
return 0;
|
||||
|
||||
log_debug("Hanging up PTY '%s'.", strna(pty->name));
|
||||
|
||||
if (pty->pin_fd < 0)
|
||||
return -ENOTTY;
|
||||
|
||||
int pin_fd = pty->pin_fd;
|
||||
|
||||
/* NB: until the child event source takes ownership of the child below, an error path kills and
|
||||
* reaps it, so that it doesn't linger as zombie */
|
||||
_cleanup_(pidref_done_sigkill_wait) PidRef pidref = PIDREF_NULL;
|
||||
r = pidref_safe_fork_full(
|
||||
"(vhangup)",
|
||||
/* stdio_fds= */ NULL,
|
||||
/* except_fds= */ &pin_fd,
|
||||
/* n_except_fds= */ 1,
|
||||
FORK_RESET_SIGNALS|FORK_CLOSE_ALL_FDS|FORK_DEATHSIG_SIGKILL,
|
||||
/* ret= */ &pidref);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to fork off vhangup() child: %m");
|
||||
if (r == 0) {
|
||||
_cleanup_close_ int fd = fd_reopen(pin_fd, O_RDWR|O_CLOEXEC|O_NOCTTY);
|
||||
if (fd < 0)
|
||||
goto child_fail;
|
||||
|
||||
pin_fd = safe_close(pin_fd);
|
||||
|
||||
if (terminal_vhangup_fd(fd) < 0)
|
||||
goto child_fail;
|
||||
|
||||
fd = safe_close(fd);
|
||||
_exit(EXIT_SUCCESS);
|
||||
|
||||
child_fail:
|
||||
_exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
/* Watch the child before we drop our fds and I/O event source: if we can't, we leave the pty as it
|
||||
* was, so that the usual hangup paths remain in effect (rather than leaving the pty around without
|
||||
* anything ever queuing it for removal). */
|
||||
r = event_add_child_pidref(
|
||||
pty->manager->event,
|
||||
&pty->vhangup_event_source,
|
||||
&pidref,
|
||||
WEXITED,
|
||||
vhangup_callback,
|
||||
pty);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to watch vhangup() child: %m");
|
||||
|
||||
r = sd_event_source_set_child_process_own(pty->vhangup_event_source, true);
|
||||
if (r < 0) {
|
||||
pty->vhangup_event_source = sd_event_source_disable_unref(pty->vhangup_event_source);
|
||||
return log_error_errno(r, "Failed to pass ownership of vhangup() child to event source: %m");
|
||||
}
|
||||
|
||||
/* The event source owns the child now, hence disarm the cleanup handler */
|
||||
pidref_done(&pidref);
|
||||
|
||||
pty->io_event_source = sd_event_source_disable_unref(pty->io_event_source);
|
||||
pty->frontend_fd = safe_close(pty->frontend_fd);
|
||||
pty->backend_fd = safe_close(pty->backend_fd);
|
||||
pty->pin_fd = safe_close(pty->pin_fd);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const char* const frontend_type_table[_FRONTEND_TYPE_MAX] = {
|
||||
[FRONTEND_TAKE] = "take",
|
||||
[FRONTEND_NULL] = "null",
|
||||
[FRONTEND_LOG] = "log",
|
||||
};
|
||||
DEFINE_STRING_TABLE_LOOKUP(frontend_type, FrontendType);
|
||||
|
||||
static const char* const backend_type_table[_BACKEND_TYPE_MAX] = {
|
||||
[BACKEND_TAKE] = "take",
|
||||
[BACKEND_SHELL] = "shell",
|
||||
};
|
||||
DEFINE_STRING_TABLE_LOOKUP(backend_type, BackendType);
|
||||
@@ -0,0 +1,110 @@
|
||||
/* SPDX-License-Identifier: LGPL-2.1-or-later */
|
||||
#pragma once
|
||||
|
||||
#include <sys/uio.h>
|
||||
|
||||
#include "sd-event.h"
|
||||
#include "sd-json.h"
|
||||
#include "sd-varlink.h"
|
||||
|
||||
#include "forward.h"
|
||||
#include "list.h"
|
||||
#include "pidref.h"
|
||||
#include "ptybroker-forward.h"
|
||||
#include "ptybroker-util.h"
|
||||
|
||||
enum FrontendType {
|
||||
FRONTEND_TAKE, /* return frontend fd to client, don't keep duplicate */
|
||||
FRONTEND_NULL, /* read data from pty, and write it to monitors + eat it up if not monitored */
|
||||
FRONTEND_LOG, /* read data from pty, and write it to journal log + monitors */
|
||||
_FRONTEND_TYPE_MAX,
|
||||
_FRONTEND_TYPE_INVALID = -EINVAL,
|
||||
};
|
||||
|
||||
enum BackendType {
|
||||
BACKEND_TAKE, /* return backend fd to client, don't keep duplicate */
|
||||
BACKEND_SHELL, /* connect shell to backend */
|
||||
_BACKEND_TYPE_MAX,
|
||||
_BACKEND_TYPE_INVALID = -EINVAL,
|
||||
};
|
||||
|
||||
struct PseudoTTY {
|
||||
Manager *manager;
|
||||
|
||||
char *name;
|
||||
char *description;
|
||||
char *tag;
|
||||
|
||||
TerminalSettings terminal_settings;
|
||||
|
||||
FrontendType frontend_type;
|
||||
BackendType backend_type;
|
||||
|
||||
/* If true, a hangup of the backend side does not release the pty, since the backend might be reopened
|
||||
* later (e.g. a getty calling vhangup() on it). */
|
||||
bool ignore_hangup;
|
||||
|
||||
int frontend_fd; /* aka "master" */
|
||||
int backend_fd; /* aka "slave" */
|
||||
int pin_fd; /* O_PATH on backend_fd */
|
||||
char *backend_path;
|
||||
char *unit;
|
||||
|
||||
/* Connection to the service manager while the StartTransient() call for the shell unit is in flight */
|
||||
sd_varlink *start_shell_link;
|
||||
|
||||
/* Client connection whose AcquirePty() reply is deferred until the shell unit start completed, plus
|
||||
* the prepared reply parameters */
|
||||
sd_varlink *acquire_link;
|
||||
sd_json_variant *acquire_reply;
|
||||
bool acquire_monitor;
|
||||
|
||||
/* Identity of the client that allocated this PTY */
|
||||
PidRef owner_pidref;
|
||||
uid_t owner_uid;
|
||||
gid_t owner_gid;
|
||||
|
||||
/* A line-based buffer with the last few lines of output on this PTY */
|
||||
char **track_buffer;
|
||||
size_t track_buffer_next_line;
|
||||
size_t track_buffer_allocated_lines;
|
||||
size_t track_buffer_n_bytes; /* Sum of the payload bytes of all lines currently in the track buffer */
|
||||
|
||||
struct iovec frontend_write_buffer;
|
||||
struct iovec frontend_read_buffer;
|
||||
EndOfLine eol_mask;
|
||||
|
||||
sd_event_source *io_event_source;
|
||||
sd_event_source *backend_inotify_event_source;
|
||||
|
||||
sd_event_source *vhangup_event_source;
|
||||
Set *vhangup_links;
|
||||
|
||||
size_t n_monitors;
|
||||
LIST_HEAD(PseudoTTYMonitor, monitors);
|
||||
|
||||
bool in_free_queue;
|
||||
LIST_FIELDS(PseudoTTY, free_queue);
|
||||
};
|
||||
|
||||
PseudoTTY* pseudo_tty_free(PseudoTTY *pty);
|
||||
DEFINE_TRIVIAL_CLEANUP_FUNC(PseudoTTY*, pseudo_tty_free);
|
||||
|
||||
int pseudo_tty_new(PseudoTTY **ret);
|
||||
int pseudo_tty_link(PseudoTTY *pty, Manager *m);
|
||||
|
||||
int pseudo_tty_set_events(PseudoTTY *pty);
|
||||
|
||||
void pseudo_tty_add_to_free_queue(PseudoTTY *pty);
|
||||
|
||||
int pseudo_tty_watch_frontend_fd(PseudoTTY *pty, sd_event *event);
|
||||
int pseudo_tty_watch_backend_node(PseudoTTY *pty, sd_event *event);
|
||||
|
||||
int pseudo_tty_track_buffer_to_json(PseudoTTY *pty, size_t n_lines, sd_json_variant **ret);
|
||||
|
||||
int pseudo_tty_vhangup(PseudoTTY *pty);
|
||||
|
||||
void pseudo_tty_fail_acquire(PseudoTTY *pty, const char *error_id, sd_json_variant *parameters);
|
||||
|
||||
DECLARE_STRING_TABLE_LOOKUP(frontend_type, FrontendType);
|
||||
DECLARE_STRING_TABLE_LOOKUP(backend_type, BackendType);
|
||||
@@ -0,0 +1,210 @@
|
||||
/* SPDX-License-Identifier: LGPL-2.1-or-later */
|
||||
|
||||
#include <sys/ioctl.h>
|
||||
|
||||
#include "json-util.h"
|
||||
#include "ptybroker-util.h"
|
||||
#include "string-util.h"
|
||||
#include "terminal-util.h"
|
||||
|
||||
bool pseudo_tty_name_valid(const char *s) {
|
||||
if (!string_is_safe(s, STRING_ASCII|STRING_DISALLOW_WHITESPACE))
|
||||
return false;
|
||||
|
||||
return strlen(s) < 64;
|
||||
}
|
||||
|
||||
bool pseudo_tty_description_valid(const char *s) {
|
||||
if (!string_is_safe(s, STRING_ALLOW_QUOTES|STRING_ALLOW_BACKSLASHES|STRING_ALLOW_GLOBS))
|
||||
return false;
|
||||
|
||||
return strlen(s) < 256;
|
||||
}
|
||||
|
||||
bool pseudo_tty_tag_valid(const char *s) {
|
||||
if (!string_is_safe(s, STRING_ASCII|STRING_DISALLOW_WHITESPACE))
|
||||
return false;
|
||||
|
||||
return strlen(s) < 64;
|
||||
}
|
||||
|
||||
EndOfLine end_of_line_from_char(char c) {
|
||||
if (c == '\n')
|
||||
return EOL_NEWLINE;
|
||||
if (c == '\r')
|
||||
return EOL_CARRIAGE_RETURN;
|
||||
if (c == '\f')
|
||||
return EOL_FORM_FEED;
|
||||
if (c == 0)
|
||||
return EOL_NUL;
|
||||
|
||||
return EOL_NONE;
|
||||
}
|
||||
|
||||
void terminal_settings_done(TerminalSettings *ts) {
|
||||
assert(ts);
|
||||
|
||||
ts->env_term = mfree(ts->env_term);
|
||||
ts->env_colorterm = mfree(ts->env_colorterm);
|
||||
}
|
||||
|
||||
int terminal_settings_from_json(sd_json_variant *v, TerminalSettings *ret) {
|
||||
int r;
|
||||
|
||||
assert(ret);
|
||||
|
||||
_cleanup_(terminal_settings_done) TerminalSettings ts = TERMINAL_SETTINGS_NULL;
|
||||
if (v) {
|
||||
static const sd_json_dispatch_field terminal_dispatch_table[] = {
|
||||
{ "lines", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint, offsetof(TerminalSettings, lines), 0 },
|
||||
{ "columns", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint, offsetof(TerminalSettings, columns), 0 },
|
||||
{ "envTERM", SD_JSON_VARIANT_STRING, sd_json_dispatch_string, offsetof(TerminalSettings, env_term), 0 },
|
||||
{ "envCOLORTERM", SD_JSON_VARIANT_STRING, sd_json_dispatch_string, offsetof(TerminalSettings, env_colorterm), 0 },
|
||||
{ "envNO_COLOR", SD_JSON_VARIANT_BOOLEAN, sd_json_dispatch_tristate, offsetof(TerminalSettings, env_no_color), 0 },
|
||||
{}
|
||||
};
|
||||
|
||||
r = sd_json_dispatch(v, terminal_dispatch_table, /* flags= */ 0, &ts);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
/* $COLORTERM values (e.g. "truecolor", "24bit") follow the same rules as $TERM values */
|
||||
if ((ts.lines != UINT_MAX && (ts.lines <= 0 || ts.lines > USHRT_MAX)) ||
|
||||
(ts.columns != UINT_MAX && (ts.columns <= 0 || ts.columns > USHRT_MAX)) ||
|
||||
(ts.env_term && !term_env_valid(ts.env_term)) ||
|
||||
(ts.env_colorterm && !term_env_valid(ts.env_colorterm)))
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
*ret = TAKE_TERMINAL_SETTINGS(ts);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int terminal_settings_to_json(const TerminalSettings *ts, sd_json_variant **ret) {
|
||||
assert(ts);
|
||||
assert(ret);
|
||||
|
||||
return sd_json_buildo(
|
||||
ret,
|
||||
SD_JSON_BUILD_PAIR_CONDITION(ts->lines != UINT_MAX, "lines", SD_JSON_BUILD_UNSIGNED(ts->lines)),
|
||||
SD_JSON_BUILD_PAIR_CONDITION(ts->columns != UINT_MAX, "columns", SD_JSON_BUILD_UNSIGNED(ts->columns)),
|
||||
JSON_BUILD_PAIR_STRING_NON_EMPTY("envTERM", ts->env_term),
|
||||
JSON_BUILD_PAIR_STRING_NON_EMPTY("envCOLORTERM", ts->env_colorterm),
|
||||
SD_JSON_BUILD_PAIR_CONDITION(ts->env_no_color >= 0, "envNO_COLOR", SD_JSON_BUILD_BOOLEAN(ts->env_no_color)));
|
||||
}
|
||||
|
||||
int terminal_settings_settle(TerminalSettings *ts) {
|
||||
assert(ts);
|
||||
|
||||
if (ts->lines == UINT_MAX)
|
||||
ts->lines = 25;
|
||||
if (ts->columns == UINT_MAX)
|
||||
ts->columns = 80;
|
||||
|
||||
if (ts->env_no_color < 0)
|
||||
ts->env_no_color = streq_ptr(ts->env_term, "dumb");
|
||||
|
||||
if (!ts->env_term) {
|
||||
ts->env_term = strdup(FALLBACK_TERM);
|
||||
if (!ts->env_term)
|
||||
return -ENOMEM;
|
||||
|
||||
/* NB: We only default to "truecolor" for $COLORTERM if $TERM is not set either. We assume
|
||||
* that if people set $TERM explicitly they will also set $COLORTERM on their own */
|
||||
if (!ts->env_colorterm && ts->env_no_color <= 0) {
|
||||
ts->env_colorterm = strdup("truecolor");
|
||||
if (!ts->env_colorterm)
|
||||
return -ENOMEM;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int terminal_settings_copy(const TerminalSettings *ts, TerminalSettings *ret) {
|
||||
int r;
|
||||
|
||||
assert(ts);
|
||||
assert(ret);
|
||||
|
||||
_cleanup_free_ char *t = NULL, *ct = NULL;
|
||||
r = strdup_to(&t, ts->env_term);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
r = strdup_to(&ct, ts->env_colorterm);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
*ret = (TerminalSettings) {
|
||||
.lines = ts->lines,
|
||||
.columns = ts->columns,
|
||||
.env_term = TAKE_PTR(t),
|
||||
.env_colorterm = TAKE_PTR(ct),
|
||||
.env_no_color = ts->env_no_color,
|
||||
};
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int terminal_settings_merge(TerminalSettings *ts, const TerminalSettings *override) {
|
||||
assert(ts);
|
||||
|
||||
if (!override)
|
||||
return 0;
|
||||
|
||||
/* Copies all values from 'override' into 'ts' – if they are set. */
|
||||
|
||||
_cleanup_free_ char *t = NULL;
|
||||
bool force_colorterm = false;
|
||||
if (override->env_term) {
|
||||
if (!streq_ptr(ts->env_term, override->env_term)) {
|
||||
t = strdup(override->env_term);
|
||||
if (!t)
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
force_colorterm = true;
|
||||
}
|
||||
|
||||
_cleanup_free_ char *ct = NULL;
|
||||
if (override->env_colorterm && (force_colorterm || !streq_ptr(ts->env_colorterm, override->env_colorterm))) {
|
||||
ct = strdup(override->env_colorterm);
|
||||
if (!ct)
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
if (t)
|
||||
free_and_replace(ts->env_term, t);
|
||||
if (force_colorterm || ct)
|
||||
free_and_replace(ts->env_colorterm, ct);
|
||||
if (override->lines != UINT_MAX)
|
||||
ts->lines = override->lines;
|
||||
if (override->columns != UINT_MAX)
|
||||
ts->columns = override->columns;
|
||||
if (override->env_no_color >= 0)
|
||||
ts->env_no_color = override->env_no_color;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int terminal_settings_sync_size_fd(TerminalSettings *ts, int fd, const char *path) {
|
||||
int r;
|
||||
|
||||
assert(ts);
|
||||
assert(fd >= 0);
|
||||
|
||||
/* First set the desired size, but don't fail if this doesn't work */
|
||||
r = terminal_set_size_fd(fd, path, ts->lines, ts->columns);
|
||||
|
||||
/* Read what is actually in effect. This is fatal if it fails */
|
||||
struct winsize ws = {};
|
||||
if (ioctl(fd, TIOCGWINSZ, &ws) < 0)
|
||||
return -errno;
|
||||
|
||||
ts->lines = ws.ws_row;
|
||||
ts->columns = ws.ws_col;
|
||||
|
||||
/* Return the original error */
|
||||
return r;
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
/* SPDX-License-Identifier: LGPL-2.1-or-later */
|
||||
#pragma once
|
||||
|
||||
#include "sd-json.h"
|
||||
|
||||
#include "ptybroker-forward.h"
|
||||
|
||||
enum EndOfLine {
|
||||
EOL_NONE = 0,
|
||||
|
||||
/* The following four are four distinct line separators we recognizes, that sometimes appear in
|
||||
* combination and sometimes along */
|
||||
EOL_NEWLINE = 1 << 0,
|
||||
EOL_CARRIAGE_RETURN = 1 << 1,
|
||||
EOL_FORM_FEED = 1 << 2,
|
||||
EOL_NUL = 1 << 3,
|
||||
|
||||
/* The following are synthetic line separators, inserted by the tracking logic */
|
||||
EOL_TOO_LONG = 1 << 4, /* line became too long */
|
||||
EOL_FORCE_FLUSH = 1 << 5, /* connection ended, and a line flush was requested */
|
||||
|
||||
_EOL_MASK_ALL = EOL_NEWLINE|EOL_CARRIAGE_RETURN|EOL_FORM_FEED|EOL_NUL|
|
||||
EOL_TOO_LONG|EOL_FORCE_FLUSH,
|
||||
};
|
||||
|
||||
bool pseudo_tty_name_valid(const char *s);
|
||||
bool pseudo_tty_description_valid(const char *s);
|
||||
bool pseudo_tty_tag_valid(const char *s);
|
||||
|
||||
EndOfLine end_of_line_from_char(char c);
|
||||
|
||||
typedef struct TerminalSettings {
|
||||
unsigned columns;
|
||||
unsigned lines;
|
||||
char *env_term;
|
||||
char *env_colorterm;
|
||||
int env_no_color;
|
||||
} TerminalSettings;
|
||||
|
||||
#define TERMINAL_SETTINGS_NULL (TerminalSettings) { \
|
||||
.columns = UINT_MAX, \
|
||||
.lines = UINT_MAX, \
|
||||
.env_no_color = -1, \
|
||||
}
|
||||
|
||||
#define TAKE_TERMINAL_SETTINGS(var) TAKE_GENERIC(var, TerminalSettings, TERMINAL_SETTINGS_NULL)
|
||||
|
||||
void terminal_settings_done(TerminalSettings *ts);
|
||||
int terminal_settings_from_json(sd_json_variant *v, TerminalSettings *ret);
|
||||
int terminal_settings_to_json(const TerminalSettings *ts, sd_json_variant **ret);
|
||||
int terminal_settings_settle(TerminalSettings *ts);
|
||||
int terminal_settings_copy(const TerminalSettings *ts, TerminalSettings *ret);
|
||||
int terminal_settings_merge(TerminalSettings *ts, const TerminalSettings *override);
|
||||
int terminal_settings_sync_size_fd(TerminalSettings *ts, int fd, const char *path);
|
||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,25 @@
|
||||
/* SPDX-License-Identifier: LGPL-2.1-or-later */
|
||||
#pragma once
|
||||
|
||||
#include "sd-event.h"
|
||||
#include "sd-varlink.h"
|
||||
|
||||
#include "list.h"
|
||||
#include "runtime-scope.h"
|
||||
#include "ptybroker-forward.h"
|
||||
|
||||
struct Manager {
|
||||
RuntimeScope scope;
|
||||
sd_event *event;
|
||||
sd_varlink_server *varlink_server;
|
||||
Hashmap *polkit_registry;
|
||||
|
||||
Hashmap *ptys; /* name string → PseudoTTY object */
|
||||
|
||||
LIST_HEAD(PseudoTTY, ptys_free_queue);
|
||||
sd_event_source *ptys_free_queue_event_source;
|
||||
|
||||
sd_event_source *exit_on_idle_event_source;
|
||||
};
|
||||
|
||||
#define BUFFER_MAX (4U * U64_MB)
|
||||
@@ -249,6 +249,7 @@ shared_sources = files(
|
||||
'varlink-io.systemd.Network.Link.c',
|
||||
'varlink-io.systemd.PCRExtend.c',
|
||||
'varlink-io.systemd.PCRLock.c',
|
||||
'varlink-io.systemd.PTYBroker.c',
|
||||
'varlink-io.systemd.Repart.c',
|
||||
'varlink-io.systemd.Report.c',
|
||||
'varlink-io.systemd.Report.Signer.c',
|
||||
|
||||
@@ -0,0 +1,268 @@
|
||||
/* SPDX-License-Identifier: LGPL-2.1-or-later */
|
||||
|
||||
#include "sd-varlink-idl.h"
|
||||
|
||||
#include "bus-polkit.h"
|
||||
#include "varlink-idl-common.h"
|
||||
#include "varlink-io.systemd.PTYBroker.h"
|
||||
|
||||
static SD_VARLINK_DEFINE_ENUM_TYPE(
|
||||
FrontendType,
|
||||
SD_VARLINK_FIELD_COMMENT("Return the file descriptor of the frontend side of the pty to the caller, do not keep a duplicate."),
|
||||
SD_VARLINK_DEFINE_ENUM_VALUE(take),
|
||||
SD_VARLINK_FIELD_COMMENT("Read any data received from the backend and send it to monitors, afterwards discard it"),
|
||||
SD_VARLINK_DEFINE_ENUM_VALUE(null),
|
||||
SD_VARLINK_FIELD_COMMENT("Read any data received from the backend and write it to monitors and the logs"),
|
||||
SD_VARLINK_DEFINE_ENUM_VALUE(log));
|
||||
|
||||
static SD_VARLINK_DEFINE_ENUM_TYPE(
|
||||
BackendType,
|
||||
SD_VARLINK_FIELD_COMMENT("Return the file descriptor of the backend side of the pty to the caller, do not keep a duplicate."),
|
||||
SD_VARLINK_DEFINE_ENUM_VALUE(take),
|
||||
SD_VARLINK_FIELD_COMMENT("Invoke a shell on the backend side."),
|
||||
SD_VARLINK_DEFINE_ENUM_VALUE(shell));
|
||||
|
||||
static SD_VARLINK_DEFINE_STRUCT_TYPE(
|
||||
TerminalSettings,
|
||||
SD_VARLINK_FIELD_COMMENT("Terminal type ($TERM)"),
|
||||
SD_VARLINK_DEFINE_FIELD(envTERM, SD_VARLINK_STRING, SD_VARLINK_NULLABLE),
|
||||
SD_VARLINK_FIELD_COMMENT("Terminal type ($COLORTERM)"),
|
||||
SD_VARLINK_DEFINE_FIELD(envCOLORTERM, SD_VARLINK_STRING, SD_VARLINK_NULLABLE),
|
||||
SD_VARLINK_FIELD_COMMENT("Whether $NO_COLOR is set to a non-empty value, i.e. color output shall be suppressed (see no-color.org)"),
|
||||
SD_VARLINK_DEFINE_FIELD(envNO_COLOR, SD_VARLINK_BOOL, SD_VARLINK_NULLABLE),
|
||||
SD_VARLINK_FIELD_COMMENT("Width (in character cell columns) for the pty"),
|
||||
SD_VARLINK_DEFINE_FIELD(columns, SD_VARLINK_INT, SD_VARLINK_NULLABLE),
|
||||
SD_VARLINK_FIELD_COMMENT("Height (in character cell lines) for the pty"),
|
||||
SD_VARLINK_DEFINE_FIELD(lines, SD_VARLINK_INT, SD_VARLINK_NULLABLE));
|
||||
|
||||
static SD_VARLINK_DEFINE_STRUCT_TYPE(
|
||||
WorkingDirectory,
|
||||
SD_VARLINK_FIELD_COMMENT("The path to the working directory. Mutually exclusive with 'home'"),
|
||||
SD_VARLINK_DEFINE_FIELD(path, SD_VARLINK_STRING, SD_VARLINK_NULLABLE),
|
||||
SD_VARLINK_FIELD_COMMENT("If true, use the configured user's home directory as the working directory. Mutually exclusive with 'path'"),
|
||||
SD_VARLINK_DEFINE_FIELD(home, SD_VARLINK_BOOL, SD_VARLINK_NULLABLE),
|
||||
SD_VARLINK_FIELD_COMMENT("Whether the path to the working directory is allowed to not exist"),
|
||||
SD_VARLINK_DEFINE_FIELD(missingOK, SD_VARLINK_BOOL, SD_VARLINK_NULLABLE));
|
||||
|
||||
static SD_VARLINK_DEFINE_METHOD_FULL(
|
||||
AcquirePty,
|
||||
SD_VARLINK_SUPPORTS_UPGRADE,
|
||||
|
||||
SD_VARLINK_FIELD_COMMENT("What to do with the 'frontend' of the pty, also known as the pty 'master'"),
|
||||
SD_VARLINK_DEFINE_INPUT_BY_TYPE(frontendType, FrontendType, 0),
|
||||
SD_VARLINK_FIELD_COMMENT("What to do with the 'backend' of the pty, also known the pty 'slave'"),
|
||||
SD_VARLINK_DEFINE_INPUT_BY_TYPE(backendType, BackendType, 0),
|
||||
SD_VARLINK_FIELD_COMMENT("A unique name to identify the pty allocation with"),
|
||||
SD_VARLINK_DEFINE_INPUT(name, SD_VARLINK_STRING, SD_VARLINK_NULLABLE),
|
||||
SD_VARLINK_FIELD_COMMENT("A descriptive string to identify the pty allocation with (doesn't have to be unique)"),
|
||||
SD_VARLINK_DEFINE_INPUT(description, SD_VARLINK_STRING, SD_VARLINK_NULLABLE),
|
||||
SD_VARLINK_FIELD_COMMENT("The syslog 'tag' to use for this pseudo TTY output when connected to logging"),
|
||||
SD_VARLINK_DEFINE_INPUT(tag, SD_VARLINK_STRING, SD_VARLINK_NULLABLE),
|
||||
SD_VARLINK_FIELD_COMMENT("For shell backend: user to run the shell as"),
|
||||
SD_VARLINK_DEFINE_INPUT(user, SD_VARLINK_STRING, SD_VARLINK_NULLABLE),
|
||||
SD_VARLINK_FIELD_COMMENT("For shell backend: group to run the shell as"),
|
||||
SD_VARLINK_DEFINE_INPUT(group, SD_VARLINK_STRING, SD_VARLINK_NULLABLE),
|
||||
SD_VARLINK_FIELD_COMMENT("For shell backend: initial directory for the shell, either an absolute path or the home directory of the user"),
|
||||
SD_VARLINK_DEFINE_INPUT_BY_TYPE(workingDirectory, WorkingDirectory, SD_VARLINK_NULLABLE),
|
||||
SD_VARLINK_FIELD_COMMENT("For shell backend: environment variables for the shell, as a list of assignments in the usual VAR=VALUE syntax"),
|
||||
SD_VARLINK_DEFINE_INPUT(environment, SD_VARLINK_STRING, SD_VARLINK_NULLABLE|SD_VARLINK_ARRAY),
|
||||
SD_VARLINK_FIELD_COMMENT("For shell backend: whether to allocate a 'lightweight' session"),
|
||||
SD_VARLINK_DEFINE_INPUT(lightweight, SD_VARLINK_BOOL, SD_VARLINK_NULLABLE),
|
||||
SD_VARLINK_FIELD_COMMENT("For shell backend: command line to invoke as main service process. If unset an interactive login shell is invoked."),
|
||||
SD_VARLINK_DEFINE_INPUT(commandLine, SD_VARLINK_STRING, SD_VARLINK_NULLABLE|SD_VARLINK_ARRAY),
|
||||
SD_VARLINK_FIELD_COMMENT("For shell backend: whether to invoke the command line via the target user's native shell. Defaults to true. If false a command line must be specified, and is invoked directly."),
|
||||
SD_VARLINK_DEFINE_INPUT(viaShell, SD_VARLINK_BOOL, SD_VARLINK_NULLABLE),
|
||||
SD_VARLINK_FIELD_COMMENT("For shell backend: absolute path of the binary to execute. Only valid if viaShell is false. If unset the first element of the command line is used, otherwise the first element of the command line is only passed as argv[0] (e.g. to request login shell semantics by prefixing it with a dash)."),
|
||||
SD_VARLINK_DEFINE_INPUT(executable, SD_VARLINK_STRING, SD_VARLINK_NULLABLE),
|
||||
SD_VARLINK_FIELD_COMMENT("Convert the connection into a monitor connection once done"),
|
||||
SD_VARLINK_DEFINE_INPUT(monitor, SD_VARLINK_BOOL, SD_VARLINK_NULLABLE),
|
||||
SD_VARLINK_FIELD_COMMENT("If true, hang up the pty once this monitor connection is disconnected. If null defaults to false."),
|
||||
SD_VARLINK_DEFINE_INPUT(hangUpOnDisconnect, SD_VARLINK_BOOL, SD_VARLINK_NULLABLE),
|
||||
SD_VARLINK_FIELD_COMMENT("If true, a hangup of the backend side of the pty (e.g. because the last process having it open closed it, or called vhangup() on it) does not release the pty, since the backend might be reopened later. Only valid in combination with the monitor and hangUpOnDisconnect flags, as the monitor connection going away then defines the lifetime of the pty. If null defaults to false."),
|
||||
SD_VARLINK_DEFINE_INPUT(ignoreHangup, SD_VARLINK_BOOL, SD_VARLINK_NULLABLE),
|
||||
SD_VARLINK_FIELD_COMMENT("Initial settings for the terminal"),
|
||||
SD_VARLINK_DEFINE_INPUT_BY_TYPE(terminalSettings, TerminalSettings, SD_VARLINK_NULLABLE),
|
||||
VARLINK_DEFINE_POLKIT_INPUT,
|
||||
|
||||
SD_VARLINK_FIELD_COMMENT("Selected name for the pty allocation"),
|
||||
SD_VARLINK_DEFINE_OUTPUT(name, SD_VARLINK_STRING, 0),
|
||||
SD_VARLINK_FIELD_COMMENT("A descriptive string to identify the pty allocation with (doesn't have to be unique)"),
|
||||
SD_VARLINK_DEFINE_OUTPUT(description, SD_VARLINK_STRING, SD_VARLINK_NULLABLE),
|
||||
SD_VARLINK_FIELD_COMMENT("The frontend type for this PTY"),
|
||||
SD_VARLINK_DEFINE_OUTPUT_BY_TYPE(frontendType, FrontendType, 0),
|
||||
SD_VARLINK_FIELD_COMMENT("The backend type for this PTY"),
|
||||
SD_VARLINK_DEFINE_OUTPUT_BY_TYPE(backendType, BackendType, 0),
|
||||
SD_VARLINK_FIELD_COMMENT("The syslog 'tag' to use for this pseudo TTY output when connected to logging"),
|
||||
SD_VARLINK_DEFINE_OUTPUT(tag, SD_VARLINK_STRING, SD_VARLINK_NULLABLE),
|
||||
SD_VARLINK_FIELD_COMMENT("The index of the PTY frontend (i.e. 'master')"),
|
||||
SD_VARLINK_DEFINE_OUTPUT(frontendFileDescriptor, SD_VARLINK_INT, SD_VARLINK_NULLABLE),
|
||||
SD_VARLINK_FIELD_COMMENT("The index of the PTY backend (i.e. 'slave')"),
|
||||
SD_VARLINK_DEFINE_OUTPUT(backendFileDescriptor, SD_VARLINK_INT, SD_VARLINK_NULLABLE),
|
||||
SD_VARLINK_FIELD_COMMENT("The TTY path name of the allocate pseudo TTY"),
|
||||
SD_VARLINK_DEFINE_OUTPUT(backendPath, SD_VARLINK_STRING, 0),
|
||||
SD_VARLINK_FIELD_COMMENT("The name of the transient service unit that runs the shell or login on the PTY backend side."),
|
||||
SD_VARLINK_DEFINE_OUTPUT(unit, SD_VARLINK_STRING, SD_VARLINK_NULLABLE),
|
||||
SD_VARLINK_FIELD_COMMENT("Settled settings for the terminal"),
|
||||
SD_VARLINK_DEFINE_OUTPUT_BY_TYPE(terminalSettings, TerminalSettings, SD_VARLINK_NULLABLE));
|
||||
|
||||
static SD_VARLINK_DEFINE_METHOD_FULL(
|
||||
EnrollPty,
|
||||
SD_VARLINK_SUPPORTS_UPGRADE,
|
||||
|
||||
SD_VARLINK_FIELD_COMMENT("The index of the PTY frontend (i.e. 'master')"),
|
||||
SD_VARLINK_DEFINE_INPUT(frontendFileDescriptor, SD_VARLINK_INT, 0),
|
||||
SD_VARLINK_FIELD_COMMENT("What to do with the 'frontend' of the pty, also known as the pty 'master'"),
|
||||
SD_VARLINK_DEFINE_INPUT_BY_TYPE(frontendType, FrontendType, 0),
|
||||
SD_VARLINK_FIELD_COMMENT("The TTY path name of the pre-allocated pseudo TTY, if known and meaningful to the broker. Leave unset if the path only makes sense within the caller's namespace."),
|
||||
SD_VARLINK_DEFINE_INPUT(backendPath, SD_VARLINK_STRING, SD_VARLINK_NULLABLE),
|
||||
SD_VARLINK_FIELD_COMMENT("A unique name to identify the pty allocation with"),
|
||||
SD_VARLINK_DEFINE_INPUT(name, SD_VARLINK_STRING, SD_VARLINK_NULLABLE),
|
||||
SD_VARLINK_FIELD_COMMENT("A descriptive string to identify the pty allocation with (doesn't have to be unique)"),
|
||||
SD_VARLINK_DEFINE_INPUT(description, SD_VARLINK_STRING, SD_VARLINK_NULLABLE),
|
||||
SD_VARLINK_FIELD_COMMENT("The syslog 'tag' to use for this pseudo TTY output when connected to logging"),
|
||||
SD_VARLINK_DEFINE_INPUT(tag, SD_VARLINK_STRING, SD_VARLINK_NULLABLE),
|
||||
SD_VARLINK_FIELD_COMMENT("Convert the connection into a monitor connection once done"),
|
||||
SD_VARLINK_DEFINE_INPUT(monitor, SD_VARLINK_BOOL, SD_VARLINK_NULLABLE),
|
||||
SD_VARLINK_FIELD_COMMENT("If true, hang up the pty once this monitor connection is disconnected. If null defaults to false."),
|
||||
SD_VARLINK_DEFINE_INPUT(hangUpOnDisconnect, SD_VARLINK_BOOL, SD_VARLINK_NULLABLE),
|
||||
SD_VARLINK_FIELD_COMMENT("If true, a hangup of the backend side of the pty (e.g. because the last process having it open closed it, or called vhangup() on it) does not release the pty, since the backend might be reopened later. Only valid in combination with the monitor and hangUpOnDisconnect flags, as the monitor connection going away then defines the lifetime of the pty. If null defaults to false."),
|
||||
SD_VARLINK_DEFINE_INPUT(ignoreHangup, SD_VARLINK_BOOL, SD_VARLINK_NULLABLE),
|
||||
SD_VARLINK_FIELD_COMMENT("Initial settings for the terminal"),
|
||||
SD_VARLINK_DEFINE_INPUT_BY_TYPE(terminalSettings, TerminalSettings, SD_VARLINK_NULLABLE),
|
||||
VARLINK_DEFINE_POLKIT_INPUT,
|
||||
|
||||
SD_VARLINK_FIELD_COMMENT("Selected name for the pty allocation"),
|
||||
SD_VARLINK_DEFINE_OUTPUT(name, SD_VARLINK_STRING, 0),
|
||||
SD_VARLINK_FIELD_COMMENT("A descriptive string to identify the pty allocation with (doesn't have to be unique)"),
|
||||
SD_VARLINK_DEFINE_OUTPUT(description, SD_VARLINK_STRING, SD_VARLINK_NULLABLE),
|
||||
SD_VARLINK_FIELD_COMMENT("The frontend type for this PTY"),
|
||||
SD_VARLINK_DEFINE_OUTPUT_BY_TYPE(frontendType, FrontendType, 0),
|
||||
SD_VARLINK_FIELD_COMMENT("The backend type for this PTY"),
|
||||
SD_VARLINK_DEFINE_OUTPUT_BY_TYPE(backendType, BackendType, 0),
|
||||
SD_VARLINK_FIELD_COMMENT("The syslog 'tag' to use for this pseudo TTY output when connected to logging"),
|
||||
SD_VARLINK_DEFINE_OUTPUT(tag, SD_VARLINK_STRING, SD_VARLINK_NULLABLE),
|
||||
SD_VARLINK_FIELD_COMMENT("Settled settings for the terminal"),
|
||||
SD_VARLINK_DEFINE_OUTPUT_BY_TYPE(terminalSettings, TerminalSettings, SD_VARLINK_NULLABLE));
|
||||
|
||||
|
||||
static SD_VARLINK_DEFINE_METHOD_FULL(
|
||||
MonitorPty,
|
||||
SD_VARLINK_REQUIRES_UPGRADE,
|
||||
|
||||
SD_VARLINK_FIELD_COMMENT("The name of the PTY allocation to connect to."),
|
||||
SD_VARLINK_DEFINE_INPUT(name, SD_VARLINK_STRING, 0),
|
||||
SD_VARLINK_FIELD_COMMENT("How many lines of track buffer to return. If null defaults to 0."),
|
||||
SD_VARLINK_DEFINE_INPUT(trackBufferLines, SD_VARLINK_INT, SD_VARLINK_NULLABLE),
|
||||
SD_VARLINK_FIELD_COMMENT("If true, hang up the pty once this monitor connection is disconnected. If null defaults to false."),
|
||||
SD_VARLINK_DEFINE_INPUT(hangUpOnDisconnect, SD_VARLINK_BOOL, SD_VARLINK_NULLABLE),
|
||||
SD_VARLINK_FIELD_COMMENT("New settings for the terminal (not that the environment based settings will not be propagated to the backend processes)"),
|
||||
SD_VARLINK_DEFINE_INPUT_BY_TYPE(terminalSettings, TerminalSettings, SD_VARLINK_NULLABLE),
|
||||
VARLINK_DEFINE_POLKIT_INPUT,
|
||||
|
||||
SD_VARLINK_FIELD_COMMENT("Name of the allocated PTY"),
|
||||
SD_VARLINK_DEFINE_OUTPUT(name, SD_VARLINK_STRING, 0),
|
||||
SD_VARLINK_FIELD_COMMENT("A descriptive string to identify the pty allocation with (doesn't have to be unique)"),
|
||||
SD_VARLINK_DEFINE_OUTPUT(description, SD_VARLINK_STRING, SD_VARLINK_NULLABLE),
|
||||
SD_VARLINK_FIELD_COMMENT("The frontend type for this PTY"),
|
||||
SD_VARLINK_DEFINE_OUTPUT_BY_TYPE(frontendType, FrontendType, 0),
|
||||
SD_VARLINK_FIELD_COMMENT("The backend type for this PTY"),
|
||||
SD_VARLINK_DEFINE_OUTPUT_BY_TYPE(backendType, BackendType, 0),
|
||||
SD_VARLINK_FIELD_COMMENT("The syslog 'tag' to use for this pseudo TTY output when connected to logging"),
|
||||
SD_VARLINK_DEFINE_OUTPUT(tag, SD_VARLINK_STRING, SD_VARLINK_NULLABLE),
|
||||
SD_VARLINK_FIELD_COMMENT("The TTY path name of the allocate pseudo TTY"),
|
||||
SD_VARLINK_DEFINE_OUTPUT(backendPath, SD_VARLINK_STRING, SD_VARLINK_NULLABLE),
|
||||
SD_VARLINK_FIELD_COMMENT("The unit backing the PTY if known"),
|
||||
SD_VARLINK_DEFINE_OUTPUT(unit, SD_VARLINK_STRING, SD_VARLINK_NULLABLE),
|
||||
SD_VARLINK_FIELD_COMMENT("Current contents of the track buffer, line by line."),
|
||||
SD_VARLINK_DEFINE_OUTPUT(trackBuffer, SD_VARLINK_STRING, SD_VARLINK_NULLABLE|SD_VARLINK_ARRAY),
|
||||
SD_VARLINK_FIELD_COMMENT("Settings for the terminal"),
|
||||
SD_VARLINK_DEFINE_OUTPUT_BY_TYPE(terminalSettings, TerminalSettings, SD_VARLINK_NULLABLE));
|
||||
|
||||
static SD_VARLINK_DEFINE_METHOD(
|
||||
ConfigurePty,
|
||||
SD_VARLINK_FIELD_COMMENT("The name of the PTY allocation to reconfigure to."),
|
||||
SD_VARLINK_DEFINE_INPUT(name, SD_VARLINK_STRING, 0),
|
||||
SD_VARLINK_FIELD_COMMENT("New settings for the terminal (not that the environment based settings will not be propagated to the backend processes)"),
|
||||
SD_VARLINK_DEFINE_INPUT_BY_TYPE(terminalSettings, TerminalSettings, SD_VARLINK_NULLABLE),
|
||||
VARLINK_DEFINE_POLKIT_INPUT,
|
||||
|
||||
SD_VARLINK_FIELD_COMMENT("Complete configured settings"),
|
||||
SD_VARLINK_DEFINE_OUTPUT_BY_TYPE(terminalSettings, TerminalSettings, SD_VARLINK_NULLABLE));
|
||||
|
||||
static SD_VARLINK_DEFINE_METHOD_FULL(
|
||||
ListPty,
|
||||
SD_VARLINK_SUPPORTS_MORE,
|
||||
|
||||
SD_VARLINK_FIELD_COMMENT("The name of the PTY allocation to list."),
|
||||
SD_VARLINK_DEFINE_INPUT(name, SD_VARLINK_STRING, SD_VARLINK_NULLABLE),
|
||||
VARLINK_DEFINE_POLKIT_INPUT,
|
||||
|
||||
SD_VARLINK_FIELD_COMMENT("Name of the allocated PTY"),
|
||||
SD_VARLINK_DEFINE_OUTPUT(name, SD_VARLINK_STRING, 0),
|
||||
SD_VARLINK_FIELD_COMMENT("A descriptive string to identify the pty allocation with (doesn't have to be unique)"),
|
||||
SD_VARLINK_DEFINE_OUTPUT(description, SD_VARLINK_STRING, SD_VARLINK_NULLABLE),
|
||||
SD_VARLINK_FIELD_COMMENT("The frontend type for this PTY"),
|
||||
SD_VARLINK_DEFINE_OUTPUT_BY_TYPE(frontendType, FrontendType, 0),
|
||||
SD_VARLINK_FIELD_COMMENT("The backend type for this PTY"),
|
||||
SD_VARLINK_DEFINE_OUTPUT_BY_TYPE(backendType, BackendType, 0),
|
||||
SD_VARLINK_FIELD_COMMENT("The syslog 'tag' to use for this pseudo TTY output when connected to logging"),
|
||||
SD_VARLINK_DEFINE_OUTPUT(tag, SD_VARLINK_STRING, SD_VARLINK_NULLABLE),
|
||||
SD_VARLINK_FIELD_COMMENT("The backend path for this PTY"),
|
||||
SD_VARLINK_DEFINE_OUTPUT(backendPath, SD_VARLINK_STRING, SD_VARLINK_NULLABLE),
|
||||
SD_VARLINK_FIELD_COMMENT("The unit backing the PTY if known"),
|
||||
SD_VARLINK_DEFINE_OUTPUT(unit, SD_VARLINK_STRING, SD_VARLINK_NULLABLE),
|
||||
SD_VARLINK_FIELD_COMMENT("The process of the client that allocated this PTY"),
|
||||
SD_VARLINK_DEFINE_OUTPUT_BY_TYPE(owner, ProcessId, SD_VARLINK_NULLABLE),
|
||||
SD_VARLINK_FIELD_COMMENT("The UID of the client that allocated this PTY"),
|
||||
SD_VARLINK_DEFINE_OUTPUT(ownerUID, SD_VARLINK_INT, SD_VARLINK_NULLABLE),
|
||||
SD_VARLINK_FIELD_COMMENT("The GID of the client that allocated this PTY"),
|
||||
SD_VARLINK_DEFINE_OUTPUT(ownerGID, SD_VARLINK_INT, SD_VARLINK_NULLABLE));
|
||||
|
||||
static SD_VARLINK_DEFINE_METHOD(
|
||||
HangUpPty,
|
||||
SD_VARLINK_FIELD_COMMENT("Name of the allocated PTY to hang up"),
|
||||
SD_VARLINK_DEFINE_INPUT(name, SD_VARLINK_STRING, 0),
|
||||
VARLINK_DEFINE_POLKIT_INPUT);
|
||||
|
||||
static SD_VARLINK_DEFINE_ERROR(NoSuchPty);
|
||||
static SD_VARLINK_DEFINE_ERROR(PtyExists);
|
||||
static SD_VARLINK_DEFINE_ERROR(TrackingNotEnabled);
|
||||
static SD_VARLINK_DEFINE_ERROR(TooManyPtys);
|
||||
static SD_VARLINK_DEFINE_ERROR(TooManyMonitors);
|
||||
|
||||
SD_VARLINK_DEFINE_INTERFACE(
|
||||
io_systemd_PTYBroker,
|
||||
"io.systemd.PTYBroker",
|
||||
SD_VARLINK_INTERFACE_COMMENT("An API for acquiring a pseudo-terminal"),
|
||||
SD_VARLINK_SYMBOL_COMMENT("Allocates a PTY pair, and configures what to do with each side."),
|
||||
&vl_method_AcquirePty,
|
||||
SD_VARLINK_SYMBOL_COMMENT("Enrolls a pre-allocated PTY frontend (i.e. the master)."),
|
||||
&vl_method_EnrollPty,
|
||||
SD_VARLINK_SYMBOL_COMMENT("Monitor frontend side of the pty."),
|
||||
&vl_method_MonitorPty,
|
||||
SD_VARLINK_SYMBOL_COMMENT("Reconfigure pseudo terminal"),
|
||||
&vl_method_ConfigurePty,
|
||||
SD_VARLINK_SYMBOL_COMMENT("List allocated pseudo-terminals"),
|
||||
&vl_method_ListPty,
|
||||
SD_VARLINK_SYMBOL_COMMENT("Hang up pseudo terminal"),
|
||||
&vl_method_HangUpPty,
|
||||
SD_VARLINK_SYMBOL_COMMENT("What to do with the PTY frontend."),
|
||||
&vl_type_FrontendType,
|
||||
SD_VARLINK_SYMBOL_COMMENT("What to do with the PTY backend."),
|
||||
&vl_type_BackendType,
|
||||
SD_VARLINK_SYMBOL_COMMENT("Details about terminal settings."),
|
||||
&vl_type_TerminalSettings,
|
||||
SD_VARLINK_SYMBOL_COMMENT("The working directory of the shell."),
|
||||
&vl_type_WorkingDirectory,
|
||||
SD_VARLINK_SYMBOL_COMMENT("An object for referencing UNIX processes"),
|
||||
&vl_type_ProcessId,
|
||||
SD_VARLINK_SYMBOL_COMMENT("No PTY by the given name known"),
|
||||
&vl_error_NoSuchPty,
|
||||
SD_VARLINK_SYMBOL_COMMENT("A PTY by the given name already exists."),
|
||||
&vl_error_PtyExists,
|
||||
SD_VARLINK_SYMBOL_COMMENT("Limit on number of PTYs reached."),
|
||||
&vl_error_TooManyPtys,
|
||||
SD_VARLINK_SYMBOL_COMMENT("Limit on number of monitors of a PTY reached."),
|
||||
&vl_error_TooManyMonitors,
|
||||
SD_VARLINK_SYMBOL_COMMENT("Tracking of PTY state not enabled for specified PTY."),
|
||||
&vl_error_TrackingNotEnabled);
|
||||
@@ -0,0 +1,6 @@
|
||||
/* SPDX-License-Identifier: LGPL-2.1-or-later */
|
||||
#pragma once
|
||||
|
||||
#include "sd-varlink-idl.h"
|
||||
|
||||
extern const sd_varlink_interface vl_interface_io_systemd_PTYBroker;
|
||||
@@ -780,6 +780,8 @@ units = [
|
||||
'file' : 'systemd-pstore.service.in',
|
||||
'conditions' : ['ENABLE_PSTORE'],
|
||||
},
|
||||
{ 'file' : 'systemd-ptybrokerd.service.in' },
|
||||
{ 'file' : 'systemd-ptybrokerd.socket' },
|
||||
{
|
||||
'file' : 'systemd-quotacheck@.service.in',
|
||||
'conditions' : ['ENABLE_QUOTACHECK'],
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
# 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.
|
||||
|
||||
[Unit]
|
||||
Description=PTY Broker
|
||||
Documentation=man:systemd-ptybrokerd.service(8)
|
||||
Requires=systemd-ptybrokerd.socket
|
||||
After=systemd-ptybrokerd.socket
|
||||
Conflicts=shutdown.target
|
||||
Before=sysinit.target shutdown.target
|
||||
DefaultDependencies=no
|
||||
|
||||
[Service]
|
||||
# CAP_CHOWN: to hand PTYs over to the requesting client's UID/GID
|
||||
# CAP_DAC_OVERRIDE: to reopen a PTY we handed over for hang-up
|
||||
# CAP_SYS_ADMIN: for TIOCVHANGUP
|
||||
CapabilityBoundingSet=CAP_CHOWN CAP_DAC_OVERRIDE CAP_SYS_ADMIN
|
||||
ExecStart={{LIBEXECDIR}}/systemd-ptybrokerd
|
||||
IPAddressDeny=any
|
||||
KillMode=mixed
|
||||
LimitNOFILE={{HIGH_RLIMIT_NOFILE}}
|
||||
LockPersonality=yes
|
||||
MemoryDenyWriteExecute=yes
|
||||
NoNewPrivileges=yes
|
||||
PrivateDevices=yes
|
||||
PrivateNetwork=yes
|
||||
PrivateTmp=disconnected
|
||||
ProtectClock=yes
|
||||
ProtectControlGroups=yes
|
||||
ProtectHome=yes
|
||||
ProtectHostname=yes
|
||||
ProtectKernelLogs=yes
|
||||
ProtectKernelModules=yes
|
||||
ProtectKernelTunables=yes
|
||||
ProtectProc=invisible
|
||||
ProtectSystem=strict
|
||||
RestrictAddressFamilies=AF_UNIX
|
||||
RestrictNamespaces=yes
|
||||
RestrictRealtime=yes
|
||||
RestrictSUIDSGID=yes
|
||||
SystemCallArchitectures=native
|
||||
SystemCallErrorNumber=EPERM
|
||||
SystemCallFilter=@system-service
|
||||
Type=notify
|
||||
{{SERVICE_WATCHDOG}}
|
||||
|
||||
[Install]
|
||||
Also=systemd-ptybrokerd.socket
|
||||
@@ -0,0 +1,27 @@
|
||||
# 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.
|
||||
|
||||
[Unit]
|
||||
Description=PTY Broker Socket
|
||||
Documentation=man:systemd-ptybrokerd.service(8)
|
||||
Conflicts=shutdown.target
|
||||
Before=sockets.target shutdown.target
|
||||
DefaultDependencies=no
|
||||
|
||||
[Socket]
|
||||
ListenStream=/run/systemd/io.systemd.PTYBroker
|
||||
Symlinks=/run/varlink/registry/io.systemd.PTYBroker
|
||||
FileDescriptorName=varlink
|
||||
SocketMode=0666
|
||||
RemoveOnStop=yes
|
||||
XAttrEntryPoint=user.varlink=entrypoint
|
||||
XAttrListen=user.varlink=listen
|
||||
|
||||
[Install]
|
||||
WantedBy=sockets.target
|
||||
@@ -43,6 +43,11 @@ units = [
|
||||
'conditions' : ['ENABLE_PORTABLED'],
|
||||
'symlinks' : ['dbus-org.freedesktop.portable1.service'],
|
||||
},
|
||||
{ 'file' : 'systemd-ptybrokerd.service.in' },
|
||||
{
|
||||
'file' : 'systemd-ptybrokerd.socket',
|
||||
'symlinks' : ['sockets.target.wants/'],
|
||||
},
|
||||
{ 'file' : 'paths.target' },
|
||||
{ 'file' : 'printer.target' },
|
||||
{ 'file' : 'session.slice' },
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
# 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.
|
||||
|
||||
[Unit]
|
||||
Description=PTY Broker
|
||||
Documentation=man:systemd-ptybrokerd.service(8)
|
||||
Requires=systemd-ptybrokerd.socket
|
||||
After=systemd-ptybrokerd.socket
|
||||
Conflicts=shutdown.target
|
||||
Before=default.target shutdown.target
|
||||
DefaultDependencies=no
|
||||
|
||||
[Service]
|
||||
ExecStart={{LIBEXECDIR}}/systemd-ptybrokerd --user
|
||||
KillMode=mixed
|
||||
LimitNOFILE={{HIGH_RLIMIT_NOFILE}}
|
||||
LockPersonality=yes
|
||||
MemoryDenyWriteExecute=yes
|
||||
NoNewPrivileges=yes
|
||||
RestrictAddressFamilies=AF_UNIX
|
||||
RestrictRealtime=yes
|
||||
SystemCallArchitectures=native
|
||||
SystemCallErrorNumber=EPERM
|
||||
SystemCallFilter=@system-service
|
||||
Type=notify
|
||||
{{SERVICE_WATCHDOG}}
|
||||
|
||||
[Install]
|
||||
Also=systemd-ptybrokerd.socket
|
||||
@@ -0,0 +1,27 @@
|
||||
# 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.
|
||||
|
||||
[Unit]
|
||||
Description=PTY Broker Socket
|
||||
Documentation=man:systemd-ptybrokerd.service(8)
|
||||
Conflicts=shutdown.target
|
||||
Before=sockets.target shutdown.target
|
||||
DefaultDependencies=no
|
||||
|
||||
[Socket]
|
||||
ListenStream=%t/systemd/io.systemd.PTYBroker
|
||||
Symlinks=%t/varlink/registry/io.systemd.PTYBroker
|
||||
FileDescriptorName=varlink
|
||||
SocketMode=0600
|
||||
RemoveOnStop=yes
|
||||
XAttrEntryPoint=user.varlink=entrypoint
|
||||
XAttrListen=user.varlink=listen
|
||||
|
||||
[Install]
|
||||
WantedBy=sockets.target
|
||||
Reference in new issue
Block a user