From bb589bec24ede0ca7bc981a5a285fb66d7242655 Mon Sep 17 00:00:00 2001 From: Daniel J Walsh Date: Thu, 13 May 2021 08:45:29 -0400 Subject: Fix problem copying files when container is in host pid namespace When attempting to copy files into and out of running containers within the host pidnamespace, the code was attempting to join the host pidns again, and getting an error. This was causing the podman cp command to fail. Since we are already in the host pid namespace, we should not be attempting to join. This PR adds a check to see if the container is in NOT host pid namespace, and only then attempts to join. Fixes: https://github.com/containers/podman/issues/9985 Signed-off-by: Daniel J Walsh --- libpod/container_copy_linux.go | 25 ++++++++++++++++++------- libpod/container_inspect.go | 23 +++++++++++++++++++++++ 2 files changed, 41 insertions(+), 7 deletions(-) (limited to 'libpod') diff --git a/libpod/container_copy_linux.go b/libpod/container_copy_linux.go index 5c275c641..0ab322829 100644 --- a/libpod/container_copy_linux.go +++ b/libpod/container_copy_linux.go @@ -237,21 +237,32 @@ func (c *Container) joinMountAndExec(ctx context.Context, f func() error) error } defer mountFD.Close() - pidFD, err := getFD(PIDNS) + inHostPidNS, err := c.inHostPidNS() if err != nil { - errChan <- err + errChan <- errors.Wrap(err, "checking inHostPidNS") return } - defer pidFD.Close() - if err := unix.Unshare(unix.CLONE_NEWNS); err != nil { - errChan <- err - return + var pidFD *os.File + if !inHostPidNS { + pidFD, err = getFD(PIDNS) + if err != nil { + errChan <- err + return + } + defer pidFD.Close() } - if err := unix.Setns(int(pidFD.Fd()), unix.CLONE_NEWPID); err != nil { + + if err := unix.Unshare(unix.CLONE_NEWNS); err != nil { errChan <- err return } + if pidFD != nil { + if err := unix.Setns(int(pidFD.Fd()), unix.CLONE_NEWPID); err != nil { + errChan <- err + return + } + } if err := unix.Setns(int(mountFD.Fd()), unix.CLONE_NEWNS); err != nil { errChan <- err return diff --git a/libpod/container_inspect.go b/libpod/container_inspect.go index 5b2103c92..b38c35697 100644 --- a/libpod/container_inspect.go +++ b/libpod/container_inspect.go @@ -890,3 +890,26 @@ func (c *Container) generateInspectContainerHostConfig(ctrSpec *spec.Spec, named return hostConfig, nil } + +// Return true if the container is running in the host's PID NS. +func (c *Container) inHostPidNS() (bool, error) { + if c.config.PIDNsCtr != "" { + return false, nil + } + ctrSpec, err := c.specFromState() + if err != nil { + return false, err + } + if ctrSpec.Linux != nil { + // Locate the spec's PID namespace. + // If there is none, it's pid=host. + // If there is one and it has a path, it's "ns:". + // If there is no path, it's default - the empty string. + for _, ns := range ctrSpec.Linux.Namespaces { + if ns.Type == spec.PIDNamespace { + return false, nil + } + } + } + return true, nil +} -- cgit v1.2.3-54-g00ecf From efa15b96c5d11796cdfbf2fdbe10664ec91b4c29 Mon Sep 17 00:00:00 2001 From: Valentin Rothberg Date: Wed, 5 May 2021 15:43:50 +0200 Subject: add libimage events libimage now supports events which `libpod.Runtime` now uses for image events. Signed-off-by: Valentin Rothberg Signed-off-by: Matthew Heon --- libpod/runtime.go | 90 +++++++++++++++++++++++++++++++++++++++------ test/system/090-events.bats | 36 ++++++++++++++++++ 2 files changed, 114 insertions(+), 12 deletions(-) (limited to 'libpod') diff --git a/libpod/runtime.go b/libpod/runtime.go index 80fe92b54..2cf2b3f79 100644 --- a/libpod/runtime.go +++ b/libpod/runtime.go @@ -13,6 +13,7 @@ import ( "strings" "sync" "syscall" + "time" "github.com/containers/common/libimage" "github.com/containers/common/pkg/config" @@ -67,17 +68,18 @@ type Runtime struct { storageConfig storage.StoreOptions storageSet storageSet - state State - store storage.Store - storageService *storageService - imageContext *types.SystemContext - defaultOCIRuntime OCIRuntime - ociRuntimes map[string]OCIRuntime - runtimeFlags []string - netPlugin ocicni.CNIPlugin - conmonPath string - libimageRuntime *libimage.Runtime - lockManager lock.Manager + state State + store storage.Store + storageService *storageService + imageContext *types.SystemContext + defaultOCIRuntime OCIRuntime + ociRuntimes map[string]OCIRuntime + runtimeFlags []string + netPlugin ocicni.CNIPlugin + conmonPath string + libimageRuntime *libimage.Runtime + libimageEventsShutdown chan bool + lockManager lock.Manager // doRenumber indicates that the runtime should perform a lock renumber // during initialization. @@ -212,6 +214,8 @@ func newRuntimeFromConfig(ctx context.Context, conf *config.Config, options ...R return nil, err } + runtime.libimageEventsShutdown = make(chan bool) + return runtime, nil } @@ -677,6 +681,62 @@ func (r *Runtime) GetConfig() (*config.Config, error) { return config, nil } +// libimageEventsMap translates a libimage event type to a libpod event status. +var libimageEventsMap = map[libimage.EventType]events.Status{ + libimage.EventTypeImagePull: events.Pull, + libimage.EventTypeImagePush: events.Push, + libimage.EventTypeImageRemove: events.Remove, + libimage.EventTypeImageLoad: events.LoadFromArchive, + libimage.EventTypeImageSave: events.Save, + libimage.EventTypeImageTag: events.Tag, + libimage.EventTypeImageUntag: events.Untag, + libimage.EventTypeImageMount: events.Mount, + libimage.EventTypeImageUnmount: events.Unmount, +} + +// libimageEvents spawns a goroutine in the background which is listenting for +// events on the libimage.Runtime. The gourtine will be cleaned up implicitly +// when the main() exists. +func (r *Runtime) libimageEvents() { + toLibpodEventStatus := func(e *libimage.Event) events.Status { + status, found := libimageEventsMap[e.Type] + if !found { + return "Unknown" + } + return status + } + + go func() { + eventChannel := r.libimageRuntime.EventChannel() + + for { + // Make sure to read and write all events before + // checking if we're about to shutdown. + for len(eventChannel) > 0 { + libimageEvent := <-eventChannel + e := events.Event{ + ID: libimageEvent.ID, + Name: libimageEvent.Name, + Status: toLibpodEventStatus(libimageEvent), + Time: libimageEvent.Time, + Type: events.Image, + } + if err := r.eventer.Write(e); err != nil { + logrus.Errorf("unable to write image event: %q", err) + } + } + + select { + case <-r.libimageEventsShutdown: + return + + default: + time.Sleep(100 * time.Millisecond) + } + } + }() +} + // DeferredShutdown shuts down the runtime without exposing any // errors. This is only meant to be used when the runtime is being // shutdown within a defer statement; else use Shutdown @@ -716,7 +776,11 @@ func (r *Runtime) Shutdown(force bool) error { // If no store was requested, it can be nil and there is no need to // attempt to shut it down if r.store != nil { - if _, err := r.store.Shutdown(force); err != nil { + // Wait for the events to be written. + r.libimageEventsShutdown <- true + + // Note that the libimage runtime shuts down the store. + if err := r.libimageRuntime.Shutdown(force); err != nil { lastError = errors.Wrapf(err, "error shutting down container storage") } } @@ -842,6 +906,8 @@ func (r *Runtime) configureStore() error { return err } r.libimageRuntime = libimageRuntime + // Run the libimage events routine. + r.libimageEvents() return nil } diff --git a/test/system/090-events.bats b/test/system/090-events.bats index 8a9db41fa..19bee5506 100644 --- a/test/system/090-events.bats +++ b/test/system/090-events.bats @@ -25,3 +25,39 @@ load helpers run_podman events --filter type=container --filter container=$cname --filter event=start --stream=false is "$output" "$expect" "filtering just by label" } + +@test "image events" { + skip_if_remote "FIXME: remove events on podman-remote seem to be broken" + pushedDir=$PODMAN_TMPDIR/dir + mkdir -p $pushedDir + + tarball=$PODMAN_TMPDIR/ball.tar + + run_podman image inspect --format "{{.ID}}" $IMAGE + imageID="$output" + + t0=$(date --iso-8601=seconds) + tag=registry.com/$(random_string 10 | tr A-Z a-z) + + # Force using the file backend since the journal backend is eating events + # (see containers/podman/pull/10219#issuecomment-842325032). + run_podman --events-backend=file push $IMAGE dir:$pushedDir + run_podman --events-backend=file save $IMAGE -o $tarball + run_podman --events-backend=file load -i $tarball + run_podman --events-backend=file pull docker-archive:$tarball + run_podman --events-backend=file tag $IMAGE $tag + run_podman --events-backend=file untag $IMAGE $tag + run_podman --events-backend=file tag $IMAGE $tag + run_podman --events-backend=file rmi $tag + + run_podman --events-backend=file events --stream=false --filter type=image --since $t0 + is "$output" ".*image push $imageID dir:$pushedDir +.*image save $imageID $tarball +.*image loadfromarchive *$tarball +.*image pull *docker-archive:$tarball +.*image tag $imageID $tag +.*image untag $imageID $tag:latest +.*image tag $imageID $tag +.*image remove $imageID $tag.*" \ + "podman events" +} -- cgit v1.2.3-54-g00ecf From c53638e9f61e61b1344cbf090c9766a1891c8a44 Mon Sep 17 00:00:00 2001 From: flouthoc Date: Wed, 19 May 2021 01:19:20 +0530 Subject: Podman info add support for status of cgroup controllers Signed-off-by: flouthoc --- docs/source/markdown/podman-info.1.md | 13 +++++ libpod/define/info.go | 31 +++++----- libpod/info.go | 44 ++++++++------ pkg/cgroups/cgroups.go | 106 +++++++++++++++++++++++++++++++--- test/e2e/info_test.go | 10 ++++ 5 files changed, 163 insertions(+), 41 deletions(-) (limited to 'libpod') diff --git a/docs/source/markdown/podman-info.1.md b/docs/source/markdown/podman-info.1.md index 4af51d3eb..227fbd92d 100644 --- a/docs/source/markdown/podman-info.1.md +++ b/docs/source/markdown/podman-info.1.md @@ -32,6 +32,12 @@ $ podman info host: arch: amd64 buildahVersion: 1.19.0-dev + cgroupControllers: + - cpuset + - cpu + - io + - memory + - pids cgroupManager: systemd cgroupVersion: v2 conmon: @@ -145,6 +151,13 @@ Run podman info with JSON formatted response: "buildahVersion": "1.19.0-dev", "cgroupManager": "systemd", "cgroupVersion": "v2", + "cgroupControllers": [ + "cpuset", + "cpu", + "io", + "memory", + "pids" + ], "conmon": { "package": "conmon-2.0.22-2.fc33.x86_64", "path": "/usr/bin/conmon", diff --git a/libpod/define/info.go b/libpod/define/info.go index c9d6877c0..de709be74 100644 --- a/libpod/define/info.go +++ b/libpod/define/info.go @@ -23,21 +23,22 @@ type SecurityInfo struct { // HostInfo describes the libpod host type HostInfo struct { - Arch string `json:"arch"` - BuildahVersion string `json:"buildahVersion"` - CgroupManager string `json:"cgroupManager"` - CGroupsVersion string `json:"cgroupVersion"` - Conmon *ConmonInfo `json:"conmon"` - CPUs int `json:"cpus"` - Distribution DistributionInfo `json:"distribution"` - EventLogger string `json:"eventLogger"` - Hostname string `json:"hostname"` - IDMappings IDMappings `json:"idMappings,omitempty"` - Kernel string `json:"kernel"` - MemFree int64 `json:"memFree"` - MemTotal int64 `json:"memTotal"` - OCIRuntime *OCIRuntimeInfo `json:"ociRuntime"` - OS string `json:"os"` + Arch string `json:"arch"` + BuildahVersion string `json:"buildahVersion"` + CgroupManager string `json:"cgroupManager"` + CGroupsVersion string `json:"cgroupVersion"` + CgroupControllers []string `json:"cgroupControllers"` + Conmon *ConmonInfo `json:"conmon"` + CPUs int `json:"cpus"` + Distribution DistributionInfo `json:"distribution"` + EventLogger string `json:"eventLogger"` + Hostname string `json:"hostname"` + IDMappings IDMappings `json:"idMappings,omitempty"` + Kernel string `json:"kernel"` + MemFree int64 `json:"memFree"` + MemTotal int64 `json:"memTotal"` + OCIRuntime *OCIRuntimeInfo `json:"ociRuntime"` + OS string `json:"os"` // RemoteSocket returns the UNIX domain socket the Podman service is listening on RemoteSocket *RemoteSocket `json:"remoteSocket,omitempty"` RuntimeInfo map[string]interface{} `json:"runtimeInfo,omitempty"` diff --git a/libpod/info.go b/libpod/info.go index 7a28a4cf7..461e39a48 100644 --- a/libpod/info.go +++ b/libpod/info.go @@ -93,20 +93,33 @@ func (r *Runtime) hostInfo() (*define.HostInfo, error) { return nil, errors.Wrapf(err, "error getting Seccomp profile path") } + // CGroups version + unified, err := cgroups.IsCgroup2UnifiedMode() + if err != nil { + return nil, errors.Wrapf(err, "error reading cgroups mode") + } + + // Get Map of all available controllers + availableControllers, err := cgroups.GetAvailableControllers(nil, unified) + if err != nil { + return nil, errors.Wrapf(err, "error getting available cgroup controllers") + } + info := define.HostInfo{ - Arch: runtime.GOARCH, - BuildahVersion: buildah.Version, - CgroupManager: r.config.Engine.CgroupManager, - Linkmode: linkmode.Linkmode(), - CPUs: runtime.NumCPU(), - Distribution: hostDistributionInfo, - EventLogger: r.eventer.String(), - Hostname: host, - IDMappings: define.IDMappings{}, - Kernel: kv, - MemFree: mi.MemFree, - MemTotal: mi.MemTotal, - OS: runtime.GOOS, + Arch: runtime.GOARCH, + BuildahVersion: buildah.Version, + CgroupManager: r.config.Engine.CgroupManager, + CgroupControllers: availableControllers, + Linkmode: linkmode.Linkmode(), + CPUs: runtime.NumCPU(), + Distribution: hostDistributionInfo, + EventLogger: r.eventer.String(), + Hostname: host, + IDMappings: define.IDMappings{}, + Kernel: kv, + MemFree: mi.MemFree, + MemTotal: mi.MemTotal, + OS: runtime.GOOS, Security: define.SecurityInfo{ AppArmorEnabled: apparmor.IsEnabled(), DefaultCapabilities: strings.Join(r.config.Containers.DefaultCapabilities, ","), @@ -120,11 +133,6 @@ func (r *Runtime) hostInfo() (*define.HostInfo, error) { SwapTotal: mi.SwapTotal, } - // CGroups version - unified, err := cgroups.IsCgroup2UnifiedMode() - if err != nil { - return nil, errors.Wrapf(err, "error reading cgroups mode") - } cgroupVersion := "v1" if unified { cgroupVersion = "v2" diff --git a/pkg/cgroups/cgroups.go b/pkg/cgroups/cgroups.go index aefb5183b..911edeb5b 100644 --- a/pkg/cgroups/cgroups.go +++ b/pkg/cgroups/cgroups.go @@ -128,28 +128,118 @@ func init() { // getAvailableControllers get the available controllers func getAvailableControllers(exclude map[string]controllerHandler, cgroup2 bool) ([]controller, error) { if cgroup2 { - return nil, fmt.Errorf("getAvailableControllers not implemented yet for cgroup v2") + controllers := []controller{} + subtreeControl := cgroupRoot + "/cgroup.subtree_control" + // rootless cgroupv2: check available controllers for current user ,systemd or servicescope will inherit + if rootless.IsRootless() { + userSlice, err := getCgroupPathForCurrentProcess() + if err != nil { + return controllers, err + } + //userSlice already contains '/' so not adding here + basePath := cgroupRoot + userSlice + subtreeControl = fmt.Sprintf("%s/cgroup.subtree_control", basePath) + } + subtreeControlBytes, err := ioutil.ReadFile(subtreeControl) + if err != nil { + return nil, errors.Wrapf(err, "failed while reading controllers for cgroup v2 from %q", subtreeControl) + } + for _, controllerName := range strings.Fields(string(subtreeControlBytes)) { + c := controller{ + name: controllerName, + symlink: false, + } + controllers = append(controllers, c) + } + return controllers, nil } - infos, err := ioutil.ReadDir(cgroupRoot) - if err != nil { - return nil, err - } + subsystems, _ := cgroupV1GetAllSubsystems() controllers := []controller{} - for _, i := range infos { - name := i.Name() + // cgroupv1 and rootless: No subsystem is available: delegation is unsafe. + if rootless.IsRootless() { + return controllers, nil + } + + for _, name := range subsystems { if _, found := exclude[name]; found { continue } + isSymLink := false + fileInfo, err := os.Stat(cgroupRoot + "/" + name) + if err != nil { + isSymLink = !fileInfo.IsDir() + } c := controller{ name: name, - symlink: !i.IsDir(), + symlink: isSymLink, } controllers = append(controllers, c) } + return controllers, nil } +// GetAvailableControllers get string:bool map of all the available controllers +func GetAvailableControllers(exclude map[string]controllerHandler, cgroup2 bool) ([]string, error) { + availableControllers, err := getAvailableControllers(exclude, cgroup2) + if err != nil { + return nil, err + } + controllerList := []string{} + for _, controller := range availableControllers { + controllerList = append(controllerList, controller.name) + } + + return controllerList, nil +} + +func cgroupV1GetAllSubsystems() ([]string, error) { + f, err := os.Open("/proc/cgroups") + if err != nil { + return nil, err + } + defer f.Close() + + subsystems := []string{} + + s := bufio.NewScanner(f) + for s.Scan() { + text := s.Text() + if text[0] != '#' { + parts := strings.Fields(text) + if len(parts) >= 4 && parts[3] != "0" { + subsystems = append(subsystems, parts[0]) + } + } + } + if err := s.Err(); err != nil { + return nil, err + } + return subsystems, nil +} + +func getCgroupPathForCurrentProcess() (string, error) { + path := fmt.Sprintf("/proc/%d/cgroup", os.Getpid()) + f, err := os.Open(path) + if err != nil { + return "", err + } + defer f.Close() + + cgroupPath := "" + s := bufio.NewScanner(f) + for s.Scan() { + text := s.Text() + procEntries := strings.SplitN(text, "::", 2) + cgroupPath = procEntries[1] + } + if err := s.Err(); err != nil { + return cgroupPath, err + } + return cgroupPath, nil +} + // getCgroupv1Path is a helper function to get the cgroup v1 path func (c *CgroupControl) getCgroupv1Path(name string) string { return filepath.Join(cgroupRoot, name, c.path) diff --git a/test/e2e/info_test.go b/test/e2e/info_test.go index 60136bcc2..f5b70d6bf 100644 --- a/test/e2e/info_test.go +++ b/test/e2e/info_test.go @@ -135,4 +135,14 @@ var _ = Describe("Podman Info", func() { Expect(session.OutputToString()).To(ContainSubstring("false")) } }) + + It("Podman info must contain cgroupControllers with ReleventControllers", func() { + SkipIfRootless("Hard to tell which controllers are going to be enabled for rootless") + SkipIfRootlessCgroupsV1("Disable cgroups not supported on cgroupv1 for rootless users") + session := podmanTest.Podman([]string{"info", "--format", "{{.Host.CgroupControllers}}"}) + session.WaitWithDefaultTimeout() + Expect(session).To(Exit(0)) + Expect(session.OutputToString()).To(ContainSubstring("memory")) + Expect(session.OutputToString()).To(ContainSubstring("pids")) + }) }) -- cgit v1.2.3-54-g00ecf From 5eecc2761b4439d8b8f41da38e7669415354d63f Mon Sep 17 00:00:00 2001 From: Daniel J Walsh Date: Fri, 21 May 2021 16:02:13 -0400 Subject: Improve OCI Runtime error ErrOCIRuntimeNotFound error is misleading. Try to make it more understandable to the user that the OCI Runtime IE crun or runc is not missing, but the command they attempted to run within the container is missing. [NO TESTS NEEDED] Regular tests should handle this. Fixes: https://github.com/containers/podman/issues/10432 Signed-off-by: Daniel J Walsh --- libpod/define/errors.go | 2 +- test/system/030-run.bats | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) (limited to 'libpod') diff --git a/libpod/define/errors.go b/libpod/define/errors.go index 81bf5f69c..9fd210eed 100644 --- a/libpod/define/errors.go +++ b/libpod/define/errors.go @@ -152,7 +152,7 @@ var ( // ErrOCIRuntimeNotFound indicates the OCI runtime attempted to invoke a command // that was not found - ErrOCIRuntimeNotFound = errors.New("OCI not found") + ErrOCIRuntimeNotFound = errors.New("OCI runtime attempted to invoke a command that was not found") // ErrOCIRuntimeUnavailable indicates that the OCI runtime associated to a container // could not be found in the configuration diff --git a/test/system/030-run.bats b/test/system/030-run.bats index 9a136ff13..ccae703e5 100644 --- a/test/system/030-run.bats +++ b/test/system/030-run.bats @@ -14,7 +14,7 @@ load helpers # ...but check the configured runtime engine, and switch to crun as needed run_podman info --format '{{ .Host.OCIRuntime.Path }}' if expr "$output" : ".*/crun"; then - err_no_such_cmd="Error: executable file.* not found in \$PATH: No such file or directory: OCI not found" + err_no_such_cmd="Error: executable file.* not found in \$PATH: No such file or directory: OCI runtime attempted to invoke a command that was not found" err_no_exec_dir="Error: open executable: Operation not permitted: OCI permission denied" fi -- cgit v1.2.3-54-g00ecf