| /* 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 |
| |
| System V style init. |
| |
| First program to run (as PID 1) when the system comes up, reading |
| /etc/inittab to determine actions. |
| */ |
| |
| #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 = getenv("CONSOLE"); |
| |
| if (!p) 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 reset_term(int fd) |
| { |
| struct termios terminal; |
| |
| tcgetattr(fd, &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 &= CRTSCTS|PARODD|PARENB|CSTOPB|CSIZE|CBAUDEX|CBAUD; |
| terminal.c_cflag |= CLOCAL|HUPCL|CREAD; |
| |
| //enable start/stop input and output control + map CR to NL on input |
| terminal.c_iflag = IXON|IXOFF|ICRNL; |
| |
| //Map NL to CR-NL on output |
| terminal.c_oflag = ONLCR|OPOST; |
| terminal.c_lflag = IEXTEN|ECHOKE|ECHOCTL|ECHOK|ECHOE|ECHO|ICANON|ISIG; |
| tcsetattr(fd, 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); |
| } |
| } |
| reset_term(0); |
| 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; |
| |
| sigatexit(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); |
| reset_term(0); |
| 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(); |
| reset_term(0); |
| |
| 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; |
| } |
| } |
| } |