Compare commits
15 Commits
e360d9b900
...
dd4f400953
Author | SHA1 | Date |
---|---|---|
Lennart Poettering | dd4f400953 | |
Luca Boccassi | 2e5b0412f9 | |
Yu Watanabe | 2b397d43ab | |
Yu Watanabe | 9ad294efd0 | |
Lennart Poettering | 4c9769353e | |
Lennart Poettering | b16c6b0c08 | |
Lennart Poettering | a48ae38ea3 | |
Lennart Poettering | 3722a71a87 | |
Lennart Poettering | 805495ade2 | |
Lennart Poettering | 03d8af8da4 | |
Lennart Poettering | 9ed47d39d9 | |
Lennart Poettering | 462baeb410 | |
Lennart Poettering | 420b74654d | |
Lennart Poettering | b56421e0dc | |
Lennart Poettering | 221d6e54c6 |
|
@ -0,0 +1,283 @@
|
|||
---
|
||||
title: OSC 300819: Hierarchial Context Signalling
|
||||
category: Interfaces
|
||||
layout: default
|
||||
SPDX-License-Identifier: LGPL-2.1-or-later
|
||||
---
|
||||
|
||||
# OSC 300819: Hierarchial Context Signalling
|
||||
|
||||
A terminal connects a user with programs. Control of the program side of
|
||||
terminals is typically passed around to various different components while the
|
||||
user is active: a shell might pass control to a process it invokes. If that
|
||||
process is `run0` then primary control is passed to the privileged session of
|
||||
the target user. If `systemd-nspawn` is invoked to start a container primary
|
||||
controls is passed to that container, and so on.
|
||||
|
||||
A terminal emulator might be interested to know which component is currently is
|
||||
in primary control of the program side of a terminal. OSC 3000910 is a
|
||||
mechanism to inform it about such contexts. Each component taking over control
|
||||
can inform the terminal emulators that a new context begins now, and then use
|
||||
the terminal or pass control down to further apps, which can introduce
|
||||
contexts. Each context may carry various discriptive metadata fields.
|
||||
|
||||
## Status
|
||||
|
||||
This OS is invented by systemd. Currently, no terminal application is known
|
||||
that consumes these sequences.
|
||||
|
||||
## Usecases
|
||||
|
||||
Terminal emulators can use hierarchial context information:
|
||||
|
||||
1. To introduce markers/bookmarks in the output that the user can jump between.
|
||||
|
||||
2. To visually identify output from different contexts. For example the
|
||||
background can be tinted in a reddish tone when privileges are acquired, and
|
||||
similar.
|
||||
|
||||
3. Meta information on specific output can be shown in a tooltip or similar
|
||||
|
||||
4. Programs (and all subcontexts) can be killed by right-clicking on the output
|
||||
they generate.
|
||||
|
||||
5. Failed commands or aborted sessions can be marked requesting use attention.
|
||||
|
||||
## Context Types
|
||||
|
||||
There are various types of contexts defined by this specification:
|
||||
|
||||
1. `boot` → a booted system initiates this context early at boot. (systemd's
|
||||
PID 1 generates this on `/dev/console`.)
|
||||
|
||||
2. `container` → a container managed initialized an interactive connection to a
|
||||
container. (`systemd-nspawn` generates this when interactively invoking a
|
||||
container. `machinectl login`, `machinectl shell` do this too.)
|
||||
|
||||
3. `vm` → a VMM initialized a terminal connection to VM. (`systemd-vmspawn`
|
||||
generates this when interactively invoking a VM, as one example.)
|
||||
|
||||
4. `elevate` → when the user interactively acquired higher privileges. (`run0`
|
||||
initiates a context of this type whenever the user invokes it to acquire
|
||||
root privileges.)
|
||||
|
||||
5. `chpriv` → similar, but when the user acquired *different* privileges, not
|
||||
necessarily higher ones. (`run0` initiates a context of this type whenever
|
||||
the user invokes it to acquire non-root privileges of another user.)
|
||||
|
||||
5. `subcontext` → similar, but the source and target privileges where
|
||||
identical. (`run0` initiates a context of this type whenever the user
|
||||
invokes it to acquire privileges of the user itself.)
|
||||
|
||||
6. `remote` → a user invoked a tool such as `ssh` to connect to a remote
|
||||
system.
|
||||
|
||||
7. `shell` → an interactive terminal shell initiates this context
|
||||
|
||||
8. `command` → a shell interactively invokes a new program.
|
||||
|
||||
9. `app` → an interactive program may initiate this context.
|
||||
|
||||
10. `service` → the service manager invokes an interactive service on the terminal
|
||||
|
||||
11. `session` → a login session of the user is initialized.
|
||||
|
||||
## Semantics
|
||||
|
||||
Contexts in the sense of OSC 300819 are hierarchial, and describe a tree
|
||||
structure: whenever a new context is opened it becomes the new active context,
|
||||
and the previously active context becomes its parent (if there is one). Only
|
||||
one context is currently active, but previously opened contexts remain valid in
|
||||
the background. Any other data written or read should be considered associated
|
||||
with the currently active context.
|
||||
|
||||
Each context carries an identifier, chosen by the component opening the
|
||||
context. The identifier can chosen freely, but must not be longer than 64
|
||||
characters. The characters may be in the 32…126 byte range. Identifiers should universally
|
||||
unique, for example randomly generated. A freshly generated UUID would work
|
||||
well for this, but this could also be something like the Linux boot ID combined
|
||||
with the 64bit inode number of Linux pidfds, or something hashed from it.
|
||||
|
||||
Fundamentally, there are two OSC 300819 commands defined:
|
||||
|
||||
1. OSC "`300819;S`" (the *start sequence*) → this initiates, updates or indicates a return to a
|
||||
context. It carries a context identifier, and typically some metadata. This
|
||||
may be send to first initiate a context. If sent again for the a context ID
|
||||
that was initiated already this indicates an update of the existing
|
||||
context. In this case, any previously set metadata fields for the context
|
||||
are flushed out, reset to their defaults, and then reinitialized from the
|
||||
newly supplied data. Also, in this case any subcontects of the contexts are
|
||||
implicitly terminated.
|
||||
|
||||
2. OSC "`300819;X`" (the *end sequence*)→ this terminates a context. It carries a context
|
||||
identifier to close, initiated before with OSC `300819;S`. It may also carry
|
||||
additional metadata.
|
||||
|
||||
## General Syntax
|
||||
|
||||
This builds on ECMA-48, and reuses the OSC and ST concepts introduced there.
|
||||
|
||||
For sequences following this specification it is recommended to encode OSC as
|
||||
0x1B 0x5D, and ST as 0x1B 0x5C.
|
||||
|
||||
ECMA-48 only allows characters from the range 0x20…0x7e (i.e. 32…126) inside
|
||||
OSC sequences. Hence, any fields that shall contain characters outside of this
|
||||
range require escaping. All textual fields must be encoded in UTF-8, which
|
||||
then must be escaped.
|
||||
|
||||
Escaping shall be applied by taking the byte values of the characters to
|
||||
escape, and formatting them as lower-case hexadecimal prefixed with
|
||||
`\x`. Example: `Schöpfgefäß` becomes `Sch\xc3\xb6pfgef\xc3\xa4\xc3\x9f`.
|
||||
|
||||
The start sequence begins with OSC, followed by the character `S`, followed by
|
||||
the context ID. This is then followed by any number of metadata fields,
|
||||
including none. Metadata fields begin with a semicolon (`;`) and end in a
|
||||
character identifiying the type of field. The sequence ends in ST.
|
||||
|
||||
The end sequence begins with OSC, followed by the character `X`, followed by
|
||||
the context ID, and a series of metadata fields in the the syntax as for the
|
||||
start sequence.
|
||||
|
||||
## Metadata Fields
|
||||
|
||||
The following fields are currently defined:
|
||||
|
||||
| Suffix | Context Types | Description |
|
||||
|--------|---------------|-------------------------------------------------------------------------------------------------------------|
|
||||
| `u` | *all* | UNIX user name the process issuing the sequence runs as |
|
||||
| `h` | *all* | UNIX host name of the system the process issuing the sequence runs on |
|
||||
| `m` | *all* | The machine ID (i.e. `/etc/machine-id`) of the system the process issuing the sequence runs on |
|
||||
| `b` | *all* | The boot ID (i.e. `/proc/sys/kernel/random/boot_id`) of the system the process issuing the sequence runs on |
|
||||
| `p` | *all* | The numeric PID of the process issuing the sequence, in decimal notation |
|
||||
| `P` | *all* | The 64bit inode number of the pidfd of the process issuing the sequence, in decimal notation |
|
||||
| `c` | *all* | The process name (i.e. `/proc/$PID/comm`, `PR_GET_NAME`) of the process issuing the sequence |
|
||||
| `v` | `vm` | The name of the VM being invoked |
|
||||
| `C` | `container` | The name of the container being invoked |
|
||||
| `U` | `elevate`, `chpriv`, `vm`, `container`, `remote` | Target UNIX user name |
|
||||
| `H` | `remote` | Target UNIX, DNS host name, or IP address |
|
||||
|
||||
All fields are optional, including the context type. However, it is generally
|
||||
recommended to always include the first 7 fields listed above, to make it easy
|
||||
to pinpoint the origin of a context in a race-free fashion without any
|
||||
ambiguities.
|
||||
|
||||
## Examples
|
||||
|
||||
1. A new container `foobar` has been invoked by user `lennart` on host `zeta`:
|
||||
`OSC "300819;Sbed86fab93af4328bbed0a1224af6d40;lennartu;zetah;3deb5353d3ba43d08201c136a47ead7bm;d4a3d0fdf2e24fdea6d971ce73f4fbf2b;1062862p;1063162P;foobarc;containert" ST`
|
||||
|
||||
2. A context ends: `OSC "300819;Xbed86fab93af4328bbed0a1224af6d40" ST`
|
||||
|
||||
## Syntax in ABNF
|
||||
|
||||
```abnf
|
||||
OSC = %x1B %x5D
|
||||
ST = %x1B %x5C
|
||||
|
||||
DECIMAL = "0"-"9"
|
||||
HEX = "0"-"9" / "A"-"F" / "a-f"
|
||||
ID128 = 32*36(HEX / "-")
|
||||
UINT64 = 1*20DECIMAL
|
||||
ESCAPED = "\x" HEX HEX
|
||||
SAFE = %x20-3a / %x3c-5b / %x5d-7e / ESCAPED
|
||||
|
||||
CTXID = 1*64SAFE
|
||||
|
||||
USER = 1*255SAFE "u"
|
||||
HOSTNAME = 1*255SAFE "h"
|
||||
MACHINEID = 1D128 "m"
|
||||
BOOTID = ID128 "b"
|
||||
PID = UINT64 "p"
|
||||
PIDFDID = UINT64 "P"
|
||||
COMM = 1*255SAFE "c"
|
||||
|
||||
TYPE = ("service" / "session" / "shell" / "command" / "vm" / "container" / "elevate" / "chpriv" / "subcontext" / "remote" / "boot" / "app") "t"
|
||||
|
||||
SESSIONID = 1*255SAFE "s"
|
||||
CWD = 1*255SAFE "d"
|
||||
CMDLINE = *255SAFE "L"
|
||||
VMNAME = 1*255SAFE "v"
|
||||
CONTAINERNAME= 1*255SAFE "C"
|
||||
TARGETUSER = 1*255SAFE "U"
|
||||
TARGETHOST = 1*255SAFE "H"
|
||||
APPID = 1*255SAFE "A"
|
||||
|
||||
STARTFIELD = (USER / HOSTNAME / MACHINEID / BOOTID / PID / PIDFDID / COMM / TYPE / SESSIONID / CWD / CMDLINE / VMNAME / CONTAINERNAME / TARGETUSER / TARGETHOST / APPID)
|
||||
STARTSEQ = OSC "300819;" CTXID "S" *(";" STARTFIELD) ST
|
||||
|
||||
EXIT = "success" / "failure" / "crash" / "interrupt"
|
||||
STATUS = UINT64
|
||||
SIGNAL = "SIGBUS" / "SIGTRAP" / "SIGABRT" / "SIGSEGV" / …
|
||||
|
||||
ENDFIELD = (EXIT / STATUS / SIGNAL)
|
||||
ENDSEQ = OSC "300819;" CTXID "X" *(";" ENDFIELD) ST
|
||||
```
|
||||
|
||||
## Known OSC Prefixes
|
||||
|
||||
Here's a list of OSC prefixes used by the various sequences currently in public
|
||||
use in various terminal emulators. It's not going to be complete, but I tried
|
||||
to do some reasonably thorough research to avoid conflicts with the new OSC
|
||||
sequence defined above.
|
||||
|
||||
| OSC Prefix | Purpose |
|
||||
|----------------:|------------------------------------------------------------|
|
||||
| `OSC "0;…"` | Icon name + window title |
|
||||
| `OSC "1;…"` | Icon name |
|
||||
| `OSC "2;…"` | Window title |
|
||||
| `OSC "3;…"` | X11 property |
|
||||
| `OSC "4;…"` | Palette |
|
||||
| `OSC "5;…"` | Special palette |
|
||||
| `OSC "6;…"` | Disable special color |
|
||||
| `OSC "7;…"` | Report cwd |
|
||||
| `OSC "8;…"` | Hyperlink |
|
||||
| `OSC "9;…"` | Progress bar (conemu) [conflict: also growl notifications] |
|
||||
| `OSC "10;…"` | Change colors |
|
||||
| `OSC "11;…"` | " |
|
||||
| `OSC "12;…"` | " |
|
||||
| `OSC "13;…"` | " |
|
||||
| `OSC "14;…"` | " |
|
||||
| `OSC "15;…"` | " |
|
||||
| `OSC "16;…"` | " |
|
||||
| `OSC "17;…"` | " |
|
||||
| `OSC "18;…"` | " |
|
||||
| `OSC "19;…"` | " |
|
||||
| `OSC "21;…"` | Query colors (kitty) |
|
||||
| `OSC "22;…"` | Cursor shape |
|
||||
| `OSC "46;…"` | Log file |
|
||||
| `OSC "50;…"` | Set font |
|
||||
| `OSC "51;…"` | Emacs shell |
|
||||
| `OSC "52;…"` | Manipulate selection data (aka clipboard) |
|
||||
| `OSC "60;…"` | Query allowed |
|
||||
| `OSC "61;…"` | Query disallowed |
|
||||
| `OSC "99;…"` | Notifications (kitty) |
|
||||
| `OSC "104;…"` | Reset color |
|
||||
| `OSC "105;…"` | Enable/disable special color |
|
||||
| `OSC "110;…"` | Reset colors |
|
||||
| `OSC "111;…"` | " |
|
||||
| `OSC "112;…"` | " |
|
||||
| `OSC "113;…"` | " |
|
||||
| `OSC "114;…"` | " |
|
||||
| `OSC "115;…"` | " |
|
||||
| `OSC "116;…"` | " |
|
||||
| `OSC "117;…"` | " |
|
||||
| `OSC "118;…"` | " |
|
||||
| `OSC "119;…"` | " |
|
||||
| `OSC "133;…"` | Prompt/command begin/command end (finalterm/iterm2) |
|
||||
| `OSC "440;…"` | Audio (mintty) |
|
||||
| `OSC "633;…"` | vscode action (Windows Terminal) |
|
||||
| `OSC "666;…"` | "termprop" (vte) |
|
||||
| `OSC "701;…"` | Locale (mintty) |
|
||||
| `OSC "777;…"` | Notification (rxvt) |
|
||||
| `OSC "7704;…"` | ANSI colors (mintty) |
|
||||
| `OSC "7750;…"` | Emoji style (mintty) |
|
||||
| `OSC "7770;…"` | Font size (mintty) |
|
||||
| `OSC "7771;…"` | Glyph coverage (mintty) |
|
||||
| `OSC "7721:…"` | Copy window title (mintty) |
|
||||
| `OSC "7777;…"` | Window size (mintty) |
|
||||
| `OSC "9001;…"` | Action (Windows Terminal) |
|
||||
| `OSC "1337;…"` | iterm2 multiplex seeuqnece |
|
||||
| `OSC "5522;…"` | Clipboard (kitty) |
|
||||
| `OSC "30001;…"` | Push color onto stack (kitty) |
|
||||
| `OSC "30101;…"` | Pop color from stack (kitty) |
|
||||
| `OSC "77119;…"` | Wide chars (mintty) |
|
|
@ -365,6 +365,8 @@ char* xescape_full(const char *s, const char *bad, size_t console_width, XEscape
|
|||
char *ans, *t, *prev, *prev2;
|
||||
const char *f;
|
||||
|
||||
assert(s);
|
||||
|
||||
/* Escapes all chars in bad, in addition to \ and all special chars, in \xFF style escaping. May be
|
||||
* reversed with cunescape(). If XESCAPE_8_BIT is specified, characters >= 127 are let through
|
||||
* unchanged. This corresponds to non-ASCII printable characters in pre-unicode encodings.
|
||||
|
@ -397,7 +399,7 @@ char* xescape_full(const char *s, const char *bad, size_t console_width, XEscape
|
|||
|
||||
if ((unsigned char) *f < ' ' ||
|
||||
(!FLAGS_SET(flags, XESCAPE_8_BIT) && (unsigned char) *f >= 127) ||
|
||||
*f == '\\' || strchr(bad, *f)) {
|
||||
*f == '\\' || (bad && strchr(bad, *f))) {
|
||||
if ((size_t) (t - ans) + 4 + 3 * force_ellipsis > console_width)
|
||||
break;
|
||||
|
||||
|
|
|
@ -866,6 +866,9 @@ void hexdump(FILE *f, const void *p, size_t s) {
|
|||
|
||||
assert(b || s == 0);
|
||||
|
||||
if (s == SIZE_MAX)
|
||||
s = strlen(p);
|
||||
|
||||
if (!f)
|
||||
f = stdout;
|
||||
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
#include <ctype.h>
|
||||
#include <errno.h>
|
||||
#include <limits.h>
|
||||
#include <linux/magic.h>
|
||||
#include <linux/oom.h>
|
||||
#include <pthread.h>
|
||||
#include <spawn.h>
|
||||
|
@ -11,6 +12,9 @@
|
|||
#include <stdlib.h>
|
||||
#include <sys/mount.h>
|
||||
#include <sys/personality.h>
|
||||
#if HAVE_PIDFD_OPEN
|
||||
#include <sys/pidfd.h>
|
||||
#endif
|
||||
#include <sys/prctl.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/wait.h>
|
||||
|
@ -40,6 +44,7 @@
|
|||
#include "log.h"
|
||||
#include "macro.h"
|
||||
#include "memory-util.h"
|
||||
#include "missing_magic.h"
|
||||
#include "missing_sched.h"
|
||||
#include "missing_syscall.h"
|
||||
#include "missing_threads.h"
|
||||
|
@ -2289,3 +2294,42 @@ _noreturn_ void report_errno_and_exit(int errno_fd, int error) {
|
|||
|
||||
_exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
int getpidfdid_cached(uint64_t *ret) {
|
||||
static uint64_t cached = 0;
|
||||
static int initialized = 0;
|
||||
int r;
|
||||
|
||||
assert(ret);
|
||||
|
||||
if (initialized > 0) {
|
||||
*ret = cached;
|
||||
return 0;
|
||||
}
|
||||
if (initialized < 0)
|
||||
return initialized;
|
||||
|
||||
_cleanup_close_ int fd = pidfd_open(getpid_cached(), 0);
|
||||
if (fd < 0) {
|
||||
if (ERRNO_IS_NOT_SUPPORTED(errno))
|
||||
return (initialized = -EOPNOTSUPP);
|
||||
|
||||
return -errno;
|
||||
}
|
||||
|
||||
r = fd_is_fs_type(fd, PID_FS_MAGIC);
|
||||
if (r < 0)
|
||||
return r;
|
||||
if (r == 0)
|
||||
return (initialized = -EOPNOTSUPP);
|
||||
|
||||
struct stat st;
|
||||
if (fstat(fd, &st) < 0)
|
||||
return -errno;
|
||||
if (st.st_ino == 0)
|
||||
return (initialized = -EOPNOTSUPP);
|
||||
|
||||
*ret = cached = st.st_ino;
|
||||
initialized = 1;
|
||||
return 0;
|
||||
}
|
||||
|
|
|
@ -275,3 +275,5 @@ int proc_dir_read(DIR *d, pid_t *ret);
|
|||
int proc_dir_read_pidref(DIR *d, PidRef *ret);
|
||||
|
||||
_noreturn_ void report_errno_and_exit(int errno_fd, int error);
|
||||
|
||||
int getpidfdid_cached(uint64_t *ret);
|
||||
|
|
|
@ -42,6 +42,7 @@ static void fallback_random_bytes(void *p, size_t n) {
|
|||
uint64_t call_id, block_id;
|
||||
usec_t stamp_mono, stamp_real;
|
||||
pid_t pid, tid;
|
||||
uint64_t pidfdid;
|
||||
uint8_t auxval[16];
|
||||
} state = {
|
||||
/* Arbitrary domain separation to prevent other usage of AT_RANDOM from clashing. */
|
||||
|
@ -57,6 +58,8 @@ static void fallback_random_bytes(void *p, size_t n) {
|
|||
memcpy(state.auxval, ULONG_TO_PTR(getauxval(AT_RANDOM)), sizeof(state.auxval));
|
||||
#endif
|
||||
|
||||
(void) getpidfdid_cached(&state.pidfdid);
|
||||
|
||||
while (n > 0) {
|
||||
struct sha256_ctx ctx;
|
||||
|
||||
|
|
|
@ -45,6 +45,7 @@
|
|||
#include "efivars.h"
|
||||
#include "emergency-action.h"
|
||||
#include "env-util.h"
|
||||
#include "escape.h"
|
||||
#include "exit-status.h"
|
||||
#include "fd-util.h"
|
||||
#include "fdset.h"
|
||||
|
@ -57,6 +58,7 @@
|
|||
#include "ima-setup.h"
|
||||
#include "import-creds.h"
|
||||
#include "initrd-util.h"
|
||||
#include "io-util.h"
|
||||
#include "ipe-setup.h"
|
||||
#include "killall.h"
|
||||
#include "kmod-setup.h"
|
||||
|
@ -73,6 +75,7 @@
|
|||
#include "mount-setup.h"
|
||||
#include "mount-util.h"
|
||||
#include "os-util.h"
|
||||
#include "osc-context.h"
|
||||
#include "pager.h"
|
||||
#include "parse-argument.h"
|
||||
#include "parse-util.h"
|
||||
|
@ -2380,6 +2383,38 @@ static void log_execution_mode(bool *ret_first_boot) {
|
|||
*ret_first_boot = first_boot;
|
||||
}
|
||||
|
||||
static int write_boot_or_shutdown_osc(bool boot) {
|
||||
int r;
|
||||
|
||||
if (getenv_terminal_is_dumb())
|
||||
return 0;
|
||||
|
||||
const char *type = boot ? "boot" : "shutdown";
|
||||
|
||||
_cleanup_close_ int fd = open_terminal("/dev/console", O_WRONLY|O_NOCTTY|O_CLOEXEC);
|
||||
if (fd < 0)
|
||||
return log_debug_errno(fd, "Failed to open /dev/console to print %s OSC, ignoring: %m", type);
|
||||
|
||||
_cleanup_free_ char *seq = NULL;
|
||||
if (boot)
|
||||
r = osc_context_open_boot(&seq);
|
||||
else
|
||||
r = osc_context_close(SD_ID128_ALLF, &seq);
|
||||
if (r < 0)
|
||||
return log_debug_errno(r, "Failed to acquire %s OSC sequence, ignoring: %m", type);
|
||||
|
||||
r = loop_write(fd, seq, strlen(seq));
|
||||
if (r < 0)
|
||||
return log_debug_errno(r, "Failed to write %s OSC sequence, ignoring: %m", type);
|
||||
|
||||
if (DEBUG_LOGGING) {
|
||||
_cleanup_free_ char *h = cescape(seq);
|
||||
log_debug("OSC sequence for %s successfully written: %s", type, strna(h));
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int initialize_runtime(
|
||||
bool skip_setup,
|
||||
bool first_boot,
|
||||
|
@ -2438,6 +2473,8 @@ static int initialize_runtime(
|
|||
|
||||
write_container_id();
|
||||
|
||||
(void) write_boot_or_shutdown_osc(/* boot= */ true);
|
||||
|
||||
/* Copy os-release to the propagate directory, so that we update it for services running
|
||||
* under RootDirectory=/RootImage= when we do a soft reboot. */
|
||||
r = setup_os_release(RUNTIME_SCOPE_SYSTEM);
|
||||
|
@ -3427,6 +3464,8 @@ finish:
|
|||
}
|
||||
#endif
|
||||
|
||||
(void) write_boot_or_shutdown_osc(/* boot= */ false);
|
||||
|
||||
if (r < 0)
|
||||
(void) sd_notifyf(/* unset_environment= */ false,
|
||||
"ERRNO=%i", -r);
|
||||
|
|
|
@ -45,6 +45,7 @@
|
|||
#include "main-func.h"
|
||||
#include "mkdir.h"
|
||||
#include "nulstr-util.h"
|
||||
#include "osc-context.h"
|
||||
#include "pager.h"
|
||||
#include "parse-argument.h"
|
||||
#include "parse-util.h"
|
||||
|
@ -1227,6 +1228,13 @@ static int process_forward(sd_event *event, PTYForward **forward, int master, PT
|
|||
log_info("Connected to machine %s. Press ^] three times within 1s to exit session.", name);
|
||||
}
|
||||
|
||||
_cleanup_(osc_context_closep) sd_id128_t osc_context_id = SD_ID128_NULL;
|
||||
if (!terminal_is_dumb()) {
|
||||
r = osc_context_open_container(name, /* ret_seq= */ NULL, &osc_context_id);
|
||||
if (r < 0)
|
||||
return r;
|
||||
}
|
||||
|
||||
r = sd_event_set_signal_exit(event, true);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to enable SIGINT/SITERM handling: %m");
|
||||
|
|
|
@ -1443,6 +1443,7 @@ int link_reconfigure_impl(Link *link, LinkReconfigurationFlag flags) {
|
|||
}
|
||||
|
||||
typedef struct LinkReconfigurationData {
|
||||
Manager *manager;
|
||||
Link *link;
|
||||
LinkReconfigurationFlag flags;
|
||||
sd_bus_message *message;
|
||||
|
@ -1473,6 +1474,12 @@ static void link_reconfiguration_data_destroy_callback(LinkReconfigurationData *
|
|||
}
|
||||
|
||||
if (!data->counter || *data->counter <= 0) {
|
||||
/* Update the state files before replying the bus method. Otherwise,
|
||||
* systemd-networkd-wait-online following networkctl reload/reconfigure may read an
|
||||
* outdated state file and wrongly handle an interface is already in the configured
|
||||
* state. */
|
||||
(void) manager_clean_all(data->manager);
|
||||
|
||||
r = sd_bus_reply_method_return(data->message, NULL);
|
||||
if (r < 0)
|
||||
log_warning_errno(r, "Failed to reply for DBus method, ignoring: %m");
|
||||
|
@ -1521,6 +1528,7 @@ int link_reconfigure_full(Link *link, LinkReconfigurationFlag flags, sd_bus_mess
|
|||
}
|
||||
|
||||
*data = (LinkReconfigurationData) {
|
||||
.manager = link->manager,
|
||||
.link = link_ref(link),
|
||||
.flags = flags,
|
||||
.message = sd_bus_message_ref(message), /* message may be NULL, but _ref() works fine. */
|
||||
|
|
|
@ -84,6 +84,7 @@
|
|||
#include "nsresource.h"
|
||||
#include "nulstr-util.h"
|
||||
#include "os-util.h"
|
||||
#include "osc-context.h"
|
||||
#include "pager.h"
|
||||
#include "parse-argument.h"
|
||||
#include "parse-util.h"
|
||||
|
@ -5685,6 +5686,13 @@ static int run_container(
|
|||
(void) expose_port_execute(rtnl, &expose_args->fw_ctx, arg_expose_ports, AF_INET6, &expose_args->address6);
|
||||
}
|
||||
|
||||
_cleanup_(osc_context_closep) sd_id128_t osc_context_id = SD_ID128_NULL;
|
||||
if (IN_SET(arg_console_mode, CONSOLE_INTERACTIVE, CONSOLE_READ_ONLY) && !terminal_is_dumb()) {
|
||||
r = osc_context_open_container(arg_machine, /* ret_seq= */ NULL, &osc_context_id);
|
||||
if (r < 0)
|
||||
return r;
|
||||
}
|
||||
|
||||
if (arg_console_mode != CONSOLE_PIPE) {
|
||||
_cleanup_close_ int fd = -EBADF;
|
||||
PTYForwardFlags flags = 0;
|
||||
|
|
|
@ -30,6 +30,7 @@
|
|||
#include "fs-util.h"
|
||||
#include "hostname-util.h"
|
||||
#include "main-func.h"
|
||||
#include "osc-context.h"
|
||||
#include "parse-argument.h"
|
||||
#include "parse-util.h"
|
||||
#include "path-util.h"
|
||||
|
@ -2041,6 +2042,7 @@ static int start_transient_service(sd_bus *bus) {
|
|||
return r;
|
||||
}
|
||||
|
||||
_cleanup_(osc_context_closep) sd_id128_t osc_context_id = SD_ID128_NULL;
|
||||
if (arg_wait || arg_stdio != ARG_STDIO_NONE) {
|
||||
_cleanup_(run_context_done) RunContext c = {
|
||||
.cpu_usage_nsec = NSEC_INFINITY,
|
||||
|
@ -2067,6 +2069,12 @@ static int start_transient_service(sd_bus *bus) {
|
|||
return log_oom();
|
||||
|
||||
if (pty_fd >= 0) {
|
||||
if (!terminal_is_dumb() && arg_exec_user) {
|
||||
r = osc_context_open_chpriv(arg_exec_user, /* ret_seq= */ NULL, &osc_context_id);
|
||||
if (r < 0)
|
||||
return r;
|
||||
}
|
||||
|
||||
(void) sd_event_set_signal_exit(c.event, true);
|
||||
|
||||
if (!arg_quiet)
|
||||
|
|
|
@ -132,6 +132,7 @@ shared_sources = files(
|
|||
'open-file.c',
|
||||
'openssl-util.c',
|
||||
'output-mode.c',
|
||||
'osc-context.c',
|
||||
'pager.c',
|
||||
'parse-argument.c',
|
||||
'parse-helpers.c',
|
||||
|
|
|
@ -0,0 +1,275 @@
|
|||
/* SPDX-License-Identifier: LGPL-2.1-or-later */
|
||||
|
||||
#if HAVE_SYS_AUXV_H
|
||||
# include <sys/auxv.h>
|
||||
#endif
|
||||
|
||||
#include "escape.h"
|
||||
#include "hostname-util.h"
|
||||
#include "osc-context.h"
|
||||
#include "process-util.h"
|
||||
#include "string-util.h"
|
||||
#include "terminal-util.h"
|
||||
#include "user-util.h"
|
||||
|
||||
/* This currently generates open sequences for OSC 300819 types "boot", "container", "vm", "elevate",
|
||||
* "chpriv", "subcontext". */
|
||||
|
||||
/* TODO:
|
||||
*
|
||||
* → "service" (from the service manager)
|
||||
* → "session" (from pam_systemd?)
|
||||
* → "shell", "command" (from a bash profile drop-in?)
|
||||
*
|
||||
* Not generated by systemd: "remote" (would have to be generated from the SSH client), "app".
|
||||
*/
|
||||
|
||||
static int strextend_escaped(char **s, const char *prefix, const char *value, const char *suffix) {
|
||||
assert(s);
|
||||
assert(value);
|
||||
|
||||
if (!strextend(s, prefix))
|
||||
return -ENOMEM;
|
||||
|
||||
_cleanup_free_ char *e = xescape(value, ";\\");
|
||||
if (!e)
|
||||
return -ENOMEM;
|
||||
|
||||
if (!strextend(s, e))
|
||||
return -ENOMEM;
|
||||
|
||||
if (!strextend(s, suffix))
|
||||
return -ENOMEM;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int osc_append_identity(char **s) {
|
||||
int r;
|
||||
|
||||
assert(s);
|
||||
|
||||
_cleanup_free_ char *u = getusername_malloc();
|
||||
if (u) {
|
||||
r = strextend_escaped(s, ";", u, "u");
|
||||
if (r < 0)
|
||||
return r;
|
||||
}
|
||||
|
||||
_cleanup_free_ char *h = gethostname_malloc();
|
||||
if (h) {
|
||||
r = strextend_escaped(s, ";", h, "h");
|
||||
if (r < 0)
|
||||
return r;
|
||||
}
|
||||
|
||||
sd_id128_t id;
|
||||
if (sd_id128_get_machine(&id) >= 0) {
|
||||
r = strextendf(s, ";" SD_ID128_FORMAT_STR "m", SD_ID128_FORMAT_VAL(id));
|
||||
if (r < 0)
|
||||
return r;
|
||||
}
|
||||
|
||||
if (sd_id128_get_boot(&id) >= 0) {
|
||||
r = strextendf(s, ";" SD_ID128_FORMAT_STR "b", SD_ID128_FORMAT_VAL(id));
|
||||
if (r < 0)
|
||||
return r;
|
||||
}
|
||||
|
||||
r = strextendf(s, ";" PID_FMT "p", getpid_cached());
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
uint64_t pidfdid;
|
||||
r = getpidfdid_cached(&pidfdid);
|
||||
if (r >= 0) {
|
||||
r = strextendf(s, ";%" PRIu64 "P", pidfdid);
|
||||
if (r < 0)
|
||||
return r;
|
||||
}
|
||||
|
||||
r = strextend_escaped(s, ";", program_invocation_short_name, "c");
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void osc_context_default_id(sd_id128_t *ret_id) {
|
||||
|
||||
/* Usually we only want one context ID per tool. Since we don't want to store the ID let's just hash
|
||||
* one from process credentials */
|
||||
|
||||
struct {
|
||||
uint64_t pidfdid;
|
||||
uint8_t auxval[16];
|
||||
pid_t pid;
|
||||
} data = {
|
||||
.pid = getpid_cached(),
|
||||
};
|
||||
|
||||
assert(ret_id);
|
||||
|
||||
(void) getpidfdid_cached(&data.pidfdid);
|
||||
|
||||
memcpy(data.auxval, ULONG_TO_PTR(getauxval(AT_RANDOM)), sizeof(data.auxval));
|
||||
|
||||
ret_id->qwords[0] = siphash24(&data, sizeof(data), SD_ID128_MAKE(3f,8c,ee,e1,fd,35,41,ec,b8,b1,90,d4,59,e2,ae,5b).bytes);
|
||||
ret_id->qwords[1] = siphash24(&data, sizeof(data), SD_ID128_MAKE(c6,41,ec,1b,d8,85,48,c0,8e,11,d7,e1,e1,fa,9e,03).bytes);
|
||||
}
|
||||
|
||||
static int osc_context_intro(char **ret_seq, sd_id128_t *ret_context_id) {
|
||||
int r;
|
||||
|
||||
assert(ret_seq);
|
||||
|
||||
/* If the user passed us a buffer for the context ID generate a randomized one, since we have a place
|
||||
* to store it. The user should pass the ID back to osc_context_close() later on. if the user did not
|
||||
* pass us a buffer, we'll use a session ID hashed from process properties that remain stable as long
|
||||
* our process exists. It hence also remains stable across reexec and similar. */
|
||||
sd_id128_t id;
|
||||
if (ret_context_id) {
|
||||
r = sd_id128_randomize(&id);
|
||||
if (r < 0)
|
||||
return r;
|
||||
} else
|
||||
osc_context_default_id(&id);
|
||||
|
||||
_cleanup_free_ char *seq = NULL;
|
||||
if (asprintf(&seq, ANSI_OSC "300819;S" SD_ID128_FORMAT_STR, SD_ID128_FORMAT_VAL(id)) < 0)
|
||||
return -ENOMEM;
|
||||
|
||||
r = osc_append_identity(&seq);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
if (ret_context_id)
|
||||
*ret_context_id = id;
|
||||
|
||||
*ret_seq = TAKE_PTR(seq);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int osc_context_outro(char *_seq, sd_id128_t id, char **ret_seq, sd_id128_t *ret_context_id) {
|
||||
_cleanup_free_ char *seq = TAKE_PTR(_seq); /* We take possession of the string no matter what */
|
||||
|
||||
if (ret_seq)
|
||||
*ret_seq = TAKE_PTR(seq);
|
||||
else {
|
||||
fputs(seq, stdout);
|
||||
fflush(stdout);
|
||||
}
|
||||
|
||||
if (ret_context_id)
|
||||
*ret_context_id = id;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int osc_context_open_boot(char **ret_seq) {
|
||||
int r;
|
||||
|
||||
_cleanup_free_ char *seq = NULL;
|
||||
sd_id128_t id;
|
||||
r = osc_context_intro(&seq, /* ret_context_id= */ NULL);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
if (!strextend(&seq, ";" "boot" "t" ANSI_ST))
|
||||
return -ENOMEM;
|
||||
|
||||
return osc_context_outro(TAKE_PTR(seq), id, ret_seq, /* ret_context_id= */ NULL);
|
||||
}
|
||||
|
||||
int osc_context_open_container(const char *name, char **ret_seq, sd_id128_t *ret_context_id) {
|
||||
int r;
|
||||
|
||||
_cleanup_free_ char *seq = NULL;
|
||||
sd_id128_t id;
|
||||
r = osc_context_intro(&seq, ret_context_id ?: &id);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
if (name) {
|
||||
r = strextend_escaped(&seq, ";", name, "C");
|
||||
if (r < 0)
|
||||
return r;
|
||||
}
|
||||
|
||||
if (!strextend(&seq, ";" "container" "t" ANSI_ST))
|
||||
return -ENOMEM;
|
||||
|
||||
return osc_context_outro(TAKE_PTR(seq), id, ret_seq, ret_context_id);
|
||||
}
|
||||
|
||||
int osc_context_open_vm(const char *name, char **ret_seq, sd_id128_t *ret_context_id) {
|
||||
int r;
|
||||
|
||||
assert(name);
|
||||
|
||||
_cleanup_free_ char *seq = NULL;
|
||||
sd_id128_t id;
|
||||
r = osc_context_intro(&seq, ret_context_id ?: &id);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
r = strextend_escaped(&seq, ";", name, "v");
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
if (!strextend(&seq, ";" "vm" "t" ANSI_ST))
|
||||
return r;
|
||||
|
||||
return osc_context_outro(TAKE_PTR(seq), id, ret_seq, ret_context_id);
|
||||
}
|
||||
|
||||
int osc_context_open_chpriv(const char *target_user, char **ret_seq, sd_id128_t *ret_context_id) {
|
||||
int r;
|
||||
|
||||
assert(target_user);
|
||||
|
||||
_cleanup_free_ char *seq = NULL;
|
||||
sd_id128_t id;
|
||||
r = osc_context_intro(&seq, ret_context_id ?: &id);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
if (STR_IN_SET(target_user, "root", "0")) {
|
||||
if (!strextend(&seq, ";" "elevate" "t" ANSI_ST))
|
||||
return -ENOMEM;
|
||||
} else if (is_this_me(target_user) > 0) {
|
||||
if (!strextend(&seq, ";" "subcontext" "t" ANSI_ST))
|
||||
return -ENOMEM;
|
||||
} else {
|
||||
r = strextend_escaped(&seq, ";", target_user, "U");
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
if (!strextend(&seq, ";" "chpriv" "t" ANSI_ST))
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
return osc_context_outro(TAKE_PTR(seq), id, ret_seq, ret_context_id);
|
||||
}
|
||||
|
||||
int osc_context_close(sd_id128_t id, char **ret_seq) {
|
||||
|
||||
if (sd_id128_is_null(id)) /* nil uuid: no session opened */
|
||||
return 0;
|
||||
|
||||
if (sd_id128_is_allf(id)) /* max uuid: default session opened */
|
||||
osc_context_default_id(&id);
|
||||
|
||||
_cleanup_free_ char *seq = NULL;
|
||||
if (asprintf(&seq, ANSI_OSC "300819;X" SD_ID128_FORMAT_STR ANSI_ST, SD_ID128_FORMAT_VAL(id)) < 0)
|
||||
return -ENOMEM;
|
||||
|
||||
if (ret_seq)
|
||||
*ret_seq = TAKE_PTR(seq);
|
||||
else {
|
||||
fputs(seq, stdout);
|
||||
fflush(stdout);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
|
@ -0,0 +1,14 @@
|
|||
/* SPDX-License-Identifier: LGPL-2.1-or-later */
|
||||
#pragma once
|
||||
|
||||
#include "sd-id128.h"
|
||||
|
||||
int osc_context_open_boot(char **ret_seq);
|
||||
int osc_context_open_container(const char *name, char **ret_seq, sd_id128_t *ret_context_id);
|
||||
int osc_context_open_vm(const char *name, char **ret_seq, sd_id128_t *ret_context_id);
|
||||
int osc_context_open_chpriv(const char *target_user, char **ret_seq, sd_id128_t *ret_context_id);
|
||||
int osc_context_close(sd_id128_t id, char **ret_seq);
|
||||
|
||||
static inline void osc_context_closep(sd_id128_t *context_id) {
|
||||
(void) osc_context_close(*context_id, NULL);
|
||||
}
|
|
@ -137,6 +137,7 @@ simple_tests += files(
|
|||
'test-open-file.c',
|
||||
'test-ordered-set.c',
|
||||
'test-os-util.c',
|
||||
'test-osc-context.c',
|
||||
'test-parse-argument.c',
|
||||
'test-parse-helpers.c',
|
||||
'test-path-lookup.c',
|
||||
|
|
|
@ -0,0 +1,48 @@
|
|||
/* SPDX-License-Identifier: LGPL-2.1-or-later */
|
||||
|
||||
#include "hexdecoct.h"
|
||||
#include "osc-context.h"
|
||||
#include "tests.h"
|
||||
|
||||
#include "escape.h"
|
||||
|
||||
TEST(osc) {
|
||||
_cleanup_free_ char *seq = NULL;
|
||||
|
||||
log_info("boot");
|
||||
assert_se(osc_context_open_boot(&seq) >= 0);
|
||||
hexdump(/* f = */ NULL, seq, SIZE_MAX);
|
||||
seq = mfree(seq);
|
||||
|
||||
assert_se(osc_context_close(SD_ID128_ALLF, &seq) >= 0);
|
||||
hexdump(/* f = */ NULL, seq, SIZE_MAX);
|
||||
seq = mfree(seq);
|
||||
|
||||
log_info("container");
|
||||
sd_id128_t id;
|
||||
assert_se(osc_context_open_container("foobar", &seq, &id) >= 0);
|
||||
hexdump(/* f = */ NULL, seq, SIZE_MAX);
|
||||
seq = mfree(seq);
|
||||
|
||||
assert_se(osc_context_close(id, &seq) >= 0);
|
||||
hexdump(/* f = */ NULL, seq, SIZE_MAX);
|
||||
seq = mfree(seq);
|
||||
|
||||
log_info("vm");
|
||||
assert_se(osc_context_open_vm("foobar", &seq, &id) >= 0);
|
||||
hexdump(/* f = */ NULL, seq, SIZE_MAX);
|
||||
seq = mfree(seq);
|
||||
|
||||
assert_se(osc_context_close(id, &seq) >= 0);
|
||||
hexdump(/* f = */ NULL, seq, SIZE_MAX);
|
||||
seq = mfree(seq);
|
||||
|
||||
printf("%s\n", xescape("Schöpfgefäß", NULL));
|
||||
}
|
||||
|
||||
static int intro(void) {
|
||||
log_show_color(true);
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
|
||||
DEFINE_TEST_MAIN_WITH_INTRO(LOG_INFO, intro);
|
|
@ -994,6 +994,21 @@ TEST(pid_get_start_time) {
|
|||
ASSERT_GE(start_time2, start_time);
|
||||
}
|
||||
|
||||
TEST(getpidfdid_cached) {
|
||||
int r;
|
||||
|
||||
log_info("pid=" PID_FMT, getpid_cached());
|
||||
|
||||
uint64_t id;
|
||||
r = getpidfdid_cached(&id);
|
||||
if (ERRNO_IS_NEG_NOT_SUPPORTED(r))
|
||||
log_info("pidfdid not supported");
|
||||
else {
|
||||
assert(r >= 0);
|
||||
log_info("pidfdid=%" PRIu64, id);
|
||||
}
|
||||
}
|
||||
|
||||
static int intro(void) {
|
||||
log_show_color(true);
|
||||
return EXIT_SUCCESS;
|
||||
|
|
|
@ -47,6 +47,7 @@
|
|||
#include "main-func.h"
|
||||
#include "mkdir.h"
|
||||
#include "netif-util.h"
|
||||
#include "osc-context.h"
|
||||
#include "pager.h"
|
||||
#include "parse-argument.h"
|
||||
#include "parse-util.h"
|
||||
|
@ -2189,8 +2190,15 @@ static int run_virtual_machine(int kvm_device_fd, int vhost_device_fd) {
|
|||
/* Exit when the child exits */
|
||||
(void) event_add_child_pidref(event, NULL, &child_pidref, WEXITED, on_child_exit, NULL);
|
||||
|
||||
_cleanup_(osc_context_closep) sd_id128_t osc_context_id = SD_ID128_NULL;
|
||||
_cleanup_(pty_forward_freep) PTYForward *forward = NULL;
|
||||
if (master >= 0) {
|
||||
if (!terminal_is_dumb()) {
|
||||
r = osc_context_open_vm(arg_machine, /* ret_seq= */ NULL, &osc_context_id);
|
||||
if (r < 0)
|
||||
return r;
|
||||
}
|
||||
|
||||
r = pty_forward_new(event, master, ptyfwd_flags, &forward);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to create PTY forwarder: %m");
|
||||
|
|
|
@ -6406,11 +6406,11 @@ class NetworkdRATests(unittest.TestCase, Utilities):
|
|||
|
||||
for i in [100, 200, 300, 512, 1024, 2048]:
|
||||
if i not in [metric_1, metric_2]:
|
||||
self.assertNotIn(f'{i}', output)
|
||||
self.assertNotIn(f'metric {i} ', output)
|
||||
|
||||
for i in ['low', 'medium', 'high']:
|
||||
if i not in [preference_1, preference_2]:
|
||||
self.assertNotIn(f'{i}', output)
|
||||
self.assertNotIn(f'pref {i}', output)
|
||||
|
||||
def test_router_preference(self):
|
||||
copy_network_unit('25-veth-client.netdev',
|
||||
|
|
Loading…
Reference in New Issue