Repository: opencontainers/runc
File: libcontainer/process_linux.go (Go)

Code before:

	return fd, nil
}

// shouldRetryWithoutCgroupFD tells if the error returned from p.cmd.Start
// could be caused by using cgroupfd.
func (p *setnsProcess) shouldRetryWithoutCgroupFD(err error) bool {
	if err == nil || !p.cmd.SysProcAttr.UseCgroupFD {
		return false
	}
	logrus.Debugf("exec with CLONE_INTO_CGROUP failed: %v", err)

	switch {
	// Cgroup in which a domain controller is enabled.
	case errors.Is(err, unix.EBUSY):
		return true
	// The cgroup is in the domain invalid state.
	case errors.Is(err, unix.EOPNOTSUPP):
		return true
	// Rootless with no direct access to cgroup.
	case p.rootlessCgroups && errors.Is(err, unix.EACCES):
		return true
	// No clone3 syscall (kernels < v5.3).
	case errors.Is(err, unix.ENOSYS):
		return true
	// No CLONE_INTO_CGROUP flag support (kernels v5.3 to v5.7).
	case errors.Is(err, unix.E2BIG):
		return true
	}

	return false
}

func (p *setnsProcess) start() (retErr error) {
	defer p.comm.closeParent()

	fd, err := p.prepareCgroupFD()
	if err != nil {
		return err
	}

	// Get the "before" value of oom kill count.
	oom, _ := p.manager.OOMKillCount()

	err = p.startWithCPUAffinity()
	if fd != nil {
		fd.Close()
	}
	if p.shouldRetryWithoutCgroupFD(err) {
		// SysProcAttr.CgroupFD is never used when UseCgroupFD is unset.
		p.cmd.SysProcAttr.UseCgroupFD = false

Review comment (suggestion):
To clarify why `clone3` returns `E2BIG` rather than `EINVAL` for kernels which don't support `CLONE_INTO_CGROUP`, I know this is because the kernel checked the oversized structures first, it might be helpful to include a link to the relevant kernel source ([kernel/fork.c#L2525-L2536](https://github.com/torvalds/linux/blob/f14c234b4bc5184fd40d9a47830e5b32c3b36d49/kernel/fork.c#L2525-L2536)). This provides direct context for readers who are puzzled by this specific error code choice.

Code after:

	return fd, nil
}

func (p *setnsProcess) start() (retErr error) {
	defer p.comm.closeParent()

	fd, err := p.prepareCgroupFD()
	if err != nil {
		return err
	}

	// Get the "before" value of oom kill count.
	oom, _ := p.manager.OOMKillCount()

	err = p.startWithCPUAffinity()
	if fd != nil {
		fd.Close()
	}
	if err != nil && p.cmd.SysProcAttr.UseCgroupFD {
		logrus.Debugf("exec with CLONE_INTO_CGROUP failed: %v; retrying without", err)
		// SysProcAttr.CgroupFD is never used when UseCgroupFD is unset.
		p.cmd.SysProcAttr.UseCgroupFD = false
		err = p.startWithCPUAffinity()
	}

	// Close the child-side of the pipes (controlled by child).
	p.comm.closeChild()
	if err != nil {
		return fmt.Errorf("error starting setns process: %w", err)
	}

	defer func() {
		if retErr != nil {
			if newOom, err := p.manager.OOMKillCount(); err == nil && newOom != oom {
				// Someone in this cgroup was killed, this _might_ be us.
				retErr = fmt.Errorf("%w (possibly OOM-killed)", retErr)
			}
			err := ignoreTerminateErrors(p.terminate())
			if err != nil {
				logrus.WithError(err).Warn("unable to terminate setnsProcess")
			}
		}
	}()

	if p.bootstrapData != nil {
		if _, err := io.Copy(p.comm.initSockParent, p.bootstrapData); err != nil {
			return fmt.Errorf("error copying bootstrap data to pipe: %w", err)
		}
	}
	if err := p.execSetns(); err != nil {