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