summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPaul Holzinger <pholzing@redhat.com>2021-09-09 22:10:17 +0200
committerPaul Holzinger <pholzing@redhat.com>2021-09-10 15:30:25 +0200
commitd2e10a71d69565929309ff3c32665b216698d8b1 (patch)
tree45901a398281cf43e315a722af4712eaf5560a61
parent580ac4c6abc336d984f3a09940a4ef3006f0e6a7 (diff)
downloadpodman-d2e10a71d69565929309ff3c32665b216698d8b1.tar.gz
podman-d2e10a71d69565929309ff3c32665b216698d8b1.tar.bz2
podman-d2e10a71d69565929309ff3c32665b216698d8b1.zip
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>
-rw-r--r--cmd/podman/system/unshare.go21
-rw-r--r--docs/source/markdown/podman-unshare.1.md29
-rw-r--r--test/e2e/unshare_test.go35
3 files changed, 82 insertions, 3 deletions
diff --git a/cmd/podman/system/unshare.go b/cmd/podman/system/unshare.go
index f930d8b62..50230609e 100644
--- a/cmd/podman/system/unshare.go
+++ b/cmd/podman/system/unshare.go
@@ -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
}
diff --git a/docs/source/markdown/podman-unshare.1.md b/docs/source/markdown/podman-unshare.1.md
index 2e7adfd34..72821b6e5 100644
--- a/docs/source/markdown/podman-unshare.1.md
+++ b/docs/source/markdown/podman-unshare.1.md
@@ -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
```
diff --git a/test/e2e/unshare_test.go b/test/e2e/unshare_test.go
index eacdda68a..79ce68e89 100644
--- a/test/e2e/unshare_test.go
+++ b/test/e2e/unshare_test.go
@@ -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"))
+ })
})