]> git.ipfire.org Git - thirdparty/util-linux.git/blob - sys-utils/nsenter.c
sys-utils: cleanup license lines, add SPDX
[thirdparty/util-linux.git] / sys-utils / nsenter.c
1 /*
2 * SPDX-License-Identifier: GPL-2.0
3 *
4 * This program is free software; you can redistribute it and/or modify it
5 * under the terms of the GNU General Public License as published by the
6 * Free Software Foundation; version 2.
7 *
8 * Copyright (C) 2012-2023 Eric Biederman <ebiederm@xmission.com>
9 *
10 * nsenter(1) - command-line interface for setns(2)
11 */
12 #include <dirent.h>
13 #include <errno.h>
14 #include <getopt.h>
15 #include <sched.h>
16 #include <stdio.h>
17 #include <stdlib.h>
18 #include <stdbool.h>
19 #include <unistd.h>
20 #include <assert.h>
21 #include <sys/types.h>
22 #include <sys/wait.h>
23 #include <grp.h>
24 #include <sys/stat.h>
25 #include <sys/statfs.h>
26
27 #include <sys/ioctl.h>
28 #ifdef HAVE_LINUX_NSFS_H
29 # include <linux/nsfs.h>
30 #endif
31 #ifndef NS_GET_USERNS
32 # define NS_GET_USERNS _IO(0xb7, 0x1)
33 #endif
34
35 #ifdef HAVE_LIBSELINUX
36 # include <selinux/selinux.h>
37 #endif
38
39 #include "strutils.h"
40 #include "nls.h"
41 #include "c.h"
42 #include "closestream.h"
43 #include "namespace.h"
44 #include "exec_shell.h"
45 #include "optutils.h"
46 #include "xalloc.h"
47 #include "all-io.h"
48 #include "env.h"
49 #include "caputils.h"
50 #include "statfs_magic.h"
51 #include "pathnames.h"
52
53 static struct namespace_file {
54 int nstype;
55 const char *name;
56 int fd;
57 } namespace_files[] = {
58 /* Careful the order is significant in this array.
59 *
60 * The user namespace comes either first or last: first if
61 * you're using it to increase your privilege and last if
62 * you're using it to decrease. We enter the namespaces in
63 * two passes starting initially from offset 1 and then offset
64 * 0 if that fails.
65 */
66 { .nstype = CLONE_NEWUSER, .name = "ns/user", .fd = -1 },
67 { .nstype = CLONE_NEWCGROUP,.name = "ns/cgroup", .fd = -1 },
68 { .nstype = CLONE_NEWIPC, .name = "ns/ipc", .fd = -1 },
69 { .nstype = CLONE_NEWUTS, .name = "ns/uts", .fd = -1 },
70 { .nstype = CLONE_NEWNET, .name = "ns/net", .fd = -1 },
71 { .nstype = CLONE_NEWPID, .name = "ns/pid", .fd = -1 },
72 { .nstype = CLONE_NEWNS, .name = "ns/mnt", .fd = -1 },
73 { .nstype = CLONE_NEWTIME, .name = "ns/time", .fd = -1 },
74 { .nstype = 0, .name = NULL, .fd = -1 }
75 };
76
77 static void __attribute__((__noreturn__)) usage(void)
78 {
79 FILE *out = stdout;
80
81 fputs(USAGE_HEADER, out);
82 fprintf(out, _(" %s [options] [<program> [<argument>...]]\n"),
83 program_invocation_short_name);
84
85 fputs(USAGE_SEPARATOR, out);
86 fputs(_("Run a program with namespaces of other processes.\n"), out);
87
88 fputs(USAGE_OPTIONS, out);
89 fputs(_(" -a, --all enter all namespaces\n"), out);
90 fputs(_(" -t, --target <pid> target process to get namespaces from\n"), out);
91 fputs(_(" -m, --mount[=<file>] enter mount namespace\n"), out);
92 fputs(_(" -u, --uts[=<file>] enter UTS namespace (hostname etc)\n"), out);
93 fputs(_(" -i, --ipc[=<file>] enter System V IPC namespace\n"), out);
94 fputs(_(" -n, --net[=<file>] enter network namespace\n"), out);
95 fputs(_(" -p, --pid[=<file>] enter pid namespace\n"), out);
96 fputs(_(" -C, --cgroup[=<file>] enter cgroup namespace\n"), out);
97 fputs(_(" -U, --user[=<file>] enter user namespace\n"), out);
98 fputs(_(" --user-parent enter parent user namespace\n"), out);
99 fputs(_(" -T, --time[=<file>] enter time namespace\n"), out);
100 fputs(_(" -S, --setuid[=<uid>] set uid in entered namespace\n"), out);
101 fputs(_(" -G, --setgid[=<gid>] set gid in entered namespace\n"), out);
102 fputs(_(" --preserve-credentials do not touch uids or gids\n"), out);
103 fputs(_(" --keep-caps retain capabilities granted in user namespaces\n"), out);
104 fputs(_(" -r, --root[=<dir>] set the root directory\n"), out);
105 fputs(_(" -w, --wd[=<dir>] set the working directory\n"), out);
106 fputs(_(" -W, --wdns <dir> set the working directory in namespace\n"), out);
107 fputs(_(" -e, --env inherit environment variables from target process\n"), out);
108 fputs(_(" -F, --no-fork do not fork before exec'ing <program>\n"), out);
109 fputs(_(" -c, --join-cgroup join the cgroup of the target process\n"), out);
110 #ifdef HAVE_LIBSELINUX
111 fputs(_(" -Z, --follow-context set SELinux context according to --target PID\n"), out);
112 #endif
113
114 fputs(USAGE_SEPARATOR, out);
115 fprintf(out, USAGE_HELP_OPTIONS(24));
116 fprintf(out, USAGE_MAN_TAIL("nsenter(1)"));
117
118 exit(EXIT_SUCCESS);
119 }
120
121 static pid_t namespace_target_pid = 0;
122 static int root_fd = -1;
123 static int wd_fd = -1;
124 static int env_fd = -1;
125 static int uid_gid_fd = -1;
126 static int cgroup_procs_fd = -1;
127
128 static void set_parent_user_ns_fd(void)
129 {
130 struct namespace_file *nsfile = NULL;
131 struct namespace_file *user_nsfile = NULL;
132 int parent_ns = -1;
133
134 for (nsfile = namespace_files; nsfile->nstype; nsfile++) {
135 if (nsfile->nstype == CLONE_NEWUSER)
136 user_nsfile = nsfile;
137
138 if (nsfile->fd == -1)
139 continue;
140
141 parent_ns = ioctl(nsfile->fd, NS_GET_USERNS);
142 if (parent_ns < 0)
143 err(EXIT_FAILURE, _("failed to open parent ns of %s"), nsfile->name);
144
145 break;
146 }
147
148 if (parent_ns < 0)
149 errx(EXIT_FAILURE, _("no namespaces to get parent of"));
150 if (user_nsfile)
151 user_nsfile->fd = parent_ns;
152 }
153
154
155 static void open_target_fd(int *fd, const char *type, const char *path)
156 {
157 char pathbuf[PATH_MAX];
158
159 if (!path && namespace_target_pid) {
160 snprintf(pathbuf, sizeof(pathbuf), "/proc/%u/%s",
161 namespace_target_pid, type);
162 path = pathbuf;
163 }
164 if (!path)
165 errx(EXIT_FAILURE,
166 _("neither filename nor target pid supplied for %s"),
167 type);
168
169 if (*fd >= 0)
170 close(*fd);
171
172 *fd = open(path, O_RDONLY);
173 if (*fd < 0)
174 err(EXIT_FAILURE, _("cannot open %s"), path);
175 }
176
177 static void open_namespace_fd(int nstype, const char *path)
178 {
179 struct namespace_file *nsfile;
180
181 for (nsfile = namespace_files; nsfile->nstype; nsfile++) {
182 if (nstype != nsfile->nstype)
183 continue;
184
185 open_target_fd(&nsfile->fd, nsfile->name, path);
186 return;
187 }
188 /* This should never happen */
189 assert(nsfile->nstype);
190 }
191
192 static int get_ns_ino(const char *path, ino_t *ino)
193 {
194 struct stat st;
195
196 if (stat(path, &st) != 0)
197 return -errno;
198 *ino = st.st_ino;
199 return 0;
200 }
201
202 static void open_cgroup_procs(void)
203 {
204 char *buf = NULL, *path = NULL, *p;
205 int cgroup_fd = 0;
206 char fdpath[PATH_MAX];
207
208 open_target_fd(&cgroup_fd, "cgroup", optarg);
209
210 if (read_all_alloc(cgroup_fd, &buf) < 1)
211 err(EXIT_FAILURE, _("failed to get cgroup path"));
212
213 p = strtok(buf, "\n");
214 if (p)
215 path = strrchr(p, ':');
216 if (!path)
217 err(EXIT_FAILURE, _("failed to get cgroup path"));
218 path++;
219
220 snprintf(fdpath, sizeof(fdpath), _PATH_SYS_CGROUP "/%s/cgroup.procs", path);
221
222 if ((cgroup_procs_fd = open(fdpath, O_WRONLY | O_APPEND)) < 0)
223 err(EXIT_FAILURE, _("failed to open cgroup.procs"));
224
225 free(buf);
226 }
227
228 static int is_cgroup2(void)
229 {
230 struct statfs fs_stat;
231 int rc;
232
233 rc = statfs(_PATH_SYS_CGROUP, &fs_stat);
234 if (rc)
235 err(EXIT_FAILURE, _("statfs %s failed"), _PATH_SYS_CGROUP);
236 return F_TYPE_EQUAL(fs_stat.f_type, STATFS_CGROUP2_MAGIC);
237 }
238
239 static void join_into_cgroup(void)
240 {
241 pid_t pid;
242 char buf[ sizeof(stringify_value(UINT32_MAX)) ];
243 int len;
244
245 pid = getpid();
246 len = snprintf(buf, sizeof(buf), "%zu", (size_t) pid);
247 if (write_all(cgroup_procs_fd, buf, len))
248 err(EXIT_FAILURE, _("write cgroup.procs failed"));
249 }
250
251 static int is_usable_namespace(pid_t target, const struct namespace_file *nsfile)
252 {
253 char path[PATH_MAX];
254 ino_t my_ino = 0;
255 int rc;
256
257 /* Check NS accessibility */
258 snprintf(path, sizeof(path), "/proc/%u/%s", getpid(), nsfile->name);
259 rc = get_ns_ino(path, &my_ino);
260 if (rc == -ENOENT)
261 return false; /* Unsupported NS */
262
263 /* It is not permitted to use setns(2) to reenter the caller's
264 * current user namespace; see setns(2) man page for more details.
265 */
266 if (nsfile->nstype & CLONE_NEWUSER) {
267 ino_t target_ino = 0;
268
269 snprintf(path, sizeof(path), "/proc/%u/%s", target, nsfile->name);
270 if (get_ns_ino(path, &target_ino) != 0)
271 err(EXIT_FAILURE, _("stat of %s failed"), path);
272
273 if (my_ino == target_ino)
274 return false;
275 }
276
277 return true; /* All pass */
278 }
279
280 static void continue_as_child(void)
281 {
282 pid_t child;
283 int status;
284 pid_t ret;
285
286 /* Clear any inherited settings */
287 signal(SIGCHLD, SIG_DFL);
288
289 child = fork();
290 if (child < 0)
291 err(EXIT_FAILURE, _("fork failed"));
292
293 /* Only the child returns */
294 if (child == 0)
295 return;
296
297 for (;;) {
298 ret = waitpid(child, &status, WUNTRACED);
299 if ((ret == child) && (WIFSTOPPED(status))) {
300 /* The child suspended so suspend us as well */
301 kill(getpid(), SIGSTOP);
302 kill(child, SIGCONT);
303 } else {
304 break;
305 }
306 }
307 /* Return the child's exit code if possible */
308 if (WIFEXITED(status)) {
309 exit(WEXITSTATUS(status));
310 } else if (WIFSIGNALED(status)) {
311 kill(getpid(), WTERMSIG(status));
312 }
313 exit(EXIT_FAILURE);
314 }
315
316 int main(int argc, char *argv[])
317 {
318 enum {
319 OPT_PRESERVE_CRED = CHAR_MAX + 1,
320 OPT_KEEPCAPS,
321 OPT_USER_PARENT,
322 };
323 static const struct option longopts[] = {
324 { "all", no_argument, NULL, 'a' },
325 { "help", no_argument, NULL, 'h' },
326 { "version", no_argument, NULL, 'V'},
327 { "target", required_argument, NULL, 't' },
328 { "mount", optional_argument, NULL, 'm' },
329 { "uts", optional_argument, NULL, 'u' },
330 { "ipc", optional_argument, NULL, 'i' },
331 { "net", optional_argument, NULL, 'n' },
332 { "pid", optional_argument, NULL, 'p' },
333 { "user", optional_argument, NULL, 'U' },
334 { "cgroup", optional_argument, NULL, 'C' },
335 { "time", optional_argument, NULL, 'T' },
336 { "setuid", required_argument, NULL, 'S' },
337 { "setgid", required_argument, NULL, 'G' },
338 { "root", optional_argument, NULL, 'r' },
339 { "wd", optional_argument, NULL, 'w' },
340 { "wdns", optional_argument, NULL, 'W' },
341 { "env", no_argument, NULL, 'e' },
342 { "no-fork", no_argument, NULL, 'F' },
343 { "join-cgroup", no_argument, NULL, 'c'},
344 { "preserve-credentials", no_argument, NULL, OPT_PRESERVE_CRED },
345 { "keep-caps", no_argument, NULL, OPT_KEEPCAPS },
346 { "user-parent", no_argument, NULL, OPT_USER_PARENT},
347 #ifdef HAVE_LIBSELINUX
348 { "follow-context", no_argument, NULL, 'Z' },
349 #endif
350 { NULL, 0, NULL, 0 }
351 };
352 static const ul_excl_t excl[] = { /* rows and cols in ASCII order */
353 { 'W', 'w' },
354 { 0 }
355 };
356 int excl_st[ARRAY_SIZE(excl)] = UL_EXCL_STATUS_INIT;
357
358 struct namespace_file *nsfile;
359 int c, pass, namespaces = 0, setgroups_nerrs = 0, preserve_cred = 0;
360 bool do_rd = false, do_wd = false, do_uid = false, force_uid = false,
361 do_gid = false, force_gid = false, do_env = false, do_all = false,
362 do_join_cgroup = false, do_user_parent = false;
363 int do_fork = -1; /* unknown yet */
364 char *wdns = NULL;
365 uid_t uid = 0;
366 gid_t gid = 0;
367 int keepcaps = 0;
368 struct ul_env_list *envls;
369 #ifdef HAVE_LIBSELINUX
370 bool selinux = 0;
371 #endif
372
373 setlocale(LC_ALL, "");
374 bindtextdomain(PACKAGE, LOCALEDIR);
375 textdomain(PACKAGE);
376 close_stdout_atexit();
377
378 while ((c =
379 getopt_long(argc, argv, "+ahVt:m::u::i::n::p::C::U::T::S:G:r::w::W::ecFZ",
380 longopts, NULL)) != -1) {
381
382 err_exclusive_options(c, longopts, excl, excl_st);
383
384 switch (c) {
385 case 'a':
386 do_all = true;
387 break;
388 case 't':
389 namespace_target_pid =
390 strtoul_or_err(optarg, _("failed to parse pid"));
391 break;
392 case 'm':
393 if (optarg)
394 open_namespace_fd(CLONE_NEWNS, optarg);
395 else
396 namespaces |= CLONE_NEWNS;
397 break;
398 case 'u':
399 if (optarg)
400 open_namespace_fd(CLONE_NEWUTS, optarg);
401 else
402 namespaces |= CLONE_NEWUTS;
403 break;
404 case 'i':
405 if (optarg)
406 open_namespace_fd(CLONE_NEWIPC, optarg);
407 else
408 namespaces |= CLONE_NEWIPC;
409 break;
410 case 'n':
411 if (optarg)
412 open_namespace_fd(CLONE_NEWNET, optarg);
413 else
414 namespaces |= CLONE_NEWNET;
415 break;
416 case 'p':
417 if (optarg)
418 open_namespace_fd(CLONE_NEWPID, optarg);
419 else
420 namespaces |= CLONE_NEWPID;
421 break;
422 case 'C':
423 if (optarg)
424 open_namespace_fd(CLONE_NEWCGROUP, optarg);
425 else
426 namespaces |= CLONE_NEWCGROUP;
427 break;
428 case 'U':
429 if (optarg)
430 open_namespace_fd(CLONE_NEWUSER, optarg);
431 else
432 namespaces |= CLONE_NEWUSER;
433 break;
434 case 'T':
435 if (optarg)
436 open_namespace_fd(CLONE_NEWTIME, optarg);
437 else
438 namespaces |= CLONE_NEWTIME;
439 break;
440 case 'S':
441 if (strcmp(optarg, "follow") == 0)
442 do_uid = true;
443 else
444 uid = strtoul_or_err(optarg, _("failed to parse uid"));
445 force_uid = true;
446 break;
447 case 'G':
448 if (strcmp(optarg, "follow") == 0)
449 do_gid = true;
450 else
451 gid = strtoul_or_err(optarg, _("failed to parse gid"));
452 force_gid = true;
453 break;
454 case 'F':
455 do_fork = 0;
456 break;
457 case 'c':
458 do_join_cgroup = true;
459 break;
460 case 'r':
461 if (optarg)
462 open_target_fd(&root_fd, "root", optarg);
463 else
464 do_rd = true;
465 break;
466 case 'w':
467 if (optarg)
468 open_target_fd(&wd_fd, "cwd", optarg);
469 else
470 do_wd = true;
471 break;
472 case 'W':
473 wdns = optarg;
474 break;
475 case 'e':
476 do_env = true;
477 break;
478 case OPT_PRESERVE_CRED:
479 preserve_cred = 1;
480 break;
481 case OPT_KEEPCAPS:
482 keepcaps = 1;
483 break;
484 case OPT_USER_PARENT:
485 do_user_parent = true;
486 break;
487 #ifdef HAVE_LIBSELINUX
488 case 'Z':
489 selinux = 1;
490 break;
491 #endif
492 case 'h':
493 usage();
494 case 'V':
495 print_version(EXIT_SUCCESS);
496 default:
497 errtryhelp(EXIT_FAILURE);
498 }
499 }
500
501 #ifdef HAVE_LIBSELINUX
502 if (selinux && is_selinux_enabled() > 0) {
503 char *scon = NULL;
504
505 if (!namespace_target_pid)
506 errx(EXIT_FAILURE, _("no target PID specified for --follow-context"));
507 if (getpidcon(namespace_target_pid, &scon) < 0)
508 errx(EXIT_FAILURE, _("failed to get %d SELinux context"),
509 (int) namespace_target_pid);
510 if (setexeccon(scon) < 0)
511 errx(EXIT_FAILURE, _("failed to set exec context to '%s'"), scon);
512 freecon(scon);
513 }
514 #endif
515
516 if (do_all) {
517 if (!namespace_target_pid)
518 errx(EXIT_FAILURE, _("no target PID specified for --all"));
519 for (nsfile = namespace_files; nsfile->nstype; nsfile++) {
520 if (nsfile->fd >= 0)
521 continue; /* namespace already specified */
522
523 if (!is_usable_namespace(namespace_target_pid, nsfile))
524 continue;
525
526 namespaces |= nsfile->nstype;
527 }
528 }
529
530 /*
531 * Open remaining namespace and directory descriptors.
532 */
533 for (nsfile = namespace_files; nsfile->nstype; nsfile++)
534 if (nsfile->nstype & namespaces)
535 open_namespace_fd(nsfile->nstype, NULL);
536 if (do_rd)
537 open_target_fd(&root_fd, "root", NULL);
538 if (do_wd)
539 open_target_fd(&wd_fd, "cwd", NULL);
540 if (do_env)
541 open_target_fd(&env_fd, "environ", NULL);
542 if (do_uid || do_gid)
543 open_target_fd(&uid_gid_fd, "", NULL);
544 if (do_join_cgroup) {
545 if (!is_cgroup2())
546 errx(EXIT_FAILURE, _("--join-cgroup is only supported in cgroup v2"));
547 open_cgroup_procs();
548 }
549
550 /*
551 * Get parent userns from any available ns.
552 */
553 if (do_user_parent)
554 set_parent_user_ns_fd();
555
556 /*
557 * Update namespaces variable to contain all requested namespaces
558 */
559 for (nsfile = namespace_files; nsfile->nstype; nsfile++) {
560 if (nsfile->fd < 0)
561 continue;
562 namespaces |= nsfile->nstype;
563 }
564
565 /* for user namespaces we always set UID and GID (default is 0)
566 * and clear root's groups if --preserve-credentials is no specified */
567 if ((namespaces & CLONE_NEWUSER) && !preserve_cred) {
568 force_uid = true, force_gid = true;
569
570 /* We call setgroups() before and after we enter user namespace,
571 * let's complain only if both fail */
572 if (setgroups(0, NULL) != 0)
573 setgroups_nerrs++;
574 }
575
576 /*
577 * Now that we know which namespaces we want to enter, enter
578 * them. Do this in two passes, not entering the user
579 * namespace on the first pass. So if we're deprivileging the
580 * container we'll enter the user namespace last and if we're
581 * privileging it then we enter the user namespace first
582 * (because the initial setns will fail).
583 */
584 for (pass = 0; pass < 2; pass ++) {
585 for (nsfile = namespace_files + 1 - pass; nsfile->nstype; nsfile++) {
586 if (nsfile->fd < 0)
587 continue;
588 if (nsfile->nstype == CLONE_NEWPID && do_fork == -1)
589 do_fork = 1;
590 if (setns(nsfile->fd, nsfile->nstype)) {
591 if (pass != 0)
592 err(EXIT_FAILURE,
593 _("reassociate to namespace '%s' failed"),
594 nsfile->name);
595 else
596 continue;
597 }
598
599 close(nsfile->fd);
600 nsfile->fd = -1;
601 }
602 }
603
604 /* Remember the current working directory if I'm not changing it */
605 if (root_fd >= 0 && wd_fd < 0 && wdns == NULL) {
606 wd_fd = open(".", O_RDONLY);
607 if (wd_fd < 0)
608 err(EXIT_FAILURE,
609 _("cannot open current working directory"));
610 }
611
612 /* Change the root directory */
613 if (root_fd >= 0) {
614 if (fchdir(root_fd) < 0)
615 err(EXIT_FAILURE,
616 _("change directory by root file descriptor failed"));
617
618 if (chroot(".") < 0)
619 err(EXIT_FAILURE, _("chroot failed"));
620 if (chdir("/"))
621 err(EXIT_FAILURE, _("cannot change directory to %s"), "/");
622
623 close(root_fd);
624 root_fd = -1;
625 }
626
627 /* working directory specified as in-namespace path */
628 if (wdns) {
629 wd_fd = open(wdns, O_RDONLY);
630 if (wd_fd < 0)
631 err(EXIT_FAILURE,
632 _("cannot open current working directory"));
633 }
634
635 /* Change the working directory */
636 if (wd_fd >= 0) {
637 if (fchdir(wd_fd) < 0)
638 err(EXIT_FAILURE,
639 _("change directory by working directory file descriptor failed"));
640
641 close(wd_fd);
642 wd_fd = -1;
643 }
644
645 /* Pass environment variables of the target process to the spawned process */
646 if (env_fd >= 0) {
647 if ((envls = env_from_fd(env_fd)) == NULL)
648 err(EXIT_FAILURE, _("failed to get environment variables"));
649 clearenv();
650 if (env_list_setenv(envls) < 0)
651 err(EXIT_FAILURE, _("failed to set environment variables"));
652 env_list_free(envls);
653 close(env_fd);
654 }
655
656 // Join into the target cgroup
657 if (cgroup_procs_fd >= 0)
658 join_into_cgroup();
659
660 if (uid_gid_fd >= 0) {
661 struct stat st;
662
663 if (fstat(uid_gid_fd, &st) > 0)
664 err(EXIT_FAILURE, _("can not get process stat"));
665
666 close(uid_gid_fd);
667 uid_gid_fd = -1;
668
669 if (do_uid)
670 uid = st.st_uid;
671 if (do_gid)
672 gid = st.st_gid;
673 }
674
675 if (do_fork == 1)
676 continue_as_child();
677
678 if (force_uid || force_gid) {
679 if (force_gid && setgroups(0, NULL) != 0 && setgroups_nerrs) /* drop supplementary groups */
680 err(EXIT_FAILURE, _("setgroups failed"));
681 if (force_gid && setgid(gid) < 0) /* change GID */
682 err(EXIT_FAILURE, _("setgid failed"));
683 if (force_uid && setuid(uid) < 0) /* change UID */
684 err(EXIT_FAILURE, _("setuid failed"));
685 }
686
687 if (keepcaps && (namespaces & CLONE_NEWUSER))
688 cap_permitted_to_ambient();
689
690 if (optind < argc) {
691 execvp(argv[optind], argv + optind);
692 errexec(argv[optind]);
693 }
694 exec_shell();
695 }