diff options
Diffstat (limited to 'pkg')
-rw-r--r-- | pkg/adapter/client.go | 2 | ||||
-rw-r--r-- | pkg/adapter/client_unix.go | 8 | ||||
-rw-r--r-- | pkg/adapter/containers.go | 26 | ||||
-rw-r--r-- | pkg/adapter/containers_remote.go | 44 | ||||
-rw-r--r-- | pkg/adapter/network.go | 68 | ||||
-rw-r--r-- | pkg/adapter/pods.go | 7 | ||||
-rw-r--r-- | pkg/cgroups/cgroups_supported.go | 62 | ||||
-rw-r--r-- | pkg/cgroups/cgroups_unsupported.go | 6 | ||||
-rw-r--r-- | pkg/lookup/lookup.go | 30 | ||||
-rw-r--r-- | pkg/netns/netns_linux.go | 35 | ||||
-rw-r--r-- | pkg/network/devices.go | 16 | ||||
-rw-r--r-- | pkg/network/files.go | 24 | ||||
-rw-r--r-- | pkg/rootless/rootless_linux.go | 2 | ||||
-rw-r--r-- | pkg/spec/createconfig.go | 2 | ||||
-rw-r--r-- | pkg/spec/spec_test.go | 38 | ||||
-rw-r--r-- | pkg/spec/storage.go | 41 | ||||
-rw-r--r-- | pkg/util/utils.go | 16 | ||||
-rw-r--r-- | pkg/varlinkapi/attach.go | 33 | ||||
-rw-r--r-- | pkg/varlinkapi/containers.go | 14 | ||||
-rw-r--r-- | pkg/varlinkapi/pods.go | 2 |
20 files changed, 377 insertions, 99 deletions
diff --git a/pkg/adapter/client.go b/pkg/adapter/client.go index da6ff5fd0..1805c758d 100644 --- a/pkg/adapter/client.go +++ b/pkg/adapter/client.go @@ -35,7 +35,7 @@ func (r RemoteRuntime) RemoteEndpoint() (remoteEndpoint *Endpoint, err error) { if len(r.cmd.RemoteUserName) < 1 { return nil, errors.New("you must provide a username when providing a remote host name") } - rc := remoteclientconfig.RemoteConnection{r.cmd.RemoteHost, r.cmd.RemoteUserName, false} + rc := remoteclientconfig.RemoteConnection{r.cmd.RemoteHost, r.cmd.RemoteUserName, false, r.cmd.Port} remoteEndpoint, err = newBridgeConnection("", &rc, r.cmd.LogLevel) // if the user has a config file with connections in it } else if len(remoteConfigConnections.Connections) > 0 { diff --git a/pkg/adapter/client_unix.go b/pkg/adapter/client_unix.go index 4781acd06..a7bc7c1c0 100644 --- a/pkg/adapter/client_unix.go +++ b/pkg/adapter/client_unix.go @@ -10,7 +10,11 @@ import ( ) func formatDefaultBridge(remoteConn *remoteclientconfig.RemoteConnection, logLevel string) string { + port := remoteConn.Port + if port == 0 { + port = 22 + } return fmt.Sprintf( - `ssh -T %s@%s -- /usr/bin/varlink -A \'/usr/bin/podman --log-level=%s varlink \\\$VARLINK_ADDRESS\' bridge`, - remoteConn.Username, remoteConn.Destination, logLevel) + `ssh -p %d -T %s@%s -- /usr/bin/varlink -A \'/usr/bin/podman --log-level=%s varlink \\\$VARLINK_ADDRESS\' bridge`, + port, remoteConn.Username, remoteConn.Destination, logLevel) } diff --git a/pkg/adapter/containers.go b/pkg/adapter/containers.go index 41607145d..47db5c0dc 100644 --- a/pkg/adapter/containers.go +++ b/pkg/adapter/containers.go @@ -341,12 +341,7 @@ func (r *LocalRuntime) Run(ctx context.Context, c *cliconfig.RunValues, exitCode // if the container was created as part of a pod, also start its dependencies, if any. if err := ctr.Start(ctx, c.IsSet("pod")); err != nil { // This means the command did not exist - exitCode = 127 - e := strings.ToLower(err.Error()) - if strings.Contains(e, "permission denied") || strings.Contains(e, "operation not permitted") || strings.Contains(e, "file not found") || strings.Contains(e, "no such file or directory") { - exitCode = 126 - } - return exitCode, err + return define.ExitCode(err), err } fmt.Printf("%s\n", ctr.ID()) @@ -401,21 +396,14 @@ func (r *LocalRuntime) Run(ctx context.Context, c *cliconfig.RunValues, exitCode // Do not perform cleanup, or wait for container exit code // Just exit immediately if errors.Cause(err) == define.ErrDetach { - exitCode = 0 - return exitCode, nil - } - // This means the command did not exist - exitCode = 127 - e := strings.ToLower(err.Error()) - if strings.Contains(e, "permission denied") || strings.Contains(e, "operation not permitted") { - exitCode = 126 + return 0, nil } if c.IsSet("rm") { if deleteError := r.Runtime.RemoveContainer(ctx, ctr, true, false); deleteError != nil { logrus.Debugf("unable to remove container %s after failing to start and attach to it", ctr.ID()) } } - return exitCode, err + return define.ExitCode(err), err } if ecode, err := ctr.Wait(); err != nil { @@ -424,7 +412,7 @@ func (r *LocalRuntime) Run(ctx context.Context, c *cliconfig.RunValues, exitCode event, err := r.Runtime.GetLastContainerEvent(ctr.ID(), events.Exited) if err != nil { logrus.Errorf("Cannot get exit code: %v", err) - exitCode = 127 + exitCode = define.ExecErrorCodeNotFound } else { exitCode = event.ContainerExitCode } @@ -576,7 +564,7 @@ func (r *LocalRuntime) Restore(ctx context.Context, c *cliconfig.RestoreValues) // Start will start a container func (r *LocalRuntime) Start(ctx context.Context, c *cliconfig.StartValues, sigProxy bool) (int, error) { var ( - exitCode = 125 + exitCode = define.ExecErrorCodeGeneric lastError error ) @@ -636,7 +624,7 @@ func (r *LocalRuntime) Start(ctx context.Context, c *cliconfig.StartValues, sigP event, err := r.Runtime.GetLastContainerEvent(ctr.ID(), events.Exited) if err != nil { logrus.Errorf("Cannot get exit code: %v", err) - exitCode = 127 + exitCode = define.ExecErrorCodeNotFound } else { exitCode = event.ContainerExitCode } @@ -914,7 +902,7 @@ func (r *LocalRuntime) ExecContainer(ctx context.Context, cli *cliconfig.ExecVal cmd []string ) // default invalid command exit code - ec := 125 + ec := define.ExecErrorCodeGeneric if cli.Latest { if ctr, err = r.GetLatestContainer(); err != nil { diff --git a/pkg/adapter/containers_remote.go b/pkg/adapter/containers_remote.go index 590fef43f..6cecb92da 100644 --- a/pkg/adapter/containers_remote.go +++ b/pkg/adapter/containers_remote.go @@ -464,19 +464,27 @@ func (r *LocalRuntime) Run(ctx context.Context, c *cliconfig.RunValues, exitCode results := shared.NewIntermediateLayer(&c.PodmanCommand, true) cid, err := iopodman.CreateContainer().Call(r.Conn, results.MakeVarlink()) if err != nil { - return 0, err + return exitCode, err } if c.Bool("detach") { - _, err := iopodman.StartContainer().Call(r.Conn, cid) + if _, err := iopodman.StartContainer().Call(r.Conn, cid); err != nil { + return exitCode, err + } fmt.Println(cid) - return 0, err + return 0, nil + } + inputStream := os.Stdin + // If -i is not set, clear stdin + if !c.Bool("interactive") { + inputStream = nil } - errChan, err := r.attach(ctx, os.Stdin, os.Stdout, cid, true, c.String("detach-keys")) + exitChan, errChan, err := r.attach(ctx, inputStream, os.Stdout, cid, true, c.String("detach-keys")) if err != nil { - return 0, err + return exitCode, err } + exitCode = <-exitChan finalError := <-errChan - return 0, finalError + return exitCode, finalError } func ReadExitFile(runtimeTmp, ctrID string) (int, error) { @@ -572,7 +580,7 @@ func (r *LocalRuntime) Attach(ctx context.Context, c *cliconfig.AttachValues) er return err } } - errChan, err := r.attach(ctx, inputStream, os.Stdout, c.InputArgs[0], false, c.DetachKeys) + _, errChan, err := r.attach(ctx, inputStream, os.Stdout, c.InputArgs[0], false, c.DetachKeys) if err != nil { return err } @@ -669,7 +677,7 @@ func (r *LocalRuntime) Restore(ctx context.Context, c *cliconfig.RestoreValues) func (r *LocalRuntime) Start(ctx context.Context, c *cliconfig.StartValues, sigProxy bool) (int, error) { var ( finalErr error - exitCode = 125 + exitCode = define.ExecErrorCodeGeneric ) // TODO Figure out how to deal with exit codes inputStream := os.Stdin @@ -686,12 +694,13 @@ func (r *LocalRuntime) Start(ctx context.Context, c *cliconfig.StartValues, sigP } // start.go makes sure that if attach, there can be only one ctr if c.Attach { - errChan, err := r.attach(ctx, inputStream, os.Stdout, containerIDs[0], true, c.DetachKeys) + exitChan, errChan, err := r.attach(ctx, inputStream, os.Stdout, containerIDs[0], true, c.DetachKeys) if err != nil { return exitCode, nil } + exitCode := <-exitChan err = <-errChan - return 0, err + return exitCode, err } // TODO the notion of starting a pod container and its deps still needs to be worked through @@ -710,13 +719,13 @@ func (r *LocalRuntime) Start(ctx context.Context, c *cliconfig.StartValues, sigP return exitCode, finalErr } -func (r *LocalRuntime) attach(ctx context.Context, stdin, stdout *os.File, cid string, start bool, detachKeys string) (chan error, error) { +func (r *LocalRuntime) attach(ctx context.Context, stdin, stdout *os.File, cid string, start bool, detachKeys string) (chan int, chan error, error) { var ( oldTermState *term.State ) spec, err := r.Spec(cid) if err != nil { - return nil, err + return nil, nil, err } resize := make(chan remotecommand.TerminalSize, 5) haveTerminal := terminal.IsTerminal(int(os.Stdin.Fd())) @@ -726,7 +735,7 @@ func (r *LocalRuntime) attach(ctx context.Context, stdin, stdout *os.File, cid s if haveTerminal && spec.Process.Terminal { cancel, oldTermState, err := handleTerminalAttach(ctx, resize) if err != nil { - return nil, err + return nil, nil, err } defer cancel() defer restoreTerminal(oldTermState) @@ -738,7 +747,7 @@ func (r *LocalRuntime) attach(ctx context.Context, stdin, stdout *os.File, cid s reply, err := iopodman.Attach().Send(r.Conn, varlink.Upgrade, cid, detachKeys, start) if err != nil { restoreTerminal(oldTermState) - return nil, err + return nil, nil, err } // See if the server accepts the upgraded connection or returns an error @@ -746,11 +755,12 @@ func (r *LocalRuntime) attach(ctx context.Context, stdin, stdout *os.File, cid s if err != nil { restoreTerminal(oldTermState) - return nil, err + return nil, nil, err } - errChan := configureVarlinkAttachStdio(r.Conn.Reader, r.Conn.Writer, stdin, stdout, oldTermState, resize, nil) - return errChan, nil + ecChan := make(chan int, 1) + errChan := configureVarlinkAttachStdio(r.Conn.Reader, r.Conn.Writer, stdin, stdout, oldTermState, resize, ecChan) + return ecChan, errChan, nil } // PauseContainers pauses container(s) based on CLI inputs. diff --git a/pkg/adapter/network.go b/pkg/adapter/network.go index e4a160767..d407984ce 100644 --- a/pkg/adapter/network.go +++ b/pkg/adapter/network.go @@ -3,9 +3,9 @@ package adapter import ( + "context" "encoding/json" "fmt" - "github.com/containers/libpod/pkg/util" "io/ioutil" "os" "path/filepath" @@ -14,6 +14,7 @@ import ( cniversion "github.com/containernetworking/cni/pkg/version" "github.com/containers/libpod/cmd/podman/cliconfig" "github.com/containers/libpod/pkg/network" + "github.com/containers/libpod/pkg/util" "github.com/pkg/errors" ) @@ -85,16 +86,69 @@ func (r *LocalRuntime) NetworkInspect(cli *cliconfig.NetworkInspectValues) error } // NetworkRemove deletes one or more CNI networks -func (r *LocalRuntime) NetworkRemove(cli *cliconfig.NetworkRmValues) error { +func (r *LocalRuntime) NetworkRemove(ctx context.Context, cli *cliconfig.NetworkRmValues) ([]string, map[string]error, error) { + var ( + networkRmSuccesses []string + lastError error + ) + networkRmErrors := make(map[string]error) + for _, name := range cli.InputArgs { - cniPath, err := network.GetCNIConfigPathByName(name) + containers, err := r.GetAllContainers() if err != nil { - return err + return networkRmSuccesses, networkRmErrors, err } - if err := os.Remove(cniPath); err != nil { - return err + if err := r.removeNetwork(ctx, name, containers, cli.Force); err != nil { + if lastError != nil { + networkRmErrors[name] = lastError + } + lastError = err + } else { + networkRmSuccesses = append(networkRmSuccesses, fmt.Sprintf("Deleted: %s\n", name)) + } + } + return networkRmSuccesses, networkRmErrors, lastError +} + +// removeNetwork removes a single network and its containers given a force bool +func (r *LocalRuntime) removeNetwork(ctx context.Context, name string, containers []*Container, force bool) error { + cniPath, err := network.GetCNIConfigPathByName(name) + if err != nil { + return err + } + // We need to iterate containers looking to see if they belong to the given network + for _, c := range containers { + if util.StringInSlice(name, c.Config().Networks) { + // if user passes force, we nuke containers + if force { + if err := r.RemoveContainer(ctx, c.Container, true, true); err != nil { + return err + } + } else { + // Without the the force option, we return an error + return errors.Errorf("%q has associated containers with it. use -f to forcibly delete containers", name) + } + } - fmt.Printf("Deleted: %s\n", name) + } + // Before we delete the configuration file, we need to make sure we can read and parse + // it to get the network interface name so we can remove that too + interfaceName, err := network.GetInterfaceNameFromConfig(cniPath) + if err != nil { + return errors.Wrapf(err, "failed to find network interface name in %q", cniPath) + } + liveNetworkNames, err := network.GetLiveNetworkNames() + if err != nil { + return errors.Wrapf(err, "failed to get live network names") + } + if util.StringInSlice(interfaceName, liveNetworkNames) { + if err := network.RemoveInterface(interfaceName); err != nil { + return errors.Wrapf(err, "failed to delete the network interface %q", interfaceName) + } + } + // Remove the configuration file + if err := os.Remove(cniPath); err != nil { + return errors.Wrapf(err, "failed to remove network configuration file %q", cniPath) } return nil } diff --git a/pkg/adapter/pods.go b/pkg/adapter/pods.go index 70293a2c5..c8d57e2a2 100644 --- a/pkg/adapter/pods.go +++ b/pkg/adapter/pods.go @@ -467,8 +467,15 @@ func (r *LocalRuntime) PlayKubeYAML(ctx context.Context, c *cliconfig.KubePlayVa return nil, errors.Wrapf(err, "unable to read %s as YAML", yamlFile) } + if podYAML.Kind != "Pod" { + return nil, errors.Errorf("Invalid YAML kind: %s. Pod is the only supported Kubernetes YAML kind", podYAML.Kind) + } + // check for name collision between pod and container podName := podYAML.ObjectMeta.Name + if podName == "" { + return nil, errors.Errorf("pod does not have a name") + } for _, n := range podYAML.Spec.Containers { if n.Name == podName { fmt.Printf("a container exists with the same name (%s) as the pod in your YAML file; changing pod name to %s_pod\n", podName, podName) diff --git a/pkg/cgroups/cgroups_supported.go b/pkg/cgroups/cgroups_supported.go index fcd44dfc8..2a36777d4 100644 --- a/pkg/cgroups/cgroups_supported.go +++ b/pkg/cgroups/cgroups_supported.go @@ -3,8 +3,15 @@ package cgroups import ( + "bufio" + "fmt" + "os" + "path/filepath" + "strings" "sync" "syscall" + + "github.com/pkg/errors" ) var ( @@ -25,3 +32,58 @@ func IsCgroup2UnifiedMode() (bool, error) { }) return isUnified, isUnifiedErr } + +// UserOwnsCurrentSystemdCgroup checks whether the current EUID owns the +// current cgroup. +func UserOwnsCurrentSystemdCgroup() (bool, error) { + uid := os.Geteuid() + + cgroup2, err := IsCgroup2UnifiedMode() + if err != nil { + return false, err + } + + f, err := os.Open("/proc/self/cgroup") + if err != nil { + return false, errors.Wrapf(err, "open file /proc/self/cgroup") + } + defer f.Close() + + scanner := bufio.NewScanner(f) + for scanner.Scan() { + line := scanner.Text() + parts := strings.SplitN(line, ":", 3) + + if len(parts) < 3 { + continue + } + + var cgroupPath string + + if cgroup2 { + cgroupPath = filepath.Join(cgroupRoot, parts[2]) + } else { + if parts[1] != "name=systemd" { + continue + } + cgroupPath = filepath.Join(cgroupRoot, "systemd", parts[2]) + } + + st, err := os.Stat(cgroupPath) + if err != nil { + return false, err + } + s := st.Sys() + if s == nil { + return false, fmt.Errorf("error stat cgroup path %s", cgroupPath) + } + + if int(s.(*syscall.Stat_t).Uid) != uid { + return false, nil + } + } + if err := scanner.Err(); err != nil { + return false, errors.Wrapf(err, "parsing file /proc/self/cgroup") + } + return true, nil +} diff --git a/pkg/cgroups/cgroups_unsupported.go b/pkg/cgroups/cgroups_unsupported.go index 9dc196e42..cd140fbf3 100644 --- a/pkg/cgroups/cgroups_unsupported.go +++ b/pkg/cgroups/cgroups_unsupported.go @@ -6,3 +6,9 @@ package cgroups func IsCgroup2UnifiedMode() (bool, error) { return false, nil } + +// UserOwnsCurrentSystemdCgroup checks whether the current EUID owns the +// current cgroup. +func UserOwnsCurrentSystemdCgroup() (bool, error) { + return false, nil +} diff --git a/pkg/lookup/lookup.go b/pkg/lookup/lookup.go index 70b97144f..a249dd753 100644 --- a/pkg/lookup/lookup.go +++ b/pkg/lookup/lookup.go @@ -29,17 +29,30 @@ func GetUserGroupInfo(containerMount, containerUser string, override *Overrides) defaultExecUser *user.ExecUser err error ) - passwdPath := etcpasswd - groupPath := etcgroup if override != nil { // Check for an override /etc/passwd path if override.ContainerEtcPasswdPath != "" { - passwdPath = override.ContainerEtcPasswdPath + passwdDest = override.ContainerEtcPasswdPath } // Check for an override for /etc/group path if override.ContainerEtcGroupPath != "" { - groupPath = override.ContainerEtcGroupPath + groupDest = override.ContainerEtcGroupPath + } + } + + if passwdDest == "" { + // Make sure the /etc/passwd destination is not a symlink to something naughty + if passwdDest, err = securejoin.SecureJoin(containerMount, etcpasswd); err != nil { + logrus.Debug(err) + return nil, err + } + } + if groupDest == "" { + // Make sure the /etc/group destination is not a symlink to something naughty + if groupDest, err = securejoin.SecureJoin(containerMount, etcgroup); err != nil { + logrus.Debug(err) + return nil, err } } @@ -56,15 +69,6 @@ func GetUserGroupInfo(containerMount, containerUser string, override *Overrides) } - // Make sure the /etc/group and /etc/passwd destinations are not a symlink to something naughty - if passwdDest, err = securejoin.SecureJoin(containerMount, passwdPath); err != nil { - logrus.Debug(err) - return nil, err - } - if groupDest, err = securejoin.SecureJoin(containerMount, groupPath); err != nil { - logrus.Debug(err) - return nil, err - } return user.GetExecUserPath(containerUser, defaultExecUser, passwdDest, groupDest) } diff --git a/pkg/netns/netns_linux.go b/pkg/netns/netns_linux.go index 1d6fb873c..a62296549 100644 --- a/pkg/netns/netns_linux.go +++ b/pkg/netns/netns_linux.go @@ -23,23 +23,42 @@ import ( "fmt" "os" "path" + "path/filepath" "runtime" "strings" "sync" "github.com/containernetworking/plugins/pkg/ns" + "github.com/containers/libpod/pkg/rootless" + "github.com/containers/libpod/pkg/util" "github.com/sirupsen/logrus" "golang.org/x/sys/unix" ) -const nsRunDir = "/var/run/netns" +// get NSRunDir returns the dir of where to create the netNS. When running +// rootless, it needs to be at a location writable by user. +func getNSRunDir() (string, error) { + if rootless.IsRootless() { + rootlessDir, err := util.GetRuntimeDir() + if err != nil { + return "", err + } + return filepath.Join(rootlessDir, "netns"), nil + } + return "/var/run/netns", nil +} // NewNS creates a new persistent (bind-mounted) network namespace and returns // an object representing that namespace, without switching to it. func NewNS() (ns.NetNS, error) { + nsRunDir, err := getNSRunDir() + if err != nil { + return nil, err + } + b := make([]byte, 16) - _, err := rand.Reader.Read(b) + _, err = rand.Reader.Read(b) if err != nil { return nil, fmt.Errorf("failed to generate random netns name: %v", err) } @@ -127,14 +146,15 @@ func NewNS() (ns.NetNS, error) { // Put this thread back to the orig ns, since it might get reused (pre go1.10) defer func() { if err := origNS.Set(); err != nil { - logrus.Errorf("unable to set namespace: %q", err) + logrus.Warnf("unable to set namespace: %q", err) } }() // bind mount the netns from the current thread (from /proc) onto the // mount point. This causes the namespace to persist, even when there - // are no threads in the ns. - err = unix.Mount(getCurrentThreadNetNSPath(), nsPath, "none", unix.MS_BIND, "") + // are no threads in the ns. Make this a shared mount; it needs to be + // back-propogated to the host + err = unix.Mount(getCurrentThreadNetNSPath(), nsPath, "none", unix.MS_BIND|unix.MS_SHARED|unix.MS_REC, "") if err != nil { err = fmt.Errorf("failed to bind mount ns at %s: %v", nsPath, err) } @@ -150,6 +170,11 @@ func NewNS() (ns.NetNS, error) { // UnmountNS unmounts the NS held by the netns object func UnmountNS(ns ns.NetNS) error { + nsRunDir, err := getNSRunDir() + if err != nil { + return err + } + nsPath := ns.Path() // Only unmount if it's been bind-mounted (don't touch namespaces in /proc...) if strings.HasPrefix(nsPath, nsRunDir) { diff --git a/pkg/network/devices.go b/pkg/network/devices.go index 26101b6f7..85068a7d1 100644 --- a/pkg/network/devices.go +++ b/pkg/network/devices.go @@ -2,8 +2,10 @@ package network import ( "fmt" - "github.com/containers/libpod/pkg/util" + "os/exec" + "github.com/containers/libpod/pkg/util" + "github.com/containers/libpod/utils" "github.com/sirupsen/logrus" ) @@ -39,3 +41,15 @@ func GetFreeDeviceName() (string, error) { } return deviceName, nil } + +// RemoveInterface removes an interface by the given name +func RemoveInterface(interfaceName string) error { + // Make sure we have the ip command on the system + ipPath, err := exec.LookPath("ip") + if err != nil { + return err + } + // Delete the network interface + _, err = utils.ExecCmd(ipPath, []string{"link", "del", interfaceName}...) + return err +} diff --git a/pkg/network/files.go b/pkg/network/files.go index 80fde5e17..d55ec2dfd 100644 --- a/pkg/network/files.go +++ b/pkg/network/files.go @@ -86,6 +86,7 @@ func GetNetworksFromFilesystem() ([]*allocator.Net, error) { return nil, err } cniNetworks = append(cniNetworks, &ipamConf) + break } } } @@ -105,3 +106,26 @@ func GetNetworkNamesFromFileSystem() ([]string, error) { } return networkNames, nil } + +// GetInterfaceNameFromConfig returns the interface name for the bridge plugin +func GetInterfaceNameFromConfig(path string) (string, error) { + var name string + conf, err := libcni.ConfListFromFile(path) + if err != nil { + return "", err + } + for _, cniplugin := range conf.Plugins { + if cniplugin.Network.Type == "bridge" { + plugin := make(map[string]interface{}) + if err := json.Unmarshal(cniplugin.Bytes, &plugin); err != nil { + return "", err + } + name = plugin["bridge"].(string) + break + } + } + if len(name) == 0 { + return "", errors.New("unable to find interface name for network") + } + return name, nil +} diff --git a/pkg/rootless/rootless_linux.go b/pkg/rootless/rootless_linux.go index ecb84f6a9..6f6239e5f 100644 --- a/pkg/rootless/rootless_linux.go +++ b/pkg/rootless/rootless_linux.go @@ -365,7 +365,7 @@ func GetConfiguredMappings() ([]idtools.IDMap, []idtools.IDMap, error) { } mappings, err := idtools.NewIDMappings(username, username) if err != nil { - logrus.Warnf("cannot find mappings for user %s: %v", username, err) + logrus.Errorf("cannot find mappings for user %s: %v", username, err) } else { uids = mappings.UIDs() gids = mappings.GIDs() diff --git a/pkg/spec/createconfig.go b/pkg/spec/createconfig.go index c17172016..7c3195be4 100644 --- a/pkg/spec/createconfig.go +++ b/pkg/spec/createconfig.go @@ -275,7 +275,7 @@ func (c *CreateConfig) getContainerCreateOptions(runtime *libpod.Runtime, pod *l options = append(options, libpod.WithNetNSFrom(connectedCtr)) } else if !c.NetMode.IsHost() && !c.NetMode.IsNone() { hasUserns := c.UsernsMode.IsContainer() || c.UsernsMode.IsNS() || len(c.IDMappings.UIDMap) > 0 || len(c.IDMappings.GIDMap) > 0 - postConfigureNetNS := c.NetMode.IsSlirp4netns() || (hasUserns && !c.UsernsMode.IsHost()) + postConfigureNetNS := hasUserns && !c.UsernsMode.IsHost() options = append(options, libpod.WithNetNS(portBindings, postConfigureNetNS, string(c.NetMode), networks)) } diff --git a/pkg/spec/spec_test.go b/pkg/spec/spec_test.go index 0abff491b..2f91e1b21 100644 --- a/pkg/spec/spec_test.go +++ b/pkg/spec/spec_test.go @@ -4,6 +4,8 @@ import ( "runtime" "testing" + "github.com/containers/libpod/pkg/cgroups" + "github.com/containers/libpod/pkg/rootless" "github.com/containers/libpod/pkg/sysinfo" "github.com/containers/storage" "github.com/containers/storage/pkg/idtools" @@ -26,14 +28,30 @@ func makeTestCreateConfig() *CreateConfig { return cc } -// TestPIDsLimit verifies the given pid-limit is correctly defined in the spec -func TestPIDsLimit(t *testing.T) { +func doCommonSkipChecks(t *testing.T) { // The default configuration of podman enables seccomp, which is not available on non-Linux systems. // Thus, any tests that use the default seccomp setting would fail. // Skip the tests on non-Linux platforms rather than explicitly disable seccomp in the test and possibly affect the test result. if runtime.GOOS != "linux" { t.Skip("seccomp, which is enabled by default, is only supported on Linux") } + + if rootless.IsRootless() { + isCgroupV2, err := cgroups.IsCgroup2UnifiedMode() + if err != nil { + t.Errorf("unexpected error: %v", err) + } + + if !isCgroupV2 { + t.Skip("cgroups v1 cannot be used when rootless") + } + } +} + +// TestPIDsLimit verifies the given pid-limit is correctly defined in the spec +func TestPIDsLimit(t *testing.T) { + doCommonSkipChecks(t) + if !sysInfo.PidsLimit { t.Skip("running test not supported by the host system") } @@ -50,12 +68,8 @@ func TestPIDsLimit(t *testing.T) { // TestBLKIOWeightDevice verifies the given blkio weight is correctly set in the // spec. func TestBLKIOWeightDevice(t *testing.T) { - // The default configuration of podman enables seccomp, which is not available on non-Linux systems. - // Thus, any tests that use the default seccomp setting would fail. - // Skip the tests on non-Linux platforms rather than explicitly disable seccomp in the test and possibly affect the test result. - if runtime.GOOS != "linux" { - t.Skip("seccomp, which is enabled by default, is only supported on Linux") - } + doCommonSkipChecks(t) + if !sysInfo.BlkioWeightDevice { t.Skip("running test not supported by the host system") } @@ -75,12 +89,8 @@ func TestBLKIOWeightDevice(t *testing.T) { // TestMemorySwap verifies that the given swap memory limit is correctly set in // the spec. func TestMemorySwap(t *testing.T) { - // The default configuration of podman enables seccomp, which is not available on non-Linux systems. - // Thus, any tests that use the default seccomp setting would fail. - // Skip the tests on non-Linux platforms rather than explicitly disable seccomp in the test and possibly affect the test result. - if runtime.GOOS != "linux" { - t.Skip("seccomp, which is enabled by default, is only supported on Linux") - } + doCommonSkipChecks(t) + if !sysInfo.SwapLimit { t.Skip("running test not supported by the host system") } diff --git a/pkg/spec/storage.go b/pkg/spec/storage.go index bc0eaad6d..93919dd0a 100644 --- a/pkg/spec/storage.go +++ b/pkg/spec/storage.go @@ -168,6 +168,9 @@ func (config *CreateConfig) parseVolumes(runtime *libpod.Runtime) ([]spec.Mount, if _, ok := baseMounts[dest]; ok { continue } + if _, ok := baseVolumes[dest]; ok { + continue + } localOpts := options if dest == "/run" { localOpts = append(localOpts, "noexec", "size=65536k") @@ -220,6 +223,7 @@ func (config *CreateConfig) parseVolumes(runtime *libpod.Runtime) ([]spec.Mount, // volumes, and return a list of them. // Conflicts are resolved simply - the last container specified wins. // Container names may be suffixed by mount options after a colon. +// TODO: We should clean these paths if possible func (config *CreateConfig) getVolumesFrom(runtime *libpod.Runtime) (map[string]spec.Mount, map[string]*libpod.ContainerNamedVolume, error) { // Both of these are maps of mount destination to mount type. // We ensure that each destination is only mounted to once in this way. @@ -389,7 +393,7 @@ func getBindMount(args []string) (spec.Mount, error) { Type: TypeBind, } - var setSource, setDest, setRORW, setSuid, setDev, setExec bool + var setSource, setDest, setRORW, setSuid, setDev, setExec, setRelabel bool for _, val := range args { kv := strings.Split(val, "=") @@ -465,8 +469,24 @@ func getBindMount(args []string) (spec.Mount, error) { if err := parse.ValidateVolumeCtrDir(kv[1]); err != nil { return newMount, err } - newMount.Destination = kv[1] + newMount.Destination = filepath.Clean(kv[1]) setDest = true + case "relabel": + if setRelabel { + return newMount, errors.Wrapf(optionArgError, "cannot pass 'relabel' option more than once") + } + setRelabel = true + if len(kv) != 2 { + return newMount, errors.Wrapf(util.ErrBadMntOption, "%s mount option must be 'private' or 'shared'", kv[0]) + } + switch kv[1] { + case "private": + newMount.Options = append(newMount.Options, "z") + case "shared": + newMount.Options = append(newMount.Options, "Z") + default: + return newMount, errors.Wrapf(util.ErrBadMntOption, "%s mount option must be 'private' or 'shared'", kv[0]) + } default: return newMount, errors.Wrapf(util.ErrBadMntOption, kv[0]) } @@ -543,7 +563,7 @@ func getTmpfsMount(args []string) (spec.Mount, error) { if err := parse.ValidateVolumeCtrDir(kv[1]); err != nil { return newMount, err } - newMount.Destination = kv[1] + newMount.Destination = filepath.Clean(kv[1]) setDest = true default: return newMount, errors.Wrapf(util.ErrBadMntOption, kv[0]) @@ -607,7 +627,7 @@ func getNamedVolume(args []string) (*libpod.ContainerNamedVolume, error) { if err := parse.ValidateVolumeCtrDir(kv[1]); err != nil { return nil, err } - newVolume.Dest = kv[1] + newVolume.Dest = filepath.Clean(kv[1]) setDest = true default: return nil, errors.Wrapf(util.ErrBadMntOption, kv[0]) @@ -662,10 +682,12 @@ func (config *CreateConfig) getVolumeMounts() (map[string]spec.Mount, map[string return nil, nil, err } + cleanDest := filepath.Clean(dest) + if strings.HasPrefix(src, "/") || strings.HasPrefix(src, ".") { // This is not a named volume newMount := spec.Mount{ - Destination: dest, + Destination: cleanDest, Type: string(TypeBind), Source: src, Options: options, @@ -678,7 +700,7 @@ func (config *CreateConfig) getVolumeMounts() (map[string]spec.Mount, map[string // This is a named volume newNamedVol := new(libpod.ContainerNamedVolume) newNamedVol.Name = src - newNamedVol.Dest = dest + newNamedVol.Dest = cleanDest newNamedVol.Options = options if _, ok := volumes[newNamedVol.Dest]; ok { @@ -703,10 +725,11 @@ func (config *CreateConfig) getImageVolumes() (map[string]spec.Mount, map[string } for vol := range config.BuiltinImgVolumes { + cleanDest := filepath.Clean(vol) if config.ImageVolumeType == "tmpfs" { // Tmpfs image volumes are handled as mounts mount := spec.Mount{ - Destination: vol, + Destination: cleanDest, Source: TypeTmpfs, Type: TypeTmpfs, Options: []string{"rprivate", "rw", "nodev"}, @@ -716,7 +739,7 @@ func (config *CreateConfig) getImageVolumes() (map[string]spec.Mount, map[string namedVolume := new(libpod.ContainerNamedVolume) namedVolume.Name = stringid.GenerateNonCryptoID() namedVolume.Options = []string{"rprivate", "rw", "nodev"} - namedVolume.Dest = vol + namedVolume.Dest = cleanDest volumes[vol] = namedVolume } } @@ -744,7 +767,7 @@ func (config *CreateConfig) getTmpfsMounts() (map[string]spec.Mount, error) { } mount := spec.Mount{ - Destination: destPath, + Destination: filepath.Clean(destPath), Type: string(TypeTmpfs), Options: options, Source: string(TypeTmpfs), diff --git a/pkg/util/utils.go b/pkg/util/utils.go index 67cb5c24b..edcad1d1b 100644 --- a/pkg/util/utils.go +++ b/pkg/util/utils.go @@ -424,3 +424,19 @@ func ValidatePullType(pullType string) (PullType, error) { return PullImageMissing, errors.Errorf("invalid pull type %q", pullType) } } + +// ExitCode reads the error message when failing to executing container process +// and then returns 0 if no error, 126 if command does not exist, or 127 for +// all other errors +func ExitCode(err error) int { + if err == nil { + return 0 + } + e := strings.ToLower(err.Error()) + if strings.Contains(e, "file not found") || + strings.Contains(e, "no such file or directory") { + return 127 + } + + return 126 +} diff --git a/pkg/varlinkapi/attach.go b/pkg/varlinkapi/attach.go index 1f8d48eb9..f8557ae0c 100644 --- a/pkg/varlinkapi/attach.go +++ b/pkg/varlinkapi/attach.go @@ -9,7 +9,9 @@ import ( "github.com/containers/libpod/cmd/podman/varlink" "github.com/containers/libpod/libpod" "github.com/containers/libpod/libpod/define" + "github.com/containers/libpod/libpod/events" "github.com/containers/libpod/pkg/varlinkapi/virtwriter" + "github.com/pkg/errors" "github.com/sirupsen/logrus" "k8s.io/client-go/tools/remotecommand" ) @@ -63,7 +65,9 @@ func (i *LibpodAPI) Attach(call iopodman.VarlinkCall, name string, detachKeys st } // ACK the client upgrade request - call.ReplyAttach() + if err := call.ReplyAttach(); err != nil { + return call.ReplyErrorOccurred(err.Error()) + } reader, writer, _, pw, streams := setupStreams(call) @@ -79,11 +83,36 @@ func (i *LibpodAPI) Attach(call iopodman.VarlinkCall, name string, detachKeys st finalErr = startAndAttach(ctr, streams, detachKeys, resize, errChan) } + exitCode := define.ExitCode(finalErr) if finalErr != define.ErrDetach && finalErr != nil { logrus.Error(finalErr) + } else { + if ecode, err := ctr.Wait(); err != nil { + if errors.Cause(err) == define.ErrNoSuchCtr { + // Check events + event, err := i.Runtime.GetLastContainerEvent(ctr.ID(), events.Exited) + if err != nil { + logrus.Errorf("Cannot get exit code: %v", err) + exitCode = define.ExecErrorCodeNotFound + } else { + exitCode = event.ContainerExitCode + } + } else { + exitCode = define.ExitCode(err) + } + } else { + exitCode = int(ecode) + } + } + + if ctr.AutoRemove() { + err := i.Runtime.RemoveContainer(getContext(), ctr, false, false) + if err != nil { + logrus.Errorf("Failed to remove container %s: %s", ctr.ID(), err.Error()) + } } - if err = virtwriter.HangUp(writer, 0); err != nil { + if err = virtwriter.HangUp(writer, uint32(exitCode)); err != nil { logrus.Errorf("Failed to HANG-UP attach to %s: %s", ctr.ID(), err.Error()) } return call.Writer.Flush() diff --git a/pkg/varlinkapi/containers.go b/pkg/varlinkapi/containers.go index 2dcdbc089..93f9d4fe3 100644 --- a/pkg/varlinkapi/containers.go +++ b/pkg/varlinkapi/containers.go @@ -319,12 +319,14 @@ func (i *LibpodAPI) ExportContainer(call iopodman.VarlinkCall, name, outPath str // GetContainerStats ... func (i *LibpodAPI) GetContainerStats(call iopodman.VarlinkCall, name string) error { - cgroupv2, err := cgroups.IsCgroup2UnifiedMode() - if err != nil { - return call.ReplyErrorOccurred(err.Error()) - } - if rootless.IsRootless() && !cgroupv2 { - return call.ReplyErrRequiresCgroupsV2ForRootless("rootless containers cannot report container stats") + if rootless.IsRootless() { + cgroupv2, err := cgroups.IsCgroup2UnifiedMode() + if err != nil { + return call.ReplyErrorOccurred(err.Error()) + } + if !cgroupv2 { + return call.ReplyErrRequiresCgroupsV2ForRootless("rootless containers cannot report container stats") + } } ctr, err := i.Runtime.LookupContainer(name) if err != nil { diff --git a/pkg/varlinkapi/pods.go b/pkg/varlinkapi/pods.go index c0fd8b1f7..9b659f66b 100644 --- a/pkg/varlinkapi/pods.go +++ b/pkg/varlinkapi/pods.go @@ -5,12 +5,12 @@ package varlinkapi import ( "encoding/json" "fmt" - "github.com/containers/libpod/pkg/adapter/shortcuts" "syscall" "github.com/containers/libpod/cmd/podman/shared" "github.com/containers/libpod/cmd/podman/varlink" "github.com/containers/libpod/libpod" + "github.com/containers/libpod/pkg/adapter/shortcuts" ) // CreatePod ... |