summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.copr/prepare.sh2
-rw-r--r--Dockerfile2
-rw-r--r--Dockerfile.centos2
-rw-r--r--Dockerfile.fedora2
-rw-r--r--cmd/podman/cp.go4
-rw-r--r--cmd/podman/main_local.go44
-rw-r--r--cmd/podman/unshare.go49
-rw-r--r--contrib/spec/podman.spec.in2
-rw-r--r--docs/podman-unshare.1.md7
-rwxr-xr-xhack/get_ci_vm.sh8
-rw-r--r--libpod/runtime.go5
-rw-r--r--libpod/runtime_ctr.go2
-rw-r--r--pkg/rootless/rootless_linux.c149
-rw-r--r--pkg/rootless/rootless_linux.go133
-rw-r--r--pkg/rootless/rootless_unsupported.go14
-rw-r--r--test/e2e/cp_test.go27
16 files changed, 347 insertions, 105 deletions
diff --git a/.copr/prepare.sh b/.copr/prepare.sh
index a40e2aadb..57c380b02 100644
--- a/.copr/prepare.sh
+++ b/.copr/prepare.sh
@@ -29,4 +29,4 @@ fi
mkdir build/
git archive --prefix "libpod-${COMMIT_SHORT}/" --format "tar.gz" HEAD -o "build/libpod-${COMMIT_SHORT}.tar.gz"
git clone https://github.com/containers/conmon
-cd conmon && git checkout f02c053eb37010fc76d1e2966de7f2cb9f969ef2 && git archive --prefix "conmon/" --format "tar.gz" HEAD -o "../build/conmon.tar.gz"
+cd conmon && git checkout 59952292a3b07ac125575024ae21956efe0ecdfb && git archive --prefix "conmon/" --format "tar.gz" HEAD -o "../build/conmon.tar.gz"
diff --git a/Dockerfile b/Dockerfile
index f3afd5e25..4fc85e959 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -56,7 +56,7 @@ RUN set -x \
&& rm -rf "$GOPATH"
# Install conmon
-ENV CONMON_COMMIT f02c053eb37010fc76d1e2966de7f2cb9f969ef2
+ENV CONMON_COMMIT 59952292a3b07ac125575024ae21956efe0ecdfb
RUN set -x \
&& export GOPATH="$(mktemp -d)" \
&& git clone https://github.com/containers/conmon.git "$GOPATH/src/github.com/containers/conmon.git" \
diff --git a/Dockerfile.centos b/Dockerfile.centos
index 47f7182b6..159449c63 100644
--- a/Dockerfile.centos
+++ b/Dockerfile.centos
@@ -64,7 +64,7 @@ RUN set -x \
&& install -D -m 755 "$GOPATH"/bin/easyjson /usr/bin/
# Install conmon
-ENV CONMON_COMMIT f02c053eb37010fc76d1e2966de7f2cb9f969ef2
+ENV CONMON_COMMIT 59952292a3b07ac125575024ae21956efe0ecdfb
RUN set -x \
&& export GOPATH="$(mktemp -d)" \
&& git clone https://github.com/containers/conmon.git "$GOPATH/src/github.com/containers/conmon.git" \
diff --git a/Dockerfile.fedora b/Dockerfile.fedora
index 290fe3f82..74a770a90 100644
--- a/Dockerfile.fedora
+++ b/Dockerfile.fedora
@@ -68,7 +68,7 @@ RUN set -x \
&& install -D -m 755 "$GOPATH"/bin/easyjson /usr/bin/
# Install conmon
-ENV CONMON_COMMIT f02c053eb37010fc76d1e2966de7f2cb9f969ef2
+ENV CONMON_COMMIT 59952292a3b07ac125575024ae21956efe0ecdfb
RUN set -x \
&& export GOPATH="$(mktemp -d)" \
&& git clone https://github.com/containers/conmon.git "$GOPATH/src/github.com/containers/conmon.git" \
diff --git a/cmd/podman/cp.go b/cmd/podman/cp.go
index 4cb8a8c54..8240cc193 100644
--- a/cmd/podman/cp.go
+++ b/cmd/podman/cp.go
@@ -272,6 +272,10 @@ func copy(src, destPath, dest string, idMappingOpts storage.IDMappingOptions, ch
}
return nil
}
+
+ if destDirIsExist || strings.HasSuffix(dest, string(os.PathSeparator)) {
+ destPath = filepath.Join(destPath, filepath.Base(srcPath))
+ }
// Copy the file, preserving attributes.
logrus.Debugf("copying %q to %q", srcPath, destPath)
if err = copyFileWithTar(srcPath, destPath); err != nil {
diff --git a/cmd/podman/main_local.go b/cmd/podman/main_local.go
index 5af05a11e..b4f21bd0c 100644
--- a/cmd/podman/main_local.go
+++ b/cmd/podman/main_local.go
@@ -4,11 +4,9 @@ package main
import (
"context"
- "io/ioutil"
"log/syslog"
"os"
"runtime/pprof"
- "strconv"
"strings"
"syscall"
@@ -120,18 +118,10 @@ func setupRootless(cmd *cobra.Command, args []string) error {
return errors.Wrapf(err, "could not get pause process pid file path")
}
- data, err := ioutil.ReadFile(pausePidPath)
- if err != nil && !os.IsNotExist(err) {
- return errors.Wrapf(err, "cannot read pause process pid file %s", pausePidPath)
- }
- if err == nil {
- pausePid, err := strconv.Atoi(string(data))
- if err != nil {
- return errors.Wrapf(err, "cannot parse pause pid file %s", pausePidPath)
- }
- became, ret, err := rootless.JoinUserAndMountNS(uint(pausePid), "")
+ if _, err := os.Stat(pausePidPath); err == nil {
+ became, ret, err := rootless.TryJoinFromFilePaths("", false, []string{pausePidPath})
if err != nil {
- logrus.Errorf("cannot join pause process pid %d. You may need to remove %s and stop all containers", pausePid, pausePidPath)
+ logrus.Errorf("cannot join pause process. You may need to remove %s and stop all containers", pausePidPath)
logrus.Errorf("you can use `system migrate` to recreate the pause process")
logrus.Errorf(err.Error())
os.Exit(1)
@@ -154,28 +144,13 @@ func setupRootless(cmd *cobra.Command, args []string) error {
logrus.Errorf(err.Error())
os.Exit(1)
}
- var became bool
- var ret int
- if len(ctrs) == 0 {
- became, ret, err = rootless.BecomeRootInUserNS(pausePidPath)
- } else {
- for _, ctr := range ctrs {
- data, err := ioutil.ReadFile(ctr.Config().ConmonPidFile)
- if err != nil {
- logrus.Errorf(err.Error())
- continue
- }
- conmonPid, err := strconv.Atoi(string(data))
- if err != nil {
- logrus.Errorf(err.Error())
- continue
- }
- became, ret, err = rootless.JoinUserAndMountNS(uint(conmonPid), pausePidPath)
- if err == nil {
- break
- }
- }
+
+ paths := []string{}
+ for _, ctr := range ctrs {
+ paths = append(paths, ctr.Config().ConmonPidFile)
}
+
+ became, ret, err := rootless.TryJoinFromFilePaths(pausePidPath, true, paths)
if err != nil {
logrus.Errorf(err.Error())
os.Exit(1)
@@ -185,6 +160,7 @@ func setupRootless(cmd *cobra.Command, args []string) error {
}
return nil
}
+
func setRLimits() error {
rlimits := new(syscall.Rlimit)
rlimits.Cur = 1048576
diff --git a/cmd/podman/unshare.go b/cmd/podman/unshare.go
index 1db647dba..4a4e371db 100644
--- a/cmd/podman/unshare.go
+++ b/cmd/podman/unshare.go
@@ -3,10 +3,14 @@
package main
import (
+ "fmt"
"os"
"os/exec"
- "github.com/containers/buildah/pkg/unshare"
+ "github.com/containers/libpod/cmd/podman/cliconfig"
+ "github.com/containers/libpod/cmd/podman/libpodruntime"
+ "github.com/containers/libpod/libpod"
+ "github.com/containers/libpod/pkg/rootless"
"github.com/pkg/errors"
"github.com/spf13/cobra"
)
@@ -17,38 +21,61 @@ var (
Use: "unshare [flags] [COMMAND [ARG]]",
Short: "Run a command in a modified user namespace",
Long: unshareDescription,
- RunE: unshareCmd,
+ RunE: func(cmd *cobra.Command, args []string) error {
+ unshareCommand.InputArgs = args
+ unshareCommand.GlobalFlags = MainGlobalOpts
+ return unshareCmd(&unshareCommand)
+ },
Example: `podman unshare id
podman unshare cat /proc/self/uid_map,
podman unshare podman-script.sh`,
}
+ unshareCommand cliconfig.PodmanCommand
)
func init() {
- _unshareCommand.SetUsageTemplate(UsageTemplate())
+ unshareCommand.Command = _unshareCommand
+ unshareCommand.SetHelpTemplate(HelpTemplate())
+ unshareCommand.SetUsageTemplate(UsageTemplate())
flags := _unshareCommand.Flags()
flags.SetInterspersed(false)
}
+func unshareEnv(config *libpod.RuntimeConfig) []string {
+ return append(os.Environ(), "_CONTAINERS_USERNS_CONFIGURED=done",
+ fmt.Sprintf("CONTAINERS_GRAPHROOT=%s", config.StorageConfig.GraphRoot),
+ fmt.Sprintf("CONTAINERS_RUNROOT=%s", config.StorageConfig.RunRoot))
+}
+
// unshareCmd execs whatever using the ID mappings that we want to use for ourselves
-func unshareCmd(c *cobra.Command, args []string) error {
- if isRootless := unshare.IsRootless(); !isRootless {
+func unshareCmd(c *cliconfig.PodmanCommand) error {
+
+ if isRootless := rootless.IsRootless(); !isRootless {
return errors.Errorf("please use unshare with rootless")
}
// exec the specified command, if there is one
- if len(args) < 1 {
+ if len(c.InputArgs) < 1 {
// try to exec the shell, if one's set
shell, shellSet := os.LookupEnv("SHELL")
if !shellSet {
return errors.Errorf("no command specified and no $SHELL specified")
}
- args = []string{shell}
+ c.InputArgs = []string{shell}
}
- cmd := exec.Command(args[0], args[1:]...)
- cmd.Env = unshare.RootlessEnv()
+
+ runtime, err := libpodruntime.GetRuntime(getContext(), c)
+ if err != nil {
+ return err
+ }
+ runtimeConfig, err := runtime.GetConfig()
+ if err != nil {
+ return err
+ }
+
+ cmd := exec.Command(c.InputArgs[0], c.InputArgs[1:]...)
+ cmd.Env = unshareEnv(runtimeConfig)
cmd.Stdin = os.Stdin
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
- unshare.ExecRunnable(cmd)
- return nil
+ return cmd.Run()
}
diff --git a/contrib/spec/podman.spec.in b/contrib/spec/podman.spec.in
index 985dbbc74..a1c11a5a6 100644
--- a/contrib/spec/podman.spec.in
+++ b/contrib/spec/podman.spec.in
@@ -35,7 +35,7 @@
# People want conmon packaged with the copr rpm
%global import_path_conmon github.com/containers/conmon
%global git_conmon https://%{import_path_conmon}
-%global commit_conmon f02c053eb37010fc76d1e2966de7f2cb9f969ef2
+%global commit_conmon 59952292a3b07ac125575024ae21956efe0ecdfb
%global shortcommit_conmon %(c=%{commit_conmon}; echo ${c:0:7})
Name: podman
diff --git a/docs/podman-unshare.1.md b/docs/podman-unshare.1.md
index a7f018ce1..a10fb40f9 100644
--- a/docs/podman-unshare.1.md
+++ b/docs/podman-unshare.1.md
@@ -19,6 +19,11 @@ manually clearing storage and other data related to images and containers.
It is also useful if you want to use the `podman mount` command. If an unprivileged users wants to mount and work with a container, then they need to execute
podman unshare. Executing `podman mount` fails for unprivileged users unless the user is running inside a `podman unshare` session.
+The unshare session defines two environment variables:
+
+**CONTAINERS_GRAPHROOT** the path to the persistent containers data.
+**CONTAINERS_RUNROOT** the path to the volatile containers data.
+
## EXAMPLE
```
@@ -34,4 +39,4 @@ $ podman unshare cat /proc/self/uid_map /proc/self/gid_map
## SEE ALSO
-podman(1), podman-mount(1), namespaces(7), newuidmap(1), newgidmap(1), user\_namespaces(7) \ No newline at end of file
+podman(1), podman-mount(1), namespaces(7), newuidmap(1), newgidmap(1), user\_namespaces(7)
diff --git a/hack/get_ci_vm.sh b/hack/get_ci_vm.sh
index aed0042fb..12dd211f4 100755
--- a/hack/get_ci_vm.sh
+++ b/hack/get_ci_vm.sh
@@ -11,7 +11,7 @@ ${YEL}WARNING: This will not work without local sudo access to run podman,${NOR}
${YEL}possession of the proper ssh private key is required.${NOR}
"
# TODO: Many/most of these values should come from .cirrus.yml
-ZONE="us-central1-a"
+ZONE="${ZONE:-us-central1-a}"
CPUS="2"
MEMORY="4Gb"
DISK="200"
@@ -238,10 +238,14 @@ showrun --background tar cjf $TMPDIR/$TARBALL --warning=no-file-changed --exclud
trap delvm INT # Allow deleting VM if CTRL-C during create
# This fails if VM already exists: permit this usage to re-init
-echo -e "\n${YEL}Trying to creating a VM named $VMNAME\n${RED}(might take a minute/two. Errors ignored).${NOR}"
+echo -e "\n${YEL}Trying to creating a VM named $VMNAME${NOR}\n${YEL}in GCE region/zone $ZONE${NOR}"
+echo -e "For faster access, export ZONE='something-closer-<any letter>'"
+echo 'List of regions and zones: https://cloud.google.com/compute/docs/regions-zones/'
+echo -e "${RED}(might take a minute/two. Errors ignored).${NOR}"
showrun $CREATE_CMD || true # allow re-running commands below when "delete: N"
# Any subsequent failure should prompt for VM deletion
+trap - INT
trap delvm EXIT
echo -e "\n${YEL}Waiting up to 30s for ssh port to open${NOR}"
diff --git a/libpod/runtime.go b/libpod/runtime.go
index def7ba639..1f8dd98b4 100644
--- a/libpod/runtime.go
+++ b/libpod/runtime.go
@@ -877,10 +877,9 @@ func makeRuntime(ctx context.Context, runtime *Runtime) (err error) {
// TODO: we can't close the FD in this lock, so we should keep it around
// and use it to lock important operations
aliveLock.Lock()
- locked := true
doRefresh := false
defer func() {
- if locked {
+ if aliveLock.Locked() {
aliveLock.Unlock()
}
}()
@@ -891,7 +890,7 @@ func makeRuntime(ctx context.Context, runtime *Runtime) (err error) {
// no containers running. Create immediately a namespace, as
// we will need to access the storage.
if os.Geteuid() != 0 {
- aliveLock.Unlock()
+ aliveLock.Unlock() // Unlock to avoid deadlock as BecomeRootInUserNS will reexec.
pausePid, err := util.GetRootlessPauseProcessPidPath()
if err != nil {
return errors.Wrapf(err, "could not get pause process pid file path")
diff --git a/libpod/runtime_ctr.go b/libpod/runtime_ctr.go
index db7a5e5c3..0c8d3edab 100644
--- a/libpod/runtime_ctr.go
+++ b/libpod/runtime_ctr.go
@@ -167,7 +167,7 @@ func (r *Runtime) newContainer(ctx context.Context, rSpec *spec.Spec, options ..
}()
if rootless.IsRootless() && ctr.config.ConmonPidFile == "" {
- ctr.config.ConmonPidFile = filepath.Join(ctr.config.StaticDir, "conmon.pid")
+ ctr.config.ConmonPidFile = filepath.Join(ctr.state.RunDir, "conmon.pid")
}
// Go through named volumes and add them.
diff --git a/pkg/rootless/rootless_linux.c b/pkg/rootless/rootless_linux.c
index 098ca7830..2356882e7 100644
--- a/pkg/rootless/rootless_linux.c
+++ b/pkg/rootless/rootless_linux.c
@@ -69,6 +69,19 @@ rootless_gid ()
static void
do_pause ()
{
+ int i;
+ struct sigaction act;
+ int const sig[] =
+ {
+ SIGALRM, SIGHUP, SIGINT, SIGPIPE, SIGQUIT, SIGTERM, SIGPOLL,
+ SIGPROF, SIGVTALRM, SIGXCPU, SIGXFSZ, 0
+ };
+
+ act.sa_handler = SIG_IGN;
+
+ for (i = 0; sig[i]; i++)
+ sigaction (sig[i], &act, NULL);
+
prctl (PR_SET_NAME, "podman pause", NULL, NULL, NULL);
while (1)
pause ();
@@ -333,6 +346,26 @@ syscall_clone (unsigned long flags, void *child_stack)
#endif
}
+int
+reexec_in_user_namespace_wait (int pid, int options)
+{
+ pid_t p;
+ int status;
+
+ do
+ p = waitpid (pid, &status, 0);
+ while (p < 0 && errno == EINTR);
+
+ if (p < 0)
+ return -1;
+
+ if (WIFEXITED (status))
+ return WEXITSTATUS (status);
+ if (WIFSIGNALED (status))
+ return 128 + WTERMSIG (status);
+ return -1;
+}
+
static int
create_pause_process (const char *pause_pid_file_path, char **argv)
{
@@ -356,6 +389,8 @@ create_pause_process (const char *pause_pid_file_path, char **argv)
while (r < 0 && errno == EINTR);
close (p[0]);
+ reexec_in_user_namespace_wait(r, 0);
+
return r == 1 && b == '0' ? 0 : -1;
}
else
@@ -560,8 +595,51 @@ check_proc_sys_userns_file (const char *path)
}
}
+static int
+copy_file_to_fd (const char *file_to_read, int outfd)
+{
+ char buf[512];
+ int fd;
+
+ fd = open (file_to_read, O_RDONLY);
+ if (fd < 0)
+ return fd;
+
+ for (;;)
+ {
+ ssize_t r, w, t = 0;
+
+ do
+ r = read (fd, buf, sizeof buf);
+ while (r < 0 && errno == EINTR);
+ if (r < 0)
+ {
+ close (fd);
+ return r;
+ }
+
+ if (r == 0)
+ break;
+
+ while (t < r)
+ {
+ do
+ w = write (outfd, &buf[t], r - t);
+ while (w < 0 && errno == EINTR);
+ if (w < 0)
+ {
+ close (fd);
+ return w;
+ }
+ t += w;
+ }
+ }
+ close (fd);
+ return 0;
+}
+
int
-reexec_in_user_namespace (int ready, char *pause_pid_file_path)
+reexec_in_user_namespace (int ready, char *pause_pid_file_path, char *file_to_read, int outputfd)
{
int ret;
pid_t pid;
@@ -574,6 +652,7 @@ reexec_in_user_namespace (int ready, char *pause_pid_file_path)
char *listen_pid = NULL;
bool do_socket_activation = false;
char *cwd = getcwd (NULL, 0);
+ sigset_t sigset, oldsigset;
if (cwd == NULL)
{
@@ -584,11 +663,11 @@ reexec_in_user_namespace (int ready, char *pause_pid_file_path)
listen_pid = getenv("LISTEN_PID");
listen_fds = getenv("LISTEN_FDS");
- if (listen_pid != NULL && listen_fds != NULL) {
- if (strtol(listen_pid, NULL, 10) == getpid()) {
- do_socket_activation = true;
+ if (listen_pid != NULL && listen_fds != NULL)
+ {
+ if (strtol(listen_pid, NULL, 10) == getpid())
+ do_socket_activation = true;
}
- }
sprintf (uid, "%d", geteuid ());
sprintf (gid, "%d", getegid ());
@@ -621,6 +700,22 @@ reexec_in_user_namespace (int ready, char *pause_pid_file_path)
return pid;
}
+ if (sigfillset (&sigset) < 0)
+ {
+ fprintf (stderr, "cannot fill sigset: %s\n", strerror (errno));
+ _exit (EXIT_FAILURE);
+ }
+ if (sigdelset (&sigset, SIGCHLD) < 0)
+ {
+ fprintf (stderr, "cannot sigdelset(SIGCHLD): %s\n", strerror (errno));
+ _exit (EXIT_FAILURE);
+ }
+ if (sigprocmask (SIG_BLOCK, &sigset, &oldsigset) < 0)
+ {
+ fprintf (stderr, "cannot block signals: %s\n", strerror (errno));
+ _exit (EXIT_FAILURE);
+ }
+
argv = get_cmd_line_args (ppid);
if (argv == NULL)
{
@@ -628,11 +723,12 @@ reexec_in_user_namespace (int ready, char *pause_pid_file_path)
_exit (EXIT_FAILURE);
}
- if (do_socket_activation) {
- char s[32];
- sprintf (s, "%d", getpid());
- setenv ("LISTEN_PID", s, true);
- }
+ if (do_socket_activation)
+ {
+ char s[32];
+ sprintf (s, "%d", getpid());
+ setenv ("LISTEN_PID", s, true);
+ }
setenv ("_CONTAINERS_USERNS_CONFIGURED", "init", 1);
setenv ("_CONTAINERS_ROOTLESS_UID", uid, 1);
@@ -685,27 +781,20 @@ reexec_in_user_namespace (int ready, char *pause_pid_file_path)
while (ret < 0 && errno == EINTR);
close (ready);
- execvp (argv[0], argv);
-
- _exit (EXIT_FAILURE);
-}
-
-int
-reexec_in_user_namespace_wait (int pid)
-{
- pid_t p;
- int status;
+ if (sigprocmask (SIG_SETMASK, &oldsigset, NULL) < 0)
+ {
+ fprintf (stderr, "cannot block signals: %s\n", strerror (errno));
+ _exit (EXIT_FAILURE);
+ }
- do
- p = waitpid (pid, &status, 0);
- while (p < 0 && errno == EINTR);
+ if (file_to_read && file_to_read[0])
+ {
+ ret = copy_file_to_fd (file_to_read, outputfd);
+ close (outputfd);
+ _exit (ret == 0 ? EXIT_SUCCESS : EXIT_FAILURE);
+ }
- if (p < 0)
- return -1;
+ execvp (argv[0], argv);
- if (WIFEXITED (status))
- return WEXITSTATUS (status);
- if (WIFSIGNALED (status))
- return 128 + WTERMSIG (status);
- return -1;
+ _exit (EXIT_FAILURE);
}
diff --git a/pkg/rootless/rootless_linux.go b/pkg/rootless/rootless_linux.go
index 9132c0fe5..d302b1777 100644
--- a/pkg/rootless/rootless_linux.go
+++ b/pkg/rootless/rootless_linux.go
@@ -26,8 +26,8 @@ import (
#include <stdlib.h>
extern uid_t rootless_uid();
extern uid_t rootless_gid();
-extern int reexec_in_user_namespace(int ready, char *pause_pid_file_path);
-extern int reexec_in_user_namespace_wait(int pid);
+extern int reexec_in_user_namespace(int ready, char *pause_pid_file_path, char *file_to_read, int fd);
+extern int reexec_in_user_namespace_wait(int pid, int options);
extern int reexec_userns_join(int userns, int mountns, char *pause_pid_file_path);
*/
import "C"
@@ -194,10 +194,24 @@ func getUserNSFirstChild(fd uintptr) (*os.File, error) {
}
}
-// JoinUserAndMountNS re-exec podman in a new userNS and join the user and mount
+func enableLinger(pausePid string) {
+ if pausePid == "" {
+ return
+ }
+ // If we are trying to write a pause pid file, make sure we can leave processes
+ // running longer than the user session.
+ err := exec.Command("loginctl", "enable-linger", fmt.Sprintf("%d", GetRootlessUID())).Run()
+ if err != nil {
+ logrus.Warnf("cannot run `loginctl enable-linger` for the current user: %v", err)
+ }
+}
+
+// joinUserAndMountNS re-exec podman in a new userNS and join the user and mount
// namespace of the specified PID without looking up its parent. Useful to join directly
// the conmon process.
-func JoinUserAndMountNS(pid uint, pausePid string) (bool, int, error) {
+func joinUserAndMountNS(pid uint, pausePid string) (bool, int, error) {
+ enableLinger(pausePid)
+
if os.Geteuid() == 0 || os.Getenv("_CONTAINERS_USERNS_CONFIGURED") != "" {
return false, -1, nil
}
@@ -226,7 +240,7 @@ func JoinUserAndMountNS(pid uint, pausePid string) (bool, int, error) {
return false, -1, errors.Errorf("cannot re-exec process")
}
- ret := C.reexec_in_user_namespace_wait(pidC)
+ ret := C.reexec_in_user_namespace_wait(pidC, 0)
if ret < 0 {
return false, -1, errors.New("error waiting for the re-exec process")
}
@@ -234,11 +248,7 @@ func JoinUserAndMountNS(pid uint, pausePid string) (bool, int, error) {
return true, int(ret), nil
}
-// BecomeRootInUserNS re-exec podman in a new userNS. It returns whether podman was re-executed
-// into a new user namespace and the return code from the re-executed podman process.
-// If podman was re-executed the caller needs to propagate the error code returned by the child
-// process.
-func BecomeRootInUserNS(pausePid string) (bool, int, error) {
+func becomeRootInUserNS(pausePid, fileToRead string, fileOutput *os.File) (bool, int, error) {
if os.Geteuid() == 0 || os.Getenv("_CONTAINERS_USERNS_CONFIGURED") != "" {
if os.Getenv("_CONTAINERS_USERNS_CONFIGURED") == "init" {
return false, 0, runInUser()
@@ -249,6 +259,13 @@ func BecomeRootInUserNS(pausePid string) (bool, int, error) {
cPausePid := C.CString(pausePid)
defer C.free(unsafe.Pointer(cPausePid))
+ cFileToRead := C.CString(fileToRead)
+ defer C.free(unsafe.Pointer(cFileToRead))
+ var fileOutputFD C.int
+ if fileOutput != nil {
+ fileOutputFD = C.int(fileOutput.Fd())
+ }
+
runtime.LockOSThread()
defer runtime.UnlockOSThread()
@@ -262,7 +279,7 @@ func BecomeRootInUserNS(pausePid string) (bool, int, error) {
defer w.Close()
defer w.Write([]byte("0"))
- pidC := C.reexec_in_user_namespace(C.int(r.Fd()), cPausePid)
+ pidC := C.reexec_in_user_namespace(C.int(r.Fd()), cPausePid, cFileToRead, fileOutputFD)
pid := int(pidC)
if pid < 0 {
return false, -1, errors.Errorf("cannot re-exec process")
@@ -328,6 +345,10 @@ func BecomeRootInUserNS(pausePid string) (bool, int, error) {
return false, -1, errors.Wrapf(err, "read from sync pipe")
}
+ if fileOutput != nil {
+ return true, 0, nil
+ }
+
if b[0] == '2' {
// We have lost the race for writing the PID file, as probably another
// process created a namespace and wrote the PID.
@@ -336,7 +357,7 @@ func BecomeRootInUserNS(pausePid string) (bool, int, error) {
if err == nil {
pid, err := strconv.ParseUint(string(data), 10, 0)
if err == nil {
- return JoinUserAndMountNS(uint(pid), "")
+ return joinUserAndMountNS(uint(pid), "")
}
}
return false, -1, errors.Wrapf(err, "error setting up the process")
@@ -368,10 +389,96 @@ func BecomeRootInUserNS(pausePid string) (bool, int, error) {
}
}()
- ret := C.reexec_in_user_namespace_wait(pidC)
+ ret := C.reexec_in_user_namespace_wait(pidC, 0)
if ret < 0 {
return false, -1, errors.New("error waiting for the re-exec process")
}
return true, int(ret), nil
}
+
+// BecomeRootInUserNS re-exec podman in a new userNS. It returns whether podman was re-executed
+// into a new user namespace and the return code from the re-executed podman process.
+// If podman was re-executed the caller needs to propagate the error code returned by the child
+// process.
+func BecomeRootInUserNS(pausePid string) (bool, int, error) {
+ enableLinger(pausePid)
+ return becomeRootInUserNS(pausePid, "", nil)
+}
+
+// TryJoinFromFilePaths attempts to join the namespaces of the pid files in paths.
+// This is useful when there are already running containers and we
+// don't have a pause process yet. We can use the paths to the conmon
+// processes to attempt joining their namespaces.
+// If needNewNamespace is set, the file is read from a temporary user
+// namespace, this is useful for containers that are running with a
+// different uidmap and the unprivileged user has no way to read the
+// file owned by the root in the container.
+func TryJoinFromFilePaths(pausePidPath string, needNewNamespace bool, paths []string) (bool, int, error) {
+ if len(paths) == 0 {
+ return BecomeRootInUserNS(pausePidPath)
+ }
+
+ var lastErr error
+ var pausePid int
+
+ for _, path := range paths {
+ if !needNewNamespace {
+ data, err := ioutil.ReadFile(path)
+ if err != nil {
+ lastErr = err
+ continue
+ }
+
+ pausePid, err = strconv.Atoi(string(data))
+ if err != nil {
+ lastErr = errors.Wrapf(err, "cannot parse file %s", path)
+ continue
+ }
+
+ lastErr = nil
+ break
+ } else {
+ fds, err := syscall.Socketpair(syscall.AF_UNIX, syscall.SOCK_DGRAM, 0)
+ if err != nil {
+ lastErr = err
+ continue
+ }
+
+ r, w := os.NewFile(uintptr(fds[0]), "read file"), os.NewFile(uintptr(fds[1]), "write file")
+
+ defer w.Close()
+ defer r.Close()
+
+ if _, _, err := becomeRootInUserNS("", path, w); err != nil {
+ lastErr = err
+ continue
+ }
+
+ w.Close()
+ defer func() {
+ r.Close()
+ C.reexec_in_user_namespace_wait(-1, 0)
+ }()
+
+ b := make([]byte, 32)
+
+ n, err := r.Read(b)
+ if err != nil {
+ lastErr = errors.Wrapf(err, "cannot read %s\n", path)
+ continue
+ }
+
+ pausePid, err = strconv.Atoi(string(b[:n]))
+ if err == nil {
+ lastErr = nil
+ break
+ }
+ }
+ }
+ if lastErr != nil {
+ return false, 0, lastErr
+ }
+
+ return joinUserAndMountNS(uint(pausePid), pausePidPath)
+}
diff --git a/pkg/rootless/rootless_unsupported.go b/pkg/rootless/rootless_unsupported.go
index 221baff97..c063adee5 100644
--- a/pkg/rootless/rootless_unsupported.go
+++ b/pkg/rootless/rootless_unsupported.go
@@ -29,10 +29,14 @@ func GetRootlessGID() int {
return -1
}
-// JoinUserAndMountNS re-exec podman in a new userNS and join the user and mount
-// namespace of the specified PID without looking up its parent. Useful to join directly
-// the conmon process. It is a convenience function for JoinUserAndMountNSWithOpts
-// with a default configuration.
-func JoinUserAndMountNS(pid uint, pausePid string) (bool, int, error) {
+// TryJoinFromFilePaths attempts to join the namespaces of the pid files in paths.
+// This is useful when there are already running containers and we
+// don't have a pause process yet. We can use the paths to the conmon
+// processes to attempt joining their namespaces.
+// If needNewNamespace is set, the file is read from a temporary user
+// namespace, this is useful for containers that are running with a
+// different uidmap and the unprivileged user has no way to read the
+// file owned by the root in the container.
+func TryJoinFromFilePaths(pausePidPath string, needNewNamespace bool, paths []string) (bool, int, error) {
return false, -1, errors.New("this function is not supported on this os")
}
diff --git a/test/e2e/cp_test.go b/test/e2e/cp_test.go
index 71fc064a5..f8df5d3d0 100644
--- a/test/e2e/cp_test.go
+++ b/test/e2e/cp_test.go
@@ -131,4 +131,31 @@ var _ = Describe("Podman cp", func() {
session.WaitWithDefaultTimeout()
Expect(session.ExitCode()).To(Equal(0))
})
+
+ It("podman cp tar", func() {
+ path, err := os.Getwd()
+ Expect(err).To(BeNil())
+ testDirPath := filepath.Join(path, "TestDir")
+ err = os.Mkdir(testDirPath, 0777)
+ Expect(err).To(BeNil())
+ cmd := exec.Command("tar", "-cvf", "file.tar", testDirPath)
+ _, err = cmd.Output()
+ Expect(err).To(BeNil())
+
+ session := podmanTest.Podman([]string{"create", "--name", "testctr", ALPINE, "ls", "-l", "foo"})
+ session.WaitWithDefaultTimeout()
+ Expect(session.ExitCode()).To(Equal(0))
+
+ session = podmanTest.Podman([]string{"cp", "file.tar", "testctr:/foo/"})
+ session.WaitWithDefaultTimeout()
+ Expect(session.ExitCode()).To(Equal(0))
+
+ session = podmanTest.Podman([]string{"start", "-a", "testctr"})
+ session.WaitWithDefaultTimeout()
+ Expect(session.ExitCode()).To(Equal(0))
+ Expect(session.OutputToString()).To(ContainSubstring("file.tar"))
+
+ os.Remove("file.tar")
+ os.RemoveAll(testDirPath)
+ })
})