Document gotcha for --pidns
[untie.git] / untie.c
blob043fdf5d6c9453f38f5999ec8e52a1c1abf943b7
1 /*
2 * This program is free software; you can redistribute it and/or
3 * modify it under the terms of the GNU General Public License
4 * as published by the Free Software Foundation; either version 2
5 * of the License, or (at your option) any later version.
7 * This program is distributed in the hope that it will be useful,
8 * but WITHOUT ANY WARRANTY; without even the implied warranty of
9 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 * GNU General Public License for more details.
12 * You should have received a copy of the GNU General Public License
13 * along with this program; if not, write to the Free Software
14 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
16 * See the COPYING file for license information.
18 * Copyright (c) 2006, 2007 Guillaume Chazarain <guichaz@yahoo.fr>
21 #include <sys/time.h>
22 #include <sys/resource.h>
23 #include <sys/types.h>
24 #include <errno.h>
25 #include <sched.h>
26 #include <signal.h>
27 #include <stdio.h>
28 #include <stdlib.h>
29 #include <unistd.h>
30 #include <fcntl.h>
31 #include <string.h>
32 #include <grp.h>
33 #include <pwd.h>
34 #include <time.h>
37 * Copied from linux-2.6/include/linux/sched.h
38 * Compile on old libc, run on new kernel :-)
40 #define CLONE_NEWNS 0x00020000 /* New namespace */
41 #define CLONE_NEWUTS 0x04000000 /* New utsname */
42 #define CLONE_NEWIPC 0x08000000 /* New ipcs */
43 #define CLONE_NEWUSER 0x10000000 /* New user namespace */
44 #define CLONE_NEWPID 0x20000000 /* New pid namespace */
45 #define CLONE_NEWNET 0x40000000 /* New network namespace */
47 #include "cmdline.h"
50 * We find out the UID/GID infos before the chroot as we may need /etc/passwd.
51 * The results are stored in this struct.
53 struct user_info {
54 uid_t uid;
55 gid_t gid;
56 size_t nr_groups;
57 gid_t *groups;
61 * The temporary stack for the cloned process
63 #define STACK_SIZE (1 << 20)
64 static char stack[STACK_SIZE];
67 * Get the clone(2) flags from the command line arguments
69 static int get_flags(struct gengetopt_args_info *args_info)
71 int flags = 0;
72 unsigned int i;
74 if (args_info->mount_flag)
75 flags |= CLONE_NEWNS;
77 if (args_info->uname_flag)
78 flags |= CLONE_NEWUTS;
80 if (args_info->ipc_flag)
81 flags |= CLONE_NEWIPC;
83 if (args_info->userns_flag)
84 flags |= CLONE_NEWUSER;
86 if (args_info->pidns_flag)
87 flags |= CLONE_NEWPID;
89 if (args_info->netns_flag)
90 flags |= CLONE_NEWNET;
92 for (i = 0; i < args_info->mask_given; i++)
93 flags |= args_info->mask_arg[i];
95 return flags;
99 * Wrapper for malloc, exit instead of returning NULL
101 static void *xmalloc(int size)
103 void *res = malloc(size);
104 if (!res) {
105 perror("malloc");
106 exit(EXIT_FAILURE);
108 return res;
112 * Do a chroot() if requested. Do also a chdir() to prevent the simplest
113 * form a chroot escape.
115 static void perform_chroot(struct gengetopt_args_info *ggo)
117 if (ggo->chroot_given) {
118 if (chroot(ggo->chroot_arg) < 0) {
119 perror("chroot");
120 exit(EXIT_FAILURE);
123 if (chdir("/") < 0) {
124 perror("chdir(\"/\")");
125 exit(EXIT_FAILURE);
131 * Find the UID associated to a username
133 static uid_t name_to_uid(const char *name)
135 struct passwd *pass;
137 errno = 0;
138 pass = getpwnam(name);
140 if (!pass) {
141 if (errno)
142 perror("getpwnam");
143 else
144 fprintf(stderr, "%s: user not found\n", name);
145 exit(EXIT_FAILURE);
148 return pass->pw_uid;
152 * Find the GID associated to a group
154 static gid_t name_to_gid(const char *name)
156 struct group *grp;
158 errno = 0;
159 grp = getgrnam(name);
161 if (!grp) {
162 if (errno)
163 perror("getgrnam");
164 else
165 fprintf(stderr, "%s: group not found\n", name);
166 exit(EXIT_FAILURE);
169 return grp->gr_gid;
173 * Transform the command line args (ggo) into our struct (user)
175 static void get_user_infos(struct user_info *user,
176 struct gengetopt_args_info *ggo)
178 size_t nr_groups = ggo->gid_given + ggo->groupname_given;
180 if (ggo->uid_given && ggo->username_given) {
181 fputs("--uid and --username are mutually exclusive\n", stderr);
182 exit(EXIT_FAILURE);
185 if (ggo->uid_given)
186 user->uid = ggo->uid_arg;
187 else if (ggo->username_given)
188 user->uid = name_to_uid(ggo->username_arg);
189 else
190 user->uid = -1;
192 if (nr_groups)
193 user->groups = xmalloc(nr_groups * sizeof(gid_t));
194 else
195 user->groups = NULL;
196 user->nr_groups = 0;
198 if (ggo->gid_given) {
199 memcpy(user->groups, ggo->gid_arg,
200 ggo->gid_given * sizeof(gid_t));
201 user->nr_groups = ggo->gid_given;
204 if (ggo->groupname_given) {
205 int i;
206 for (i = 0; i < ggo->groupname_given; i++) {
207 gid_t gid = name_to_gid(ggo->groupname_arg[i]);
208 user->groups[user->nr_groups] = gid;
209 user->nr_groups++;
213 if (user->nr_groups)
214 user->gid = user->groups[0];
215 else {
216 user->gid = user->uid;
217 if (user->uid != -1) {
218 user->nr_groups = 1;
219 user->groups = &user->uid;
225 * Change the UID and GID if requested.
227 static void change_id(struct user_info *user)
229 if (user->nr_groups) {
230 if (setgroups(user->nr_groups, user->groups) < 0) {
231 perror("setgroups");
232 exit(EXIT_FAILURE);
236 if (user->gid != -1) {
237 if (setgid(user->gid) < 0) {
238 perror("setgid");
239 exit(EXIT_FAILURE);
243 if (user->uid != -1) {
244 if (setuid(user->uid) < 0) {
245 perror("setuid");
246 exit(EXIT_FAILURE);
252 * Run as a daemon
254 static void daemonize(void)
256 int fd;
258 switch (fork()) {
259 case -1:
260 perror("fork");
261 exit(EXIT_FAILURE);
262 case 0:
263 break;
264 default:
265 exit(EXIT_SUCCESS);
268 if (setsid() < 0)
269 perror("setsid");
271 if (chdir("/") < 0)
272 perror("chdir(\"/\")");
274 fd = open("/dev/null", O_RDWR, 0);
275 if (fd >= 0) {
276 if (dup2(fd, STDIN_FILENO) < 0)
277 perror("dup2(fd, STDIN_FILENO)");
278 if (dup2(fd, STDOUT_FILENO) < 0)
279 perror("dup2(fd, STDOUT_FILENO)");
280 if (dup2(fd, STDERR_FILENO) < 0)
281 perror("dup2(fd, STDERR_FILENO)");
282 if (fd > STDERR_FILENO)
283 close(fd);
284 } else
285 perror("open(\"/dev/null\")");
289 * Configure a (posssibly RT) scheduling policy
291 static void set_scheduler(struct gengetopt_args_info *ggo)
293 static struct {
294 const char *name;
295 int value;
296 } policies[] = {
297 /* *INDENT-OFF* */
298 {"fifo", SCHED_FIFO},
299 {"rr", SCHED_RR},
300 {"other", SCHED_OTHER},
301 {"normal", SCHED_OTHER}
302 /* *INDENT-ON* */
304 struct sched_param param;
306 if (ggo->sched_given) {
307 int policy;
308 int policy_found = 0;
309 int i;
311 for (i = 0; i < sizeof(policies) / sizeof(policies[0]); i++)
312 if (!strcasecmp(ggo->sched_arg, policies[i].name)) {
313 policy = policies[i].value;
314 policy_found = 1;
315 break;
318 if (!policy_found) {
319 char *end;
320 policy = strtol(ggo->sched_arg, &end, 0);
321 if (ggo->sched_arg[0] == '\0' || end[0] != '\0') {
322 fprintf(stderr,
323 "Unknown scheduling policy: %s\n",
324 ggo->sched_arg);
325 exit(EXIT_FAILURE);
329 if (ggo->schedprio_given)
330 param.sched_priority = ggo->schedprio_arg;
331 else {
332 param.sched_priority = sched_get_priority_min(policy);
333 if (param.sched_priority < 0) {
334 perror("sched_get_priority_min");
335 exit(EXIT_FAILURE);
339 if (sched_setscheduler(0, policy, &param) < 0) {
340 perror("sched_setscheduler");
341 exit(EXIT_FAILURE);
343 } else if (ggo->schedprio_given) {
344 param.sched_priority = ggo->schedprio_arg;
345 if (sched_setparam(0, &param) < 0) {
346 perror("sched_setparam");
347 exit(EXIT_FAILURE);
352 static pid_t child_pid;
354 static void kill_child()
356 if (child_pid > 0)
357 kill(child_pid, SIGKILL);
360 static void deadly_signal_handler(int sig)
362 kill_child();
366 * If the parent is killed, it must kill its child too
368 static void install_signals_handlers(void)
370 int signals[] = { SIGHUP, SIGINT, SIGQUIT, SIGTERM };
371 int i;
373 for (i = 0; i < sizeof(signals) / sizeof(signals[0]); i++)
374 signal(signals[i], deadly_signal_handler);
378 * Exit when a SIGCHLD signals that the child has exited
380 static void sig_chld_handler(int sig, siginfo_t * info, void *data)
382 int code;
384 kill_child();
386 code = info->si_status;
387 if (info->si_code & CLD_KILLED) {
388 raise(code);
389 code += 128;
392 exit(code);
396 * Configure the SIGCHLD handler to detect the child termination and propagate
397 * its exit status.
399 static void install_sigchld_handler(struct gengetopt_args_info *ggo)
401 struct sigaction act;
403 act.sa_sigaction = sig_chld_handler;
404 act.sa_flags = SA_SIGINFO | SA_NOCLDSTOP;
405 sigemptyset(&act.sa_mask);
407 if (sigaction(SIGCHLD, &act, NULL) < 0) {
408 perror("sigaction");
409 exit(EXIT_FAILURE);
414 * Check double parameters to be used as struct timespec
416 static void check_double_as_timespec(double seconds)
418 time_t as_integer = (time_t) seconds;
419 double delta = seconds - as_integer;
421 if (seconds <= 0.0 || as_integer < 0) {
422 fprintf(stderr, "Invalid (negative?) timeout: %lf\n", seconds);
423 exit(EXIT_FAILURE);
426 if (delta < 0.0 || delta >= 1.0) {
427 fprintf(stderr, "Invalid (overflow?) timeout: %lf\n", seconds);
428 exit(EXIT_FAILURE);
433 * Kill the child after the specified delay
435 static void wait_to_kill(double seconds, int sig)
437 struct timespec sleep_time;
439 sleep_time.tv_sec = (time_t) seconds;
440 sleep_time.tv_nsec = (long)((seconds - sleep_time.tv_sec) * 1e9);
442 if (nanosleep(&sleep_time, NULL) < 0) {
443 perror("nanosleep");
444 exit(EXIT_FAILURE);
447 if (kill(child_pid, sig) < 0) {
448 perror("kill");
449 exit(EXIT_FAILURE);
454 * Kill the child according to the two possible delays
456 static void watchdog(struct gengetopt_args_info *ggo)
458 double delay;
459 int sig;
461 if (!ggo->timeout_term_given && !ggo->timeout_kill_given)
462 return;
464 if (ggo->daemonize_flag) {
465 fputs("--timeout-XXXX is incompatible with --daemonize\n",
466 stderr);
467 exit(EXIT_FAILURE);
470 if (!ggo->timeout_term_given || (ggo->timeout_kill_given &&
471 ggo->timeout_kill_arg <
472 ggo->timeout_term_arg)) {
473 /* SIGKILL before SIGTERM? Why not ;-) */
474 sig = SIGKILL;
475 delay = ggo->timeout_kill_arg;
476 } else {
477 sig = SIGTERM;
478 delay = ggo->timeout_term_arg;
481 wait_to_kill(delay, sig);
483 if (ggo->timeout_term_given != ggo->timeout_kill_given)
484 return;
486 delay = ggo->timeout_kill_arg - ggo->timeout_term_arg;
487 if (delay < 0.0)
488 delay = -delay;
490 /* The other signal */
491 sig = SIGKILL + SIGTERM - sig;
492 wait_to_kill(delay, sig);
496 * The new process in its own namespace can now execute the command passed as
497 * argument or a shell
499 static int child_process(void *ggo_arg)
501 struct gengetopt_args_info *ggo = ggo_arg;
502 struct user_info user;
504 set_scheduler(ggo);
506 if (ggo->nice_given)
507 if (setpriority(PRIO_PROCESS, 0, ggo->nice_arg) < 0) {
508 perror("setpriority");
509 return EXIT_FAILURE;
512 get_user_infos(&user, ggo);
513 perform_chroot(ggo);
514 change_id(&user);
516 if (ggo->daemonize_flag)
517 daemonize();
519 if (ggo->alarm_given)
520 alarm(ggo->alarm_arg);
522 if (ggo->inputs_num) {
523 char **argv = xmalloc(sizeof(char *) * (ggo->inputs_num + 1));
524 memcpy(argv, ggo->inputs, ggo->inputs_num * sizeof(char *));
525 argv[ggo->inputs_num] = NULL;
526 execvp(argv[0], argv);
527 perror("execvp");
528 } else {
529 char *argv[] = { NULL, NULL };
530 char *shell = getenv("SHELL");
531 if (!shell)
532 shell = "/bin/sh";
533 argv[0] = shell;
534 execv(argv[0], argv);
535 perror("execv");
538 return EXIT_FAILURE;
541 int main(int argc, char *argv[])
543 struct gengetopt_args_info args_info;
544 int ret;
545 int flags;
546 int fork_needed = 0;
548 ret = cmdline_parser(argc, argv, &args_info);
549 if (ret)
550 return ret;
551 if (argc - args_info.inputs_num <= 1) {
552 fputs("No flag specified, exiting\n", stderr);
553 return EXIT_FAILURE;
556 if (args_info.alarm_given &&
557 (args_info.alarm_arg < 0 || args_info.alarm_arg > 65535)) {
558 fprintf(stderr,
559 "The alarm value must be between 0 and 65535 got: %d\n",
560 args_info.alarm_arg);
561 return EXIT_FAILURE;
564 if (args_info.timeout_term_given)
565 check_double_as_timespec(args_info.timeout_term_arg);
566 if (args_info.timeout_kill_given)
567 check_double_as_timespec(args_info.timeout_kill_arg);
569 flags = get_flags(&args_info);
570 fork_needed = flags != 0 || args_info.timeout_term_given ||
571 args_info.timeout_kill_given;
573 if (fork_needed) {
574 atexit(kill_child);
575 install_sigchld_handler(&args_info);
576 install_signals_handlers();
577 flags |= SIGCHLD;
578 child_pid = clone(child_process, &stack[STACK_SIZE - 1], flags,
579 &args_info);
580 if (child_pid < 0) {
581 perror("clone");
582 return EXIT_FAILURE;
585 watchdog(&args_info);
586 pause();
587 } else
588 ret = child_process(&args_info);
590 return ret;