fix(enter): reorder PATH so /usr/local/{s,}bin precede /usr/{s,}bin

Signed-off-by: Luca Di Maio <luca.dimaio1@gmail.com>
This commit is contained in:
Luca Di Maio
2026-06-25 09:07:22 +02:00
parent 5a3c65bb62
commit ca404a285d
2 changed files with 43 additions and 10 deletions
+35 -2
View File
@@ -155,11 +155,44 @@ func BuildContainerPath(cleanPath bool, hostPath string, containerPath string) s
}
}
merged := hostPath
if len(additionalPaths) > 0 {
return hostPath + ":" + strings.Join(additionalPaths, ":")
merged = hostPath + ":" + strings.Join(additionalPaths, ":")
}
return hostPath
return reorderFHSPath(merged)
}
// reorderFHSPath ensures /usr/local/bin precedes /usr/bin and /usr/local/sbin
// precedes /usr/sbin, so distrobox wrappers in /usr/local/* win — mirroring the
// reference shell (distrobox-enter:478-512).
func reorderFHSPath(path string) string {
var reordered []string
for _, p := range strings.Split(path, ":") {
switch p {
case "/usr/local/bin", "/usr/local/sbin":
// Skip here; re-inserted right before its /usr counterpart.
case "/usr/bin":
reordered = append(reordered, "/usr/local/bin", "/usr/bin")
case "/usr/sbin":
reordered = append(reordered, "/usr/local/sbin", "/usr/sbin")
default:
reordered = append(reordered, p)
}
}
result := strings.Join(reordered, ":")
// If /usr/bin or /usr/sbin were absent, their local counterparts were
// skipped above; re-add any that went missing (prepended, like the shell).
for _, lp := range []string{"/usr/local/bin", "/usr/local/sbin"} {
pattern := regexp.MustCompile(`(:|^)` + regexp.QuoteMeta(lp) + `(:|$)`)
if !pattern.MatchString(result) {
result = lp + ":" + result
}
}
return result
}
func BuildXDGPaths(envVar string, standardPaths []string) string {
@@ -148,21 +148,21 @@ func TestBuildContainerPath(t *testing.T) {
cleanPath: false,
hostPath: "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
containerPath: "/container/path",
want: "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
want: "/usr/local/sbin:/usr/sbin:/usr/local/bin:/usr/bin:/sbin:/bin",
},
{
name: "hostPath missing some standard paths - adds them",
cleanPath: false,
hostPath: "/usr/bin:/bin",
containerPath: "/container/path",
want: "/usr/bin:/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/sbin",
want: "/usr/local/bin:/usr/bin:/bin:/usr/local/sbin:/usr/sbin:/sbin",
},
{
name: "hostPath with custom paths and missing standard paths",
cleanPath: false,
hostPath: "/custom/bin:/usr/bin:/another/path",
containerPath: "/container/path",
want: "/custom/bin:/usr/bin:/another/path:/usr/local/sbin:/usr/local/bin:/usr/sbin:/sbin:/bin",
want: "/custom/bin:/usr/local/bin:/usr/bin:/another/path:/usr/local/sbin:/usr/sbin:/sbin:/bin",
},
{
name: "empty hostPath - returns containerPath",
@@ -181,32 +181,32 @@ func TestBuildContainerPath(t *testing.T) {
name: "hostPath with standard paths at beginning",
cleanPath: false,
hostPath: "/bin:/usr/bin:/custom/path",
want: "/bin:/usr/bin:/custom/path:/usr/local/sbin:/usr/local/bin:/usr/sbin:/sbin",
want: "/bin:/usr/local/bin:/usr/bin:/custom/path:/usr/local/sbin:/usr/sbin:/sbin",
},
{
name: "hostPath with standard paths at end",
cleanPath: false,
hostPath: "/custom/path:/bin:/usr/bin",
want: "/custom/path:/bin:/usr/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/sbin",
want: "/custom/path:/bin:/usr/local/bin:/usr/bin:/usr/local/sbin:/usr/sbin:/sbin",
},
{
name: "hostPath with similar but not exact standard paths",
cleanPath: false,
hostPath: "/usr/binary:/binfo",
want: "/usr/binary:/binfo:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
want: "/usr/binary:/binfo:/usr/local/sbin:/usr/sbin:/usr/local/bin:/usr/bin:/sbin:/bin",
},
{
name: "single custom path in hostPath",
cleanPath: false,
hostPath: "/opt/custom/bin",
containerPath: "/container/path",
want: "/opt/custom/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
want: "/opt/custom/bin:/usr/local/sbin:/usr/sbin:/usr/local/bin:/usr/bin:/sbin:/bin",
},
{
name: "empty containerPath with non-empty hostPath",
cleanPath: false,
hostPath: "/custom/path",
want: "/custom/path:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
want: "/custom/path:/usr/local/sbin:/usr/sbin:/usr/local/bin:/usr/bin:/sbin:/bin",
},
{
name: "empty containerPath with empty hostPath",