/*
 * ngetty.c - daemon for virtual console terminals
 *
 * Copyright 2007 Andre Oliveira
 * Copyright 2007,2008 Nikola Vladov
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 */

/*
 * Synopsis: ngetty tty...
 *
 * ngetty open()s the virtual console terminals specified as arguments and
 * poll()s them for user input.  Once input is available on one of the
 * terminals, it fork()s and exec()s the ngetty-helper programs with that 
 * terminal set up as the controlling terminal.  ngetty-helper make 
 * utmp/wtmp records, print /etc/issue, ask for user name and then 
 * exec()s login(1) program.  When a ngetty session terminates,
 * the sig_handler() restarts the corresponding terminal.
 */

#include <unistd.h>
#include <fcntl.h>
#include <poll.h>
#include <alloca.h>
#include <errno.h>
#include <sys/wait.h>
#include "lib.h"
#include "sig_action.h"

#ifdef NEED_HELPER_OPTION
static char *helper;
#else
#define helper NGETTY_HELPER
#endif

struct tty_name {
  char *tty;	/* original ttyname */
  char tmp[23]; /* expanded ttyname; /dev/ttyX or /dev/vc/X */
  char st;
};
typedef struct tty_name tty_name_;
typedef struct pollfd pollfd_;

static tty_name_ *tty;

/* Number of managed terminals (argc - 1). */
static int npfd;

/*
 * Poll data for the terminals.
 * When polling for input on a terminal, the fd field stores its
 * file descriptor, as customary.  After fork()ing, though, the parent 
 * process closes the terminal and stores the child's _negative_ pid 
 * in the fd field, so that poll() ignores that entry and 
 * main() knows every child's pid.
 */
static pollfd_ *pfd;

static void nano_sleep(int n) {		/* sleep n.5 secs */
  struct timespec ts = { n, 500111222 };
  nanosleep(&ts, 0);
}

static void max_copy(char *out,const char *in) {
  int len;
  for (len=0; len<10; len++) { if (!(out[len]=in[len])) break; }
  out[len] = 0;
}

/*
 * Exec NGETTY_HELPER.
 */
static void exechelper(int i, char *login) {
  pollfd_ *xx = pfd + i;
  int pid;
  while ((pid=fork()) < 0)
    nano_sleep(0);

  if (pid==0) {
    tty_name_ *tt = tty + i;
    char *a1 = (tt->tmp[0]) ? tt->tmp : tt->tty;
    char *arg[4] = { helper, a1, login, 0 };

#ifndef NGETTY_SELFPIPE
    if (xx->fd)
#endif
      dup2(xx->fd, 0);
    i = npfd + 8;
    while (--i) close(i);

    execve(*arg, arg, environ);
    nano_sleep(5);
    _exit(127);
  }
  close(xx->fd);
  xx->fd = -pid;
}

/*
 * Open virtual console terminal i.
 *
 * In case of failure, most likely because the device node does not exist,
 * poll() will simply ignore this entry and users get a blank screen and
 * locked keyboard on this console.  That's enough error reporting. ;-)
 * No need to add code to check all syscalls' return values.
 *
 * Linux does not implement revoke(); the *BSD do.
 */
void opentty(int k) {
  tty_name_ *tt = tty + k;
  char *path = tt->tty, *m = tt->tmp, st;
  int fd;

  if (*path != '/') {
    max_copy(m, "/dev/vc/");
    if ('0'<=*path && *path<='9') {
      max_copy(m+8, path);
      if (chown(m,0,0))
	{ m[5]='t'; m[6]='t'; m[7]='y'; }
    } else {
      if (path[0]==0) { fd = -1; goto do_it; }
      max_copy(m+5, path);
    }
    path = m;
  } else
    m[0] = 0;

  chown(path, 0, 0);
  chmod(path, 0600);
#ifdef HAVE_REVOKE
  revoke(path);
#endif

  fd = open(path, O_RDWR | O_NOCTTY);
  if (fd > npfd + 4) _exit(100);
 do_it:
  pfd[k].fd = fd;
  if (fd < 0) st = 0;
  else {
    st = tt->st ^ 1;
    if (st) exechelper(k,0);
  }
  tt->st = st;
}

#ifndef NGETTY_SELFPIPE
static int volatile poll_timeout;
#endif
/*
 * SIG handler.
 */
static void sig_handler(int sig) {
  if (sig == SIGTERM) {
    int k;
  again:
    for (k = 0; k < npfd; k++) {
      int pid = -pfd[k].fd;
      if (pid > 1) kill(pid, sig);
    }
    nano_sleep(0);
    if (sig == SIGKILL) _exit(0);
    sig = SIGKILL;
    goto again;
  }
#ifdef NGETTY_SELFPIPE
  write(1,"",1);
#else
  poll_timeout = 1;
#endif
}

static void set_handler() {
  struct sigaction sa;
  sigset_t *mask = &sa.sa_mask;
  sigemptyset(mask);
  sa.sa_flags =  SA_RESTART | SA_NOCLDSTOP;
  sa.sa_handler = sig_handler;
  sigaction(SIGTERM, &sa, 0);
  sigaction(SIGCHLD, &sa, 0);
}

#ifdef NGETTY_SORT
#include "sortpfd.h"
#define SORT_x_PFD k = sortpfd()
#define KK_selfpipe k+1
#define KK k
#else 
#define SORT_x_PFD
#define KK_selfpipe argc
#define KK npfd
#endif

#ifdef NGETTY_SELFPIPE
#define DO_x_POLL poll(pfd-1, KK_selfpipe, -1)
#else
#define DO_x_POLL poll(pfd, KK, poll_timeout); poll_timeout = 512*1024
#endif

/*
 * Synopsis: ngetty tty...
 *
 * Since each terminal's struct pollfd only takes up 8 bytes, just 
 * alloca()te the array on the stack, instead of using the bloated malloc().
 */
int main(int argc, char **argv) {
  int i,k;
#ifdef NGETTY_SELFPIPE
  int pi[6];
#endif

#ifdef NEED_HELPER_OPTION
  if ((helper=argv[1]) && helper[0] == '-' && helper[1] == 'H') {
    argv++; argc--; helper += 2;
  } else helper = NGETTY_HELPER;
#endif

  npfd = argc - 1;
  tty = alloca(npfd * sizeof(tty_name_)); if (tty==0) _exit(111);
  pfd = alloca(argc * sizeof(pollfd_));   if (pfd==0) _exit(111);

#ifdef NGETTY_SELFPIPE
  pfd->fd = 0;
  pfd->events = POLLIN;
  pfd++;
#endif

  for (k = 0; k < npfd; k++) {
    tty[k].tty = *++argv;
    tty[k].st = 0;
    pfd[k].fd = -1;
    pfd[k].events = POLLIN;
  }
  for (k = 0; k < 127; k++) close(k);

#ifdef NGETTY_SELFPIPE
  if (pipe(pi) || pi[0] != 0 || pi[1] != 1) _exit(111);
  for (k=0; k<2; k++)
    fcntl(k, F_SETFL, fcntl(k,F_GETFL,0) | O_NONBLOCK);
#endif 

  set_handler();

 again:
  while ((i = waitpid(-1, NULL, WNOHANG)) > 1)
    for (k = 0; k < npfd; k++)
      if (pfd[k].fd == -i)
	{ pfd[k].fd = -1; break; }

  for (k = 0; k < npfd; k++)
    if (pfd[k].fd == -1) opentty(k);

  SORT_x_PFD;
  i = DO_x_POLL;

  if (i==-1) {
    if (errno != EINTR)
      sig_handler(SIGTERM);	/* poll failed, what to do? */
  } else {
    for (i = 0; i < KK; i++)
      if (pfd[i].revents)
	exechelper(i,"login");
  }

#ifdef NGETTY_SELFPIPE
  read(0, pi, sizeof pi);
#endif
  goto again;
  return 1;
}
