//go:build dragonfly || freebsd || linux || netbsd || openbsd || darwin package shim import ( "io/fs" "net" "os" "path/filepath" "github.com/containers/podman/v5/pkg/machine" "github.com/containers/podman/v5/pkg/machine/define" "github.com/containers/podman/v5/pkg/machine/vmconfigs" "github.com/sirupsen/logrus" ) func setupMachineSockets(mc *vmconfigs.MachineConfig, dirs *define.MachineDirs) ([]string, string, machine.APIForwardingState, error) { hostSocket, err := mc.APISocket() if err != nil { return nil, "", 0, err } forwardSock, state, err := setupForwardingLinks(hostSocket, dirs.DataDir) if err != nil { return nil, "", 0, err } return []string{hostSocket.GetPath()}, forwardSock, state, nil } func setupForwardingLinks(hostSocket, dataDir *define.VMFile) (string, machine.APIForwardingState, error) { // Sets up a cooperative link structure to help a separate privileged // service manage /var/run/docker.sock (currently only on MacOS via // podman-mac-helper, but potentially other OSs in the future). // // The linking pattern is: // // /var/run/docker.sock (link) -> user global sock (link) -> machine sock // // This allows the helper to only have to maintain one constant target to // the user, which can be repositioned without updating docker.sock. // // Since these link locations are global/shared across multiple machine // instances, they must coordinate on the winner. The scheme is first come // first serve, whoever is actively answering on the socket first wins. All // other machine instances backs off. As soon as the winner is no longer // active another machine instance start will become the new active winner. // The same applies to a competing container runtime trying to use // /var/run/docker.sock, if the socket is in use by another runtime, podman // machine will back off. In the start message "Losing" machine instances // will instead advertise the direct machine socket, while "winning" // instances will simply note they listen on the standard // /var/run/docker.sock address. The APIForwardingState return value is // returned by this function to indicate how the start message should behave // Skip any OS not supported for helper usage if !dockerClaimSupported() { return hostSocket.GetPath(), machine.ClaimUnsupported, nil } // Verify the helper system service was installed and report back if not if !dockerClaimHelperInstalled() { return hostSocket.GetPath(), machine.NotInstalled, nil } dataPath := filepath.Dir(dataDir.GetPath()) userGlobalSocket, err := define.NewMachineFile(filepath.Join(dataPath, "podman.sock"), nil) if err != nil { return "", 0, err } // Setup the user global socket if not in use // (e.g ~/.local/share/containers/podman/machine/podman.sock) if !alreadyLinked(hostSocket.GetPath(), userGlobalSocket.GetPath()) { if checkSockInUse(userGlobalSocket.GetPath()) { return hostSocket.GetPath(), machine.MachineLocal, nil } _ = userGlobalSocket.Delete() if err := os.Symlink(hostSocket.GetPath(), userGlobalSocket.GetPath()); err != nil { logrus.Warnf("could not create user global API forwarding link: %s", err.Error()) return hostSocket.GetPath(), machine.MachineLocal, nil } } // Setup /var/run/docker.sock if not in use if !alreadyLinked(userGlobalSocket.GetPath(), dockerSock) { if checkSockInUse(dockerSock) { return hostSocket.GetPath(), machine.MachineLocal, nil } if !claimDockerSock() { logrus.Warn("podman helper is installed, but was not able to claim the global docker sock") return hostSocket.GetPath(), machine.MachineLocal, nil } } return dockerSock, machine.DockerGlobal, nil } func alreadyLinked(target string, link string) bool { read, err := os.Readlink(link) return err == nil && read == target } func checkSockInUse(sock string) bool { if info, err := os.Stat(sock); err == nil && info.Mode()&fs.ModeSocket == fs.ModeSocket { _, err = net.DialTimeout("unix", dockerSock, dockerConnectTimeout) return err == nil } return false }