podman

Форк
0
/
machine_windows.go 
269 строк · 6.7 Кб
1
//go:build windows
2

3
package machine
4

5
import (
6
	"context"
7
	"errors"
8
	"fmt"
9
	"io/fs"
10
	"net"
11
	"os"
12
	"os/exec"
13
	"path/filepath"
14
	"strings"
15
	"syscall"
16
	"time"
17

18
	winio "github.com/Microsoft/go-winio"
19
	"github.com/containers/podman/v5/pkg/machine/define"
20
	"github.com/containers/podman/v5/pkg/machine/env"
21
	"github.com/containers/storage/pkg/fileutils"
22
	"github.com/sirupsen/logrus"
23
)
24

25
const (
26
	NamedPipePrefix = "npipe:////./pipe/"
27
	GlobalNamedPipe = "docker_engine"
28
	winSSHProxy     = "win-sshproxy.exe"
29
	winSSHProxyTid  = "win-sshproxy.tid"
30
	rootfulSock     = "/run/podman/podman.sock"
31
	rootlessSock    = "/run/user/1000/podman/podman.sock"
32

33
	// machine wait is longer since we must hard fail
34
	MachineNameWait = 5 * time.Second
35
	GlobalNameWait  = 250 * time.Millisecond
36
)
37

38
//nolint:stylecheck
39
const WM_QUIT = 0x12
40

41
type WinProxyOpts struct {
42
	Name           string
43
	IdentityPath   string
44
	Port           int
45
	RemoteUsername string
46
	Rootful        bool
47
	VMType         define.VMType
48
}
49

50
func GetProcessState(pid int) (active bool, exitCode int) {
51
	const da = syscall.STANDARD_RIGHTS_READ | syscall.PROCESS_QUERY_INFORMATION | syscall.SYNCHRONIZE
52
	handle, err := syscall.OpenProcess(da, false, uint32(pid))
53
	if err != nil {
54
		logrus.Debugf("Error retrieving process %d: %v", pid, err)
55
		return false, int(syscall.ERROR_PROC_NOT_FOUND)
56
	}
57

58
	var code uint32
59
	if err := syscall.GetExitCodeProcess(handle, &code); err != nil {
60
		logrus.Errorf("Error retrieving process %d exit code: %v", pid, err)
61
	}
62
	return code == 259, int(code)
63
}
64

65
func PipeNameAvailable(pipeName string, maxWait time.Duration) bool {
66
	const interval = 250 * time.Millisecond
67
	var wait time.Duration
68
	for {
69
		err := fileutils.Exists(`\\.\pipe\` + pipeName)
70
		if errors.Is(err, fs.ErrNotExist) {
71
			return true
72
		}
73
		if wait >= maxWait {
74
			return false
75
		}
76
		time.Sleep(interval)
77
		wait += interval
78
	}
79
}
80

81
func WaitPipeExists(pipeName string, retries int, checkFailure func() error) error {
82
	var err error
83
	for i := 0; i < retries; i++ {
84
		err = fileutils.Exists(`\\.\pipe\` + pipeName)
85
		if err == nil {
86
			break
87
		}
88
		if fail := checkFailure(); fail != nil {
89
			return fail
90
		}
91
		time.Sleep(250 * time.Millisecond)
92
	}
93

94
	return err
95
}
96

97
func DialNamedPipe(ctx context.Context, path string) (net.Conn, error) {
98
	path = strings.ReplaceAll(path, "/", "\\")
99
	return winio.DialPipeContext(ctx, path)
100
}
101

102
func LaunchWinProxy(opts WinProxyOpts, noInfo bool) {
103
	globalName, pipeName, err := launchWinProxy(opts)
104
	if !noInfo {
105
		if err != nil {
106
			fmt.Fprintln(os.Stderr, "API forwarding for Docker API clients is not available due to the following startup failures.")
107
			fmt.Fprintf(os.Stderr, "\t%s\n", err.Error())
108
			fmt.Fprintln(os.Stderr, "\nPodman clients are still able to connect.")
109
		} else {
110
			fmt.Printf("API forwarding listening on: %s\n", pipeName)
111
			if globalName {
112
				fmt.Printf("\nDocker API clients default to this address. You do not need to set DOCKER_HOST.\n")
113
			} else {
114
				fmt.Printf("\nAnother process was listening on the default Docker API pipe address.\n")
115
				fmt.Printf("You can still connect Docker API clients by setting DOCKER HOST using the\n")
116
				fmt.Printf("following powershell command in your terminal session:\n")
117
				fmt.Printf("\n\t$Env:DOCKER_HOST = '%s'\n", pipeName)
118
				fmt.Printf("\nOr in a classic CMD prompt:\n")
119
				fmt.Printf("\n\tset DOCKER_HOST=%s\n", pipeName)
120
				fmt.Printf("\nAlternatively, terminate the other process and restart podman machine.\n")
121
			}
122
		}
123
	}
124
}
125

126
func launchWinProxy(opts WinProxyOpts) (bool, string, error) {
127
	machinePipe := env.WithPodmanPrefix(opts.Name)
128
	if !PipeNameAvailable(machinePipe, MachineNameWait) {
129
		return false, "", fmt.Errorf("could not start api proxy since expected pipe is not available: %s", machinePipe)
130
	}
131

132
	globalName := false
133
	if PipeNameAvailable(GlobalNamedPipe, GlobalNameWait) {
134
		globalName = true
135
	}
136

137
	command, err := FindExecutablePeer(winSSHProxy)
138
	if err != nil {
139
		return globalName, "", err
140
	}
141

142
	stateDir, err := GetWinProxyStateDir(opts.Name, opts.VMType)
143
	if err != nil {
144
		return globalName, "", err
145
	}
146

147
	destSock := rootlessSock
148
	forwardUser := opts.RemoteUsername
149

150
	if opts.Rootful {
151
		destSock = rootfulSock
152
		forwardUser = "root"
153
	}
154

155
	dest := fmt.Sprintf("ssh://%s@localhost:%d%s", forwardUser, opts.Port, destSock)
156
	args := []string{opts.Name, stateDir, NamedPipePrefix + machinePipe, dest, opts.IdentityPath}
157
	waitPipe := machinePipe
158
	if globalName {
159
		args = append(args, NamedPipePrefix+GlobalNamedPipe, dest, opts.IdentityPath)
160
		waitPipe = GlobalNamedPipe
161
	}
162

163
	cmd := exec.Command(command, args...)
164
	logrus.Debugf("winssh command: %s %v", command, args)
165
	if err := cmd.Start(); err != nil {
166
		return globalName, "", err
167
	}
168

169
	return globalName, NamedPipePrefix + waitPipe, WaitPipeExists(waitPipe, 80, func() error {
170
		active, exitCode := GetProcessState(cmd.Process.Pid)
171
		if !active {
172
			return fmt.Errorf("win-sshproxy.exe failed to start, exit code: %d (see windows event logs)", exitCode)
173
		}
174

175
		return nil
176
	})
177
}
178

179
func StopWinProxy(name string, vmtype define.VMType) error {
180
	pid, tid, tidFile, err := readWinProxyTid(name, vmtype)
181
	if err != nil {
182
		return err
183
	}
184

185
	proc, err := os.FindProcess(int(pid))
186
	if err != nil {
187
		//nolint:nilerr
188
		return nil
189
	}
190
	sendQuit(tid)
191
	_ = waitTimeout(proc, 20*time.Second)
192
	_ = os.Remove(tidFile)
193

194
	return nil
195
}
196

197
func readWinProxyTid(name string, vmtype define.VMType) (uint32, uint32, string, error) {
198
	stateDir, err := GetWinProxyStateDir(name, vmtype)
199
	if err != nil {
200
		return 0, 0, "", err
201
	}
202

203
	tidFile := filepath.Join(stateDir, winSSHProxyTid)
204
	contents, err := os.ReadFile(tidFile)
205
	if err != nil {
206
		return 0, 0, "", err
207
	}
208

209
	var pid, tid uint32
210
	fmt.Sscanf(string(contents), "%d:%d", &pid, &tid)
211
	return pid, tid, tidFile, nil
212
}
213

214
func waitTimeout(proc *os.Process, timeout time.Duration) bool {
215
	done := make(chan bool)
216
	go func() {
217
		_, _ = proc.Wait()
218
		done <- true
219
	}()
220
	ret := false
221
	select {
222
	case <-time.After(timeout):
223
		_ = proc.Kill()
224
		<-done
225
	case <-done:
226
		ret = true
227
		break
228
	}
229

230
	return ret
231
}
232

233
func sendQuit(tid uint32) {
234
	user32 := syscall.NewLazyDLL("user32.dll")
235
	postMessage := user32.NewProc("PostThreadMessageW")
236
	//nolint:dogsled
237
	_, _, _ = postMessage.Call(uintptr(tid), WM_QUIT, 0, 0)
238
}
239

240
func FindExecutablePeer(name string) (string, error) {
241
	exe, err := os.Executable()
242
	if err != nil {
243
		return "", err
244
	}
245

246
	exe, err = filepath.EvalSymlinks(exe)
247
	if err != nil {
248
		return "", err
249
	}
250

251
	return filepath.Join(filepath.Dir(exe), name), nil
252
}
253

254
func GetWinProxyStateDir(name string, vmtype define.VMType) (string, error) {
255
	dir, err := env.GetDataDir(vmtype)
256
	if err != nil {
257
		return "", err
258
	}
259
	stateDir := filepath.Join(dir, name)
260
	if err = os.MkdirAll(stateDir, 0755); err != nil {
261
		return "", err
262
	}
263

264
	return stateDir, nil
265
}
266

267
func GetEnvSetString(env string, val string) string {
268
	return fmt.Sprintf("$Env:%s=\"%s\"", env, val)
269
}
270

Использование cookies

Мы используем файлы cookie в соответствии с Политикой конфиденциальности и Политикой использования cookies.

Нажимая кнопку «Принимаю», Вы даете АО «СберТех» согласие на обработку Ваших персональных данных в целях совершенствования нашего веб-сайта и Сервиса GitVerse, а также повышения удобства их использования.

Запретить использование cookies Вы можете самостоятельно в настройках Вашего браузера.