]> git.ipfire.org Git - thirdparty/util-linux.git/blame - sys-utils/unshare.c
Use --help suggestion on invalid option
[thirdparty/util-linux.git] / sys-utils / unshare.c
CommitLineData
4205f1fd
MG
1/*
2 * unshare(1) - command-line interface for unshare(2)
3 *
4 * Copyright (C) 2009 Mikhail Gusarov <dottedmag@dottedmag.net>
5 *
6 * This program is free software; you can redistribute it and/or modify it
7 * under the terms of the GNU General Public License as published by the
8 * Free Software Foundation; either version 2, or (at your option) any
9 * later version.
10 *
11 * This program is distributed in the hope that it will be useful, but
12 * WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License along
17 * with this program; if not, write to the Free Software Foundation, Inc.,
7cebf0bb 18 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
4205f1fd
MG
19 */
20
4205f1fd
MG
21#include <errno.h>
22#include <getopt.h>
23#include <sched.h>
24#include <stdio.h>
25#include <stdlib.h>
26#include <unistd.h>
5088ec33 27#include <sys/wait.h>
6728ca10 28#include <sys/mount.h>
c84f2590
KZ
29#include <sys/types.h>
30#include <sys/stat.h>
c84f2590 31
d754315c
RM
32/* we only need some defines missing in sys/mount.h, no libmount linkage */
33#include <libmount.h>
34
4205f1fd 35#include "nls.h"
eb76ca98 36#include "c.h"
efb8854f 37#include "closestream.h"
c91280a4 38#include "namespace.h"
57580694 39#include "exec_shell.h"
4da21e37
LR
40#include "xalloc.h"
41#include "pathnames.h"
42#include "all-io.h"
43
99fcafdf
YK
44/* synchronize parent and child by pipe */
45#define PIPE_SYNC_BYTE 0x06
46
f0f22e9c
KZ
47/* 'private' is kernel default */
48#define UNSHARE_PROPAGATION_DEFAULT (MS_REC | MS_PRIVATE)
49
0490a6ca
KZ
50/* /proc namespace files and mountpoints for binds */
51static struct namespace_file {
52 int type; /* CLONE_NEW* */
53 const char *name; /* ns/<type> */
54 const char *target; /* user specified target for bind mount */
55} namespace_files[] = {
f9e7b66d
SH
56 { .type = CLONE_NEWUSER, .name = "ns/user" },
57 { .type = CLONE_NEWCGROUP,.name = "ns/cgroup" },
58 { .type = CLONE_NEWIPC, .name = "ns/ipc" },
59 { .type = CLONE_NEWUTS, .name = "ns/uts" },
60 { .type = CLONE_NEWNET, .name = "ns/net" },
61 { .type = CLONE_NEWPID, .name = "ns/pid" },
62 { .type = CLONE_NEWNS, .name = "ns/mnt" },
0490a6ca
KZ
63 { .name = NULL }
64};
65
66static int npersists; /* number of persistent namespaces */
67
68
fbceefde
KZ
69enum {
70 SETGROUPS_NONE = -1,
71 SETGROUPS_DENY = 0,
72 SETGROUPS_ALLOW = 1,
73};
74
75static const char *setgroups_strings[] =
76{
77 [SETGROUPS_DENY] = "deny",
78 [SETGROUPS_ALLOW] = "allow"
79};
80
81static int setgroups_str2id(const char *str)
82{
83 size_t i;
84
85 for (i = 0; i < ARRAY_SIZE(setgroups_strings); i++)
86 if (strcmp(str, setgroups_strings[i]) == 0)
87 return i;
88
89 errx(EXIT_FAILURE, _("unsupported --setgroups argument '%s'"), str);
90}
91
92static void setgroups_control(int action)
0bf15941
EB
93{
94 const char *file = _PATH_PROC_SETGROUPS;
fbceefde 95 const char *cmd;
0bf15941
EB
96 int fd;
97
fbceefde
KZ
98 if (action < 0 || (size_t) action >= ARRAY_SIZE(setgroups_strings))
99 return;
100 cmd = setgroups_strings[action];
101
0bf15941
EB
102 fd = open(file, O_WRONLY);
103 if (fd < 0) {
104 if (errno == ENOENT)
105 return;
7ff635bf 106 err(EXIT_FAILURE, _("cannot open %s"), file);
0bf15941
EB
107 }
108
fbceefde 109 if (write_all(fd, cmd, strlen(cmd)))
0bf15941
EB
110 err(EXIT_FAILURE, _("write failed %s"), file);
111 close(fd);
112}
113
4da21e37
LR
114static void map_id(const char *file, uint32_t from, uint32_t to)
115{
116 char *buf;
117 int fd;
118
119 fd = open(file, O_WRONLY);
120 if (fd < 0)
121 err(EXIT_FAILURE, _("cannot open %s"), file);
122
123 xasprintf(&buf, "%u %u 1", from, to);
124 if (write_all(fd, buf, strlen(buf)))
125 err(EXIT_FAILURE, _("write failed %s"), file);
126 free(buf);
127 close(fd);
128}
4205f1fd 129
f0f22e9c
KZ
130static unsigned long parse_propagation(const char *str)
131{
132 size_t i;
133 static const struct prop_opts {
134 const char *name;
135 unsigned long flag;
136 } opts[] = {
137 { "slave", MS_REC | MS_SLAVE },
138 { "private", MS_REC | MS_PRIVATE },
139 { "shared", MS_REC | MS_SHARED },
140 { "unchanged", 0 }
141 };
142
143 for (i = 0; i < ARRAY_SIZE(opts); i++) {
144 if (strcmp(opts[i].name, str) == 0)
145 return opts[i].flag;
146 }
147
148 errx(EXIT_FAILURE, _("unsupported propagation mode: %s"), str);
149}
150
151static void set_propagation(unsigned long flags)
152{
153 if (flags == 0)
154 return;
155
156 if (mount("none", "/", NULL, flags, NULL) != 0)
157 err(EXIT_FAILURE, _("cannot change root filesystem propagation"));
158}
159
0490a6ca
KZ
160
161static int set_ns_target(int type, const char *path)
162{
163 struct namespace_file *ns;
164
165 for (ns = namespace_files; ns->name; ns++) {
166 if (ns->type != type)
167 continue;
168 ns->target = path;
169 npersists++;
170 return 0;
171 }
172
173 return -EINVAL;
174}
175
176static int bind_ns_files(pid_t pid)
177{
178 struct namespace_file *ns;
179 char src[PATH_MAX];
180
181 for (ns = namespace_files; ns->name; ns++) {
182 if (!ns->target)
183 continue;
184
185 snprintf(src, sizeof(src), "/proc/%u/%s", (unsigned) pid, ns->name);
186
187 if (mount(src, ns->target, NULL, MS_BIND, NULL) != 0)
188 err(EXIT_FAILURE, _("mount %s on %s failed"), src, ns->target);
189 }
190
191 return 0;
192}
193
c84f2590
KZ
194static ino_t get_mnt_ino(pid_t pid)
195{
196 struct stat st;
197 char path[PATH_MAX];
198
199 snprintf(path, sizeof(path), "/proc/%u/ns/mnt", (unsigned) pid);
200
201 if (stat(path, &st) != 0)
202 err(EXIT_FAILURE, _("cannot stat %s"), path);
203 return st.st_ino;
204}
205
99fcafdf 206static void bind_ns_files_from_child(pid_t *child, int fds[2])
c84f2590 207{
99fcafdf 208 char ch;
c84f2590
KZ
209 pid_t ppid = getpid();
210 ino_t ino = get_mnt_ino(ppid);
211
99fcafdf
YK
212 if (pipe(fds) < 0)
213 err(EXIT_FAILURE, _("pipe failed"));
214
c84f2590
KZ
215 *child = fork();
216
99fcafdf 217 switch (*child) {
c84f2590
KZ
218 case -1:
219 err(EXIT_FAILURE, _("fork failed"));
99fcafdf 220
c84f2590 221 case 0: /* child */
99fcafdf
YK
222 close(fds[1]);
223 fds[1] = -1;
224
225 /* wait for parent */
226 if (read_all(fds[0], &ch, 1) != 1 && ch != PIPE_SYNC_BYTE)
227 err(EXIT_FAILURE, _("failed to read pipe"));
228 if (get_mnt_ino(ppid) == ino)
229 exit(EXIT_FAILURE);
c84f2590
KZ
230 bind_ns_files(ppid);
231 exit(EXIT_SUCCESS);
232 break;
99fcafdf 233
c84f2590 234 default: /* parent */
99fcafdf
YK
235 close(fds[0]);
236 fds[0] = -1;
c84f2590
KZ
237 break;
238 }
239}
240
4205f1fd
MG
241static void usage(int status)
242{
243 FILE *out = status == EXIT_SUCCESS ? stdout : stderr;
244
6a87798a 245 fputs(USAGE_HEADER, out);
298dc4ff
BS
246 fprintf(out, _(" %s [options] <program> [<argument>...]\n"),
247 program_invocation_short_name);
4205f1fd 248
451dbcfa
BS
249 fputs(USAGE_SEPARATOR, out);
250 fputs(_("Run a program with some namespaces unshared from the parent.\n"), out);
251
6a87798a 252 fputs(USAGE_OPTIONS, out);
0490a6ca
KZ
253 fputs(_(" -m, --mount[=<file>] unshare mounts namespace\n"), out);
254 fputs(_(" -u, --uts[=<file>] unshare UTS namespace (hostname etc)\n"), out);
255 fputs(_(" -i, --ipc[=<file>] unshare System V IPC namespace\n"), out);
256 fputs(_(" -n, --net[=<file>] unshare network namespace\n"), out);
257 fputs(_(" -p, --pid[=<file>] unshare pid namespace\n"), out);
258 fputs(_(" -U, --user[=<file>] unshare user namespace\n"), out);
f9e7b66d 259 fputs(_(" -C, --cgroup[=<file>] unshare cgroup namespace\n"), out);
6728ca10
KZ
260 fputs(_(" -f, --fork fork before launching <program>\n"), out);
261 fputs(_(" --mount-proc[=<dir>] mount proc filesystem first (implies --mount)\n"), out);
4da21e37 262 fputs(_(" -r, --map-root-user map current user to root (implies --user)\n"), out);
d000470d 263 fputs(_(" --propagation slave|shared|private|unchanged\n"
f0f22e9c 264 " modify mount propagation in mount namespace\n"), out);
54fefa07 265 fputs(_(" -s, --setgroups allow|deny control the setgroups syscall in user namespaces\n"), out);
4205f1fd 266
6a87798a
SK
267 fputs(USAGE_SEPARATOR, out);
268 fputs(USAGE_HELP, out);
269 fputs(USAGE_VERSION, out);
270 fprintf(out, USAGE_MAN_TAIL("unshare(1)"));
271
4205f1fd
MG
272 exit(status);
273}
274
275int main(int argc, char *argv[])
276{
6728ca10 277 enum {
fbceefde 278 OPT_MOUNTPROC = CHAR_MAX + 1,
f0f22e9c 279 OPT_PROPAGATION,
fbceefde 280 OPT_SETGROUPS
6728ca10 281 };
6c7d5ae9 282 static const struct option longopts[] = {
ef6acdb8 283 { "help", no_argument, 0, 'h' },
6a87798a 284 { "version", no_argument, 0, 'V'},
0490a6ca 285
f9e7b66d
SH
286 { "mount", optional_argument, 0, 'm' },
287 { "uts", optional_argument, 0, 'u' },
288 { "ipc", optional_argument, 0, 'i' },
289 { "net", optional_argument, 0, 'n' },
290 { "pid", optional_argument, 0, 'p' },
291 { "user", optional_argument, 0, 'U' },
292 { "cgroup", optional_argument, 0, 'C' },
0490a6ca 293
5088ec33 294 { "fork", no_argument, 0, 'f' },
6728ca10 295 { "mount-proc", optional_argument, 0, OPT_MOUNTPROC },
4da21e37 296 { "map-root-user", no_argument, 0, 'r' },
f0f22e9c 297 { "propagation", required_argument, 0, OPT_PROPAGATION },
fbceefde 298 { "setgroups", required_argument, 0, OPT_SETGROUPS },
4ffde0b6 299 { NULL, 0, 0, 0 }
4205f1fd
MG
300 };
301
fbceefde 302 int setgrpcmd = SETGROUPS_NONE;
4205f1fd 303 int unshare_flags = 0;
4da21e37 304 int c, forkit = 0, maproot = 0;
6728ca10 305 const char *procmnt = NULL;
c84f2590 306 pid_t pid = 0;
99fcafdf 307 int fds[2];
c84f2590 308 int status;
f0f22e9c 309 unsigned long propagation = UNSHARE_PROPAGATION_DEFAULT;
4da21e37
LR
310 uid_t real_euid = geteuid();
311 gid_t real_egid = getegid();;
4205f1fd 312
999ac5e2 313 setlocale(LC_ALL, "");
4205f1fd
MG
314 bindtextdomain(PACKAGE, LOCALEDIR);
315 textdomain(PACKAGE);
efb8854f 316 atexit(close_stdout);
4205f1fd 317
f9e7b66d 318 while ((c = getopt_long(argc, argv, "+fhVmuinpCUr", longopts, NULL)) != -1) {
2eefe517 319 switch (c) {
5088ec33
MF
320 case 'f':
321 forkit = 1;
322 break;
4205f1fd
MG
323 case 'h':
324 usage(EXIT_SUCCESS);
6a87798a
SK
325 case 'V':
326 printf(UTIL_LINUX_VERSION);
327 return EXIT_SUCCESS;
4205f1fd 328 case 'm':
ef6acdb8 329 unshare_flags |= CLONE_NEWNS;
0490a6ca
KZ
330 if (optarg)
331 set_ns_target(CLONE_NEWNS, optarg);
4205f1fd
MG
332 break;
333 case 'u':
ef6acdb8 334 unshare_flags |= CLONE_NEWUTS;
0490a6ca
KZ
335 if (optarg)
336 set_ns_target(CLONE_NEWUTS, optarg);
4205f1fd
MG
337 break;
338 case 'i':
ef6acdb8 339 unshare_flags |= CLONE_NEWIPC;
0490a6ca
KZ
340 if (optarg)
341 set_ns_target(CLONE_NEWIPC, optarg);
4205f1fd
MG
342 break;
343 case 'n':
ef6acdb8 344 unshare_flags |= CLONE_NEWNET;
0490a6ca
KZ
345 if (optarg)
346 set_ns_target(CLONE_NEWNET, optarg);
4205f1fd 347 break;
bc7f9b95
EB
348 case 'p':
349 unshare_flags |= CLONE_NEWPID;
0490a6ca
KZ
350 if (optarg)
351 set_ns_target(CLONE_NEWPID, optarg);
bc7f9b95
EB
352 break;
353 case 'U':
354 unshare_flags |= CLONE_NEWUSER;
0490a6ca
KZ
355 if (optarg)
356 set_ns_target(CLONE_NEWUSER, optarg);
bc7f9b95 357 break;
f9e7b66d
SH
358 case 'C':
359 unshare_flags |= CLONE_NEWCGROUP;
360 if (optarg)
361 set_ns_target(CLONE_NEWCGROUP, optarg);
362 break;
6728ca10
KZ
363 case OPT_MOUNTPROC:
364 unshare_flags |= CLONE_NEWNS;
365 procmnt = optarg ? optarg : "/proc";
366 break;
4da21e37
LR
367 case 'r':
368 unshare_flags |= CLONE_NEWUSER;
369 maproot = 1;
370 break;
fbceefde
KZ
371 case OPT_SETGROUPS:
372 setgrpcmd = setgroups_str2id(optarg);
373 break;
f0f22e9c
KZ
374 case OPT_PROPAGATION:
375 propagation = parse_propagation(optarg);
376 break;
4205f1fd 377 default:
677ec86c 378 errtryhelp(EXIT_FAILURE);
4205f1fd
MG
379 }
380 }
381
c84f2590 382 if (npersists && (unshare_flags & CLONE_NEWNS))
99fcafdf 383 bind_ns_files_from_child(&pid, fds);
c84f2590 384
2eefe517 385 if (-1 == unshare(unshare_flags))
4205f1fd
MG
386 err(EXIT_FAILURE, _("unshare failed"));
387
c84f2590
KZ
388 if (npersists) {
389 if (pid && (unshare_flags & CLONE_NEWNS)) {
c84f2590 390 int rc;
99fcafdf
YK
391 char ch = PIPE_SYNC_BYTE;
392
393 /* signal child we are ready */
394 write_all(fds[1], &ch, 1);
395 close(fds[1]);
396 fds[1] = -1;
c84f2590 397
99fcafdf 398 /* wait for bind_ns_files_from_child() */
c84f2590
KZ
399 do {
400 rc = waitpid(pid, &status, 0);
401 if (rc < 0) {
402 if (errno == EINTR)
403 continue;
404 err(EXIT_FAILURE, _("waitpid failed"));
405 }
406 if (WIFEXITED(status) &&
407 WEXITSTATUS(status) != EXIT_SUCCESS)
408 return WEXITSTATUS(status);
409 } while (rc < 0);
410 } else
411 /* simple way, just bind */
412 bind_ns_files(getpid());
413 }
414
5088ec33 415 if (forkit) {
c84f2590 416 pid = fork();
5088ec33
MF
417
418 switch(pid) {
419 case -1:
420 err(EXIT_FAILURE, _("fork failed"));
421 case 0: /* child */
422 break;
423 default: /* parent */
424 if (waitpid(pid, &status, 0) == -1)
425 err(EXIT_FAILURE, _("waitpid failed"));
426 if (WIFEXITED(status))
427 return WEXITSTATUS(status);
428 else if (WIFSIGNALED(status))
429 kill(getpid(), WTERMSIG(status));
430 err(EXIT_FAILURE, _("child exit failed"));
431 }
432 }
433
0490a6ca 434
4da21e37 435 if (maproot) {
fbceefde
KZ
436 if (setgrpcmd == SETGROUPS_ALLOW)
437 errx(EXIT_FAILURE, _("options --setgroups=allow and "
54fefa07 438 "--map-root-user are mutually exclusive"));
fbceefde
KZ
439
440 /* since Linux 3.19 unprivileged writing of /proc/self/gid_map
441 * has s been disabled unless /proc/self/setgroups is written
442 * first to permanently disable the ability to call setgroups
443 * in that user namespace. */
444 setgroups_control(SETGROUPS_DENY);
4da21e37
LR
445 map_id(_PATH_PROC_UIDMAP, 0, real_euid);
446 map_id(_PATH_PROC_GIDMAP, 0, real_egid);
fbceefde
KZ
447
448 } else if (setgrpcmd != SETGROUPS_NONE)
449 setgroups_control(setgrpcmd);
4da21e37 450
f0f22e9c
KZ
451 if ((unshare_flags & CLONE_NEWNS) && propagation)
452 set_propagation(propagation);
453
6728ca10
KZ
454 if (procmnt &&
455 (mount("none", procmnt, NULL, MS_PRIVATE|MS_REC, NULL) != 0 ||
456 mount("proc", procmnt, "proc", MS_NOSUID|MS_NOEXEC|MS_NODEV, NULL) != 0))
457 err(EXIT_FAILURE, _("mount %s failed"), procmnt);
458
57580694
ZJS
459 if (optind < argc) {
460 execvp(argv[optind], argv + optind);
461 err(EXIT_FAILURE, _("failed to execute %s"), argv[optind]);
462 }
463 exec_shell();
4205f1fd 464}