diff options
Diffstat (limited to 'cmd')
-rw-r--r-- | cmd/podman/common/create.go | 17 | ||||
-rw-r--r-- | cmd/podman/common/create_opts.go | 7 | ||||
-rw-r--r-- | cmd/podman/common/netflags.go | 53 | ||||
-rw-r--r-- | cmd/podman/containers/checkpoint.go | 4 | ||||
-rw-r--r-- | cmd/podman/containers/create.go | 5 | ||||
-rw-r--r-- | cmd/podman/containers/run.go | 4 | ||||
-rw-r--r-- | cmd/podman/machine/init.go | 48 | ||||
-rw-r--r-- | cmd/podman/machine/list.go | 23 | ||||
-rw-r--r-- | cmd/podman/machine/machine.go | 6 | ||||
-rw-r--r-- | cmd/podman/machine/machine_unsupported.go | 2 | ||||
-rw-r--r-- | cmd/podman/machine/platform.go | 12 | ||||
-rw-r--r-- | cmd/podman/machine/platform_windows.go | 10 | ||||
-rw-r--r-- | cmd/podman/machine/rm.go | 15 | ||||
-rw-r--r-- | cmd/podman/machine/ssh.go | 29 | ||||
-rw-r--r-- | cmd/podman/machine/start.go | 22 | ||||
-rw-r--r-- | cmd/podman/machine/stop.go | 15 | ||||
-rw-r--r-- | cmd/winpath/main.go | 184 |
17 files changed, 348 insertions, 108 deletions
diff --git a/cmd/podman/common/create.go b/cmd/podman/common/create.go index dad79348d..f02c5713b 100644 --- a/cmd/podman/common/create.go +++ b/cmd/podman/common/create.go @@ -292,6 +292,14 @@ func DefineCreateFlags(cmd *cobra.Command, cf *entities.ContainerCreateOptions, "Set proxy environment variables in the container based on the host proxy vars", ) + hostUserFlagName := "hostuser" + createFlags.StringSliceVar( + &cf.HostUsers, + hostUserFlagName, []string{}, + "Host user account to add to /etc/passwd within container", + ) + _ = cmd.RegisterFlagCompletionFunc(hostUserFlagName, completion.AutocompleteNone) + imageVolumeFlagName := "image-volume" createFlags.StringVar( &cf.ImageVolume, @@ -327,13 +335,10 @@ func DefineCreateFlags(cmd *cobra.Command, cf *entities.ContainerCreateOptions, ) _ = cmd.RegisterFlagCompletionFunc(ipcFlagName, AutocompleteNamespace) - kernelMemoryFlagName := "kernel-memory" - createFlags.StringVar( - &cf.KernelMemory, - kernelMemoryFlagName, "", - "Kernel memory limit "+sizeWithUnitFormat, + createFlags.String( + "kernel-memory", "", + "DEPRECATED: Option is just hear for compatibility with Docker", ) - _ = cmd.RegisterFlagCompletionFunc(kernelMemoryFlagName, completion.AutocompleteNone) // kernel-memory is deprecated in the runtime spec. _ = createFlags.MarkHidden("kernel-memory") diff --git a/cmd/podman/common/create_opts.go b/cmd/podman/common/create_opts.go index 990c1c063..f2335a2be 100644 --- a/cmd/podman/common/create_opts.go +++ b/cmd/podman/common/create_opts.go @@ -18,7 +18,6 @@ import ( "github.com/containers/podman/v3/pkg/specgen" "github.com/docker/docker/api/types/mount" "github.com/pkg/errors" - "github.com/sirupsen/logrus" ) func stringMaptoArray(m map[string]string) []string { @@ -385,9 +384,6 @@ func ContainerCreateToContainerCLIOpts(cc handlers.CreateContainerConfig, rtc *c if cc.HostConfig.Memory > 0 { cliOpts.Memory = strconv.Itoa(int(cc.HostConfig.Memory)) } - if cc.HostConfig.KernelMemory > 0 { - logrus.Warnf("The --kernel-memory flag has been deprecated. May not work properly on your system.") - } if cc.HostConfig.MemoryReservation > 0 { cliOpts.MemoryReservation = strconv.Itoa(int(cc.HostConfig.MemoryReservation)) @@ -409,9 +405,6 @@ func ContainerCreateToContainerCLIOpts(cc handlers.CreateContainerConfig, rtc *c cliOpts.ShmSize = strconv.Itoa(int(cc.HostConfig.ShmSize)) } - if cc.HostConfig.KernelMemory > 0 { - cliOpts.KernelMemory = strconv.Itoa(int(cc.HostConfig.KernelMemory)) - } if len(cc.HostConfig.RestartPolicy.Name) > 0 { policy := cc.HostConfig.RestartPolicy.Name // only add restart count on failure diff --git a/cmd/podman/common/netflags.go b/cmd/podman/common/netflags.go index ba8ab7a8b..425d85c9d 100644 --- a/cmd/podman/common/netflags.go +++ b/cmd/podman/common/netflags.go @@ -53,6 +53,13 @@ func DefineNetFlags(cmd *cobra.Command) { ) _ = cmd.RegisterFlagCompletionFunc(ipFlagName, completion.AutocompleteNone) + ip6FlagName := "ip6" + netFlags.String( + ip6FlagName, "", + "Specify a static IPv6 address for the container", + ) + _ = cmd.RegisterFlagCompletionFunc(ip6FlagName, completion.AutocompleteNone) + macAddressFlagName := "mac-address" netFlags.String( macAddressFlagName, "", @@ -185,7 +192,7 @@ func NetFlagsToNetOptions(opts *entities.NetOptions, flags pflag.FlagSet) (*enti opts.Networks = networks } - if flags.Changed("ip") || flags.Changed("mac-address") || flags.Changed("network-alias") { + if flags.Changed("ip") || flags.Changed("ip6") || flags.Changed("mac-address") || flags.Changed("network-alias") { // if there is no network we add the default if len(opts.Networks) == 0 { opts.Networks = map[string]types.PerNetworkOptions{ @@ -193,29 +200,31 @@ func NetFlagsToNetOptions(opts *entities.NetOptions, flags pflag.FlagSet) (*enti } } - ip, err := flags.GetString("ip") - if err != nil { - return nil, err - } - if ip != "" { - // if pod create --infra=false - if infra, err := flags.GetBool("infra"); err == nil && !infra { - return nil, errors.Wrap(define.ErrInvalidArg, "cannot set --ip without infra container") + for _, ipFlagName := range []string{"ip", "ip6"} { + ip, err := flags.GetString(ipFlagName) + if err != nil { + return nil, err } + if ip != "" { + // if pod create --infra=false + if infra, err := flags.GetBool("infra"); err == nil && !infra { + return nil, errors.Wrapf(define.ErrInvalidArg, "cannot set --%s without infra container", ipFlagName) + } - staticIP := net.ParseIP(ip) - if staticIP == nil { - return nil, errors.Errorf("%s is not an ip address", ip) - } - if !opts.Network.IsBridge() && !opts.Network.IsDefault() { - return nil, errors.Wrap(define.ErrInvalidArg, "--ip can only be set when the network mode is bridge") - } - if len(opts.Networks) != 1 { - return nil, errors.Wrap(define.ErrInvalidArg, "--ip can only be set for a single network") - } - for name, netOpts := range opts.Networks { - netOpts.StaticIPs = append(netOpts.StaticIPs, staticIP) - opts.Networks[name] = netOpts + staticIP := net.ParseIP(ip) + if staticIP == nil { + return nil, errors.Errorf("%q is not an ip address", ip) + } + if !opts.Network.IsBridge() && !opts.Network.IsDefault() { + return nil, errors.Wrapf(define.ErrInvalidArg, "--%s can only be set when the network mode is bridge", ipFlagName) + } + if len(opts.Networks) != 1 { + return nil, errors.Wrapf(define.ErrInvalidArg, "--%s can only be set for a single network", ipFlagName) + } + for name, netOpts := range opts.Networks { + netOpts.StaticIPs = append(netOpts.StaticIPs, staticIP) + opts.Networks[name] = netOpts + } } } diff --git a/cmd/podman/containers/checkpoint.go b/cmd/podman/containers/checkpoint.go index e8dd25978..43a1b75e5 100644 --- a/cmd/podman/containers/checkpoint.go +++ b/cmd/podman/containers/checkpoint.go @@ -11,6 +11,7 @@ import ( "github.com/containers/podman/v3/cmd/podman/registry" "github.com/containers/podman/v3/cmd/podman/utils" "github.com/containers/podman/v3/cmd/podman/validate" + "github.com/containers/podman/v3/pkg/criu" "github.com/containers/podman/v3/pkg/domain/entities" "github.com/containers/podman/v3/pkg/rootless" "github.com/containers/storage/pkg/archive" @@ -113,6 +114,9 @@ func checkpoint(cmd *cobra.Command, args []string) error { if checkpointOptions.WithPrevious && checkpointOptions.PreCheckPoint { return errors.Errorf("--with-previous can not be used with --pre-checkpoint") } + if (checkpointOptions.WithPrevious || checkpointOptions.PreCheckPoint) && !criu.MemTrack() { + return errors.New("system (architecture/kernel/CRIU) does not support memory tracking") + } responses, err := registry.ContainerEngine().ContainerCheckpoint(context.Background(), args, checkpointOptions) if err != nil { return err diff --git a/cmd/podman/containers/create.go b/cmd/podman/containers/create.go index e004f4ab2..9610c29dc 100644 --- a/cmd/podman/containers/create.go +++ b/cmd/podman/containers/create.go @@ -21,6 +21,7 @@ import ( "github.com/containers/podman/v3/pkg/util" "github.com/mattn/go-isatty" "github.com/pkg/errors" + "github.com/sirupsen/logrus" "github.com/spf13/cobra" ) @@ -191,6 +192,10 @@ func CreateInit(c *cobra.Command, vals entities.ContainerCreateOptions, isInfra vals.UserNS = "private" } } + if c.Flag("kernel-memory") != nil && c.Flag("kernel-memory").Changed { + logrus.Warnf("The --kernel-memory flag is no longer supported. This flag is a noop.") + } + if cliVals.LogDriver == define.PassthroughLogging { if isatty.IsTerminal(0) || isatty.IsTerminal(1) || isatty.IsTerminal(2) { return vals, errors.New("the '--log-driver passthrough' option cannot be used on a TTY") diff --git a/cmd/podman/containers/run.go b/cmd/podman/containers/run.go index cfb89ce57..b9a2c3bb5 100644 --- a/cmd/podman/containers/run.go +++ b/cmd/podman/containers/run.go @@ -83,6 +83,9 @@ func runFlags(cmd *cobra.Command) { _ = cmd.RegisterFlagCompletionFunc(gpuFlagName, completion.AutocompleteNone) _ = flags.MarkHidden("gpus") + passwdFlagName := "passwd" + flags.BoolVar(&runOpts.Passwd, passwdFlagName, true, "add entries to /etc/passwd and /etc/group") + if registry.IsRemote() { _ = flags.MarkHidden("preserve-fds") _ = flags.MarkHidden("conmon-pidfile") @@ -191,6 +194,7 @@ func run(cmd *cobra.Command, args []string) error { return err } s.RawImageName = rawImageName + s.Passwd = &runOpts.Passwd runOpts.Spec = s if _, err := createPodIfNecessary(cmd, s, cliVals.Net); err != nil { diff --git a/cmd/podman/machine/init.go b/cmd/podman/machine/init.go index bee6844df..14e87c201 100644 --- a/cmd/podman/machine/init.go +++ b/cmd/podman/machine/init.go @@ -1,4 +1,4 @@ -// +build amd64,!windows arm64,!windows +// +build amd64 arm64 package machine @@ -8,7 +8,6 @@ import ( "github.com/containers/common/pkg/completion" "github.com/containers/podman/v3/cmd/podman/registry" "github.com/containers/podman/v3/pkg/machine" - "github.com/containers/podman/v3/pkg/machine/qemu" "github.com/pkg/errors" "github.com/spf13/cobra" ) @@ -38,6 +37,8 @@ func init() { }) flags := initCmd.Flags() cfg := registry.PodmanConfig() + initOpts.Username = cfg.Config.Machine.User + cpusFlagName := "cpus" flags.Uint64Var( &initOpts.CPUS, @@ -76,6 +77,13 @@ func init() { flags.StringVar(&initOpts.TimeZone, timezoneFlagName, defaultTz, "Set timezone") _ = initCmd.RegisterFlagCompletionFunc(timezoneFlagName, completion.AutocompleteDefault) + flags.BoolVar( + &initOpts.ReExec, + "reexec", false, + "process was rexeced", + ) + flags.MarkHidden("reexec") + ImagePathFlagName := "image-path" flags.StringVar(&initOpts.ImagePath, ImagePathFlagName, cfg.Machine.Image, "Path to qcow image") _ = initCmd.RegisterFlagCompletionFunc(ImagePathFlagName, completion.AutocompleteDefault) @@ -88,33 +96,47 @@ func init() { // TODO should we allow for a users to append to the qemu cmdline? func initMachine(cmd *cobra.Command, args []string) error { var ( - vm machine.VM - vmType string - err error + vm machine.VM + err error ) + + provider := getSystemDefaultProvider() initOpts.Name = defaultMachineName if len(args) > 0 { initOpts.Name = args[0] } - switch vmType { - default: // qemu is the default - if _, err := qemu.LoadVMByName(initOpts.Name); err == nil { - return errors.Wrap(machine.ErrVMAlreadyExists, initOpts.Name) - } - vm, err = qemu.NewMachine(initOpts) + if _, err := provider.LoadVMByName(initOpts.Name); err == nil { + return errors.Wrap(machine.ErrVMAlreadyExists, initOpts.Name) } + + vm, err = provider.NewMachine(initOpts) if err != nil { return err } - err = vm.Init(initOpts) - if err != nil { + + if finished, err := vm.Init(initOpts); err != nil || !finished { + // Finished = true, err = nil - Success! Log a message with further instructions + // Finished = false, err = nil - The installation is partially complete and podman should + // exit gracefully with no error and no success message. + // Examples: + // - a user has chosen to perform their own reboot + // - reexec for limited admin operations, returning to parent + // Finished = *, err != nil - Exit with an error message + return err } + fmt.Println("Machine init complete") if now { err = vm.Start(initOpts.Name, machine.StartOptions{}) if err == nil { fmt.Printf("Machine %q started successfully\n", initOpts.Name) } + } else { + extra := "" + if initOpts.Name != defaultMachineName { + extra = " " + initOpts.Name + } + fmt.Printf("To start your machine run:\n\n\tpodman machine start%s\n\n", extra) } return err } diff --git a/cmd/podman/machine/list.go b/cmd/podman/machine/list.go index 774ab4fd0..858d87401 100644 --- a/cmd/podman/machine/list.go +++ b/cmd/podman/machine/list.go @@ -1,4 +1,5 @@ -// +build amd64,!windows arm64,!windows +//go:build amd64 || arm64 +// +build amd64 arm64 package machine @@ -16,7 +17,6 @@ import ( "github.com/containers/podman/v3/cmd/podman/registry" "github.com/containers/podman/v3/cmd/podman/validate" "github.com/containers/podman/v3/pkg/machine" - "github.com/containers/podman/v3/pkg/machine/qemu" "github.com/docker/go-units" "github.com/pkg/errors" "github.com/spf13/cobra" @@ -69,9 +69,14 @@ func init() { } func list(cmd *cobra.Command, args []string) error { - var opts machine.ListOptions - // We only have qemu VM's for now - listResponse, err := qemu.List(opts) + var ( + opts machine.ListOptions + listResponse []*machine.ListResponse + err error + ) + + provider := getSystemDefaultProvider() + listResponse, err = provider.List(opts) if err != nil { return errors.Wrap(err, "error listing vms") } @@ -182,8 +187,8 @@ func toMachineFormat(vms []*machine.ListResponse) ([]*machineReporter, error) { response.Stream = streamName(vm.Stream) response.VMType = vm.VMType response.CPUs = vm.CPUs - response.Memory = strUint(vm.Memory * units.MiB) - response.DiskSize = strUint(vm.DiskSize * units.GiB) + response.Memory = strUint(vm.Memory) + response.DiskSize = strUint(vm.DiskSize) machineResponses = append(machineResponses, response) } @@ -214,8 +219,8 @@ func toHumanFormat(vms []*machine.ListResponse) ([]*machineReporter, error) { response.Created = units.HumanDuration(time.Since(vm.CreatedAt)) + " ago" response.VMType = vm.VMType response.CPUs = vm.CPUs - response.Memory = units.HumanSize(float64(vm.Memory) * units.MiB) - response.DiskSize = units.HumanSize(float64(vm.DiskSize) * units.GiB) + response.Memory = units.HumanSize(float64(vm.Memory)) + response.DiskSize = units.HumanSize(float64(vm.DiskSize)) humanResponses = append(humanResponses, response) } diff --git a/cmd/podman/machine/machine.go b/cmd/podman/machine/machine.go index 8ff9055f0..22ffbbee7 100644 --- a/cmd/podman/machine/machine.go +++ b/cmd/podman/machine/machine.go @@ -1,4 +1,4 @@ -// +build amd64,!windows arm64,!windows +// +build amd64 arm64 package machine @@ -8,7 +8,6 @@ import ( "github.com/containers/podman/v3/cmd/podman/registry" "github.com/containers/podman/v3/cmd/podman/validate" "github.com/containers/podman/v3/pkg/machine" - "github.com/containers/podman/v3/pkg/machine/qemu" "github.com/spf13/cobra" ) @@ -51,7 +50,8 @@ func autocompleteMachine(cmd *cobra.Command, args []string, toComplete string) ( func getMachines(toComplete string) ([]string, cobra.ShellCompDirective) { suggestions := []string{} - machines, err := qemu.List(machine.ListOptions{}) + provider := getSystemDefaultProvider() + machines, err := provider.List(machine.ListOptions{}) if err != nil { cobra.CompErrorln(err.Error()) return nil, cobra.ShellCompDirectiveNoFileComp diff --git a/cmd/podman/machine/machine_unsupported.go b/cmd/podman/machine/machine_unsupported.go index f8392694a..2f4189446 100644 --- a/cmd/podman/machine/machine_unsupported.go +++ b/cmd/podman/machine/machine_unsupported.go @@ -1,4 +1,4 @@ -// +build !amd64 amd64,windows +// +build !amd64,!arm64 package machine diff --git a/cmd/podman/machine/platform.go b/cmd/podman/machine/platform.go new file mode 100644 index 000000000..fc3186205 --- /dev/null +++ b/cmd/podman/machine/platform.go @@ -0,0 +1,12 @@ +// +build amd64,!windows arm64,!windows + +package machine + +import ( + "github.com/containers/podman/v3/pkg/machine" + "github.com/containers/podman/v3/pkg/machine/qemu" +) + +func getSystemDefaultProvider() machine.Provider { + return qemu.GetQemuProvider() +} diff --git a/cmd/podman/machine/platform_windows.go b/cmd/podman/machine/platform_windows.go new file mode 100644 index 000000000..a4a35e712 --- /dev/null +++ b/cmd/podman/machine/platform_windows.go @@ -0,0 +1,10 @@ +package machine + +import ( + "github.com/containers/podman/v3/pkg/machine" + "github.com/containers/podman/v3/pkg/machine/wsl" +) + +func getSystemDefaultProvider() machine.Provider { + return wsl.GetWSLProvider() +} diff --git a/cmd/podman/machine/rm.go b/cmd/podman/machine/rm.go index c17399c78..c58e74a42 100644 --- a/cmd/podman/machine/rm.go +++ b/cmd/podman/machine/rm.go @@ -1,4 +1,4 @@ -// +build amd64,!windows arm64,!windows +// +build amd64 arm64 package machine @@ -10,7 +10,6 @@ import ( "github.com/containers/podman/v3/cmd/podman/registry" "github.com/containers/podman/v3/pkg/machine" - "github.com/containers/podman/v3/pkg/machine/qemu" "github.com/spf13/cobra" ) @@ -52,18 +51,16 @@ func init() { func rm(cmd *cobra.Command, args []string) error { var ( - err error - vm machine.VM - vmType string + err error + vm machine.VM ) vmName := defaultMachineName if len(args) > 0 && len(args[0]) > 0 { vmName = args[0] } - switch vmType { - default: - vm, err = qemu.LoadVMByName(vmName) - } + + provider := getSystemDefaultProvider() + vm, err = provider.LoadVMByName(vmName) if err != nil { return err } diff --git a/cmd/podman/machine/ssh.go b/cmd/podman/machine/ssh.go index da0a09338..5ef34afc6 100644 --- a/cmd/podman/machine/ssh.go +++ b/cmd/podman/machine/ssh.go @@ -1,4 +1,4 @@ -// +build amd64,!windows arm64,!windows +// +build amd64 arm64 package machine @@ -9,7 +9,6 @@ import ( "github.com/containers/common/pkg/config" "github.com/containers/podman/v3/cmd/podman/registry" "github.com/containers/podman/v3/pkg/machine" - "github.com/containers/podman/v3/pkg/machine/qemu" "github.com/pkg/errors" "github.com/spf13/cobra" ) @@ -47,27 +46,24 @@ func ssh(cmd *cobra.Command, args []string) error { err error validVM bool vm machine.VM - vmType string ) // Set the VM to default vmName := defaultMachineName + provider := getSystemDefaultProvider() // If len is greater than 0, it means we may have been // provided the VM name. If so, we check. The VM name, // if provided, must be in args[0]. if len(args) > 0 { - switch vmType { - default: - validVM, err = qemu.IsValidVMName(args[0]) - if err != nil { - return err - } - if validVM { - vmName = args[0] - } else { - sshOpts.Args = append(sshOpts.Args, args[0]) - } + validVM, err = provider.IsValidVMName(args[0]) + if err != nil { + return err + } + if validVM { + vmName = args[0] + } else { + sshOpts.Args = append(sshOpts.Args, args[0]) } } @@ -88,10 +84,7 @@ func ssh(cmd *cobra.Command, args []string) error { } } - switch vmType { - default: - vm, err = qemu.LoadVMByName(vmName) - } + vm, err = provider.LoadVMByName(vmName) if err != nil { return errors.Wrapf(err, "vm %s not found", vmName) } diff --git a/cmd/podman/machine/start.go b/cmd/podman/machine/start.go index 4ae31e6de..9c9c24f64 100644 --- a/cmd/podman/machine/start.go +++ b/cmd/podman/machine/start.go @@ -1,4 +1,4 @@ -// +build amd64,!windows arm64,!windows +// +build amd64 arm64 package machine @@ -7,7 +7,6 @@ import ( "github.com/containers/podman/v3/cmd/podman/registry" "github.com/containers/podman/v3/pkg/machine" - "github.com/containers/podman/v3/pkg/machine/qemu" "github.com/pkg/errors" "github.com/spf13/cobra" ) @@ -33,30 +32,31 @@ func init() { func start(cmd *cobra.Command, args []string) error { var ( - err error - vm machine.VM - vmType string + err error + vm machine.VM ) vmName := defaultMachineName if len(args) > 0 && len(args[0]) > 0 { vmName = args[0] } - // We only have qemu VM's for now - active, activeName, err := qemu.CheckActiveVM() + provider := getSystemDefaultProvider() + vm, err = provider.LoadVMByName(vmName) if err != nil { return err } + + active, activeName, cerr := provider.CheckExclusiveActiveVM() + if cerr != nil { + return cerr + } if active { if vmName == activeName { return errors.Wrapf(machine.ErrVMAlreadyRunning, "cannot start VM %s", vmName) } return errors.Wrapf(machine.ErrMultipleActiveVM, "cannot start VM %s. VM %s is currently running", vmName, activeName) } - switch vmType { - default: - vm, err = qemu.LoadVMByName(vmName) - } + vm, err = provider.LoadVMByName(vmName) if err != nil { return err } diff --git a/cmd/podman/machine/stop.go b/cmd/podman/machine/stop.go index 75666f734..17969298b 100644 --- a/cmd/podman/machine/stop.go +++ b/cmd/podman/machine/stop.go @@ -1,4 +1,5 @@ -// +build amd64,!windows arm64,!windows +//go:build amd64 || arm64 +// +build amd64 arm64 package machine @@ -7,7 +8,6 @@ import ( "github.com/containers/podman/v3/cmd/podman/registry" "github.com/containers/podman/v3/pkg/machine" - "github.com/containers/podman/v3/pkg/machine/qemu" "github.com/spf13/cobra" ) @@ -33,18 +33,15 @@ func init() { // TODO Name shouldn't be required, need to create a default vm func stop(cmd *cobra.Command, args []string) error { var ( - err error - vm machine.VM - vmType string + err error + vm machine.VM ) vmName := defaultMachineName if len(args) > 0 && len(args[0]) > 0 { vmName = args[0] } - switch vmType { - default: - vm, err = qemu.LoadVMByName(vmName) - } + provider := getSystemDefaultProvider() + vm, err = provider.LoadVMByName(vmName) if err != nil { return err } diff --git a/cmd/winpath/main.go b/cmd/winpath/main.go new file mode 100644 index 000000000..494d1cf3c --- /dev/null +++ b/cmd/winpath/main.go @@ -0,0 +1,184 @@ +//go:build windows +// +build windows + +package main + +import ( + "errors" + "io/fs" + "os" + "path/filepath" + "strings" + "syscall" + "unsafe" + + "golang.org/x/sys/windows/registry" +) + +type operation int + +const ( + HWND_BROADCAST = 0xFFFF + WM_SETTINGCHANGE = 0x001A + SMTO_ABORTIFHUNG = 0x0002 + ERR_BAD_ARGS = 0x000A + OPERATION_FAILED = 0x06AC + Environment = "Environment" + Add operation = iota + Remove + NotSpecified +) + +func main() { + op := NotSpecified + if len(os.Args) >= 2 { + switch os.Args[1] { + case "add": + op = Add + case "remove": + op = Remove + } + } + + // Stay silent since ran from an installer + if op == NotSpecified { + alert("Usage: " + filepath.Base(os.Args[0]) + " [add|remove]\n\nThis utility adds or removes the podman directory to the Windows Path.") + os.Exit(ERR_BAD_ARGS) + } + + if err := modify(op); err != nil { + os.Exit(OPERATION_FAILED) + } +} + +func modify(op operation) error { + exe, err := os.Executable() + if err != nil { + return err + } + exe, err = filepath.EvalSymlinks(exe) + if err != nil { + return err + } + target := filepath.Dir(exe) + + if op == Remove { + return removePathFromRegistry(target) + } + + return addPathToRegistry(target) +} + +// Appends a directory to the Windows Path stored in the registry +func addPathToRegistry(dir string) error { + k, _, err := registry.CreateKey(registry.CURRENT_USER, Environment, registry.WRITE|registry.READ) + if err != nil { + return err + } + + defer k.Close() + + existing, typ, err := k.GetStringValue("Path") + if err != nil { + return err + } + + // Is this directory already on the windows path? + for _, element := range strings.Split(existing, ";") { + if strings.EqualFold(element, dir) { + // Path already added + return nil + } + } + + // If the existing path is empty we don't want to start with a delimiter + if len(existing) > 0 { + existing += ";" + } + + existing += dir + + // It's important to preserve the registry key type so that it will be interpreted correctly + // EXPAND = evaluate variables in the expression, e.g. %PATH% should be expanded to the system path + // STRING = treat the contents as a string literal + if typ == registry.EXPAND_SZ { + err = k.SetExpandStringValue("Path", existing) + } else { + err = k.SetStringValue("Path", existing) + } + + if err == nil { + broadcastEnvironmentChange() + } + + return err +} + +// Removes all occurences of a directory path from the Windows path stored in the registry +func removePathFromRegistry(path string) error { + k, err := registry.OpenKey(registry.CURRENT_USER, Environment, registry.READ|registry.WRITE) + if err != nil { + if errors.Is(err, fs.ErrNotExist) { + // Nothing to cleanup, the Environment registry key does not exist. + return nil + } + return err + } + + defer k.Close() + + existing, typ, err := k.GetStringValue("Path") + if err != nil { + return err + } + + var elements []string + for _, element := range strings.Split(existing, ";") { + if strings.EqualFold(element, path) { + continue + } + elements = append(elements, element) + } + + newPath := strings.Join(elements, ";") + // Preserve value type (see corresponding comment above) + if typ == registry.EXPAND_SZ { + err = k.SetExpandStringValue("Path", newPath) + } else { + err = k.SetStringValue("Path", newPath) + } + + if err == nil { + broadcastEnvironmentChange() + } + + return err +} + +// Sends a notification message to all top level windows informing them the environmental setings have changed. +// Applications such as the Windows command prompt and powershell will know to stop caching stale values on +// subsequent restarts. Since applications block the sender when receiving a message, we set a 3 second timeout +func broadcastEnvironmentChange() { + env, _ := syscall.UTF16PtrFromString(Environment) + user32 := syscall.NewLazyDLL("user32") + proc := user32.NewProc("SendMessageTimeoutW") + millis := 3000 + _, _, _ = proc.Call(HWND_BROADCAST, WM_SETTINGCHANGE, 0, uintptr(unsafe.Pointer(env)), SMTO_ABORTIFHUNG, uintptr(millis), 0) +} + +// Creates an "error" style pop-up window +func alert(caption string) int { + // Error box style + format := 0x10 + + user32 := syscall.NewLazyDLL("user32.dll") + captionPtr, _ := syscall.UTF16PtrFromString(caption) + titlePtr, _ := syscall.UTF16PtrFromString("winpath") + ret, _, _ := user32.NewProc("MessageBoxW").Call( + uintptr(0), + uintptr(unsafe.Pointer(captionPtr)), + uintptr(unsafe.Pointer(titlePtr)), + uintptr(format)) + + return int(ret) +} |