unshare: add --fork options for pid namespaces

The ability of unshare to launch a new pid namespace is a bit limited.
The first process in the namespace is expected to be the "init" for it.
When it's not, you get bad behavior.

For example, trying to launch a shell in a new pid namespace fails very
quickly:
	$ sudo unshare -p dash
	# uname -r
	3.8.3
	# uname -m
	dash: 2: Cannot fork
	# ls -ld /
	dash: 3: Cannot fork
	# echo $$
	1324

For this to work smoothly, we need an init process to actively watch over
things.  But forcing people to re-use an existing init or write their own
mini init is a bit overkill.  So let's add a --fork option to unshare to
do this common bit of book keeping.  Now we can do:
	$ sudo unshare -p --fork dash
	# uname -r
	3.8.3
	# uname -m
	x86_64
	# ls -ld /
	drwxr-xr-x 22 root root 4096 May  4 14:01 /
	# echo $$
	1

Thanks to Michael Kerrisk for his namespace articles on lwn.net

[kzak@redhat.com: - fix "forkif logic, remove --mount-proc]

Signed-off-by: Mike Frysinger <vapier@gentoo.org>
Signed-off-by: Karel Zak <kzak@redhat.com>
This commit is contained in:
Mike Frysinger 2013-06-27 20:04:58 -04:00 committed by Karel Zak
parent c9678832b1
commit 5088ec338f
2 changed files with 33 additions and 4 deletions

View File

@ -56,13 +56,17 @@ Unshare the mount namespace.
Unshare the network namespace.
.TP
.BR \-p , " \-\-pid"
Unshare the pid namespace.
Unshare the pid namespace. See also \fB--fork\fP option.
.TP
.BR \-u , " \-\-uts"
Unshare the UTS namespace.
.TP
.BR \-U , " \-\-user"
Unshare the user namespace.
.TP
.BR \-f , " \-\-fork"
Fork the specified process as a child of unshare rather than running it
directly. This is useful when creating a new pid namespace.
.SH SEE ALSO
.BR unshare (2),
.BR clone (2)

View File

@ -24,6 +24,7 @@
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>
#include "nls.h"
#include "c.h"
@ -46,6 +47,7 @@ static void usage(int status)
fputs(_(" -n, --net unshare network namespace\n"), out);
fputs(_(" -p, --pid unshare pid namespace\n"), out);
fputs(_(" -U, --user unshare user namespace\n"), out);
fputs(_(" -f, --fork fork before launching <program>\n"), out);
fputs(USAGE_SEPARATOR, out);
fputs(USAGE_HELP, out);
@ -66,20 +68,23 @@ int main(int argc, char *argv[])
{ "net", no_argument, 0, 'n' },
{ "pid", no_argument, 0, 'p' },
{ "user", no_argument, 0, 'U' },
{ "fork", no_argument, 0, 'f' },
{ NULL, 0, 0, 0 }
};
int unshare_flags = 0;
int c;
int c, forkit = 0;
setlocale(LC_MESSAGES, "");
bindtextdomain(PACKAGE, LOCALEDIR);
textdomain(PACKAGE);
atexit(close_stdout);
while ((c = getopt_long(argc, argv, "hVmuinpU", longopts, NULL)) != -1) {
while ((c = getopt_long(argc, argv, "fhVmuinpU", longopts, NULL)) != -1) {
switch (c) {
case 'f':
forkit = 1;
break;
case 'h':
usage(EXIT_SUCCESS);
case 'V':
@ -111,6 +116,26 @@ int main(int argc, char *argv[])
if (-1 == unshare(unshare_flags))
err(EXIT_FAILURE, _("unshare failed"));
if (forkit) {
int status;
pid_t pid = fork();
switch(pid) {
case -1:
err(EXIT_FAILURE, _("fork failed"));
case 0: /* child */
break;
default: /* parent */
if (waitpid(pid, &status, 0) == -1)
err(EXIT_FAILURE, _("waitpid failed"));
if (WIFEXITED(status))
return WEXITSTATUS(status);
else if (WIFSIGNALED(status))
kill(getpid(), WTERMSIG(status));
err(EXIT_FAILURE, _("child exit failed"));
}
}
if (optind < argc) {
execvp(argv[optind], argv + optind);
err(EXIT_FAILURE, _("failed to execute %s"), argv[optind]);