summaryrefslogtreecommitdiff
path: root/pkg/machine/wsl
diff options
context:
space:
mode:
authorJason T. Greene <jason@stacksmash.com>2021-11-12 00:10:58 -0600
committerJason T. Greene <jason.greene@redhat.com>2021-12-24 19:28:10 -0600
commit803defbe509af1902a1fdc2ed7f41b49ebd241f6 (patch)
tree54fe3a08b58b9129f87e51cd1b8fcd938f582777 /pkg/machine/wsl
parent73a54ea54d0a1b4ccaa2a0e23c678e5b7c1d5c37 (diff)
downloadpodman-803defbe509af1902a1fdc2ed7f41b49ebd241f6.tar.gz
podman-803defbe509af1902a1fdc2ed7f41b49ebd241f6.tar.bz2
podman-803defbe509af1902a1fdc2ed7f41b49ebd241f6.zip
Introduce Windows WSL implementation of podman machine
[NO NEW TESTS NEEDED] for now Signed-off-by: Jason Greene <jason.greene@redhat.com>
Diffstat (limited to 'pkg/machine/wsl')
-rw-r--r--pkg/machine/wsl/machine.go1119
-rw-r--r--pkg/machine/wsl/machine_unsupported.go3
-rw-r--r--pkg/machine/wsl/util_windows.go338
3 files changed, 1460 insertions, 0 deletions
diff --git a/pkg/machine/wsl/machine.go b/pkg/machine/wsl/machine.go
new file mode 100644
index 000000000..b4ee79acc
--- /dev/null
+++ b/pkg/machine/wsl/machine.go
@@ -0,0 +1,1119 @@
+//go:build windows
+// +build windows
+
+package wsl
+
+import (
+ "bufio"
+ "encoding/json"
+ "fmt"
+ "io"
+ "io/ioutil"
+ "os"
+ "os/exec"
+ "path/filepath"
+ "strconv"
+ "strings"
+ "time"
+
+ "github.com/containers/podman/v3/pkg/machine"
+ "github.com/containers/podman/v3/utils"
+ "github.com/containers/storage/pkg/homedir"
+ "github.com/pkg/errors"
+ "github.com/sirupsen/logrus"
+ "golang.org/x/text/encoding/unicode"
+ "golang.org/x/text/transform"
+)
+
+var (
+ wslProvider = &Provider{}
+ // vmtype refers to qemu (vs libvirt, krun, etc)
+ vmtype = "wsl"
+)
+
+const (
+ ErrorSuccessRebootInitiated = 1641
+ ErrorSuccessRebootRequired = 3010
+)
+
+// Usermode networking avoids potential nftables compatibility issues between the distro
+// and the WSL Kernel. Additionally it avoids fw rule conflicts between distros, since
+// all instances run under the same Kernel at runtime
+const containersConf = `[containers]
+
+[engine]
+cgroup_manager = "cgroupfs"
+events_logger = "file"
+`
+
+const appendPort = `grep -q Port\ %d /etc/ssh/sshd_config || echo Port %d >> /etc/ssh/sshd_config`
+
+const configServices = `ln -fs /usr/lib/systemd/system/sshd.service /etc/systemd/system/multi-user.target.wants/sshd.service
+ln -fs /usr/lib/systemd/system/podman.socket /etc/systemd/system/sockets.target.wants/podman.socket
+rm -f /etc/systemd/system/getty.target.wants/console-getty.service
+rm -f /etc/systemd/system/getty.target.wants/getty@tty1.service
+rm -f /etc/systemd/system/multi-user.target.wants/systemd-resolved.service
+rm -f /etc/systemd/system/dbus-org.freedesktop.resolve1.service
+ln -fs /dev/null /etc/systemd/system/console-getty.service
+mkdir -p /etc/systemd/system/systemd-sysusers.service.d/
+adduser -m [USER] -G wheel
+mkdir -p /home/[USER]/.config/systemd/[USER]/
+chown [USER]:[USER] /home/[USER]/.config
+`
+
+const sudoers = `%wheel ALL=(ALL) NOPASSWD: ALL
+`
+
+const bootstrap = `#!/bin/bash
+ps -ef | grep -v grep | grep -q systemd && exit 0
+nohup unshare --kill-child --fork --pid --mount --mount-proc --propagation shared /lib/systemd/systemd >/dev/null 2>&1 &
+sleep 0.1
+`
+
+const wslmotd = `
+You will be automatically entered into a nested process namespace where
+systemd is running. If you need to access the parent namespace, hit ctrl-d
+or type exit. This also means to log out you need to exit twice.
+
+`
+
+const sysdpid = "SYSDPID=`ps -eo cmd,pid | grep -m 1 ^/lib/systemd/systemd | awk '{print $2}'`"
+
+const profile = sysdpid + `
+if [ ! -z "$SYSDPID" ] && [ "$SYSDPID" != "1" ]; then
+ cat /etc/wslmotd
+ /usr/local/bin/enterns
+fi
+`
+
+const enterns = "#!/bin/bash\n" + sysdpid + `
+if [ ! -z "$SYSDPID" ] && [ "$SYSDPID" != "1" ]; then
+ nsenter -m -p -t $SYSDPID "$@"
+fi
+`
+
+const waitTerm = sysdpid + `
+if [ ! -z "$SYSDPID" ]; then
+ timeout 60 tail -f /dev/null --pid $SYSDPID
+fi
+`
+
+// WSL kernel does not have sg and crypto_user modules
+const overrideSysusers = `[Service]
+LoadCredential=
+`
+
+const lingerService = `[Unit]
+Description=A systemd user unit demo
+After=network-online.target
+Wants=network-online.target podman.socket
+[Service]
+ExecStart=/usr/bin/sleep infinity
+`
+
+const lingerSetup = `mkdir -p /home/[USER]/.config/systemd/[USER]/default.target.wants
+ln -fs /home/[USER]/.config/systemd/[USER]/linger-example.service \
+ /home/[USER]/.config/systemd/[USER]/default.target.wants/linger-example.service
+`
+
+const wslInstallError = `Could not %s. See previous output for any potential failure details.
+If you can not resolve the issue, and rerunning fails, try the "wsl --install" process
+outlined in the following article:
+
+http://docs.microsoft.com/en-us/windows/wsl/install
+
+`
+
+const wslKernelError = `Could not %s. See previous output for any potential failure details.
+If you can not resolve the issue, try rerunning the "podman machine init command". If that fails
+try the "wsl --update" command and then rerun "podman machine init". Finally, if all else fails,
+try following the steps outlined in the following article:
+
+http://docs.microsoft.com/en-us/windows/wsl/install
+
+`
+
+const wslInstallKernel = "install the WSL Kernel"
+
+const wslOldVersion = `Automatic installation of WSL can not be performed on this version of Windows
+Either update to Build 19041 (or later), or perform the manual installation steps
+outlined in the following article:
+
+http://docs.microsoft.com/en-us/windows/wsl/install\
+
+`
+
+type Provider struct{}
+
+type MachineVM struct {
+ // IdentityPath is the fq path to the ssh priv key
+ IdentityPath string
+ // IgnitionFilePath is the fq path to the .ign file
+ ImageStream string
+ // ImagePath is the fq path to
+ ImagePath string
+ // Name of the vm
+ Name string
+ // SSH port for user networking
+ Port int
+ // RemoteUsername of the vm user
+ RemoteUsername string
+}
+
+type ExitCodeError struct {
+ code uint
+}
+
+func (e *ExitCodeError) Error() string {
+ return fmt.Sprintf("Process failed with exit code: %d", e.code)
+}
+
+func GetWSLProvider() machine.Provider {
+ return wslProvider
+}
+
+// NewMachine initializes an instance of a virtual machine based on the qemu
+// virtualization.
+func (p *Provider) NewMachine(opts machine.InitOptions) (machine.VM, error) {
+ vm := new(MachineVM)
+ if len(opts.Name) > 0 {
+ vm.Name = opts.Name
+ }
+
+ vm.ImagePath = opts.ImagePath
+ vm.RemoteUsername = opts.Username
+
+ // Add a random port for ssh
+ port, err := utils.GetRandomPort()
+ if err != nil {
+ return nil, err
+ }
+ vm.Port = port
+
+ return vm, nil
+}
+
+// LoadByName reads a json file that describes a known qemu vm
+// and returns a vm instance
+func (p *Provider) LoadVMByName(name string) (machine.VM, error) {
+ vm := new(MachineVM)
+ vmConfigDir, err := machine.GetConfDir(vmtype)
+ if err != nil {
+ return nil, err
+ }
+ b, err := ioutil.ReadFile(filepath.Join(vmConfigDir, name+".json"))
+ if os.IsNotExist(err) {
+ return nil, errors.Wrap(machine.ErrNoSuchVM, name)
+ }
+ if err != nil {
+ return nil, err
+ }
+ err = json.Unmarshal(b, vm)
+ return vm, err
+}
+
+// Init writes the json configuration file to the filesystem for
+// other verbs (start, stop)
+func (v *MachineVM) Init(opts machine.InitOptions) (bool, error) {
+ if cont, err := checkAndInstallWSL(opts); !cont {
+ appendOutputIfError(opts.ReExec, err)
+ return cont, err
+ }
+
+ homeDir := homedir.Get()
+ sshDir := filepath.Join(homeDir, ".ssh")
+ v.IdentityPath = filepath.Join(sshDir, v.Name)
+
+ if err := downloadDistro(v, opts); err != nil {
+ return false, err
+ }
+
+ if err := writeJSON(v); err != nil {
+ return false, err
+ }
+
+ if err := setupConnections(v, opts, sshDir); err != nil {
+ return false, err
+ }
+
+ dist, err := provisionWSLDist(v)
+ if err != nil {
+ return false, err
+ }
+
+ fmt.Println("Configuring system...")
+ if err = configureSystem(v, dist); err != nil {
+ return false, err
+ }
+
+ if err = installScripts(dist); err != nil {
+ return false, err
+ }
+
+ if err = createKeys(v, dist, sshDir); err != nil {
+ return false, err
+ }
+
+ return true, nil
+}
+
+func downloadDistro(v *MachineVM, opts machine.InitOptions) error {
+ var (
+ dd machine.DistributionDownload
+ err error
+ )
+
+ if _, e := strconv.Atoi(opts.ImagePath); e == nil {
+ v.ImageStream = opts.ImagePath
+ dd, err = machine.NewFedoraDownloader(vmtype, v.Name, v.ImageStream)
+ } else {
+ v.ImageStream = "custom"
+ dd, err = machine.NewGenericDownloader(vmtype, v.Name, opts.ImagePath)
+ }
+ if err != nil {
+ return err
+ }
+
+ v.ImagePath = dd.Get().LocalUncompressedFile
+ return machine.DownloadImage(dd)
+}
+
+func writeJSON(v *MachineVM) error {
+ vmConfigDir, err := machine.GetConfDir(vmtype)
+ if err != nil {
+ return err
+ }
+
+ jsonFile := filepath.Join(vmConfigDir, v.Name) + ".json"
+
+ b, err := json.MarshalIndent(v, "", " ")
+ if err != nil {
+ return err
+ }
+ if err := ioutil.WriteFile(jsonFile, b, 0644); err != nil {
+ return errors.Wrap(err, "could not write machine json config")
+ }
+
+ return nil
+}
+
+func setupConnections(v *MachineVM, opts machine.InitOptions, sshDir string) error {
+ uriRoot := machine.SSHRemoteConnection.MakeSSHURL("localhost", "/run/podman/podman.sock", strconv.Itoa(v.Port), "root")
+ if err := machine.AddConnection(&uriRoot, v.Name+"-root", filepath.Join(sshDir, v.Name), opts.IsDefault); err != nil {
+ return err
+ }
+
+ user := opts.Username
+ uri := machine.SSHRemoteConnection.MakeSSHURL("localhost", withUser("/run/[USER]/1000/podman/podman.sock", user), strconv.Itoa(v.Port), v.RemoteUsername)
+ return machine.AddConnection(&uri, v.Name, filepath.Join(sshDir, v.Name), opts.IsDefault)
+}
+
+func provisionWSLDist(v *MachineVM) (string, error) {
+ vmDataDir, err := machine.GetDataDir(vmtype)
+ if err != nil {
+ return "", err
+ }
+
+ distDir := filepath.Join(vmDataDir, "wsldist")
+ distTarget := filepath.Join(distDir, v.Name)
+ if err := os.MkdirAll(distDir, 0755); err != nil {
+ return "", errors.Wrap(err, "could not create wsldist directory")
+ }
+
+ dist := toDist(v.Name)
+ fmt.Println("Importing operating system into WSL (this may take 5+ minutes on a new WSL install)...")
+ if err = runCmdPassThrough("wsl", "--import", dist, distTarget, v.ImagePath); err != nil {
+ return "", errors.Wrap(err, "WSL import of guest OS failed")
+ }
+
+ fmt.Println("Installing packages (this will take awhile)...")
+ if err = runCmdPassThrough("wsl", "-d", dist, "dnf", "upgrade", "-y"); err != nil {
+ return "", errors.Wrap(err, "package upgrade on guest OS failed")
+ }
+
+ if err = runCmdPassThrough("wsl", "-d", dist, "dnf", "install",
+ "podman", "podman-docker", "openssh-server", "procps-ng", "-y"); err != nil {
+ return "", errors.Wrap(err, "package installation on guest OS failed")
+ }
+
+ // Fixes newuidmap
+ if err = runCmdPassThrough("wsl", "-d", dist, "dnf", "reinstall", "shadow-utils", "-y"); err != nil {
+ return "", errors.Wrap(err, "package reinstallation of shadow-utils on guest OS failed")
+ }
+
+ // Windows 11 (NT Version = 10, Build 22000) generates harmless but scary messages on every
+ // operation when mount was not present on the initial start. Force a cycle so that it won't
+ // repeatedly complain.
+ if winVersionAtLeast(10, 0, 22000) {
+ if err := runCmdPassThrough("wsl", "--terminate", dist); err != nil {
+ logrus.Warnf("could not cycle WSL dist: %s", err.Error())
+ }
+ }
+
+ return dist, nil
+}
+
+func createKeys(v *MachineVM, dist string, sshDir string) error {
+ user := v.RemoteUsername
+
+ if err := os.MkdirAll(sshDir, 0700); err != nil {
+ return errors.Wrap(err, "could not create ssh directory")
+ }
+
+ if err := runCmdPassThrough("wsl", "--terminate", dist); err != nil {
+ return errors.Wrap(err, "could not cycle WSL dist")
+ }
+
+ key, err := machine.CreateSSHKeysPrefix(sshDir, v.Name, true, true, "wsl", "-d", dist)
+ if err != nil {
+ return errors.Wrap(err, "could not create ssh keys")
+ }
+
+ if err := pipeCmdPassThrough("wsl", key+"\n", "-d", dist, "sh", "-c", "mkdir -p /root/.ssh;"+
+ "cat >> /root/.ssh/authorized_keys; chmod 600 /root/.ssh/authorized_keys"); err != nil {
+ return errors.Wrap(err, "could not create root authorized keys on guest OS")
+ }
+
+ userAuthCmd := withUser("mkdir -p /home/[USER]/.ssh;"+
+ "cat >> /home/[USER]/.ssh/authorized_keys; chown -R [USER]:[USER] /home/[USER]/.ssh;"+
+ "chmod 600 /home/[USER]/.ssh/authorized_keys", user)
+ if err := pipeCmdPassThrough("wsl", key+"\n", "-d", dist, "sh", "-c", userAuthCmd); err != nil {
+ return errors.Wrapf(err, "could not create '%s' authorized keys on guest OS", v.RemoteUsername)
+ }
+
+ return nil
+}
+
+func configureSystem(v *MachineVM, dist string) error {
+ user := v.RemoteUsername
+ if err := runCmdPassThrough("wsl", "-d", dist, "sh", "-c", fmt.Sprintf(appendPort, v.Port, v.Port)); err != nil {
+ return errors.Wrap(err, "could not configure SSH port for guest OS")
+ }
+
+ if err := pipeCmdPassThrough("wsl", withUser(configServices, user), "-d", dist, "sh"); err != nil {
+ return errors.Wrap(err, "could not configure systemd settings for guest OS")
+ }
+
+ if err := pipeCmdPassThrough("wsl", sudoers, "-d", dist, "sh", "-c", "cat >> /etc/sudoers"); err != nil {
+ return errors.Wrap(err, "could not add wheel to sudoers")
+ }
+
+ if err := pipeCmdPassThrough("wsl", overrideSysusers, "-d", dist, "sh", "-c",
+ "cat > /etc/systemd/system/systemd-sysusers.service.d/override.conf"); err != nil {
+ return errors.Wrap(err, "could not generate systemd-sysusers override for guest OS")
+ }
+
+ lingerCmd := withUser("cat > /home/[USER]/.config/systemd/[USER]/linger-example.service", user)
+ if err := pipeCmdPassThrough("wsl", lingerService, "-d", dist, "sh", "-c", lingerCmd); err != nil {
+ return errors.Wrap(err, "could not generate linger service for guest OS")
+ }
+
+ if err := pipeCmdPassThrough("wsl", withUser(lingerSetup, user), "-d", dist, "sh"); err != nil {
+ return errors.Wrap(err, "could not configure systemd settomgs for guest OS")
+ }
+
+ if err := pipeCmdPassThrough("wsl", containersConf, "-d", dist, "sh", "-c", "cat > /etc/containers/containers.conf"); err != nil {
+ return errors.Wrap(err, "could not create containers.conf for guest OS")
+ }
+
+ return nil
+}
+
+func installScripts(dist string) error {
+ if err := pipeCmdPassThrough("wsl", enterns, "-d", dist, "sh", "-c",
+ "cat > /usr/local/bin/enterns; chmod 755 /usr/local/bin/enterns"); err != nil {
+ return errors.Wrap(err, "could not create enterns script for guest OS")
+ }
+
+ if err := pipeCmdPassThrough("wsl", profile, "-d", dist, "sh", "-c",
+ "cat > /etc/profile.d/enterns.sh"); err != nil {
+ return errors.Wrap(err, "could not create motd profile script for guest OS")
+ }
+
+ if err := pipeCmdPassThrough("wsl", wslmotd, "-d", dist, "sh", "-c", "cat > /etc/wslmotd"); err != nil {
+ return errors.Wrap(err, "could not create a WSL MOTD for guest OS")
+ }
+
+ if err := pipeCmdPassThrough("wsl", bootstrap, "-d", dist, "sh", "-c",
+ "cat > /root/bootstrap; chmod 755 /root/bootstrap"); err != nil {
+ return errors.Wrap(err, "could not create bootstrap script for guest OS")
+ }
+
+ return nil
+}
+
+func checkAndInstallWSL(opts machine.InitOptions) (bool, error) {
+ if isWSLInstalled() {
+ return true, nil
+ }
+
+ admin := hasAdminRights()
+
+ if !isWSLFeatureEnabled() {
+ return false, attemptFeatureInstall(opts, admin)
+ }
+
+ skip := false
+ if !opts.ReExec && !admin {
+ fmt.Println("Launching WSL Kernel Install...")
+ if err := launchElevate(wslInstallKernel); err != nil {
+ return false, err
+ }
+
+ skip = true
+ }
+
+ if !skip {
+ if err := installWslKernel(); err != nil {
+ fmt.Fprintf(os.Stderr, wslKernelError, wslInstallKernel)
+ return false, err
+ }
+
+ if opts.ReExec {
+ return false, nil
+ }
+ }
+
+ return true, nil
+}
+
+func attemptFeatureInstall(opts machine.InitOptions, admin bool) error {
+ if !winVersionAtLeast(10, 0, 18362) {
+ return errors.Errorf("Your version of Windows does not support WSL. Update to Windows 10 Build 19041 or later")
+ } else if !winVersionAtLeast(10, 0, 19041) {
+ fmt.Fprint(os.Stderr, wslOldVersion)
+ return errors.Errorf("WSL can not be automatically installed")
+ }
+
+ message := "WSL is not installed on this system, installing it.\n\n"
+
+ if !admin {
+ message += "Since you are not running as admin, a new window will open and " +
+ "require you to approve administrator privileges.\n\n"
+ }
+
+ message += "NOTE: A system reboot will be required as part of this process. " +
+ "If you prefer, you may abort now, and perform a manual installation using the \"wsl --install\" command."
+
+ if !opts.ReExec && MessageBox(message, "Podman Machine", false) != 1 {
+ return errors.Errorf("WSL installation aborted")
+ }
+
+ if !opts.ReExec && !admin {
+ return launchElevate("install the Windows WSL Features")
+ }
+
+ return installWsl()
+}
+
+func launchElevate(operation string) error {
+ truncateElevatedOutputFile()
+ err := relaunchElevatedWait()
+ if err != nil {
+ if eerr, ok := err.(*ExitCodeError); ok {
+ if eerr.code == ErrorSuccessRebootRequired {
+ fmt.Println("Reboot is required to continue installation, please reboot at your convenience")
+ return nil
+ }
+ }
+
+ fmt.Fprintf(os.Stderr, "Elevated process failed with error: %v\n\n", err)
+ dumpOutputFile()
+ fmt.Fprintf(os.Stderr, wslInstallError, operation)
+ }
+ return err
+}
+
+func installWsl() error {
+ log, err := getElevatedOutputFileWrite()
+ if err != nil {
+ return err
+ }
+ defer log.Close()
+ if err := runCmdPassThroughTee(log, "dism", "/online", "/enable-feature",
+ "/featurename:Microsoft-Windows-Subsystem-Linux", "/all", "/norestart"); isMsiError(err) {
+ return errors.Wrap(err, "could not enable WSL Feature")
+ }
+
+ if err = runCmdPassThroughTee(log, "dism", "/online", "/enable-feature",
+ "/featurename:VirtualMachinePlatform", "/all", "/norestart"); isMsiError(err) {
+ return errors.Wrap(err, "could not enable Virtual Machine Feature")
+ }
+ log.Close()
+
+ return reboot()
+}
+
+func installWslKernel() error {
+ log, err := getElevatedOutputFileWrite()
+ if err != nil {
+ return err
+ }
+ defer log.Close()
+
+ message := "Installing WSL Kernel Update"
+ fmt.Println(message)
+ fmt.Fprintln(log, message)
+
+ backoff := 500 * time.Millisecond
+ for i := 0; i < 5; i++ {
+ err = runCmdPassThroughTee(log, "wsl", "--update")
+ if err == nil {
+ break
+ }
+ // In case of unusual circumstances (e.g. race with installer actions)
+ // retry a few times
+ message = "An error occured attempting the WSL Kernel update, retrying..."
+ fmt.Println(message)
+ fmt.Fprintln(log, message)
+ time.Sleep(backoff)
+ backoff *= 2
+ }
+
+ if err != nil {
+ return errors.Wrap(err, "could not install WSL Kernel")
+ }
+
+ return nil
+}
+
+func getElevatedOutputFileName() (string, error) {
+ dir, err := homedir.GetDataHome()
+ if err != nil {
+ return "", err
+ }
+ return filepath.Join(dir, "podman-elevated-output.log"), nil
+}
+
+func dumpOutputFile() {
+ file, err := getElevatedOutputFileRead()
+ if err != nil {
+ logrus.Debug("could not find elevated child output file")
+ return
+ }
+ defer file.Close()
+ _, _ = io.Copy(os.Stdout, file)
+}
+
+func getElevatedOutputFileRead() (*os.File, error) {
+ return getElevatedOutputFile(os.O_RDONLY)
+}
+
+func getElevatedOutputFileWrite() (*os.File, error) {
+ return getElevatedOutputFile(os.O_WRONLY | os.O_CREATE | os.O_APPEND)
+}
+
+func appendOutputIfError(write bool, err error) {
+ if write && err == nil {
+ return
+ }
+
+ if file, check := getElevatedOutputFileWrite(); check == nil {
+ defer file.Close()
+ fmt.Fprintf(file, "Error: %v\n", err)
+ }
+}
+
+func truncateElevatedOutputFile() error {
+ name, err := getElevatedOutputFileName()
+ if err != nil {
+ return err
+ }
+
+ return os.Truncate(name, 0)
+}
+
+func getElevatedOutputFile(mode int) (*os.File, error) {
+ name, err := getElevatedOutputFileName()
+ if err != nil {
+ return nil, err
+ }
+
+ dir, err := homedir.GetDataHome()
+ if err != nil {
+ return nil, err
+ }
+
+ if err = os.MkdirAll(dir, 0755); err != nil {
+ return nil, err
+ }
+
+ return os.OpenFile(name, mode, 0644)
+}
+
+func isMsiError(err error) bool {
+ if err == nil {
+ return false
+ }
+
+ if eerr, ok := err.(*exec.ExitError); ok {
+ switch eerr.ExitCode() {
+ case 0:
+ fallthrough
+ case ErrorSuccessRebootInitiated:
+ fallthrough
+ case ErrorSuccessRebootRequired:
+ return false
+ }
+ }
+
+ return true
+}
+func toDist(name string) string {
+ if !strings.HasPrefix(name, "podman") {
+ name = "podman-" + name
+ }
+ return name
+}
+
+func withUser(s string, user string) string {
+ return strings.ReplaceAll(s, "[USER]", user)
+}
+
+func runCmdPassThrough(name string, arg ...string) error {
+ logrus.Debugf("Running command: %s %v", name, arg)
+ cmd := exec.Command(name, arg...)
+ cmd.Stdin = os.Stdin
+ cmd.Stdout = os.Stdout
+ cmd.Stderr = os.Stderr
+ return cmd.Run()
+}
+
+func runCmdPassThroughTee(out io.Writer, name string, arg ...string) error {
+ logrus.Debugf("Running command: %s %v", name, arg)
+
+ // TODO - Perhaps improve this with a conpty pseudo console so that
+ // dism installer text bars mirror console behavior (redraw)
+ cmd := exec.Command(name, arg...)
+ cmd.Stdin = os.Stdin
+ cmd.Stdout = io.MultiWriter(os.Stdout, out)
+ cmd.Stderr = io.MultiWriter(os.Stderr, out)
+ return cmd.Run()
+}
+
+func pipeCmdPassThrough(name string, input string, arg ...string) error {
+ logrus.Debugf("Running command: %s %v", name, arg)
+ cmd := exec.Command(name, arg...)
+ cmd.Stdin = strings.NewReader(input)
+ cmd.Stdout = os.Stdout
+ cmd.Stderr = os.Stderr
+ return cmd.Run()
+}
+
+func (v *MachineVM) Start(name string, _ machine.StartOptions) error {
+ if v.isRunning() {
+ return errors.Errorf("%q is already running", name)
+ }
+
+ fmt.Println("Starting machine...")
+
+ dist := toDist(name)
+
+ err := runCmdPassThrough("wsl", "-d", dist, "/root/bootstrap")
+ if err != nil {
+ return errors.Wrap(err, "WSL bootstrap script failed")
+ }
+
+ return markStart(name)
+}
+
+func isWSLInstalled() bool {
+ cmd := exec.Command("wsl", "--status")
+ out, err := cmd.StdoutPipe()
+ if err != nil {
+ return false
+ }
+ if err = cmd.Start(); err != nil {
+ return false
+ }
+ scanner := bufio.NewScanner(transform.NewReader(out, unicode.UTF16(unicode.LittleEndian, unicode.UseBOM).NewDecoder()))
+ result := true
+ for scanner.Scan() {
+ line := scanner.Text()
+ // Windows 11 does not set an error exit code when a kernel is not avail
+ if strings.Contains(line, "kernel file is not found") {
+ result = false
+ break
+ }
+ }
+ if err := cmd.Wait(); !result || err != nil {
+ return false
+ }
+
+ return true
+}
+
+func isWSLFeatureEnabled() bool {
+ cmd := exec.Command("wsl", "--set-default-version", "2")
+ return cmd.Run() == nil
+}
+
+func isWSLRunning(dist string) (bool, error) {
+ cmd := exec.Command("wsl", "-l", "--running")
+ out, err := cmd.StdoutPipe()
+ if err != nil {
+ return false, err
+ }
+ if err = cmd.Start(); err != nil {
+ return false, err
+ }
+ scanner := bufio.NewScanner(transform.NewReader(out, unicode.UTF16(unicode.LittleEndian, unicode.UseBOM).NewDecoder()))
+ result := false
+ for scanner.Scan() {
+ fields := strings.Fields(scanner.Text())
+ if len(fields) > 0 && dist == fields[0] {
+ result = true
+ break
+ }
+ }
+
+ _ = cmd.Wait()
+
+ return result, nil
+}
+
+func isSystemdRunning(dist string) (bool, error) {
+ cmd := exec.Command("wsl", "-d", dist, "sh")
+ cmd.Stdin = strings.NewReader(sysdpid + "\necho $SYSDPID\n")
+ out, err := cmd.StdoutPipe()
+ if err != nil {
+ return false, err
+ }
+ if err = cmd.Start(); err != nil {
+ return false, err
+ }
+ scanner := bufio.NewScanner(out)
+ result := false
+ if scanner.Scan() {
+ text := scanner.Text()
+ i, err := strconv.Atoi(text)
+ if err == nil && i > 0 {
+ result = true
+ }
+ }
+
+ _ = cmd.Wait()
+
+ return result, nil
+}
+
+func (v *MachineVM) Stop(name string, _ machine.StopOptions) error {
+ dist := toDist(v.Name)
+
+ wsl, err := isWSLRunning(dist)
+ if err != nil {
+ return err
+ }
+
+ sysd := false
+ if wsl {
+ sysd, err = isSystemdRunning(dist)
+ if err != nil {
+ return err
+ }
+ }
+
+ if !wsl || !sysd {
+ return errors.Errorf("%q is not running", v.Name)
+ }
+
+ cmd := exec.Command("wsl", "-d", dist, "sh")
+ cmd.Stdin = strings.NewReader(waitTerm)
+ if err = cmd.Start(); err != nil {
+ return errors.Wrap(err, "Error executing wait command")
+ }
+
+ exitCmd := exec.Command("wsl", "-d", dist, "/usr/local/bin/enterns", "systemctl", "exit", "0")
+ if err = exitCmd.Run(); err != nil {
+ return errors.Wrap(err, "Error stopping sysd")
+ }
+
+ if err = cmd.Wait(); err != nil {
+ return err
+ }
+
+ cmd = exec.Command("wsl", "--terminate", dist)
+ if err = cmd.Run(); err != nil {
+ return err
+ }
+
+ return nil
+}
+
+//nolint:cyclop
+func (v *MachineVM) Remove(name string, opts machine.RemoveOptions) (string, func() error, error) {
+ var files []string
+
+ if v.isRunning() {
+ return "", nil, errors.Errorf("running vm %q cannot be destroyed", v.Name)
+ }
+
+ // Collect all the files that need to be destroyed
+ if !opts.SaveKeys {
+ files = append(files, v.IdentityPath, v.IdentityPath+".pub")
+ }
+ if !opts.SaveImage {
+ files = append(files, v.ImagePath)
+ }
+
+ vmConfigDir, err := machine.GetConfDir(vmtype)
+ if err != nil {
+ return "", nil, err
+ }
+ files = append(files, filepath.Join(vmConfigDir, v.Name+".json"))
+
+ vmDataDir, err := machine.GetDataDir(vmtype)
+ if err != nil {
+ return "", nil, err
+ }
+ files = append(files, filepath.Join(vmDataDir, "wsldist", v.Name))
+
+ confirmationMessage := "\nThe following files will be deleted:\n\n"
+ for _, msg := range files {
+ confirmationMessage += msg + "\n"
+ }
+
+ confirmationMessage += "\n"
+ return confirmationMessage, func() error {
+ if err := machine.RemoveConnection(v.Name); err != nil {
+ logrus.Error(err)
+ }
+ if err := machine.RemoveConnection(v.Name + "-root"); err != nil {
+ logrus.Error(err)
+ }
+ if err := runCmdPassThrough("wsl", "--unregister", toDist(v.Name)); err != nil {
+ logrus.Error(err)
+ }
+ for _, f := range files {
+ if err := os.RemoveAll(f); err != nil {
+ logrus.Error(err)
+ }
+ }
+ return nil
+ }, nil
+}
+
+func (v *MachineVM) isRunning() bool {
+ dist := toDist(v.Name)
+
+ wsl, err := isWSLRunning(dist)
+ if err != nil {
+ return false
+ }
+
+ sysd := false
+ if wsl {
+ sysd, err = isSystemdRunning(dist)
+
+ if err != nil {
+ return false
+ }
+ }
+
+ return sysd
+}
+
+// SSH opens an interactive SSH session to the vm specified.
+// Added ssh function to VM interface: pkg/machine/config/go : line 58
+func (v *MachineVM) SSH(name string, opts machine.SSHOptions) error {
+ if !v.isRunning() {
+ return errors.Errorf("vm %q is not running.", v.Name)
+ }
+
+ username := opts.Username
+ if username == "" {
+ username = v.RemoteUsername
+ }
+
+ sshDestination := username + "@localhost"
+ port := strconv.Itoa(v.Port)
+
+ args := []string{"-i", v.IdentityPath, "-p", port, sshDestination, "-o", "UserKnownHostsFile /dev/null", "-o", "StrictHostKeyChecking no"}
+ if len(opts.Args) > 0 {
+ args = append(args, opts.Args...)
+ } else {
+ fmt.Printf("Connecting to vm %s. To close connection, use `~.` or `exit`\n", v.Name)
+ }
+
+ cmd := exec.Command("ssh", args...)
+ logrus.Debugf("Executing: ssh %v\n", args)
+
+ cmd.Stdout = os.Stdout
+ cmd.Stderr = os.Stderr
+ cmd.Stdin = os.Stdin
+
+ return cmd.Run()
+}
+
+// List lists all vm's that use qemu virtualization
+func (p *Provider) List(_ machine.ListOptions) ([]*machine.ListResponse, error) {
+ return GetVMInfos()
+}
+
+func GetVMInfos() ([]*machine.ListResponse, error) {
+ vmConfigDir, err := machine.GetConfDir(vmtype)
+ if err != nil {
+ return nil, err
+ }
+
+ var listed []*machine.ListResponse
+
+ if err = filepath.Walk(vmConfigDir, func(path string, info os.FileInfo, err error) error {
+ vm := new(MachineVM)
+ if strings.HasSuffix(info.Name(), ".json") {
+ fullPath := filepath.Join(vmConfigDir, info.Name())
+ b, err := ioutil.ReadFile(fullPath)
+ if err != nil {
+ return err
+ }
+ err = json.Unmarshal(b, vm)
+ if err != nil {
+ return err
+ }
+ listEntry := new(machine.ListResponse)
+
+ listEntry.Name = vm.Name
+ listEntry.Stream = vm.ImageStream
+ listEntry.VMType = "wsl"
+ listEntry.CPUs, _ = getCPUs(vm)
+ listEntry.Memory, _ = getMem(vm)
+ listEntry.DiskSize = getDiskSize(vm)
+ fi, err := os.Stat(fullPath)
+ if err != nil {
+ return err
+ }
+ listEntry.CreatedAt = fi.ModTime()
+ listEntry.LastUp = getLastStart(vm, fi.ModTime())
+ if vm.isRunning() {
+ listEntry.Running = true
+ }
+
+ listed = append(listed, listEntry)
+ }
+ return nil
+ }); err != nil {
+ return nil, err
+ }
+ return listed, err
+}
+
+func getDiskSize(vm *MachineVM) uint64 {
+ vmDataDir, err := machine.GetDataDir(vmtype)
+ if err != nil {
+ return 0
+ }
+ distDir := filepath.Join(vmDataDir, "wsldist")
+ disk := filepath.Join(distDir, vm.Name, "ext4.vhdx")
+ info, err := os.Stat(disk)
+ if err != nil {
+ return 0
+ }
+ return uint64(info.Size())
+}
+
+func markStart(name string) error {
+ vmDataDir, err := machine.GetDataDir(vmtype)
+ if err != nil {
+ return err
+ }
+ distDir := filepath.Join(vmDataDir, "wsldist")
+ start := filepath.Join(distDir, name, "laststart")
+ file, err := os.Create(start)
+ if err != nil {
+ return err
+ }
+ file.Close()
+
+ return nil
+}
+
+func getLastStart(vm *MachineVM, created time.Time) time.Time {
+ vmDataDir, err := machine.GetDataDir(vmtype)
+ if err != nil {
+ return created
+ }
+ distDir := filepath.Join(vmDataDir, "wsldist")
+ start := filepath.Join(distDir, vm.Name, "laststart")
+ info, err := os.Stat(start)
+ if err != nil {
+ return created
+ }
+ return info.ModTime()
+}
+
+func getCPUs(vm *MachineVM) (uint64, error) {
+ dist := toDist(vm.Name)
+ if run, _ := isWSLRunning(dist); !run {
+ return 0, nil
+ }
+ cmd := exec.Command("wsl", "-d", dist, "nproc")
+ out, err := cmd.StdoutPipe()
+ if err != nil {
+ return 0, err
+ }
+ if err = cmd.Start(); err != nil {
+ return 0, err
+ }
+ scanner := bufio.NewScanner(out)
+ var result string
+ for scanner.Scan() {
+ result = scanner.Text()
+ }
+ _ = cmd.Wait()
+
+ ret, err := strconv.Atoi(result)
+ return uint64(ret), err
+}
+
+func getMem(vm *MachineVM) (uint64, error) {
+ dist := toDist(vm.Name)
+ if run, _ := isWSLRunning(dist); !run {
+ return 0, nil
+ }
+ cmd := exec.Command("wsl", "-d", dist, "cat", "/proc/meminfo")
+ out, err := cmd.StdoutPipe()
+ if err != nil {
+ return 0, err
+ }
+ if err = cmd.Start(); err != nil {
+ return 0, err
+ }
+ scanner := bufio.NewScanner(out)
+ var (
+ total, available uint64
+ t, a int
+ )
+ for scanner.Scan() {
+ fields := strings.Fields(scanner.Text())
+ if strings.HasPrefix(fields[0], "MemTotal") && len(fields) >= 2 {
+ t, err = strconv.Atoi(fields[1])
+ total = uint64(t) * 1024
+ } else if strings.HasPrefix(fields[0], "MemAvailable") && len(fields) >= 2 {
+ a, err = strconv.Atoi(fields[1])
+ available = uint64(a) * 1024
+ }
+ if err != nil {
+ break
+ }
+ }
+ _ = cmd.Wait()
+
+ return total - available, err
+}
+
+func (p *Provider) IsValidVMName(name string) (bool, error) {
+ infos, err := GetVMInfos()
+ if err != nil {
+ return false, err
+ }
+ for _, vm := range infos {
+ if vm.Name == name {
+ return true, nil
+ }
+ }
+ return false, nil
+}
+
+func (p *Provider) CheckExclusiveActiveVM() (bool, string, error) {
+ return false, "", nil
+}
diff --git a/pkg/machine/wsl/machine_unsupported.go b/pkg/machine/wsl/machine_unsupported.go
new file mode 100644
index 000000000..043c5d729
--- /dev/null
+++ b/pkg/machine/wsl/machine_unsupported.go
@@ -0,0 +1,3 @@
+// +build !windows
+
+package wsl
diff --git a/pkg/machine/wsl/util_windows.go b/pkg/machine/wsl/util_windows.go
new file mode 100644
index 000000000..95e4c9894
--- /dev/null
+++ b/pkg/machine/wsl/util_windows.go
@@ -0,0 +1,338 @@
+package wsl
+
+import (
+ "encoding/base64"
+ "fmt"
+ "io/ioutil"
+ "os"
+ "path/filepath"
+ "strings"
+ "syscall"
+ "unicode/utf16"
+ "unsafe"
+
+ "github.com/pkg/errors"
+ "github.com/sirupsen/logrus"
+ "golang.org/x/sys/windows"
+ "golang.org/x/sys/windows/registry"
+
+ "github.com/containers/storage/pkg/homedir"
+)
+
+//nolint
+type SHELLEXECUTEINFO struct {
+ cbSize uint32
+ fMask uint32
+ hwnd syscall.Handle
+ lpVerb uintptr
+ lpFile uintptr
+ lpParameters uintptr
+ lpDirectory uintptr
+ nShow int
+ hInstApp syscall.Handle
+ lpIDList uintptr
+ lpClass uintptr
+ hkeyClass syscall.Handle
+ dwHotKey uint32
+ hIconOrMonitor syscall.Handle
+ hProcess syscall.Handle
+}
+
+//nolint
+type Luid struct {
+ lowPart uint32
+ highPart int32
+}
+
+type LuidAndAttributes struct {
+ luid Luid
+ attributes uint32
+}
+
+type TokenPrivileges struct {
+ privilegeCount uint32
+ privileges [1]LuidAndAttributes
+}
+
+//nolint // Cleaner to refer to the official OS constant names, and consistent with syscall
+const (
+ SEE_MASK_NOCLOSEPROCESS = 0x40
+ EWX_FORCEIFHUNG = 0x10
+ EWX_REBOOT = 0x02
+ EWX_RESTARTAPPS = 0x40
+ SHTDN_REASON_MAJOR_APPLICATION = 0x00040000
+ SHTDN_REASON_MINOR_INSTALLATION = 0x00000002
+ SHTDN_REASON_FLAG_PLANNED = 0x80000000
+ TOKEN_ADJUST_PRIVILEGES = 0x0020
+ TOKEN_QUERY = 0x0008
+ SE_PRIVILEGE_ENABLED = 0x00000002
+ SE_ERR_ACCESSDENIED = 0x05
+)
+
+func winVersionAtLeast(major uint, minor uint, build uint) bool {
+ var out [3]uint32
+
+ in := []uint32{uint32(major), uint32(minor), uint32(build)}
+ out[0], out[1], out[2] = windows.RtlGetNtVersionNumbers()
+
+ for i, o := range out {
+ if in[i] > o {
+ return false
+ }
+ if in[i] < o {
+ return true
+ }
+ }
+
+ return true
+}
+
+func hasAdminRights() bool {
+ var sid *windows.SID
+
+ // See: https://coolaj86.com/articles/golang-and-windows-and-admins-oh-my/
+ if err := windows.AllocateAndInitializeSid(
+ &windows.SECURITY_NT_AUTHORITY,
+ 2,
+ windows.SECURITY_BUILTIN_DOMAIN_RID,
+ windows.DOMAIN_ALIAS_RID_ADMINS,
+ 0, 0, 0, 0, 0, 0,
+ &sid); err != nil {
+ logrus.Warnf("SID allocation error: %s", err)
+ return false
+ }
+ defer windows.FreeSid(sid)
+
+ // From MS docs:
+ // "If TokenHandle is NULL, CheckTokenMembership uses the impersonation
+ // token of the calling thread. If the thread is not impersonating,
+ // the function duplicates the thread's primary token to create an
+ // impersonation token."
+ token := windows.Token(0)
+
+ member, err := token.IsMember(sid)
+ if err != nil {
+ logrus.Warnf("Token Membership Error: %s", err)
+ return false
+ }
+
+ return member || token.IsElevated()
+}
+
+func relaunchElevatedWait() error {
+ e, _ := os.Executable()
+ d, _ := os.Getwd()
+ exe, _ := syscall.UTF16PtrFromString(e)
+ cwd, _ := syscall.UTF16PtrFromString(d)
+ arg, _ := syscall.UTF16PtrFromString(buildCommandArgs(true))
+ verb, _ := syscall.UTF16PtrFromString("runas")
+
+ shell32 := syscall.NewLazyDLL("shell32.dll")
+
+ info := &SHELLEXECUTEINFO{
+ fMask: SEE_MASK_NOCLOSEPROCESS,
+ hwnd: 0,
+ lpVerb: uintptr(unsafe.Pointer(verb)),
+ lpFile: uintptr(unsafe.Pointer(exe)),
+ lpParameters: uintptr(unsafe.Pointer(arg)),
+ lpDirectory: uintptr(unsafe.Pointer(cwd)),
+ nShow: 1,
+ }
+ info.cbSize = uint32(unsafe.Sizeof(*info))
+ procShellExecuteEx := shell32.NewProc("ShellExecuteExW")
+ if ret, _, _ := procShellExecuteEx.Call(uintptr(unsafe.Pointer(info))); ret == 0 { // 0 = False
+ err := syscall.GetLastError()
+ if info.hInstApp == SE_ERR_ACCESSDENIED {
+ return wrapMaybe(err, "request to elevate privileges was denied")
+ }
+ return wrapMaybef(err, "could not launch process, ShellEX Error = %d", info.hInstApp)
+ }
+
+ handle := syscall.Handle(info.hProcess)
+ defer syscall.CloseHandle(handle)
+
+ w, err := syscall.WaitForSingleObject(handle, syscall.INFINITE)
+ switch w {
+ case syscall.WAIT_OBJECT_0:
+ break
+ case syscall.WAIT_FAILED:
+ return errors.Wrap(err, "could not wait for process, failed")
+ default:
+ return errors.Errorf("could not wait for process, unknown error")
+ }
+ var code uint32
+ if err := syscall.GetExitCodeProcess(handle, &code); err != nil {
+ return err
+ }
+ if code != 0 {
+ return &ExitCodeError{uint(code)}
+ }
+
+ return nil
+}
+
+func wrapMaybe(err error, message string) error {
+ if err != nil {
+ return errors.Wrap(err, message)
+ }
+
+ return errors.New(message)
+}
+
+func wrapMaybef(err error, format string, args ...interface{}) error {
+ if err != nil {
+ return errors.Wrapf(err, format, args...)
+ }
+
+ return errors.Errorf(format, args...)
+}
+
+func reboot() error {
+ const (
+ wtLocation = `Microsoft\WindowsApps\wt.exe`
+ wtPrefix = `%LocalAppData%\Microsoft\WindowsApps\wt -p "Windows PowerShell" `
+ localAppData = "LocalAppData"
+ pShellLaunch = `powershell -noexit "powershell -EncodedCommand (Get-Content '%s')"`
+ )
+
+ exe, _ := os.Executable()
+ relaunch := fmt.Sprintf("& %s %s", syscall.EscapeArg(exe), buildCommandArgs(false))
+ encoded := base64.StdEncoding.EncodeToString(encodeUTF16Bytes(relaunch))
+
+ dataDir, err := homedir.GetDataHome()
+ if err != nil {
+ return errors.Wrap(err, "could not determine data directory")
+ }
+ if err := os.MkdirAll(dataDir, 0755); err != nil {
+ return errors.Wrap(err, "could not create data directory")
+ }
+ commFile := filepath.Join(dataDir, "podman-relaunch.dat")
+ if err := ioutil.WriteFile(commFile, []byte(encoded), 0600); err != nil {
+ return errors.Wrap(err, "could not serialize command state")
+ }
+
+ command := fmt.Sprintf(pShellLaunch, commFile)
+ if _, err := os.Lstat(filepath.Join(os.Getenv(localAppData), wtLocation)); err == nil {
+ wtCommand := wtPrefix + command
+ // RunOnce is limited to 260 chars (supposedly no longer in Builds >= 19489)
+ // For now fallbacak in cases of long usernames (>89 chars)
+ if len(wtCommand) < 260 {
+ command = wtCommand
+ }
+ }
+
+ if err := addRunOnceRegistryEntry(command); err != nil {
+ return err
+ }
+
+ if err := obtainShutdownPrivilege(); err != nil {
+ return err
+ }
+
+ message := "To continue the process of enabling WSL, the system needs to reboot. " +
+ "Alternatively, you can cancel and reboot manually\n\n" +
+ "After rebooting, please wait a minute or two for podman machine to relaunch and continue installing."
+
+ if MessageBox(message, "Podman Machine", false) != 1 {
+ fmt.Println("Reboot is required to continue installation, please reboot at your convenience")
+ os.Exit(ErrorSuccessRebootRequired)
+ return nil
+ }
+
+ user32 := syscall.NewLazyDLL("user32")
+ procExit := user32.NewProc("ExitWindowsEx")
+ if ret, _, err := procExit.Call(EWX_REBOOT|EWX_RESTARTAPPS|EWX_FORCEIFHUNG,
+ SHTDN_REASON_MAJOR_APPLICATION|SHTDN_REASON_MINOR_INSTALLATION|SHTDN_REASON_FLAG_PLANNED); ret != 1 {
+ return errors.Wrap(err, "reboot failed")
+ }
+
+ return nil
+}
+
+func obtainShutdownPrivilege() error {
+ const SeShutdownName = "SeShutdownPrivilege"
+
+ advapi32 := syscall.NewLazyDLL("advapi32")
+ OpenProcessToken := advapi32.NewProc("OpenProcessToken")
+ LookupPrivilegeValue := advapi32.NewProc("LookupPrivilegeValueW")
+ AdjustTokenPrivileges := advapi32.NewProc("AdjustTokenPrivileges")
+
+ proc, _ := syscall.GetCurrentProcess()
+
+ var hToken uintptr
+ if ret, _, err := OpenProcessToken.Call(uintptr(proc), TOKEN_ADJUST_PRIVILEGES|TOKEN_QUERY, uintptr(unsafe.Pointer(&hToken))); ret != 1 {
+ return errors.Wrap(err, "error opening process token")
+ }
+
+ var privs TokenPrivileges
+ if ret, _, err := LookupPrivilegeValue.Call(uintptr(0), uintptr(unsafe.Pointer(syscall.StringToUTF16Ptr(SeShutdownName))), uintptr(unsafe.Pointer(&(privs.privileges[0].luid)))); ret != 1 {
+ return errors.Wrap(err, "error looking up shutdown privilege")
+ }
+
+ privs.privilegeCount = 1
+ privs.privileges[0].attributes = SE_PRIVILEGE_ENABLED
+
+ if ret, _, err := AdjustTokenPrivileges.Call(hToken, 0, uintptr(unsafe.Pointer(&privs)), 0, uintptr(0), 0); ret != 1 {
+ return errors.Wrap(err, "error enabling shutdown privilege on token")
+ }
+
+ return nil
+}
+
+func addRunOnceRegistryEntry(command string) error {
+ k, _, err := registry.CreateKey(registry.CURRENT_USER, `Software\Microsoft\Windows\CurrentVersion\RunOnce`, registry.WRITE)
+ if err != nil {
+ return errors.Wrap(err, "could not open RunOnce registry entry")
+ }
+
+ defer k.Close()
+
+ if err := k.SetExpandStringValue("podman-machine", command); err != nil {
+ return errors.Wrap(err, "could not open RunOnce registry entry")
+ }
+
+ return nil
+}
+
+func encodeUTF16Bytes(s string) []byte {
+ u16 := utf16.Encode([]rune(s))
+ u16le := make([]byte, len(u16)*2)
+ for i := 0; i < len(u16); i++ {
+ u16le[i<<1] = byte(u16[i])
+ u16le[(i<<1)+1] = byte(u16[i] >> 8)
+ }
+ return u16le
+}
+
+func MessageBox(caption, title string, fail bool) int {
+ var format int
+ if fail {
+ format = 0x10
+ } else {
+ format = 0x41
+ }
+
+ user32 := syscall.NewLazyDLL("user32.dll")
+ captionPtr, _ := syscall.UTF16PtrFromString(caption)
+ titlePtr, _ := syscall.UTF16PtrFromString(title)
+ ret, _, _ := user32.NewProc("MessageBoxW").Call(
+ uintptr(0),
+ uintptr(unsafe.Pointer(captionPtr)),
+ uintptr(unsafe.Pointer(titlePtr)),
+ uintptr(format))
+
+ return int(ret)
+}
+
+func buildCommandArgs(elevate bool) string {
+ var args []string
+ for _, arg := range os.Args[1:] {
+ if arg != "--reexec" {
+ args = append(args, syscall.EscapeArg(arg))
+ if elevate && arg == "init" {
+ args = append(args, "--reexec")
+ }
+ }
+ }
+ return strings.Join(args, " ")
+}