aboutsummaryrefslogtreecommitdiff
path: root/pkg
diff options
context:
space:
mode:
Diffstat (limited to 'pkg')
-rw-r--r--pkg/machine/config.go2
-rw-r--r--pkg/machine/ignition.go20
-rw-r--r--pkg/machine/qemu/config.go6
-rw-r--r--pkg/machine/qemu/machine.go75
-rw-r--r--pkg/machine/wsl/machine.go6
-rw-r--r--pkg/rootless/rootless_linux.go2
-rw-r--r--pkg/specgen/volumes.go27
-rw-r--r--pkg/specgen/winpath.go59
-rw-r--r--pkg/specgen/winpath_linux.go24
-rw-r--r--pkg/specgen/winpath_unsupported.go20
-rw-r--r--pkg/specgen/winpath_windows.go30
-rw-r--r--pkg/specgenutil/specgenutil_test.go77
-rw-r--r--pkg/specgenutil/volumes.go21
13 files changed, 335 insertions, 34 deletions
diff --git a/pkg/machine/config.go b/pkg/machine/config.go
index 6c2fab0e5..1103933cd 100644
--- a/pkg/machine/config.go
+++ b/pkg/machine/config.go
@@ -121,7 +121,7 @@ type VM interface {
Set(name string, opts SetOptions) error
SSH(name string, opts SSHOptions) error
Start(name string, opts StartOptions) error
- State() (Status, error)
+ State(bypass bool) (Status, error)
Stop(name string, opts StopOptions) error
}
diff --git a/pkg/machine/ignition.go b/pkg/machine/ignition.go
index fe47437e3..35a9a30cb 100644
--- a/pkg/machine/ignition.go
+++ b/pkg/machine/ignition.go
@@ -304,6 +304,8 @@ ExecStart=/usr/bin/sleep infinity
containers := `[containers]
netns="bridge"
`
+ // Set deprecated machine_enabled until podman package on fcos is
+ // current enough to no longer require it
rootContainers := `[engine]
machine_enabled=true
`
@@ -392,7 +394,7 @@ Delegate=memory pids cpu io
FileEmbedded1: FileEmbedded1{Mode: intToPtr(0644)},
})
- // Set machine_enabled to true to indicate we're in a VM
+ // Set deprecated machine_enabled to true to indicate we're in a VM
files = append(files, File{
Node: Node{
Group: getNodeGrp("root"),
@@ -408,6 +410,22 @@ Delegate=memory pids cpu io
},
})
+ // Set machine marker file to indicate podman is in a qemu based machine
+ files = append(files, File{
+ Node: Node{
+ Group: getNodeGrp("root"),
+ Path: "/etc/containers/podman-machine",
+ User: getNodeUsr("root"),
+ },
+ FileEmbedded1: FileEmbedded1{
+ Append: nil,
+ Contents: Resource{
+ Source: encodeDataURLPtr("qemu\n"),
+ },
+ Mode: intToPtr(0644),
+ },
+ })
+
// Issue #11489: make sure that we can inject a custom registries.conf
// file on the system level to force a single search registry.
// The remote client does not yet support prompting for short-name
diff --git a/pkg/machine/qemu/config.go b/pkg/machine/qemu/config.go
index 7340de604..9473eef6f 100644
--- a/pkg/machine/qemu/config.go
+++ b/pkg/machine/qemu/config.go
@@ -86,6 +86,12 @@ type MachineVM struct {
ResourceConfig
// SSHConfig for accessing the remote vm
SSHConfig
+ // Starting tells us whether the machine is running or if we have just dialed it to start it
+ Starting bool
+ // Created contains the original created time instead of querying the file mod time
+ Created time.Time
+ // LastUp contains the last recorded uptime
+ LastUp time.Time
}
// ImageConfig describes the bootable image for the VM
diff --git a/pkg/machine/qemu/machine.go b/pkg/machine/qemu/machine.go
index 69a986102..969acb760 100644
--- a/pkg/machine/qemu/machine.go
+++ b/pkg/machine/qemu/machine.go
@@ -94,6 +94,8 @@ func (p *Provider) NewMachine(opts machine.InitOptions) (machine.VM, error) {
vm.Memory = opts.Memory
vm.DiskSize = opts.DiskSize
+ vm.Created = time.Now()
+
// Find the qemu executable
cfg, err := config.Default()
if err != nil {
@@ -436,7 +438,7 @@ func (v *MachineVM) Set(_ string, opts machine.SetOptions) error {
return nil
}
- state, err := v.State()
+ state, err := v.State(false)
if err != nil {
return err
}
@@ -477,6 +479,17 @@ func (v *MachineVM) Start(name string, _ machine.StartOptions) error {
wait = time.Millisecond * 500
)
+ v.Starting = true
+ if err := v.writeConfig(); err != nil {
+ return fmt.Errorf("writing JSON file: %w", err)
+ }
+ defer func() error {
+ v.Starting = false
+ if err := v.writeConfig(); err != nil {
+ return fmt.Errorf("writing JSON file: %w", err)
+ }
+ return nil
+ }()
if v.isIncompatible() {
logrus.Errorf("machine %q is incompatible with this release of podman and needs to be recreated, starting for recovery only", v.Name)
}
@@ -498,6 +511,7 @@ func (v *MachineVM) Start(name string, _ machine.StartOptions) error {
return err
}
}
+
// If the qemusocketpath exists and the vm is off/down, we should rm
// it before the dial as to avoid a segv
if err := v.QMPMonitor.Address.Delete(); err != nil {
@@ -589,14 +603,14 @@ func (v *MachineVM) Start(name string, _ machine.StartOptions) error {
return err
}
if len(v.Mounts) > 0 {
- state, err := v.State()
+ state, err := v.State(true)
if err != nil {
return err
}
listening := v.isListening()
for state != machine.Running || !listening {
time.Sleep(100 * time.Millisecond)
- state, err = v.State()
+ state, err = v.State(true)
if err != nil {
return err
}
@@ -638,7 +652,6 @@ func (v *MachineVM) Start(name string, _ machine.StartOptions) error {
}
v.waitAPIAndPrintInfo(forwardState, forwardSock)
-
return nil
}
@@ -647,9 +660,10 @@ func (v *MachineVM) checkStatus(monitor *qmp.SocketMonitor) (machine.Status, err
// {"return": {"status": "running", "singlestep": false, "running": true}}
type statusDetails struct {
- Status string `json:"status"`
- Step bool `json:"singlestep"`
- Running bool `json:"running"`
+ Status string `json:"status"`
+ Step bool `json:"singlestep"`
+ Running bool `json:"running"`
+ Starting bool `json:"starting"`
}
type statusResponse struct {
Response statusDetails `json:"return"`
@@ -735,6 +749,11 @@ func (v *MachineVM) Stop(_ string, _ machine.StopOptions) error {
if p == nil && err != nil {
return err
}
+
+ v.LastUp = time.Now()
+ if err := v.writeConfig(); err != nil { // keep track of last up
+ return err
+ }
// Kill the process
if err := p.Kill(); err != nil {
return err
@@ -756,7 +775,7 @@ func (v *MachineVM) Stop(_ string, _ machine.StopOptions) error {
disconnected = true
waitInternal := 250 * time.Millisecond
for i := 0; i < 5; i++ {
- state, err := v.State()
+ state, err := v.State(false)
if err != nil {
return err
}
@@ -808,7 +827,7 @@ func (v *MachineVM) Remove(_ string, opts machine.RemoveOptions) (string, func()
)
// cannot remove a running vm unless --force is used
- state, err := v.State()
+ state, err := v.State(false)
if err != nil {
return "", nil, err
}
@@ -874,12 +893,19 @@ func (v *MachineVM) Remove(_ string, opts machine.RemoveOptions) (string, func()
}, nil
}
-func (v *MachineVM) State() (machine.Status, error) {
+func (v *MachineVM) State(bypass bool) (machine.Status, error) {
// Check if qmp socket path exists
if _, err := os.Stat(v.QMPMonitor.Address.GetPath()); os.IsNotExist(err) {
return "", nil
}
+ err := v.update()
+ if err != nil {
+ return "", err
+ }
// Check if we can dial it
+ if v.Starting && !bypass {
+ return "", nil
+ }
monitor, err := qmp.NewSocketMonitor(v.QMPMonitor.Network, v.QMPMonitor.Address.GetPath(), v.QMPMonitor.Timeout)
if err != nil {
// FIXME: this error should probably be returned
@@ -910,7 +936,7 @@ func (v *MachineVM) isListening() bool {
// SSH opens an interactive SSH session to the vm specified.
// Added ssh function to VM interface: pkg/machine/config/go : line 58
func (v *MachineVM) SSH(_ string, opts machine.SSHOptions) error {
- state, err := v.State()
+ state, err := v.State(true)
if err != nil {
return err
}
@@ -1024,20 +1050,29 @@ func getVMInfos() ([]*machine.ListResponse, error) {
listEntry.Port = vm.Port
listEntry.RemoteUsername = vm.RemoteUsername
listEntry.IdentityPath = vm.IdentityPath
- fi, err := os.Stat(fullPath)
- if err != nil {
- return err
+ listEntry.CreatedAt = vm.Created
+
+ if listEntry.CreatedAt.IsZero() {
+ listEntry.CreatedAt = time.Now()
+ vm.Created = time.Now()
+ if err := vm.writeConfig(); err != nil {
+ return err
+ }
}
- listEntry.CreatedAt = fi.ModTime()
- fi, err = os.Stat(vm.getImageFile())
+ state, err := vm.State(false)
if err != nil {
return err
}
- listEntry.LastUp = fi.ModTime()
- state, err := vm.State()
- if err != nil {
- return err
+
+ if !vm.LastUp.IsZero() {
+ listEntry.LastUp = vm.LastUp
+ } else {
+ listEntry.LastUp = vm.Created
+ vm.Created = time.Now()
+ if err := vm.writeConfig(); err != nil {
+ return err
+ }
}
if state == machine.Running {
listEntry.Running = true
diff --git a/pkg/machine/wsl/machine.go b/pkg/machine/wsl/machine.go
index dff7bfef9..f57dbd299 100644
--- a/pkg/machine/wsl/machine.go
+++ b/pkg/machine/wsl/machine.go
@@ -448,6 +448,10 @@ func configureSystem(v *MachineVM, dist string) error {
return errors.Wrap(err, "could not create containers.conf for guest OS")
}
+ if err := runCmdPassThrough("wsl", "-d", dist, "sh", "-c", "echo wsl > /etc/containers/podman-machine"); err != nil {
+ return errors.Wrap(err, "could not create podman-machine file for guest OS")
+ }
+
return nil
}
@@ -1024,7 +1028,7 @@ func (v *MachineVM) Stop(name string, _ machine.StopOptions) error {
// TODO: We need to rename isRunning to State(); I do not have a
// windows system to test this on.
-func (v *MachineVM) State() (machine.Status, error) {
+func (v *MachineVM) State(bypass bool) (machine.Status, error) {
return "", define.ErrNotImplemented
}
diff --git a/pkg/rootless/rootless_linux.go b/pkg/rootless/rootless_linux.go
index e4d89294e..5af9a978b 100644
--- a/pkg/rootless/rootless_linux.go
+++ b/pkg/rootless/rootless_linux.go
@@ -30,7 +30,7 @@ import (
)
/*
-#cgo remoteclient CFLAGS: -Wall -Werror -DDISABLE_JOIN_SHORTCUT
+#cgo remote CFLAGS: -Wall -Werror -DDISABLE_JOIN_SHORTCUT
#include <stdlib.h>
#include <sys/types.h>
extern uid_t rootless_uid();
diff --git a/pkg/specgen/volumes.go b/pkg/specgen/volumes.go
index eca8c0c35..b26666df3 100644
--- a/pkg/specgen/volumes.go
+++ b/pkg/specgen/volumes.go
@@ -65,7 +65,7 @@ func GenVolumeMounts(volumeFlag []string) (map[string]spec.Mount, map[string]*Na
err error
)
- splitVol := strings.Split(vol, ":")
+ splitVol := SplitVolumeString(vol)
if len(splitVol) > 3 {
return nil, nil, nil, errors.Wrapf(volumeFormatErr, vol)
}
@@ -93,7 +93,7 @@ func GenVolumeMounts(volumeFlag []string) (map[string]spec.Mount, map[string]*Na
}
}
- if strings.HasPrefix(src, "/") || strings.HasPrefix(src, ".") {
+ if strings.HasPrefix(src, "/") || strings.HasPrefix(src, ".") || isHostWinPath(src) {
// This is not a named volume
overlayFlag := false
chownFlag := false
@@ -152,3 +152,26 @@ func GenVolumeMounts(volumeFlag []string) (map[string]spec.Mount, map[string]*Na
return mounts, volumes, overlayVolumes, nil
}
+
+// Splits a volume string, accounting for Win drive paths
+// when running as a WSL linux guest or Windows client
+func SplitVolumeString(vol string) []string {
+ parts := strings.Split(vol, ":")
+ if !shouldResolveWinPaths() {
+ return parts
+ }
+
+ // Skip extended marker prefix if present
+ n := 0
+ if strings.HasPrefix(vol, `\\?\`) {
+ n = 4
+ }
+
+ if hasWinDriveScheme(vol, n) {
+ first := parts[0] + ":" + parts[1]
+ parts = parts[1:]
+ parts[0] = first
+ }
+
+ return parts
+}
diff --git a/pkg/specgen/winpath.go b/pkg/specgen/winpath.go
new file mode 100644
index 000000000..f4249fab1
--- /dev/null
+++ b/pkg/specgen/winpath.go
@@ -0,0 +1,59 @@
+package specgen
+
+import (
+ "fmt"
+ "strings"
+ "unicode"
+
+ "github.com/pkg/errors"
+)
+
+func isHostWinPath(path string) bool {
+ return shouldResolveWinPaths() && strings.HasPrefix(path, `\\`) || hasWinDriveScheme(path, 0) || winPathExists(path)
+}
+
+func hasWinDriveScheme(path string, start int) bool {
+ if len(path) < start+2 || path[start+1] != ':' {
+ return false
+ }
+
+ drive := rune(path[start])
+ return drive < unicode.MaxASCII && unicode.IsLetter(drive)
+}
+
+// Converts a Windows path to a WSL guest path if local env is a WSL linux guest or this is a Windows client.
+func ConvertWinMountPath(path string) (string, error) {
+ if !shouldResolveWinPaths() {
+ return path, nil
+ }
+
+ if strings.HasPrefix(path, "/") {
+ // Handle /[driveletter]/windows/path form (e.g. c:\Users\bar == /c/Users/bar)
+ if len(path) > 2 && path[2] == '/' && shouldResolveUnixWinVariant(path) {
+ drive := unicode.ToLower(rune(path[1]))
+ if unicode.IsLetter(drive) && drive <= unicode.MaxASCII {
+ return fmt.Sprintf("/mnt/%c/%s", drive, path[3:]), nil
+ }
+ }
+
+ // unix path - pass through
+ return path, nil
+ }
+
+ // Convert remote win client relative paths to absolute
+ path = resolveRelativeOnWindows(path)
+
+ // Strip extended marker prefix if present
+ path = strings.TrimPrefix(path, `\\?\`)
+
+ // Drive installed via wsl --mount
+ if strings.HasPrefix(path, `\\.\`) {
+ path = "/mnt/wsl/" + path[4:]
+ } else if len(path) > 1 && path[1] == ':' {
+ path = "/mnt/" + strings.ToLower(path[0:1]) + path[2:]
+ } else {
+ return path, errors.New("unsupported UNC path")
+ }
+
+ return strings.ReplaceAll(path, `\`, "/"), nil
+}
diff --git a/pkg/specgen/winpath_linux.go b/pkg/specgen/winpath_linux.go
new file mode 100644
index 000000000..f42ac7639
--- /dev/null
+++ b/pkg/specgen/winpath_linux.go
@@ -0,0 +1,24 @@
+package specgen
+
+import (
+ "os"
+
+ "github.com/containers/common/pkg/machine"
+)
+
+func shouldResolveWinPaths() bool {
+ return machine.MachineHostType() == "wsl"
+}
+
+func shouldResolveUnixWinVariant(path string) bool {
+ _, err := os.Stat(path)
+ return err != nil
+}
+
+func resolveRelativeOnWindows(path string) string {
+ return path
+}
+
+func winPathExists(path string) bool {
+ return false
+}
diff --git a/pkg/specgen/winpath_unsupported.go b/pkg/specgen/winpath_unsupported.go
new file mode 100644
index 000000000..4cd008fdd
--- /dev/null
+++ b/pkg/specgen/winpath_unsupported.go
@@ -0,0 +1,20 @@
+//go:build !linux && !windows
+// +build !linux,!windows
+
+package specgen
+
+func shouldResolveWinPaths() bool {
+ return false
+}
+
+func shouldResolveUnixWinVariant(path string) bool {
+ return false
+}
+
+func resolveRelativeOnWindows(path string) string {
+ return path
+}
+
+func winPathExists(path string) bool {
+ return false
+}
diff --git a/pkg/specgen/winpath_windows.go b/pkg/specgen/winpath_windows.go
new file mode 100644
index 000000000..c6aad314a
--- /dev/null
+++ b/pkg/specgen/winpath_windows.go
@@ -0,0 +1,30 @@
+package specgen
+
+import (
+ "github.com/sirupsen/logrus"
+ "os"
+ "path/filepath"
+)
+
+func shouldResolveUnixWinVariant(path string) bool {
+ return true
+}
+
+func shouldResolveWinPaths() bool {
+ return true
+}
+
+func resolveRelativeOnWindows(path string) string {
+ ret, err := filepath.Abs(path)
+ if err != nil {
+ logrus.Debugf("problem resolving possible relative path %q: %s", path, err.Error())
+ return path
+ }
+
+ return ret
+}
+
+func winPathExists(path string) bool {
+ _, err := os.Stat(path)
+ return err == nil
+}
diff --git a/pkg/specgenutil/specgenutil_test.go b/pkg/specgenutil/specgenutil_test.go
new file mode 100644
index 000000000..5867b0ae0
--- /dev/null
+++ b/pkg/specgenutil/specgenutil_test.go
@@ -0,0 +1,77 @@
+//go:build linux
+// +build linux
+
+package specgenutil
+
+import (
+ "testing"
+
+ "github.com/containers/common/pkg/machine"
+ "github.com/containers/podman/v4/pkg/domain/entities"
+ "github.com/containers/podman/v4/pkg/specgen"
+ "github.com/stretchr/testify/assert"
+)
+
+func TestWinPath(t *testing.T) {
+ const (
+ fail = false
+ pass = true
+ )
+ tests := []struct {
+ vol string
+ source string
+ dest string
+ isN bool
+ outcome bool
+ mach string
+ }{
+ {`C:\Foo:/blah`, "/mnt/c/Foo", "/blah", false, pass, "wsl"},
+ {`C:\Foo:/blah`, "/mnt/c/Foo", "/blah", false, fail, ""},
+ {`\\?\C:\Foo:/blah`, "/mnt/c/Foo", "/blah", false, pass, "wsl"},
+ {`/c/bar:/blah`, "/mnt/c/bar", "/blah", false, pass, "wsl"},
+ {`/c/bar:/blah`, "/c/bar", "/blah", false, pass, ""},
+ {`/test/this:/blah`, "/test/this", "/blah", false, pass, "wsl"},
+ {`c:/bar/something:/other`, "/mnt/c/bar/something", "/other", false, pass, "wsl"},
+ {`c:/foo:ro`, "c", "/foo", true, pass, ""},
+ {`\\computer\loc:/dest`, "", "", false, fail, "wsl"},
+ {`\\.\drive\loc:/target`, "/mnt/wsl/drive/loc", "/target", false, pass, "wsl"},
+ }
+
+ f := func(vol string, mach string) (*specgen.SpecGenerator, error) {
+ machine := machine.GetMachineMarker()
+ oldEnable, oldType := machine.Enabled, machine.Type
+ machine.Enabled, machine.Type = len(mach) > 0, mach
+ sg := specgen.NewSpecGenerator("nothing", false)
+ err := FillOutSpecGen(sg, &entities.ContainerCreateOptions{
+ ImageVolume: "ignore",
+ Volume: []string{vol}}, []string{},
+ )
+ machine.Enabled, machine.Type = oldEnable, oldType
+ return sg, err
+ }
+
+ for _, test := range tests {
+ msg := "Checking: " + test.vol
+ sg, err := f(test.vol, test.mach)
+ if test.outcome == fail {
+ assert.NotNil(t, err, msg)
+ continue
+ }
+ if !assert.Nil(t, err, msg) {
+ continue
+ }
+ if test.isN {
+ if !assert.Equal(t, 1, len(sg.Volumes), msg) {
+ continue
+ }
+ assert.Equal(t, test.source, sg.Volumes[0].Name, msg)
+ assert.Equal(t, test.dest, sg.Volumes[0].Dest, msg)
+ } else {
+ if !assert.Equal(t, 1, len(sg.Mounts), msg) {
+ continue
+ }
+ assert.Equal(t, test.source, sg.Mounts[0].Source, msg)
+ assert.Equal(t, test.dest, sg.Mounts[0].Destination, msg)
+ }
+ }
+}
diff --git a/pkg/specgenutil/volumes.go b/pkg/specgenutil/volumes.go
index 95ce420f8..50d745380 100644
--- a/pkg/specgenutil/volumes.go
+++ b/pkg/specgenutil/volumes.go
@@ -3,7 +3,7 @@ package specgenutil
import (
"encoding/csv"
"fmt"
- "path/filepath"
+ "path"
"strings"
"github.com/containers/common/pkg/parse"
@@ -123,7 +123,7 @@ func parseVolumes(volumeFlag, mountFlag, tmpfsFlag []string, addReadOnlyTmpfs bo
finalMounts := make([]spec.Mount, 0, len(unifiedMounts))
for _, mount := range unifiedMounts {
if mount.Type == define.TypeBind {
- absSrc, err := filepath.Abs(mount.Source)
+ absSrc, err := specgen.ConvertWinMountPath(mount.Source)
if err != nil {
return nil, nil, nil, nil, errors.Wrapf(err, "error getting absolute path of %s", mount.Source)
}
@@ -334,7 +334,7 @@ func getBindMount(args []string) (spec.Mount, error) {
if err := parse.ValidateVolumeCtrDir(kv[1]); err != nil {
return newMount, err
}
- newMount.Destination = filepath.Clean(kv[1])
+ newMount.Destination = unixPathClean(kv[1])
setDest = true
case "relabel":
if setRelabel {
@@ -456,7 +456,7 @@ func getTmpfsMount(args []string) (spec.Mount, error) {
if err := parse.ValidateVolumeCtrDir(kv[1]); err != nil {
return newMount, err
}
- newMount.Destination = filepath.Clean(kv[1])
+ newMount.Destination = unixPathClean(kv[1])
setDest = true
case "U", "chown":
if setOwnership {
@@ -507,7 +507,7 @@ func getDevptsMount(args []string) (spec.Mount, error) {
if err := parse.ValidateVolumeCtrDir(kv[1]); err != nil {
return newMount, err
}
- newMount.Destination = filepath.Clean(kv[1])
+ newMount.Destination = unixPathClean(kv[1])
setDest = true
default:
return newMount, errors.Wrapf(util.ErrBadMntOption, "%s", kv[0])
@@ -572,7 +572,7 @@ func getNamedVolume(args []string) (*specgen.NamedVolume, error) {
if err := parse.ValidateVolumeCtrDir(kv[1]); err != nil {
return nil, err
}
- newVolume.Dest = filepath.Clean(kv[1])
+ newVolume.Dest = unixPathClean(kv[1])
setDest = true
case "U", "chown":
if setOwnership {
@@ -624,7 +624,7 @@ func getImageVolume(args []string) (*specgen.ImageVolume, error) {
if err := parse.ValidateVolumeCtrDir(kv[1]); err != nil {
return nil, err
}
- newVolume.Destination = filepath.Clean(kv[1])
+ newVolume.Destination = unixPathClean(kv[1])
case "rw", "readwrite":
switch kv[1] {
case "true":
@@ -670,7 +670,7 @@ func getTmpfsMounts(tmpfsFlag []string) (map[string]spec.Mount, error) {
}
mount := spec.Mount{
- Destination: filepath.Clean(destPath),
+ Destination: unixPathClean(destPath),
Type: define.TypeTmpfs,
Options: options,
Source: define.TypeTmpfs,
@@ -700,3 +700,8 @@ func validChownFlag(flag string) (bool, error) {
return true, nil
}
+
+// Use path instead of filepath to preserve Unix style paths on Windows
+func unixPathClean(p string) string {
+ return path.Clean(p)
+}