mirror of
https://github.com/safedep/pmg.git
synced 2026-08-03 07:24:09 +02:00
* fix(shim): recognize shims at arbitrary paths via PMG_SHIM_PATH The recursion guard in FilterPMGFromPath hardcoded the `/.pmg/bin` suffix, so shims placed anywhere else (e.g. `/usr/local/lib/pmg/bin`, `/shims`, or any future system-wide location) would not be stripped from PATH when PMG resolved the real package manager. The shim would resolve back to itself and PMG would re-exec it in an infinite loop. This blocks moving shims out of `~/.pmg/bin` — needed for a future `pmg setup install --system` (#317) — and also any user attempt to relocate shims manually. Have the shim export its own path before exec'ing pmg, and let the filter use that to strip the exact dir at runtime. Keep the legacy suffix check as a fallback so already-installed shims keep working until they are regenerated. Also drop `PMG_SHIM_PATH` from the env passed to the real package manager so child processes don't inherit a stale marker. * docs(shim): clarify PMG_SHIM_PATH is internal and unsupported to set manually * remove comment * update comment
107 lines
2.9 KiB
Go
107 lines
2.9 KiB
Go
package shim
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
"os/exec"
|
|
"path/filepath"
|
|
"strings"
|
|
"sync"
|
|
|
|
"github.com/safedep/dry/log"
|
|
)
|
|
|
|
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"
|
|
)
|
|
|
|
var resolverMu sync.Mutex
|
|
|
|
func FilterPMGFromPath(pathEnv string) string {
|
|
if pathEnv == "" {
|
|
return ""
|
|
}
|
|
|
|
var shimDir string
|
|
if shimPath := os.Getenv(pmgShimPathEnv); shimPath != "" {
|
|
shimDir = filepath.Clean(filepath.Dir(shimPath))
|
|
}
|
|
|
|
entries := filepath.SplitList(pathEnv)
|
|
filtered := make([]string, 0, len(entries))
|
|
|
|
for _, entry := range entries {
|
|
if strings.HasSuffix(entry, pmgBinSuffix) {
|
|
continue
|
|
}
|
|
if shimDir != "" && filepath.Clean(entry) == shimDir {
|
|
continue
|
|
}
|
|
filtered = append(filtered, entry)
|
|
}
|
|
|
|
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 {
|
|
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)
|
|
continue
|
|
}
|
|
// 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)
|
|
}
|
|
|
|
return result
|
|
}
|