When clone() fails, child_pid is -1. Avoid killing -1.
[untie.git] / untie.c
blobc53c582cb440eec31a71345ab98be824c5ccefe7
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 */
45 #include "cmdline.h"
48 * We find out the UID/GID infos before the chroot as we may need /etc/passwd.
49 * The results are stored in this struct.
51 struct user_info {
52 uid_t uid;
53 gid_t gid;
54 size_t nr_groups;
55 gid_t *groups;
59 * The temporary stack for the cloned process
61 #define STACK_SIZE (1 << 20)
62 static char stack[STACK_SIZE];
65 * Get the clone(2) flags from the command line arguments
67 static int get_flags(struct gengetopt_args_info *args_info)
69 int flags = 0;
70 unsigned int i;
72 if (args_info->mount_flag)
73 flags |= CLONE_NEWNS;
75 if (args_info->uname_flag)
76 flags |= CLONE_NEWUTS;
78 if (args_info->ipc_flag)
79 flags |= CLONE_NEWIPC;
81 if (args_info->userns_flag)
82 flags |= CLONE_NEWUSER;
84 for (i = 0; i < args_info->mask_given; i++)
85 flags |= args_info->mask_arg[i];
87 return flags;
91 * Wrapper for malloc, exit instead of returning NULL
93 static void *xmalloc(int size)
95 void *res = malloc(size);
96 if (!res) {
97 perror("malloc");
98 exit(EXIT_FAILURE);
100 return res;
104 * Do a chroot() if requested. Do also a chdir() to prevent the simplest
105 * form a chroot escape.
107 static void perform_chroot(struct gengetopt_args_info *ggo)
109 if (ggo->chroot_given) {
110 if (chroot(ggo->chroot_arg) < 0) {
111 perror("chroot");
112 exit(EXIT_FAILURE);
115 if (chdir("/") < 0) {
116 perror("chdir(\"/\")");
117 exit(EXIT_FAILURE);
123 * Find the UID associated to a username
125 static uid_t name_to_uid(const char *name)
127 struct passwd *pass;
129 errno = 0;
130 pass = getpwnam(name);
132 if (!pass) {
133 if (errno)
134 perror("getpwnam");
135 else
136 fprintf(stderr, "%s: user not found\n", name);
137 exit(EXIT_FAILURE);
140 return pass->pw_uid;
144 * Find the GID associated to a group
146 static gid_t name_to_gid(const char *name)
148 struct group *grp;
150 errno = 0;
151 grp = getgrnam(name);
153 if (!grp) {
154 if (errno)
155 perror("getgrnam");
156 else
157 fprintf(stderr, "%s: group not found\n", name);
158 exit(EXIT_FAILURE);
161 return grp->gr_gid;
165 * Transform the command line args (ggo) into our struct (user)
167 static void get_user_infos(struct user_info *user,
168 struct gengetopt_args_info *ggo)
170 size_t nr_groups = ggo->gid_given + ggo->groupname_given;
172 if (ggo->uid_given && ggo->username_given) {
173 fputs("--uid and --username are mutually exclusive\n", stderr);
174 exit(EXIT_FAILURE);
177 if (ggo->uid_given)
178 user->uid = ggo->uid_arg;
179 else if (ggo->username_given)
180 user->uid = name_to_uid(ggo->username_arg);
181 else
182 user->uid = -1;
184 if (nr_groups)
185 user->groups = xmalloc(nr_groups * sizeof(gid_t));
186 else
187 user->groups = NULL;
188 user->nr_groups = 0;
190 if (ggo->gid_given) {
191 memcpy(user->groups, ggo->gid_arg,
192 ggo->gid_given * sizeof(gid_t));
193 user->nr_groups = ggo->gid_given;
196 if (ggo->groupname_given) {
197 int i;
198 for (i = 0; i < ggo->groupname_given; i++) {
199 gid_t gid = name_to_gid(ggo->groupname_arg[i]);
200 user->groups[user->nr_groups] = gid;
201 user->nr_groups++;
205 if (user->nr_groups)
206 user->gid = user->groups[0];
207 else {
208 user->gid = user->uid;
209 if (user->uid != -1) {
210 user->nr_groups = 1;
211 user->groups = &user->uid;
217 * Change the UID and GID if requested.
219 static void change_id(struct user_info *user)
221 if (user->nr_groups) {
222 if (setgroups(user->nr_groups, user->groups) < 0) {
223 perror("setgroups");
224 exit(EXIT_FAILURE);
228 if (user->gid != -1) {
229 if (setgid(user->gid) < 0) {
230 perror("setgid");
231 exit(EXIT_FAILURE);
235 if (user->uid != -1) {
236 if (setuid(user->uid) < 0) {
237 perror("setuid");
238 exit(EXIT_FAILURE);
244 * Run as a daemon
246 static void daemonize(void)
248 int fd;
250 switch (fork()) {
251 case -1:
252 perror("fork");
253 exit(EXIT_FAILURE);
254 case 0:
255 break;
256 default:
257 exit(EXIT_SUCCESS);
260 if (setsid() < 0)
261 perror("setsid");
263 if (chdir("/") < 0)
264 perror("chdir(\"/\")");
266 fd = open("/dev/null", O_RDWR, 0);
267 if (fd >= 0) {
268 if (dup2(fd, STDIN_FILENO) < 0)
269 perror("dup2(fd, STDIN_FILENO)");
270 if (dup2(fd, STDOUT_FILENO) < 0)
271 perror("dup2(fd, STDOUT_FILENO)");
272 if (dup2(fd, STDERR_FILENO) < 0)
273 perror("dup2(fd, STDERR_FILENO)");
274 if (fd > STDERR_FILENO)
275 close(fd);
276 } else
277 perror("open(\"/dev/null\")");
281 * Configure a (posssibly RT) scheduling policy
283 static void set_scheduler(struct gengetopt_args_info *ggo)
285 static struct {
286 const char *name;
287 int value;
288 } policies[] = {
289 /* *INDENT-OFF* */
290 {"fifo", SCHED_FIFO},
291 {"rr", SCHED_RR},
292 {"other", SCHED_OTHER},
293 {"normal", SCHED_OTHER}
294 /* *INDENT-ON* */
296 struct sched_param param;
298 if (ggo->sched_given) {
299 int policy;
300 int policy_found = 0;
301 int i;
303 for (i = 0; i < sizeof(policies) / sizeof(policies[0]); i++)
304 if (!strcasecmp(ggo->sched_arg, policies[i].name)) {
305 policy = policies[i].value;
306 policy_found = 1;
307 break;
310 if (!policy_found) {
311 char *end;
312 policy = strtol(ggo->sched_arg, &end, 0);
313 if (ggo->sched_arg[0] == '\0' || end[0] != '\0') {
314 fprintf(stderr,
315 "Unknown scheduling policy: %s\n",
316 ggo->sched_arg);
317 exit(EXIT_FAILURE);
321 if (ggo->schedprio_given)
322 param.sched_priority = ggo->schedprio_arg;
323 else {
324 param.sched_priority = sched_get_priority_min(policy);
325 if (param.sched_priority < 0) {
326 perror("sched_get_priority_min");
327 exit(EXIT_FAILURE);
331 if (sched_setscheduler(0, policy, &param) < 0) {
332 perror("sched_setscheduler");
333 exit(EXIT_FAILURE);
335 } else if (ggo->schedprio_given) {
336 param.sched_priority = ggo->schedprio_arg;
337 if (sched_setparam(0, &param) < 0) {
338 perror("sched_setparam");
339 exit(EXIT_FAILURE);
344 static pid_t child_pid;
346 static void kill_child()
348 if (child_pid > 0)
349 kill(child_pid, SIGKILL);
352 static void deadly_signal_handler(int sig)
354 kill_child();
358 * If the parent is killed, it must kill its child too
360 static void install_signals_handlers(void)
362 int signals[] = { SIGHUP, SIGINT, SIGQUIT, SIGTERM };
363 int i;
365 for (i = 0; i < sizeof(signals) / sizeof(signals[0]); i++)
366 signal(signals[i], deadly_signal_handler);
370 * Exit when a SIGCHLD signals that the child has exited
372 static void sig_chld_handler(int sig, siginfo_t * info, void *data)
374 int code;
376 kill_child();
378 code = info->si_status;
379 if (info->si_code & CLD_KILLED) {
380 raise(code);
381 code += 128;
384 exit(code);
388 * Configure the SIGCHLD handler to detect the child termination and propagate
389 * its exit status.
391 static void install_sigchld_handler(struct gengetopt_args_info *ggo)
393 struct sigaction act;
395 act.sa_sigaction = sig_chld_handler;
396 act.sa_flags = SA_SIGINFO | SA_NOCLDSTOP;
397 sigemptyset(&act.sa_mask);
399 if (sigaction(SIGCHLD, &act, NULL) < 0) {
400 perror("sigaction");
401 exit(EXIT_FAILURE);
406 * Check double parameters to be used as struct timespec
408 static void check_double_as_timespec(double seconds)
410 time_t as_integer = (time_t) seconds;
411 double delta = seconds - as_integer;
413 if (seconds <= 0.0 || as_integer < 0) {
414 fprintf(stderr, "Invalid (negative?) timeout: %lf\n", seconds);
415 exit(EXIT_FAILURE);
418 if (delta < 0.0 || delta >= 1.0) {
419 fprintf(stderr, "Invalid (overflow?) timeout: %lf\n", seconds);
420 exit(EXIT_FAILURE);
425 * Kill the child after the specified delay
427 static void wait_to_kill(double seconds, int sig)
429 struct timespec sleep_time;
431 sleep_time.tv_sec = (time_t) seconds;
432 sleep_time.tv_nsec = (long)((seconds - sleep_time.tv_sec) * 1e9);
434 if (nanosleep(&sleep_time, NULL) < 0) {
435 perror("nanosleep");
436 exit(EXIT_FAILURE);
439 if (kill(child_pid, sig) < 0) {
440 perror("kill");
441 exit(EXIT_FAILURE);
446 * Kill the child according to the two possible delays
448 static void watchdog(struct gengetopt_args_info *ggo)
450 double delay;
451 int sig;
453 if (!ggo->timeout_term_given && !ggo->timeout_kill_given)
454 return;
456 if (ggo->daemonize_flag) {
457 fputs("--timeout-XXXX is incompatible with --daemonize\n",
458 stderr);
459 exit(EXIT_FAILURE);
462 if (!ggo->timeout_term_given || (ggo->timeout_kill_given &&
463 ggo->timeout_kill_arg <
464 ggo->timeout_term_arg)) {
465 /* SIGKILL before SIGTERM? Why not ;-) */
466 sig = SIGKILL;
467 delay = ggo->timeout_kill_arg;
468 } else {
469 sig = SIGTERM;
470 delay = ggo->timeout_term_arg;
473 wait_to_kill(delay, sig);
475 if (ggo->timeout_term_given != ggo->timeout_kill_given)
476 return;
478 delay = ggo->timeout_kill_arg - ggo->timeout_term_arg;
479 if (delay < 0.0)
480 delay = -delay;
482 /* The other signal */
483 sig = SIGKILL + SIGTERM - sig;
484 wait_to_kill(delay, sig);
488 * The new process in its own namespace can now execute the command passed as
489 * argument or a shell
491 static int child_process(void *ggo_arg)
493 struct gengetopt_args_info *ggo = ggo_arg;
494 struct user_info user;
496 set_scheduler(ggo);
498 if (ggo->nice_given)
499 if (setpriority(PRIO_PROCESS, 0, ggo->nice_arg) < 0) {
500 perror("setpriority");
501 return EXIT_FAILURE;
504 get_user_infos(&user, ggo);
505 perform_chroot(ggo);
506 change_id(&user);
508 if (ggo->daemonize_flag)
509 daemonize();
511 if (ggo->alarm_given)
512 alarm(ggo->alarm_arg);
514 if (ggo->inputs_num) {
515 char **argv = xmalloc(sizeof(char *) * (ggo->inputs_num + 1));
516 memcpy(argv, ggo->inputs, ggo->inputs_num * sizeof(char *));
517 argv[ggo->inputs_num] = NULL;
518 execvp(argv[0], argv);
519 perror("execvp");
520 } else {
521 char *argv[] = { NULL, NULL };
522 char *shell = getenv("SHELL");
523 if (!shell)
524 shell = "/bin/sh";
525 argv[0] = shell;
526 execv(argv[0], argv);
527 perror("execv");
530 return EXIT_FAILURE;
533 int main(int argc, char *argv[])
535 struct gengetopt_args_info args_info;
536 int ret;
537 int flags;
538 int fork_needed = 0;
540 ret = cmdline_parser(argc, argv, &args_info);
541 if (ret)
542 return ret;
543 if (argc - args_info.inputs_num <= 1) {
544 fputs("No flag specified, exiting\n", stderr);
545 return EXIT_FAILURE;
548 if (args_info.alarm_given &&
549 (args_info.alarm_arg < 0 || args_info.alarm_arg > 65535)) {
550 fprintf(stderr,
551 "The alarm value must be between 0 and 65535 got: %d\n",
552 args_info.alarm_arg);
553 return EXIT_FAILURE;
556 if (args_info.timeout_term_given)
557 check_double_as_timespec(args_info.timeout_term_arg);
558 if (args_info.timeout_kill_given)
559 check_double_as_timespec(args_info.timeout_kill_arg);
561 flags = get_flags(&args_info);
562 fork_needed = flags != 0 || args_info.timeout_term_given ||
563 args_info.timeout_kill_given;
565 if (fork_needed) {
566 atexit(kill_child);
567 install_sigchld_handler(&args_info);
568 install_signals_handlers();
569 flags |= SIGCHLD;
570 child_pid = clone(child_process, &stack[STACK_SIZE - 1], flags,
571 &args_info);
572 if (child_pid < 0) {
573 perror("clone");
574 return EXIT_FAILURE;
577 watchdog(&args_info);
578 pause();
579 } else
580 ret = child_process(&args_info);
582 return ret;