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.
105 lines
4.4 KiB
Bash
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
|