mirror of
https://github.com/containers/podman.git
synced 2025-06-25 03:52:15 +08:00
Complete WSL implementation, refactor a few areas
Also addresses a number of issues: - StopHostNetworking isn't plumbed, win-sshproxy leaks on hyperv - Wait api and print output doesn't work properly on Windows - API forwarding doesn't work on WSL - Terminal corruption with after start/stop on Windows - Gvproxy is forcefully killed vs gracefully quit - Switching rootful/rootless does not update /var/run/docker.sock on the guest - File already closed error on init - HyperV backend is publishing Unix sockets when it should be named pipes - User-mode networking doesn't always work - Stop state outside of lock boundaries - WSL blocks parallel machined (should be supported) [NO NEW TESTS NEEDED] Signed-off-by: Jason T. Greene <jason.greene@redhat.com>
This commit is contained in:
@ -128,7 +128,6 @@ func setMachine(cmd *cobra.Command, args []string) error {
|
||||
setOpts.DiskSize = &newDiskSizeGB
|
||||
}
|
||||
if cmd.Flags().Changed("user-mode-networking") {
|
||||
// TODO This needs help
|
||||
setOpts.UserModeNetworking = &setFlags.UserModeNetworking
|
||||
}
|
||||
if cmd.Flags().Changed("usb") {
|
||||
|
1
go.mod
1
go.mod
@ -19,6 +19,7 @@ require (
|
||||
github.com/containers/ocicrypt v1.1.9
|
||||
github.com/containers/psgo v1.9.0
|
||||
github.com/containers/storage v1.52.1-0.20240202181245-1419a5980565
|
||||
github.com/containers/winquit v1.1.0
|
||||
github.com/coreos/go-systemd/v22 v22.5.1-0.20231103132048-7d375ecc2b09
|
||||
github.com/coreos/stream-metadata-go v0.4.4
|
||||
github.com/crc-org/crc/v2 v2.32.0
|
||||
|
2
go.sum
2
go.sum
@ -93,6 +93,8 @@ github.com/containers/psgo v1.9.0 h1:eJ74jzSaCHnWt26OlKZROSyUyRcGDf+gYBdXnxrMW4g
|
||||
github.com/containers/psgo v1.9.0/go.mod h1:0YoluUm43Mz2UnBIh1P+6V6NWcbpTL5uRtXyOcH0B5A=
|
||||
github.com/containers/storage v1.52.1-0.20240202181245-1419a5980565 h1:Gcirfx2DNoayB/+ypLgl5+ABzIPPDAoncs1qgZHHQHE=
|
||||
github.com/containers/storage v1.52.1-0.20240202181245-1419a5980565/go.mod h1:2E/QBqWVcJXwumP7nVUrampwRNL4XKjHL/aQya7ZdhI=
|
||||
github.com/containers/winquit v1.1.0 h1:jArun04BNDQvt2W0Y78kh9TazN2EIEMG5Im6/JY7+pE=
|
||||
github.com/containers/winquit v1.1.0/go.mod h1:PsPeZlnbkmGGIToMPHF1zhWjBUkd8aHjMOr/vFcPxw8=
|
||||
github.com/coreos/go-oidc/v3 v3.9.0 h1:0J/ogVOd4y8P0f0xUh8l9t07xRP/d8tccvjHl2dcsSo=
|
||||
github.com/coreos/go-oidc/v3 v3.9.0/go.mod h1:rTKz2PYwftcrtoCzV5g5kvfJoWcm0Mk8AF8y1iAQro4=
|
||||
github.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f h1:JOrtw2xFKzlg+cbHpyrpLDmnN1HqhBfnX7WDiW7eG2c=
|
||||
|
@ -40,6 +40,14 @@ type AppleHVStubber struct {
|
||||
}
|
||||
|
||||
func (a AppleHVStubber) UserModeNetworkEnabled(_ *vmconfigs.MachineConfig) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func (a AppleHVStubber) UseProviderNetworkSetup() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (a AppleHVStubber) RequireExclusiveActive() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
@ -319,7 +327,7 @@ func (a AppleHVStubber) PrepareIgnition(_ *vmconfigs.MachineConfig, _ *ignition.
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (a AppleHVStubber) PostStartNetworking(mc *vmconfigs.MachineConfig) error {
|
||||
func (a AppleHVStubber) PostStartNetworking(mc *vmconfigs.MachineConfig, noInfo bool) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@ -32,8 +32,8 @@ func Decompress(localPath *define.VMFile, uncompressedPath string) error {
|
||||
return err
|
||||
}
|
||||
defer func() {
|
||||
if err := uncompressedFileWriter.Close(); err != nil {
|
||||
logrus.Errorf("unable to to close decompressed file %s: %q", uncompressedPath, err)
|
||||
if err := uncompressedFileWriter.Close(); err != nil && !errors.Is(err, os.ErrClosed) {
|
||||
logrus.Warnf("unable to close decompressed file %s: %q", uncompressedPath, err)
|
||||
}
|
||||
}()
|
||||
sourceFile, err := localPath.Read()
|
||||
|
@ -333,11 +333,55 @@ func NewVirtualization(artifact define.Artifact, compression compression.ImageCo
|
||||
}
|
||||
}
|
||||
|
||||
func dialSocket(socket string, timeout time.Duration) (net.Conn, error) {
|
||||
scheme := "unix"
|
||||
if strings.Contains(socket, "://") {
|
||||
url, err := url.Parse(socket)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
scheme = url.Scheme
|
||||
socket = url.Path
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), timeout)
|
||||
defer cancel()
|
||||
var dial func() (net.Conn, error)
|
||||
switch scheme {
|
||||
default:
|
||||
fallthrough
|
||||
case "unix":
|
||||
dial = func() (net.Conn, error) {
|
||||
var dialer net.Dialer
|
||||
return dialer.DialContext(ctx, "unix", socket)
|
||||
}
|
||||
case "npipe":
|
||||
dial = func() (net.Conn, error) {
|
||||
return DialNamedPipe(ctx, socket)
|
||||
}
|
||||
}
|
||||
|
||||
backoff := 500 * time.Millisecond
|
||||
for {
|
||||
conn, err := dial()
|
||||
if !errors.Is(err, os.ErrNotExist) {
|
||||
return conn, err
|
||||
}
|
||||
|
||||
select {
|
||||
case <-time.After(backoff):
|
||||
backoff *= 2
|
||||
case <-ctx.Done():
|
||||
return nil, ctx.Err()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func WaitAndPingAPI(sock string) {
|
||||
client := http.Client{
|
||||
Transport: &http.Transport{
|
||||
DialContext: func(context.Context, string, string) (net.Conn, error) {
|
||||
con, err := net.DialTimeout("unix", sock, apiUpTimeout)
|
||||
con, err := dialSocket(sock, apiUpTimeout)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -2,7 +2,6 @@ package e2e_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/containers/podman/v4/pkg/machine/wsl"
|
||||
"io"
|
||||
url2 "net/url"
|
||||
"os"
|
||||
@ -13,6 +12,8 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/containers/podman/v5/pkg/machine/wsl"
|
||||
|
||||
"github.com/containers/podman/v5/pkg/machine"
|
||||
"github.com/containers/podman/v5/pkg/machine/compression"
|
||||
"github.com/containers/podman/v5/pkg/machine/define"
|
||||
|
@ -1,16 +1,12 @@
|
||||
package machine
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"github.com/containers/podman/v5/pkg/machine/define"
|
||||
psutil "github.com/shirou/gopsutil/v3/process"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
const (
|
||||
@ -39,49 +35,6 @@ func backoffForProcess(p *psutil.Process) error {
|
||||
return fmt.Errorf("process %d has not ended", p.Pid)
|
||||
}
|
||||
|
||||
// waitOnProcess takes a pid and sends a sigterm to it. it then waits for the
|
||||
// process to not exist. if the sigterm does not end the process after an interval,
|
||||
// then sigkill is sent. it also waits for the process to exit after the sigkill too.
|
||||
func waitOnProcess(processID int) error {
|
||||
logrus.Infof("Going to stop gvproxy (PID %d)", processID)
|
||||
|
||||
p, err := psutil.NewProcess(int32(processID))
|
||||
if err != nil {
|
||||
return fmt.Errorf("looking up PID %d: %w", processID, err)
|
||||
}
|
||||
|
||||
// Try to kill the pid with sigterm
|
||||
if runtime.GOOS != "windows" { // FIXME: temporary work around because signals are lame in windows
|
||||
if err := p.SendSignal(syscall.SIGTERM); err != nil {
|
||||
if errors.Is(err, syscall.ESRCH) {
|
||||
return nil
|
||||
}
|
||||
return fmt.Errorf("sending SIGTERM to grproxy: %w", err)
|
||||
}
|
||||
|
||||
if err := backoffForProcess(p); err == nil {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
running, err := p.IsRunning()
|
||||
if err != nil {
|
||||
return fmt.Errorf("checking if gvproxy is running: %w", err)
|
||||
}
|
||||
if !running {
|
||||
return nil
|
||||
}
|
||||
|
||||
if err := p.Kill(); err != nil {
|
||||
if errors.Is(err, syscall.ESRCH) {
|
||||
logrus.Debugf("Gvproxy already dead, exiting cleanly")
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
return backoffForProcess(p)
|
||||
}
|
||||
|
||||
// CleanupGVProxy reads the --pid-file for gvproxy attempts to stop it
|
||||
func CleanupGVProxy(f define.VMFile) error {
|
||||
gvPid, err := f.Read()
|
||||
|
41
pkg/machine/gvproxy_unix.go
Normal file
41
pkg/machine/gvproxy_unix.go
Normal file
@ -0,0 +1,41 @@
|
||||
//go:build darwin || dragonfly || freebsd || linux || netbsd || openbsd
|
||||
|
||||
package machine
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"syscall"
|
||||
|
||||
psutil "github.com/shirou/gopsutil/v3/process"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
// / waitOnProcess takes a pid and sends a sigterm to it. it then waits for the
|
||||
// process to not exist. if the sigterm does not end the process after an interval,
|
||||
// then sigkill is sent. it also waits for the process to exit after the sigkill too.
|
||||
func waitOnProcess(processID int) error {
|
||||
logrus.Infof("Going to stop gvproxy (PID %d)", processID)
|
||||
|
||||
p, err := psutil.NewProcess(int32(processID))
|
||||
if err != nil {
|
||||
return fmt.Errorf("looking up PID %d: %w", processID, err)
|
||||
}
|
||||
|
||||
running, err := p.IsRunning()
|
||||
if err != nil {
|
||||
return fmt.Errorf("checking if gvproxy is running: %w", err)
|
||||
}
|
||||
if !running {
|
||||
return nil
|
||||
}
|
||||
|
||||
if err := p.Kill(); err != nil {
|
||||
if errors.Is(err, syscall.ESRCH) {
|
||||
logrus.Debugf("Gvproxy already dead, exiting cleanly")
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
return backoffForProcess(p)
|
||||
}
|
42
pkg/machine/gvproxy_windows.go
Normal file
42
pkg/machine/gvproxy_windows.go
Normal file
@ -0,0 +1,42 @@
|
||||
package machine
|
||||
|
||||
import (
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/containers/winquit/pkg/winquit"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
func waitOnProcess(processID int) error {
|
||||
logrus.Infof("Going to stop gvproxy (PID %d)", processID)
|
||||
|
||||
p, err := os.FindProcess(processID)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Gracefully quit and force kill after 30 seconds
|
||||
if err := winquit.QuitProcess(processID, 30*time.Second); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
logrus.Debugf("completed grace quit || kill of gvproxy (PID %d)", processID)
|
||||
|
||||
// Make sure the process is gone
|
||||
done := make(chan struct{})
|
||||
go func() {
|
||||
_, _ = p.Wait()
|
||||
done <- struct{}{}
|
||||
}()
|
||||
|
||||
select {
|
||||
case <-done:
|
||||
logrus.Debugf("verified gvproxy termination (PID %d)", processID)
|
||||
case <-time.After(10 * time.Second):
|
||||
// Very unlikely but track just in case
|
||||
logrus.Errorf("was not able to kill gvproxy (PID %d)", processID)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
@ -33,6 +33,14 @@ func (h HyperVStubber) UserModeNetworkEnabled(mc *vmconfigs.MachineConfig) bool
|
||||
return true
|
||||
}
|
||||
|
||||
func (h HyperVStubber) UseProviderNetworkSetup() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (h HyperVStubber) RequireExclusiveActive() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func (h HyperVStubber) CreateVM(opts define.CreateVMOpts, mc *vmconfigs.MachineConfig, builder *ignition.IgnitionBuilder) error {
|
||||
var (
|
||||
err error
|
||||
@ -374,7 +382,7 @@ func (h HyperVStubber) PrepareIgnition(mc *vmconfigs.MachineConfig, ignBuilder *
|
||||
return &ignOpts, nil
|
||||
}
|
||||
|
||||
func (h HyperVStubber) PostStartNetworking(mc *vmconfigs.MachineConfig) error {
|
||||
func (h HyperVStubber) PostStartNetworking(mc *vmconfigs.MachineConfig, noInfo bool) error {
|
||||
var (
|
||||
err error
|
||||
executable string
|
||||
@ -383,25 +391,6 @@ func (h HyperVStubber) PostStartNetworking(mc *vmconfigs.MachineConfig) error {
|
||||
defer callbackFuncs.CleanIfErr(&err)
|
||||
go callbackFuncs.CleanOnSignal()
|
||||
|
||||
winProxyOpts := machine.WinProxyOpts{
|
||||
Name: mc.Name,
|
||||
IdentityPath: mc.SSH.IdentityPath,
|
||||
Port: mc.SSH.Port,
|
||||
RemoteUsername: mc.SSH.RemoteUsername,
|
||||
Rootful: mc.HostUser.Rootful,
|
||||
VMType: h.VMType(),
|
||||
}
|
||||
// TODO Should this process be fatal on error; currenty, no error is
|
||||
// returned but an error can occur in the func itself
|
||||
// TODO we do not currently pass "noinfo" (quiet) into the StartVM
|
||||
// func so this is hard set to false
|
||||
machine.LaunchWinProxy(winProxyOpts, false)
|
||||
|
||||
winProxyCallbackFunc := func() error {
|
||||
return machine.StopWinProxy(mc.Name, h.VMType())
|
||||
}
|
||||
callbackFuncs.Add(winProxyCallbackFunc)
|
||||
|
||||
if len(mc.Mounts) != 0 {
|
||||
var (
|
||||
dirs *define.MachineDirs
|
||||
|
@ -6,6 +6,7 @@ import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/containers/podman/v5/pkg/machine/connection"
|
||||
"github.com/containers/storage/pkg/ioutils"
|
||||
@ -46,19 +47,6 @@ func WaitAPIAndPrintInfo(forwardState APIForwardingState, name, helper, forwardS
|
||||
WaitAndPingAPI(forwardSock)
|
||||
|
||||
if !noInfo {
|
||||
if !rootful {
|
||||
fmtString = `
|
||||
This machine is currently configured in rootless mode. If your containers
|
||||
require root permissions (e.g. ports < 1024), or if you run into compatibility
|
||||
issues with non-podman clients, you can switch using the following command:
|
||||
|
||||
podman machine set --rootful%s
|
||||
|
||||
`
|
||||
|
||||
fmt.Printf(fmtString, suffix)
|
||||
}
|
||||
|
||||
fmt.Printf("API forwarding listening on: %s\n", forwardSock)
|
||||
if forwardState == DockerGlobal {
|
||||
fmt.Printf("Docker API clients default to this address. You do not need to set DOCKER_HOST.\n\n")
|
||||
@ -79,7 +67,7 @@ address can't be used by podman. `
|
||||
sudo %s install
|
||||
podman machine stop%[2]s; podman machine start%[2]s
|
||||
|
||||
`
|
||||
`
|
||||
fmt.Printf(fmtString, helper, suffix)
|
||||
}
|
||||
case MachineLocal:
|
||||
@ -93,13 +81,33 @@ address can't be used by podman. `
|
||||
fmtString = `You can %sconnect Docker API clients by setting DOCKER_HOST using the
|
||||
following command in your terminal session:
|
||||
|
||||
export DOCKER_HOST='unix://%s'
|
||||
%s'
|
||||
|
||||
`
|
||||
prefix := ""
|
||||
if !strings.Contains(forwardSock, "://") {
|
||||
prefix = "unix://"
|
||||
}
|
||||
fmt.Printf(fmtString, stillString, GetEnvSetString("DOCKER_HOST", prefix+forwardSock))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fmt.Printf(fmtString, stillString, forwardSock)
|
||||
}
|
||||
func PrintRootlessWarning(name string) {
|
||||
suffix := ""
|
||||
if name != DefaultMachineName {
|
||||
suffix = " " + name
|
||||
}
|
||||
|
||||
fmtString := `
|
||||
This machine is currently configured in rootless mode. If your containers
|
||||
require root permissions (e.g. ports < 1024), or if you run into compatibility
|
||||
issues with non-podman clients, you can switch using the following command:
|
||||
|
||||
podman machine set --rootful%s
|
||||
|
||||
`
|
||||
fmt.Printf(fmtString, suffix)
|
||||
}
|
||||
|
||||
// SetRootful modifies the machine's default connection to be either rootful or
|
||||
|
@ -3,7 +3,10 @@
|
||||
package machine
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"strings"
|
||||
)
|
||||
|
||||
@ -32,3 +35,11 @@ func ParseVolumeFromPath(v string) (source, target, options string, readonly boo
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func DialNamedPipe(ctx context.Context, path string) (net.Conn, error) {
|
||||
return nil, errors.New("not implemented")
|
||||
}
|
||||
|
||||
func GetEnvSetString(env string, val string) string {
|
||||
return fmt.Sprintf("export %s='%s'", env, val)
|
||||
}
|
||||
|
@ -3,7 +3,9 @@
|
||||
package machine
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
@ -12,12 +14,13 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/containers/podman/v5/pkg/machine/define"
|
||||
winio "github.com/Microsoft/go-winio"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
const (
|
||||
pipePrefix = "npipe:////./pipe/"
|
||||
globalPipe = "docker_engine"
|
||||
NamedPipePrefix = "npipe:////./pipe/"
|
||||
GlobalNamedPipe = "docker_engine"
|
||||
winSShProxy = "win-sshproxy.exe"
|
||||
winSshProxyTid = "win-sshproxy.tid"
|
||||
rootfulSock = "/run/podman/podman.sock"
|
||||
@ -71,6 +74,11 @@ func WaitPipeExists(pipeName string, retries int, checkFailure func() error) err
|
||||
return err
|
||||
}
|
||||
|
||||
func DialNamedPipe(ctx context.Context, path string) (net.Conn, error) {
|
||||
path = strings.Replace(path, "/", "\\", -1)
|
||||
return winio.DialPipeContext(ctx, path)
|
||||
}
|
||||
|
||||
func LaunchWinProxy(opts WinProxyOpts, noInfo bool) {
|
||||
globalName, pipeName, err := launchWinProxy(opts)
|
||||
if !noInfo {
|
||||
@ -102,7 +110,7 @@ func launchWinProxy(opts WinProxyOpts) (bool, string, error) {
|
||||
}
|
||||
|
||||
globalName := false
|
||||
if PipeNameAvailable(globalPipe) {
|
||||
if PipeNameAvailable(GlobalNamedPipe) {
|
||||
globalName = true
|
||||
}
|
||||
|
||||
@ -125,27 +133,20 @@ func launchWinProxy(opts WinProxyOpts) (bool, string, error) {
|
||||
}
|
||||
|
||||
dest := fmt.Sprintf("ssh://%s@localhost:%d%s", forwardUser, opts.Port, destSock)
|
||||
args := []string{opts.Name, stateDir, pipePrefix + machinePipe, dest, opts.IdentityPath}
|
||||
args := []string{opts.Name, stateDir, NamedPipePrefix + machinePipe, dest, opts.IdentityPath}
|
||||
waitPipe := machinePipe
|
||||
if globalName {
|
||||
args = append(args, pipePrefix+globalPipe, dest, opts.IdentityPath)
|
||||
waitPipe = globalPipe
|
||||
args = append(args, NamedPipePrefix+GlobalNamedPipe, dest, opts.IdentityPath)
|
||||
waitPipe = GlobalNamedPipe
|
||||
}
|
||||
|
||||
cmd := exec.Command(command, args...)
|
||||
logrus.Debugf("winssh command: %s %v", command, args)
|
||||
f, err := os.Open("c:\\Users\\baude\\sshproxy.log")
|
||||
if err != nil {
|
||||
return false, "", err
|
||||
}
|
||||
cmd.Stderr = f
|
||||
cmd.Stdout = f
|
||||
defer f.Close()
|
||||
if err := cmd.Start(); err != nil {
|
||||
return globalName, "", err
|
||||
}
|
||||
|
||||
return globalName, pipePrefix + waitPipe, WaitPipeExists(waitPipe, 80, func() error {
|
||||
return globalName, NamedPipePrefix + waitPipe, WaitPipeExists(waitPipe, 80, func() error {
|
||||
active, exitCode := GetProcessState(cmd.Process.Pid)
|
||||
if !active {
|
||||
return fmt.Errorf("win-sshproxy.exe failed to start, exit code: %d (see windows event logs)", exitCode)
|
||||
@ -247,3 +248,7 @@ func ToDist(name string) string {
|
||||
}
|
||||
return name
|
||||
}
|
||||
|
||||
func GetEnvSetString(env string, val string) string {
|
||||
return fmt.Sprintf("$Env:%s=\"%s\"", env, val)
|
||||
}
|
||||
|
@ -34,6 +34,14 @@ func (q QEMUStubber) UserModeNetworkEnabled(*vmconfigs.MachineConfig) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func (q QEMUStubber) UseProviderNetworkSetup() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (q QEMUStubber) RequireExclusiveActive() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func (q *QEMUStubber) setQEMUCommandLine(mc *vmconfigs.MachineConfig) error {
|
||||
qemuBinary, err := findQEMUBinary()
|
||||
if err != nil {
|
||||
@ -73,7 +81,7 @@ func (q *QEMUStubber) setQEMUCommandLine(mc *vmconfigs.MachineConfig) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (q *QEMUStubber) CreateVM(opts define.CreateVMOpts, mc *vmconfigs.MachineConfig, _ *ignition.IgnitionBuilder) error {
|
||||
func (q *QEMUStubber) CreateVM(opts define.CreateVMOpts, mc *vmconfigs.MachineConfig, builder *ignition.IgnitionBuilder) error {
|
||||
monitor, err := command.NewQMPMonitor(opts.Name, opts.Dirs.RuntimeDir)
|
||||
if err != nil {
|
||||
return err
|
||||
@ -327,7 +335,7 @@ func (q *QEMUStubber) MountType() vmconfigs.VolumeMountType {
|
||||
return vmconfigs.NineP
|
||||
}
|
||||
|
||||
func (q *QEMUStubber) PostStartNetworking(mc *vmconfigs.MachineConfig) error {
|
||||
func (q *QEMUStubber) PostStartNetworking(mc *vmconfigs.MachineConfig, noInfo bool) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@ -256,6 +256,11 @@ func VMExists(name string, vmstubbers []vmconfigs.VMProvider) (*vmconfigs.Machin
|
||||
|
||||
// CheckExclusiveActiveVM checks if any of the machines are already running
|
||||
func CheckExclusiveActiveVM(provider vmconfigs.VMProvider, mc *vmconfigs.MachineConfig) error {
|
||||
// Don't check if provider supports parallel running machines
|
||||
if !provider.RequireExclusiveActive() {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Check if any other machines are running; if so, we error
|
||||
localMachines, err := getMCsOverProviders([]vmconfigs.VMProvider{provider})
|
||||
if err != nil {
|
||||
@ -330,7 +335,7 @@ func Stop(mc *vmconfigs.MachineConfig, mp vmconfigs.VMProvider, dirs *machineDef
|
||||
}
|
||||
|
||||
// Stop GvProxy and remove PID file
|
||||
if mp.UserModeNetworkEnabled(mc) {
|
||||
if !mp.UseProviderNetworkSetup() {
|
||||
gvproxyPidFile, err := dirs.RuntimeDir.AppendToNewVMFile("gvproxy.pid", nil)
|
||||
if err != nil {
|
||||
return err
|
||||
@ -381,7 +386,11 @@ func Start(mc *vmconfigs.MachineConfig, mp vmconfigs.VMProvider, _ *machineDefin
|
||||
}
|
||||
}
|
||||
|
||||
err = mp.PostStartNetworking(mc)
|
||||
if !opts.NoInfo && !mc.HostUser.Rootful {
|
||||
machine.PrintRootlessWarning(mc.Name)
|
||||
}
|
||||
|
||||
err = mp.PostStartNetworking(mc, opts.NoInfo)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -408,15 +417,6 @@ func Start(mc *vmconfigs.MachineConfig, mp vmconfigs.VMProvider, _ *machineDefin
|
||||
return err
|
||||
}
|
||||
|
||||
machine.WaitAPIAndPrintInfo(
|
||||
forwardingState,
|
||||
mc.Name,
|
||||
findClaimHelper(),
|
||||
forwardSocketPath,
|
||||
opts.NoInfo,
|
||||
mc.HostUser.Rootful,
|
||||
)
|
||||
|
||||
// update the podman/docker socket service if the host user has been modified at all (UID or Rootful)
|
||||
if mc.HostUser.Modified {
|
||||
if machine.UpdatePodmanDockerSockService(mc) == nil {
|
||||
@ -428,5 +428,22 @@ func Start(mc *vmconfigs.MachineConfig, mp vmconfigs.VMProvider, _ *machineDefin
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Provider is responsible for waiting
|
||||
if mp.UseProviderNetworkSetup() {
|
||||
return nil
|
||||
}
|
||||
|
||||
noInfo := opts.NoInfo
|
||||
|
||||
machine.WaitAPIAndPrintInfo(
|
||||
forwardingState,
|
||||
mc.Name,
|
||||
findClaimHelper(),
|
||||
forwardSocketPath,
|
||||
noInfo,
|
||||
mc.HostUser.Rootful,
|
||||
)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
@ -2,9 +2,7 @@ package shim
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/fs"
|
||||
"net"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
@ -23,7 +21,7 @@ const (
|
||||
dockerConnectTimeout = 5 * time.Second
|
||||
)
|
||||
|
||||
func startUserModeNetworking(mc *vmconfigs.MachineConfig, provider vmconfigs.VMProvider, dirs *define.MachineDirs, hostSocket *define.VMFile) error {
|
||||
func startHostForwarder(mc *vmconfigs.MachineConfig, provider vmconfigs.VMProvider, dirs *define.MachineDirs, hostSocks []string) error {
|
||||
forwardUser := mc.SSH.RemoteUsername
|
||||
|
||||
// TODO should this go up the stack higher or
|
||||
@ -57,10 +55,13 @@ func startUserModeNetworking(mc *vmconfigs.MachineConfig, provider vmconfigs.VMP
|
||||
|
||||
cmd.SSHPort = mc.SSH.Port
|
||||
|
||||
cmd.AddForwardSock(hostSocket.GetPath())
|
||||
// Windows providers listen on multiple sockets since they do not involve links
|
||||
for _, hostSock := range hostSocks {
|
||||
cmd.AddForwardSock(hostSock)
|
||||
cmd.AddForwardDest(guestSock)
|
||||
cmd.AddForwardUser(forwardUser)
|
||||
cmd.AddForwardIdentity(mc.SSH.IdentityPath)
|
||||
}
|
||||
|
||||
if logrus.IsLevelEnabled(logrus.DebugLevel) {
|
||||
cmd.Debug = true
|
||||
@ -84,92 +85,28 @@ func startUserModeNetworking(mc *vmconfigs.MachineConfig, provider vmconfigs.VMP
|
||||
}
|
||||
|
||||
func startNetworking(mc *vmconfigs.MachineConfig, provider vmconfigs.VMProvider) (string, machine.APIForwardingState, error) {
|
||||
var (
|
||||
forwardingState machine.APIForwardingState
|
||||
forwardSock string
|
||||
)
|
||||
// Provider has its own networking code path (e.g. WSL)
|
||||
if provider.UseProviderNetworkSetup() {
|
||||
return "", 0, provider.StartNetworking(mc, nil)
|
||||
}
|
||||
|
||||
dirs, err := machine.GetMachineDirs(provider.VMType())
|
||||
if err != nil {
|
||||
return "", 0, err
|
||||
}
|
||||
hostSocket, err := dirs.DataDir.AppendToNewVMFile("podman.sock", nil)
|
||||
|
||||
hostSocks, forwardSock, forwardingState, err := setupMachineSockets(mc.Name, dirs)
|
||||
if err != nil {
|
||||
return "", 0, err
|
||||
}
|
||||
|
||||
linkSocketPath := filepath.Dir(dirs.DataDir.GetPath())
|
||||
linkSocket, err := define.NewMachineFile(filepath.Join(linkSocketPath, "podman.sock"), nil)
|
||||
if err != nil {
|
||||
if err := startHostForwarder(mc, provider, dirs, hostSocks); err != nil {
|
||||
return "", 0, err
|
||||
}
|
||||
|
||||
if mc.HostUser.UID != -1 {
|
||||
forwardSock, forwardingState = setupAPIForwarding(hostSocket, linkSocket)
|
||||
}
|
||||
|
||||
if provider.UserModeNetworkEnabled(mc) {
|
||||
if err := startUserModeNetworking(mc, provider, dirs, hostSocket); err != nil {
|
||||
return "", 0, err
|
||||
}
|
||||
}
|
||||
|
||||
return forwardSock, forwardingState, nil
|
||||
}
|
||||
|
||||
func setupAPIForwarding(hostSocket, linkSocket *define.VMFile) (string, machine.APIForwardingState) {
|
||||
// The linking pattern is /var/run/docker.sock -> user global sock (link) -> machine sock (socket)
|
||||
// This allows the helper to only have to maintain one constant target to the user, which can be
|
||||
// repositioned without updating docker.sock.
|
||||
|
||||
if !dockerClaimSupported() {
|
||||
return hostSocket.GetPath(), machine.ClaimUnsupported
|
||||
}
|
||||
|
||||
if !dockerClaimHelperInstalled() {
|
||||
return hostSocket.GetPath(), machine.NotInstalled
|
||||
}
|
||||
|
||||
if !alreadyLinked(hostSocket.GetPath(), linkSocket.GetPath()) {
|
||||
if checkSockInUse(linkSocket.GetPath()) {
|
||||
return hostSocket.GetPath(), machine.MachineLocal
|
||||
}
|
||||
|
||||
_ = linkSocket.Delete()
|
||||
|
||||
if err := os.Symlink(hostSocket.GetPath(), linkSocket.GetPath()); err != nil {
|
||||
logrus.Warnf("could not create user global API forwarding link: %s", err.Error())
|
||||
return hostSocket.GetPath(), machine.MachineLocal
|
||||
}
|
||||
}
|
||||
|
||||
if !alreadyLinked(linkSocket.GetPath(), dockerSock) {
|
||||
if checkSockInUse(dockerSock) {
|
||||
return hostSocket.GetPath(), machine.MachineLocal
|
||||
}
|
||||
|
||||
if !claimDockerSock() {
|
||||
logrus.Warn("podman helper is installed, but was not able to claim the global docker sock")
|
||||
return hostSocket.GetPath(), machine.MachineLocal
|
||||
}
|
||||
}
|
||||
|
||||
return dockerSock, machine.DockerGlobal
|
||||
}
|
||||
|
||||
func alreadyLinked(target string, link string) bool {
|
||||
read, err := os.Readlink(link)
|
||||
return err == nil && read == target
|
||||
}
|
||||
|
||||
func checkSockInUse(sock string) bool {
|
||||
if info, err := os.Stat(sock); err == nil && info.Mode()&fs.ModeSocket == fs.ModeSocket {
|
||||
_, err = net.DialTimeout("unix", dockerSock, dockerConnectTimeout)
|
||||
return err == nil
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// conductVMReadinessCheck checks to make sure the machine is in the proper state
|
||||
// and that SSH is up and running
|
||||
func conductVMReadinessCheck(mc *vmconfigs.MachineConfig, maxBackoffs int, backoff time.Duration, stateF func() (define.Status, error)) (connected bool, sshError error, err error) {
|
||||
@ -191,7 +128,7 @@ func conductVMReadinessCheck(mc *vmconfigs.MachineConfig, maxBackoffs int, backo
|
||||
// CoreOS users have reported the same observation but
|
||||
// the underlying source of the issue remains unknown.
|
||||
|
||||
if sshError = machine.CommonSSH(mc.SSH.RemoteUsername, mc.SSH.IdentityPath, mc.Name, mc.SSH.Port, []string{"true"}); sshError != nil {
|
||||
if sshError = machine.CommonSSHSilent(mc.SSH.RemoteUsername, mc.SSH.IdentityPath, mc.Name, mc.SSH.Port, []string{"true"}); sshError != nil {
|
||||
logrus.Debugf("SSH readiness check for machine failed: %v", sshError)
|
||||
continue
|
||||
}
|
||||
|
84
pkg/machine/shim/networking_unix.go
Normal file
84
pkg/machine/shim/networking_unix.go
Normal file
@ -0,0 +1,84 @@
|
||||
//go:build dragonfly || freebsd || linux || netbsd || openbsd || darwin
|
||||
|
||||
package shim
|
||||
|
||||
import (
|
||||
"io/fs"
|
||||
"net"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/containers/podman/v5/pkg/machine"
|
||||
"github.com/containers/podman/v5/pkg/machine/define"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
func setupMachineSockets(name string, dirs *define.MachineDirs) ([]string, string, machine.APIForwardingState, error) {
|
||||
hostSocket, err := dirs.DataDir.AppendToNewVMFile("podman.sock", nil)
|
||||
if err != nil {
|
||||
return nil, "", 0, err
|
||||
}
|
||||
|
||||
linkSocketPath := filepath.Dir(dirs.DataDir.GetPath())
|
||||
linkSocket, err := define.NewMachineFile(filepath.Join(linkSocketPath, "podman.sock"), nil)
|
||||
if err != nil {
|
||||
return nil, "", 0, err
|
||||
}
|
||||
|
||||
forwardSock, state := setupForwardingLinks(hostSocket, linkSocket)
|
||||
return []string{hostSocket.GetPath()}, forwardSock, state, nil
|
||||
}
|
||||
|
||||
func setupForwardingLinks(hostSocket, linkSocket *define.VMFile) (string, machine.APIForwardingState) {
|
||||
// The linking pattern is /var/run/docker.sock -> user global sock (link) -> machine sock (socket)
|
||||
// This allows the helper to only have to maintain one constant target to the user, which can be
|
||||
// repositioned without updating docker.sock.
|
||||
|
||||
if !dockerClaimSupported() {
|
||||
return hostSocket.GetPath(), machine.ClaimUnsupported
|
||||
}
|
||||
|
||||
if !dockerClaimHelperInstalled() {
|
||||
return hostSocket.GetPath(), machine.NotInstalled
|
||||
}
|
||||
|
||||
if !alreadyLinked(hostSocket.GetPath(), linkSocket.GetPath()) {
|
||||
if checkSockInUse(linkSocket.GetPath()) {
|
||||
return hostSocket.GetPath(), machine.MachineLocal
|
||||
}
|
||||
|
||||
_ = linkSocket.Delete()
|
||||
|
||||
if err := os.Symlink(hostSocket.GetPath(), linkSocket.GetPath()); err != nil {
|
||||
logrus.Warnf("could not create user global API forwarding link: %s", err.Error())
|
||||
return hostSocket.GetPath(), machine.MachineLocal
|
||||
}
|
||||
}
|
||||
|
||||
if !alreadyLinked(linkSocket.GetPath(), dockerSock) {
|
||||
if checkSockInUse(dockerSock) {
|
||||
return hostSocket.GetPath(), machine.MachineLocal
|
||||
}
|
||||
|
||||
if !claimDockerSock() {
|
||||
logrus.Warn("podman helper is installed, but was not able to claim the global docker sock")
|
||||
return hostSocket.GetPath(), machine.MachineLocal
|
||||
}
|
||||
}
|
||||
|
||||
return dockerSock, machine.DockerGlobal
|
||||
}
|
||||
|
||||
func alreadyLinked(target string, link string) bool {
|
||||
read, err := os.Readlink(link)
|
||||
return err == nil && read == target
|
||||
}
|
||||
|
||||
func checkSockInUse(sock string) bool {
|
||||
if info, err := os.Stat(sock); err == nil && info.Mode()&fs.ModeSocket == fs.ModeSocket {
|
||||
_, err = net.DialTimeout("unix", dockerSock, dockerConnectTimeout)
|
||||
return err == nil
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
24
pkg/machine/shim/networking_windows.go
Normal file
24
pkg/machine/shim/networking_windows.go
Normal file
@ -0,0 +1,24 @@
|
||||
package shim
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/containers/podman/v5/pkg/machine"
|
||||
"github.com/containers/podman/v5/pkg/machine/define"
|
||||
)
|
||||
|
||||
func setupMachineSockets(name string, dirs *define.MachineDirs) ([]string, string, machine.APIForwardingState, error) {
|
||||
machinePipe := machine.ToDist(name)
|
||||
if !machine.PipeNameAvailable(machinePipe) {
|
||||
return nil, "", 0, fmt.Errorf("could not start api proxy since expected pipe is not available: %s", machinePipe)
|
||||
}
|
||||
sockets := []string{machine.NamedPipePrefix + machinePipe}
|
||||
state := machine.MachineLocal
|
||||
|
||||
if machine.PipeNameAvailable(machine.GlobalNamedPipe) {
|
||||
sockets = append(sockets, machine.NamedPipePrefix+machine.GlobalNamedPipe)
|
||||
state = machine.DockerGlobal
|
||||
}
|
||||
|
||||
return sockets, sockets[len(sockets)-1], state, nil
|
||||
}
|
@ -2,7 +2,6 @@ package machine
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"strconv"
|
||||
|
||||
@ -13,24 +12,38 @@ import (
|
||||
// and a port
|
||||
// TODO This should probably be taught about an machineconfig to reduce input
|
||||
func CommonSSH(username, identityPath, name string, sshPort int, inputArgs []string) error {
|
||||
return commonSSH(username, identityPath, name, sshPort, inputArgs, false)
|
||||
}
|
||||
|
||||
func CommonSSHSilent(username, identityPath, name string, sshPort int, inputArgs []string) error {
|
||||
return commonSSH(username, identityPath, name, sshPort, inputArgs, true)
|
||||
}
|
||||
|
||||
func commonSSH(username, identityPath, name string, sshPort int, inputArgs []string, silent bool) error {
|
||||
sshDestination := username + "@localhost"
|
||||
port := strconv.Itoa(sshPort)
|
||||
interactive := true
|
||||
|
||||
args := []string{"-i", identityPath, "-p", port, sshDestination,
|
||||
"-o", "IdentitiesOnly=yes",
|
||||
"-o", "StrictHostKeyChecking=no", "-o", "LogLevel=ERROR", "-o", "SetEnv=LC_ALL="}
|
||||
if len(inputArgs) > 0 {
|
||||
interactive = false
|
||||
args = append(args, inputArgs...)
|
||||
} else {
|
||||
// ensure we have a tty
|
||||
args = append(args, "-t")
|
||||
fmt.Printf("Connecting to vm %s. To close connection, use `~.` or `exit`\n", name)
|
||||
}
|
||||
|
||||
cmd := exec.Command("ssh", args...)
|
||||
logrus.Debugf("Executing: ssh %v\n", args)
|
||||
|
||||
cmd.Stdout = os.Stdout
|
||||
cmd.Stderr = os.Stderr
|
||||
cmd.Stdin = os.Stdin
|
||||
if !silent {
|
||||
if err := setupIOPassthrough(cmd, interactive); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return cmd.Run()
|
||||
}
|
||||
|
16
pkg/machine/ssh_unix.go
Normal file
16
pkg/machine/ssh_unix.go
Normal file
@ -0,0 +1,16 @@
|
||||
//go:build darwin || dragonfly || freebsd || linux || netbsd || openbsd
|
||||
|
||||
package machine
|
||||
|
||||
import (
|
||||
"os"
|
||||
"os/exec"
|
||||
)
|
||||
|
||||
func setupIOPassthrough(cmd *exec.Cmd, interactive bool) error {
|
||||
cmd.Stdin = os.Stdin
|
||||
cmd.Stdout = os.Stdout
|
||||
cmd.Stderr = os.Stderr
|
||||
|
||||
return nil
|
||||
}
|
42
pkg/machine/ssh_windows.go
Normal file
42
pkg/machine/ssh_windows.go
Normal file
@ -0,0 +1,42 @@
|
||||
package machine
|
||||
|
||||
import (
|
||||
"io"
|
||||
"os"
|
||||
"os/exec"
|
||||
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
func setupIOPassthrough(cmd *exec.Cmd, interactive bool) error {
|
||||
cmd.Stdin = os.Stdin
|
||||
|
||||
if interactive {
|
||||
cmd.Stdout = os.Stdout
|
||||
cmd.Stderr = os.Stderr
|
||||
return nil
|
||||
}
|
||||
|
||||
// OpenSSh mucks with the associated virtual console when there is no pty,
|
||||
// leaving it in a broken state. Pipe the output to isolate stdout/stderr
|
||||
stdout, err := cmd.StdoutPipe()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
stderr, err := cmd.StderrPipe()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
copier := func(name string, dest string, from io.Reader, to io.Writer) {
|
||||
if _, err := io.Copy(to, from); err != nil {
|
||||
logrus.Warnf("could not copy output from command %s to %s", name, dest)
|
||||
}
|
||||
}
|
||||
|
||||
go copier(cmd.Path, "stdout", stdout, os.Stdout)
|
||||
go copier(cmd.Path, "stderr", stderr, os.Stderr)
|
||||
|
||||
return nil
|
||||
}
|
@ -119,13 +119,15 @@ type VMProvider interface { //nolint:interfacebloat
|
||||
RemoveAndCleanMachines(dirs *define.MachineDirs) error
|
||||
SetProviderAttrs(mc *MachineConfig, opts define.SetOptions) error
|
||||
StartNetworking(mc *MachineConfig, cmd *gvproxy.GvproxyCommand) error
|
||||
PostStartNetworking(mc *MachineConfig) error
|
||||
PostStartNetworking(mc *MachineConfig, noInfo bool) error
|
||||
StartVM(mc *MachineConfig) (func() error, func() error, error)
|
||||
State(mc *MachineConfig, bypass bool) (define.Status, error)
|
||||
StopVM(mc *MachineConfig, hardStop bool) error
|
||||
StopHostNetworking(mc *MachineConfig, vmType define.VMType) error
|
||||
VMType() define.VMType
|
||||
UserModeNetworkEnabled(mc *MachineConfig) bool
|
||||
UseProviderNetworkSetup() bool
|
||||
RequireExclusiveActive() bool
|
||||
}
|
||||
|
||||
// HostUser describes the host user
|
||||
|
@ -1,3 +1,5 @@
|
||||
//go:build windows
|
||||
|
||||
package wsl
|
||||
|
||||
const (
|
||||
|
@ -3,7 +3,6 @@ package wsl
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/sirupsen/logrus"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
@ -13,6 +12,8 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/sirupsen/logrus"
|
||||
|
||||
"github.com/containers/podman/v5/pkg/machine"
|
||||
"github.com/containers/podman/v5/pkg/machine/define"
|
||||
)
|
||||
|
@ -56,10 +56,11 @@ func getConfigPathExt(name string, extension string) (string, error) {
|
||||
// TODO like provisionWSL, i think this needs to be pushed to use common
|
||||
// paths and types where possible
|
||||
func unprovisionWSL(mc *vmconfigs.MachineConfig) error {
|
||||
if err := terminateDist(mc.Name); err != nil {
|
||||
dist := machine.ToDist(mc.Name)
|
||||
if err := terminateDist(dist); err != nil {
|
||||
logrus.Error(err)
|
||||
}
|
||||
if err := unregisterDist(mc.Name); err != nil {
|
||||
if err := unregisterDist(dist); err != nil {
|
||||
logrus.Error(err)
|
||||
}
|
||||
|
||||
@ -87,17 +88,18 @@ func provisionWSLDist(name string, imagePath string, prompt string) (string, err
|
||||
return "", fmt.Errorf("could not create wsldist directory: %w", err)
|
||||
}
|
||||
|
||||
dist := machine.ToDist(name)
|
||||
fmt.Println(prompt)
|
||||
if err = runCmdPassThrough("wsl", "--import", name, distTarget, imagePath, "--version", "2"); err != nil {
|
||||
if err = runCmdPassThrough("wsl", "--import", dist, distTarget, imagePath, "--version", "2"); err != nil {
|
||||
return "", fmt.Errorf("the WSL import of guest OS failed: %w", err)
|
||||
}
|
||||
|
||||
// Fixes newuidmap
|
||||
if err = wslInvoke(name, "rpm", "--restore", "shadow-utils"); err != nil {
|
||||
if err = wslInvoke(dist, "rpm", "--restore", "shadow-utils"); err != nil {
|
||||
return "", fmt.Errorf("package permissions restore of shadow-utils on guest OS failed: %w", err)
|
||||
}
|
||||
|
||||
return name, nil
|
||||
return dist, nil
|
||||
}
|
||||
|
||||
func createKeys(mc *vmconfigs.MachineConfig, dist string) error {
|
||||
@ -139,21 +141,21 @@ func configureSystem(mc *vmconfigs.MachineConfig, dist string) error {
|
||||
return fmt.Errorf("could not configure SSH port for guest OS: %w", err)
|
||||
}
|
||||
|
||||
if err := wslPipe(withUser(configServices, user), mc.Name, "sh"); err != nil {
|
||||
if err := wslPipe(withUser(configServices, user), dist, "sh"); err != nil {
|
||||
return fmt.Errorf("could not configure systemd settings for guest OS: %w", err)
|
||||
}
|
||||
|
||||
if err := wslPipe(sudoers, mc.Name, "sh", "-c", "cat >> /etc/sudoers"); err != nil {
|
||||
if err := wslPipe(sudoers, dist, "sh", "-c", "cat >> /etc/sudoers"); err != nil {
|
||||
return fmt.Errorf("could not add wheel to sudoers: %w", err)
|
||||
}
|
||||
|
||||
if err := wslPipe(overrideSysusers, mc.Name, "sh", "-c",
|
||||
if err := wslPipe(overrideSysusers, dist, "sh", "-c",
|
||||
"cat > /etc/systemd/system/systemd-sysusers.service.d/override.conf"); err != nil {
|
||||
return fmt.Errorf("could not generate systemd-sysusers override for guest OS: %w", err)
|
||||
}
|
||||
|
||||
lingerCmd := withUser("cat > /home/[USER]/.config/systemd/[USER]/linger-example.service", user)
|
||||
if err := wslPipe(lingerService, mc.Name, "sh", "-c", lingerCmd); err != nil {
|
||||
if err := wslPipe(lingerService, dist, "sh", "-c", lingerCmd); err != nil {
|
||||
return fmt.Errorf("could not generate linger service for guest OS: %w", err)
|
||||
}
|
||||
|
||||
@ -714,14 +716,15 @@ func unregisterDist(dist string) error {
|
||||
}
|
||||
|
||||
func isRunning(name string) (bool, error) {
|
||||
wsl, err := isWSLRunning(name)
|
||||
dist := machine.ToDist(name)
|
||||
wsl, err := isWSLRunning(dist)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
sysd := false
|
||||
if wsl {
|
||||
sysd, err = isSystemdRunning(name)
|
||||
sysd, err = isSystemdRunning(dist)
|
||||
|
||||
if err != nil {
|
||||
return false, err
|
||||
@ -746,10 +749,11 @@ func getDiskSize(name string) uint64 {
|
||||
}
|
||||
|
||||
func getCPUs(name string) (uint64, error) {
|
||||
if run, _ := isWSLRunning(name); !run {
|
||||
dist := machine.ToDist(name)
|
||||
if run, _ := isWSLRunning(dist); !run {
|
||||
return 0, nil
|
||||
}
|
||||
cmd := exec.Command("wsl", "-u", "root", "-d", name, "nproc")
|
||||
cmd := exec.Command("wsl", "-u", "root", "-d", dist, "nproc")
|
||||
out, err := cmd.StdoutPipe()
|
||||
if err != nil {
|
||||
return 0, err
|
||||
@ -769,10 +773,11 @@ func getCPUs(name string) (uint64, error) {
|
||||
}
|
||||
|
||||
func getMem(name string) (uint64, error) {
|
||||
if run, _ := isWSLRunning(name); !run {
|
||||
dist := machine.ToDist(name)
|
||||
if run, _ := isWSLRunning(dist); !run {
|
||||
return 0, nil
|
||||
}
|
||||
cmd := exec.Command("wsl", "-u", "root", "-d", name, "cat", "/proc/meminfo")
|
||||
cmd := exec.Command("wsl", "-u", "root", "-d", dist, "cat", "/proc/meminfo")
|
||||
out, err := cmd.StdoutPipe()
|
||||
if err != nil {
|
||||
return 0, err
|
||||
|
@ -120,7 +120,7 @@ func (w WSLStubber) Remove(mc *vmconfigs.MachineConfig) ([]string, func() error,
|
||||
// below if we wanted to hard error on the wsl unregister
|
||||
// of the vm
|
||||
wslRemoveFunc := func() error {
|
||||
if err := runCmdPassThrough("wsl", "--unregister", mc.Name); err != nil {
|
||||
if err := runCmdPassThrough("wsl", "--unregister", machine.ToDist(mc.Name)); err != nil {
|
||||
logrus.Error(err)
|
||||
}
|
||||
return machine.ReleaseMachinePort(mc.SSH.Port)
|
||||
@ -178,24 +178,19 @@ func (w WSLStubber) SetProviderAttrs(mc *vmconfigs.MachineConfig, opts define.Se
|
||||
return errors.New("changing disk size not supported for WSL machines")
|
||||
}
|
||||
|
||||
// TODO This needs to be plumbed in for set as well
|
||||
//if opts.UserModeNetworking != nil && *opts.UserModeNetworking != v.UserModeNetworking {
|
||||
// update := true
|
||||
//
|
||||
// if v.isRunning() {
|
||||
// update = false
|
||||
// setErrors = append(setErrors, fmt.Errorf("user-mode networking can only be changed when the machine is not running"))
|
||||
// } else {
|
||||
// dist := toDist(v.Name)
|
||||
// if err := changeDistUserModeNetworking(dist, v.RemoteUsername, v.ImagePath, *opts.UserModeNetworking); err != nil {
|
||||
// update = false
|
||||
// setErrors = append(setErrors, err)
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// if update {
|
||||
// v.UserModeNetworking = *opts.UserModeNetworking
|
||||
// }
|
||||
if opts.UserModeNetworking != nil && mc.WSLHypervisor.UserModeNetworking != *opts.UserModeNetworking {
|
||||
if running, _ := isRunning(mc.Name); running {
|
||||
return errors.New("user-mode networking can only be changed when the machine is not running")
|
||||
}
|
||||
|
||||
dist := machine.ToDist(mc.Name)
|
||||
if err := changeDistUserModeNetworking(dist, mc.SSH.RemoteUsername, mc.ImagePath.GetPath(), *opts.UserModeNetworking); err != nil {
|
||||
return fmt.Errorf("failure changing state of user-mode networking setting", err)
|
||||
}
|
||||
|
||||
mc.WSLHypervisor.UserModeNetworking = *opts.UserModeNetworking
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -211,8 +206,15 @@ func (w WSLStubber) UserModeNetworkEnabled(mc *vmconfigs.MachineConfig) bool {
|
||||
return mc.WSLHypervisor.UserModeNetworking
|
||||
}
|
||||
|
||||
func (w WSLStubber) PostStartNetworking(mc *vmconfigs.MachineConfig) error {
|
||||
if mc.WSLHypervisor.UserModeNetworking {
|
||||
func (w WSLStubber) UseProviderNetworkSetup() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func (w WSLStubber) RequireExclusiveActive() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (w WSLStubber) PostStartNetworking(mc *vmconfigs.MachineConfig, noInfo bool) error {
|
||||
winProxyOpts := machine.WinProxyOpts{
|
||||
Name: mc.Name,
|
||||
IdentityPath: mc.SSH.IdentityPath,
|
||||
@ -221,16 +223,17 @@ func (w WSLStubber) PostStartNetworking(mc *vmconfigs.MachineConfig) error {
|
||||
Rootful: mc.HostUser.Rootful,
|
||||
VMType: w.VMType(),
|
||||
}
|
||||
machine.LaunchWinProxy(winProxyOpts, false)
|
||||
}
|
||||
machine.LaunchWinProxy(winProxyOpts, noInfo)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (w WSLStubber) StartVM(mc *vmconfigs.MachineConfig) (func() error, func() error, error) {
|
||||
useProxy := setupWslProxyEnv()
|
||||
dist := machine.ToDist(mc.Name)
|
||||
|
||||
// TODO Quiet is hard set to false: follow up
|
||||
if err := configureProxy(mc.Name, useProxy, false); err != nil {
|
||||
if err := configureProxy(dist, useProxy, false); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
@ -243,25 +246,11 @@ func (w WSLStubber) StartVM(mc *vmconfigs.MachineConfig) (func() error, func() e
|
||||
// }
|
||||
// }
|
||||
|
||||
err := wslInvoke(mc.Name, "/root/bootstrap")
|
||||
err := wslInvoke(dist, "/root/bootstrap")
|
||||
if err != nil {
|
||||
err = fmt.Errorf("the WSL bootstrap script failed: %w", err)
|
||||
}
|
||||
|
||||
// TODO we dont show this for any other provider. perhaps we should ? and if
|
||||
// so, we need to move it up the stack
|
||||
//if !v.Rootful && !opts.NoInfo {
|
||||
// fmt.Printf("\nThis machine is currently configured in rootless mode. If your containers\n")
|
||||
// fmt.Printf("require root permissions (e.g. ports < 1024), or if you run into compatibility\n")
|
||||
// fmt.Printf("issues with non-podman clients, you can switch using the following command: \n")
|
||||
//
|
||||
// suffix := ""
|
||||
// if name != machine.DefaultMachineName {
|
||||
// suffix = " " + name
|
||||
// }
|
||||
// fmt.Printf("\n\tpodman machine set --rootful%s\n\n", suffix)
|
||||
//}
|
||||
|
||||
readyFunc := func() error {
|
||||
return nil
|
||||
}
|
||||
@ -284,11 +273,16 @@ func (w WSLStubber) StopVM(mc *vmconfigs.MachineConfig, hardStop bool) error {
|
||||
var (
|
||||
err error
|
||||
)
|
||||
// by this time, state has been verified to be running and a request
|
||||
// to stop is fair game
|
||||
mc.Lock()
|
||||
defer mc.Unlock()
|
||||
|
||||
// recheck after lock
|
||||
if running, err := isRunning(mc.Name); !running {
|
||||
return err
|
||||
}
|
||||
|
||||
dist := machine.ToDist(mc.Name)
|
||||
|
||||
// Stop user-mode networking if enabled
|
||||
if err := stopUserModeNetworking(mc); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Could not cleanly stop user-mode networking: %s\n", err.Error())
|
||||
@ -298,13 +292,13 @@ func (w WSLStubber) StopVM(mc *vmconfigs.MachineConfig, hardStop bool) error {
|
||||
fmt.Fprintf(os.Stderr, "Could not stop API forwarding service (win-sshproxy.exe): %s\n", err.Error())
|
||||
}
|
||||
|
||||
cmd := exec.Command("wsl", "-u", "root", "-d", mc.Name, "sh")
|
||||
cmd := exec.Command("wsl", "-u", "root", "-d", dist, "sh")
|
||||
cmd.Stdin = strings.NewReader(waitTerm)
|
||||
if err = cmd.Start(); err != nil {
|
||||
return fmt.Errorf("executing wait command: %w", err)
|
||||
}
|
||||
|
||||
exitCmd := exec.Command("wsl", "-u", "root", "-d", mc.Name, "/usr/local/bin/enterns", "systemctl", "exit", "0")
|
||||
exitCmd := exec.Command("wsl", "-u", "root", "-d", dist, "/usr/local/bin/enterns", "systemctl", "exit", "0")
|
||||
if err = exitCmd.Run(); err != nil {
|
||||
return fmt.Errorf("stopping sysd: %w", err)
|
||||
}
|
||||
@ -313,7 +307,7 @@ func (w WSLStubber) StopVM(mc *vmconfigs.MachineConfig, hardStop bool) error {
|
||||
return err
|
||||
}
|
||||
|
||||
return terminateDist(mc.Name)
|
||||
return terminateDist(dist)
|
||||
}
|
||||
|
||||
func (w WSLStubber) StopHostNetworking(mc *vmconfigs.MachineConfig, vmType define.VMType) error {
|
||||
|
@ -99,7 +99,7 @@ func startUserModeNetworking(mc *vmconfigs.MachineConfig) error {
|
||||
}
|
||||
}
|
||||
|
||||
if err := createUserModeResolvConf(mc.Name); err != nil {
|
||||
if err := createUserModeResolvConf(machine.ToDist(mc.Name)); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@ -255,7 +255,7 @@ func addUserModeNetEntry(mc *vmconfigs.MachineConfig) error {
|
||||
return err
|
||||
}
|
||||
|
||||
path := filepath.Join(entriesDir, mc.Name)
|
||||
path := filepath.Join(entriesDir, machine.ToDist(mc.Name))
|
||||
file, err := os.OpenFile(path, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0644)
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not add user-mode networking registration: %w", err)
|
||||
@ -270,7 +270,7 @@ func removeUserModeNetEntry(name string) error {
|
||||
return err
|
||||
}
|
||||
|
||||
path := filepath.Join(entriesDir, name)
|
||||
path := filepath.Join(entriesDir, machine.ToDist(name))
|
||||
return os.Remove(path)
|
||||
}
|
||||
|
||||
|
202
vendor/github.com/containers/winquit/LICENSE
generated
vendored
Normal file
202
vendor/github.com/containers/winquit/LICENSE
generated
vendored
Normal file
@ -0,0 +1,202 @@
|
||||
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
50
vendor/github.com/containers/winquit/pkg/winquit/channels_windows.go
generated
vendored
Normal file
50
vendor/github.com/containers/winquit/pkg/winquit/channels_windows.go
generated
vendored
Normal file
@ -0,0 +1,50 @@
|
||||
package winquit
|
||||
|
||||
import (
|
||||
"os"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
type baseChannelType interface {
|
||||
getKey() any
|
||||
notifyNonBlocking()
|
||||
notifyBlocking()
|
||||
}
|
||||
|
||||
type boolChannelType struct {
|
||||
channel chan bool
|
||||
}
|
||||
|
||||
func (b *boolChannelType) getKey() any {
|
||||
return b.channel
|
||||
}
|
||||
|
||||
func (b *boolChannelType) notifyNonBlocking() {
|
||||
select {
|
||||
case b.channel <- true:
|
||||
default:
|
||||
}
|
||||
}
|
||||
|
||||
func (s *boolChannelType) notifyBlocking() {
|
||||
s.channel <- true
|
||||
}
|
||||
|
||||
type sigChannelType struct {
|
||||
channel chan os.Signal
|
||||
}
|
||||
|
||||
func (s *sigChannelType) getKey() any {
|
||||
return s.channel
|
||||
}
|
||||
|
||||
func (s *sigChannelType) notifyNonBlocking() {
|
||||
select {
|
||||
case s.channel <- syscall.SIGTERM:
|
||||
default:
|
||||
}
|
||||
}
|
||||
|
||||
func (s *sigChannelType) notifyBlocking() {
|
||||
s.channel <- syscall.SIGTERM
|
||||
}
|
31
vendor/github.com/containers/winquit/pkg/winquit/client.go
generated
vendored
Normal file
31
vendor/github.com/containers/winquit/pkg/winquit/client.go
generated
vendored
Normal file
@ -0,0 +1,31 @@
|
||||
package winquit
|
||||
|
||||
import (
|
||||
"time"
|
||||
)
|
||||
|
||||
// RequestQuit sends a Windows quit notification to the specified process id.
|
||||
// Since communication is performed over the Win32 GUI messaging facilities,
|
||||
// console applications may not respond, as they require special handling to do
|
||||
// so. Additionally incorrectly written or buggy GUI applications may not listen
|
||||
// or respond appropriately to the event.
|
||||
//
|
||||
// All applications, console or GUI, which use the notification mechanisms
|
||||
// provided by this package (NotifyOnQuit, SimulateSigTermOnQuit) will react
|
||||
// appropriately to the event sent by RequestQuit.
|
||||
//
|
||||
// Callers must have appropriate security permissions, otherwise an error will
|
||||
// be returned. See the notes in the package documentation for more details.
|
||||
func RequestQuit(pid int) error {
|
||||
return requestQuit(pid)
|
||||
}
|
||||
|
||||
// QuitProcess first sends a Windows quit notification to the specified process id,
|
||||
// and waits, up the amount of time passed in the waitNicely argument, for it to
|
||||
// exit. If the process does not exit in time, it is forcefully terminated.
|
||||
//
|
||||
// Callers must have appropriate security permissions, otherwise an error will
|
||||
// be returned. See the notes in the package documentation for more details.
|
||||
func QuitProcess(pid int, waitNicely time.Duration) error {
|
||||
return quitProcess(pid, waitNicely)
|
||||
}
|
17
vendor/github.com/containers/winquit/pkg/winquit/client_unsupported.go
generated
vendored
Normal file
17
vendor/github.com/containers/winquit/pkg/winquit/client_unsupported.go
generated
vendored
Normal file
@ -0,0 +1,17 @@
|
||||
//go:build !windows
|
||||
// +build !windows
|
||||
|
||||
package winquit
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
)
|
||||
|
||||
func requestQuit(pid int) error {
|
||||
return fmt.Errorf("not implemented on non-Windows")
|
||||
}
|
||||
|
||||
func quitProcess(pid int, waitNicely time.Duration) error {
|
||||
return fmt.Errorf("not implemented on non-Windows")
|
||||
}
|
47
vendor/github.com/containers/winquit/pkg/winquit/client_windows.go
generated
vendored
Normal file
47
vendor/github.com/containers/winquit/pkg/winquit/client_windows.go
generated
vendored
Normal file
@ -0,0 +1,47 @@
|
||||
package winquit
|
||||
|
||||
import (
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/containers/winquit/pkg/winquit/win32"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
func requestQuit(pid int) error {
|
||||
threads, err := win32.GetProcThreads(uint32(pid))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, thread := range threads {
|
||||
logrus.Debugf("Closing windows on thread %d", thread)
|
||||
win32.CloseThreadWindows(uint32(thread))
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func quitProcess(pid int, waitNicely time.Duration) error {
|
||||
_ = RequestQuit(pid)
|
||||
|
||||
proc, err := os.FindProcess(pid)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
done := make(chan bool)
|
||||
|
||||
go func() {
|
||||
proc.Wait()
|
||||
done <- true
|
||||
}()
|
||||
|
||||
select {
|
||||
case <-done:
|
||||
return nil
|
||||
case <-time.After(waitNicely):
|
||||
}
|
||||
|
||||
return proc.Kill()
|
||||
}
|
135
vendor/github.com/containers/winquit/pkg/winquit/doc.go
generated
vendored
Normal file
135
vendor/github.com/containers/winquit/pkg/winquit/doc.go
generated
vendored
Normal file
@ -0,0 +1,135 @@
|
||||
// Package winquit supports graceful shutdown of Windows applications through
|
||||
// the sending and receiving of Windows quit events on Win32 message queues.
|
||||
// This allows golang applications to implement behavior comparable to SIGTERM
|
||||
// signal handling on UNIX derived systems. Additionally, it supports the
|
||||
// graceful shutdown mechanism employed by Windows system tools, such as
|
||||
// taskkill. See the "How it works" section for more details.
|
||||
//
|
||||
// To aid application portability, and provide familiarity, the API follows a
|
||||
// similar convention and approach as the os.signal package. Additionally, the
|
||||
// SimulateSigTermOnQuit function supports reuse of the same underlying channel,
|
||||
// supporting the blending of os.signal and winquit together (a subset of
|
||||
// signals provided by os.signal are still relevant and desirable on Windows,
|
||||
// for example, break handling in console applications).
|
||||
//
|
||||
// # Simple server example
|
||||
//
|
||||
// The following example demonstrates usage of NotifyOnQuit() to wait for a
|
||||
// windows quit event before shutting down:
|
||||
//
|
||||
// func server() {
|
||||
// fmt.Println("Starting server")
|
||||
//
|
||||
// // Create a channel, and register it
|
||||
// done := make(chan bool, 1)
|
||||
// winquit.NotifyOnQuit(done)
|
||||
//
|
||||
// // Wait until we receive a quit event
|
||||
// <-done
|
||||
//
|
||||
// fmt.Println("Shutting down")
|
||||
// // Perform cleanup tasks
|
||||
// }
|
||||
//
|
||||
// # Blended signal example
|
||||
//
|
||||
// The following example demonstrates usage of SimulateSigTermOnQuit() in
|
||||
// concert with signal.Notify():
|
||||
//
|
||||
// func server() {
|
||||
// fmt.Println("Starting server")
|
||||
//
|
||||
// // Create a channel, and register it
|
||||
// done := make(chan os.Signal, 1)
|
||||
//
|
||||
// // Wait on console interrupt events
|
||||
// signal.Notify(done, syscall.SIGINT)
|
||||
//
|
||||
// // Simulate SIGTERM when a quit occurs
|
||||
// winquit.SimulateSigTermOnQuit(done)
|
||||
//
|
||||
// // Wait until we receive a signal or quit event
|
||||
// <-done
|
||||
//
|
||||
// fmt.Println("Shutting down")
|
||||
// // Perform cleanup tasks
|
||||
// }
|
||||
//
|
||||
// # Client example
|
||||
//
|
||||
// The following example demonstrates how an application can ask or
|
||||
// force other windows programs to quit:
|
||||
//
|
||||
// func client() {
|
||||
// // Ask nicely for program "one" to quit. This request may not
|
||||
// // be honored if its a console application, or if the program
|
||||
// // is hung
|
||||
// if err := winquit.RequestQuit(pidOne); err != nil {
|
||||
// fmt.Printf("error sending quit request, %s", err.Error())
|
||||
// }
|
||||
//
|
||||
// // Force program "two" to quit, but give it 20 seconds to
|
||||
// // perform any cleanup tasks and quit on it's own
|
||||
// timeout := time.Second * 20
|
||||
// if err := winquit.QuitProcess(pidTwo, timeout); err != nil {
|
||||
// fmt.Printf("error killing process, %s", err.Error())
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// # How it works
|
||||
//
|
||||
// Windows GUI applications consist of multiple components (and windows) which
|
||||
// intercommunicate with events over per-thread message queues and/or direct
|
||||
// event handoff to window procedures for cross-thread communication.
|
||||
// Additionally, GUI applications can use the same mechanism to communicate with
|
||||
// windows and threads owned by other applications, including common desktop
|
||||
// components.
|
||||
//
|
||||
// winquit utilizes this mechanism by creating a standard win32 message loop
|
||||
// thread and registering a non-visible window to relay a quit message (WM_QUIT)
|
||||
// in the event of a window close event. WM_CLOSE is sent by Windows in response
|
||||
// to certain system events, or by other requesting applications. For example,
|
||||
// the system provided taskkill.exe (similar to the kill command on Unix), works
|
||||
// by iterating all windows on the system, and sending a WM_CLOSE when the
|
||||
// process owner matches the specified pid. Note that, unlike UNIX/X11 style
|
||||
// systems, on Windows the graphical APIs are built-in and accessible to all
|
||||
// win32 applications, including console based applications. Therefore, the APIs
|
||||
// provided by winquit *do not* require compilation as a windowsgui app to
|
||||
// effectively use them.
|
||||
//
|
||||
// winquit also provides APIs to trigger a quit of another process using a
|
||||
// WM_CLOSE event, although in a more efficient manner than taskkill.exe. It
|
||||
// instead captures a thread snapshot of the target process (effectively a
|
||||
// memory read on Windows), and enumerates each thread's associated Windows, and
|
||||
// sending the event to each. In addition to supporting a graceful close of any
|
||||
// Windows application, which may have multiple message loops, this approach
|
||||
// also obviates the need for cumbersome approaches to lock code to the main
|
||||
// thread of the application. The message loop used by winquit does not care
|
||||
// which thread the golang runtime internally designates. Note that winquit
|
||||
// purposefully relays through a thread's windows as opposed to posting directly
|
||||
// to each thread's message queue, since the former is more likely to be
|
||||
// expected by an application, and it ensures all window procedures have an
|
||||
// opportunity to perform cleanup work not associated with the thread's message
|
||||
// loop.
|
||||
//
|
||||
// # Limitations
|
||||
//
|
||||
// This API is only implemented on Windows platforms. Non-operational stubs
|
||||
// are provided for compilation purposes.
|
||||
//
|
||||
// In addition to requiring appropriate security permissions (typically a user
|
||||
// can only send events to other applications ran by the same user), Windows
|
||||
// also restricts inter-app messaging operations to programs running in the same
|
||||
// user logon session. While logons migrate between RDP and console sessions,
|
||||
// non-graphical logins (e.g sshd) typically create a logon per connection. For
|
||||
// this reason, tools like taskkill and winquit are normally disallowed from
|
||||
// crossing this boundary. Therefore, a user will not be able to gracefully stop
|
||||
// applications between ssh/winrm sessions, and in between ssh and graphical
|
||||
// logons. However, the typical user use-case of logging into Windows and
|
||||
// running multiple applications and terminals will work fine. Additionally,
|
||||
// multiple back-grounded processes in the same ssh session will be able to
|
||||
// communicate. Finally, it's possible to bypass this limitation by executing
|
||||
// code under the system user using the SeTcbPrivilege. The psexec tool does
|
||||
// exactly this, and can additionally be used as a workaround to this
|
||||
// limitation.
|
||||
package winquit
|
45
vendor/github.com/containers/winquit/pkg/winquit/server.go
generated
vendored
Normal file
45
vendor/github.com/containers/winquit/pkg/winquit/server.go
generated
vendored
Normal file
@ -0,0 +1,45 @@
|
||||
package winquit
|
||||
|
||||
import (
|
||||
"os"
|
||||
)
|
||||
|
||||
// NotifyOnQuit relays a Windows quit notification to the boolean done channel.
|
||||
// This is a one-shot operation (will only be delivered once), however multiple
|
||||
// channels may be registered. Each registered channel is sent one copy of the
|
||||
// same one-shot value.
|
||||
//
|
||||
// This function is a no-op on non-Windows platforms. While the call will
|
||||
// succeed, no notifications will be delivered to the passed channel. Each
|
||||
// channel will only ever receive a "true" value.
|
||||
//
|
||||
// It is recommended that registered channels establish a buffer of 1, since
|
||||
// values are sent non-blocking. Blocking redelivery may be attempted to reduce
|
||||
// the chance of bugs; however, it should not be relied upon.
|
||||
//
|
||||
// If this function is called after a Windows quit notification has occurred, it
|
||||
// will immediately deliver a "true" value.
|
||||
func NotifyOnQuit(done chan bool) {
|
||||
notifyOnQuit(done)
|
||||
}
|
||||
|
||||
// SimulateSigTermOnQuit relays a Windows quit notification following the same
|
||||
// semantics as NotifyOnQuit; however, instead of a boolean message value, this
|
||||
// function will send a SIGTERM signal to the passed channel.
|
||||
//
|
||||
// This function allows for the reuse of the same underlying channel used with
|
||||
// in a separate os.signal.Notify method call.
|
||||
func SimulateSigTermOnQuit(handler chan os.Signal) {
|
||||
simulateSigTermOnQuit(handler)
|
||||
}
|
||||
|
||||
// Returns the thread id of the message loop thread created by winquit, or "0"
|
||||
// if one is not running. The latter indicates a mistake, as this function
|
||||
// should only be called after a call to one of the _OnQuit functions.
|
||||
//
|
||||
// In most cases this method should not be necessary, as RequestQuit and
|
||||
// QuitProcess do not require it. It is primarily provided to enable legacy
|
||||
// patterns that serialize the thread id for later direct signaling.
|
||||
func GetCurrentMessageLoopThreadId() uint32 {
|
||||
return getCurrentMessageLoopThreadId()
|
||||
}
|
18
vendor/github.com/containers/winquit/pkg/winquit/server_unsupported.go
generated
vendored
Normal file
18
vendor/github.com/containers/winquit/pkg/winquit/server_unsupported.go
generated
vendored
Normal file
@ -0,0 +1,18 @@
|
||||
//go:build !windows
|
||||
// +build !windows
|
||||
|
||||
package winquit
|
||||
|
||||
import (
|
||||
"os"
|
||||
)
|
||||
|
||||
func notifyOnQuit(done chan bool) {
|
||||
}
|
||||
|
||||
func simulateSigTermOnQuit(handler chan os.Signal) {
|
||||
}
|
||||
|
||||
func getCurrentMessageLoopThreadId() uint32 {
|
||||
return 0
|
||||
}
|
147
vendor/github.com/containers/winquit/pkg/winquit/server_windows.go
generated
vendored
Normal file
147
vendor/github.com/containers/winquit/pkg/winquit/server_windows.go
generated
vendored
Normal file
@ -0,0 +1,147 @@
|
||||
package winquit
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strings"
|
||||
"sync"
|
||||
"syscall"
|
||||
|
||||
"github.com/containers/winquit/pkg/winquit/win32"
|
||||
"github.com/sirupsen/logrus"
|
||||
"golang.org/x/sys/windows"
|
||||
)
|
||||
|
||||
type receiversType struct {
|
||||
sync.Mutex
|
||||
|
||||
result bool
|
||||
channels map[any]baseChannelType
|
||||
}
|
||||
|
||||
var (
|
||||
receivers *receiversType = &receiversType{
|
||||
channels: make(map[any]baseChannelType),
|
||||
}
|
||||
|
||||
loopInit sync.Once
|
||||
loopTid uint32
|
||||
)
|
||||
|
||||
func (r *receiversType) add(channel baseChannelType) {
|
||||
r.Lock()
|
||||
defer r.Unlock()
|
||||
|
||||
if _, ok := r.channels[channel.getKey()]; ok {
|
||||
return
|
||||
}
|
||||
|
||||
if r.result {
|
||||
go func() {
|
||||
channel.notifyBlocking()
|
||||
}()
|
||||
return
|
||||
}
|
||||
|
||||
r.channels[channel.getKey()] = channel
|
||||
}
|
||||
|
||||
func (r *receiversType) notifyAll() {
|
||||
r.Lock()
|
||||
defer r.Unlock()
|
||||
r.result = true
|
||||
for _, channel := range r.channels {
|
||||
channel.notifyNonBlocking()
|
||||
delete(r.channels, channel.getKey())
|
||||
}
|
||||
for _, channel := range r.channels {
|
||||
channel.notifyBlocking()
|
||||
delete(r.channels, channel)
|
||||
}
|
||||
}
|
||||
|
||||
func initLoop() {
|
||||
loopInit.Do(func() {
|
||||
go messageLoop()
|
||||
})
|
||||
}
|
||||
|
||||
func notifyOnQuit(done chan bool) {
|
||||
receivers.add(&boolChannelType{done})
|
||||
initLoop()
|
||||
}
|
||||
|
||||
func simulateSigTermOnQuit(handler chan os.Signal) {
|
||||
receivers.add(&sigChannelType{handler})
|
||||
initLoop()
|
||||
}
|
||||
|
||||
func getCurrentMessageLoopThreadId() uint32 {
|
||||
return loopTid
|
||||
}
|
||||
|
||||
func messageLoop() {
|
||||
runtime.LockOSThread()
|
||||
defer runtime.UnlockOSThread()
|
||||
|
||||
loopTid = windows.GetCurrentThreadId()
|
||||
registerDummyWindow()
|
||||
|
||||
logrus.Debug("Entering loop for quit")
|
||||
for {
|
||||
ret, msg, err := win32.GetMessage(0, 0, 0)
|
||||
if err != nil {
|
||||
logrus.Debugf("Error receiving win32 message, %s", err.Error())
|
||||
continue
|
||||
}
|
||||
if ret == 0 {
|
||||
logrus.Debug("Received QUIT notification")
|
||||
receivers.notifyAll()
|
||||
|
||||
return
|
||||
}
|
||||
logrus.Debugf("Unhandled message: %d", msg.Message)
|
||||
win32.TranslateMessage(msg)
|
||||
win32.DispatchMessage(msg)
|
||||
}
|
||||
}
|
||||
|
||||
func getAppName() (string, error) {
|
||||
exeName, err := os.Executable()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
suffix := filepath.Ext(exeName)
|
||||
return strings.TrimSuffix(filepath.Base(exeName), suffix), nil
|
||||
}
|
||||
|
||||
func registerDummyWindow() error {
|
||||
var app syscall.Handle
|
||||
var err error
|
||||
|
||||
app, err = win32.GetModuleHandle("")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
appName, err := getAppName()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
className := appName + "-rclass"
|
||||
winName := appName + "-root"
|
||||
|
||||
_, err = win32.RegisterDummyWinClass(className, app)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = win32.CreateDummyWindow(winName, className, app)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
17
vendor/github.com/containers/winquit/pkg/winquit/win32/common.go
generated
vendored
Normal file
17
vendor/github.com/containers/winquit/pkg/winquit/win32/common.go
generated
vendored
Normal file
@ -0,0 +1,17 @@
|
||||
//go:build windows
|
||||
// +build windows
|
||||
|
||||
package win32
|
||||
|
||||
import (
|
||||
"syscall"
|
||||
)
|
||||
|
||||
const (
|
||||
ERROR_NO_MORE_ITEMS = 259
|
||||
)
|
||||
|
||||
var (
|
||||
kernel32 = syscall.NewLazyDLL("kernel32.dll")
|
||||
user32 = syscall.NewLazyDLL("user32.dll")
|
||||
)
|
4
vendor/github.com/containers/winquit/pkg/winquit/win32/common_unsupported.go
generated
vendored
Normal file
4
vendor/github.com/containers/winquit/pkg/winquit/win32/common_unsupported.go
generated
vendored
Normal file
@ -0,0 +1,4 @@
|
||||
//go:build !windows
|
||||
// +build !windows
|
||||
|
||||
package win32
|
87
vendor/github.com/containers/winquit/pkg/winquit/win32/msg.go
generated
vendored
Normal file
87
vendor/github.com/containers/winquit/pkg/winquit/win32/msg.go
generated
vendored
Normal file
@ -0,0 +1,87 @@
|
||||
//go:build windows
|
||||
// +build windows
|
||||
|
||||
package win32
|
||||
|
||||
import (
|
||||
"syscall"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
type MSG struct {
|
||||
HWnd uintptr
|
||||
Message uint32
|
||||
WParam uintptr
|
||||
LParam uintptr
|
||||
Time uint32
|
||||
Pt struct{ X, Y int32 }
|
||||
}
|
||||
|
||||
const (
|
||||
WM_QUIT = 0x12
|
||||
WM_DESTROY = 0x02
|
||||
WM_CLOSE = 0x10
|
||||
)
|
||||
|
||||
var (
|
||||
postQuitMessage = user32.NewProc("PostQuitMessage")
|
||||
procGetMessage = user32.NewProc("GetMessageW")
|
||||
procTranslateMessage = user32.NewProc("TranslateMessage")
|
||||
procDispatchMessage = user32.NewProc("DispatchMessageW")
|
||||
procSendMessage = user32.NewProc("SendMessageW")
|
||||
)
|
||||
|
||||
func TranslateMessage(msg *MSG) bool {
|
||||
ret, _, _ :=
|
||||
procTranslateMessage.Call( // BOOL TranslateMessage()
|
||||
uintptr(unsafe.Pointer(msg)), // [in] const MSG *lpMsg
|
||||
)
|
||||
|
||||
return ret != 0
|
||||
|
||||
}
|
||||
|
||||
func DispatchMessage(msg *MSG) uintptr {
|
||||
ret, _, _ :=
|
||||
procDispatchMessage.Call( // LRESULT DispatchMessage()
|
||||
uintptr(unsafe.Pointer(msg)), // [in] const MSG *lpMsg
|
||||
)
|
||||
|
||||
return ret
|
||||
}
|
||||
|
||||
func SendMessage(handle syscall.Handle, message uint, wparm uintptr, lparam uintptr) uintptr {
|
||||
ret, _, _ :=
|
||||
procSendMessage.Call( // LRESULT SendMessage()
|
||||
uintptr(handle), // [in] HWND hWnd
|
||||
uintptr(message), // [in] UINT Msg
|
||||
wparm, // [in] WPARAM wParam
|
||||
lparam, // [in] LPARAM lParam
|
||||
)
|
||||
|
||||
return ret
|
||||
}
|
||||
|
||||
func PostQuitMessage(code int) {
|
||||
_, _, _ =
|
||||
postQuitMessage.Call( // void PostQuitMessage()
|
||||
uintptr(code), // [in] int nExitCode
|
||||
)
|
||||
}
|
||||
|
||||
func GetMessage(handle syscall.Handle, int, max int) (int32, *MSG, error) {
|
||||
var msg MSG
|
||||
ret, _, err :=
|
||||
procGetMessage.Call( // // BOOL GetMessage()
|
||||
uintptr(unsafe.Pointer(&msg)), // [out] LPMSG lpMsg,
|
||||
uintptr(handle), // [in, optional] HWND hWnd,
|
||||
0, // [in] UINT wMsgFilterMin,
|
||||
0, // [in] UINT wMsgFilterMax
|
||||
)
|
||||
|
||||
if int32(ret) == -1 {
|
||||
return 0, nil, err
|
||||
}
|
||||
|
||||
return int32(ret), &msg, nil
|
||||
}
|
59
vendor/github.com/containers/winquit/pkg/winquit/win32/proc.go
generated
vendored
Normal file
59
vendor/github.com/containers/winquit/pkg/winquit/win32/proc.go
generated
vendored
Normal file
@ -0,0 +1,59 @@
|
||||
//go:build windows
|
||||
// +build windows
|
||||
|
||||
package win32
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
const (
|
||||
MAXIMUM_ALLOWED = 0x02000000
|
||||
)
|
||||
|
||||
var (
|
||||
procOpenProcess = kernel32.NewProc("OpenProcess")
|
||||
procCloseHandle = kernel32.NewProc("CloseHandle")
|
||||
procGetModuleHandle = kernel32.NewProc("GetModuleHandleW")
|
||||
)
|
||||
|
||||
func OpenProcess(pid uint32) (syscall.Handle, error) {
|
||||
ret, _, err :=
|
||||
procOpenProcess.Call( // HANDLE OpenProcess()
|
||||
MAXIMUM_ALLOWED, // [in] DWORD dwDesiredAccess,
|
||||
0, // [in] BOOL bInheritHandle,
|
||||
uintptr(pid), // [in] DWORD dwProcessId
|
||||
)
|
||||
|
||||
if ret == 0 {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
return syscall.Handle(ret), nil
|
||||
}
|
||||
|
||||
func CloseHandle(handle syscall.Handle) error {
|
||||
ret, _, err :=
|
||||
procCloseHandle.Call( // BOOL CloseHandle()
|
||||
uintptr(handle), // [in] HANDLE hObject
|
||||
)
|
||||
if ret != 0 {
|
||||
return fmt.Errorf("error closing handle: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func GetProcThreads(pid uint32) ([]uint, error) {
|
||||
process, err := OpenProcess(pid)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
defer func() {
|
||||
_ = CloseHandle(process)
|
||||
}()
|
||||
|
||||
return GetProcThreadIds(process)
|
||||
}
|
160
vendor/github.com/containers/winquit/pkg/winquit/win32/pss.go
generated
vendored
Normal file
160
vendor/github.com/containers/winquit/pkg/winquit/win32/pss.go
generated
vendored
Normal file
@ -0,0 +1,160 @@
|
||||
//go:build windows
|
||||
// +build windows
|
||||
|
||||
package win32
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"syscall"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
type PSS_THREAD_ENTRY struct {
|
||||
ExitStatus uint32
|
||||
TebBaseAddress uintptr
|
||||
ProcessId uint32
|
||||
ThreadId uint32
|
||||
AffinityMask uintptr
|
||||
Priority int32
|
||||
BasePriority int32
|
||||
LastSyscallFirstArgument uintptr
|
||||
LastSyscallNumber uint16
|
||||
CreateTime uint64
|
||||
ExitTime uint64
|
||||
KernelTime uint64
|
||||
UserTime uint64
|
||||
Win32StartAddress uintptr
|
||||
CaptureTime uint64
|
||||
Flags uint32
|
||||
SuspendCount uint16
|
||||
SizeOfContextRecord uint16
|
||||
ContextRecord uintptr
|
||||
}
|
||||
|
||||
const (
|
||||
PSS_CAPTURE_THREADS = 0x00000080
|
||||
PSS_WALK_THREADS = 3
|
||||
PSS_QUERY_THREAD_INFORMATION = 5
|
||||
)
|
||||
|
||||
var (
|
||||
procPssCaptureSnapshot = kernel32.NewProc("PssCaptureSnapshot")
|
||||
procPssFreeSnapshot = kernel32.NewProc("PssFreeSnapshot")
|
||||
procPssWalkMarkerCreate = kernel32.NewProc("PssWalkMarkerCreate")
|
||||
procPssWalkMarkerFree = kernel32.NewProc("PssWalkMarkerFree")
|
||||
procPssWalkSnapshot = kernel32.NewProc("PssWalkSnapshot")
|
||||
)
|
||||
|
||||
func PssCaptureSnapshot(process syscall.Handle, flags int32, contextFlags int32) (syscall.Handle, error) {
|
||||
var snapshot syscall.Handle
|
||||
ret, _, err :=
|
||||
procPssCaptureSnapshot.Call( // DWORD PssCaptureSnapshot()
|
||||
uintptr(process), // [in] HANDLE ProcessHandle,
|
||||
uintptr(flags), // [in] PSS_CAPTURE_FLAGS CaptureFlags,
|
||||
uintptr(contextFlags), // [in, optional] DWORD ThreadContextFlags,
|
||||
uintptr(unsafe.Pointer(&snapshot)), // [out] HPSS *SnapshotHandle
|
||||
)
|
||||
|
||||
if ret != 0 {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
return snapshot, nil
|
||||
}
|
||||
|
||||
func PssFreeSnapshot(process syscall.Handle, snapshot syscall.Handle) error {
|
||||
ret, _, _ :=
|
||||
procPssFreeSnapshot.Call( // DWORD PssFreeSnapshot()
|
||||
uintptr(process), // [in] HANDLE ProcessHandle,
|
||||
uintptr(snapshot), // [in] HPSS SnapshotHandle
|
||||
)
|
||||
if ret != 0 {
|
||||
return fmt.Errorf("error freeing snapshot: %d", ret)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func PssWalkMarkerCreate() (syscall.Handle, error) {
|
||||
var walkptr uintptr
|
||||
|
||||
ret, _, _ :=
|
||||
procPssWalkMarkerCreate.Call( // // DWORD PssWalkMarkerCreate()
|
||||
0, // [in, optional] PSS_ALLOCATOR const *Allocator
|
||||
uintptr(unsafe.Pointer(&walkptr)), // [out] HPSSWALK *WalkMarkerHandle
|
||||
)
|
||||
if ret != 0 {
|
||||
return 0, fmt.Errorf("error creating process walker mark: %d", ret)
|
||||
}
|
||||
|
||||
return syscall.Handle(walkptr), nil
|
||||
}
|
||||
|
||||
func PssWalkMarkerFree(handle syscall.Handle) error {
|
||||
ret, _, _ :=
|
||||
procPssWalkMarkerFree.Call( // DWORD PssWalkMarkerFree()
|
||||
uintptr(handle), // [in] HPSSWALK WalkMarkerHandle
|
||||
)
|
||||
if ret != 0 {
|
||||
return fmt.Errorf("error freeing process walker mark: %d", ret)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func PssWalkThreadSnapshot(snapshot syscall.Handle, marker syscall.Handle) (*PSS_THREAD_ENTRY, error) {
|
||||
var thread PSS_THREAD_ENTRY
|
||||
ret, _, err :=
|
||||
procPssWalkSnapshot.Call( // // DWORD PssWalkSnapshot()
|
||||
uintptr(snapshot), // [in] HPSS SnapshotHandle,
|
||||
PSS_WALK_THREADS, // [in] PSS_WALK_INFORMATION_CLASS InformationClass,
|
||||
uintptr(marker), // [in] HPSSWALK WalkMarkerHandle,
|
||||
uintptr(unsafe.Pointer(&thread)), // [out] void *Buffer,
|
||||
unsafe.Sizeof(thread), // [in] DWORD BufferLength
|
||||
)
|
||||
|
||||
if ret == ERROR_NO_MORE_ITEMS {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
if ret != 0 {
|
||||
return nil, fmt.Errorf("error waling thread snapshot: %d (%w)", ret, err)
|
||||
}
|
||||
|
||||
return &thread, nil
|
||||
}
|
||||
|
||||
func GetProcThreadIds(process syscall.Handle) ([]uint, error) {
|
||||
snapshot, err := PssCaptureSnapshot(process, PSS_CAPTURE_THREADS, 0)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer func() {
|
||||
_ = PssFreeSnapshot(process, snapshot)
|
||||
}()
|
||||
|
||||
marker, err := PssWalkMarkerCreate()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
defer func() {
|
||||
_ = PssWalkMarkerFree(marker)
|
||||
}()
|
||||
|
||||
var threads []uint
|
||||
|
||||
for {
|
||||
thread, err := PssWalkThreadSnapshot(snapshot, marker)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if thread == nil {
|
||||
break
|
||||
}
|
||||
|
||||
threads = append(threads, uint(thread.ThreadId))
|
||||
}
|
||||
|
||||
return threads, nil
|
||||
}
|
162
vendor/github.com/containers/winquit/pkg/winquit/win32/win.go
generated
vendored
Normal file
162
vendor/github.com/containers/winquit/pkg/winquit/win32/win.go
generated
vendored
Normal file
@ -0,0 +1,162 @@
|
||||
//go:build windows
|
||||
// +build windows
|
||||
|
||||
package win32
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"syscall"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
type WNDCLASSEX struct {
|
||||
cbSize uint32
|
||||
style uint32
|
||||
lpfnWndProc uintptr
|
||||
cbClsExtra int32
|
||||
cbWndExtra int32
|
||||
hInstance syscall.Handle
|
||||
hIcon syscall.Handle
|
||||
hCursor syscall.Handle
|
||||
hbrBackground syscall.Handle
|
||||
menuName *uint16
|
||||
className *uint16
|
||||
hIconSm syscall.Handle
|
||||
}
|
||||
|
||||
const (
|
||||
COLOR_WINDOW = 0x05
|
||||
CW_USEDEFAULT = ^0x7fffffff
|
||||
)
|
||||
|
||||
var (
|
||||
procEnumThreadWindows = user32.NewProc("EnumThreadWindows")
|
||||
procRegisterClassEx = user32.NewProc("RegisterClassExW")
|
||||
procCreateWindowEx = user32.NewProc("CreateWindowExW")
|
||||
procDefWinProc = user32.NewProc("DefWindowProcW")
|
||||
|
||||
callbackEnumThreadWindows = syscall.NewCallback(wndProcCloseWindow)
|
||||
)
|
||||
|
||||
func DefWindowProc(hWnd syscall.Handle, msg uint32, wParam uintptr, lParam uintptr) int32 {
|
||||
|
||||
ret, _, _ :=
|
||||
procDefWinProc.Call( // LRESULT DefWindowProcW()
|
||||
uintptr(hWnd), // [in] HWND hWnd,
|
||||
uintptr(msg), // [in] UINT Msg,
|
||||
wParam, // [in] WPARAM wParam,
|
||||
lParam, // [in] LPARAM lParam
|
||||
)
|
||||
return int32(ret)
|
||||
}
|
||||
|
||||
func GetModuleHandle(name string) (syscall.Handle, error) {
|
||||
var name16 *uint16
|
||||
var err error
|
||||
|
||||
if len(name) > 0 {
|
||||
if name16, err = syscall.UTF16PtrFromString(name); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
}
|
||||
|
||||
ret, _, err :=
|
||||
procGetModuleHandle.Call( // HMODULE GetModuleHandleW()
|
||||
uintptr(unsafe.Pointer(name16)), // [in, optional] LPCWSTR lpModuleName
|
||||
)
|
||||
if ret == 0 {
|
||||
return 0, fmt.Errorf("could not obtain module handle: %w", err)
|
||||
}
|
||||
|
||||
return syscall.Handle(ret), nil
|
||||
}
|
||||
|
||||
func RegisterClassEx(class *WNDCLASSEX) (uint16, error) {
|
||||
|
||||
ret, _, err :=
|
||||
procRegisterClassEx.Call( // ATOM RegisterClassExW()
|
||||
uintptr(unsafe.Pointer(class)), // [in] const WNDCLASSEXW *unnamedParam1
|
||||
)
|
||||
if ret == 0 {
|
||||
return 0, fmt.Errorf("could not register window class: %w", err)
|
||||
}
|
||||
|
||||
return uint16(ret), nil
|
||||
}
|
||||
|
||||
func wndProc(hWnd syscall.Handle, msg uint32, wParam uintptr, lParam uintptr) uintptr {
|
||||
switch msg {
|
||||
case WM_DESTROY:
|
||||
PostQuitMessage(0)
|
||||
return 0
|
||||
default:
|
||||
return uintptr(DefWindowProc(hWnd, msg, wParam, lParam))
|
||||
}
|
||||
}
|
||||
|
||||
func CloseThreadWindows(threadId uint32) {
|
||||
_, _, _ =
|
||||
procEnumThreadWindows.Call( // // BOOL EnumThreadWindows()
|
||||
uintptr(threadId), // [in] DWORD dwThreadId,
|
||||
callbackEnumThreadWindows, // [in] WNDENUMPROC lpfn,
|
||||
0, // [in] LPARAM lParam
|
||||
)
|
||||
}
|
||||
|
||||
func wndProcCloseWindow(hwnd uintptr, lparam uintptr) uintptr {
|
||||
SendMessage(syscall.Handle(hwnd), WM_CLOSE, 0, 0)
|
||||
|
||||
return 1
|
||||
}
|
||||
|
||||
func RegisterDummyWinClass(name string, appInstance syscall.Handle) (uint16, error) {
|
||||
var class16 *uint16
|
||||
var err error
|
||||
if class16, err = syscall.UTF16PtrFromString(name); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
class := WNDCLASSEX{
|
||||
className: class16,
|
||||
hInstance: appInstance,
|
||||
lpfnWndProc: syscall.NewCallback(wndProc),
|
||||
}
|
||||
|
||||
class.cbSize = uint32(unsafe.Sizeof(class))
|
||||
|
||||
return RegisterClassEx(&class)
|
||||
}
|
||||
|
||||
func CreateDummyWindow(name string, className string, appInstance syscall.Handle) (syscall.Handle, error) {
|
||||
var name16, class16 *uint16
|
||||
var err error
|
||||
|
||||
cwDefault := CW_USEDEFAULT
|
||||
|
||||
if name16, err = syscall.UTF16PtrFromString(name); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if class16, err = syscall.UTF16PtrFromString(className); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
ret, _, err := procCreateWindowEx.Call( //HWND CreateWindowExW(
|
||||
0, // [in] DWORD dwExStyle,
|
||||
uintptr(unsafe.Pointer(class16)), // [in, optional] LPCWSTR lpClassName,
|
||||
uintptr(unsafe.Pointer(name16)), // [in, optional] LPCWSTR lpWindowName,
|
||||
0, // [in] DWORD dwStyle,
|
||||
uintptr(cwDefault), // [in] int X,
|
||||
uintptr(cwDefault), // [in] int Y,
|
||||
0, // [in] int nWidth,
|
||||
0, // [in] int nHeight,
|
||||
0, // [in, optional] HWND hWndParent,
|
||||
0, // [in, optional] HMENU hMenu,
|
||||
uintptr(appInstance), // [in, optional] HINSTANCE hInstance,
|
||||
0, // [in, optional] LPVOID lpParam
|
||||
)
|
||||
|
||||
if ret == 0 {
|
||||
return 0, fmt.Errorf("could not create window: %w", err)
|
||||
}
|
||||
|
||||
return syscall.Handle(ret), nil
|
||||
}
|
4
vendor/modules.txt
vendored
4
vendor/modules.txt
vendored
@ -401,6 +401,10 @@ github.com/containers/storage/pkg/tarlog
|
||||
github.com/containers/storage/pkg/truncindex
|
||||
github.com/containers/storage/pkg/unshare
|
||||
github.com/containers/storage/types
|
||||
# github.com/containers/winquit v1.1.0
|
||||
## explicit; go 1.19
|
||||
github.com/containers/winquit/pkg/winquit
|
||||
github.com/containers/winquit/pkg/winquit/win32
|
||||
# github.com/coreos/go-oidc/v3 v3.9.0
|
||||
## explicit; go 1.19
|
||||
github.com/coreos/go-oidc/v3/oidc
|
||||
|
Reference in New Issue
Block a user