From 955222b147b69fd7d2214227daee85bb0de93c6b Mon Sep 17 00:00:00 2001 From: Jan Dubois Date: Wed, 21 May 2025 13:45:37 -0700 Subject: [PATCH] Locate the limactl executable via os.Args[0] instead of os.Executable() We need the symlink directory to find the the ../share/lima directory relative to it. os.Executable() uses /proc/self/exe on Linux and returns the symlink target, which will break things with a Homebrew installation. This commit also removes the dead code that pretends to resolve any symlinks. It never worked because it called os.Stat() instead of os.Lstat(). If it had worked, then code would have been broken on macOS too. Signed-off-by: Jan Dubois --- pkg/usrlocalsharelima/usrlocalsharelima.go | 42 +++++++++++++++------- 1 file changed, 30 insertions(+), 12 deletions(-) diff --git a/pkg/usrlocalsharelima/usrlocalsharelima.go b/pkg/usrlocalsharelima/usrlocalsharelima.go index c96a698e2a6..4ef008c1b17 100644 --- a/pkg/usrlocalsharelima/usrlocalsharelima.go +++ b/pkg/usrlocalsharelima/usrlocalsharelima.go @@ -8,29 +8,47 @@ import ( "fmt" "io/fs" "os" + "os/exec" "path/filepath" "runtime" + "sync" "github.com/lima-vm/lima/pkg/debugutil" "github.com/lima-vm/lima/pkg/limayaml" "github.com/sirupsen/logrus" ) -func Dir() (string, error) { - self, err := os.Executable() - if err != nil { - return "", err +// executableViaArgs0 returns the absolute path to the executable used to start this process. +// It will also append the file extension on Windows, if necessary. +// This function is different from os.Executable(), which will use /proc/self/exe on Linux +// and therefore will resolve any symlink used to locate the executable. This function will +// return the symlink instead because we want to locate ../share/lima relative to the location +// of the symlink, and not the actual executable. This is important when using Homebrew. +// +// If os.Args[0] is invalid, this function still falls back on os.Executable(). +var executableViaArgs0 = sync.OnceValues(func() (string, error) { + if os.Args[0] == "" { + logrus.Warn("os.Args[0] has not been set") + } else { + executable, err := exec.LookPath(os.Args[0]) + if err == nil { + // LookPath() will add the `.exe` file extension on Windows, but will not return an + // absolute path if the argument contained any of `:/\` (or just `/` on Unix). + return filepath.Abs(executable) + } + logrus.Warnf("os.Args[0] is invalid: %v", err) } - selfSt, err := os.Stat(self) + return os.Executable() +}) + +// Dir returns the location of the /lima/share directory, relative to the location +// of the current executable. It checks for multiple possible filesystem layouts and returns +// the first candidate that contains the native guest agent binary. +func Dir() (string, error) { + self, err := executableViaArgs0() if err != nil { return "", err } - if selfSt.Mode()&fs.ModeSymlink != 0 { - self, err = os.Readlink(self) - if err != nil { - return "", err - } - } ostype := limayaml.NewOS("linux") arch := limayaml.NewArch(runtime.GOARCH) @@ -80,7 +98,7 @@ func Dir() (string, error) { ostype, arch, self, gaCandidates) } -// GuestAgentBinary returns the guest agent binary, possibly with ".gz" suffix. +// GuestAgentBinary returns the absolute path of the guest agent binary, possibly with ".gz" suffix. func GuestAgentBinary(ostype limayaml.OS, arch limayaml.Arch) (string, error) { if ostype == "" { return "", errors.New("os must be set")