diff --git a/libcontainer/cmd_clone.go b/libcontainer/cmd_clone.go new file mode 100644 index 000000000..67418070f --- /dev/null +++ b/libcontainer/cmd_clone.go @@ -0,0 +1,37 @@ +package libcontainer + +import "os/exec" + +// cloneCmd creates a copy of exec.Cmd. It is needed because cmd.Start +// must only be used once, and go1.26 actually enforces that (see +// https://go-review.googlesource.com/c/go/+/728642). The implementation +// is similar to +// +// cmd = *c +// return &cmd +// +// except it does not copy private fields, or fields populated +// after the call to cmd.Start. +// +// NOTE if Go will add exec.Cmd.Clone, we should switch to it. +func cloneCmd(c *exec.Cmd) *exec.Cmd { + cmd := &exec.Cmd{ + Path: c.Path, + Args: c.Args, + Env: c.Env, + Dir: c.Dir, + Stdin: c.Stdin, + Stdout: c.Stdout, + Stderr: c.Stderr, + ExtraFiles: c.ExtraFiles, + SysProcAttr: c.SysProcAttr, + // Don't copy Process, ProcessState, Err since + // these fields are populated after the start. + + // Technically, we do not use Cancel or WaitDelay, + // but they are here for the sake of completeness. + Cancel: c.Cancel, + WaitDelay: c.WaitDelay, + } + return cmd +} diff --git a/libcontainer/process_linux.go b/libcontainer/process_linux.go index b3487fd0b..fd075d4b9 100644 --- a/libcontainer/process_linux.go +++ b/libcontainer/process_linux.go @@ -380,11 +380,14 @@ func (p *setnsProcess) startWithCgroupFD() error { defer fd.Close() } + cmdCopy := cloneCmd(p.cmd) err = p.startWithCPUAffinity() 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 + cmdCopy.SysProcAttr.UseCgroupFD = false + // Must not reuse exec.Cmd. + p.cmd = cmdCopy err = p.startWithCPUAffinity() }