mirror of
https://github.com/containers/podman.git
synced 2025-05-21 00:56:36 +08:00
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:
@ -83,6 +83,10 @@ func execFlags(cmd *cobra.Command) {
|
||||
flags.UintVar(&execOpts.PreserveFDs, preserveFdsFlagName, 0, "Pass N additional file descriptors to the container")
|
||||
_ = cmd.RegisterFlagCompletionFunc(preserveFdsFlagName, completion.AutocompleteNone)
|
||||
|
||||
preserveFdFlagName := "preserve-fd"
|
||||
flags.UintSliceVar(&execOpts.PreserveFD, preserveFdFlagName, nil, "Pass a list of additional file descriptors to the container")
|
||||
_ = cmd.RegisterFlagCompletionFunc(preserveFdFlagName, completion.AutocompleteNone)
|
||||
|
||||
workdirFlagName := "workdir"
|
||||
flags.StringVarP(&execOpts.WorkDir, workdirFlagName, "w", "", "Working directory inside the container")
|
||||
_ = cmd.RegisterFlagCompletionFunc(workdirFlagName, completion.AutocompleteDefault)
|
||||
@ -139,6 +143,12 @@ func exec(cmd *cobra.Command, args []string) error {
|
||||
|
||||
execOpts.Envs = envLib.Join(execOpts.Envs, cliEnv)
|
||||
|
||||
for _, fd := range execOpts.PreserveFD {
|
||||
if !rootless.IsFdInherited(int(fd)) {
|
||||
return fmt.Errorf("file descriptor %d is not available - the preserve-fd option requires that file descriptors must be passed", fd)
|
||||
}
|
||||
}
|
||||
|
||||
for fd := 3; fd < int(3+execOpts.PreserveFDs); fd++ {
|
||||
if !rootless.IsFdInherited(fd) {
|
||||
return fmt.Errorf("file descriptor %d is not available - the preserve-fds option requires that file descriptors must be passed", fd)
|
||||
|
@ -67,9 +67,13 @@ func runFlags(cmd *cobra.Command) {
|
||||
flags.BoolVar(&runRmi, "rmi", false, "Remove image unless used by other containers, implies --rm")
|
||||
|
||||
preserveFdsFlagName := "preserve-fds"
|
||||
flags.UintVar(&runOpts.PreserveFDs, "preserve-fds", 0, "Pass a number of additional file descriptors into the container")
|
||||
flags.UintVar(&runOpts.PreserveFDs, preserveFdsFlagName, 0, "Pass a number of additional file descriptors into the container")
|
||||
_ = cmd.RegisterFlagCompletionFunc(preserveFdsFlagName, completion.AutocompleteNone)
|
||||
|
||||
preserveFdFlagName := "preserve-fd"
|
||||
flags.UintSliceVar(&runOpts.PreserveFD, preserveFdFlagName, nil, "Pass a file descriptor into the container")
|
||||
_ = cmd.RegisterFlagCompletionFunc(preserveFdFlagName, completion.AutocompleteNone)
|
||||
|
||||
flags.BoolVarP(&runOpts.Detach, "detach", "d", false, "Run container in background and print container ID")
|
||||
|
||||
detachKeysFlagName := "detach-keys"
|
||||
@ -85,7 +89,8 @@ func runFlags(cmd *cobra.Command) {
|
||||
flags.BoolVar(&runOpts.Passwd, passwdFlagName, true, "add entries to /etc/passwd and /etc/group")
|
||||
|
||||
if registry.IsRemote() {
|
||||
_ = flags.MarkHidden("preserve-fds")
|
||||
_ = flags.MarkHidden(preserveFdsFlagName)
|
||||
_ = flags.MarkHidden(preserveFdFlagName)
|
||||
_ = flags.MarkHidden("conmon-pidfile")
|
||||
_ = flags.MarkHidden("pidfile")
|
||||
}
|
||||
@ -135,6 +140,11 @@ func run(cmd *cobra.Command, args []string) error {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, fd := range runOpts.PreserveFD {
|
||||
if !rootless.IsFdInherited(int(fd)) {
|
||||
return fmt.Errorf("file descriptor %d is not available - the preserve-fd option requires that file descriptors must be passed", fd)
|
||||
}
|
||||
}
|
||||
for fd := 3; fd < int(3+runOpts.PreserveFDs); fd++ {
|
||||
if !rootless.IsFdInherited(fd) {
|
||||
return fmt.Errorf("file descriptor %d is not available - the preserve-fds option requires that file descriptors must be passed", fd)
|
||||
@ -196,6 +206,7 @@ func run(cmd *cobra.Command, args []string) error {
|
||||
}
|
||||
|
||||
cliVals.PreserveFDs = runOpts.PreserveFDs
|
||||
cliVals.PreserveFD = runOpts.PreserveFD
|
||||
s := specgen.NewSpecGenerator(imageName, cliVals.RootFS)
|
||||
if err := specgenutil.FillOutSpecGen(s, &cliVals, args); err != nil {
|
||||
return err
|
||||
|
10
docs/source/markdown/options/preserve-fd.md
Normal file
10
docs/source/markdown/options/preserve-fd.md
Normal file
@ -0,0 +1,10 @@
|
||||
####> This option file is used in:
|
||||
####> podman exec, run
|
||||
####> If file is edited, make sure the changes
|
||||
####> are applicable to all of those.
|
||||
#### **--preserve-fd**=*FD1[,FD2,...]*
|
||||
|
||||
Pass down to the process the additional file descriptors specified in the comma separated list. It can be specified multiple times.
|
||||
This option is only supported with the crun OCI runtime. It might be a security risk to use this option with other OCI runtimes.
|
||||
|
||||
(This option is not available with the remote Podman client, including Mac and Windows (excluding WSL2) machines)
|
@ -27,6 +27,8 @@ Start the exec session, but do not attach to it. The command runs in the backgro
|
||||
|
||||
@@option latest
|
||||
|
||||
@@option preserve-fd
|
||||
|
||||
@@option preserve-fds
|
||||
|
||||
@@option privileged
|
||||
|
@ -308,6 +308,8 @@ This is used to override the Podman provided user setup in favor of entrypoint c
|
||||
|
||||
@@option pod-id-file.container
|
||||
|
||||
@@option preserve-fd
|
||||
|
||||
@@option preserve-fds
|
||||
|
||||
@@option privileged
|
||||
|
@ -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"`
|
||||
|
@ -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
|
||||
|
@ -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.
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
|
@ -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 {
|
||||
|
@ -297,6 +297,7 @@ type ExecOptions struct {
|
||||
Interactive bool
|
||||
Latest bool
|
||||
PreserveFDs uint
|
||||
PreserveFD []uint
|
||||
Privileged bool
|
||||
Tty bool
|
||||
User string
|
||||
@ -360,6 +361,7 @@ type ContainerRunOptions struct {
|
||||
InputStream *os.File
|
||||
OutputStream *os.File
|
||||
PreserveFDs uint
|
||||
PreserveFD []uint
|
||||
Rm bool
|
||||
SigProxy bool
|
||||
Spec *specgen.SpecGenerator
|
||||
|
@ -250,6 +250,7 @@ type ContainerCreateOptions struct {
|
||||
PodIDFile string
|
||||
Personality string
|
||||
PreserveFDs uint
|
||||
PreserveFD []uint
|
||||
Privileged bool
|
||||
PublishAll bool
|
||||
Pull string
|
||||
|
@ -809,6 +809,7 @@ func makeExecConfig(options entities.ExecOptions, rt *libpod.Runtime) (*libpod.E
|
||||
execConfig.WorkDir = options.WorkDir
|
||||
execConfig.DetachKeys = &options.DetachKeys
|
||||
execConfig.PreserveFDs = options.PreserveFDs
|
||||
execConfig.PreserveFD = options.PreserveFD
|
||||
execConfig.AttachStdin = options.Interactive
|
||||
|
||||
// Make an exit command
|
||||
@ -858,6 +859,7 @@ func (ic *ContainerEngine) ContainerExec(ctx context.Context, nameOrID string, o
|
||||
if err != nil {
|
||||
return ec, err
|
||||
}
|
||||
|
||||
containers, err := getContainers(ic.Libpod, getContainersOptions{latest: options.Latest, names: []string{nameOrID}})
|
||||
if err != nil {
|
||||
return ec, err
|
||||
|
@ -355,6 +355,10 @@ func createContainerOptions(rt *libpod.Runtime, s *specgen.SpecGenerator, pod *l
|
||||
options = append(options, libpod.WithPreserveFDs(s.PreserveFDs))
|
||||
}
|
||||
|
||||
if s.PreserveFD != nil {
|
||||
options = append(options, libpod.WithPreserveFD(s.PreserveFD))
|
||||
}
|
||||
|
||||
if s.Stdin {
|
||||
options = append(options, libpod.WithStdin())
|
||||
}
|
||||
|
@ -180,6 +180,11 @@ type ContainerBasicConfig struct {
|
||||
// set tags as `json:"-"` for not supported remote
|
||||
// Optional.
|
||||
PreserveFDs uint `json:"-"`
|
||||
// PreserveFD is a list of additional file descriptors (in addition
|
||||
// to 0, 1, 2) that will be passed to the executed process.
|
||||
// set tags as `json:"-"` for not supported remote
|
||||
// Optional.
|
||||
PreserveFD []uint `json:"-"`
|
||||
// Timezone is the timezone inside the container.
|
||||
// Local means it has the same timezone as the host machine
|
||||
// Optional.
|
||||
|
@ -838,9 +838,17 @@ func FillOutSpecGen(s *specgen.SpecGenerator, c *entities.ContainerCreateOptions
|
||||
if len(s.Name) == 0 || len(c.Name) != 0 {
|
||||
s.Name = c.Name
|
||||
}
|
||||
|
||||
if c.PreserveFDs != 0 && c.PreserveFD != nil {
|
||||
return errors.New("cannot specify both --preserve-fds and --preserve-fd")
|
||||
}
|
||||
|
||||
if s.PreserveFDs == 0 || c.PreserveFDs != 0 {
|
||||
s.PreserveFDs = c.PreserveFDs
|
||||
}
|
||||
if s.PreserveFD == nil || c.PreserveFD != nil {
|
||||
s.PreserveFD = c.PreserveFD
|
||||
}
|
||||
|
||||
if s.OOMScoreAdj == nil || c.OOMScoreAdj != nil {
|
||||
s.OOMScoreAdj = c.OOMScoreAdj
|
||||
|
@ -80,6 +80,25 @@ echo $rand | 0 | $rand
|
||||
is "$output" "$content" "container read input from fd 4"
|
||||
}
|
||||
|
||||
# 'run --preserve-fd' passes a list of additional file descriptors into the container
|
||||
@test "podman run --preserve-fd" {
|
||||
skip_if_remote "preserve-fd is meaningless over remote"
|
||||
|
||||
runtime=$(podman_runtime)
|
||||
if [[ $runtime != "crun" ]]; then
|
||||
skip "runtime is $runtime; preserve-fd requires crun"
|
||||
fi
|
||||
|
||||
content=$(random_string 20)
|
||||
echo "$content" > $PODMAN_TMPDIR/tempfile
|
||||
|
||||
# /proc/self/fd will have 0 1 2, possibly 3 & 4, but no 2-digit fds other than 40
|
||||
run_podman run --rm -i --preserve-fd=9,40 $IMAGE sh -c '/bin/ls -C -w999 /proc/self/fd; cat <&9; cat <&40' 9<<<"fd9" 10</dev/null 40<$PODMAN_TMPDIR/tempfile
|
||||
assert "${lines[0]}" !~ [123][0-9] "/proc/self/fd must not contain 10-39"
|
||||
assert "${lines[1]}" = "fd9" "cat from fd 9"
|
||||
assert "${lines[2]}" = "$content" "cat from fd 40"
|
||||
}
|
||||
|
||||
@test "podman run - uidmapping has no /sys/kernel mounts" {
|
||||
skip_if_cgroupsv1 "run --uidmap fails on cgroups v1 (issue 15025, wontfix)"
|
||||
skip_if_rootless "cannot umount as rootless"
|
||||
|
@ -223,4 +223,26 @@ load helpers
|
||||
assert "$output" = "0" ".ExecIDs must be empty"
|
||||
}
|
||||
|
||||
# 'exec --preserve-fd' passes a list of additional file descriptors into the container
|
||||
@test "podman exec --preserve-fd" {
|
||||
skip_if_remote "preserve-fd is meaningless over remote"
|
||||
|
||||
runtime=$(podman_runtime)
|
||||
if [[ $runtime != "crun" ]]; then
|
||||
skip "runtime is $runtime; preserve-fd requires crun"
|
||||
fi
|
||||
|
||||
run_podman run -d $IMAGE top
|
||||
cid="$output"
|
||||
|
||||
content=$(random_string 20)
|
||||
echo "$content" > $PODMAN_TMPDIR/tempfile
|
||||
|
||||
# /proc/self/fd will have 0 1 2, possibly 3 & 4, but no 2-digit fds other than 40
|
||||
run_podman exec --preserve-fd=9,40 $cid sh -c '/bin/ls -C -w999 /proc/self/fd; cat <&9; cat <&40' 9<<<"fd9" 10</dev/null 40<$PODMAN_TMPDIR/tempfile
|
||||
assert "${lines[0]}" !~ [123][0-9] "/proc/self/fd must not contain 10-39"
|
||||
assert "${lines[1]}" = "fd9" "cat from fd 9"
|
||||
assert "${lines[2]}" = "$content" "cat from fd 40"
|
||||
}
|
||||
|
||||
# vim: filetype=sh
|
||||
|
Reference in New Issue
Block a user