Skip to content
Closed
8 changes: 3 additions & 5 deletions bin/fm-backlog-receive.sh
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ LOCK_STALE_SECS=30
. "$SCRIPT_DIR/fm-tasks-axi-lib.sh"
# shellcheck source=bin/fm-wake-lib.sh
. "$SCRIPT_DIR/fm-wake-lib.sh"
# shellcheck source=bin/fm-stat-lib.sh
. "$SCRIPT_DIR/fm-stat-lib.sh"

die() { printf 'error: %s\n' "$1" >&2; exit 1; }
usage() { sed -n '2,16p' "$0" | sed 's/^# \{0,1\}//'; exit 2; }
Expand Down Expand Up @@ -56,11 +58,7 @@ list_keys() { # <file>

lock_age() {
local modified now
if [ "$(uname 2>/dev/null)" = Darwin ]; then
modified=$(stat -f '%m' "$1" 2>/dev/null) || return 1
else
modified=$(stat -c '%Y' "$1" 2>/dev/null) || return 1
fi
modified=$(fm_stat_mtime "$1") || return 1
now=$(date +%s) || return 1
case "$modified$now" in *[!0-9]*) return 1 ;; esac
printf '%s\n' "$((now - modified))"
Expand Down
15 changes: 14 additions & 1 deletion bin/fm-busy-event.sh
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,8 @@ EOF
}

SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
# shellcheck source=bin/fm-stat-lib.sh
. "$SCRIPT_DIR/fm-stat-lib.sh"
# shellcheck source=bin/fm-busy-lib.sh
. "$SCRIPT_DIR/fm-busy-lib.sh"

Expand Down Expand Up @@ -103,7 +105,18 @@ lock_acquire() {
tries=$((tries + 1))
if [ "$tries" -ge 40 ]; then
now=$(date +%s)
mtime=$(stat -f %m "$LOCK" 2>/dev/null || stat -c %Y "$LOCK" 2>/dev/null || echo "$now")
# NOT `stat -f %m ... || stat -c %Y ...`: on GNU coreutils `-f` is
# --file-system, so `stat -f %m "$LOCK"` dumps the filesystem of $LOCK to
# STDOUT and only then exits 1. Both halves of the chain share one pipe,
# so the fallback's correct answer is APPENDED to that dump and the whole
# substitution succeeds at rc=0 - the `|| echo "$now"` guard never fires
# either. $mtime came back multi-line, the arithmetic below threw, and the
# stale-lock reap was unreachable: a lock left by a holder that died
# mid-write was NEVER reclaimed on a GNU-on-Darwin host, permanently, not
# for FM_BUSY_LOCK_STALE_SECS. fm-stat-lib.sh feature-detects the binary
# and returns a bare integer or nothing (robots-ivgz).
mtime=$(fm_stat_mtime "$LOCK" 2>/dev/null) || mtime=$now
case "$mtime" in ''|*[!0-9]*) mtime=$now ;; esac
age=$((now - mtime))
if [ "$age" -ge "${FM_BUSY_LOCK_STALE_SECS:-5}" ]; then
rmdir "$LOCK" 2>/dev/null || rm -rf "$LOCK" 2>/dev/null || true
Expand Down
18 changes: 11 additions & 7 deletions bin/fm-classify-lib.sh
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,9 @@ _FM_CLASSIFY_LIB_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd 2>/dev/null)"
# or no-mistakes install; absent, it points at the real sibling script.
FM_CREW_STATE_BIN="${FM_CREW_STATE_BIN:-$_FM_CLASSIFY_LIB_DIR/fm-crew-state.sh}"

# shellcheck source=bin/fm-stat-lib.sh
. "$_FM_CLASSIFY_LIB_DIR/fm-stat-lib.sh"

# Captain-relevant status verbs. A status line carrying any of these is work
# firstmate must see. Lines without these verbs are no-verb signals: the watcher
# absorbs them only with positive provably-working evidence, while the daemon uses
Expand Down Expand Up @@ -395,14 +398,15 @@ _fm_open_decisions_cursor_path() { # <status-file>

FM_OPEN_DECISIONS_FOLD_VERSION=2

# Portable device:inode identity for the rotation/recreation check below.
# Portable device:inode identity for the rotation/recreation check below. The
# flavor comes from the binary's own dialect (bin/fm-stat-lib.sh), not from
# `uname -s`: a Darwin kernel routinely resolves `stat` to GNU coreutils, and a
# wrong flavor here makes every read look like a rotation.
# Non-zero (with empty stdout) on I/O failure: the caller at
# status_open_decisions_incremental keys its "trust the cursor" early return on
# that exit status, so it must survive.
_fm_open_decisions_file_ident() { # <file> -> "dev:inode", empty on I/O failure
local f=$1
if [ "$(uname -s 2>/dev/null)" = Darwin ]; then
LC_ALL=C stat -f '%d:%i' "$f" 2>/dev/null
else
LC_ALL=C stat -c '%d:%i' "$f" 2>/dev/null
fi
fm_stat_identity "$1"
}

status_open_decisions_incremental() { # <status-file>
Expand Down
18 changes: 11 additions & 7 deletions bin/fm-lock-lib.sh
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,10 @@
# binary through the same function so one host can never answer "is lsof here?"
# two different ways.

_FM_LOCK_LIB_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd 2>/dev/null)" || _FM_LOCK_LIB_DIR="."
# shellcheck source=bin/fm-stat-lib.sh
. "$_FM_LOCK_LIB_DIR/fm-stat-lib.sh"

fm_lock_log() {
echo "${FM_LOCK_LOG_PREFIX:-fm-lock}: $*" >&2
}
Expand Down Expand Up @@ -54,14 +58,14 @@ fm_lsof_bin() {
return 1
}

# Portable mtime in epoch seconds. Kept self-contained so this leaf lib drags in
# no wake-queue machinery when a caller only needs the staleness proof.
# Portable mtime in epoch seconds. Still drags in no wake-queue machinery when a
# caller only needs the staleness proof: fm-stat-lib.sh is a dependency-free leaf
# whose only job is answering which dialect this host's `stat` speaks. Asking
# `uname` instead is wrong - a Darwin kernel routinely resolves `stat` to GNU
# coreutils - and a wrong answer here reads every lock as unreadable, which fails
# safe but permanently refuses to reap an abandoned lock.
fm_lock_path_mtime() {
if [ "$(uname)" = Darwin ]; then
stat -f %m "$1" 2>/dev/null
else
stat -c %Y "$1" 2>/dev/null
fi
fm_stat_mtime "$1"
}

# fm_lock_lsof_holder <target>: 0 a process holds it, 1 provably none, 2 lsof
Expand Down
12 changes: 7 additions & 5 deletions bin/fm-pending-reply-lib.sh
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,8 @@ _FM_PENDING_REPLY_LIB_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd 2>/dev/n
. "$_FM_PENDING_REPLY_LIB_DIR/fm-tmux-lib.sh"
# shellcheck source=bin/fm-classify-lib.sh
. "$_FM_PENDING_REPLY_LIB_DIR/fm-classify-lib.sh"
# shellcheck source=bin/fm-stat-lib.sh
. "$_FM_PENDING_REPLY_LIB_DIR/fm-stat-lib.sh"

FM_PENDING_REPLY_SCHEMA='fm-pending-reply.v1'
FM_PENDING_REPLY_CORR_RE='corr=[A-Fa-f0-9]{16}'
Expand Down Expand Up @@ -480,11 +482,11 @@ fm_pending_reply_find_resolve_line() { # <status-file> <corr_id>
fm_pending_reply_file_signature() { # <path>
local path=$1
[ -f "$path" ] || { printf 'missing'; return 0; }
if [ "$(uname -s 2>/dev/null)" = Darwin ]; then
LC_ALL=C stat -f '%d:%i:%z:%m:%c' "$path" 2>/dev/null || printf 'unreadable'
else
LC_ALL=C stat -c '%d:%i:%s:%Y:%Z' "$path" 2>/dev/null || printf 'unreadable'
fi
# Field flavor from the binary's own dialect, not `uname -s`: a Darwin kernel
# routinely resolves `stat` to GNU coreutils, and the wrong flavor would pin
# every file at 'unreadable' - a signature that never changes, so no missed
# reply would ever be detected. See bin/fm-stat-lib.sh.
fm_stat_fingerprint "$path" || printf 'unreadable'
}

fm_pending_reply_status_set_signature() { # <status-dir>
Expand Down
40 changes: 12 additions & 28 deletions bin/fm-pr-lib.sh
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,10 @@
# The receipt binds the terminal observation to the canonical registration and
# lets a restart finish fixed-path removal without executing state-file bytes.

_FM_PR_LIB_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd 2>/dev/null)" || _FM_PR_LIB_DIR="."
# shellcheck source=bin/fm-stat-lib.sh
. "$_FM_PR_LIB_DIR/fm-stat-lib.sh"

FM_PR_PROVIDER=
FM_PR_URL=
FM_PR_HOST=
Expand Down Expand Up @@ -213,37 +217,17 @@ fm_pr_head_valid() {
[[ "$head" =~ ^[0-9a-f]{40}$|^[0-9a-f]{64}$ ]]
}

fm_pr_file_mode() {
if [ "$(uname)" = Darwin ]; then
stat -f %Lp "$1" 2>/dev/null
else
stat -c %a "$1" 2>/dev/null
fi
}
# Field flavor comes from bin/fm-stat-lib.sh, which detects what this host's
# `stat` binary actually speaks. Keying on `uname` would be wrong: a Darwin
# kernel routinely resolves `stat` to GNU coreutils (nix-darwin, or Homebrew
# coreutils ahead of /usr/bin on PATH).
fm_pr_file_mode() { fm_stat_mode "$1"; }

fm_pr_file_device() {
if [ "$(uname)" = Darwin ]; then
stat -f %d "$1" 2>/dev/null
else
stat -c %d "$1" 2>/dev/null
fi
}
fm_pr_file_device() { fm_stat_device "$1"; }

fm_pr_file_link_count() {
if [ "$(uname)" = Darwin ]; then
stat -f %l "$1" 2>/dev/null
else
stat -c %h "$1" 2>/dev/null
fi
}
fm_pr_file_link_count() { fm_stat_links "$1"; }

fm_pr_file_inode() {
if [ "$(uname)" = Darwin ]; then
stat -f %i "$1" 2>/dev/null
else
stat -c %i "$1" 2>/dev/null
fi
}
fm_pr_file_inode() { fm_stat_inode "$1"; }

fm_pr_file_identity() {
local device inode
Expand Down
10 changes: 3 additions & 7 deletions bin/fm-remote-file.sh
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"

# shellcheck source=bin/fm-wake-lib.sh
. "$SCRIPT_DIR/fm-wake-lib.sh"
# shellcheck source=bin/fm-stat-lib.sh
. "$SCRIPT_DIR/fm-stat-lib.sh"

die() { printf 'error: %s\n' "$1" >&2; exit 1; }
usage() { sed -n '2,12p' "$0" | sed 's/^# \{0,1\}//'; exit 2; }
Expand Down Expand Up @@ -75,13 +77,7 @@ snapshot_bounded_file() { # <file> <max-bytes> <destination> <size-file>
)
}

directory_identity() {
if [ "$(uname)" = Darwin ]; then
stat -f '%d:%i' . 2>/dev/null
else
stat -c '%d:%i' . 2>/dev/null
fi
}
directory_identity() { fm_stat_identity .; }

put_handoff_file() { # <home-real> <name> <max-bytes> <relative-path> <bytes> <sha256> <generation>
local home_real=$1 name=$2 max=$3 rel=$4 expected_bytes=$5 expected_hash=$6 generation=$7
Expand Down
6 changes: 3 additions & 3 deletions bin/fm-remote-inherit-push.sh
Original file line number Diff line number Diff line change
Expand Up @@ -21,14 +21,14 @@ DATA="${FM_DATA_OVERRIDE:-$FM_HOME/data}"
. "$SCRIPT_DIR/fm-secondmate-registry-lib.sh"
# shellcheck source=bin/fm-config-inherit-lib.sh
. "$SCRIPT_DIR/fm-config-inherit-lib.sh"
# shellcheck source=bin/fm-stat-lib.sh
. "$SCRIPT_DIR/fm-stat-lib.sh"

die() { printf 'error: %s\n' "$1" >&2; exit 1; }
sha256_file() {
if command -v shasum >/dev/null 2>&1; then shasum -a 256 "$1" | awk '{print $1}'; else sha256sum "$1" | awk '{print $1}'; fi
}
file_link_count() {
if [ "$(uname)" = Darwin ]; then stat -f %l "$1" 2>/dev/null; else stat -c %h "$1" 2>/dev/null; fi
}
file_link_count() { fm_stat_links "$1"; }
shared_captain_header_valid() {
local head
head=$(sed -n '1,12p' "$1" 2>/dev/null) || return 1
Expand Down
6 changes: 3 additions & 3 deletions bin/fm-remote-inherit.sh
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,12 @@ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
. "$SCRIPT_DIR/fm-wake-lib.sh"
# shellcheck source=bin/fm-config-inherit-lib.sh
. "$SCRIPT_DIR/fm-config-inherit-lib.sh"
# shellcheck source=bin/fm-stat-lib.sh
. "$SCRIPT_DIR/fm-stat-lib.sh"

die() { printf 'error: %s\n' "$1" >&2; exit 1; }
usage() { sed -n '2,10p' "$0" | sed 's/^# \{0,1\}//'; exit 2; }
file_link_count() {
if [ "$(uname)" = Darwin ]; then stat -f %l "$1" 2>/dev/null; else stat -c %h "$1" 2>/dev/null; fi
}
file_link_count() { fm_stat_links "$1"; }
sha256_file() {
if command -v shasum >/dev/null 2>&1; then shasum -a 256 "$1" | awk '{print $1}'; else sha256sum "$1" | awk '{print $1}'; fi
}
Expand Down
25 changes: 9 additions & 16 deletions bin/fm-remote-job-lib.sh
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,10 @@
# bin/fm-remote-job-reap-orphans.sh uses it to reap workers that were already
# orphaned that way.

_FM_REMOTE_JOB_LIB_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd 2>/dev/null)" || _FM_REMOTE_JOB_LIB_DIR="."
# shellcheck source=bin/fm-stat-lib.sh
. "$_FM_REMOTE_JOB_LIB_DIR/fm-stat-lib.sh"

FM_REMOTE_JOB_LABEL=dev.firstmate.remote-job
FM_REMOTE_JOB_MAX_BYTES=${FM_REMOTE_JOB_MAX_BYTES:-1048576}
FM_REMOTE_JOB_QUEUE_TIMEOUT=${FM_REMOTE_JOB_QUEUE_TIMEOUT:-360}
Expand Down Expand Up @@ -583,22 +587,11 @@ fm_remote_job_reap() { # <account-home> <id>; only removes an exact completed re
}

fm_remote_job_path_mtime() { # <path>; prints epoch seconds, or nothing and returns 1
# Do NOT infer stat's dialect from `uname`. On a nix-darwin or Homebrew-coreutils
# Mac, GNU coreutils can sit ahead of /usr/bin on the restricted child PATH, so
# `stat` is GNU even though the kernel is Darwin. GNU `stat -f` means *filesystem*
# status: it exits 0 while printing an apfs dump instead of an mtime, and every
# numeric check downstream then fails forever (the remote-job readiness probe can
# never go green on such a host).
#
# Probe the binary's own dialect instead, and order the probes GNU-first: BSD stat
# rejects `-c` with a usage error on stderr and writes nothing to stdout, so the
# fallback stays clean. The reverse order is the trap documented in fm-watch.sh -
# GNU `stat -f` would poison stdout before the fallback ever ran.
local mtime
mtime=$(stat -c %Y "$1" 2>/dev/null) || mtime=$(stat -f %m "$1" 2>/dev/null) || return 1
# Guard the contract even if some third stat dialect answers both probes.
case "$mtime" in ''|*[!0-9]*) return 1 ;; esac
printf '%s\n' "$mtime"
# Neither the platform override nor the host kernel decides stat's syntax: the
# `stat` BINARY does, and a Darwin kernel routinely resolves it to GNU
# coreutils. Getting this wrong made the readiness probe fail permanently
# (robots-xw8p). bin/fm-stat-lib.sh feature-detects the binary instead.
fm_stat_mtime "$1"
}

fm_remote_job_reap_stale() { # <account-home>
Expand Down
Loading