Stopping stay-awake sends TERM to the held process, which was an exec'd sleep. systemd-inhibit reports a child killed by a signal as an error, so every update ended with "'/usr/bin/setpriv' terminated by signal TERM." The held process now stays a shell that traps TERM, kills its sleep, and exits cleanly. Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
597 lines
20 KiB
Bash
Executable File
597 lines
20 KiB
Bash
Executable File
#!/bin/bash -p
|
|
|
|
# omarchy:summary=Manage sleep and idle inhibition during an update
|
|
# omarchy:args=<start|stop>
|
|
# omarchy:hidden=true
|
|
|
|
if [[ $- != *p* ]]; then
|
|
echo "Refusing an unsafe Bash startup." >&2
|
|
exit 126
|
|
fi
|
|
|
|
security_entrypoint=$(/usr/bin/readlink -e -- "${BASH_SOURCE[0]}") || exit 126
|
|
source "${security_entrypoint%/*}/omarchy-security-functions" || exit 126
|
|
omarchy_security_require_privileged_bash_startup || exit 126
|
|
set -e
|
|
omarchy_security_sanitize_bash_environment "$0" "$@"
|
|
omarchy_security_require_source_root "$0"
|
|
# omarchy update authorizes once and revokes when it is done. Revoking here
|
|
# would throw that away and prompt again for the rest of the update.
|
|
update_sudo_session=0
|
|
if [[ ${OMARCHY_UPDATE_SUDO_SESSION:-} == "1" ]]; then
|
|
update_sudo_session=1
|
|
omarchy_security_revoke_sudo_timestamp() { :; }
|
|
fi
|
|
# Traps first, so a signal or failure during the entry revocation still
|
|
# exits through the cleanup path.
|
|
omarchy_security_install_sudo_cleanup_traps
|
|
omarchy_security_revoke_sudo_timestamp || exit 1
|
|
omarchy_security_enable_no_update_sudo
|
|
|
|
stay_awake_state="$HOME/.local/state/omarchy/indicators/stay-awake"
|
|
caller_uid=""
|
|
state_base=""
|
|
state_dir=""
|
|
idle_owner_file=""
|
|
inhibit_pid_file=""
|
|
launch_control_file=""
|
|
launch_pending=0
|
|
launch_token=""
|
|
|
|
fail_state_boundary() {
|
|
echo "Refusing to use an unsafe Omarchy update inhibitor state path." >&2
|
|
return 1
|
|
}
|
|
|
|
directory_is_private() {
|
|
local directory="$1"
|
|
local expected_owner="$2"
|
|
local legacy="${3:-0}"
|
|
local canonical=""
|
|
local owner=""
|
|
local mode=""
|
|
|
|
[[ -d $directory && ! -L $directory ]] || return 1
|
|
canonical=$(readlink -e -- "$directory") || return 1
|
|
[[ $canonical == "$directory" ]] || return 1
|
|
read -r owner mode < <(stat -Lc '%u %a' -- "$directory") || return 1
|
|
[[ $owner == "$expected_owner" ]] || return 1
|
|
[[ $mode == "700" ]] || (( legacy == 1 && (8#$mode & 022) == 0 ))
|
|
}
|
|
|
|
root_owned_parent_chain() {
|
|
local directory="$1"
|
|
local parent owner mode type canonical
|
|
|
|
parent=$(/usr/bin/dirname -- "$directory") || return 1
|
|
while :; do
|
|
[[ -d $parent && ! -L $parent ]] || return 1
|
|
canonical=$(/usr/bin/readlink -e -- "$parent") || return 1
|
|
[[ $canonical == "$parent" ]] || return 1
|
|
read -r owner mode type < <(/usr/bin/stat -Lc '%u %a %F' -- "$parent") || return 1
|
|
[[ $owner == 0 && $type == "directory" ]] || return 1
|
|
! ((8#$mode & 022)) || return 1
|
|
[[ $parent == / ]] && break
|
|
parent=$(/usr/bin/dirname -- "$parent") || return 1
|
|
done
|
|
}
|
|
|
|
runtime_directory_is_private() {
|
|
local directory="$1" expected_owner="$2"
|
|
directory_is_private "$directory" "$expected_owner" &&
|
|
root_owned_parent_chain "$directory"
|
|
}
|
|
|
|
ensure_private_directory() {
|
|
local directory="$1"
|
|
|
|
if [[ ! -e $directory && ! -L $directory ]]; then
|
|
mkdir -m 700 -- "$directory" 2>/dev/null || true
|
|
fi
|
|
# Older helpers used mkdir -p with the caller's umask. Tighten only a
|
|
# canonical, caller-owned directory that other accounts cannot write.
|
|
directory_is_private "$directory" "$caller_uid" 1 || return 1
|
|
chmod 700 -- "$directory" || return 1
|
|
directory_is_private "$directory" "$caller_uid"
|
|
}
|
|
|
|
initialize_state_boundary() {
|
|
local canonical_tmp=""
|
|
local tmp_owner=""
|
|
local tmp_mode=""
|
|
|
|
caller_uid="$EUID"
|
|
[[ $caller_uid =~ ^[0-9]+$ ]] || return 1
|
|
|
|
if [[ -n ${XDG_RUNTIME_DIR:-} ]]; then
|
|
runtime_directory_is_private "$XDG_RUNTIME_DIR" "$caller_uid" || fail_state_boundary
|
|
state_base="$XDG_RUNTIME_DIR"
|
|
else
|
|
[[ -d /tmp && ! -L /tmp ]] || fail_state_boundary
|
|
canonical_tmp=$(readlink -e -- /tmp) || fail_state_boundary
|
|
read -r tmp_owner tmp_mode < <(stat -Lc '%u %a' -- /tmp) || fail_state_boundary
|
|
[[ $canonical_tmp == "/tmp" && $tmp_owner == "0" && $tmp_mode == "1777" ]] || fail_state_boundary
|
|
|
|
state_base="/tmp/omarchy-$caller_uid"
|
|
ensure_private_directory "$state_base" || fail_state_boundary
|
|
fi
|
|
|
|
state_dir="$state_base/omarchy-update-stay-awake"
|
|
idle_owner_file="$state_dir/idle-owner"
|
|
inhibit_pid_file="$state_dir/inhibit-pid"
|
|
launch_control_file="$state_dir/launch-control"
|
|
}
|
|
|
|
state_file_is_private() {
|
|
local state_file="$1"
|
|
local legacy="${2:-0}"
|
|
local owner=""
|
|
local mode=""
|
|
local links=""
|
|
|
|
[[ -f $state_file && ! -L $state_file ]] || return 1
|
|
read -r owner mode links < <(stat -Lc '%u %a %h' -- "$state_file") || return 1
|
|
[[ $owner == "$caller_uid" && $links == "1" ]] || return 1
|
|
[[ $mode == "600" ]] || (( legacy == 1 && (8#$mode & 022) == 0 ))
|
|
}
|
|
|
|
read_state_record() {
|
|
local state_file="$1"
|
|
local legacy="${2:-0}"
|
|
local records=()
|
|
local file_size=""
|
|
local LC_ALL=C
|
|
|
|
state_file_is_private "$state_file" "$legacy" || return 1
|
|
mapfile -t records <"$state_file" || return 1
|
|
(( ${#records[@]} == 1 )) || return 1
|
|
file_size=$(stat -Lc '%s' -- "$state_file") || return 1
|
|
(( file_size == ${#records[0]} + 1 )) || return 1
|
|
state_file_is_private "$state_file" "$legacy" || return 1
|
|
printf '%s\n' "${records[0]}"
|
|
}
|
|
|
|
atomic_write_state() {
|
|
local state_file="$1"
|
|
local record="$2"
|
|
local temporary=""
|
|
|
|
[[ $record != *$'\n'* ]] || return 1
|
|
temporary=$(mktemp "$state_dir/.${state_file##*/}.XXXXXXXX") || return 1
|
|
chmod 600 "$temporary" || {
|
|
rm -f -- "$temporary"
|
|
return 1
|
|
}
|
|
if ! printf '%s\n' "$record" >"$temporary" || ! state_file_is_private "$temporary"; then
|
|
rm -f -- "$temporary"
|
|
return 1
|
|
fi
|
|
if [[ -e $state_file || -L $state_file ]]; then
|
|
state_file_is_private "$state_file" || {
|
|
rm -f -- "$temporary"
|
|
return 1
|
|
}
|
|
fi
|
|
mv -fT -- "$temporary" "$state_file" || {
|
|
rm -f -- "$temporary"
|
|
return 1
|
|
}
|
|
state_file_is_private "$state_file"
|
|
}
|
|
|
|
rollback_pending_launch() {
|
|
local control_fd=""
|
|
local inhibit_record=""
|
|
local inhibit_pid=""
|
|
local recorded_start_time=""
|
|
local recorded_owner=""
|
|
local token=""
|
|
|
|
(( launch_pending == 1 )) || return 0
|
|
|
|
if [[ -e $launch_control_file || -L $launch_control_file ]]; then
|
|
state_file_is_private "$launch_control_file" || return 1
|
|
exec {control_fd}<>"$launch_control_file" || return 1
|
|
/usr/bin/flock -x "$control_fd" || {
|
|
exec {control_fd}>&-
|
|
return 1
|
|
}
|
|
: >"/proc/self/fd/$control_fd"
|
|
printf 'cancelled %s\n' "$launch_token" >&"$control_fd"
|
|
rm -f -- "$launch_control_file"
|
|
/usr/bin/flock -u "$control_fd"
|
|
exec {control_fd}>&-
|
|
fi
|
|
|
|
inhibit_record=$(read_state_record "$inhibit_pid_file" 2>/dev/null || true)
|
|
if [[ $inhibit_record =~ ^1\ ([1-9][0-9]{0,18})\ ([1-9][0-9]{0,18})\ ([0-9]{1,10})\ ($launch_token)$ ]]; then
|
|
inhibit_pid="${BASH_REMATCH[1]}"
|
|
recorded_start_time="${BASH_REMATCH[2]}"
|
|
recorded_owner="${BASH_REMATCH[3]}"
|
|
token="${BASH_REMATCH[4]}"
|
|
if process_matches "$inhibit_pid" "$recorded_start_time" "$recorded_owner" "$token"; then
|
|
discard_launched_inhibitor "$inhibit_pid" "$recorded_start_time" "$recorded_owner" "$token"
|
|
fi
|
|
[[ $(read_state_record "$inhibit_pid_file" 2>/dev/null || true) != "$inhibit_record" ]] ||
|
|
rm -f -- "$inhibit_pid_file"
|
|
fi
|
|
|
|
# A signal may interrupt either parent-side atomic write before its rename.
|
|
rm -f -- "$state_dir"/.launch-control.* "$state_dir"/.idle-owner.*
|
|
stop_locked || return 1
|
|
launch_pending=0
|
|
}
|
|
|
|
cleanup_pending_launch() {
|
|
local status=$?
|
|
|
|
trap - EXIT HUP INT TERM
|
|
if ! rollback_pending_launch; then
|
|
echo "Failed to roll back the pending Omarchy update sleep inhibitor." >&2
|
|
(( status != 0 )) || status=1
|
|
fi
|
|
omarchy_security_exit_with_revoked_sudo "$status" \
|
|
"Failed to invalidate sudo credentials after the update sleep inhibitor."
|
|
}
|
|
|
|
process_start_time() {
|
|
local process_pid="$1"
|
|
local process_stat=""
|
|
local stat_fields=()
|
|
|
|
[[ -r /proc/$process_pid/stat ]] || return 1
|
|
process_stat=$(</proc/"$process_pid"/stat)
|
|
process_stat="${process_stat##*) }"
|
|
read -r -a stat_fields <<<"$process_stat"
|
|
(( ${#stat_fields[@]} > 19 )) || return 1
|
|
[[ ${stat_fields[19]} =~ ^[0-9]+$ ]] || return 1
|
|
printf '%s\n' "${stat_fields[19]}"
|
|
}
|
|
|
|
process_owner() {
|
|
local process_pid="$1"
|
|
local token="$2"
|
|
local owner=""
|
|
|
|
if [[ -n $token ]]; then
|
|
owner=$(stat -Lc '%u' -- "/proc/$process_pid") || return 1
|
|
else
|
|
# Legacy state can name sudo: its real UID remains the invoking account,
|
|
# even when /proc ownership reflects its effective root credentials.
|
|
owner=$(awk '/^Uid:/ { print $2; exit }' "/proc/$process_pid/status") || return 1
|
|
fi
|
|
[[ $owner =~ ^[0-9]+$ ]] || return 1
|
|
printf '%s\n' "$owner"
|
|
}
|
|
|
|
process_has_token() {
|
|
local process_pid="$1"
|
|
local token="$2"
|
|
local argument=""
|
|
local expected="--why=Omarchy update in progress [$token]"
|
|
|
|
[[ -r /proc/$process_pid/cmdline ]] || return 1
|
|
while IFS= read -r -d '' argument; do
|
|
[[ $argument == "$expected" ]] && return 0
|
|
done <"/proc/$process_pid/cmdline"
|
|
return 1
|
|
}
|
|
|
|
process_identity() {
|
|
local process_pid="$1"
|
|
local token="$2"
|
|
local start_before=""
|
|
local start_after=""
|
|
local owner_before=""
|
|
local owner_after=""
|
|
|
|
start_before=$(process_start_time "$process_pid") || return 1
|
|
owner_before=$(process_owner "$process_pid" "$token") || return 1
|
|
[[ -z $token ]] || process_has_token "$process_pid" "$token" || return 1
|
|
start_after=$(process_start_time "$process_pid") || return 1
|
|
owner_after=$(process_owner "$process_pid" "$token") || return 1
|
|
[[ $start_before == "$start_after" && $owner_before == "$owner_after" ]] || return 1
|
|
printf '%s %s\n' "$start_before" "$owner_before"
|
|
}
|
|
|
|
process_matches() {
|
|
local process_pid="$1"
|
|
local expected_start="$2"
|
|
local expected_owner="$3"
|
|
local token="$4"
|
|
local identity=""
|
|
|
|
identity=$(process_identity "$process_pid" "$token" 2>/dev/null) || return 1
|
|
[[ $identity == "$expected_start $expected_owner" ]]
|
|
}
|
|
|
|
process_state() {
|
|
local process_pid="$1"
|
|
local process_stat=""
|
|
|
|
[[ -r /proc/$process_pid/stat ]] || return 1
|
|
process_stat=$(</proc/"$process_pid"/stat)
|
|
process_stat="${process_stat##*) }"
|
|
printf '%s\n' "${process_stat%% *}"
|
|
}
|
|
|
|
discard_launched_inhibitor() {
|
|
local inhibit_pid="$1"
|
|
local recorded_start_time="$2"
|
|
local recorded_owner="$3"
|
|
local token="$4"
|
|
|
|
signal_inhibitor "$inhibit_pid" "$recorded_start_time" "$recorded_owner" "$token" TERM || true
|
|
for (( attempt = 0; attempt < 25; attempt++ )); do
|
|
process_matches "$inhibit_pid" "$recorded_start_time" "$recorded_owner" "$token" || break
|
|
[[ $(process_state "$inhibit_pid" 2>/dev/null || true) == "Z" ]] && break
|
|
sleep 0.02
|
|
done
|
|
if process_matches "$inhibit_pid" "$recorded_start_time" "$recorded_owner" "$token" &&
|
|
[[ $(process_state "$inhibit_pid" 2>/dev/null || true) != "Z" ]]; then
|
|
signal_inhibitor "$inhibit_pid" "$recorded_start_time" "$recorded_owner" "$token" KILL || true
|
|
fi
|
|
|
|
if [[ ! -e /proc/$inhibit_pid ]] || [[ $(process_state "$inhibit_pid" 2>/dev/null || true) == "Z" ]]; then
|
|
wait "$inhibit_pid" 2>/dev/null || true
|
|
fi
|
|
}
|
|
|
|
signal_inhibitor() {
|
|
local inhibit_pid="$1"
|
|
local recorded_start_time="$2"
|
|
local recorded_owner="$3"
|
|
local token="$4"
|
|
local signal="$5"
|
|
|
|
[[ $recorded_owner == "$caller_uid" ]] || return 1
|
|
[[ $signal == "TERM" || $signal == "KILL" ]] || return 1
|
|
process_matches "$inhibit_pid" "$recorded_start_time" "$recorded_owner" "$token" || return 1
|
|
builtin kill -s "$signal" -- "$inhibit_pid" 2>/dev/null
|
|
}
|
|
|
|
terminate_inhibitor() {
|
|
local inhibit_pid="$1"
|
|
local recorded_start_time="$2"
|
|
local recorded_owner="$3"
|
|
local token="$4"
|
|
|
|
signal_inhibitor "$inhibit_pid" "$recorded_start_time" "$recorded_owner" "$token" TERM || true
|
|
|
|
for (( attempt = 0; attempt < 50; attempt++ )); do
|
|
process_matches "$inhibit_pid" "$recorded_start_time" "$recorded_owner" "$token" || return 0
|
|
[[ $(process_state "$inhibit_pid" 2>/dev/null || true) != "Z" ]] || return 0
|
|
sleep 0.02
|
|
done
|
|
|
|
process_matches "$inhibit_pid" "$recorded_start_time" "$recorded_owner" "$token" || return 0
|
|
[[ $(process_state "$inhibit_pid" 2>/dev/null || true) == "Z" ]] && return 0
|
|
return 1
|
|
}
|
|
|
|
stop_locked() {
|
|
local inhibit_record=""
|
|
local inhibit_pid=""
|
|
local recorded_start_time=""
|
|
local recorded_owner=""
|
|
local token=""
|
|
local idle_owner=""
|
|
local current_idle_owner=""
|
|
local current_start=""
|
|
local failed=0
|
|
local remove_inhibit_state=1
|
|
|
|
if [[ ! -e $state_dir && ! -L $state_dir ]]; then
|
|
return 0
|
|
fi
|
|
if ! ensure_private_directory "$state_dir"; then
|
|
fail_state_boundary
|
|
return 1
|
|
fi
|
|
|
|
if [[ -e $idle_owner_file || -L $idle_owner_file ]]; then
|
|
idle_owner=$(read_state_record "$idle_owner_file" 1 2>/dev/null || true)
|
|
if [[ $idle_owner =~ ^[0-9]+:[0-9]+:[0-9]+$ ]]; then
|
|
if [[ -f $stay_awake_state ]]; then
|
|
current_idle_owner=$(<"$stay_awake_state")
|
|
fi
|
|
if [[ $current_idle_owner == "$idle_owner" ]]; then
|
|
omarchy-toggle-idle allow-idle >/dev/null 2>&1 || true
|
|
fi
|
|
else
|
|
echo "Ignoring unsafe Omarchy update idle ownership state." >&2
|
|
failed=1
|
|
fi
|
|
rm -f -- "$idle_owner_file"
|
|
fi
|
|
|
|
if [[ -e $inhibit_pid_file || -L $inhibit_pid_file ]]; then
|
|
inhibit_record=$(read_state_record "$inhibit_pid_file" 1 2>/dev/null || true)
|
|
if state_file_is_private "$inhibit_pid_file" &&
|
|
[[ $inhibit_record =~ ^1\ ([1-9][0-9]{0,18})\ ([1-9][0-9]{0,18})\ ([0-9]{1,10})\ ([0-9a-f]{32})$ ]]; then
|
|
inhibit_pid="${BASH_REMATCH[1]}"
|
|
recorded_start_time="${BASH_REMATCH[2]}"
|
|
recorded_owner="${BASH_REMATCH[3]}"
|
|
token="${BASH_REMATCH[4]}"
|
|
elif [[ $inhibit_record =~ ^([1-9][0-9]{0,18})\ ([1-9][0-9]{0,18})$ ]]; then
|
|
# The first update starts the old helper and installs this one before
|
|
# cleanup. Its protected same-account record predates launch tokens.
|
|
inhibit_pid="${BASH_REMATCH[1]}"
|
|
recorded_start_time="${BASH_REMATCH[2]}"
|
|
recorded_owner="$caller_uid"
|
|
if [[ -e /proc/$inhibit_pid && $(process_state "$inhibit_pid" 2>/dev/null || true) != "Z" ]] &&
|
|
! process_matches "$inhibit_pid" "$recorded_start_time" "$recorded_owner" ""; then
|
|
current_start=$(process_start_time "$inhibit_pid" 2>/dev/null || true)
|
|
if [[ -z $current_start || $current_start == "$recorded_start_time" ]]; then
|
|
echo "Cannot verify permission to stop the legacy update inhibitor; retained its state for recovery." >&2
|
|
failed=1
|
|
remove_inhibit_state=0
|
|
fi
|
|
fi
|
|
else
|
|
echo "Ignoring unsafe Omarchy update sleep inhibitor state." >&2
|
|
failed=1
|
|
fi
|
|
if [[ -n $inhibit_pid ]] && (( remove_inhibit_state == 1 )); then
|
|
if process_matches "$inhibit_pid" "$recorded_start_time" "$recorded_owner" "$token"; then
|
|
if ! terminate_inhibitor "$inhibit_pid" "$recorded_start_time" "$recorded_owner" "$token"; then
|
|
echo "Failed to stop the Omarchy update sleep inhibitor." >&2
|
|
failed=1
|
|
remove_inhibit_state=0
|
|
fi
|
|
fi
|
|
fi
|
|
(( remove_inhibit_state == 0 )) || rm -f -- "$inhibit_pid_file"
|
|
fi
|
|
|
|
rmdir "$state_dir" 2>/dev/null || true
|
|
(( failed == 0 ))
|
|
}
|
|
|
|
start_locked() {
|
|
local idle_owner="$$:$RANDOM:$RANDOM"
|
|
local token=""
|
|
local launcher_pid=""
|
|
local inhibit_record=""
|
|
local inhibit_pid=""
|
|
local inhibit_start_time=""
|
|
local inhibit_owner=""
|
|
local readiness_attempts=0
|
|
|
|
stop_locked || return 1
|
|
ensure_private_directory "$state_dir" || fail_state_boundary
|
|
|
|
token=$(LC_ALL=C /usr/bin/od -An -N16 -tx1 /dev/urandom | /usr/bin/tr -d ' \n')
|
|
[[ $token =~ ^[0-9a-f]{32}$ ]] || return 1
|
|
launch_token="$token"
|
|
launch_pending=1
|
|
trap cleanup_pending_launch EXIT
|
|
atomic_write_state "$launch_control_file" "active $token" || return 1
|
|
|
|
# The child is identifiable by the token in its arguments from the moment it
|
|
# starts. It stays a shell so stop's TERM ends it with a clean exit, since
|
|
# systemd-inhibit reports a child killed by a signal as an error.
|
|
local hold_command=(
|
|
/usr/bin/systemd-inhibit --what=sleep:idle --who=omarchy-update
|
|
--why="Omarchy update in progress [$token]" --mode=block
|
|
/usr/bin/setpriv --reuid "$caller_uid" --regid "$(/usr/bin/id -g)" --clear-groups
|
|
/usr/bin/bash -p -c '
|
|
set -e
|
|
umask 077
|
|
state_dir=$1
|
|
token=$2
|
|
owner=$3
|
|
control=$4
|
|
temporary=""
|
|
cleanup() { [[ -z $temporary ]] || /usr/bin/rm -f -- "$temporary"; }
|
|
trap cleanup EXIT
|
|
read -r process_stat <"/proc/$$/stat"
|
|
process_stat=${process_stat##*) }
|
|
read -r -a fields <<<"$process_stat"
|
|
temporary=$(/usr/bin/mktemp "$state_dir/.inhibit-pid.XXXXXXXX")
|
|
/usr/bin/chmod 600 "$temporary"
|
|
printf "1 %s %s %s %s\n" "$$" "${fields[19]}" "$owner" "$token" >"$temporary"
|
|
exec {control_fd}<"$control"
|
|
/usr/bin/flock -x "$control_fd"
|
|
IFS= read -r control_record <&"$control_fd"
|
|
[[ $control_record == "active $token" ]]
|
|
/usr/bin/mv -fT -- "$temporary" "$state_dir/inhibit-pid"
|
|
temporary=""
|
|
/usr/bin/flock -u "$control_fd"
|
|
exec {control_fd}>&-
|
|
trap - EXIT
|
|
trap "kill \$!; exit 0" TERM
|
|
/usr/bin/sleep infinity &
|
|
wait
|
|
' "--why=Omarchy update in progress [$token]" "$state_dir" "$token" "$caller_uid" "$launch_control_file"
|
|
)
|
|
|
|
if (( EUID == 0 )); then
|
|
(
|
|
[[ -z ${OMARCHY_UPDATE_LOCK_FD:-} ]] || exec {OMARCHY_UPDATE_LOCK_FD}>&-
|
|
exec {state_lock_fd}>&-
|
|
exec "${hold_command[@]}"
|
|
) &
|
|
launcher_pid=$!
|
|
elif [[ -t 0 ]] || { (( update_sudo_session )) && /usr/bin/sudo -n -N /usr/bin/true 2>/dev/null; }; then
|
|
# Inside an update without a terminal, reuse its authorization
|
|
# non-interactively rather than asking again through polkit.
|
|
sudo_options=(-N -b)
|
|
[[ -t 0 ]] || sudo_options=(-n -N -b)
|
|
if ! (
|
|
[[ -z ${OMARCHY_UPDATE_LOCK_FD:-} ]] || exec {OMARCHY_UPDATE_LOCK_FD}>&-
|
|
exec {state_lock_fd}>&-
|
|
exec /usr/bin/sudo "${sudo_options[@]}" -- "${hold_command[@]}"
|
|
); then
|
|
return 1
|
|
fi
|
|
else
|
|
(
|
|
[[ -z ${OMARCHY_UPDATE_LOCK_FD:-} ]] || exec {OMARCHY_UPDATE_LOCK_FD}>&-
|
|
exec {state_lock_fd}>&-
|
|
exec /usr/bin/pkexec "${hold_command[@]}"
|
|
) &
|
|
launcher_pid=$!
|
|
fi
|
|
|
|
while :; do
|
|
inhibit_record=$(read_state_record "$inhibit_pid_file" 2>/dev/null || true)
|
|
if [[ $inhibit_record =~ ^1\ ([1-9][0-9]{0,18})\ ([1-9][0-9]{0,18})\ ([0-9]{1,10})\ ($token)$ ]]; then
|
|
inhibit_pid="${BASH_REMATCH[1]}"
|
|
inhibit_start_time="${BASH_REMATCH[2]}"
|
|
inhibit_owner="${BASH_REMATCH[3]}"
|
|
process_matches "$inhibit_pid" "$inhibit_start_time" "$inhibit_owner" "$token" && break
|
|
fi
|
|
if [[ -n $launcher_pid ]] && ! kill -0 "$launcher_pid" 2>/dev/null; then
|
|
wait "$launcher_pid" || return 1
|
|
echo "The update sleep inhibitor did not start." >&2
|
|
return 1
|
|
fi
|
|
(( readiness_attempts += 1 ))
|
|
if [[ -z $launcher_pid ]] && (( EUID != 0 && readiness_attempts >= 100 )); then
|
|
echo "The update sleep inhibitor did not become ready." >&2
|
|
return 1
|
|
fi
|
|
/usr/bin/sleep 0.05
|
|
done
|
|
|
|
if [[ -e $launch_control_file || -L $launch_control_file ]]; then
|
|
state_file_is_private "$launch_control_file" || return 1
|
|
rm -f -- "$launch_control_file"
|
|
fi
|
|
if [[ ! -f $stay_awake_state ]]; then
|
|
atomic_write_state "$idle_owner_file" "$idle_owner" || return 1
|
|
mkdir -p "$(dirname "$stay_awake_state")" || return 1
|
|
# The idle toggle uses this marker. Publish its owner in the same write so
|
|
# cancellation cannot leave an unowned Stay Awake setting behind.
|
|
if ! printf '%s\n' "$idle_owner" >"$stay_awake_state"; then
|
|
omarchy-toggle-idle allow-idle >/dev/null 2>&1 || true
|
|
return 1
|
|
fi
|
|
fi
|
|
launch_pending=0
|
|
omarchy_security_install_sudo_cleanup_traps
|
|
}
|
|
|
|
case "${1:-}" in
|
|
start | stop) ;;
|
|
*)
|
|
echo "Usage: omarchy-update-stay-awake <start|stop>" >&2
|
|
exit 2
|
|
;;
|
|
esac
|
|
|
|
initialize_state_boundary
|
|
exec {state_lock_fd}<"$state_base" || fail_state_boundary
|
|
flock -x "$state_lock_fd" || fail_state_boundary
|
|
directory_is_private "$state_base" "$caller_uid" || fail_state_boundary
|
|
|
|
case "$1" in
|
|
start)
|
|
start_locked
|
|
;;
|
|
stop)
|
|
stop_locked
|
|
;;
|
|
esac
|