mirror of
https://github.com/containers/podman.git
synced 2025-09-12 09:54:43 +08:00

Using the gvproxy application on the host, we can now port forward from the machine vm on the host. It requires that 'gvproxy' be installed in an executable location. gvproxy can be found in the containers/gvisor-tap-vsock github repo. [NO TESTS NEEDED] Signed-off-by: Brent Baude <bbaude@redhat.com>
632 lines
17 KiB
Go
632 lines
17 KiB
Go
package qemu
|
|
|
|
import (
|
|
"bufio"
|
|
"encoding/json"
|
|
"fmt"
|
|
"io/ioutil"
|
|
"net"
|
|
"os"
|
|
"os/exec"
|
|
"path/filepath"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/containers/podman/v3/pkg/rootless"
|
|
|
|
"github.com/containers/podman/v3/pkg/machine"
|
|
"github.com/containers/podman/v3/utils"
|
|
"github.com/containers/storage/pkg/homedir"
|
|
"github.com/digitalocean/go-qemu/qmp"
|
|
"github.com/pkg/errors"
|
|
"github.com/sirupsen/logrus"
|
|
)
|
|
|
|
var (
|
|
// vmtype refers to qemu (vs libvirt, krun, etc)
|
|
vmtype = "qemu"
|
|
)
|
|
|
|
// NewMachine initializes an instance of a virtual machine based on the qemu
|
|
// virtualization.
|
|
func NewMachine(opts machine.InitOptions) (machine.VM, error) {
|
|
vmConfigDir, err := machine.GetConfDir(vmtype)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
vm := new(MachineVM)
|
|
if len(opts.Name) > 0 {
|
|
vm.Name = opts.Name
|
|
}
|
|
ignitionFile := filepath.Join(vmConfigDir, vm.Name+".ign")
|
|
vm.IgnitionFilePath = ignitionFile
|
|
|
|
// An image was specified
|
|
if len(opts.ImagePath) > 0 {
|
|
vm.ImagePath = opts.ImagePath
|
|
}
|
|
|
|
// Assign remote user name. if not provided, use default
|
|
vm.RemoteUsername = opts.Username
|
|
if len(vm.RemoteUsername) < 1 {
|
|
vm.RemoteUsername = defaultRemoteUser
|
|
}
|
|
|
|
// Add a random port for ssh
|
|
port, err := utils.GetRandomPort()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
vm.Port = port
|
|
|
|
vm.CPUs = opts.CPUS
|
|
vm.Memory = opts.Memory
|
|
|
|
// Look up the executable
|
|
execPath, err := exec.LookPath(QemuCommand)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
cmd := append([]string{execPath})
|
|
// Add memory
|
|
cmd = append(cmd, []string{"-m", strconv.Itoa(int(vm.Memory))}...)
|
|
// Add cpus
|
|
cmd = append(cmd, []string{"-smp", strconv.Itoa(int(vm.CPUs))}...)
|
|
// Add ignition file
|
|
cmd = append(cmd, []string{"-fw_cfg", "name=opt/com.coreos/config,file=" + vm.IgnitionFilePath}...)
|
|
// Add qmp socket
|
|
monitor, err := NewQMPMonitor("unix", vm.Name, defaultQMPTimeout)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
vm.QMPMonitor = monitor
|
|
cmd = append(cmd, []string{"-qmp", monitor.Network + ":/" + monitor.Address + ",server=on,wait=off"}...)
|
|
|
|
// Add network
|
|
// Right now the mac address is hardcoded so that the host networking gives it a specific IP address. This is
|
|
// why we can only run one vm at a time right now
|
|
cmd = append(cmd, []string{"-netdev", "socket,id=vlan,fd=3", "-device", "virtio-net-pci,netdev=vlan,mac=5a:94:ef:e4:0c:ee"}...)
|
|
socketPath, err := getRuntimeDir()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
virtualSocketPath := filepath.Join(socketPath, "podman", vm.Name+"_ready.sock")
|
|
// Add serial port for readiness
|
|
cmd = append(cmd, []string{
|
|
"-device", "virtio-serial",
|
|
"-chardev", "socket,path=" + virtualSocketPath + ",server=on,wait=off,id=" + vm.Name + "_ready",
|
|
"-device", "virtserialport,chardev=" + vm.Name + "_ready" + ",name=org.fedoraproject.port.0"}...)
|
|
vm.CmdLine = cmd
|
|
return vm, nil
|
|
}
|
|
|
|
// LoadByName reads a json file that describes a known qemu vm
|
|
// and returns a vm instance
|
|
func LoadVMByName(name string) (machine.VM, error) {
|
|
vm := new(MachineVM)
|
|
vmConfigDir, err := machine.GetConfDir(vmtype)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
b, err := ioutil.ReadFile(filepath.Join(vmConfigDir, name+".json"))
|
|
if os.IsNotExist(err) {
|
|
return nil, errors.Wrap(machine.ErrNoSuchVM, name)
|
|
}
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
err = json.Unmarshal(b, vm)
|
|
logrus.Debug(vm.CmdLine)
|
|
return vm, err
|
|
}
|
|
|
|
// Init writes the json configuration file to the filesystem for
|
|
// other verbs (start, stop)
|
|
func (v *MachineVM) Init(opts machine.InitOptions) error {
|
|
var (
|
|
key string
|
|
)
|
|
sshDir := filepath.Join(homedir.Get(), ".ssh")
|
|
// GetConfDir creates the directory so no need to check for
|
|
// its existence
|
|
vmConfigDir, err := machine.GetConfDir(vmtype)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
jsonFile := filepath.Join(vmConfigDir, v.Name) + ".json"
|
|
v.IdentityPath = filepath.Join(sshDir, v.Name)
|
|
|
|
// The user has provided an alternate image which can be a file path
|
|
// or URL.
|
|
if len(opts.ImagePath) > 0 {
|
|
g, err := machine.NewGenericDownloader(vmtype, v.Name, opts.ImagePath)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
v.ImagePath = g.Get().LocalUncompressedFile
|
|
if err := g.DownloadImage(); err != nil {
|
|
return err
|
|
}
|
|
} else {
|
|
// Get the image as usual
|
|
dd, err := machine.NewFcosDownloader(vmtype, v.Name)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
v.ImagePath = dd.Get().LocalUncompressedFile
|
|
if err := dd.DownloadImage(); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
// Add arch specific options including image location
|
|
v.CmdLine = append(v.CmdLine, v.addArchOptions()...)
|
|
|
|
// Add location of bootable image
|
|
v.CmdLine = append(v.CmdLine, "-drive", "if=virtio,file="+v.ImagePath)
|
|
// This kind of stinks but no other way around this r/n
|
|
if len(opts.IgnitionPath) < 1 {
|
|
uri := machine.SSHRemoteConnection.MakeSSHURL("localhost", "/run/user/1000/podman/podman.sock", strconv.Itoa(v.Port), v.RemoteUsername)
|
|
if err := machine.AddConnection(&uri, v.Name, filepath.Join(sshDir, v.Name), opts.IsDefault); err != nil {
|
|
return err
|
|
}
|
|
|
|
uriRoot := machine.SSHRemoteConnection.MakeSSHURL("localhost", "/run/podman/podman.sock", strconv.Itoa(v.Port), "root")
|
|
if err := machine.AddConnection(&uriRoot, v.Name+"-root", filepath.Join(sshDir, v.Name), opts.IsDefault); err != nil {
|
|
return err
|
|
}
|
|
} else {
|
|
fmt.Println("An ignition path was provided. No SSH connection was added to Podman")
|
|
}
|
|
// Write the JSON file
|
|
b, err := json.MarshalIndent(v, "", " ")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if err := ioutil.WriteFile(jsonFile, b, 0644); err != nil {
|
|
return err
|
|
}
|
|
|
|
// User has provided ignition file so keygen
|
|
// will be skipped.
|
|
if len(opts.IgnitionPath) < 1 {
|
|
key, err = machine.CreateSSHKeys(v.IdentityPath)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
// Run arch specific things that need to be done
|
|
if err := v.prepare(); err != nil {
|
|
return err
|
|
}
|
|
|
|
originalDiskSize, err := getDiskSize(v.ImagePath)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
// Resize the disk image to input disk size
|
|
// only if the virtualdisk size is less than
|
|
// the given disk size
|
|
if opts.DiskSize<<(10*3) > originalDiskSize {
|
|
resize := exec.Command("qemu-img", []string{"resize", v.ImagePath, strconv.Itoa(int(opts.DiskSize)) + "G"}...)
|
|
resize.Stdout = os.Stdout
|
|
resize.Stderr = os.Stderr
|
|
if err := resize.Run(); err != nil {
|
|
return errors.Errorf("error resizing image: %q", err)
|
|
}
|
|
}
|
|
// If the user provides an ignition file, we need to
|
|
// copy it into the conf dir
|
|
if len(opts.IgnitionPath) > 0 {
|
|
inputIgnition, err := ioutil.ReadFile(opts.IgnitionPath)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return ioutil.WriteFile(v.IgnitionFilePath, inputIgnition, 0644)
|
|
}
|
|
// Write the ignition file
|
|
ign := machine.DynamicIgnition{
|
|
Name: opts.Username,
|
|
Key: key,
|
|
VMName: v.Name,
|
|
WritePath: v.IgnitionFilePath,
|
|
}
|
|
return machine.NewIgnitionFile(ign)
|
|
}
|
|
|
|
// Start executes the qemu command line and forks it
|
|
func (v *MachineVM) Start(name string, _ machine.StartOptions) error {
|
|
var (
|
|
conn net.Conn
|
|
err error
|
|
qemuSocketConn net.Conn
|
|
wait time.Duration = time.Millisecond * 500
|
|
)
|
|
if err := v.startHostNetworking(); err != nil {
|
|
return errors.Errorf("unable to start host networking: %q", err)
|
|
}
|
|
qemuSocketPath, _, err := v.getSocketandPid()
|
|
|
|
for i := 0; i < 6; i++ {
|
|
qemuSocketConn, err = net.Dial("unix", qemuSocketPath)
|
|
if err == nil {
|
|
break
|
|
}
|
|
time.Sleep(wait)
|
|
wait++
|
|
}
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
fd, err := qemuSocketConn.(*net.UnixConn).File()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
attr := new(os.ProcAttr)
|
|
files := []*os.File{os.Stdin, os.Stdout, os.Stderr, fd}
|
|
attr.Files = files
|
|
logrus.Debug(v.CmdLine)
|
|
cmd := v.CmdLine
|
|
|
|
// Disable graphic window when not in debug mode
|
|
// Done in start, so we're not suck with the debug level we used on init
|
|
if logrus.GetLevel() != logrus.DebugLevel {
|
|
cmd = append(cmd, "-display", "none")
|
|
}
|
|
|
|
_, err = os.StartProcess(v.CmdLine[0], cmd, attr)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
fmt.Println("Waiting for VM ...")
|
|
socketPath, err := getRuntimeDir()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// The socket is not made until the qemu process is running so here
|
|
// we do a backoff waiting for it. Once we have a conn, we break and
|
|
// then wait to read it.
|
|
for i := 0; i < 6; i++ {
|
|
conn, err = net.Dial("unix", filepath.Join(socketPath, "podman", v.Name+"_ready.sock"))
|
|
if err == nil {
|
|
break
|
|
}
|
|
time.Sleep(wait)
|
|
wait++
|
|
}
|
|
if err != nil {
|
|
return err
|
|
}
|
|
_, err = bufio.NewReader(conn).ReadString('\n')
|
|
return err
|
|
}
|
|
|
|
// Stop uses the qmp monitor to call a system_powerdown
|
|
func (v *MachineVM) Stop(name string, _ machine.StopOptions) error {
|
|
// check if the qmp socket is there. if not, qemu instance is gone
|
|
if _, err := os.Stat(v.QMPMonitor.Address); os.IsNotExist(err) {
|
|
// Right now it is NOT an error to stop a stopped machine
|
|
logrus.Debugf("QMP monitor socket %v does not exist", v.QMPMonitor.Address)
|
|
return nil
|
|
}
|
|
qmpMonitor, err := qmp.NewSocketMonitor(v.QMPMonitor.Network, v.QMPMonitor.Address, v.QMPMonitor.Timeout)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
// Simple JSON formation for the QAPI
|
|
stopCommand := struct {
|
|
Execute string `json:"execute"`
|
|
}{
|
|
Execute: "system_powerdown",
|
|
}
|
|
input, err := json.Marshal(stopCommand)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if err := qmpMonitor.Connect(); err != nil {
|
|
return err
|
|
}
|
|
defer func() {
|
|
if err := qmpMonitor.Disconnect(); err != nil {
|
|
logrus.Error(err)
|
|
}
|
|
}()
|
|
if _, err = qmpMonitor.Run(input); err != nil {
|
|
return err
|
|
}
|
|
_, pidFile, err := v.getSocketandPid()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if _, err := os.Stat(pidFile); os.IsNotExist(err) {
|
|
logrus.Infof("pid file %s does not exist", pidFile)
|
|
return nil
|
|
}
|
|
pidString, err := ioutil.ReadFile(pidFile)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
pidNum, err := strconv.Atoi(string(pidString))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
p, err := os.FindProcess(pidNum)
|
|
if p == nil && err != nil {
|
|
return err
|
|
}
|
|
return p.Kill()
|
|
}
|
|
|
|
// NewQMPMonitor creates the monitor subsection of our vm
|
|
func NewQMPMonitor(network, name string, timeout time.Duration) (Monitor, error) {
|
|
rtDir, err := getRuntimeDir()
|
|
if err != nil {
|
|
return Monitor{}, err
|
|
}
|
|
if !rootless.IsRootless() {
|
|
rtDir = "/run"
|
|
}
|
|
rtDir = filepath.Join(rtDir, "podman")
|
|
if _, err := os.Stat(filepath.Join(rtDir)); os.IsNotExist(err) {
|
|
// TODO 0644 is fine on linux but macos is weird
|
|
if err := os.MkdirAll(rtDir, 0755); err != nil {
|
|
return Monitor{}, err
|
|
}
|
|
}
|
|
if timeout == 0 {
|
|
timeout = defaultQMPTimeout
|
|
}
|
|
monitor := Monitor{
|
|
Network: network,
|
|
Address: filepath.Join(rtDir, "qmp_"+name+".sock"),
|
|
Timeout: timeout,
|
|
}
|
|
return monitor, nil
|
|
}
|
|
|
|
func (v *MachineVM) Remove(name string, opts machine.RemoveOptions) (string, func() error, error) {
|
|
var (
|
|
files []string
|
|
)
|
|
|
|
// cannot remove a running vm
|
|
if v.isRunning() {
|
|
return "", nil, errors.Errorf("running vm %q cannot be destroyed", v.Name)
|
|
}
|
|
|
|
// Collect all the files that need to be destroyed
|
|
if !opts.SaveKeys {
|
|
files = append(files, v.IdentityPath, v.IdentityPath+".pub")
|
|
}
|
|
if !opts.SaveIgnition {
|
|
files = append(files, v.IgnitionFilePath)
|
|
}
|
|
if !opts.SaveImage {
|
|
files = append(files, v.ImagePath)
|
|
}
|
|
files = append(files, v.archRemovalFiles()...)
|
|
|
|
if err := machine.RemoveConnection(v.Name); err != nil {
|
|
logrus.Error(err)
|
|
}
|
|
if err := machine.RemoveConnection(v.Name + "-root"); err != nil {
|
|
logrus.Error(err)
|
|
}
|
|
|
|
vmConfigDir, err := machine.GetConfDir(vmtype)
|
|
if err != nil {
|
|
return "", nil, err
|
|
}
|
|
files = append(files, filepath.Join(vmConfigDir, v.Name+".json"))
|
|
confirmationMessage := "\nThe following files will be deleted:\n\n"
|
|
for _, msg := range files {
|
|
confirmationMessage += msg + "\n"
|
|
}
|
|
confirmationMessage += "\n"
|
|
return confirmationMessage, func() error {
|
|
for _, f := range files {
|
|
if err := os.Remove(f); err != nil {
|
|
logrus.Error(err)
|
|
}
|
|
}
|
|
return nil
|
|
}, nil
|
|
}
|
|
|
|
func (v *MachineVM) isRunning() bool {
|
|
// Check if qmp socket path exists
|
|
if _, err := os.Stat(v.QMPMonitor.Address); os.IsNotExist(err) {
|
|
return false
|
|
}
|
|
// Check if we can dial it
|
|
if _, err := qmp.NewSocketMonitor(v.QMPMonitor.Network, v.QMPMonitor.Address, v.QMPMonitor.Timeout); err != nil {
|
|
return false
|
|
}
|
|
return true
|
|
}
|
|
|
|
// 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(name string, opts machine.SSHOptions) error {
|
|
if !v.isRunning() {
|
|
return errors.Errorf("vm %q is not running.", v.Name)
|
|
}
|
|
|
|
sshDestination := v.RemoteUsername + "@localhost"
|
|
port := strconv.Itoa(v.Port)
|
|
|
|
args := []string{"-i", v.IdentityPath, "-p", port, sshDestination, "-o", "UserKnownHostsFile /dev/null", "-o", "StrictHostKeyChecking no"}
|
|
if len(opts.Args) > 0 {
|
|
args = append(args, opts.Args...)
|
|
} else {
|
|
fmt.Printf("Connecting to vm %s. To close connection, use `~.` or `exit`\n", v.Name)
|
|
}
|
|
|
|
cmd := exec.Command("ssh", args...)
|
|
cmd.Stdout = os.Stdout
|
|
cmd.Stderr = os.Stderr
|
|
cmd.Stdin = os.Stdin
|
|
|
|
return cmd.Run()
|
|
}
|
|
|
|
// executes qemu-image info to get the virtual disk size
|
|
// of the diskimage
|
|
func getDiskSize(path string) (uint64, error) {
|
|
diskInfo := exec.Command("qemu-img", "info", "--output", "json", path)
|
|
stdout, err := diskInfo.StdoutPipe()
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
if err := diskInfo.Start(); err != nil {
|
|
return 0, err
|
|
}
|
|
tmpInfo := struct {
|
|
VirtualSize uint64 `json:"virtual-size"`
|
|
Filename string `json:"filename"`
|
|
ClusterSize int64 `json:"cluster-size"`
|
|
Format string `json:"format"`
|
|
FormatSpecific struct {
|
|
Type string `json:"type"`
|
|
Data map[string]string `json:"data"`
|
|
}
|
|
DirtyFlag bool `json:"dirty-flag"`
|
|
}{}
|
|
if err := json.NewDecoder(stdout).Decode(&tmpInfo); err != nil {
|
|
return 0, err
|
|
}
|
|
if err := diskInfo.Wait(); err != nil {
|
|
return 0, err
|
|
}
|
|
return tmpInfo.VirtualSize, nil
|
|
}
|
|
|
|
// List lists all vm's that use qemu virtualization
|
|
func List(_ machine.ListOptions) ([]*machine.ListResponse, error) {
|
|
return GetVMInfos()
|
|
}
|
|
|
|
func GetVMInfos() ([]*machine.ListResponse, error) {
|
|
vmConfigDir, err := machine.GetConfDir(vmtype)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
var listed []*machine.ListResponse
|
|
|
|
if err = filepath.Walk(vmConfigDir, func(path string, info os.FileInfo, err error) error {
|
|
vm := new(MachineVM)
|
|
if strings.HasSuffix(info.Name(), ".json") {
|
|
fullPath := filepath.Join(vmConfigDir, info.Name())
|
|
b, err := ioutil.ReadFile(fullPath)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
err = json.Unmarshal(b, vm)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
listEntry := new(machine.ListResponse)
|
|
|
|
listEntry.Name = vm.Name
|
|
listEntry.VMType = "qemu"
|
|
fi, err := os.Stat(fullPath)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
listEntry.CreatedAt = fi.ModTime()
|
|
|
|
fi, err = os.Stat(vm.ImagePath)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
listEntry.LastUp = fi.ModTime()
|
|
if vm.isRunning() {
|
|
listEntry.Running = true
|
|
}
|
|
|
|
listed = append(listed, listEntry)
|
|
}
|
|
return nil
|
|
}); err != nil {
|
|
return nil, err
|
|
}
|
|
return listed, err
|
|
}
|
|
|
|
func IsValidVMName(name string) (bool, error) {
|
|
infos, err := GetVMInfos()
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
for _, vm := range infos {
|
|
if vm.Name == name {
|
|
return true, nil
|
|
}
|
|
}
|
|
return false, nil
|
|
}
|
|
|
|
// CheckActiveVM checks if there is a VM already running
|
|
func CheckActiveVM() (bool, string, error) {
|
|
vms, err := GetVMInfos()
|
|
if err != nil {
|
|
return false, "", errors.Wrap(err, "error checking VM active")
|
|
}
|
|
for _, vm := range vms {
|
|
if vm.Running {
|
|
return true, vm.Name, nil
|
|
}
|
|
}
|
|
return false, "", nil
|
|
}
|
|
|
|
// startHostNetworking runs a binary on the host system that allows users
|
|
// to setup port forwarding to the podman virtual machine
|
|
func (v *MachineVM) startHostNetworking() error {
|
|
binary, err := exec.LookPath(machine.ForwarderBinaryName)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
// Listen on all at port 7777 for setting up and tearing
|
|
// down forwarding
|
|
listenSocket := "tcp://0.0.0.0:7777"
|
|
qemuSocket, pidFile, err := v.getSocketandPid()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
attr := new(os.ProcAttr)
|
|
// Pass on stdin, stdout, stderr
|
|
files := []*os.File{os.Stdin, os.Stdout, os.Stderr}
|
|
attr.Files = files
|
|
cmd := []string{binary}
|
|
cmd = append(cmd, []string{"-listen", listenSocket, "-listen-qemu", fmt.Sprintf("unix://%s", qemuSocket), "-pid-file", pidFile}...)
|
|
// Add the ssh port
|
|
cmd = append(cmd, []string{"-ssh-port", fmt.Sprintf("%d", v.Port)}...)
|
|
if logrus.GetLevel() == logrus.DebugLevel {
|
|
cmd = append(cmd, "--debug")
|
|
fmt.Println(cmd)
|
|
}
|
|
_, err = os.StartProcess(cmd[0], cmd, attr)
|
|
return err
|
|
}
|
|
|
|
func (v *MachineVM) getSocketandPid() (string, string, error) {
|
|
rtPath, err := getRuntimeDir()
|
|
if err != nil {
|
|
return "", "", err
|
|
}
|
|
if !rootless.IsRootless() {
|
|
rtPath = "/run"
|
|
}
|
|
socketDir := filepath.Join(rtPath, "podman")
|
|
pidFile := filepath.Join(socketDir, fmt.Sprintf("%s.pid", v.Name))
|
|
qemuSocket := filepath.Join(socketDir, fmt.Sprintf("qemu_%s.sock", v.Name))
|
|
return qemuSocket, pidFile, nil
|
|
}
|