From 169be1e1f27133d37409f800c5ddb4aa27883e10 Mon Sep 17 00:00:00 2001 From: Tw93 Date: Sat, 14 Mar 2026 08:32:11 +0800 Subject: [PATCH] fix(timeout): inherit helper state and pass checks --- cmd/analyze/delete.go | 7 +++---- cmd/status/metrics_disk.go | 17 ++++++++--------- lib/core/timeout.sh | 12 ++++++++++-- 3 files changed, 21 insertions(+), 15 deletions(-) diff --git a/cmd/analyze/delete.go b/cmd/analyze/delete.go index 3b5ff1e..2855a28 100644 --- a/cmd/analyze/delete.go +++ b/cmd/analyze/delete.go @@ -8,6 +8,7 @@ import ( "os" "os/exec" "path/filepath" + "slices" "sort" "strings" "sync/atomic" @@ -165,10 +166,8 @@ func validatePath(path string) error { return fmt.Errorf("path contains null bytes") } // Check for path traversal attempts (.. components). - for _, component := range strings.Split(path, string(filepath.Separator)) { - if component == ".." { - return fmt.Errorf("path contains traversal components: %s", path) - } + if slices.Contains(strings.Split(path, string(filepath.Separator)), "..") { + return fmt.Errorf("path contains traversal components: %s", path) } return nil } diff --git a/cmd/status/metrics_disk.go b/cmd/status/metrics_disk.go index 9f2fc48..d4cf39f 100644 --- a/cmd/status/metrics_disk.go +++ b/cmd/status/metrics_disk.go @@ -275,23 +275,22 @@ func getAPFSContainerFreeBytes(mountpoint string) (uint64, error) { } const key = "APFSContainerFree" - idx := strings.Index(out, key) - if idx == -1 { + _, rest, found := strings.Cut(out, key) + if !found { return 0, fmt.Errorf("APFSContainerFree not found") } - rest := out[idx+len(key):] - start := strings.Index(rest, "") - if start == -1 { + _, rest, found = strings.Cut(rest, "") + if !found { return 0, fmt.Errorf("APFSContainerFree value not found") } - rest = rest[start+len(""):] - end := strings.Index(rest, "") - if end == -1 { + + value, _, found := strings.Cut(rest, "") + if !found { return 0, fmt.Errorf("APFSContainerFree end tag not found") } - val, err := strconv.ParseUint(strings.TrimSpace(rest[:end]), 10, 64) + val, err := strconv.ParseUint(strings.TrimSpace(value), 10, 64) if err != nil { return 0, fmt.Errorf("failed to parse APFSContainerFree: %v", err) } diff --git a/lib/core/timeout.sh b/lib/core/timeout.sh index edd7051..06bde93 100644 --- a/lib/core/timeout.sh +++ b/lib/core/timeout.sh @@ -55,6 +55,11 @@ if [[ -z "${MO_TIMEOUT_INITIALIZED:-}" ]]; then echo "[TIMEOUT] Install coreutils for better reliability: brew install coreutils" >&2 fi + # Export so child processes inherit detected values and skip re-detection. + # Without this, children that inherit MO_TIMEOUT_INITIALIZED=1 skip the init + # block but have empty bin vars, forcing the slow shell fallback. + export MO_TIMEOUT_BIN + export MO_TIMEOUT_PERL_BIN export MO_TIMEOUT_INITIALIZED=1 fi @@ -181,7 +186,10 @@ run_with_timeout() { "$@" & local cmd_pid=$! - # Start timeout killer in background + # Start timeout killer in background. + # Redirect all FDs to /dev/null so orphaned child processes (e.g. sleep $duration) + # do not inherit open file descriptors from the caller and block output pipes + # (notably bats output capture pipes that wait for all writers to close). ( # Wait for timeout duration sleep "$duration" @@ -200,7 +208,7 @@ run_with_timeout() { kill -KILL -"$cmd_pid" 2> /dev/null || kill -KILL "$cmd_pid" 2> /dev/null || true fi fi - ) & + ) < /dev/null > /dev/null 2>&1 & local killer_pid=$! # Wait for command to complete