Files
systemd/profile.d/80-systemd-osc-context.sh
T
Rasmus Villemoes 8c6f1c4a21 profile/systemd-osc-context: also escape $USER and $HOSTNAME
Claude pointed out that while rather unlikely, it is possible for the USER and
HOSTNAME environment variables to contain problematic characters that require
escaping. Now that that escaping no longer involves the fork+exec overhead of
calling sed, let's ensure those fields do get put through the escape routine.

Refactor __systemd_osc_context_escape so that it not only takes the value and
prints that, but also the format specifier used for emitting the field. That
way, we avoid an extra subshell (i.e. fork+pipe and all that overhead), and
combined with the previous commit, we now only spawn one subshell instead of
two per start= sequence.

That could be reduced to zero, if the "callers" were rewritten to something
like

    printf "\033]3008;start=%.64s;type=shell" "$systemd_osc_context_shell_id"
    __systemd_osc_context_common
    printf "\033\\"

but that will mean that the sequence is not emitted with a single write()
system call [which isn't really guaranteed currently either, just very likely],
so some background process could end up writing to the middle of the sequence,
thus losing that output and mangling the OSC3008 info. This in turn could be
overcome by building the whole sequence in a shell variable using 'printf -v'
and only printing at the end, but that would be a somewhat invasive change.
2026-07-21 11:21:00 +02:00

105 lines
4.4 KiB
Bash

# SPDX-License-Identifier: LGPL-2.1-or-later
# shellcheck shell=bash
# shellcheck disable=SC2016
# shellcheck disable=SC1003
# 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.
# This implements the UAPI.15 "OSC 3008: Hierarchical Context Signalling"
# specification for the shell prompt. For details see:
# https://uapi-group.org/specifications/specs/osc_context/
# This file is "activated" through systemd-tmpfiles which links it into
# /etc/profile.d/. To disable this, remove the
# /etc/profile.d/80-systemd-osc-context.sh symlink and mask the
# 20-systemd-osc-context.conf snippet (as root):
#
# test -h /etc/profile.d/80-systemd-osc-context.sh && \
# rm -v /etc/profile.d/80-systemd-osc-context.sh && \
# ln -s /dev/null /etc/tmpfiles.d/20-systemd-osc-context.conf
# Not bash?
[ -n "${BASH_VERSION:-}" ] || return 0
# If we're on a "dumb" terminal, do not install the prompt.
# Treat missing $TERM same as "dumb".
[ "${TERM:-dumb}" = "dumb" ] && return 0
# We need promptvars, otherwise the prompt strings won't undergo parameter expansion
# and we'd print them literally
shopt -q promptvars || return 0
__systemd_osc_context_escape() {
# Escape according to the OSC 3008 spec. We'll skip it when processing
# strings we are pretty sure we won't need it for, such as uuids
# (originating from /proc/sys/kernel/ or /etc/machine-id) and special shell
# variables such as $$ and $? that always expand to decimal numbers.
local systemd_format="$1"
local systemd_value="$2"
systemd_value="${systemd_value//\\/\\x5c}"
systemd_value="${systemd_value//;/\\x3b}"
systemd_value="${systemd_value//[[:cntrl:]]/⍰}"
# shellcheck disable=SC2059 # always called with fixed format strings with a single string specifier
printf "$systemd_format" "$systemd_value"
}
__systemd_osc_context_common() {
if [ -f /etc/machine-id ]; then
printf ";machineid=%.36s" "$(</etc/machine-id)"
fi
__systemd_osc_context_escape ";user=%.255s" "$USER"
__systemd_osc_context_escape ";hostname=%.255s" "$HOSTNAME"
printf ";bootid=%.36s;pid=%.20s" "$(</proc/sys/kernel/random/boot_id)" "$$"
__systemd_osc_context_escape ";cwd=%.255s" "$PWD"
}
__systemd_osc_context_precmdline() {
local systemd_exitstatus="$?" systemd_signal
# Close previous command
if [ -n "${systemd_osc_context_cmd_id:-}" ]; then
if [ "$systemd_exitstatus" -gt 128 ] && systemd_signal=$(kill -l "$systemd_exitstatus" 2>&-); then
printf "\033]3008;end=%.64s;exit=failure;status=%d;signal=SIG%s\033\\" "$systemd_osc_context_cmd_id" "$systemd_exitstatus" "$systemd_signal"
elif [ "$systemd_exitstatus" -ne 0 ]; then
printf "\033]3008;end=%.64s;exit=failure;status=%d\033\\" "$systemd_osc_context_cmd_id" "$systemd_exitstatus"
else
printf "\033]3008;end=%.64s;exit=success\033\\" "$systemd_osc_context_cmd_id"
fi
fi
# Prepare a context ID for this shell if we have none
if [ -z "${systemd_osc_context_shell_id:-}" ]; then
read -r systemd_osc_context_shell_id </proc/sys/kernel/random/uuid
fi
# Create or update the shell session
printf "\033]3008;start=%.64s;type=shell%s\033\\" "$systemd_osc_context_shell_id" "$(__systemd_osc_context_common)"
# Prepare cmd id for next command
read -r systemd_osc_context_cmd_id </proc/sys/kernel/random/uuid
}
__systemd_osc_context_ps0() {
# Skip if PROMPT_COMMAND= is cleared manually or by other profiles.
[ -n "${systemd_osc_context_cmd_id:-}" ] || return
printf "\033]3008;start=%.64s;type=command%s\033\\" "$systemd_osc_context_cmd_id" "$(__systemd_osc_context_common)"
}
if [ -n "${BASH_VERSION:-}" ]; then
# Legacy bashrc will assign PROMPT_COMMAND=, which is equivalent to assigning
# index 0 in the array. Leave an empty spot to handle this gracefully.
[ -n "$(declare -p PROMPT_COMMAND 2>/dev/null)" ] || PROMPT_COMMAND+=('')
# Whenever a new prompt is shown, close the previous command, and prepare new command
PROMPT_COMMAND+=(__systemd_osc_context_precmdline)
# PS0 is shown right after a prompt completed, but before the command is executed
PS0='$(__systemd_osc_context_ps0)'"${PS0:-}"
fi