mirror of
https://github.com/containers/podman.git
synced 2026-07-11 15:55:22 -04:00
move wsl' util_windows to windows package so it can be reused by hyperv
Signed-off-by: lstocchi <lstocchi@redhat.com>
This commit is contained in:
@@ -1,7 +1,17 @@
|
||||
package windows
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"syscall"
|
||||
"unsafe"
|
||||
|
||||
"github.com/sirupsen/logrus"
|
||||
"go.podman.io/storage/pkg/homedir"
|
||||
"golang.org/x/sys/windows"
|
||||
)
|
||||
|
||||
@@ -38,3 +48,204 @@ func HasAdminRights() bool {
|
||||
|
||||
return member || token.IsElevated()
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
// Cleaner to refer to the official OS constant names, and consistent with syscall
|
||||
// Ref: https://learn.microsoft.com/en-us/windows/win32/api/shellapi/ns-shellapi-shellexecuteinfow#members
|
||||
const (
|
||||
SEE_MASK_NOCLOSEPROCESS = 0x40
|
||||
SE_ERR_ACCESSDENIED = 0x05
|
||||
)
|
||||
|
||||
type ExitCodeError struct {
|
||||
Code uint
|
||||
}
|
||||
|
||||
func (e *ExitCodeError) Error() string {
|
||||
return fmt.Sprintf("process exited with code %d", e.Code)
|
||||
}
|
||||
|
||||
// BuildCommandArgs builds command line arguments for re-execution, optionally adding --reexec flag
|
||||
// for specified commands (init, rm)
|
||||
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" || arg == "rm") {
|
||||
args = append(args, "--reexec")
|
||||
}
|
||||
}
|
||||
}
|
||||
return strings.Join(args, " ")
|
||||
}
|
||||
|
||||
// RelaunchElevatedWait launches the current executable with elevated privileges and waits for it to complete
|
||||
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: syscall.SW_SHOWNORMAL,
|
||||
}
|
||||
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 := info.hProcess
|
||||
defer func() {
|
||||
_ = syscall.CloseHandle(handle)
|
||||
}()
|
||||
|
||||
w, err := syscall.WaitForSingleObject(handle, syscall.INFINITE)
|
||||
switch w {
|
||||
case syscall.WAIT_OBJECT_0:
|
||||
break
|
||||
case syscall.WAIT_FAILED:
|
||||
return fmt.Errorf("could not wait for process, failed: %w", err)
|
||||
default:
|
||||
return fmt.Errorf("could not wait for process, unknown error. event: %X, err: %v", w, err)
|
||||
}
|
||||
var code uint32
|
||||
if err := syscall.GetExitCodeProcess(handle, &code); err != nil {
|
||||
return err
|
||||
}
|
||||
if code != 0 {
|
||||
return &ExitCodeError{uint(code)}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// MessageBox shows a Windows message box and returns the user's choice
|
||||
func MessageBox(caption, title string, fail bool) int {
|
||||
var format uint32
|
||||
if fail {
|
||||
format = windows.MB_ICONERROR
|
||||
} else {
|
||||
format = windows.MB_OKCANCEL | windows.MB_ICONINFORMATION
|
||||
}
|
||||
|
||||
captionPtr, _ := syscall.UTF16PtrFromString(caption)
|
||||
titlePtr, _ := syscall.UTF16PtrFromString(title)
|
||||
|
||||
ret, _ := windows.MessageBox(0, captionPtr, titlePtr, format)
|
||||
|
||||
return int(ret)
|
||||
}
|
||||
|
||||
func wrapMaybe(err error, message string) error {
|
||||
if err != nil {
|
||||
return fmt.Errorf("%v: %w", message, err)
|
||||
}
|
||||
|
||||
return errors.New(message)
|
||||
}
|
||||
|
||||
func wrapMaybef(err error, format string, args ...any) error {
|
||||
if err != nil {
|
||||
return fmt.Errorf(format+": %w", append(args, err)...)
|
||||
}
|
||||
|
||||
return fmt.Errorf(format, args...)
|
||||
}
|
||||
|
||||
// IsReExecuting checks if the current process was re-executed with --reexec flag
|
||||
func IsReExecuting() bool {
|
||||
for _, arg := range os.Args {
|
||||
if arg == "--reexec" {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func CreateOrTruncateElevatedOutputFile() error {
|
||||
name, err := getElevatedOutputFileName()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = os.Create(name)
|
||||
return err
|
||||
}
|
||||
|
||||
// getElevatedOutputFileName returns the path to the elevated output log file
|
||||
func getElevatedOutputFileName() (string, error) {
|
||||
dir, err := homedir.GetDataHome()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return filepath.Join(dir, "podman-elevated-output.log"), nil
|
||||
}
|
||||
|
||||
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 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, 0o755); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return os.OpenFile(name, mode, 0o644)
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
@@ -18,12 +18,12 @@ import (
|
||||
"github.com/containers/podman/v6/pkg/machine/env"
|
||||
"github.com/containers/podman/v6/pkg/machine/ignition"
|
||||
"github.com/containers/podman/v6/pkg/machine/vmconfigs"
|
||||
winutil "github.com/containers/podman/v6/pkg/machine/windows"
|
||||
"github.com/containers/podman/v6/pkg/machine/wsl/wutil"
|
||||
"github.com/containers/podman/v6/utils"
|
||||
"github.com/sirupsen/logrus"
|
||||
"go.podman.io/common/pkg/config"
|
||||
"go.podman.io/common/pkg/strongunits"
|
||||
"go.podman.io/storage/pkg/homedir"
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -32,14 +32,6 @@ var (
|
||||
ErrWslNotSupported = errors.New("wsl features not supported or configured correctly")
|
||||
)
|
||||
|
||||
type ExitCodeError struct {
|
||||
code uint
|
||||
}
|
||||
|
||||
func (e *ExitCodeError) Error() string {
|
||||
return fmt.Sprintf("Process failed with exit code: %d", e.code)
|
||||
}
|
||||
|
||||
//nolint:unused
|
||||
func getConfigPath(name string) (string, error) {
|
||||
return getConfigPathExt(name, "json")
|
||||
@@ -331,7 +323,7 @@ func attemptFeatureInstall(reExec, admin bool) error {
|
||||
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 !reExec && MessageBox(message, "Podman Machine", false) != 1 {
|
||||
if !reExec && winutil.MessageBox(message, "Podman Machine", false) != 1 {
|
||||
return fmt.Errorf("the WSL installation aborted: %w", define.ErrInitRelaunchAttempt)
|
||||
}
|
||||
|
||||
@@ -342,20 +334,20 @@ func attemptFeatureInstall(reExec, admin bool) error {
|
||||
}
|
||||
|
||||
func launchElevate(operation string) error {
|
||||
if err := createOrTruncateElevatedOutputFile(); err != nil {
|
||||
if err := winutil.CreateOrTruncateElevatedOutputFile(); err != nil {
|
||||
return err
|
||||
}
|
||||
err := relaunchElevatedWait()
|
||||
err := winutil.RelaunchElevatedWait()
|
||||
if err != nil {
|
||||
if eerr, ok := err.(*ExitCodeError); ok {
|
||||
if eerr.code == ErrorSuccessRebootRequired {
|
||||
if eerr, ok := err.(*winutil.ExitCodeError); ok {
|
||||
if eerr.Code == ErrorSuccessRebootRequired {
|
||||
fmt.Println("Reboot is required to continue installation, please reboot at your convenience")
|
||||
return define.ErrInitRelaunchAttempt
|
||||
}
|
||||
}
|
||||
|
||||
fmt.Fprintf(os.Stderr, "Elevated process failed with error: %v\n\n", err)
|
||||
dumpOutputFile()
|
||||
winutil.DumpOutputFile()
|
||||
fmt.Fprintf(os.Stderr, wslInstallError, operation)
|
||||
return fmt.Errorf("%w: %w", err, define.ErrInitRelaunchAttempt)
|
||||
}
|
||||
@@ -363,7 +355,7 @@ func launchElevate(operation string) error {
|
||||
}
|
||||
|
||||
func installWsl() error {
|
||||
log, err := getElevatedOutputFileWrite()
|
||||
log, err := winutil.GetElevatedOutputFileWrite()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -383,60 +375,6 @@ func installWsl() error {
|
||||
return reboot()
|
||||
}
|
||||
|
||||
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 createOrTruncateElevatedOutputFile() error {
|
||||
name, err := getElevatedOutputFileName()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = os.Create(name)
|
||||
return err
|
||||
}
|
||||
|
||||
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, 0o755); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return os.OpenFile(name, mode, 0o644)
|
||||
}
|
||||
|
||||
func isMsiError(err error) bool {
|
||||
if err == nil {
|
||||
return false
|
||||
|
||||
@@ -6,48 +6,21 @@ import (
|
||||
"bytes"
|
||||
"encoding/base64"
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"syscall"
|
||||
"unicode/utf16"
|
||||
"unsafe"
|
||||
|
||||
"github.com/Microsoft/go-winio"
|
||||
"github.com/containers/podman/v6/pkg/machine/define"
|
||||
winutil "github.com/containers/podman/v6/pkg/machine/windows"
|
||||
"go.podman.io/storage/pkg/fileutils"
|
||||
"go.podman.io/storage/pkg/homedir"
|
||||
"golang.org/x/sys/windows"
|
||||
"golang.org/x/sys/windows/registry"
|
||||
)
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
// Cleaner to refer to the official OS constant names, and consistent with syscall
|
||||
// Ref: https://learn.microsoft.com/en-us/windows/win32/api/shellapi/ns-shellapi-shellexecuteinfow#members
|
||||
const (
|
||||
SEE_MASK_NOCLOSEPROCESS = 0x40
|
||||
SE_ERR_ACCESSDENIED = 0x05
|
||||
)
|
||||
|
||||
const (
|
||||
// ref: https://learn.microsoft.com/en-us/windows/win32/secauthz/privilege-constants#constants
|
||||
rebootPrivilege = "SeShutdownPrivilege"
|
||||
@@ -78,76 +51,6 @@ func winVersionAtLeast(major uint, minor uint, build uint) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
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: syscall.SW_SHOWNORMAL,
|
||||
}
|
||||
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 := info.hProcess
|
||||
defer func() {
|
||||
_ = syscall.CloseHandle(handle)
|
||||
}()
|
||||
|
||||
w, err := syscall.WaitForSingleObject(handle, syscall.INFINITE)
|
||||
switch w {
|
||||
case syscall.WAIT_OBJECT_0:
|
||||
break
|
||||
case syscall.WAIT_FAILED:
|
||||
return fmt.Errorf("could not wait for process, failed: %w", err)
|
||||
default:
|
||||
return fmt.Errorf("could not wait for process, unknown error. event: %X, err: %v", w, err)
|
||||
}
|
||||
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 fmt.Errorf("%v: %w", message, err)
|
||||
}
|
||||
|
||||
return errors.New(message)
|
||||
}
|
||||
|
||||
func wrapMaybef(err error, format string, args ...any) error {
|
||||
if err != nil {
|
||||
return fmt.Errorf(format+": %w", append(args, err)...)
|
||||
}
|
||||
|
||||
return fmt.Errorf(format, args...)
|
||||
}
|
||||
|
||||
func reboot() error {
|
||||
const (
|
||||
wtLocation = `Microsoft\WindowsApps\wt.exe`
|
||||
@@ -157,7 +60,7 @@ func reboot() error {
|
||||
)
|
||||
|
||||
exe, _ := os.Executable()
|
||||
relaunch := fmt.Sprintf("& %s %s", syscall.EscapeArg(exe), buildCommandArgs(false))
|
||||
relaunch := fmt.Sprintf("& %s %s", syscall.EscapeArg(exe), winutil.BuildCommandArgs(false))
|
||||
encoded := base64.StdEncoding.EncodeToString(encodeUTF16Bytes(relaunch))
|
||||
|
||||
dataDir, err := homedir.GetDataHome()
|
||||
@@ -190,7 +93,7 @@ func reboot() error {
|
||||
"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 {
|
||||
if winutil.MessageBox(message, "Podman Machine", false) != 1 {
|
||||
fmt.Println("Reboot is required to continue installation, please reboot at your convenience")
|
||||
os.Exit(ErrorSuccessRebootRequired)
|
||||
return nil
|
||||
@@ -231,32 +134,3 @@ func encodeUTF16Bytes(s string) []byte {
|
||||
}
|
||||
return buf.Bytes()
|
||||
}
|
||||
|
||||
func MessageBox(caption, title string, fail bool) int {
|
||||
var format uint32
|
||||
if fail {
|
||||
format = windows.MB_ICONERROR
|
||||
} else {
|
||||
format = windows.MB_OKCANCEL | windows.MB_ICONINFORMATION
|
||||
}
|
||||
|
||||
captionPtr, _ := syscall.UTF16PtrFromString(caption)
|
||||
titlePtr, _ := syscall.UTF16PtrFromString(title)
|
||||
|
||||
ret, _ := windows.MessageBox(0, captionPtr, titlePtr, 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, " ")
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user