//go:build !remote package ps import ( "errors" "fmt" "os" "path/filepath" "regexp" "sort" "strconv" "strings" "time" libnetworkTypes "github.com/containers/common/libnetwork/types" "github.com/containers/podman/v5/libpod" "github.com/containers/podman/v5/libpod/define" "github.com/containers/podman/v5/pkg/domain/entities" "github.com/containers/podman/v5/pkg/domain/filters" psdefine "github.com/containers/podman/v5/pkg/ps/define" "github.com/containers/storage" "github.com/containers/storage/types" "github.com/sirupsen/logrus" ) // ExternalContainerFilter is a function to determine whether a container list is included // in command output. Container lists to be outputted are tested using the function. // A true return will include the container list, a false return will exclude it. type ExternalContainerFilter func(*entities.ListContainer) bool func GetContainerLists(runtime *libpod.Runtime, options entities.ContainerListOptions) ([]entities.ListContainer, error) { var ( pss = []entities.ListContainer{} ) filterFuncs := make([]libpod.ContainerFilter, 0, len(options.Filters)) filterExtFuncs := make([]entities.ExternalContainerFilter, 0, len(options.Filters)) all := options.All || options.Last > 0 if len(options.Filters) > 0 { for k, v := range options.Filters { generatedFunc, err := filters.GenerateContainerFilterFuncs(k, v, runtime) if err != nil && !options.External { return nil, err } filterFuncs = append(filterFuncs, generatedFunc) if options.External { generatedExtFunc, err := filters.GenerateExternalContainerFilterFuncs(k, v, runtime) if err != nil { return nil, err } filterExtFuncs = append(filterExtFuncs, generatedExtFunc) } } } // Docker thinks that if status is given as an input, then we should override // the all setting and always deal with all containers. if len(options.Filters["status"]) > 0 { all = true } if !all { runningOnly, err := filters.GenerateContainerFilterFuncs("status", []string{define.ContainerStateRunning.String()}, runtime) if err != nil { return nil, err } filterFuncs = append(filterFuncs, runningOnly) } // Load the containers with their states populated. This speeds things // up considerably as we use a signel DB connection to load the // containers' states instead of one per container. // // This may return slightly outdated states but that's acceptable for // listing containers; any state is outdated the point a container lock // gets released. cons, err := runtime.GetContainers(true, filterFuncs...) if err != nil { return nil, err } if options.Last > 0 { // Sort the libpod containers sort.Sort(SortCreateTime{SortContainers: cons}) // we should perform the lopping before we start getting // the expensive information on containers if options.Last < len(cons) { cons = cons[:options.Last] } } for _, con := range cons { listCon, err := ListContainerBatch(runtime, con, options) switch { // ignore both no ctr and no such pod errors as it means the ctr is gone now case errors.Is(err, define.ErrNoSuchCtr), errors.Is(err, define.ErrNoSuchPod): continue case err != nil: return nil, err default: pss = append(pss, listCon) } } if options.External { listCon, err := GetExternalContainerLists(runtime, filterExtFuncs...) if err != nil { return nil, err } pss = append(pss, listCon...) } // Sort the containers we got sort.Sort(SortPSCreateTime{SortPSContainers: pss}) if options.Last > 0 { // only return the "last" containers caller requested if options.Last < len(pss) { pss = pss[:options.Last] } } return pss, nil } // GetExternalContainerLists returns list of external containers for e.g. created by buildah func GetExternalContainerLists(runtime *libpod.Runtime, filterExtFuncs ...entities.ExternalContainerFilter) ([]entities.ListContainer, error) { var ( pss = []*entities.ListContainer{} ) externCons, err := runtime.StorageContainers() if err != nil { return nil, err } for _, con := range externCons { listCon, err := ListStorageContainer(runtime, con) switch { case errors.Is(err, types.ErrLoadError): continue // Container could have been removed since listing case errors.Is(err, types.ErrContainerUnknown): continue case err != nil: return nil, err default: pss = append(pss, &listCon) } } filteredPss := applyExternalContainersFilters(pss, filterExtFuncs...) return filteredPss, nil } // Apply container filters on bunch of external container lists func applyExternalContainersFilters(containersList []*entities.ListContainer, filters ...entities.ExternalContainerFilter) []entities.ListContainer { ctrsFiltered := make([]entities.ListContainer, 0, len(containersList)) for _, ctr := range containersList { include := true for _, filter := range filters { include = include && filter(ctr) } if include { ctrsFiltered = append(ctrsFiltered, *ctr) } } return ctrsFiltered } // ListContainerBatch is used in ps to reduce performance hits by "batching" // locks. func ListContainerBatch(rt *libpod.Runtime, ctr *libpod.Container, opts entities.ContainerListOptions) (entities.ListContainer, error) { var ( conConfig *libpod.ContainerConfig conState define.ContainerStatus err error exitCode int32 exited bool pid int size *psdefine.ContainerSize startedTime time.Time exitedTime time.Time cgroup, ipc, mnt, net, pidns, user, uts string portMappings []libnetworkTypes.PortMapping networks []string healthStatus string restartCount uint podName string ) batchErr := ctr.Batch(func(c *libpod.Container) error { if opts.Sync { if err := c.Sync(); err != nil { return fmt.Errorf("unable to update container state from OCI runtime: %w", err) } } conConfig = c.ConfigNoCopy() conState, err = c.State() if err != nil { return fmt.Errorf("unable to obtain container state: %w", err) } exitCode, exited, err = c.ExitCode() if err != nil { return fmt.Errorf("unable to obtain container exit code: %w", err) } startedTime, err = c.StartedTime() if err != nil { logrus.Errorf("Getting started time for %q: %v", c.ID(), err) } exitedTime, err = c.FinishedTime() if err != nil { logrus.Errorf("Getting exited time for %q: %v", c.ID(), err) } pid, err = c.PID() if err != nil { return fmt.Errorf("unable to obtain container pid: %w", err) } portMappings, err = c.PortMappings() if err != nil { return err } networks, err = c.Networks() if err != nil { return err } healthStatus, err = c.HealthCheckStatus() if err != nil { return err } restartCount, err = c.RestartCount() if err != nil { return err } if opts.Namespace { ctrPID := strconv.Itoa(pid) cgroup, _ = getNamespaceInfo(filepath.Join("/proc", ctrPID, "ns", "cgroup")) ipc, _ = getNamespaceInfo(filepath.Join("/proc", ctrPID, "ns", "ipc")) mnt, _ = getNamespaceInfo(filepath.Join("/proc", ctrPID, "ns", "mnt")) net, _ = getNamespaceInfo(filepath.Join("/proc", ctrPID, "ns", "net")) pidns, _ = getNamespaceInfo(filepath.Join("/proc", ctrPID, "ns", "pid")) user, _ = getNamespaceInfo(filepath.Join("/proc", ctrPID, "ns", "user")) uts, _ = getNamespaceInfo(filepath.Join("/proc", ctrPID, "ns", "uts")) } if opts.Size { size = new(psdefine.ContainerSize) rootFsSize, err := c.RootFsSize() if err != nil { logrus.Errorf("Getting root fs size for %q: %v", c.ID(), err) } rwSize, err := c.RWSize() if err != nil { logrus.Errorf("Getting rw size for %q: %v", c.ID(), err) } size.RootFsSize = rootFsSize size.RwSize = rwSize } if opts.Pod && len(conConfig.Pod) > 0 { podName, err = rt.GetPodName(conConfig.Pod) if err != nil { return fmt.Errorf("could not find container %s pod (id %s) in state: %w", conConfig.ID, conConfig.Pod, err) } } return nil }) if batchErr != nil { return entities.ListContainer{}, batchErr } ps := entities.ListContainer{ AutoRemove: ctr.AutoRemove(), CIDFile: conConfig.Spec.Annotations[define.InspectAnnotationCIDFile], Command: conConfig.Command, Created: conConfig.CreatedTime, ExitCode: exitCode, Exited: exited, ExitedAt: exitedTime.Unix(), ExposedPorts: conConfig.ExposedPorts, ID: conConfig.ID, Image: conConfig.RootfsImageName, ImageID: conConfig.RootfsImageID, IsInfra: conConfig.IsInfra, Labels: conConfig.Labels, Mounts: ctr.UserVolumes(), Names: []string{conConfig.Name}, Networks: networks, Pid: pid, Pod: conConfig.Pod, PodName: podName, Ports: portMappings, Restarts: restartCount, Size: size, StartedAt: startedTime.Unix(), State: conState.String(), Status: healthStatus, } if opts.Namespace { ps.Namespaces = entities.ListContainerNamespaces{ Cgroup: cgroup, IPC: ipc, MNT: mnt, NET: net, PIDNS: pidns, User: user, UTS: uts, } } return ps, nil } func ListStorageContainer(rt *libpod.Runtime, ctr storage.Container) (entities.ListContainer, error) { name := "unknown" if len(ctr.Names) > 0 { name = ctr.Names[0] } ps := entities.ListContainer{ ID: ctr.ID, Created: ctr.Created, ImageID: ctr.ImageID, State: "storage", Names: []string{name}, } buildahCtr, err := rt.IsBuildahContainer(ctr.ID) if err != nil { return ps, fmt.Errorf("determining buildah container for container %s: %w", ctr.ID, err) } if buildahCtr { ps.Command = []string{"buildah"} } else { ps.Command = []string{"storage"} } imageName := "" if ctr.ImageID != "" { image, _, err := rt.LibimageRuntime().LookupImage(ctr.ImageID, nil) if err != nil { return ps, err } if len(image.NamesHistory()) > 0 { imageName = image.NamesHistory()[0] } } else if buildahCtr { imageName = "scratch" } ps.Image = imageName return ps, nil } func getNamespaceInfo(path string) (string, error) { val, err := os.Readlink(path) if err != nil { return "", fmt.Errorf("getting info from %q: %w", path, err) } return getStrFromSquareBrackets(val), nil } // getStrFromSquareBrackets gets the string inside [] from a string. func getStrFromSquareBrackets(cmd string) string { reg := regexp.MustCompile(`.*\[|\].*`) arr := strings.Split(reg.ReplaceAllLiteralString(cmd, ""), ",") return strings.Join(arr, ",") } // SortContainers helps us set-up ability to sort by createTime type SortContainers []*libpod.Container func (a SortContainers) Len() int { return len(a) } func (a SortContainers) Swap(i, j int) { a[i], a[j] = a[j], a[i] } type SortCreateTime struct{ SortContainers } func (a SortCreateTime) Less(i, j int) bool { return a.SortContainers[i].CreatedTime().After(a.SortContainers[j].CreatedTime()) } // SortPSContainers helps us set-up ability to sort by createTime type SortPSContainers []entities.ListContainer func (a SortPSContainers) Len() int { return len(a) } func (a SortPSContainers) Swap(i, j int) { a[i], a[j] = a[j], a[i] } type SortPSCreateTime struct{ SortPSContainers } func (a SortPSCreateTime) Less(i, j int) bool { return a.SortPSContainers[i].Created.Before(a.SortPSContainers[j].Created) }