// +build linux package libpod import ( "context" "fmt" "path" "path/filepath" "strings" "syscall" "time" "github.com/containerd/cgroups" "github.com/containers/storage/pkg/idtools" spec "github.com/opencontainers/runtime-spec/specs-go" "github.com/opencontainers/runtime-tools/generate" "github.com/opencontainers/selinux/go-selinux/label" "github.com/pkg/errors" crioAnnotations "github.com/projectatomic/libpod/pkg/annotations" "github.com/projectatomic/libpod/pkg/chrootuser" "github.com/projectatomic/libpod/pkg/rootless" "github.com/sirupsen/logrus" "golang.org/x/sys/unix" ) // cleanupCgroup cleans up residual CGroups after container execution // This is a no-op for the systemd cgroup driver func (c *Container) cleanupCgroups() error { if !c.state.CgroupCreated { logrus.Debugf("Cgroups are not present, ignoring...") return nil } if c.runtime.config.CgroupManager == SystemdCgroupsManager { return nil } // Remove the base path of the container's cgroups path := filepath.Join(c.config.CgroupParent, fmt.Sprintf("libpod-%s", c.ID())) logrus.Debugf("Removing CGroup %s", path) cgroup, err := cgroups.Load(cgroups.V1, cgroups.StaticPath(path)) if err != nil { // It's fine for the cgroup to not exist // We want it gone, it's gone if err == cgroups.ErrCgroupDeleted { return nil } return err } if err := cgroup.Delete(); err != nil { return err } c.state.CgroupCreated = false if c.valid { return c.save() } return nil } func (c *Container) mountSHM(shmOptions string) error { if err := unix.Mount("shm", c.config.ShmDir, "tmpfs", unix.MS_NOEXEC|unix.MS_NOSUID|unix.MS_NODEV, label.FormatMountLabel(shmOptions, c.config.MountLabel)); err != nil { return errors.Wrapf(err, "failed to mount shm tmpfs %q", c.config.ShmDir) } return nil } func (c *Container) unmountSHM(mount string) error { if err := unix.Unmount(mount, unix.MNT_DETACH); err != nil { if err != syscall.EINVAL { logrus.Warnf("container %s failed to unmount %s : %v", c.ID(), mount, err) } } return nil } // prepare mounts the container and sets up other required resources like net // namespaces func (c *Container) prepare() (err error) { // Mount storage if not mounted if err := c.mountStorage(); err != nil { return err } // Set up network namespace if not already set up if c.config.CreateNetNS && c.state.NetNS == nil && !c.config.PostConfigureNetNS { if err := c.runtime.createNetNS(c); err != nil { // Tear down storage before exiting to make sure we // don't leak mounts if err2 := c.cleanupStorage(); err2 != nil { logrus.Errorf("Error cleaning up storage for container %s: %v", c.ID(), err2) } return err } } return nil } // cleanupNetwork unmounts and cleans up the container's network func (c *Container) cleanupNetwork() error { if c.state.NetNS == nil { logrus.Debugf("Network is already cleaned up, skipping...") return nil } // Stop the container's network namespace (if it has one) if err := c.runtime.teardownNetNS(c); err != nil { logrus.Errorf("unable to cleanup network for container %s: %q", c.ID(), err) } c.state.NetNS = nil c.state.IPs = nil c.state.Interfaces = nil c.state.Routes = nil if c.valid { return c.save() } return nil } // Generate spec for a container // Accepts a map of the container's dependencies func (c *Container) generateSpec(ctx context.Context) (*spec.Spec, error) { g := generate.NewFromSpec(c.config.Spec) // If network namespace was requested, add it now if c.config.CreateNetNS { if c.config.PostConfigureNetNS { g.AddOrReplaceLinuxNamespace(spec.NetworkNamespace, "") } else { g.AddOrReplaceLinuxNamespace(spec.NetworkNamespace, c.state.NetNS.Path()) } } // Remove the default /dev/shm mount to ensure we overwrite it g.RemoveMount("/dev/shm") // Add bind mounts to container for dstPath, srcPath := range c.state.BindMounts { newMount := spec.Mount{ Type: "bind", Source: srcPath, Destination: dstPath, Options: []string{"rw", "bind"}, } if !MountExists(g.Mounts(), dstPath) { g.AddMount(newMount) } else { logrus.Warnf("User mount overriding libpod mount at %q", dstPath) } } var err error if !rootless.IsRootless() { if c.state.ExtensionStageHooks, err = c.setupOCIHooks(ctx, g.Config); err != nil { return nil, errors.Wrapf(err, "error setting up OCI Hooks") } } // Bind builtin image volumes if c.config.Rootfs == "" && c.config.ImageVolumes { if err := c.addImageVolumes(ctx, &g); err != nil { return nil, errors.Wrapf(err, "error mounting image volumes") } } if c.config.User != "" { if !c.state.Mounted { return nil, errors.Wrapf(ErrCtrStateInvalid, "container %s must be mounted in order to translate User field", c.ID()) } uid, gid, err := chrootuser.GetUser(c.state.Mountpoint, c.config.User) if err != nil { return nil, err } // User and Group must go together g.SetProcessUID(uid) g.SetProcessGID(gid) } // Add addition groups if c.config.GroupAdd is not empty if len(c.config.Groups) > 0 { if !c.state.Mounted { return nil, errors.Wrapf(ErrCtrStateInvalid, "container %s must be mounted in order to add additional groups", c.ID()) } for _, group := range c.config.Groups { gid, err := chrootuser.GetGroup(c.state.Mountpoint, group) if err != nil { return nil, err } g.AddProcessAdditionalGid(gid) } } // Look up and add groups the user belongs to, if a group wasn't directly specified if !rootless.IsRootless() && !strings.Contains(c.config.User, ":") { groups, err := chrootuser.GetAdditionalGroupsForUser(c.state.Mountpoint, uint64(g.Config.Process.User.UID)) if err != nil && errors.Cause(err) != chrootuser.ErrNoSuchUser { return nil, err } for _, gid := range groups { g.AddProcessAdditionalGid(gid) } } // Add shared namespaces from other containers if c.config.IPCNsCtr != "" { if err := c.addNamespaceContainer(&g, IPCNS, c.config.IPCNsCtr, spec.IPCNamespace); err != nil { return nil, err } } if c.config.MountNsCtr != "" { if err := c.addNamespaceContainer(&g, MountNS, c.config.MountNsCtr, spec.MountNamespace); err != nil { return nil, err } } if c.config.NetNsCtr != "" { if err := c.addNamespaceContainer(&g, NetNS, c.config.NetNsCtr, spec.NetworkNamespace); err != nil { return nil, err } } if c.config.PIDNsCtr != "" { if err := c.addNamespaceContainer(&g, PIDNS, c.config.PIDNsCtr, string(spec.PIDNamespace)); err != nil { return nil, err } } if c.config.UserNsCtr != "" { if err := c.addNamespaceContainer(&g, UserNS, c.config.UserNsCtr, spec.UserNamespace); err != nil { return nil, err } } if c.config.UTSNsCtr != "" { if err := c.addNamespaceContainer(&g, UTSNS, c.config.UTSNsCtr, spec.UTSNamespace); err != nil { return nil, err } } if c.config.CgroupNsCtr != "" { if err := c.addNamespaceContainer(&g, CgroupNS, c.config.CgroupNsCtr, spec.CgroupNamespace); err != nil { return nil, err } } if c.config.Rootfs == "" { if err := idtools.MkdirAllAs(c.state.RealMountpoint, 0700, c.RootUID(), c.RootGID()); err != nil { return nil, err } } g.SetRootPath(c.state.RealMountpoint) g.AddAnnotation(crioAnnotations.Created, c.config.CreatedTime.Format(time.RFC3339Nano)) g.AddAnnotation("org.opencontainers.image.stopSignal", fmt.Sprintf("%d", c.config.StopSignal)) g.SetHostname(c.Hostname()) g.AddProcessEnv("HOSTNAME", g.Config.Hostname) // Only add container environment variable if not already present foundContainerEnv := false for _, env := range g.Config.Process.Env { if strings.HasPrefix(env, "container=") { foundContainerEnv = true break } } if !foundContainerEnv { g.AddProcessEnv("container", "libpod") } if rootless.IsRootless() { g.SetLinuxCgroupsPath("") } else if c.runtime.config.CgroupManager == SystemdCgroupsManager { // When runc is set to use Systemd as a cgroup manager, it // expects cgroups to be passed as follows: // slice:prefix:name systemdCgroups := fmt.Sprintf("%s:libpod:%s", path.Base(c.config.CgroupParent), c.ID()) logrus.Debugf("Setting CGroups for container %s to %s", c.ID(), systemdCgroups) g.SetLinuxCgroupsPath(systemdCgroups) } else { cgroupPath, err := c.CGroupPath() if err != nil { return nil, err } logrus.Debugf("Setting CGroup path for container %s to %s", c.ID(), cgroupPath) g.SetLinuxCgroupsPath(cgroupPath) } return g.Config, nil } // Add an existing container's namespace to the spec func (c *Container) addNamespaceContainer(g *generate.Generator, ns LinuxNS, ctr string, specNS string) error { nsCtr, err := c.runtime.state.Container(ctr) if err != nil { return errors.Wrapf(err, "error retrieving dependency %s of container %s from state", ctr, c.ID()) } // TODO need unlocked version of this for use in pods nsPath, err := nsCtr.NamespacePath(ns) if err != nil { return err } if err := g.AddOrReplaceLinuxNamespace(specNS, nsPath); err != nil { return err } return nil }