Skip to content

Search for the data directory using argv[0] *and* os.Executable() #3621

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Jun 20, 2025
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
98 changes: 56 additions & 42 deletions pkg/usrlocalsharelima/usrlocalsharelima.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,32 +23,42 @@ import (
// 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().
// return the symlink instead because we want to be able to locate ../share/lima relative
// to the location of the symlink, and not the actual executable. This is important when
// using Homebrew.
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)
return "", errors.New("os.Args[0] has not been set")
}
executable, err := exec.LookPath(os.Args[0])
if err == nil {
executable, err = filepath.Abs(executable)
}
if err != nil {
return "", fmt.Errorf("os.Args[0] is invalid: %w", err)
}
return os.Executable()

return executable, nil
})

// Dir returns the location of the <PREFIX>/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()
selfPaths := []string{}

selfViaArgs0, err := executableViaArgs0()
if err != nil {
return "", err
logrus.WithError(err).Warn("failed to find executable from os.Args[0]")
} else {
selfPaths = append(selfPaths, selfViaArgs0)
}

selfViaOS, err := os.Executable()
if err != nil {
logrus.WithError(err).Warn("failed to find os.Executable()")
} else if len(selfPaths) == 0 || selfViaOS != selfPaths[0] {
selfPaths = append(selfPaths, selfViaOS)
}

ostype := limayaml.NewOS("linux")
Expand All @@ -57,31 +67,35 @@ func Dir() (string, error) {
return "", fmt.Errorf("failed to get arch for %q", runtime.GOARCH)
}

// self: /usr/local/bin/limactl
selfDir := filepath.Dir(self)
selfDirDir := filepath.Dir(selfDir)
gaCandidates := []string{
// candidate 0:
// - self: /Applications/Lima.app/Contents/MacOS/limactl
// - agent: /Applications/Lima.app/Contents/MacOS/lima-guestagent.Linux-x86_64
// - dir: /Applications/Lima.app/Contents/MacOS
filepath.Join(selfDir, "lima-guestagent."+ostype+"-"+arch),
// candidate 1:
// - self: /usr/local/bin/limactl
// - agent: /usr/local/share/lima/lima-guestagent.Linux-x86_64
// - dir: /usr/local/share/lima
filepath.Join(selfDirDir, "share/lima/lima-guestagent."+ostype+"-"+arch),
// TODO: support custom path
}
if debugutil.Debug {
// candidate 2: launched by `~/go/bin/dlv dap`
// - self: ${workspaceFolder}/cmd/limactl/__debug_bin_XXXXXX
// - agent: ${workspaceFolder}/_output/share/lima/lima-guestagent.Linux-x86_64
// - dir: ${workspaceFolder}/_output/share/lima
candidateForDebugBuild := filepath.Join(filepath.Dir(selfDirDir), "_output/share/lima/lima-guestagent."+ostype+"-"+arch)
gaCandidates = append(gaCandidates, candidateForDebugBuild)
logrus.Infof("debug mode detected, adding more guest agent candidates: %v", candidateForDebugBuild)
gaCandidates := []string{}
for _, self := range selfPaths {
// self: /usr/local/bin/limactl
selfDir := filepath.Dir(self)
selfDirDir := filepath.Dir(selfDir)
gaCandidates = append(gaCandidates,
// candidate 0:
// - self: /Applications/Lima.app/Contents/MacOS/limactl
// - agent: /Applications/Lima.app/Contents/MacOS/lima-guestagent.Linux-x86_64
// - dir: /Applications/Lima.app/Contents/MacOS
filepath.Join(selfDir, "lima-guestagent."+ostype+"-"+arch),
// candidate 1:
// - self: /usr/local/bin/limactl
// - agent: /usr/local/share/lima/lima-guestagent.Linux-x86_64
// - dir: /usr/local/share/lima
filepath.Join(selfDirDir, "share/lima/lima-guestagent."+ostype+"-"+arch),
// TODO: support custom path
)
if debugutil.Debug {
// candidate 2: launched by `~/go/bin/dlv dap`
// - self: ${workspaceFolder}/cmd/limactl/__debug_bin_XXXXXX
// - agent: ${workspaceFolder}/_output/share/lima/lima-guestagent.Linux-x86_64
// - dir: ${workspaceFolder}/_output/share/lima
candidateForDebugBuild := filepath.Join(filepath.Dir(selfDirDir), "_output/share/lima/lima-guestagent."+ostype+"-"+arch)
gaCandidates = append(gaCandidates, candidateForDebugBuild)
logrus.Infof("debug mode detected, adding more guest agent candidates: %v", candidateForDebugBuild)
}
}

for _, gaCandidate := range gaCandidates {
if _, err := os.Stat(gaCandidate); err == nil {
return filepath.Dir(gaCandidate), nil
Expand All @@ -95,8 +109,8 @@ func Dir() (string, error) {
}
}

return "", fmt.Errorf("failed to find \"lima-guestagent.%s-%s\" binary for %q, attempted %v",
ostype, arch, self, gaCandidates)
return "", fmt.Errorf("failed to find \"lima-guestagent.%s-%s\" binary for %v, attempted %v",
ostype, arch, selfPaths, gaCandidates)
}

// GuestAgentBinary returns the absolute path of the guest agent binary, possibly with ".gz" suffix.
Expand Down
Loading