podman unshare keep exit code

In case the command inside the podman unshare env failed podman unshare
always exits with 125 and prints `Error: exit status 125`. This is a
bad user experience and makes it difficult to use in scripts which could
expect certain exit codes.
This commit makes sure podman unshare uses the same exit code as the
command and does not print the useless `exit status X` message.

Also to match podman run/exec it should return 126 for EPERM
and 127 for ENOENT.

Signed-off-by: Paul Holzinger <pholzing@redhat.com>
This commit is contained in:
Paul Holzinger 2021-09-09 22:10:17 +02:00
parent 580ac4c6ab
commit d2e10a71d6
No known key found for this signature in database
GPG Key ID: EB145DD938A3CAF2
3 changed files with 82 additions and 3 deletions

View File

@ -2,6 +2,7 @@ package system
import (
"os"
"os/exec"
"github.com/containers/common/pkg/completion"
"github.com/containers/podman/v3/cmd/podman/registry"
@ -50,5 +51,23 @@ func unshare(cmd *cobra.Command, args []string) error {
args = []string{shell}
}
return registry.ContainerEngine().Unshare(registry.Context(), args, unshareOptions)
err := registry.ContainerEngine().Unshare(registry.Context(), args, unshareOptions)
if err != nil {
if exitError, ok := err.(*exec.ExitError); ok {
// the user command inside the unshare env has failed
// we set the exit code, do not return the error to the user
// otherwise "exit status X" will be printed
registry.SetExitCode(exitError.ExitCode())
return nil
}
// cmd.Run() can return fs.ErrNotExist, fs.ErrPermission or exec.ErrNotFound
// follow podman run/exec standard with the exit codes
if errors.Is(err, os.ErrNotExist) || errors.Is(err, exec.ErrNotFound) {
registry.SetExitCode(127)
} else if errors.Is(err, os.ErrPermission) {
registry.SetExitCode(126)
}
return err
}
return nil
}

View File

@ -37,6 +37,35 @@ connect to a rootless container via IP address (CNI networking). This is otherwi
not possible from the host network namespace.
_Note: Using this option with more than one unshare session can have unexpected results._
## Exit Codes
The exit code from `podman unshare` gives information about why the container
failed to run or why it exited. When `podman unshare` commands exit with a non-zero code,
the exit codes follow the `chroot` standard, see below:
**125** The error is with podman **_itself_**
$ podman unshare --foo; echo $?
Error: unknown flag: --foo
125
**126** Executing a _contained command_ and the _command_ cannot be invoked
$ podman unshare /etc; echo $?
Error: fork/exec /etc: permission denied
126
**127** Executing a _contained command_ and the _command_ cannot be found
$ podman run busybox foo; echo $?
Error: fork/exec /usr/bin/bogus: no such file or directory
127
**Exit code** _contained command_ exit code
$ podman run busybox /bin/sh -c 'exit 3'; echo $?
3
## EXAMPLE
```

View File

@ -47,8 +47,7 @@ var _ = Describe("Podman unshare", func() {
session := podmanTest.Podman([]string{"unshare", "readlink", "/proc/self/ns/user"})
session.WaitWithDefaultTimeout()
Expect(session).Should(Exit(0))
ok, _ := session.GrepString(userNS)
Expect(ok).To(BeFalse())
Expect(session.OutputToString()).ToNot(ContainSubstring(userNS))
})
It("podman unshare --rootles-cni", func() {
@ -57,4 +56,36 @@ var _ = Describe("Podman unshare", func() {
Expect(session).Should(Exit(0))
Expect(session.OutputToString()).To(ContainSubstring("tap0"))
})
It("podman unshare exit codes", func() {
session := podmanTest.Podman([]string{"unshare", "false"})
session.WaitWithDefaultTimeout()
Expect(session).Should(Exit(1))
Expect(session.OutputToString()).Should(Equal(""))
Expect(session.ErrorToString()).Should(Equal(""))
session = podmanTest.Podman([]string{"unshare", "/usr/bin/bogus"})
session.WaitWithDefaultTimeout()
Expect(session).Should(Exit(127))
Expect(session.OutputToString()).Should(Equal(""))
Expect(session.ErrorToString()).Should(ContainSubstring("no such file or directory"))
session = podmanTest.Podman([]string{"unshare", "bogus"})
session.WaitWithDefaultTimeout()
Expect(session).Should(Exit(127))
Expect(session.OutputToString()).Should(Equal(""))
Expect(session.ErrorToString()).Should(ContainSubstring("executable file not found in $PATH"))
session = podmanTest.Podman([]string{"unshare", "/usr"})
session.WaitWithDefaultTimeout()
Expect(session).Should(Exit(126))
Expect(session.OutputToString()).Should(Equal(""))
Expect(session.ErrorToString()).Should(ContainSubstring("permission denied"))
session = podmanTest.Podman([]string{"unshare", "--bogus"})
session.WaitWithDefaultTimeout()
Expect(session).Should(Exit(125))
Expect(session.OutputToString()).Should(Equal(""))
Expect(session.ErrorToString()).Should(ContainSubstring("unknown flag: --bogus"))
})
})