From 24e380651c55e133b9fa214457f5a607bc0afdc8 Mon Sep 17 00:00:00 2001 From: Hojun-Cho Date: Fri, 14 Aug 2026 03:30:29 +0900 Subject: [PATCH] run: replace only owned launcher instance --- README.md | 9 ++- run.sh | 194 +++++++++++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 199 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index c1b6c56..57c1889 100644 --- a/README.md +++ b/README.md @@ -129,9 +129,12 @@ font-file arguments replace these defaults: that it starts. It waits for that daemon's IPC socket and IBus address file before reporting success. With `DISPLAY` set it then starts XIM; without a display it keeps the headless daemon in the foreground and does not start -XIM. A signal or XIM exit stops and reaps the launcher's daemon. The -launcher never replaces an existing daemon: an occupied per-user socket is a -visible startup failure. +XIM. A signal or XIM exit stops and reaps the launcher's daemon. On repeated +invocation, it stops a previous live `run.sh` only after confirming that the +old launcher directly owns the daemon serving the same IPC and IBus +endpoints and that the daemon carries that launcher's PID and process-start +identity. A manually started or orphaned daemon is left untouched and +reported as a visible startup failure. The IPC socket is placed at `$XDG_RUNTIME_DIR/strans.sock` when `XDG_RUNTIME_DIR` is an absolute path. Otherwise strans uses the per-user diff --git a/run.sh b/run.sh index 8e371f0..602b51c 100755 --- a/run.sh +++ b/run.sh @@ -1,6 +1,8 @@ #!/bin/sh cd "$(dirname "$0")" || exit 1 +root=$(pwd -P) || exit 1 +uid=$(id -u) || exit 1 if test "$#" -eq 0; then set -- @@ -46,6 +48,10 @@ fi strans_pid= xim_pid= +old_daemon_pid= +old_daemon_start= +old_launcher_pid= +old_launcher_start= cleanup() { @@ -83,12 +89,198 @@ ibusready() return 1 } +procuid() +{ + { + while IFS=' ' read -r key real effective saved filesystem rest; do + case $key in + Uid:) + test "$real" = "$effective" && + test "$real" = "$saved" && + test "$real" = "$filesystem" || return 1 + printf '%s\n' "$real" + return 0 + ;; + esac + done <"/proc/$1/status" + } 2>/dev/null + return 1 +} + +procparent() +{ + { + while IFS=' ' read -r key value rest; do + case $key in + PPid:) printf '%s\n' "$value"; return 0 ;; + esac + done <"/proc/$1/status" + } 2>/dev/null + return 1 +} + +procstart() +{ + proc_stat= + { IFS= read -r proc_stat <"/proc/$1/stat"; } 2>/dev/null || return 1 + proc_stat=${proc_stat##*) } + set -- $proc_stat + test "$#" -ge 20 || return 1 + test "$1" != Z || return 1 + printf '%s\n' "${20}" +} + +sameproc() +{ + test -n "$2" || return 1 + proc_now=$(procstart "$1") || return 1 + test "$proc_now" = "$2" +} + +listeninginode() +{ + listener_inode= + listener_count=0 + { + while IFS=' ' read -r num ref proto flags type state inode path; do + if test "$flags" = 00010000 && test "$type" = 0001 && + test "$state" = 01 && test "$path" = "$1"; then + listener_inode=$inode + listener_count=$((listener_count + 1)) + fi + done /dev/null + test "$listener_count" -eq 1 || return 1 + printf '%s\n' "$listener_inode" +} + +socketowned() +{ + socket_inode=$(listeninginode "$2") || return 1 + for socket_fd in "/proc/$1/fd"/*; do + test "$(readlink "$socket_fd" 2>/dev/null)" = \ + "socket:[$socket_inode]" && return 0 + done + return 1 +} + +validdaemon() +{ + test "$(procuid "$1")" = "$uid" || return 1 + test "$(readlink "/proc/$1/cwd" 2>/dev/null)" = "$root" || return 1 + daemon_exe=$(readlink "/proc/$1/exe" 2>/dev/null) || return 1 + case $daemon_exe in + "$root/strans"|"$root/strans (deleted)") ;; + *) return 1 ;; + esac + test -S "$ipc" || return 1 + socketowned "$1" "$ipc" || return 1 + socketowned "$1" "@strans-$1" +} + +finddaemon() +{ + old_daemon_pid= + old_daemon_start= + for address in "$busdir"/*; do + test -f "$address" || continue + address_pid= + address_ok= + while IFS= read -r line; do + case $line in + IBUS_DAEMON_PID=*) address_pid=${line#IBUS_DAEMON_PID=} ;; + esac + done <"$address" + case $address_pid in + ''|*[!0-9]*) continue ;; + esac + while IFS= read -r line; do + case $line in + IBUS_ADDRESS=unix:abstract=strans-"$address_pid",*) + address_ok=1 ;; + esac + done <"$address" + test -n "$address_ok" || continue + validdaemon "$address_pid" || continue + address_start=$(procstart "$address_pid") || continue + validdaemon "$address_pid" || continue + old_daemon_pid=$address_pid + old_daemon_start=$address_start + return 0 + done + return 1 +} + +findlauncher() +{ + launcher_env=$({ tr '\000' '\n' <"/proc/$old_daemon_pid/environ"; } \ + 2>/dev/null) || return 1 + old_launcher_pid=$(printf '%s\n' "$launcher_env" | + sed -n 's/^STRANS_RUN_OWNER_PID=//p') + old_launcher_start=$(printf '%s\n' "$launcher_env" | + sed -n 's/^STRANS_RUN_OWNER_START=//p') + case $old_launcher_pid in + ''|*[!0-9]*|0|1|"$$") return 1 ;; + esac + case $old_launcher_start in + ''|*[!0-9]*) return 1 ;; + esac + test "$(procuid "$old_launcher_pid")" = "$uid" || return 1 + test "$(readlink "/proc/$old_launcher_pid/cwd" 2>/dev/null)" = "$root" || + return 1 + sameproc "$old_launcher_pid" "$old_launcher_start" || return 1 + sameproc "$old_daemon_pid" "$old_daemon_start" || return 1 + test "$(procparent "$old_daemon_pid")" = "$old_launcher_pid" || return 1 + return 0 +} + +stoplauncher() +{ + sameproc "$old_daemon_pid" "$old_daemon_start" || return 1 + test "$(procparent "$old_daemon_pid")" = "$old_launcher_pid" || return 1 + sameproc "$old_launcher_pid" "$old_launcher_start" || return 1 + kill "$old_launcher_pid" 2>/dev/null || return 1 + echo "run.sh: stopping previous launcher $old_launcher_pid (daemon $old_daemon_pid)" + + stop_attempt=0 + while test "$stop_attempt" -lt 10; do + if ! sameproc "$old_launcher_pid" "$old_launcher_start" && + ! sameproc "$old_daemon_pid" "$old_daemon_start"; then + return 0 + fi + stop_attempt=$((stop_attempt + 1)) + if test "$stop_attempt" -lt 10; then + sleep 1 + fi + done + echo "run.sh: previous launcher did not stop within 10 seconds" >&2 + return 1 +} + trap cleanup 0 trap 'exit 129' 1 trap 'exit 130' 2 trap 'exit 143' 15 -./strans map "$@" & +launcher_start=$(procstart "$$") || { + echo "run.sh: cannot read launcher process identity from /proc" >&2 + exit 1 +} + +if finddaemon; then + if ! findlauncher; then + echo "run.sh: daemon $old_daemon_pid owns the current endpoints but has no" >&2 + echo "run.sh: live run.sh owner; leaving it untouched (stop it once, then retry)" >&2 + exit 1 + fi + if ! stoplauncher; then + echo "run.sh: previous launcher did not stop cleanly; refusing to start another daemon" >&2 + exit 1 + fi +fi + +STRANS_RUN_OWNER_PID=$$ STRANS_RUN_OWNER_START=$launcher_start \ + ./strans map "$@" & strans_pid=$! attempt=0