view toys/pending/init.c @ 1187:18cc63376e66 draft

init: don't use VT_OPENQRY. The original codepath checks if there is a VT available, and if there isn't sets TERM to vt102 (unless TERM is set to something other than "linux"). Otherwise, TERM is set to "linux" if it is not already set. However, we can rely on getty/... to set TERM if "linux" is not suitable. This has the benefit of dropping a slightly messy section.
author Isaac Dunham <ibid.ag@gmail.com>
date Tue, 28 Jan 2014 17:46:14 -0600
parents 2a4f1dc494d0
children 6ca31490f581
line wrap: on
line source

/* 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;
    }
  }
}