/* init.c - init program. * * Copyright 2012 Harvind Singh <harvindsingh1981@gmail.com> * Copyright 2013 Kyungwan Han <asura321@gmail.com> * * No Standard USE_INIT(NEWTOY(init, "", TOYFLAG_SBIN)) config INIT bool "init" default n help usage: init init the system. */ #include "toys.h" #include <sys/reboot.h> struct action_list_seed { struct action_list_seed *next; pid_t pid; uint8_t action; char *terminal_name; char *command; } *action_list_pointer = NULL; int caught_signal; //INITTAB action defination #define SYSINIT 0x01 #define WAIT 0x02 #define ONCE 0x04 #define RESPAWN 0x08 #define ASKFIRST 0x10 #define CTRLALTDEL 0x20 #define SHUTDOWN 0x40 #define RESTART 0x80 static void initialize_console(void) { int fd; char *p = (p = getenv("CONSOLE")) ? p : getenv("console"); if (!p) { fd = open("/dev/null", O_RDWR); if (fd >= 0) { while (fd < 2) fd = dup(fd); while (fd > 2) close(fd--); } } else { fd = open(p, O_RDWR | O_NONBLOCK | O_NOCTTY); if (fd < 0) printf("Unable to open console %s\n",p); else { dup2(fd,0); dup2(fd,1); dup2(fd,2); } } if (!getenv("TERM")) putenv("TERM=linux"); } static void set_sane_term(void) { struct termios terminal; tcgetattr(0, &terminal); terminal.c_cc[VINTR] = 3; //ctrl-c terminal.c_cc[VQUIT] = 28; /*ctrl-\*/ terminal.c_cc[VERASE] = 127; //ctrl-? terminal.c_cc[VKILL] = 21; //ctrl-u terminal.c_cc[VEOF] = 4; //ctrl-d terminal.c_cc[VSTART] = 17; //ctrl-q terminal.c_cc[VSTOP] = 19; //ctrl-s terminal.c_cc[VSUSP] = 26; //ctrl-z terminal.c_line = 0; terminal.c_cflag = terminal.c_cflag&(CRTSCTS|PARODD|PARENB|CSTOPB|CSIZE|CBAUDEX|CBAUD); terminal.c_cflag = terminal.c_cflag|(CLOCAL|HUPCL|CREAD); terminal.c_iflag = IXON|IXOFF|ICRNL;//enable start/stop input and output control + map CR to NL on input terminal.c_oflag = ONLCR|OPOST;//Map NL to CR-NL on output terminal.c_lflag = IEXTEN|ECHOKE|ECHOCTL|ECHOK|ECHOE|ECHO|ICANON|ISIG; tcsetattr(0, TCSANOW, &terminal); } static void add_new_action(uint8_t action,char *command,char *term) { struct action_list_seed *x,**y; y = &action_list_pointer; x = *y; while (x) { if (!(strcmp(x->command, command)) && !(strcmp(x->terminal_name, term))) { *y = x->next; //remove from the list while(*y) y = &(*y)->next; //traverse through list till end x->next = NULL; break; } y = &(x)->next; x = *y; } //create a new node if (!x) { x = xzalloc(sizeof(*x)); x->command = xstrdup(command); x->terminal_name = xstrdup(term); } x->action = action; *y = x; } static void inittab_parsing(void) { int i, fd, line_number = 0, token_count = 0; char *p, *q, *extracted_token, *tty_name = NULL, *command = NULL, *tmp; uint8_t action = 0; char *act_name = "sysinit\0wait\0once\0respawn\0askfirst\0ctrlaltdel\0" "shutdown\0restart\0"; fd = open("/etc/inittab", O_RDONLY); if (fd < 0) { error_msg("Unable to open /etc/inittab. Using Default inittab"); add_new_action(SYSINIT, "/etc/init.d/rcS", ""); add_new_action(RESPAWN, "/sbin/getty -n -l /bin/sh -L 115200 tty1 vt100", ""); } else { while((q = p = get_line(fd))) { //read single line from /etc/inittab char *x; if ((x = strchr(p, '#'))) *x = '\0'; line_number++; token_count = 0; action = 0; while ((extracted_token = strsep(&p,":"))) { token_count++; switch (token_count) { case 1: if (*extracted_token) { if (!strncmp(extracted_token, "/dev/", 5)) tty_name = xmprintf("%s",extracted_token); else tty_name = xmprintf("/dev/%s",extracted_token); } else tty_name = xstrdup(""); break; case 2: break; case 3: for (tmp = act_name, i = 0; *tmp; i++, tmp += strlen(tmp) +1) { if (!strcmp(tmp, extracted_token)) { action = 1 << i; break; } } if (!*tmp) error_msg("Invalid action at line number %d ---- ignoring",line_number); break; case 4: command = xstrdup(extracted_token); break; default: error_msg("Bad inittab entry at line %d", line_number); break; } } //while token if (q) free(q); if (token_count != 4) continue; if (action) add_new_action(action, command, tty_name); free(tty_name); free(command); } //while line close(fd); } } static void run_command(char *command) { char *final_command[128]; int hyphen = (command[0]=='-'); command = command + hyphen; if (!strpbrk(command, "?<>'\";[]{}\\|=()*&^$!`~")) { char *next_command; char *extracted_command; int x = 0; next_command = strncpy(toybuf, command - hyphen, sizeof(toybuf)); next_command[sizeof(toybuf) - 1] = toybuf[sizeof(toybuf) - 1 ] = '\0'; command = next_command + hyphen; while ((extracted_command = strsep(&next_command," \t"))) { if (*extracted_command) { final_command[x] = extracted_command; x++; } } final_command[x] = NULL; } else { snprintf(toybuf, sizeof(toybuf), "exec %s", command); command = "-/bin/sh"+1; final_command[0] = ("-/bin/sh"+!hyphen); final_command[1] = "-c"; final_command[2] = toybuf; final_command[3] = NULL; } if (hyphen) ioctl(0, TIOCSCTTY, 0); execvp(command, final_command); error_msg("unable to run %s",command); } //runs all same type of actions static pid_t final_run(struct action_list_seed *x) { pid_t pid; int fd; sigset_t signal_set; sigfillset(&signal_set); sigprocmask(SIG_BLOCK, &signal_set, NULL); if (x->action & ASKFIRST) pid = fork(); else pid = vfork(); if (pid > 0) { //parent process or error //unblock the signals sigfillset(&signal_set); sigprocmask(SIG_UNBLOCK, &signal_set, NULL); return pid; } else if (pid < 0) { perror_msg("fork fail"); sleep(1); return 0; } //new born child process sigset_t signal_set_c; sigfillset(&signal_set_c); sigprocmask(SIG_UNBLOCK, &signal_set_c, NULL); setsid(); //new session if (x->terminal_name[0]) { close(0); fd = open(x->terminal_name, (O_RDWR|O_NONBLOCK),0600); if (fd != 0) { error_msg("Unable to open %s,%s\n", x->terminal_name, strerror(errno)); _exit(EXIT_FAILURE); } else { dup2(0, 1); dup2(0, 2); } } set_sane_term(); run_command(x->command); _exit(-1); } static struct action_list_seed* mark_as_terminated_process(pid_t pid) { struct action_list_seed *x; if (pid > 0) { for (x = action_list_pointer; x; x = x->next) { if (x->pid == pid) { x->pid = 0; return x; } } } return NULL; } static void waitforpid(pid_t pid) { if (pid <= 0) return; for(;;) { pid_t y = wait(NULL); mark_as_terminated_process(y); if (kill(y, 0)) break; } } static void run_action_from_list(int action) { pid_t pid; struct action_list_seed *x = action_list_pointer; for (; x; x = x->next) { if (!(x->action & action)) continue; if (x->action & (SHUTDOWN|ONCE|SYSINIT|CTRLALTDEL|WAIT)) { pid = final_run(x); if (!pid) return; if (x->action & (SHUTDOWN|SYSINIT|CTRLALTDEL|WAIT)) waitforpid(pid); } if (x->action & (ASKFIRST|RESPAWN)) if (!(x->pid)) x->pid = final_run(x); } } static void set_default(void) { sigset_t signal_set_c; signal(SIGUSR1,SIG_DFL); signal(SIGUSR2,SIG_DFL); signal(SIGTERM,SIG_DFL); signal(SIGQUIT,SIG_DFL); signal(SIGINT,SIG_DFL); signal(SIGHUP,SIG_DFL); signal(SIGTSTP,SIG_DFL); signal(SIGSTOP,SIG_DFL); sigfillset(&signal_set_c); sigprocmask(SIG_UNBLOCK,&signal_set_c, NULL); run_action_from_list(SHUTDOWN); error_msg("The system is going down NOW!"); kill(-1, SIGTERM); error_msg("Sent SIGTERM to all processes"); sync(); sleep(1); kill(-1,SIGKILL); sync(); } static void halt_poweroff_reboot_handler(int sig_no) { unsigned int reboot_magic_no = 0; pid_t pid; set_default(); switch (sig_no) { case SIGUSR1: error_msg("Requesting system halt"); reboot_magic_no=RB_HALT_SYSTEM; break; case SIGUSR2: error_msg("Requesting system poweroff"); reboot_magic_no=RB_POWER_OFF; break; case SIGTERM: error_msg("Requesting system reboot"); reboot_magic_no=RB_AUTOBOOT; break; default: break; } sleep(1); pid = vfork(); if (pid == 0) { reboot(reboot_magic_no); _exit(EXIT_SUCCESS); } while(1) sleep(1); } static void restart_init_handler(int sig_no) { struct action_list_seed *x; pid_t pid; int fd; for (x = action_list_pointer; x; x = x->next) { if (!(x->action & RESTART)) continue; set_default(); if (x->terminal_name[0]) { close(0); fd = open(x->terminal_name, (O_RDWR|O_NONBLOCK),0600); if (fd != 0) { error_msg("Unable to open %s,%s\n", x->terminal_name, strerror(errno)); sleep(1); pid = vfork(); if (pid == 0) { reboot(RB_HALT_SYSTEM); _exit(EXIT_SUCCESS); } while(1) sleep(1); } else { dup2(0, 1); dup2(0, 2); set_sane_term(); run_command(x->command); } } } } static void catch_signal(int sig_no) { caught_signal = sig_no; error_msg("signal seen"); } static void pause_handler(int sig_no) { int signal_backup,errno_backup; pid_t pid; errno_backup = errno; signal_backup = caught_signal; signal(SIGCONT, catch_signal); while(1) { if (caught_signal == SIGCONT) break; do pid = waitpid(-1,NULL,WNOHANG); while((pid==-1) && (errno=EINTR)); mark_as_terminated_process(pid); sleep(1); } signal(SIGCONT, SIG_DFL); errno = errno_backup; caught_signal = signal_backup; } static int check_if_pending_signals(void) { int signal_caught = 0; while(1) { int sig = caught_signal; if (!sig) return signal_caught; caught_signal = 0; signal_caught = 1; if (sig == SIGINT) run_action_from_list(CTRLALTDEL); } } void init_main(void) { struct sigaction sig_act; if (getpid() != 1) error_exit("Already running"); printf("Started init\n"); initialize_console(); set_sane_term(); if (chdir("/")) perror_exit("Can't cd to /"); setsid(); putenv("HOME=/"); putenv("PATH=/sbin:/usr/sbin:/bin:/usr/bin"); putenv("SHELL=/bin/sh"); putenv("USER=root"); inittab_parsing(); signal(SIGUSR1, halt_poweroff_reboot_handler);//halt signal(SIGUSR2, halt_poweroff_reboot_handler);//poweroff signal(SIGTERM, halt_poweroff_reboot_handler);//reboot signal(SIGQUIT, restart_init_handler);//restart init memset(&sig_act, 0, sizeof(sig_act)); sigfillset(&sig_act.sa_mask); sigdelset(&sig_act.sa_mask, SIGCONT); sig_act.sa_handler = pause_handler; sigaction(SIGTSTP, &sig_act, NULL); memset(&sig_act, 0, sizeof(sig_act)); sig_act.sa_handler = catch_signal; sigaction(SIGINT, &sig_act, NULL); sigaction(SIGHUP, &sig_act, NULL); run_action_from_list(SYSINIT); check_if_pending_signals(); run_action_from_list(WAIT); check_if_pending_signals(); run_action_from_list(ONCE); while (1) { int suspected_WNOHANG = check_if_pending_signals(); run_action_from_list(RESPAWN | ASKFIRST); suspected_WNOHANG = suspected_WNOHANG|check_if_pending_signals(); sleep(1);//let cpu breath suspected_WNOHANG = suspected_WNOHANG|check_if_pending_signals(); if (suspected_WNOHANG) suspected_WNOHANG=WNOHANG; while(1) { pid_t pid = waitpid(-1, NULL, suspected_WNOHANG); if (pid <= 0) break; mark_as_terminated_process(pid); suspected_WNOHANG = WNOHANG; } } }