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