2026-05-12 22:27:05 +05:30
|
|
|
package shim
|
|
|
|
|
|
|
|
|
|
import (
|
2026-07-06 18:31:18 +05:30
|
|
|
"errors"
|
2026-05-12 22:27:05 +05:30
|
|
|
"fmt"
|
|
|
|
|
"os"
|
|
|
|
|
"os/exec"
|
|
|
|
|
"path/filepath"
|
|
|
|
|
"strings"
|
|
|
|
|
"sync"
|
|
|
|
|
|
|
|
|
|
"github.com/safedep/dry/log"
|
|
|
|
|
)
|
|
|
|
|
|
2026-06-09 20:33:14 +05:30
|
|
|
const (
|
|
|
|
|
// pmgBinSuffix matches the legacy per-user shim dir (~/.pmg/bin). Retained
|
|
|
|
|
// as a fallback so shims installed by older PMG versions keep working after
|
|
|
|
|
// upgrade, until they are regenerated by a fresh `pmg setup install`.
|
|
|
|
|
pmgBinSuffix = "/.pmg/bin"
|
|
|
|
|
|
|
|
|
|
// pmgShimPathEnv is the env var the shim script exports before exec'ing
|
|
|
|
|
// pmg. Its value is the absolute path of the shim that was invoked, which
|
|
|
|
|
// lets FilterPMGFromPath strip exactly the dir the shim lives in — no
|
|
|
|
|
// matter where it was placed (`~/.pmg/bin`, `/usr/local/lib/pmg/bin`, a
|
|
|
|
|
// custom location, etc.).
|
|
|
|
|
//
|
|
|
|
|
// Internal: set by the shim script, consumed by pmg in the same process
|
|
|
|
|
// tree. Setting it manually from a user shell is unsupported — it would
|
|
|
|
|
// cause FilterPMGFromPath to strip the wrong directory from PATH lookup
|
|
|
|
|
// and could prevent pmg from resolving the real package manager.
|
|
|
|
|
pmgShimPathEnv = "PMG_SHIM_PATH"
|
|
|
|
|
)
|
2026-05-12 22:27:05 +05:30
|
|
|
|
|
|
|
|
var resolverMu sync.Mutex
|
|
|
|
|
|
|
|
|
|
func FilterPMGFromPath(pathEnv string) string {
|
|
|
|
|
if pathEnv == "" {
|
|
|
|
|
return ""
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-09 20:33:14 +05:30
|
|
|
var shimDir string
|
|
|
|
|
if shimPath := os.Getenv(pmgShimPathEnv); shimPath != "" {
|
|
|
|
|
shimDir = filepath.Clean(filepath.Dir(shimPath))
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-12 22:27:05 +05:30
|
|
|
entries := filepath.SplitList(pathEnv)
|
|
|
|
|
filtered := make([]string, 0, len(entries))
|
|
|
|
|
|
|
|
|
|
for _, entry := range entries {
|
2026-06-09 20:33:14 +05:30
|
|
|
if strings.HasSuffix(entry, pmgBinSuffix) {
|
|
|
|
|
continue
|
2026-05-12 22:27:05 +05:30
|
|
|
}
|
2026-07-14 21:34:22 +05:30
|
|
|
if filepath.Clean(entry) == filepath.Clean(SystemBinDir()) {
|
|
|
|
|
continue
|
|
|
|
|
}
|
2026-06-09 20:33:14 +05:30
|
|
|
if shimDir != "" && filepath.Clean(entry) == shimDir {
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
filtered = append(filtered, entry)
|
2026-05-12 22:27:05 +05:30
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return strings.Join(filtered, string(os.PathListSeparator))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ResolveRealBinary finds the real binary path for a command by searching
|
|
|
|
|
// PATH with ~/.pmg/bin stripped out. This prevents exec.CommandContext from
|
|
|
|
|
// resolving to the shim script, which would cause infinite recursion.
|
|
|
|
|
func ResolveRealBinary(name string) (string, error) {
|
|
|
|
|
resolverMu.Lock()
|
|
|
|
|
defer resolverMu.Unlock()
|
|
|
|
|
|
|
|
|
|
originalPath := os.Getenv("PATH")
|
|
|
|
|
filteredPath := FilterPMGFromPath(originalPath)
|
|
|
|
|
|
|
|
|
|
if err := os.Setenv("PATH", filteredPath); err != nil {
|
|
|
|
|
return "", fmt.Errorf("failed to set filtered PATH: %w", err)
|
|
|
|
|
}
|
|
|
|
|
defer func() {
|
|
|
|
|
if err := os.Setenv("PATH", originalPath); err != nil {
|
|
|
|
|
log.Warnf("failed to restore PATH: %v", err)
|
|
|
|
|
}
|
|
|
|
|
}()
|
|
|
|
|
|
|
|
|
|
resolved, err := exec.LookPath(name)
|
|
|
|
|
if err != nil {
|
2026-07-06 18:31:18 +05:30
|
|
|
if errors.Is(err, exec.ErrNotFound) {
|
|
|
|
|
return "", &BinaryNotFoundError{Name: name}
|
|
|
|
|
}
|
2026-05-12 22:27:05 +05:30
|
|
|
return "", fmt.Errorf("could not find %s in PATH (excluding pmg shims): %w", name, err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return resolved, nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func FilterPMGFromEnv(env []string) []string {
|
|
|
|
|
result := make([]string, 0, len(env))
|
|
|
|
|
|
|
|
|
|
for _, entry := range env {
|
|
|
|
|
if pathValue, ok := strings.CutPrefix(entry, "PATH="); ok {
|
|
|
|
|
filtered := FilterPMGFromPath(pathValue)
|
|
|
|
|
result = append(result, "PATH="+filtered)
|
2026-06-09 20:33:14 +05:30
|
|
|
continue
|
2026-05-12 22:27:05 +05:30
|
|
|
}
|
2026-06-09 20:33:14 +05:30
|
|
|
// Drop PMG_SHIM_PATH so child processes don't inherit a stale marker
|
|
|
|
|
// from the shim invocation that triggered this exec.
|
|
|
|
|
if strings.HasPrefix(entry, pmgShimPathEnv+"=") {
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
result = append(result, entry)
|
2026-05-12 22:27:05 +05:30
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return result
|
|
|
|
|
}
|