podman: new option --preserve-fd

add a new option --preserve-fd that allows to specify a list of FDs to
pass down to the container.

It is similar to --preserve-fds but it allows to specify a list of FDs
instead of the maximum FD number to preserve.

--preserve-fd and --preserve-fds are mutually exclusive.

It requires crun since runc would complain if any fd below
--preserve-fds is not preserved.

Closes: https://github.com/containers/podman/issues/20844

Signed-off-by: Giuseppe Scrivano <gscrivan@redhat.com>
This commit is contained in:
Giuseppe Scrivano
2023-12-01 11:49:29 +01:00
parent 6b9221d852
commit 01d397a658
19 changed files with 172 additions and 23 deletions

View File

@ -416,6 +416,9 @@ type ContainerMiscConfig struct {
// to 0, 1, 2) that will be passed to the executed process. The total FDs
// passed will be 3 + PreserveFDs.
PreserveFDs uint `json:"preserveFds,omitempty"`
// PreserveFD is a list of additional file descriptors (in addition
// to 0, 1, 2) that will be passed to the executed process.
PreserveFD []uint `json:"preserveFd,omitempty"`
// Timezone is the timezone inside the container.
// Local means it has the same timezone as the host machine
Timezone string `json:"timezone,omitempty"`

View File

@ -66,6 +66,9 @@ type ExecConfig struct {
// given is the number that will be passed into the exec session,
// starting at 3.
PreserveFDs uint `json:"preserveFds,omitempty"`
// PreserveFD is a list of additional file descriptors (in addition
// to 0, 1, 2) that will be passed to the executed process.
PreserveFD []uint `json:"preserveFd,omitempty"`
// ExitCommand is the exec session's exit command.
// This command will be executed when the exec session exits.
// If unset, no command will be executed.
@ -1092,6 +1095,7 @@ func prepareForExec(c *Container, session *ExecSession) (*ExecOptions, error) {
opts.Cwd = session.Config.WorkDir
opts.User = session.Config.User
opts.PreserveFDs = session.Config.PreserveFDs
opts.PreserveFD = session.Config.PreserveFD
opts.DetachKeys = session.Config.DetachKeys
opts.ExitCommand = session.Config.ExitCommand
opts.ExitCommandDelay = session.Config.ExitCommandDelay

View File

@ -202,6 +202,9 @@ type ExecOptions struct {
// to 0, 1, 2) that will be passed to the executed process. The total FDs
// passed will be 3 + PreserveFDs.
PreserveFDs uint
// PreserveFD is a list of additional file descriptors (in addition
// to 0, 1, 2) that will be passed to the executed process.
PreserveFD []uint
// DetachKeys is a set of keys that, when pressed in sequence, will
// detach from the container.
// If not provided, the default keys will be used.

View File

@ -1038,6 +1038,39 @@ func (r *ConmonOCIRuntime) getLogTag(ctr *Container) (string, error) {
return b.String(), nil
}
func getPreserveFdExtraFiles(preserveFD []uint, preserveFDs uint) (uint, []*os.File, []*os.File, error) {
var filesToClose []*os.File
var extraFiles []*os.File
preserveFDsMap := make(map[uint]struct{})
for _, i := range preserveFD {
if i < 3 {
return 0, nil, nil, fmt.Errorf("cannot preserve FD %d, consider using the passthrough log-driver to pass STDIO streams into the container: %w", i, define.ErrInvalidArg)
}
if i-2 > preserveFDs {
// preserveFDs is the number of FDs above 2 to keep around.
// e.g. if the user specified FD=3, then preserveFDs must be 1.
preserveFDs = i - 2
}
preserveFDsMap[i] = struct{}{}
}
if preserveFDs > 0 {
for fd := 3; fd < int(3+preserveFDs); fd++ {
if len(preserveFDsMap) > 0 {
if _, ok := preserveFDsMap[uint(fd)]; !ok {
extraFiles = append(extraFiles, nil)
continue
}
}
f := os.NewFile(uintptr(fd), fmt.Sprintf("fd-%d", fd))
filesToClose = append(filesToClose, f)
extraFiles = append(extraFiles, f)
}
}
return preserveFDs, filesToClose, extraFiles, nil
}
// createOCIContainer generates this container's main conmon instance and prepares it for starting
func (r *ConmonOCIRuntime) createOCIContainer(ctr *Container, restoreOptions *ContainerCheckpointOptions) (int64, error) {
var stderrBuf bytes.Buffer
@ -1114,10 +1147,11 @@ func (r *ConmonOCIRuntime) createOCIContainer(ctr *Container, restoreOptions *Co
args = append(args, []string{"--exit-command-arg", arg}...)
}
// Pass down the LISTEN_* environment (see #10443).
preserveFDs := ctr.config.PreserveFDs
// Pass down the LISTEN_* environment (see #10443).
if val := os.Getenv("LISTEN_FDS"); val != "" {
if ctr.config.PreserveFDs > 0 {
if preserveFDs > 0 || len(ctr.config.PreserveFD) > 0 {
logrus.Warnf("Ignoring LISTEN_FDS to preserve custom user-specified FDs")
} else {
fds, err := strconv.Atoi(val)
@ -1128,6 +1162,10 @@ func (r *ConmonOCIRuntime) createOCIContainer(ctr *Container, restoreOptions *Co
}
}
preserveFDs, filesToClose, extraFiles, err := getPreserveFdExtraFiles(ctr.config.PreserveFD, preserveFDs)
if err != nil {
return 0, err
}
if preserveFDs > 0 {
args = append(args, formatRuntimeOpts("--preserve-fds", strconv.FormatUint(uint64(preserveFDs), 10))...)
}
@ -1189,14 +1227,7 @@ func (r *ConmonOCIRuntime) createOCIContainer(ctr *Container, restoreOptions *Co
return 0, fmt.Errorf("configuring conmon env: %w", err)
}
var filesToClose []*os.File
if preserveFDs > 0 {
for fd := 3; fd < int(3+preserveFDs); fd++ {
f := os.NewFile(uintptr(fd), fmt.Sprintf("fd-%d", fd))
filesToClose = append(filesToClose, f)
cmd.ExtraFiles = append(cmd.ExtraFiles, f)
}
}
cmd.ExtraFiles = extraFiles
cmd.Env = r.conmonEnv
// we don't want to step on users fds they asked to preserve

View File

@ -391,8 +391,13 @@ func (r *ConmonOCIRuntime) startExec(c *Container, sessionID string, options *Ex
args := r.sharedConmonArgs(c, sessionID, c.execBundlePath(sessionID), c.execPidPath(sessionID), c.execLogPath(sessionID), c.execExitFileDir(sessionID), ociLog, define.NoLogging, c.config.LogTag)
if options.PreserveFDs > 0 {
args = append(args, formatRuntimeOpts("--preserve-fds", strconv.FormatUint(uint64(options.PreserveFDs), 10))...)
preserveFDs, filesToClose, extraFiles, err := getPreserveFdExtraFiles(options.PreserveFD, options.PreserveFDs)
if err != nil {
return nil, nil, err
}
if preserveFDs > 0 {
args = append(args, formatRuntimeOpts("--preserve-fds", strconv.FormatUint(uint64(preserveFDs), 10))...)
}
if options.Terminal {
@ -442,19 +447,12 @@ func (r *ConmonOCIRuntime) startExec(c *Container, sessionID string, options *Ex
return nil, nil, fmt.Errorf("configuring conmon env: %w", err)
}
var filesToClose []*os.File
if options.PreserveFDs > 0 {
for fd := 3; fd < int(3+options.PreserveFDs); fd++ {
f := os.NewFile(uintptr(fd), fmt.Sprintf("fd-%d", fd))
filesToClose = append(filesToClose, f)
execCmd.ExtraFiles = append(execCmd.ExtraFiles, f)
}
}
execCmd.ExtraFiles = extraFiles
// we don't want to step on users fds they asked to preserve
// Since 0-2 are used for stdio, start the fds we pass in at preserveFDs+3
execCmd.Env = r.conmonEnv
execCmd.Env = append(execCmd.Env, fmt.Sprintf("_OCI_SYNCPIPE=%d", options.PreserveFDs+3), fmt.Sprintf("_OCI_STARTPIPE=%d", options.PreserveFDs+4), fmt.Sprintf("_OCI_ATTACHPIPE=%d", options.PreserveFDs+5))
execCmd.Env = append(execCmd.Env, fmt.Sprintf("_OCI_SYNCPIPE=%d", preserveFDs+3), fmt.Sprintf("_OCI_STARTPIPE=%d", preserveFDs+4), fmt.Sprintf("_OCI_ATTACHPIPE=%d", preserveFDs+5))
execCmd.Env = append(execCmd.Env, conmonEnv...)
execCmd.ExtraFiles = append(execCmd.ExtraFiles, childSyncPipe, childStartPipe, childAttachPipe)

View File

@ -1555,6 +1555,18 @@ func WithPreserveFDs(fd uint) CtrCreateOption {
}
}
// WithPreserveFD forwards from the process running Libpod into the container
// the given list of extra FDs to the created container
func WithPreserveFD(fds []uint) CtrCreateOption {
return func(ctr *Container) error {
if ctr.valid {
return define.ErrCtrFinalized
}
ctr.config.PreserveFD = fds
return nil
}
}
// WithCreateCommand adds the full command plus arguments of the current
// process to the container config.
func WithCreateCommand(cmd []string) CtrCreateOption {