Skip to content

Commit e30d693

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 e30d693

3 files changed

Lines changed: 222 additions & 199 deletions

File tree

pkg/machine/windows/util_windows.go

Lines changed: 211 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,17 @@
11
package windows
22

33
import (
4+
"errors"
5+
"fmt"
6+
"io"
7+
"os"
8+
"path/filepath"
9+
"strings"
10+
"syscall"
11+
"unsafe"
12+
413
"github.com/sirupsen/logrus"
14+
"go.podman.io/storage/pkg/homedir"
515
"golang.org/x/sys/windows"
616
)
717

@@ -38,3 +48,204 @@ func HasAdminRights() bool {
3848

3949
return member || token.IsElevated()
4050
}
51+
52+
type SHELLEXECUTEINFO struct {
53+
cbSize uint32
54+
fMask uint32
55+
hwnd syscall.Handle
56+
lpVerb uintptr
57+
lpFile uintptr
58+
lpParameters uintptr
59+
lpDirectory uintptr
60+
nShow int
61+
hInstApp syscall.Handle
62+
lpIDList uintptr
63+
lpClass uintptr
64+
hkeyClass syscall.Handle
65+
dwHotKey uint32
66+
hIconOrMonitor syscall.Handle
67+
hProcess syscall.Handle
68+
}
69+
70+
// Cleaner to refer to the official OS constant names, and consistent with syscall
71+
// Ref: https://learn.microsoft.com/en-us/windows/win32/api/shellapi/ns-shellapi-shellexecuteinfow#members
72+
const (
73+
SEE_MASK_NOCLOSEPROCESS = 0x40
74+
SE_ERR_ACCESSDENIED = 0x05
75+
)
76+
77+
type ExitCodeError struct {
78+
Code uint
79+
}
80+
81+
func (e *ExitCodeError) Error() string {
82+
return fmt.Sprintf("process exited with code %d", e.Code)
83+
}
84+
85+
// BuildCommandArgs builds command line arguments for re-execution, optionally adding --reexec flag
86+
// for specified commands (init, rm)
87+
func BuildCommandArgs(elevate bool) string {
88+
var args []string
89+
for _, arg := range os.Args[1:] {
90+
if arg != "--reexec" {
91+
args = append(args, syscall.EscapeArg(arg))
92+
if elevate && (arg == "init" || arg == "rm") {
93+
args = append(args, "--reexec")
94+
}
95+
}
96+
}
97+
return strings.Join(args, " ")
98+
}
99+
100+
// RelaunchElevatedWait launches the current executable with elevated privileges and waits for it to complete
101+
func RelaunchElevatedWait() error {
102+
e, _ := os.Executable()
103+
d, _ := os.Getwd()
104+
exe, _ := syscall.UTF16PtrFromString(e)
105+
cwd, _ := syscall.UTF16PtrFromString(d)
106+
arg, _ := syscall.UTF16PtrFromString(BuildCommandArgs(true))
107+
verb, _ := syscall.UTF16PtrFromString("runas")
108+
109+
shell32 := syscall.NewLazyDLL("shell32.dll")
110+
111+
info := &SHELLEXECUTEINFO{
112+
fMask: SEE_MASK_NOCLOSEPROCESS,
113+
hwnd: 0,
114+
lpVerb: uintptr(unsafe.Pointer(verb)),
115+
lpFile: uintptr(unsafe.Pointer(exe)),
116+
lpParameters: uintptr(unsafe.Pointer(arg)),
117+
lpDirectory: uintptr(unsafe.Pointer(cwd)),
118+
nShow: syscall.SW_SHOWNORMAL,
119+
}
120+
info.cbSize = uint32(unsafe.Sizeof(*info))
121+
procShellExecuteEx := shell32.NewProc("ShellExecuteExW")
122+
if ret, _, _ := procShellExecuteEx.Call(uintptr(unsafe.Pointer(info))); ret == 0 { // 0 = False
123+
err := syscall.GetLastError()
124+
if info.hInstApp == SE_ERR_ACCESSDENIED {
125+
return wrapMaybe(err, "request to elevate privileges was denied")
126+
}
127+
return wrapMaybef(err, "could not launch process, ShellEX Error = %d", info.hInstApp)
128+
}
129+
130+
handle := info.hProcess
131+
defer func() {
132+
_ = syscall.CloseHandle(handle)
133+
}()
134+
135+
w, err := syscall.WaitForSingleObject(handle, syscall.INFINITE)
136+
switch w {
137+
case syscall.WAIT_OBJECT_0:
138+
break
139+
case syscall.WAIT_FAILED:
140+
return fmt.Errorf("could not wait for process, failed: %w", err)
141+
default:
142+
return fmt.Errorf("could not wait for process, unknown error. event: %X, err: %v", w, err)
143+
}
144+
var code uint32
145+
if err := syscall.GetExitCodeProcess(handle, &code); err != nil {
146+
return err
147+
}
148+
if code != 0 {
149+
return &ExitCodeError{uint(code)}
150+
}
151+
152+
return nil
153+
}
154+
155+
// MessageBox shows a Windows message box and returns the user's choice
156+
func MessageBox(caption, title string, fail bool) int {
157+
var format uint32
158+
if fail {
159+
format = windows.MB_ICONERROR
160+
} else {
161+
format = windows.MB_OKCANCEL | windows.MB_ICONINFORMATION
162+
}
163+
164+
captionPtr, _ := syscall.UTF16PtrFromString(caption)
165+
titlePtr, _ := syscall.UTF16PtrFromString(title)
166+
167+
ret, _ := windows.MessageBox(0, captionPtr, titlePtr, format)
168+
169+
return int(ret)
170+
}
171+
172+
func wrapMaybe(err error, message string) error {
173+
if err != nil {
174+
return fmt.Errorf("%v: %w", message, err)
175+
}
176+
177+
return errors.New(message)
178+
}
179+
180+
func wrapMaybef(err error, format string, args ...any) error {
181+
if err != nil {
182+
return fmt.Errorf(format+": %w", append(args, err)...)
183+
}
184+
185+
return fmt.Errorf(format, args...)
186+
}
187+
188+
// IsReExecuting checks if the current process was re-executed with --reexec flag
189+
func IsReExecuting() bool {
190+
for _, arg := range os.Args {
191+
if arg == "--reexec" {
192+
return true
193+
}
194+
}
195+
return false
196+
}
197+
198+
func CreateOrTruncateElevatedOutputFile() error {
199+
name, err := getElevatedOutputFileName()
200+
if err != nil {
201+
return err
202+
}
203+
204+
_, err = os.Create(name)
205+
return err
206+
}
207+
208+
// getElevatedOutputFileName returns the path to the elevated output log file
209+
func getElevatedOutputFileName() (string, error) {
210+
dir, err := homedir.GetDataHome()
211+
if err != nil {
212+
return "", err
213+
}
214+
return filepath.Join(dir, "podman-elevated-output.log"), nil
215+
}
216+
217+
func getElevatedOutputFileRead() (*os.File, error) {
218+
return getElevatedOutputFile(os.O_RDONLY)
219+
}
220+
221+
func GetElevatedOutputFileWrite() (*os.File, error) {
222+
return getElevatedOutputFile(os.O_WRONLY | os.O_CREATE | os.O_APPEND)
223+
}
224+
225+
func getElevatedOutputFile(mode int) (*os.File, error) {
226+
name, err := getElevatedOutputFileName()
227+
if err != nil {
228+
return nil, err
229+
}
230+
231+
dir, err := homedir.GetDataHome()
232+
if err != nil {
233+
return nil, err
234+
}
235+
236+
if err = os.MkdirAll(dir, 0o755); err != nil {
237+
return nil, err
238+
}
239+
240+
return os.OpenFile(name, mode, 0o644)
241+
}
242+
243+
func DumpOutputFile() {
244+
file, err := getElevatedOutputFileRead()
245+
if err != nil {
246+
logrus.Debug("could not find elevated child output file")
247+
return
248+
}
249+
defer file.Close()
250+
_, _ = io.Copy(os.Stdout, file)
251+
}

pkg/machine/wsl/machine.go

Lines changed: 8 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -18,12 +18,12 @@ 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"
2425
"go.podman.io/common/pkg/config"
2526
"go.podman.io/common/pkg/strongunits"
26-
"go.podman.io/storage/pkg/homedir"
2727
)
2828

2929
var (
@@ -32,14 +32,6 @@ var (
3232
ErrWslNotSupported = errors.New("wsl features not supported or configured correctly")
3333
)
3434

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-
4335
//nolint:unused
4436
func getConfigPath(name string) (string, error) {
4537
return getConfigPathExt(name, "json")
@@ -331,7 +323,7 @@ func attemptFeatureInstall(reExec, admin bool) error {
331323
message += "NOTE: A system reboot will be required as part of this process. " +
332324
"If you prefer, you may abort now, and perform a manual installation using the \"wsl --install\" command."
333325

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

@@ -342,28 +334,28 @@ func attemptFeatureInstall(reExec, admin bool) error {
342334
}
343335

344336
func launchElevate(operation string) error {
345-
if err := createOrTruncateElevatedOutputFile(); err != nil {
337+
if err := winutil.CreateOrTruncateElevatedOutputFile(); err != nil {
346338
return err
347339
}
348-
err := relaunchElevatedWait()
340+
err := winutil.RelaunchElevatedWait()
349341
if err != nil {
350-
if eerr, ok := err.(*ExitCodeError); ok {
351-
if eerr.code == ErrorSuccessRebootRequired {
342+
if eerr, ok := err.(*winutil.ExitCodeError); ok {
343+
if eerr.Code == ErrorSuccessRebootRequired {
352344
fmt.Println("Reboot is required to continue installation, please reboot at your convenience")
353345
return define.ErrInitRelaunchAttempt
354346
}
355347
}
356348

357349
fmt.Fprintf(os.Stderr, "Elevated process failed with error: %v\n\n", err)
358-
dumpOutputFile()
350+
winutil.DumpOutputFile()
359351
fmt.Fprintf(os.Stderr, wslInstallError, operation)
360352
return fmt.Errorf("%w: %w", err, define.ErrInitRelaunchAttempt)
361353
}
362354
return define.ErrInitRelaunchAttempt
363355
}
364356

365357
func installWsl() error {
366-
log, err := getElevatedOutputFileWrite()
358+
log, err := winutil.GetElevatedOutputFileWrite()
367359
if err != nil {
368360
return err
369361
}
@@ -383,60 +375,6 @@ func installWsl() error {
383375
return reboot()
384376
}
385377

386-
func getElevatedOutputFileName() (string, error) {
387-
dir, err := homedir.GetDataHome()
388-
if err != nil {
389-
return "", err
390-
}
391-
return filepath.Join(dir, "podman-elevated-output.log"), nil
392-
}
393-
394-
func dumpOutputFile() {
395-
file, err := getElevatedOutputFileRead()
396-
if err != nil {
397-
logrus.Debug("could not find elevated child output file")
398-
return
399-
}
400-
defer file.Close()
401-
_, _ = io.Copy(os.Stdout, file)
402-
}
403-
404-
func getElevatedOutputFileRead() (*os.File, error) {
405-
return getElevatedOutputFile(os.O_RDONLY)
406-
}
407-
408-
func getElevatedOutputFileWrite() (*os.File, error) {
409-
return getElevatedOutputFile(os.O_WRONLY | os.O_CREATE | os.O_APPEND)
410-
}
411-
412-
func createOrTruncateElevatedOutputFile() error {
413-
name, err := getElevatedOutputFileName()
414-
if err != nil {
415-
return err
416-
}
417-
418-
_, err = os.Create(name)
419-
return err
420-
}
421-
422-
func getElevatedOutputFile(mode int) (*os.File, error) {
423-
name, err := getElevatedOutputFileName()
424-
if err != nil {
425-
return nil, err
426-
}
427-
428-
dir, err := homedir.GetDataHome()
429-
if err != nil {
430-
return nil, err
431-
}
432-
433-
if err = os.MkdirAll(dir, 0o755); err != nil {
434-
return nil, err
435-
}
436-
437-
return os.OpenFile(name, mode, 0o644)
438-
}
439-
440378
func isMsiError(err error) bool {
441379
if err == nil {
442380
return false

0 commit comments

Comments
 (0)