Skip to content

Commit 34931c8

Browse files
committed
move wsl' util_windows to windows package so it can be reused by hyperv
Signed-off-by: lstocchi <lstocchi@redhat.com>
1 parent f9c3f89 commit 34931c8

3 files changed

Lines changed: 151 additions & 141 deletions

File tree

pkg/machine/windows/util_windows.go

Lines changed: 143 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,13 @@
11
package windows
22

33
import (
4+
"errors"
5+
"fmt"
6+
"os"
7+
"strings"
8+
"syscall"
9+
"unsafe"
10+
411
"github.com/sirupsen/logrus"
512
"golang.org/x/sys/windows"
613
)
@@ -38,3 +45,139 @@ func HasAdminRights() bool {
3845

3946
return member || token.IsElevated()
4047
}
48+
49+
type SHELLEXECUTEINFO struct {
50+
cbSize uint32
51+
fMask uint32
52+
hwnd syscall.Handle
53+
lpVerb uintptr
54+
lpFile uintptr
55+
lpParameters uintptr
56+
lpDirectory uintptr
57+
nShow int
58+
hInstApp syscall.Handle
59+
lpIDList uintptr
60+
lpClass uintptr
61+
hkeyClass syscall.Handle
62+
dwHotKey uint32
63+
hIconOrMonitor syscall.Handle
64+
hProcess syscall.Handle
65+
}
66+
67+
// Cleaner to refer to the official OS constant names, and consistent with syscall
68+
// Ref: https://learn.microsoft.com/en-us/windows/win32/api/shellapi/ns-shellapi-shellexecuteinfow#members
69+
const (
70+
SEE_MASK_NOCLOSEPROCESS = 0x40
71+
SE_ERR_ACCESSDENIED = 0x05
72+
)
73+
74+
type ExitCodeError struct {
75+
Code uint
76+
}
77+
78+
func (e *ExitCodeError) Error() string {
79+
return fmt.Sprintf("process exited with code %d", e.Code)
80+
}
81+
82+
// BuildCommandArgs builds command line arguments for re-execution, optionally adding --reexec flag
83+
// for specified commands (init, rm)
84+
func BuildCommandArgs(elevate bool) string {
85+
var args []string
86+
for _, arg := range os.Args[1:] {
87+
if arg != "--reexec" {
88+
args = append(args, syscall.EscapeArg(arg))
89+
if elevate && (arg == "init" || arg == "rm") {
90+
args = append(args, "--reexec")
91+
}
92+
}
93+
}
94+
return strings.Join(args, " ")
95+
}
96+
97+
// RelaunchElevatedWait launches the current executable with elevated privileges and waits for it to complete
98+
func RelaunchElevatedWait() error {
99+
e, _ := os.Executable()
100+
d, _ := os.Getwd()
101+
exe, _ := syscall.UTF16PtrFromString(e)
102+
cwd, _ := syscall.UTF16PtrFromString(d)
103+
arg, _ := syscall.UTF16PtrFromString(BuildCommandArgs(true))
104+
verb, _ := syscall.UTF16PtrFromString("runas")
105+
106+
shell32 := syscall.NewLazyDLL("shell32.dll")
107+
108+
info := &SHELLEXECUTEINFO{
109+
fMask: SEE_MASK_NOCLOSEPROCESS,
110+
hwnd: 0,
111+
lpVerb: uintptr(unsafe.Pointer(verb)),
112+
lpFile: uintptr(unsafe.Pointer(exe)),
113+
lpParameters: uintptr(unsafe.Pointer(arg)),
114+
lpDirectory: uintptr(unsafe.Pointer(cwd)),
115+
nShow: syscall.SW_SHOWNORMAL,
116+
}
117+
info.cbSize = uint32(unsafe.Sizeof(*info))
118+
procShellExecuteEx := shell32.NewProc("ShellExecuteExW")
119+
if ret, _, _ := procShellExecuteEx.Call(uintptr(unsafe.Pointer(info))); ret == 0 { // 0 = False
120+
err := syscall.GetLastError()
121+
if info.hInstApp == SE_ERR_ACCESSDENIED {
122+
return wrapMaybe(err, "request to elevate privileges was denied")
123+
}
124+
return wrapMaybef(err, "could not launch process, ShellEX Error = %d", info.hInstApp)
125+
}
126+
127+
handle := info.hProcess
128+
defer func() {
129+
_ = syscall.CloseHandle(handle)
130+
}()
131+
132+
w, err := syscall.WaitForSingleObject(handle, syscall.INFINITE)
133+
switch w {
134+
case syscall.WAIT_OBJECT_0:
135+
break
136+
case syscall.WAIT_FAILED:
137+
return fmt.Errorf("could not wait for process, failed: %w", err)
138+
default:
139+
return fmt.Errorf("could not wait for process, unknown error. event: %X, err: %v", w, err)
140+
}
141+
var code uint32
142+
if err := syscall.GetExitCodeProcess(handle, &code); err != nil {
143+
return err
144+
}
145+
if code != 0 {
146+
return &ExitCodeError{uint(code)}
147+
}
148+
149+
return nil
150+
}
151+
152+
// MessageBox shows a Windows message box and returns the user's choice
153+
func MessageBox(caption, title string, fail bool) int {
154+
var format uint32
155+
if fail {
156+
format = windows.MB_ICONERROR
157+
} else {
158+
format = windows.MB_OKCANCEL | windows.MB_ICONINFORMATION
159+
}
160+
161+
captionPtr, _ := syscall.UTF16PtrFromString(caption)
162+
titlePtr, _ := syscall.UTF16PtrFromString(title)
163+
164+
ret, _ := windows.MessageBox(0, captionPtr, titlePtr, format)
165+
166+
return int(ret)
167+
}
168+
169+
func wrapMaybe(err error, message string) error {
170+
if err != nil {
171+
return fmt.Errorf("%v: %w", message, err)
172+
}
173+
174+
return errors.New(message)
175+
}
176+
177+
func wrapMaybef(err error, format string, args ...any) error {
178+
if err != nil {
179+
return fmt.Errorf(format+": %w", append(args, err)...)
180+
}
181+
182+
return fmt.Errorf(format, args...)
183+
}

pkg/machine/wsl/machine.go

Lines changed: 5 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import (
1818
"github.com/containers/podman/v6/pkg/machine/env"
1919
"github.com/containers/podman/v6/pkg/machine/ignition"
2020
"github.com/containers/podman/v6/pkg/machine/vmconfigs"
21+
winutil "github.com/containers/podman/v6/pkg/machine/windows"
2122
"github.com/containers/podman/v6/pkg/machine/wsl/wutil"
2223
"github.com/containers/podman/v6/utils"
2324
"github.com/sirupsen/logrus"
@@ -32,14 +33,6 @@ var (
3233
ErrWslNotSupported = errors.New("wsl features not supported or configured correctly")
3334
)
3435

35-
type ExitCodeError struct {
36-
code uint
37-
}
38-
39-
func (e *ExitCodeError) Error() string {
40-
return fmt.Sprintf("Process failed with exit code: %d", e.code)
41-
}
42-
4336
//nolint:unused
4437
func getConfigPath(name string) (string, error) {
4538
return getConfigPathExt(name, "json")
@@ -331,7 +324,7 @@ func attemptFeatureInstall(reExec, admin bool) error {
331324
message += "NOTE: A system reboot will be required as part of this process. " +
332325
"If you prefer, you may abort now, and perform a manual installation using the \"wsl --install\" command."
333326

334-
if !reExec && MessageBox(message, "Podman Machine", false) != 1 {
327+
if !reExec && winutil.MessageBox(message, "Podman Machine", false) != 1 {
335328
return fmt.Errorf("the WSL installation aborted: %w", define.ErrInitRelaunchAttempt)
336329
}
337330

@@ -345,10 +338,10 @@ func launchElevate(operation string) error {
345338
if err := createOrTruncateElevatedOutputFile(); err != nil {
346339
return err
347340
}
348-
err := relaunchElevatedWait()
341+
err := winutil.RelaunchElevatedWait()
349342
if err != nil {
350-
if eerr, ok := err.(*ExitCodeError); ok {
351-
if eerr.code == ErrorSuccessRebootRequired {
343+
if eerr, ok := err.(*winutil.ExitCodeError); ok {
344+
if eerr.Code == ErrorSuccessRebootRequired {
352345
fmt.Println("Reboot is required to continue installation, please reboot at your convenience")
353346
return define.ErrInitRelaunchAttempt
354347
}

pkg/machine/wsl/util_windows.go

Lines changed: 3 additions & 129 deletions
Original file line numberDiff line numberDiff line change
@@ -6,48 +6,21 @@ import (
66
"bytes"
77
"encoding/base64"
88
"encoding/binary"
9-
"errors"
109
"fmt"
1110
"os"
1211
"path/filepath"
13-
"strings"
1412
"syscall"
1513
"unicode/utf16"
16-
"unsafe"
1714

1815
"github.com/Microsoft/go-winio"
1916
"github.com/containers/podman/v6/pkg/machine/define"
17+
winutil "github.com/containers/podman/v6/pkg/machine/windows"
2018
"go.podman.io/storage/pkg/fileutils"
2119
"go.podman.io/storage/pkg/homedir"
2220
"golang.org/x/sys/windows"
2321
"golang.org/x/sys/windows/registry"
2422
)
2523

26-
type SHELLEXECUTEINFO struct {
27-
cbSize uint32
28-
fMask uint32
29-
hwnd syscall.Handle
30-
lpVerb uintptr
31-
lpFile uintptr
32-
lpParameters uintptr
33-
lpDirectory uintptr
34-
nShow int
35-
hInstApp syscall.Handle
36-
lpIDList uintptr
37-
lpClass uintptr
38-
hkeyClass syscall.Handle
39-
dwHotKey uint32
40-
hIconOrMonitor syscall.Handle
41-
hProcess syscall.Handle
42-
}
43-
44-
// Cleaner to refer to the official OS constant names, and consistent with syscall
45-
// Ref: https://learn.microsoft.com/en-us/windows/win32/api/shellapi/ns-shellapi-shellexecuteinfow#members
46-
const (
47-
SEE_MASK_NOCLOSEPROCESS = 0x40
48-
SE_ERR_ACCESSDENIED = 0x05
49-
)
50-
5124
const (
5225
// ref: https://learn.microsoft.com/en-us/windows/win32/secauthz/privilege-constants#constants
5326
rebootPrivilege = "SeShutdownPrivilege"
@@ -78,76 +51,6 @@ func winVersionAtLeast(major uint, minor uint, build uint) bool {
7851
return true
7952
}
8053

81-
func relaunchElevatedWait() error {
82-
e, _ := os.Executable()
83-
d, _ := os.Getwd()
84-
exe, _ := syscall.UTF16PtrFromString(e)
85-
cwd, _ := syscall.UTF16PtrFromString(d)
86-
arg, _ := syscall.UTF16PtrFromString(buildCommandArgs(true))
87-
verb, _ := syscall.UTF16PtrFromString("runas")
88-
89-
shell32 := syscall.NewLazyDLL("shell32.dll")
90-
91-
info := &SHELLEXECUTEINFO{
92-
fMask: SEE_MASK_NOCLOSEPROCESS,
93-
hwnd: 0,
94-
lpVerb: uintptr(unsafe.Pointer(verb)),
95-
lpFile: uintptr(unsafe.Pointer(exe)),
96-
lpParameters: uintptr(unsafe.Pointer(arg)),
97-
lpDirectory: uintptr(unsafe.Pointer(cwd)),
98-
nShow: syscall.SW_SHOWNORMAL,
99-
}
100-
info.cbSize = uint32(unsafe.Sizeof(*info))
101-
procShellExecuteEx := shell32.NewProc("ShellExecuteExW")
102-
if ret, _, _ := procShellExecuteEx.Call(uintptr(unsafe.Pointer(info))); ret == 0 { // 0 = False
103-
err := syscall.GetLastError()
104-
if info.hInstApp == SE_ERR_ACCESSDENIED {
105-
return wrapMaybe(err, "request to elevate privileges was denied")
106-
}
107-
return wrapMaybef(err, "could not launch process, ShellEX Error = %d", info.hInstApp)
108-
}
109-
110-
handle := info.hProcess
111-
defer func() {
112-
_ = syscall.CloseHandle(handle)
113-
}()
114-
115-
w, err := syscall.WaitForSingleObject(handle, syscall.INFINITE)
116-
switch w {
117-
case syscall.WAIT_OBJECT_0:
118-
break
119-
case syscall.WAIT_FAILED:
120-
return fmt.Errorf("could not wait for process, failed: %w", err)
121-
default:
122-
return fmt.Errorf("could not wait for process, unknown error. event: %X, err: %v", w, err)
123-
}
124-
var code uint32
125-
if err := syscall.GetExitCodeProcess(handle, &code); err != nil {
126-
return err
127-
}
128-
if code != 0 {
129-
return &ExitCodeError{uint(code)}
130-
}
131-
132-
return nil
133-
}
134-
135-
func wrapMaybe(err error, message string) error {
136-
if err != nil {
137-
return fmt.Errorf("%v: %w", message, err)
138-
}
139-
140-
return errors.New(message)
141-
}
142-
143-
func wrapMaybef(err error, format string, args ...any) error {
144-
if err != nil {
145-
return fmt.Errorf(format+": %w", append(args, err)...)
146-
}
147-
148-
return fmt.Errorf(format, args...)
149-
}
150-
15154
func reboot() error {
15255
const (
15356
wtLocation = `Microsoft\WindowsApps\wt.exe`
@@ -157,7 +60,7 @@ func reboot() error {
15760
)
15861

15962
exe, _ := os.Executable()
160-
relaunch := fmt.Sprintf("& %s %s", syscall.EscapeArg(exe), buildCommandArgs(false))
63+
relaunch := fmt.Sprintf("& %s %s", syscall.EscapeArg(exe), winutil.BuildCommandArgs(false))
16164
encoded := base64.StdEncoding.EncodeToString(encodeUTF16Bytes(relaunch))
16265

16366
dataDir, err := homedir.GetDataHome()
@@ -190,7 +93,7 @@ func reboot() error {
19093
"Alternatively, you can cancel and reboot manually\n\n" +
19194
"After rebooting, please wait a minute or two for podman machine to relaunch and continue installing."
19295

193-
if MessageBox(message, "Podman Machine", false) != 1 {
96+
if winutil.MessageBox(message, "Podman Machine", false) != 1 {
19497
fmt.Println("Reboot is required to continue installation, please reboot at your convenience")
19598
os.Exit(ErrorSuccessRebootRequired)
19699
return nil
@@ -231,32 +134,3 @@ func encodeUTF16Bytes(s string) []byte {
231134
}
232135
return buf.Bytes()
233136
}
234-
235-
func MessageBox(caption, title string, fail bool) int {
236-
var format uint32
237-
if fail {
238-
format = windows.MB_ICONERROR
239-
} else {
240-
format = windows.MB_OKCANCEL | windows.MB_ICONINFORMATION
241-
}
242-
243-
captionPtr, _ := syscall.UTF16PtrFromString(caption)
244-
titlePtr, _ := syscall.UTF16PtrFromString(title)
245-
246-
ret, _ := windows.MessageBox(0, captionPtr, titlePtr, format)
247-
248-
return int(ret)
249-
}
250-
251-
func buildCommandArgs(elevate bool) string {
252-
var args []string
253-
for _, arg := range os.Args[1:] {
254-
if arg != "--reexec" {
255-
args = append(args, syscall.EscapeArg(arg))
256-
if elevate && arg == "init" {
257-
args = append(args, "--reexec")
258-
}
259-
}
260-
}
261-
return strings.Join(args, " ")
262-
}

0 commit comments

Comments
 (0)