github.com/hanks177/podman/v4@v4.1.3-0.20220613032544-16d90015bc83/pkg/machine/wsl/machine.go (about)

     1  //go:build windows
     2  // +build windows
     3  
     4  package wsl
     5  
     6  import (
     7  	"bufio"
     8  	"encoding/json"
     9  	"fmt"
    10  	"io"
    11  	"io/fs"
    12  	"io/ioutil"
    13  	"net/url"
    14  	"os"
    15  	"os/exec"
    16  	"path/filepath"
    17  	"strconv"
    18  	"strings"
    19  	"time"
    20  
    21  	"github.com/hanks177/podman/v4/pkg/machine"
    22  	"github.com/hanks177/podman/v4/utils"
    23  	"github.com/containers/storage/pkg/homedir"
    24  	"github.com/pkg/errors"
    25  	"github.com/sirupsen/logrus"
    26  	"golang.org/x/text/encoding/unicode"
    27  	"golang.org/x/text/transform"
    28  )
    29  
    30  var (
    31  	wslProvider = &Provider{}
    32  	// vmtype refers to qemu (vs libvirt, krun, etc)
    33  	vmtype = "wsl"
    34  )
    35  
    36  const (
    37  	ErrorSuccessRebootInitiated = 1641
    38  	ErrorSuccessRebootRequired  = 3010
    39  	currentMachineVersion       = 2
    40  )
    41  
    42  const containersConf = `[containers]
    43  
    44  [engine]
    45  cgroup_manager = "cgroupfs"
    46  events_logger = "file"
    47  `
    48  
    49  const appendPort = `grep -q Port\ %d /etc/ssh/sshd_config || echo Port %d >> /etc/ssh/sshd_config`
    50  
    51  const configServices = `ln -fs /usr/lib/systemd/system/sshd.service /etc/systemd/system/multi-user.target.wants/sshd.service
    52  ln -fs /usr/lib/systemd/system/podman.socket /etc/systemd/system/sockets.target.wants/podman.socket
    53  rm -f /etc/systemd/system/getty.target.wants/console-getty.service
    54  rm -f /etc/systemd/system/getty.target.wants/getty@tty1.service
    55  rm -f /etc/systemd/system/multi-user.target.wants/systemd-resolved.service
    56  rm -f /etc/systemd/system/dbus-org.freedesktop.resolve1.service
    57  ln -fs /dev/null /etc/systemd/system/console-getty.service
    58  mkdir -p /etc/systemd/system/systemd-sysusers.service.d/
    59  adduser -m [USER] -G wheel
    60  mkdir -p /home/[USER]/.config/systemd/[USER]/
    61  chown [USER]:[USER] /home/[USER]/.config
    62  `
    63  
    64  const sudoers = `%wheel        ALL=(ALL)       NOPASSWD: ALL
    65  `
    66  
    67  const bootstrap = `#!/bin/bash
    68  ps -ef | grep -v grep | grep -q systemd && exit 0
    69  nohup unshare --kill-child --fork --pid --mount --mount-proc --propagation shared /lib/systemd/systemd >/dev/null 2>&1 &
    70  sleep 0.1
    71  `
    72  
    73  const wslmotd = `
    74  You will be automatically entered into a nested process namespace where
    75  systemd is running. If you need to access the parent namespace, hit ctrl-d
    76  or type exit. This also means to log out you need to exit twice.
    77  
    78  `
    79  
    80  const sysdpid = "SYSDPID=`ps -eo cmd,pid | grep -m 1 ^/lib/systemd/systemd | awk '{print $2}'`"
    81  
    82  const profile = sysdpid + `
    83  if [ ! -z "$SYSDPID" ] && [ "$SYSDPID" != "1" ]; then
    84      cat /etc/wslmotd
    85  	/usr/local/bin/enterns
    86  fi
    87  `
    88  
    89  const enterns = "#!/bin/bash\n" + sysdpid + `
    90  if [ ! -z "$SYSDPID" ] && [ "$SYSDPID" != "1" ]; then
    91  	nsenter -m -p -t $SYSDPID "$@"
    92  fi
    93  `
    94  
    95  const waitTerm = sysdpid + `
    96  if [ ! -z "$SYSDPID" ]; then
    97  	timeout 60 tail -f /dev/null --pid $SYSDPID
    98  fi
    99  `
   100  
   101  // WSL kernel does not have sg and crypto_user modules
   102  const overrideSysusers = `[Service]
   103  LoadCredential=
   104  `
   105  
   106  const lingerService = `[Unit]
   107  Description=A systemd user unit demo
   108  After=network-online.target
   109  Wants=network-online.target podman.socket
   110  [Service]
   111  ExecStart=/usr/bin/sleep infinity
   112  `
   113  
   114  const lingerSetup = `mkdir -p /home/[USER]/.config/systemd/[USER]/default.target.wants
   115  ln -fs /home/[USER]/.config/systemd/[USER]/linger-example.service \
   116         /home/[USER]/.config/systemd/[USER]/default.target.wants/linger-example.service
   117  `
   118  
   119  const wslInstallError = `Could not %s. See previous output for any potential failure details.
   120  If you can not resolve the issue, and rerunning fails, try the "wsl --install" process
   121  outlined in the following article:
   122  
   123  http://docs.microsoft.com/en-us/windows/wsl/install
   124  
   125  `
   126  
   127  const wslKernelError = `Could not %s. See previous output for any potential failure details.
   128  If you can not resolve the issue, try rerunning the "podman machine init command". If that fails
   129  try the "wsl --update" command and then rerun "podman machine init". Finally, if all else fails,
   130  try following the steps outlined in the following article:
   131  
   132  http://docs.microsoft.com/en-us/windows/wsl/install
   133  
   134  `
   135  
   136  const wslInstallKernel = "install the WSL Kernel"
   137  
   138  const wslOldVersion = `Automatic installation of WSL can not be performed on this version of Windows
   139  Either update to Build 19041 (or later), or perform the manual installation steps
   140  outlined in the following article:
   141  
   142  http://docs.microsoft.com/en-us/windows/wsl/install\
   143  
   144  `
   145  
   146  const (
   147  	winSShProxy    = "win-sshproxy.exe"
   148  	winSshProxyTid = "win-sshproxy.tid"
   149  	pipePrefix     = "npipe:////./pipe/"
   150  	globalPipe     = "docker_engine"
   151  )
   152  
   153  type Provider struct{}
   154  
   155  type MachineVM struct {
   156  	// ConfigPath is the path to the configuration file
   157  	ConfigPath string
   158  	// Created contains the original created time instead of querying the file mod time
   159  	Created time.Time
   160  	// ImageStream is the version of fcos being used
   161  	ImageStream string
   162  	// ImagePath is the fq path to
   163  	ImagePath string
   164  	// LastUp contains the last recorded uptime
   165  	LastUp time.Time
   166  	// Name of the vm
   167  	Name string
   168  	// Whether this machine should run in a rootful or rootless manner
   169  	Rootful bool
   170  	// SSH identity, username, etc
   171  	machine.SSHConfig
   172  	// machine version
   173  	Version int
   174  }
   175  
   176  type ExitCodeError struct {
   177  	code uint
   178  }
   179  
   180  func (e *ExitCodeError) Error() string {
   181  	return fmt.Sprintf("Process failed with exit code: %d", e.code)
   182  }
   183  
   184  func GetWSLProvider() machine.Provider {
   185  	return wslProvider
   186  }
   187  
   188  // NewMachine initializes an instance of a wsl machine
   189  func (p *Provider) NewMachine(opts machine.InitOptions) (machine.VM, error) {
   190  	vm := new(MachineVM)
   191  	if len(opts.Name) > 0 {
   192  		vm.Name = opts.Name
   193  	}
   194  	configPath, err := getConfigPath(opts.Name)
   195  	if err != nil {
   196  		return vm, err
   197  	}
   198  
   199  	vm.ConfigPath = configPath
   200  	vm.ImagePath = opts.ImagePath
   201  	vm.RemoteUsername = opts.Username
   202  	vm.Created = time.Now()
   203  	vm.LastUp = vm.Created
   204  
   205  	// Add a random port for ssh
   206  	port, err := utils.GetRandomPort()
   207  	if err != nil {
   208  		return nil, err
   209  	}
   210  	vm.Port = port
   211  
   212  	return vm, nil
   213  }
   214  
   215  func getConfigPath(name string) (string, error) {
   216  	vmConfigDir, err := machine.GetConfDir(vmtype)
   217  	if err != nil {
   218  		return "", err
   219  	}
   220  	return filepath.Join(vmConfigDir, name+".json"), nil
   221  }
   222  
   223  // LoadByName reads a json file that describes a known qemu vm
   224  // and returns a vm instance
   225  func (p *Provider) LoadVMByName(name string) (machine.VM, error) {
   226  	configPath, err := getConfigPath(name)
   227  	if err != nil {
   228  		return nil, err
   229  	}
   230  
   231  	vm, err := readAndMigrate(configPath, name)
   232  	return vm, err
   233  }
   234  
   235  // readAndMigrate returns the content of the VM's
   236  // configuration file in json
   237  func readAndMigrate(configPath string, name string) (*MachineVM, error) {
   238  	vm := new(MachineVM)
   239  	b, err := os.ReadFile(configPath)
   240  	if err != nil {
   241  		if errors.Is(err, os.ErrNotExist) {
   242  			return nil, errors.Wrap(machine.ErrNoSuchVM, name)
   243  		}
   244  		return vm, err
   245  	}
   246  	err = json.Unmarshal(b, vm)
   247  	if err == nil && vm.Version < currentMachineVersion {
   248  		err = vm.migrateMachine(configPath)
   249  	}
   250  
   251  	return vm, err
   252  }
   253  
   254  func (v *MachineVM) migrateMachine(configPath string) error {
   255  	if v.Created.IsZero() {
   256  		if err := v.migrate40(configPath); err != nil {
   257  			return err
   258  		}
   259  	}
   260  
   261  	// Update older machines to use lingering
   262  	if err := enableUserLinger(v, toDist(v.Name)); err != nil {
   263  		return err
   264  	}
   265  
   266  	v.Version = currentMachineVersion
   267  	return v.writeConfig()
   268  }
   269  
   270  func (v *MachineVM) migrate40(configPath string) error {
   271  	v.ConfigPath = configPath
   272  	fi, err := os.Stat(configPath)
   273  	if err != nil {
   274  		return err
   275  	}
   276  	v.Created = fi.ModTime()
   277  	v.LastUp = getLegacyLastStart(v)
   278  	return nil
   279  }
   280  
   281  func getLegacyLastStart(vm *MachineVM) time.Time {
   282  	vmDataDir, err := machine.GetDataDir(vmtype)
   283  	if err != nil {
   284  		return vm.Created
   285  	}
   286  	distDir := filepath.Join(vmDataDir, "wsldist")
   287  	start := filepath.Join(distDir, vm.Name, "laststart")
   288  	info, err := os.Stat(start)
   289  	if err != nil {
   290  		return vm.Created
   291  	}
   292  	return info.ModTime()
   293  }
   294  
   295  // Init writes the json configuration file to the filesystem for
   296  // other verbs (start, stop)
   297  func (v *MachineVM) Init(opts machine.InitOptions) (bool, error) {
   298  	if cont, err := checkAndInstallWSL(opts); !cont {
   299  		appendOutputIfError(opts.ReExec, err)
   300  		return cont, err
   301  	}
   302  
   303  	homeDir := homedir.Get()
   304  	sshDir := filepath.Join(homeDir, ".ssh")
   305  	v.IdentityPath = filepath.Join(sshDir, v.Name)
   306  	v.Rootful = opts.Rootful
   307  	v.Version = currentMachineVersion
   308  
   309  	if err := downloadDistro(v, opts); err != nil {
   310  		return false, err
   311  	}
   312  
   313  	if err := v.writeConfig(); err != nil {
   314  		return false, err
   315  	}
   316  
   317  	if err := setupConnections(v, opts, sshDir); err != nil {
   318  		return false, err
   319  	}
   320  
   321  	dist, err := provisionWSLDist(v)
   322  	if err != nil {
   323  		return false, err
   324  	}
   325  
   326  	fmt.Println("Configuring system...")
   327  	if err = configureSystem(v, dist); err != nil {
   328  		return false, err
   329  	}
   330  
   331  	if err = installScripts(dist); err != nil {
   332  		return false, err
   333  	}
   334  
   335  	if err = createKeys(v, dist, sshDir); err != nil {
   336  		return false, err
   337  	}
   338  
   339  	return true, nil
   340  }
   341  
   342  func downloadDistro(v *MachineVM, opts machine.InitOptions) error {
   343  	var (
   344  		dd  machine.DistributionDownload
   345  		err error
   346  	)
   347  
   348  	if _, e := strconv.Atoi(opts.ImagePath); e == nil {
   349  		v.ImageStream = opts.ImagePath
   350  		dd, err = machine.NewFedoraDownloader(vmtype, v.Name, v.ImageStream)
   351  	} else {
   352  		v.ImageStream = "custom"
   353  		dd, err = machine.NewGenericDownloader(vmtype, v.Name, opts.ImagePath)
   354  	}
   355  	if err != nil {
   356  		return err
   357  	}
   358  
   359  	v.ImagePath = dd.Get().LocalUncompressedFile
   360  	return machine.DownloadImage(dd)
   361  }
   362  
   363  func (v *MachineVM) writeConfig() error {
   364  	jsonFile := v.ConfigPath
   365  
   366  	b, err := json.MarshalIndent(v, "", " ")
   367  	if err != nil {
   368  		return err
   369  	}
   370  	if err := ioutil.WriteFile(jsonFile, b, 0644); err != nil {
   371  		return errors.Wrap(err, "could not write machine json config")
   372  	}
   373  
   374  	return nil
   375  }
   376  
   377  func setupConnections(v *MachineVM, opts machine.InitOptions, sshDir string) error {
   378  	uri := machine.SSHRemoteConnection.MakeSSHURL("localhost", "/run/user/1000/podman/podman.sock", strconv.Itoa(v.Port), v.RemoteUsername)
   379  	uriRoot := machine.SSHRemoteConnection.MakeSSHURL("localhost", "/run/podman/podman.sock", strconv.Itoa(v.Port), "root")
   380  	identity := filepath.Join(sshDir, v.Name)
   381  
   382  	uris := []url.URL{uri, uriRoot}
   383  	names := []string{v.Name, v.Name + "-root"}
   384  
   385  	// The first connection defined when connections is empty will become the default
   386  	// regardless of IsDefault, so order according to rootful
   387  	if opts.Rootful {
   388  		uris[0], names[0], uris[1], names[1] = uris[1], names[1], uris[0], names[0]
   389  	}
   390  
   391  	for i := 0; i < 2; i++ {
   392  		if err := machine.AddConnection(&uris[i], names[i], identity, opts.IsDefault && i == 0); err != nil {
   393  			return err
   394  		}
   395  	}
   396  
   397  	return nil
   398  }
   399  
   400  func provisionWSLDist(v *MachineVM) (string, error) {
   401  	vmDataDir, err := machine.GetDataDir(vmtype)
   402  	if err != nil {
   403  		return "", err
   404  	}
   405  
   406  	distDir := filepath.Join(vmDataDir, "wsldist")
   407  	distTarget := filepath.Join(distDir, v.Name)
   408  	if err := os.MkdirAll(distDir, 0755); err != nil {
   409  		return "", errors.Wrap(err, "could not create wsldist directory")
   410  	}
   411  
   412  	dist := toDist(v.Name)
   413  	fmt.Println("Importing operating system into WSL (this may take 5+ minutes on a new WSL install)...")
   414  	if err = runCmdPassThrough("wsl", "--import", dist, distTarget, v.ImagePath); err != nil {
   415  		return "", errors.Wrap(err, "WSL import of guest OS failed")
   416  	}
   417  
   418  	fmt.Println("Installing packages (this will take awhile)...")
   419  	if err = runCmdPassThrough("wsl", "-d", dist, "dnf", "upgrade", "-y"); err != nil {
   420  		return "", errors.Wrap(err, "package upgrade on guest OS failed")
   421  	}
   422  
   423  	fmt.Println("Enabling Copr")
   424  	if err = runCmdPassThrough("wsl", "-d", dist, "dnf", "install", "-y", "'dnf-command(copr)'"); err != nil {
   425  		return "", errors.Wrap(err, "enabling copr failed")
   426  	}
   427  
   428  	fmt.Println("Enabling podman4 repo")
   429  	if err = runCmdPassThrough("wsl", "-d", dist, "dnf", "-y", "copr", "enable", "rhcontainerbot/podman4"); err != nil {
   430  		return "", errors.Wrap(err, "enabling copr failed")
   431  	}
   432  
   433  	if err = runCmdPassThrough("wsl", "-d", dist, "dnf", "install",
   434  		"podman", "podman-docker", "openssh-server", "procps-ng", "-y"); err != nil {
   435  		return "", errors.Wrap(err, "package installation on guest OS failed")
   436  	}
   437  
   438  	// Fixes newuidmap
   439  	if err = runCmdPassThrough("wsl", "-d", dist, "dnf", "reinstall", "shadow-utils", "-y"); err != nil {
   440  		return "", errors.Wrap(err, "package reinstallation of shadow-utils on guest OS failed")
   441  	}
   442  
   443  	// Windows 11 (NT Version = 10, Build 22000) generates harmless but scary messages on every
   444  	// operation when mount was not present on the initial start. Force a cycle so that it won't
   445  	// repeatedly complain.
   446  	if winVersionAtLeast(10, 0, 22000) {
   447  		if err := runCmdPassThrough("wsl", "--terminate", dist); err != nil {
   448  			logrus.Warnf("could not cycle WSL dist: %s", err.Error())
   449  		}
   450  	}
   451  
   452  	return dist, nil
   453  }
   454  
   455  func createKeys(v *MachineVM, dist string, sshDir string) error {
   456  	user := v.RemoteUsername
   457  
   458  	if err := os.MkdirAll(sshDir, 0700); err != nil {
   459  		return errors.Wrap(err, "could not create ssh directory")
   460  	}
   461  
   462  	if err := runCmdPassThrough("wsl", "--terminate", dist); err != nil {
   463  		return errors.Wrap(err, "could not cycle WSL dist")
   464  	}
   465  
   466  	key, err := machine.CreateSSHKeysPrefix(sshDir, v.Name, true, true, "wsl", "-d", dist)
   467  	if err != nil {
   468  		return errors.Wrap(err, "could not create ssh keys")
   469  	}
   470  
   471  	if err := pipeCmdPassThrough("wsl", key+"\n", "-d", dist, "sh", "-c", "mkdir -p /root/.ssh;"+
   472  		"cat >> /root/.ssh/authorized_keys; chmod 600 /root/.ssh/authorized_keys"); err != nil {
   473  		return errors.Wrap(err, "could not create root authorized keys on guest OS")
   474  	}
   475  
   476  	userAuthCmd := withUser("mkdir -p /home/[USER]/.ssh;"+
   477  		"cat >> /home/[USER]/.ssh/authorized_keys; chown -R [USER]:[USER] /home/[USER]/.ssh;"+
   478  		"chmod 600 /home/[USER]/.ssh/authorized_keys", user)
   479  	if err := pipeCmdPassThrough("wsl", key+"\n", "-d", dist, "sh", "-c", userAuthCmd); err != nil {
   480  		return errors.Wrapf(err, "could not create '%s' authorized keys on guest OS", v.RemoteUsername)
   481  	}
   482  
   483  	return nil
   484  }
   485  
   486  func configureSystem(v *MachineVM, dist string) error {
   487  	user := v.RemoteUsername
   488  	if err := runCmdPassThrough("wsl", "-d", dist, "sh", "-c", fmt.Sprintf(appendPort, v.Port, v.Port)); err != nil {
   489  		return errors.Wrap(err, "could not configure SSH port for guest OS")
   490  	}
   491  
   492  	if err := pipeCmdPassThrough("wsl", withUser(configServices, user), "-d", dist, "sh"); err != nil {
   493  		return errors.Wrap(err, "could not configure systemd settings for guest OS")
   494  	}
   495  
   496  	if err := pipeCmdPassThrough("wsl", sudoers, "-d", dist, "sh", "-c", "cat >> /etc/sudoers"); err != nil {
   497  		return errors.Wrap(err, "could not add wheel to sudoers")
   498  	}
   499  
   500  	if err := pipeCmdPassThrough("wsl", overrideSysusers, "-d", dist, "sh", "-c",
   501  		"cat > /etc/systemd/system/systemd-sysusers.service.d/override.conf"); err != nil {
   502  		return errors.Wrap(err, "could not generate systemd-sysusers override for guest OS")
   503  	}
   504  
   505  	lingerCmd := withUser("cat > /home/[USER]/.config/systemd/[USER]/linger-example.service", user)
   506  	if err := pipeCmdPassThrough("wsl", lingerService, "-d", dist, "sh", "-c", lingerCmd); err != nil {
   507  		return errors.Wrap(err, "could not generate linger service for guest OS")
   508  	}
   509  
   510  	if err := enableUserLinger(v, dist); err != nil {
   511  		return err
   512  	}
   513  
   514  	if err := pipeCmdPassThrough("wsl", withUser(lingerSetup, user), "-d", dist, "sh"); err != nil {
   515  		return errors.Wrap(err, "could not configure systemd settomgs for guest OS")
   516  	}
   517  
   518  	if err := pipeCmdPassThrough("wsl", containersConf, "-d", dist, "sh", "-c", "cat > /etc/containers/containers.conf"); err != nil {
   519  		return errors.Wrap(err, "could not create containers.conf for guest OS")
   520  	}
   521  
   522  	if err := runCmdPassThrough("wsl", "-d", dist, "sh", "-c", "echo wsl > /etc/containers/podman-machine"); err != nil {
   523  		return errors.Wrap(err, "could not create podman-machine file for guest OS")
   524  	}
   525  
   526  	return nil
   527  }
   528  
   529  func enableUserLinger(v *MachineVM, dist string) error {
   530  	lingerCmd := "mkdir -p /var/lib/systemd/linger; touch /var/lib/systemd/linger/" + v.RemoteUsername
   531  	if err := runCmdPassThrough("wsl", "-d", dist, "sh", "-c", lingerCmd); err != nil {
   532  		return errors.Wrap(err, "could not enable linger for remote user on guest OS")
   533  	}
   534  
   535  	return nil
   536  }
   537  
   538  func installScripts(dist string) error {
   539  	if err := pipeCmdPassThrough("wsl", enterns, "-d", dist, "sh", "-c",
   540  		"cat > /usr/local/bin/enterns; chmod 755 /usr/local/bin/enterns"); err != nil {
   541  		return errors.Wrap(err, "could not create enterns script for guest OS")
   542  	}
   543  
   544  	if err := pipeCmdPassThrough("wsl", profile, "-d", dist, "sh", "-c",
   545  		"cat > /etc/profile.d/enterns.sh"); err != nil {
   546  		return errors.Wrap(err, "could not create motd profile script for guest OS")
   547  	}
   548  
   549  	if err := pipeCmdPassThrough("wsl", wslmotd, "-d", dist, "sh", "-c", "cat > /etc/wslmotd"); err != nil {
   550  		return errors.Wrap(err, "could not create a WSL MOTD for guest OS")
   551  	}
   552  
   553  	if err := pipeCmdPassThrough("wsl", bootstrap, "-d", dist, "sh", "-c",
   554  		"cat > /root/bootstrap; chmod 755 /root/bootstrap"); err != nil {
   555  		return errors.Wrap(err, "could not create bootstrap script for guest OS")
   556  	}
   557  
   558  	return nil
   559  }
   560  
   561  func checkAndInstallWSL(opts machine.InitOptions) (bool, error) {
   562  	if isWSLInstalled() {
   563  		return true, nil
   564  	}
   565  
   566  	admin := hasAdminRights()
   567  
   568  	if !isWSLFeatureEnabled() {
   569  		return false, attemptFeatureInstall(opts, admin)
   570  	}
   571  
   572  	skip := false
   573  	if !opts.ReExec && !admin {
   574  		fmt.Println("Launching WSL Kernel Install...")
   575  		if err := launchElevate(wslInstallKernel); err != nil {
   576  			return false, err
   577  		}
   578  
   579  		skip = true
   580  	}
   581  
   582  	if !skip {
   583  		if err := installWslKernel(); err != nil {
   584  			fmt.Fprintf(os.Stderr, wslKernelError, wslInstallKernel)
   585  			return false, err
   586  		}
   587  
   588  		if opts.ReExec {
   589  			return false, nil
   590  		}
   591  	}
   592  
   593  	return true, nil
   594  }
   595  
   596  func attemptFeatureInstall(opts machine.InitOptions, admin bool) error {
   597  	if !winVersionAtLeast(10, 0, 18362) {
   598  		return errors.Errorf("Your version of Windows does not support WSL. Update to Windows 10 Build 19041 or later")
   599  	} else if !winVersionAtLeast(10, 0, 19041) {
   600  		fmt.Fprint(os.Stderr, wslOldVersion)
   601  		return errors.Errorf("WSL can not be automatically installed")
   602  	}
   603  
   604  	message := "WSL is not installed on this system, installing it.\n\n"
   605  
   606  	if !admin {
   607  		message += "Since you are not running as admin, a new window will open and " +
   608  			"require you to approve administrator privileges.\n\n"
   609  	}
   610  
   611  	message += "NOTE: A system reboot will be required as part of this process. " +
   612  		"If you prefer, you may abort now, and perform a manual installation using the \"wsl --install\" command."
   613  
   614  	if !opts.ReExec && MessageBox(message, "Podman Machine", false) != 1 {
   615  		return errors.Errorf("WSL installation aborted")
   616  	}
   617  
   618  	if !opts.ReExec && !admin {
   619  		return launchElevate("install the Windows WSL Features")
   620  	}
   621  
   622  	return installWsl()
   623  }
   624  
   625  func launchElevate(operation string) error {
   626  	truncateElevatedOutputFile()
   627  	err := relaunchElevatedWait()
   628  	if err != nil {
   629  		if eerr, ok := err.(*ExitCodeError); ok {
   630  			if eerr.code == ErrorSuccessRebootRequired {
   631  				fmt.Println("Reboot is required to continue installation, please reboot at your convenience")
   632  				return nil
   633  			}
   634  		}
   635  
   636  		fmt.Fprintf(os.Stderr, "Elevated process failed with error: %v\n\n", err)
   637  		dumpOutputFile()
   638  		fmt.Fprintf(os.Stderr, wslInstallError, operation)
   639  	}
   640  	return err
   641  }
   642  
   643  func installWsl() error {
   644  	log, err := getElevatedOutputFileWrite()
   645  	if err != nil {
   646  		return err
   647  	}
   648  	defer log.Close()
   649  	if err := runCmdPassThroughTee(log, "dism", "/online", "/enable-feature",
   650  		"/featurename:Microsoft-Windows-Subsystem-Linux", "/all", "/norestart"); isMsiError(err) {
   651  		return errors.Wrap(err, "could not enable WSL Feature")
   652  	}
   653  
   654  	if err = runCmdPassThroughTee(log, "dism", "/online", "/enable-feature",
   655  		"/featurename:VirtualMachinePlatform", "/all", "/norestart"); isMsiError(err) {
   656  		return errors.Wrap(err, "could not enable Virtual Machine Feature")
   657  	}
   658  	log.Close()
   659  
   660  	return reboot()
   661  }
   662  
   663  func installWslKernel() error {
   664  	log, err := getElevatedOutputFileWrite()
   665  	if err != nil {
   666  		return err
   667  	}
   668  	defer log.Close()
   669  
   670  	message := "Installing WSL Kernel Update"
   671  	fmt.Println(message)
   672  	fmt.Fprintln(log, message)
   673  
   674  	backoff := 500 * time.Millisecond
   675  	for i := 0; i < 5; i++ {
   676  		err = runCmdPassThroughTee(log, "wsl", "--update")
   677  		if err == nil {
   678  			break
   679  		}
   680  		// In case of unusual circumstances (e.g. race with installer actions)
   681  		// retry a few times
   682  		message = "An error occurred attempting the WSL Kernel update, retrying..."
   683  		fmt.Println(message)
   684  		fmt.Fprintln(log, message)
   685  		time.Sleep(backoff)
   686  		backoff *= 2
   687  	}
   688  
   689  	if err != nil {
   690  		return errors.Wrap(err, "could not install WSL Kernel")
   691  	}
   692  
   693  	return nil
   694  }
   695  
   696  func getElevatedOutputFileName() (string, error) {
   697  	dir, err := homedir.GetDataHome()
   698  	if err != nil {
   699  		return "", err
   700  	}
   701  	return filepath.Join(dir, "podman-elevated-output.log"), nil
   702  }
   703  
   704  func dumpOutputFile() {
   705  	file, err := getElevatedOutputFileRead()
   706  	if err != nil {
   707  		logrus.Debug("could not find elevated child output file")
   708  		return
   709  	}
   710  	defer file.Close()
   711  	_, _ = io.Copy(os.Stdout, file)
   712  }
   713  
   714  func getElevatedOutputFileRead() (*os.File, error) {
   715  	return getElevatedOutputFile(os.O_RDONLY)
   716  }
   717  
   718  func getElevatedOutputFileWrite() (*os.File, error) {
   719  	return getElevatedOutputFile(os.O_WRONLY | os.O_CREATE | os.O_APPEND)
   720  }
   721  
   722  func appendOutputIfError(write bool, err error) {
   723  	if write && err == nil {
   724  		return
   725  	}
   726  
   727  	if file, check := getElevatedOutputFileWrite(); check == nil {
   728  		defer file.Close()
   729  		fmt.Fprintf(file, "Error: %v\n", err)
   730  	}
   731  }
   732  
   733  func truncateElevatedOutputFile() error {
   734  	name, err := getElevatedOutputFileName()
   735  	if err != nil {
   736  		return err
   737  	}
   738  
   739  	return os.Truncate(name, 0)
   740  }
   741  
   742  func getElevatedOutputFile(mode int) (*os.File, error) {
   743  	name, err := getElevatedOutputFileName()
   744  	if err != nil {
   745  		return nil, err
   746  	}
   747  
   748  	dir, err := homedir.GetDataHome()
   749  	if err != nil {
   750  		return nil, err
   751  	}
   752  
   753  	if err = os.MkdirAll(dir, 0755); err != nil {
   754  		return nil, err
   755  	}
   756  
   757  	return os.OpenFile(name, mode, 0644)
   758  }
   759  
   760  func isMsiError(err error) bool {
   761  	if err == nil {
   762  		return false
   763  	}
   764  
   765  	if eerr, ok := err.(*exec.ExitError); ok {
   766  		switch eerr.ExitCode() {
   767  		case 0:
   768  			fallthrough
   769  		case ErrorSuccessRebootInitiated:
   770  			fallthrough
   771  		case ErrorSuccessRebootRequired:
   772  			return false
   773  		}
   774  	}
   775  
   776  	return true
   777  }
   778  func toDist(name string) string {
   779  	if !strings.HasPrefix(name, "podman") {
   780  		name = "podman-" + name
   781  	}
   782  	return name
   783  }
   784  
   785  func withUser(s string, user string) string {
   786  	return strings.ReplaceAll(s, "[USER]", user)
   787  }
   788  
   789  func runCmdPassThrough(name string, arg ...string) error {
   790  	logrus.Debugf("Running command: %s %v", name, arg)
   791  	cmd := exec.Command(name, arg...)
   792  	cmd.Stdin = os.Stdin
   793  	cmd.Stdout = os.Stdout
   794  	cmd.Stderr = os.Stderr
   795  	return cmd.Run()
   796  }
   797  
   798  func runCmdPassThroughTee(out io.Writer, name string, arg ...string) error {
   799  	logrus.Debugf("Running command: %s %v", name, arg)
   800  
   801  	// TODO - Perhaps improve this with a conpty pseudo console so that
   802  	//        dism installer text bars mirror console behavior (redraw)
   803  	cmd := exec.Command(name, arg...)
   804  	cmd.Stdin = os.Stdin
   805  	cmd.Stdout = io.MultiWriter(os.Stdout, out)
   806  	cmd.Stderr = io.MultiWriter(os.Stderr, out)
   807  	return cmd.Run()
   808  }
   809  
   810  func pipeCmdPassThrough(name string, input string, arg ...string) error {
   811  	logrus.Debugf("Running command: %s %v", name, arg)
   812  	cmd := exec.Command(name, arg...)
   813  	cmd.Stdin = strings.NewReader(input)
   814  	cmd.Stdout = os.Stdout
   815  	cmd.Stderr = os.Stderr
   816  	return cmd.Run()
   817  }
   818  
   819  func (v *MachineVM) Set(_ string, opts machine.SetOptions) ([]error, error) {
   820  	// If one setting fails to be applied, the others settings will not fail and still be applied.
   821  	// The setting(s) that failed to be applied will have its errors returned in setErrors
   822  	var setErrors []error
   823  
   824  	if opts.Rootful != nil && v.Rootful != *opts.Rootful {
   825  		err := v.setRootful(*opts.Rootful)
   826  		if err != nil {
   827  			setErrors = append(setErrors, errors.Wrapf(err, "error setting rootful option"))
   828  		} else {
   829  			v.Rootful = *opts.Rootful
   830  		}
   831  	}
   832  
   833  	if opts.CPUs != nil {
   834  		setErrors = append(setErrors, errors.Errorf("changing CPUs not supported for WSL machines"))
   835  	}
   836  
   837  	if opts.Memory != nil {
   838  		setErrors = append(setErrors, errors.Errorf("changing memory not supported for WSL machines"))
   839  
   840  	}
   841  
   842  	if opts.DiskSize != nil {
   843  		setErrors = append(setErrors, errors.Errorf("changing Disk Size not supported for WSL machines"))
   844  	}
   845  
   846  	return setErrors, v.writeConfig()
   847  }
   848  
   849  func (v *MachineVM) Start(name string, _ machine.StartOptions) error {
   850  	if v.isRunning() {
   851  		return errors.Errorf("%q is already running", name)
   852  	}
   853  
   854  	dist := toDist(name)
   855  
   856  	err := runCmdPassThrough("wsl", "-d", dist, "/root/bootstrap")
   857  	if err != nil {
   858  		return errors.Wrap(err, "WSL bootstrap script failed")
   859  	}
   860  
   861  	if !v.Rootful {
   862  		fmt.Printf("\nThis machine is currently configured in rootless mode. If your containers\n")
   863  		fmt.Printf("require root permissions (e.g. ports < 1024), or if you run into compatibility\n")
   864  		fmt.Printf("issues with non-podman clients, you can switch using the following command: \n")
   865  
   866  		suffix := ""
   867  		if name != machine.DefaultMachineName {
   868  			suffix = " " + name
   869  		}
   870  		fmt.Printf("\n\tpodman machine set --rootful%s\n\n", suffix)
   871  	}
   872  
   873  	globalName, pipeName, err := launchWinProxy(v)
   874  	if err != nil {
   875  		fmt.Fprintln(os.Stderr, "API forwarding for Docker API clients is not available due to the following startup failures.")
   876  		fmt.Fprintf(os.Stderr, "\t%s\n", err.Error())
   877  		fmt.Fprintln(os.Stderr, "\nPodman clients are still able to connect.")
   878  	} else {
   879  		fmt.Printf("API forwarding listening on: %s\n", pipeName)
   880  		if globalName {
   881  			fmt.Printf("\nDocker API clients default to this address. You do not need to set DOCKER_HOST.\n")
   882  		} else {
   883  			fmt.Printf("\nAnother process was listening on the default Docker API pipe address.\n")
   884  			fmt.Printf("You can still connect Docker API clients by setting DOCKER HOST using the\n")
   885  			fmt.Printf("following powershell command in your terminal session:\n")
   886  			fmt.Printf("\n\t$Env:DOCKER_HOST = '%s'\n", pipeName)
   887  			fmt.Printf("\nOr in a classic CMD prompt:\n")
   888  			fmt.Printf("\n\tset DOCKER_HOST = '%s'\n", pipeName)
   889  			fmt.Printf("\nAlternatively terminate the other process and restart podman machine.\n")
   890  		}
   891  	}
   892  
   893  	_, _, err = v.updateTimeStamps(true)
   894  	return err
   895  }
   896  
   897  func launchWinProxy(v *MachineVM) (bool, string, error) {
   898  	machinePipe := toDist(v.Name)
   899  	if !pipeAvailable(machinePipe) {
   900  		return false, "", errors.Errorf("could not start api proxy since expected pipe is not available: %s", machinePipe)
   901  	}
   902  
   903  	globalName := false
   904  	if pipeAvailable(globalPipe) {
   905  		globalName = true
   906  	}
   907  
   908  	exe, err := os.Executable()
   909  	if err != nil {
   910  		return globalName, "", err
   911  	}
   912  
   913  	exe, err = filepath.EvalSymlinks(exe)
   914  	if err != nil {
   915  		return globalName, "", err
   916  	}
   917  
   918  	command := filepath.Join(filepath.Dir(exe), winSShProxy)
   919  	stateDir, err := getWinProxyStateDir(v)
   920  	if err != nil {
   921  		return globalName, "", err
   922  	}
   923  
   924  	destSock := "/run/user/1000/podman/podman.sock"
   925  	forwardUser := v.RemoteUsername
   926  
   927  	if v.Rootful {
   928  		destSock = "/run/podman/podman.sock"
   929  		forwardUser = "root"
   930  	}
   931  
   932  	dest := fmt.Sprintf("ssh://%s@localhost:%d%s", forwardUser, v.Port, destSock)
   933  	args := []string{v.Name, stateDir, pipePrefix + machinePipe, dest, v.IdentityPath}
   934  	waitPipe := machinePipe
   935  	if globalName {
   936  		args = append(args, pipePrefix+globalPipe, dest, v.IdentityPath)
   937  		waitPipe = globalPipe
   938  	}
   939  
   940  	cmd := exec.Command(command, args...)
   941  	if err := cmd.Start(); err != nil {
   942  		return globalName, "", err
   943  	}
   944  
   945  	return globalName, pipePrefix + waitPipe, waitPipeExists(waitPipe, 30, func() error {
   946  		active, exitCode := getProcessState(cmd.Process.Pid)
   947  		if !active {
   948  			return errors.Errorf("win-sshproxy.exe failed to start, exit code: %d (see windows event logs)", exitCode)
   949  		}
   950  
   951  		return nil
   952  	})
   953  }
   954  
   955  func getWinProxyStateDir(v *MachineVM) (string, error) {
   956  	dir, err := machine.GetDataDir(vmtype)
   957  	if err != nil {
   958  		return "", err
   959  	}
   960  	stateDir := filepath.Join(dir, v.Name)
   961  	if err = os.MkdirAll(stateDir, 0755); err != nil {
   962  		return "", err
   963  	}
   964  
   965  	return stateDir, nil
   966  }
   967  
   968  func pipeAvailable(pipeName string) bool {
   969  	_, err := os.Stat(`\\.\pipe\` + pipeName)
   970  	return os.IsNotExist(err)
   971  }
   972  
   973  func waitPipeExists(pipeName string, retries int, checkFailure func() error) error {
   974  	var err error
   975  	for i := 0; i < retries; i++ {
   976  		_, err = os.Stat(`\\.\pipe\` + pipeName)
   977  		if err == nil {
   978  			break
   979  		}
   980  		if fail := checkFailure(); fail != nil {
   981  			return fail
   982  		}
   983  		time.Sleep(100 * time.Millisecond)
   984  	}
   985  
   986  	return err
   987  }
   988  
   989  func isWSLInstalled() bool {
   990  	cmd := exec.Command("wsl", "--status")
   991  	out, err := cmd.StdoutPipe()
   992  	if err != nil {
   993  		return false
   994  	}
   995  	if err = cmd.Start(); err != nil {
   996  		return false
   997  	}
   998  	scanner := bufio.NewScanner(transform.NewReader(out, unicode.UTF16(unicode.LittleEndian, unicode.UseBOM).NewDecoder()))
   999  	result := true
  1000  	for scanner.Scan() {
  1001  		line := scanner.Text()
  1002  		// Windows 11 does not set an error exit code when a kernel is not avail
  1003  		if strings.Contains(line, "kernel file is not found") {
  1004  			result = false
  1005  			break
  1006  		}
  1007  	}
  1008  	if err := cmd.Wait(); !result || err != nil {
  1009  		return false
  1010  	}
  1011  
  1012  	return true
  1013  }
  1014  
  1015  func isWSLFeatureEnabled() bool {
  1016  	cmd := exec.Command("wsl", "--set-default-version", "2")
  1017  	return cmd.Run() == nil
  1018  }
  1019  
  1020  func isWSLRunning(dist string) (bool, error) {
  1021  	cmd := exec.Command("wsl", "-l", "--running")
  1022  	out, err := cmd.StdoutPipe()
  1023  	if err != nil {
  1024  		return false, err
  1025  	}
  1026  	if err = cmd.Start(); err != nil {
  1027  		return false, err
  1028  	}
  1029  	scanner := bufio.NewScanner(transform.NewReader(out, unicode.UTF16(unicode.LittleEndian, unicode.UseBOM).NewDecoder()))
  1030  	result := false
  1031  	for scanner.Scan() {
  1032  		fields := strings.Fields(scanner.Text())
  1033  		if len(fields) > 0 && dist == fields[0] {
  1034  			result = true
  1035  			break
  1036  		}
  1037  	}
  1038  
  1039  	_ = cmd.Wait()
  1040  
  1041  	return result, nil
  1042  }
  1043  
  1044  func isSystemdRunning(dist string) (bool, error) {
  1045  	cmd := exec.Command("wsl", "-d", dist, "sh")
  1046  	cmd.Stdin = strings.NewReader(sysdpid + "\necho $SYSDPID\n")
  1047  	out, err := cmd.StdoutPipe()
  1048  	if err != nil {
  1049  		return false, err
  1050  	}
  1051  	if err = cmd.Start(); err != nil {
  1052  		return false, err
  1053  	}
  1054  	scanner := bufio.NewScanner(out)
  1055  	result := false
  1056  	if scanner.Scan() {
  1057  		text := scanner.Text()
  1058  		i, err := strconv.Atoi(text)
  1059  		if err == nil && i > 0 {
  1060  			result = true
  1061  		}
  1062  	}
  1063  
  1064  	_ = cmd.Wait()
  1065  
  1066  	return result, nil
  1067  }
  1068  
  1069  func (v *MachineVM) Stop(name string, _ machine.StopOptions) error {
  1070  	dist := toDist(v.Name)
  1071  
  1072  	wsl, err := isWSLRunning(dist)
  1073  	if err != nil {
  1074  		return err
  1075  	}
  1076  
  1077  	sysd := false
  1078  	if wsl {
  1079  		sysd, err = isSystemdRunning(dist)
  1080  		if err != nil {
  1081  			return err
  1082  		}
  1083  	}
  1084  
  1085  	if !wsl || !sysd {
  1086  		return errors.Errorf("%q is not running", v.Name)
  1087  	}
  1088  
  1089  	_, _, _ = v.updateTimeStamps(true)
  1090  
  1091  	if err := stopWinProxy(v); err != nil {
  1092  		fmt.Fprintf(os.Stderr, "Could not stop API forwarding service (win-sshproxy.exe): %s\n", err.Error())
  1093  	}
  1094  
  1095  	cmd := exec.Command("wsl", "-d", dist, "sh")
  1096  	cmd.Stdin = strings.NewReader(waitTerm)
  1097  	if err = cmd.Start(); err != nil {
  1098  		return errors.Wrap(err, "Error executing wait command")
  1099  	}
  1100  
  1101  	exitCmd := exec.Command("wsl", "-d", dist, "/usr/local/bin/enterns", "systemctl", "exit", "0")
  1102  	if err = exitCmd.Run(); err != nil {
  1103  		return errors.Wrap(err, "Error stopping sysd")
  1104  	}
  1105  
  1106  	if err = cmd.Wait(); err != nil {
  1107  		return err
  1108  	}
  1109  
  1110  	cmd = exec.Command("wsl", "--terminate", dist)
  1111  	if err = cmd.Run(); err != nil {
  1112  		return err
  1113  	}
  1114  
  1115  	return nil
  1116  }
  1117  
  1118  func (v *MachineVM) State(bypass bool) (machine.Status, error) {
  1119  	if v.isRunning() {
  1120  		return machine.Running, nil
  1121  	}
  1122  
  1123  	return machine.Stopped, nil
  1124  }
  1125  
  1126  func stopWinProxy(v *MachineVM) error {
  1127  	pid, tid, tidFile, err := readWinProxyTid(v)
  1128  	if err != nil {
  1129  		return err
  1130  	}
  1131  
  1132  	proc, err := os.FindProcess(int(pid))
  1133  	if err != nil {
  1134  		return nil
  1135  	}
  1136  	sendQuit(tid)
  1137  	_ = waitTimeout(proc, 20*time.Second)
  1138  	_ = os.Remove(tidFile)
  1139  
  1140  	return nil
  1141  }
  1142  
  1143  func waitTimeout(proc *os.Process, timeout time.Duration) bool {
  1144  	done := make(chan bool)
  1145  	go func() {
  1146  		proc.Wait()
  1147  		done <- true
  1148  	}()
  1149  	ret := false
  1150  	select {
  1151  	case <-time.After(timeout):
  1152  		proc.Kill()
  1153  		<-done
  1154  	case <-done:
  1155  		ret = true
  1156  		break
  1157  	}
  1158  
  1159  	return ret
  1160  }
  1161  
  1162  func readWinProxyTid(v *MachineVM) (uint32, uint32, string, error) {
  1163  	stateDir, err := getWinProxyStateDir(v)
  1164  	if err != nil {
  1165  		return 0, 0, "", err
  1166  	}
  1167  
  1168  	tidFile := filepath.Join(stateDir, winSshProxyTid)
  1169  	contents, err := ioutil.ReadFile(tidFile)
  1170  	if err != nil {
  1171  		return 0, 0, "", err
  1172  	}
  1173  
  1174  	var pid, tid uint32
  1175  	fmt.Sscanf(string(contents), "%d:%d", &pid, &tid)
  1176  	return pid, tid, tidFile, nil
  1177  }
  1178  
  1179  //nolint:cyclop
  1180  func (v *MachineVM) Remove(name string, opts machine.RemoveOptions) (string, func() error, error) {
  1181  	var files []string
  1182  
  1183  	if v.isRunning() {
  1184  		return "", nil, errors.Errorf("running vm %q cannot be destroyed", v.Name)
  1185  	}
  1186  
  1187  	// Collect all the files that need to be destroyed
  1188  	if !opts.SaveKeys {
  1189  		files = append(files, v.IdentityPath, v.IdentityPath+".pub")
  1190  	}
  1191  	if !opts.SaveImage {
  1192  		files = append(files, v.ImagePath)
  1193  	}
  1194  
  1195  	vmConfigDir, err := machine.GetConfDir(vmtype)
  1196  	if err != nil {
  1197  		return "", nil, err
  1198  	}
  1199  	files = append(files, filepath.Join(vmConfigDir, v.Name+".json"))
  1200  
  1201  	vmDataDir, err := machine.GetDataDir(vmtype)
  1202  	if err != nil {
  1203  		return "", nil, err
  1204  	}
  1205  	files = append(files, filepath.Join(vmDataDir, "wsldist", v.Name))
  1206  
  1207  	confirmationMessage := "\nThe following files will be deleted:\n\n"
  1208  	for _, msg := range files {
  1209  		confirmationMessage += msg + "\n"
  1210  	}
  1211  
  1212  	confirmationMessage += "\n"
  1213  	return confirmationMessage, func() error {
  1214  		if err := machine.RemoveConnection(v.Name); err != nil {
  1215  			logrus.Error(err)
  1216  		}
  1217  		if err := machine.RemoveConnection(v.Name + "-root"); err != nil {
  1218  			logrus.Error(err)
  1219  		}
  1220  		if err := runCmdPassThrough("wsl", "--unregister", toDist(v.Name)); err != nil {
  1221  			logrus.Error(err)
  1222  		}
  1223  		for _, f := range files {
  1224  			if err := os.RemoveAll(f); err != nil {
  1225  				logrus.Error(err)
  1226  			}
  1227  		}
  1228  		return nil
  1229  	}, nil
  1230  }
  1231  
  1232  func (v *MachineVM) isRunning() bool {
  1233  	dist := toDist(v.Name)
  1234  
  1235  	wsl, err := isWSLRunning(dist)
  1236  	if err != nil {
  1237  		return false
  1238  	}
  1239  
  1240  	sysd := false
  1241  	if wsl {
  1242  		sysd, err = isSystemdRunning(dist)
  1243  
  1244  		if err != nil {
  1245  			return false
  1246  		}
  1247  	}
  1248  
  1249  	return sysd
  1250  }
  1251  
  1252  // SSH opens an interactive SSH session to the vm specified.
  1253  // Added ssh function to VM interface: pkg/machine/config/go : line 58
  1254  func (v *MachineVM) SSH(name string, opts machine.SSHOptions) error {
  1255  	if !v.isRunning() {
  1256  		return errors.Errorf("vm %q is not running.", v.Name)
  1257  	}
  1258  
  1259  	username := opts.Username
  1260  	if username == "" {
  1261  		username = v.RemoteUsername
  1262  	}
  1263  
  1264  	sshDestination := username + "@localhost"
  1265  	port := strconv.Itoa(v.Port)
  1266  
  1267  	args := []string{"-i", v.IdentityPath, "-p", port, sshDestination, "-o", "UserKnownHostsFile /dev/null", "-o", "StrictHostKeyChecking no"}
  1268  	if len(opts.Args) > 0 {
  1269  		args = append(args, opts.Args...)
  1270  	} else {
  1271  		fmt.Printf("Connecting to vm %s. To close connection, use `~.` or `exit`\n", v.Name)
  1272  	}
  1273  
  1274  	cmd := exec.Command("ssh", args...)
  1275  	logrus.Debugf("Executing: ssh %v\n", args)
  1276  
  1277  	cmd.Stdout = os.Stdout
  1278  	cmd.Stderr = os.Stderr
  1279  	cmd.Stdin = os.Stdin
  1280  
  1281  	return cmd.Run()
  1282  }
  1283  
  1284  // List lists all vm's that use qemu virtualization
  1285  func (p *Provider) List(_ machine.ListOptions) ([]*machine.ListResponse, error) {
  1286  	return GetVMInfos()
  1287  }
  1288  
  1289  func GetVMInfos() ([]*machine.ListResponse, error) {
  1290  	vmConfigDir, err := machine.GetConfDir(vmtype)
  1291  	if err != nil {
  1292  		return nil, err
  1293  	}
  1294  
  1295  	var listed []*machine.ListResponse
  1296  
  1297  	if err = filepath.WalkDir(vmConfigDir, func(path string, d fs.DirEntry, err error) error {
  1298  		if strings.HasSuffix(d.Name(), ".json") {
  1299  			path := filepath.Join(vmConfigDir, d.Name())
  1300  			vm, err := readAndMigrate(path, strings.TrimSuffix(d.Name(), ".json"))
  1301  			if err != nil {
  1302  				return err
  1303  			}
  1304  			listEntry := new(machine.ListResponse)
  1305  
  1306  			listEntry.Name = vm.Name
  1307  			listEntry.Stream = vm.ImageStream
  1308  			listEntry.VMType = "wsl"
  1309  			listEntry.CPUs, _ = getCPUs(vm)
  1310  			listEntry.Memory, _ = getMem(vm)
  1311  			listEntry.DiskSize = getDiskSize(vm)
  1312  			listEntry.RemoteUsername = vm.RemoteUsername
  1313  			listEntry.Port = vm.Port
  1314  			listEntry.IdentityPath = vm.IdentityPath
  1315  
  1316  			running := vm.isRunning()
  1317  			listEntry.CreatedAt, listEntry.LastUp, _ = vm.updateTimeStamps(running)
  1318  			listEntry.Running = running
  1319  
  1320  			listed = append(listed, listEntry)
  1321  		}
  1322  		return nil
  1323  	}); err != nil {
  1324  		return nil, err
  1325  	}
  1326  	return listed, err
  1327  }
  1328  
  1329  func (vm *MachineVM) updateTimeStamps(updateLast bool) (time.Time, time.Time, error) {
  1330  	var err error
  1331  	if updateLast {
  1332  		vm.LastUp = time.Now()
  1333  		err = vm.writeConfig()
  1334  	}
  1335  
  1336  	return vm.Created, vm.LastUp, err
  1337  }
  1338  
  1339  func getDiskSize(vm *MachineVM) uint64 {
  1340  	vmDataDir, err := machine.GetDataDir(vmtype)
  1341  	if err != nil {
  1342  		return 0
  1343  	}
  1344  	distDir := filepath.Join(vmDataDir, "wsldist")
  1345  	disk := filepath.Join(distDir, vm.Name, "ext4.vhdx")
  1346  	info, err := os.Stat(disk)
  1347  	if err != nil {
  1348  		return 0
  1349  	}
  1350  	return uint64(info.Size())
  1351  }
  1352  
  1353  func getCPUs(vm *MachineVM) (uint64, error) {
  1354  	dist := toDist(vm.Name)
  1355  	if run, _ := isWSLRunning(dist); !run {
  1356  		return 0, nil
  1357  	}
  1358  	cmd := exec.Command("wsl", "-d", dist, "nproc")
  1359  	out, err := cmd.StdoutPipe()
  1360  	if err != nil {
  1361  		return 0, err
  1362  	}
  1363  	if err = cmd.Start(); err != nil {
  1364  		return 0, err
  1365  	}
  1366  	scanner := bufio.NewScanner(out)
  1367  	var result string
  1368  	for scanner.Scan() {
  1369  		result = scanner.Text()
  1370  	}
  1371  	_ = cmd.Wait()
  1372  
  1373  	ret, err := strconv.Atoi(result)
  1374  	return uint64(ret), err
  1375  }
  1376  
  1377  func getMem(vm *MachineVM) (uint64, error) {
  1378  	dist := toDist(vm.Name)
  1379  	if run, _ := isWSLRunning(dist); !run {
  1380  		return 0, nil
  1381  	}
  1382  	cmd := exec.Command("wsl", "-d", dist, "cat", "/proc/meminfo")
  1383  	out, err := cmd.StdoutPipe()
  1384  	if err != nil {
  1385  		return 0, err
  1386  	}
  1387  	if err = cmd.Start(); err != nil {
  1388  		return 0, err
  1389  	}
  1390  	scanner := bufio.NewScanner(out)
  1391  	var (
  1392  		total, available uint64
  1393  		t, a             int
  1394  	)
  1395  	for scanner.Scan() {
  1396  		fields := strings.Fields(scanner.Text())
  1397  		if strings.HasPrefix(fields[0], "MemTotal") && len(fields) >= 2 {
  1398  			t, err = strconv.Atoi(fields[1])
  1399  			total = uint64(t) * 1024
  1400  		} else if strings.HasPrefix(fields[0], "MemAvailable") && len(fields) >= 2 {
  1401  			a, err = strconv.Atoi(fields[1])
  1402  			available = uint64(a) * 1024
  1403  		}
  1404  		if err != nil {
  1405  			break
  1406  		}
  1407  	}
  1408  	_ = cmd.Wait()
  1409  
  1410  	return total - available, err
  1411  }
  1412  
  1413  func (p *Provider) IsValidVMName(name string) (bool, error) {
  1414  	infos, err := GetVMInfos()
  1415  	if err != nil {
  1416  		return false, err
  1417  	}
  1418  	for _, vm := range infos {
  1419  		if vm.Name == name {
  1420  			return true, nil
  1421  		}
  1422  	}
  1423  	return false, nil
  1424  }
  1425  
  1426  func (p *Provider) CheckExclusiveActiveVM() (bool, string, error) {
  1427  	return false, "", nil
  1428  }
  1429  
  1430  func (v *MachineVM) setRootful(rootful bool) error {
  1431  	changeCon, err := machine.AnyConnectionDefault(v.Name, v.Name+"-root")
  1432  	if err != nil {
  1433  		return err
  1434  	}
  1435  
  1436  	if changeCon {
  1437  		newDefault := v.Name
  1438  		if rootful {
  1439  			newDefault += "-root"
  1440  		}
  1441  		err := machine.ChangeDefault(newDefault)
  1442  		if err != nil {
  1443  			return err
  1444  		}
  1445  	}
  1446  	return nil
  1447  }
  1448  
  1449  // Inspect returns verbose detail about the machine
  1450  func (v *MachineVM) Inspect() (*machine.InspectInfo, error) {
  1451  	state, err := v.State(false)
  1452  	if err != nil {
  1453  		return nil, err
  1454  	}
  1455  
  1456  	created, lastUp, _ := v.updateTimeStamps(state == machine.Running)
  1457  
  1458  	return &machine.InspectInfo{
  1459  		ConfigPath: machine.VMFile{Path: v.ConfigPath},
  1460  		Created:    created,
  1461  		Image: machine.ImageConfig{
  1462  			ImagePath:   machine.VMFile{Path: v.ImagePath},
  1463  			ImageStream: v.ImageStream,
  1464  		},
  1465  		LastUp:    lastUp,
  1466  		Name:      v.Name,
  1467  		Resources: v.getResources(),
  1468  		SSHConfig: v.SSHConfig,
  1469  		State:     state,
  1470  	}, nil
  1471  }
  1472  
  1473  func (v *MachineVM) getResources() (resources machine.ResourceConfig) {
  1474  	resources.CPUs, _ = getCPUs(v)
  1475  	resources.Memory, _ = getMem(v)
  1476  	resources.DiskSize = getDiskSize(v)
  1477  	return
  1478  }
  1479  
  1480  // RemoveAndCleanMachines removes all machine and cleans up any other files associated with podman machine
  1481  func (p *Provider) RemoveAndCleanMachines() error {
  1482  	var (
  1483  		vm             machine.VM
  1484  		listResponse   []*machine.ListResponse
  1485  		opts           machine.ListOptions
  1486  		destroyOptions machine.RemoveOptions
  1487  	)
  1488  	destroyOptions.Force = true
  1489  	var prevErr error
  1490  
  1491  	listResponse, err := p.List(opts)
  1492  	if err != nil {
  1493  		return err
  1494  	}
  1495  
  1496  	for _, mach := range listResponse {
  1497  		vm, err = p.LoadVMByName(mach.Name)
  1498  		if err != nil {
  1499  			if prevErr != nil {
  1500  				logrus.Error(prevErr)
  1501  			}
  1502  			prevErr = err
  1503  		}
  1504  		_, remove, err := vm.Remove(mach.Name, destroyOptions)
  1505  		if err != nil {
  1506  			if prevErr != nil {
  1507  				logrus.Error(prevErr)
  1508  			}
  1509  			prevErr = err
  1510  		} else {
  1511  			if err := remove(); err != nil {
  1512  				if prevErr != nil {
  1513  					logrus.Error(prevErr)
  1514  				}
  1515  				prevErr = err
  1516  			}
  1517  		}
  1518  	}
  1519  
  1520  	// Clean leftover files in data dir
  1521  	dataDir, err := machine.DataDirPrefix()
  1522  	if err != nil {
  1523  		if prevErr != nil {
  1524  			logrus.Error(prevErr)
  1525  		}
  1526  		prevErr = err
  1527  	} else {
  1528  		err := os.RemoveAll(dataDir)
  1529  		if err != nil {
  1530  			if prevErr != nil {
  1531  				logrus.Error(prevErr)
  1532  			}
  1533  			prevErr = err
  1534  		}
  1535  	}
  1536  
  1537  	// Clean leftover files in conf dir
  1538  	confDir, err := machine.ConfDirPrefix()
  1539  	if err != nil {
  1540  		if prevErr != nil {
  1541  			logrus.Error(prevErr)
  1542  		}
  1543  		prevErr = err
  1544  	} else {
  1545  		err := os.RemoveAll(confDir)
  1546  		if err != nil {
  1547  			if prevErr != nil {
  1548  				logrus.Error(prevErr)
  1549  			}
  1550  			prevErr = err
  1551  		}
  1552  	}
  1553  	return prevErr
  1554  }