From 7707ef366fa48c51233e7530ec4d7cddc4e457b1 Mon Sep 17 00:00:00 2001 From: Andrei Shitov Date: Tue, 23 Sep 2025 11:25:11 +0300 Subject: [PATCH 1/3] fix(detect): correct symlink resolution --- internal/detect/detect.go | 43 ++++++++++-- internal/detect/detect_test.go | 115 +++++++++++++++++++++++++++++++++ 2 files changed, 154 insertions(+), 4 deletions(-) diff --git a/internal/detect/detect.go b/internal/detect/detect.go index d5463f3..b44c849 100644 --- a/internal/detect/detect.go +++ b/internal/detect/detect.go @@ -7,9 +7,24 @@ import ( "strings" "github.com/arenadata/ad-runtime-utils/internal/config" - "github.com/arenadata/ad-runtime-utils/internal/fs" ) +func hasGlobMeta(s string) bool { + return strings.ContainsAny(s, "*?[") +} + +func checkCandidate(cand, exe string) (string, bool) { + resolved, evalErr := filepath.EvalSymlinks(cand) + if evalErr != nil || resolved == "" { + resolved = cand + } + candidateExe := filepath.Join(resolved, "bin", exe) + if _, statErr := os.Stat(candidateExe); statErr == nil { + return resolved, true + } + return "", false +} + // expandPath expands a leading '~' to the user home directory and // replaces any environment variables in the path. func expandPath(p string) string { @@ -57,11 +72,31 @@ func tryEnvVar(cfg config.RuntimeSetting, exe string) (string, bool) { func tryPaths(cfg config.RuntimeSetting, exe string) (string, bool) { for _, pat := range cfg.Paths { base := expandPath(pat) - cands, _ := filepath.Glob(base) + + if hasGlobMeta(base) { + cands, _ := filepath.Glob(base) + sort.Sort(sort.Reverse(sort.StringSlice(cands))) + for _, cand := range cands { + if p, ok := checkCandidate(cand, exe); ok { + return p, true + } + } + continue + } + + if _, statErr := os.Stat(base); statErr == nil { + if p, ok := checkCandidate(base, exe); ok { + return p, true + } + continue + } + + globPat := base + "*" + cands, _ := filepath.Glob(globPat) sort.Sort(sort.Reverse(sort.StringSlice(cands))) for _, cand := range cands { - if fs.ExistsInBin(cand, exe) { - return cand, true + if p, ok := checkCandidate(cand, exe); ok { + return p, true } } } diff --git a/internal/detect/detect_test.go b/internal/detect/detect_test.go index 1ce6467..c0820ac 100644 --- a/internal/detect/detect_test.go +++ b/internal/detect/detect_test.go @@ -148,3 +148,118 @@ func TestDetectPath_Order(t *testing.T) { t.Errorf("detectPath empty = (%q, %v), want ('', false)", got, ok) } } +func TestTryPaths_ExactSymlinkResolves(t *testing.T) { + if testing.Short() { + t.Skip("skipping in short mode") + } + tmp := t.TempDir() + target := createRuntimeDir(t, tmp, "jdk-real", "java") + + altsDir := filepath.Join(tmp, "alternatives") + if err := os.MkdirAll(altsDir, 0o755); err != nil { + t.Fatalf("mkdir: %v", err) + } + link := filepath.Join(altsDir, "jre") + if err := os.Symlink(target, link); err != nil { + t.Fatalf("symlink: %v", err) + } + + rt := config.RuntimeSetting{Paths: []string{link}} + got, ok := tryPaths(rt, "java") + if !ok { + t.Fatalf("tryPaths symlink failed") + } + if got != target { + t.Errorf("got %q, want symlink target %q", got, target) + } +} + +func TestTryPaths_ExactExistsButInvalid_NoGlobFallback(t *testing.T) { + tmp := t.TempDir() + exact := filepath.Join(tmp, "java-1.8.0") + if err := os.MkdirAll(exact, 0o755); err != nil { + t.Fatalf("mkdir: %v", err) + } + valid := createRuntimeDir(t, tmp, "java-1.8.0-openjdk-1.8.0.412", "java") + + rt := config.RuntimeSetting{Paths: []string{exact}} + if p, ok := tryPaths(rt, "java"); ok || p != "" { + t.Errorf("expected no detection, got (%q, %v) with valid sibling %q", p, ok, valid) + } +} + +func TestTryPaths_PrefixWhenBaseNotExists_GlobsAndFinds(t *testing.T) { + tmp := t.TempDir() + prefix := filepath.Join(tmp, "jdk-17") + _ = os.RemoveAll(prefix) + + _ = createRuntimeDir(t, tmp, "jdk-17.0.8", "java") + _ = createRuntimeDir(t, tmp, "jdk-17.0.7", "java") + + rt := config.RuntimeSetting{Paths: []string{prefix}} + got, ok := tryPaths(rt, "java") + if !ok { + t.Fatalf("expected detection via prefix glob") + } + + cands, _ := filepath.Glob(prefix + "*") + sort.Sort(sort.Reverse(sort.StringSlice(cands))) + want := cands[0] + if got != want { + t.Errorf("got %q, want %q", got, want) + } +} + +func TestTryPaths_GlobPatternKeptVerbatim(t *testing.T) { + tmp := t.TempDir() + _ = createRuntimeDir(t, tmp, "liberica-jre-17.0.9+9", "java") + _ = createRuntimeDir(t, tmp, "liberica-jre-17.0.8+8", "java") + + pattern := filepath.Join(tmp, "liberica-jre-17*") + rt := config.RuntimeSetting{Paths: []string{pattern}} + + got, ok := tryPaths(rt, "java") + if !ok { + t.Fatalf("glob pattern failed") + } + cands, _ := filepath.Glob(pattern) + sort.Sort(sort.Reverse(sort.StringSlice(cands))) + if got != cands[0] { + t.Errorf("got %q, want top %q", got, cands[0]) + } +} + +func TestTryPaths_SymlinkGlob(t *testing.T) { + tmp := t.TempDir() + target := createRuntimeDir(t, tmp, "jdk-real-21", "java") + link := filepath.Join(tmp, "jre-21") // имя под паттерн + if err := os.Symlink(target, link); err != nil { + t.Fatalf("symlink: %v", err) + } + + pattern := filepath.Join(tmp, "jre-21*") + rt := config.RuntimeSetting{Paths: []string{pattern}} + + got, ok := tryPaths(rt, "java") + if !ok { + t.Fatalf("symlink via glob failed") + } + if got != target { + t.Errorf("got %q, want resolved %q", got, target) + } +} + +func TestTryPaths_NoBinSkip(t *testing.T) { + tmp := t.TempDir() + bad := filepath.Join(tmp, "jdk-23.0.0") + if err := os.MkdirAll(bad, 0o755); err != nil { + t.Fatalf("mkdir: %v", err) + } + good := createRuntimeDir(t, tmp, "jdk-23.0.1", "java") + + rt := config.RuntimeSetting{Paths: []string{filepath.Join(tmp, "jdk-23*")}} + got, ok := tryPaths(rt, "java") + if !ok || got != good { + t.Errorf("got (%q,%v), want (%q,true)", got, ok, good) + } +} From dd38938388dc2a76e32ecdaab4bd71cc60d3450b Mon Sep 17 00:00:00 2001 From: Andrei Shitov Date: Tue, 23 Sep 2025 11:34:55 +0300 Subject: [PATCH 2/3] fix(bash): prevent shell exit when sourced and add safe output validation --- scripts/bigtop-detect-javahome | 45 +++++++++++++++++++++++++++++----- 1 file changed, 39 insertions(+), 6 deletions(-) diff --git a/scripts/bigtop-detect-javahome b/scripts/bigtop-detect-javahome index 25903c8..8a57deb 100644 --- a/scripts/bigtop-detect-javahome +++ b/scripts/bigtop-detect-javahome @@ -1,8 +1,28 @@ #!/usr/bin/env bash +# +# Wrapper for ad-runtime-utils that detects JAVA_HOME. +# Safe to be sourced (will return on error instead of exiting the shell). -# only run if JAVA_HOME is unset +# Detect if this script is being sourced instead of executed. +_is_sourced() { + # In bash, sourced if BASH_SOURCE[0] != $0 + [[ "${BASH_SOURCE[0]}" != "$0" ]] +} + +# Unified error handler: print an error message, then either return (if sourced) +# or exit (if executed as a normal script). +_die() { + echo "Error running ad-runtime-utils: $*" >&2 + if _is_sourced; then + return 1 + else + exit 1 + fi +} + +# Only run detection if JAVA_HOME is not already set if [ -z "$JAVA_HOME" ]; then - # run the detector, capture both stdout and stderr + # Run the detector and capture both stdout and stderr EXPORT_CMD=$( /usr/lib/ad-runtime-utils/bin/ad-runtime-utils \ --config "/etc/ad-runtime-utils/config.yaml" \ @@ -10,15 +30,28 @@ if [ -z "$JAVA_HOME" ]; then --runtime java 2>&1 ) RET=$? + + # If the binary failed, print the message and stop (return or exit) if [ $RET -ne 0 ]; then - # on error, print the message and exit with the same code - echo "Error running ad-runtime-utils: $EXPORT_CMD" >&2 - exit $RET + _die "$EXPORT_CMD" + return 1 2>/dev/null || exit 1 fi - # if we got back an export line, eval it in this shell + # If we got something back, validate and evaluate it if [ -n "$EXPORT_CMD" ]; then + # Safety check: only accept an "export JAVA_HOME=..." line + case "$EXPORT_CMD" in + export\ JAVA_HOME=*) : ;; # OK + *) + _die "unexpected output: $EXPORT_CMD" + return 1 2>/dev/null || exit 1 + ;; + esac + + # Evaluate the export in the current shell eval "$EXPORT_CMD" + + # Optional debug output if [ -n "$AD_UTILS_DEBUG" ]; then echo "Using detected JAVA_HOME: $JAVA_HOME" fi From 44e04047530f4d7a19d9067918742359747a54e9 Mon Sep 17 00:00:00 2001 From: Andrei Shitov Date: Tue, 23 Sep 2025 12:33:25 +0300 Subject: [PATCH 3/3] fix(default): added /usr/lib/jvm/jre --- configs/config.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/configs/config.yaml b/configs/config.yaml index ab439ce..0641ea1 100644 --- a/configs/config.yaml +++ b/configs/config.yaml @@ -19,6 +19,7 @@ autodetect: - /usr/lib/jvm/java-1.8.0 - /usr/lib/jvm/java-1.8.0-oracle - /usr/lib/jvm/bellsoft-java8-amd64 + - /usr/lib/jvm/jre - /usr/lib/jvm/java-arenadata-openjdk-8 "17":