Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 8 additions & 1 deletion src/cm/lsp/serverLauncher.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,13 @@ const STATUS_FAILED: InstallStatus = "failed";

const DONT_ASK_TERMINAL_REQUIRED_FOR_LSP = "dontAskTerminalRequiredForLsp";

// PRoot prints `can't sanitize binding "/proc/self/fd/N"` when one of the
// session's stdio descriptors is a pipe/socket and therefore cannot be
// canonicalized (see init-sandbox.sh). Drop only that known, harmless message
// so genuine proot warnings are still surfaced in the LSP log.
const PROOT_FD_BINDING_WARNING =
/can'?t sanitize binding "\/proc\/self\/fd(?:\/[012])?"/i;

let alreadyInformed = false;

function getTerminalRequiredMessage(): string {
Expand Down Expand Up @@ -911,7 +918,7 @@ async function startInteractiveServer(
): Promise<string> {
const executor = getExecutor();
const callback: ExecutorCallback = (type, data) => {
if (type === "stderr" && /proot warning/i.test(data)) return;
if (type === "stderr" && PROOT_FD_BINDING_WARNING.test(data)) return;
if (type === "stdout" && /listening on/i.test(data)) {
signalServerReady(serverId);
}
Expand Down
40 changes: 36 additions & 4 deletions src/plugins/terminal/scripts/init-sandbox.sh
Original file line number Diff line number Diff line change
Expand Up @@ -69,19 +69,51 @@ ARGS="$ARGS -b $PREFIX/public:/root"
ARGS="$ARGS -b $PREFIX/alpine/tmp:/dev/shm"


if [ -e "/proc/self/fd" ]; then
# PRoot canonicalizes every -b host path with realpath(3). The magic links
# under /proc/self/fd only resolve when the descriptor points at a real file:
# for pipes, sockets, anon inodes or memfds the link target is not a path
# ("pipe:[123]"), so realpath(3) fails with ENOENT, proot drops the binding and
# prints `can't sanitize binding "/proc/self/fd/N"`. `[ -e ]` follows the magic
# link to the underlying inode and therefore returns true for those descriptors,
# which is why it does not filter them out. Test the link target the same way
# realpath(3) does instead.
#
# Probe through this shell's own pid ($$), not /proc/self: readlink(1) runs as a
# child process, so for fd 2 its /proc/self/fd/2 is the /dev/null of the
# `2>/dev/null` redirect below rather than the stderr proot inherits. $$ is
# unchanged by the exec at the end of this script, so it always names the
# process proot will run as.
SELF_PID=$$

can_bind() {
# Directories (e.g. /proc/self/fd) are canonicalizable as-is.
if [ -d "$1" ]; then
return 0
fi

# A /proc/self/fd/N magic link is only canonicalizable when it resolves to
# an existing absolute path; "pipe:[N]", "socket:[N]" and friends are not.
target=$(readlink "$1" 2>/dev/null) || return 1

case "$target" in
/*) [ -e "$target" ] ;;
*) return 1 ;;
esac
}

if can_bind "/proc/$SELF_PID/fd"; then
ARGS="$ARGS -b /proc/self/fd:/dev/fd"
fi

if [ -e "/proc/self/fd/0" ]; then
if can_bind "/proc/$SELF_PID/fd/0"; then
ARGS="$ARGS -b /proc/self/fd/0:/dev/stdin"
fi

if [ -e "/proc/self/fd/1" ]; then
if can_bind "/proc/$SELF_PID/fd/1"; then
ARGS="$ARGS -b /proc/self/fd/1:/dev/stdout"
fi

if [ -e "/proc/self/fd/2" ]; then
if can_bind "/proc/$SELF_PID/fd/2"; then
ARGS="$ARGS -b /proc/self/fd/2:/dev/stderr"
fi

Expand Down
15 changes: 8 additions & 7 deletions src/settings/lspServerDetail.js
Original file line number Diff line number Diff line change
Expand Up @@ -154,18 +154,19 @@ function formatStartupTimeoutValue(timeout) {
: strings["lsp-default"];
}

// PRoot prints `can't sanitize binding "/proc/self/fd/N"` when one of the
// session's stdio descriptors is a pipe/socket and therefore cannot be
// canonicalized (see init-sandbox.sh). Drop only that known, harmless message
// so genuine proot warnings still reach the UI.
const PROOT_FD_BINDING_WARNING =
/can'?t sanitize binding "\/proc\/self\/fd(?:\/[012])?"/i;

function sanitizeInstallMessage(message) {
const lines = String(message || "")
.split("\n")
.map((line) => line.trim())
.filter(Boolean)
.filter(
(line) =>
!/^proot warning:/i.test(line) &&
!line.includes(`"/proc/self/fd/0"`) &&
!line.includes(`"/proc/self/fd/1"`) &&
!line.includes(`"/proc/self/fd/2"`),
);
.filter((line) => !PROOT_FD_BINDING_WARNING.test(line));

return lines.join(" ");
}
Expand Down