package libpod import ( "fmt" "sort" "strings" "github.com/containers/common/pkg/config" "github.com/containers/podman/v4/libpod/define" "github.com/containers/podman/v4/pkg/util" spec "github.com/opencontainers/runtime-spec/specs-go" "github.com/opencontainers/runtime-tools/generate" "github.com/opencontainers/runtime-tools/validate" "github.com/sirupsen/logrus" "github.com/syndtr/gocapability/capability" ) func (c *Container) platformInspectContainerHostConfig(ctrSpec *spec.Spec, hostConfig *define.InspectContainerHostConfig) error { // This is very expensive to initialize. // So we don't want to initialize it unless we absolutely have to - IE, // there are things that require a major:minor to path translation. var deviceNodes map[string]string // Resource limits if ctrSpec.Linux != nil { if ctrSpec.Linux.Resources != nil { if ctrSpec.Linux.Resources.CPU != nil { if ctrSpec.Linux.Resources.CPU.Shares != nil { hostConfig.CpuShares = *ctrSpec.Linux.Resources.CPU.Shares } if ctrSpec.Linux.Resources.CPU.Period != nil { hostConfig.CpuPeriod = *ctrSpec.Linux.Resources.CPU.Period } if ctrSpec.Linux.Resources.CPU.Quota != nil { hostConfig.CpuQuota = *ctrSpec.Linux.Resources.CPU.Quota } if ctrSpec.Linux.Resources.CPU.RealtimePeriod != nil { hostConfig.CpuRealtimePeriod = *ctrSpec.Linux.Resources.CPU.RealtimePeriod } if ctrSpec.Linux.Resources.CPU.RealtimeRuntime != nil { hostConfig.CpuRealtimeRuntime = *ctrSpec.Linux.Resources.CPU.RealtimeRuntime } hostConfig.CpusetCpus = ctrSpec.Linux.Resources.CPU.Cpus hostConfig.CpusetMems = ctrSpec.Linux.Resources.CPU.Mems } if ctrSpec.Linux.Resources.Memory != nil { if ctrSpec.Linux.Resources.Memory.Limit != nil { hostConfig.Memory = *ctrSpec.Linux.Resources.Memory.Limit } if ctrSpec.Linux.Resources.Memory.Reservation != nil { hostConfig.MemoryReservation = *ctrSpec.Linux.Resources.Memory.Reservation } if ctrSpec.Linux.Resources.Memory.Swap != nil { hostConfig.MemorySwap = *ctrSpec.Linux.Resources.Memory.Swap } if ctrSpec.Linux.Resources.Memory.Swappiness != nil { hostConfig.MemorySwappiness = int64(*ctrSpec.Linux.Resources.Memory.Swappiness) } else { // Swappiness has a default of -1 hostConfig.MemorySwappiness = -1 } if ctrSpec.Linux.Resources.Memory.DisableOOMKiller != nil { hostConfig.OomKillDisable = *ctrSpec.Linux.Resources.Memory.DisableOOMKiller } } if ctrSpec.Linux.Resources.Pids != nil { hostConfig.PidsLimit = ctrSpec.Linux.Resources.Pids.Limit } hostConfig.CgroupConf = ctrSpec.Linux.Resources.Unified if ctrSpec.Linux.Resources.BlockIO != nil { if ctrSpec.Linux.Resources.BlockIO.Weight != nil { hostConfig.BlkioWeight = *ctrSpec.Linux.Resources.BlockIO.Weight } hostConfig.BlkioWeightDevice = []define.InspectBlkioWeightDevice{} for _, dev := range ctrSpec.Linux.Resources.BlockIO.WeightDevice { key := fmt.Sprintf("%d:%d", dev.Major, dev.Minor) // TODO: how do we handle LeafWeight vs // Weight? For now, ignore anything // without Weight set. if dev.Weight == nil { logrus.Infof("Ignoring weight device %s as it lacks a weight", key) continue } if deviceNodes == nil { nodes, err := util.FindDeviceNodes() if err != nil { return err } deviceNodes = nodes } path, ok := deviceNodes[key] if !ok { logrus.Infof("Could not locate weight device %s in system devices", key) continue } weightDev := define.InspectBlkioWeightDevice{} weightDev.Path = path weightDev.Weight = *dev.Weight hostConfig.BlkioWeightDevice = append(hostConfig.BlkioWeightDevice, weightDev) } readBps, err := blkioDeviceThrottle(deviceNodes, ctrSpec.Linux.Resources.BlockIO.ThrottleReadBpsDevice) if err != nil { return err } hostConfig.BlkioDeviceReadBps = readBps writeBps, err := blkioDeviceThrottle(deviceNodes, ctrSpec.Linux.Resources.BlockIO.ThrottleWriteBpsDevice) if err != nil { return err } hostConfig.BlkioDeviceWriteBps = writeBps readIops, err := blkioDeviceThrottle(deviceNodes, ctrSpec.Linux.Resources.BlockIO.ThrottleReadIOPSDevice) if err != nil { return err } hostConfig.BlkioDeviceReadIOps = readIops writeIops, err := blkioDeviceThrottle(deviceNodes, ctrSpec.Linux.Resources.BlockIO.ThrottleWriteIOPSDevice) if err != nil { return err } hostConfig.BlkioDeviceWriteIOps = writeIops } } } // Cap add and cap drop. // We need a default set of capabilities to compare against. // The OCI generate package has one, and is commonly used, so we'll // use it. // Problem: there are 5 sets of capabilities. // Use the bounding set for this computation, it's the most encompassing // (but still not perfect). capAdd := []string{} capDrop := []string{} // No point in continuing if we got a spec without a Process block... if ctrSpec.Process != nil { // Max an O(1) lookup table for default bounding caps. boundingCaps := make(map[string]bool) g, err := generate.New("linux") if err != nil { return err } if !hostConfig.Privileged { for _, cap := range g.Config.Process.Capabilities.Bounding { boundingCaps[cap] = true } } else { // If we are privileged, use all caps. for _, cap := range capability.List() { if g.HostSpecific && cap > validate.LastCap() { continue } boundingCaps[fmt.Sprintf("CAP_%s", strings.ToUpper(cap.String()))] = true } } // Iterate through spec caps. // If it's not in default bounding caps, it was added. // If it is, delete from the default set. Whatever remains after // we finish are the dropped caps. for _, cap := range ctrSpec.Process.Capabilities.Bounding { if _, ok := boundingCaps[cap]; ok { delete(boundingCaps, cap) } else { capAdd = append(capAdd, cap) } } for cap := range boundingCaps { capDrop = append(capDrop, cap) } // Sort CapDrop so it displays in consistent order (GH #9490) sort.Strings(capDrop) } hostConfig.CapAdd = capAdd hostConfig.CapDrop = capDrop switch { case c.config.IPCNsCtr != "": hostConfig.IpcMode = fmt.Sprintf("container:%s", c.config.IPCNsCtr) case ctrSpec.Linux != nil: // Locate the spec's IPC namespace. // If there is none, it's ipc=host. // If there is one and it has a path, it's "ns:". // If no path, it's default - the empty string. for _, ns := range ctrSpec.Linux.Namespaces { if ns.Type == spec.IPCNamespace { if ns.Path != "" { hostConfig.IpcMode = fmt.Sprintf("ns:%s", ns.Path) } else { break } } } case c.config.NoShm: hostConfig.IpcMode = "none" case c.config.NoShmShare: hostConfig.IpcMode = "private" } if hostConfig.IpcMode == "" { hostConfig.IpcMode = "shareable" } // Cgroup namespace mode cgroupMode := "" if c.config.CgroupNsCtr != "" { cgroupMode = fmt.Sprintf("container:%s", c.config.CgroupNsCtr) } else if ctrSpec.Linux != nil { // Locate the spec's cgroup namespace // If there is none, it's cgroup=host. // If there is one and it has a path, it's "ns:". // If there is no path, it's private. for _, ns := range ctrSpec.Linux.Namespaces { if ns.Type == spec.CgroupNamespace { if ns.Path != "" { cgroupMode = fmt.Sprintf("ns:%s", ns.Path) } else { cgroupMode = "private" } } } if cgroupMode == "" { cgroupMode = "host" } } hostConfig.CgroupMode = cgroupMode // Cgroup parent // Need to check if it's the default, and not print if so. defaultCgroupParent := "" switch c.CgroupManager() { case config.CgroupfsCgroupsManager: defaultCgroupParent = CgroupfsDefaultCgroupParent case config.SystemdCgroupsManager: defaultCgroupParent = SystemdDefaultCgroupParent } if c.config.CgroupParent != defaultCgroupParent { hostConfig.CgroupParent = c.config.CgroupParent } hostConfig.CgroupManager = c.CgroupManager() // PID namespace mode pidMode := "" if c.config.PIDNsCtr != "" { pidMode = fmt.Sprintf("container:%s", c.config.PIDNsCtr) } else 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 { if ns.Path != "" { pidMode = fmt.Sprintf("ns:%s", ns.Path) } else { pidMode = "private" } break } } if pidMode == "" { pidMode = "host" } } hostConfig.PidMode = pidMode // UTS namespace mode utsMode := c.NamespaceMode(spec.UTSNamespace, ctrSpec) hostConfig.UTSMode = utsMode // User namespace mode usernsMode := "" if c.config.UserNsCtr != "" { usernsMode = fmt.Sprintf("container:%s", c.config.UserNsCtr) } else if ctrSpec.Linux != nil { // Locate the spec's user namespace. // If there is none, it's default - the empty string. // If there is one, it's "private" if no path, or "ns:" if // there's a path. for _, ns := range ctrSpec.Linux.Namespaces { if ns.Type == spec.UserNamespace { if ns.Path != "" { usernsMode = fmt.Sprintf("ns:%s", ns.Path) } else { usernsMode = "private" } } } } hostConfig.UsernsMode = usernsMode if c.config.IDMappings.UIDMap != nil && c.config.IDMappings.GIDMap != nil { hostConfig.IDMappings = generateIDMappings(c.config.IDMappings) } // Devices // Do not include if privileged - assumed that all devices will be // included. var err error hostConfig.Devices, err = c.GetDevices(hostConfig.Privileged, *ctrSpec, deviceNodes) if err != nil { return err } return nil }