diff --git a/cmd/podman/machine/client9p.go b/cmd/podman/machine/client9p.go index 00f90fad4a..1153ecbd00 100644 --- a/cmd/podman/machine/client9p.go +++ b/cmd/podman/machine/client9p.go @@ -8,6 +8,7 @@ import ( "os/exec" "path/filepath" "strconv" + "time" "github.com/containers/common/pkg/completion" "github.com/containers/podman/v5/cmd/podman/registry" @@ -69,8 +70,22 @@ func client9p(portNum uint32, mountPath string) error { logrus.Infof("Going to mount 9p on vsock port %d to directory %s", portNum, mountPath) - // Host connects to non-hypervisor processes on the host running the VM. - conn, err := vsock.Dial(vsock.Host, portNum, nil) + // The server is starting at the same time. + // Perform up to 5 retries with a backoff. + var ( + conn *vsock.Conn + retries = 20 + ) + for i := 0; i < retries; i++ { + // Host connects to non-hypervisor processes on the host running the VM. + conn, err = vsock.Dial(vsock.Host, portNum, nil) + // If errors.Is worked on this error, we could detect non-timeout errors. + // But it doesn't. So retry 5 times regardless. + if err == nil { + break + } + time.Sleep(250 * time.Millisecond) + } if err != nil { return fmt.Errorf("dialing vsock port %d: %w", portNum, err) } diff --git a/pkg/machine/e2e/basic_test.go b/pkg/machine/e2e/basic_test.go index ab5c5c6312..f3d211c46e 100644 --- a/pkg/machine/e2e/basic_test.go +++ b/pkg/machine/e2e/basic_test.go @@ -1,10 +1,14 @@ package e2e_test import ( + "fmt" "io" "net" "net/http" "net/url" + "os" + "path" + "path/filepath" "time" . "github.com/onsi/ginkgo/v2" @@ -93,6 +97,31 @@ var _ = Describe("run basic podman commands", func() { Expect(out).ToNot(ContainSubstring("gvproxy")) }) + It("podman volume on non-standard path", func() { + skipIfWSL("Requires standard volume handling") + dir, err := os.MkdirTemp("", "machine-volume") + Expect(err).ToNot(HaveOccurred()) + defer os.RemoveAll(dir) + + testString := "abcdefg1234567" + testFile := "testfile" + err = os.WriteFile(filepath.Join(dir, testFile), []byte(testString), 0644) + Expect(err).ToNot(HaveOccurred()) + + name := randomString() + machinePath := "/does/not/exist" + init := new(initMachine).withVolume(fmt.Sprintf("%s:%s", dir, machinePath)).withImagePath(mb.imagePath).withNow() + session, err := mb.setName(name).setCmd(init).run() + Expect(err).ToNot(HaveOccurred()) + Expect(session).To(Exit(0)) + + // Must use path.Join to ensure forward slashes are used, even on Windows. + ssh := new(sshMachine).withSSHCommand([]string{"cat", path.Join(machinePath, testFile)}) + ls, err := mb.setName(name).setCmd(ssh).run() + Expect(err).ToNot(HaveOccurred()) + Expect(ls).To(Exit(0)) + Expect(ls.outputToString()).To(ContainSubstring(testString)) + }) }) func testHTTPServer(port string, shouldErr bool, expectedResponse string) { diff --git a/pkg/machine/hyperv/stubber.go b/pkg/machine/hyperv/stubber.go index 03838a9aee..316a412571 100644 --- a/pkg/machine/hyperv/stubber.go +++ b/pkg/machine/hyperv/stubber.go @@ -423,7 +423,7 @@ func (h HyperVStubber) PostStartNetworking(mc *vmconfigs.MachineConfig, noInfo b for _, mount := range mc.Mounts { if mount.VSockNumber == nil { - return fmt.Errorf("mount %s has not vsock port defined", mount.Source) + return fmt.Errorf("mount %s has no vsock port defined", mount.Source) } p9ServerArgs = append(p9ServerArgs, "--serve", fmt.Sprintf("%s:%s", mount.Source, winio.VsockServiceID(uint32(*mount.VSockNumber)).String())) } diff --git a/pkg/machine/hyperv/volumes.go b/pkg/machine/hyperv/volumes.go index 239a12cc3e..a34ea4bc83 100644 --- a/pkg/machine/hyperv/volumes.go +++ b/pkg/machine/hyperv/volumes.go @@ -5,6 +5,8 @@ package hyperv import ( "errors" "fmt" + "path" + "strings" "github.com/containers/podman/v5/pkg/machine" "github.com/containers/podman/v5/pkg/machine/hyperv/vsock" @@ -40,7 +42,19 @@ func removeShares(mc *vmconfigs.MachineConfig) error { func startShares(mc *vmconfigs.MachineConfig) error { for _, mount := range mc.Mounts { - args := []string{"-q", "--", "sudo", "podman"} + args := []string{"-q", "--"} + + cleanTarget := path.Clean(mount.Target) + requiresChattr := !strings.HasPrefix(cleanTarget, "/home") && !strings.HasPrefix(cleanTarget, "/mnt") + if requiresChattr { + args = append(args, "sudo", "chattr", "-i", "/", "; ") + } + args = append(args, "sudo", "mkdir", "-p", cleanTarget, "; ") + if requiresChattr { + args = append(args, "sudo", "chattr", "+i", "/", "; ") + } + + args = append(args, "sudo", "podman") if logrus.IsLevelEnabled(logrus.DebugLevel) { args = append(args, "--log-level=debug") } @@ -48,7 +62,7 @@ func startShares(mc *vmconfigs.MachineConfig) error { if mount.VSockNumber == nil { return errors.New("cannot start 9p shares with undefined vsock number") } - args = append(args, "machine", "client9p", fmt.Sprintf("%d", mount.VSockNumber), mount.Target) + args = append(args, "machine", "client9p", fmt.Sprintf("%d", *mount.VSockNumber), mount.Target) if err := machine.CommonSSH(mc.SSH.RemoteUsername, mc.SSH.IdentityPath, mc.Name, mc.SSH.Port, args); err != nil { return err