changeset 1244:40ed517e0cda draft

A getty implementation from Sandeep Sharma and Kyungwan Han.
author Rob Landley <rob@landley.net>
date Wed, 09 Apr 2014 07:45:59 -0500
parents eae29e8e2bc8
children 7b3c1238380a
files toys/pending/getty.c
diffstat 1 files changed, 340 insertions(+), 0 deletions(-) [+]
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/toys/pending/getty.c	Wed Apr 09 07:45:59 2014 -0500
@@ -0,0 +1,340 @@
+/* getty.c - A getty program to get controlling terminal.
+ *
+ * Copyright 2012 Sandeep Sharma <sandeep.jack2756@gamil.com>
+ * Copyright 2013 Kyungwan Han <asura321@gmail.com>
+ *
+ * No Standard.
+
+USE_GETTY(NEWTOY(getty, "<2t#<0H:I:l:f:iwnmLh",TOYFLAG_SBIN))
+
+config GETTY
+  bool "getty"
+  default n
+  help
+    Usage: getty [OPTIONS] BAUD_RATE[,BAUD_RATE]... TTY [TERMTYPE]
+
+    -h    Enable hardware RTS/CTS flow control
+    -L    Set CLOCAL (ignore Carrier Detect state)
+    -m    Get baud rate from modem's CONNECT status message
+    -n    Don't prompt for login name
+    -w    Wait for CR or LF before sending /etc/issue
+    -i    Don't display /etc/issue
+    -f ISSUE_FILE  Display ISSUE_FILE instead of /etc/issue
+    -l LOGIN  Invoke LOGIN instead of /bin/login
+    -t SEC    Terminate after SEC if no login name is read
+    -I INITSTR  Send INITSTR before anything else
+    -H HOST    Log HOST into the utmp file as the hostname
+*/
+#define FOR_getty
+#include "toys.h"
+#include <utmp.h>
+
+GLOBALS(
+  char *issue_str;
+  char *login_str;
+  char *init_str;
+  char *host_str; 
+  long timeout;
+  
+  char *tty_name;  
+  int  speeds[20];
+  int  sc;              
+  struct termios termios;
+  char buff[128];
+)
+
+#define CTL(x)        ((x) ^ 0100) 
+#define HOSTNAME_SIZE 32
+
+typedef void (*sighandler_t)(int);
+struct speed_mapper {
+  long speed;
+  speed_t code;
+};
+
+struct speed_mapper speedtab[] = {
+  {50, B50}, {75, B75}, {110, B110}, {134, B134}, {150, B150}, {200, B200},
+  {300, B300}, {600, B600}, {1200, B1200}, {1800, B1800}, {2400, B2400},
+  {4800, B4800}, {9600, B9600},
+#ifdef  B19200
+  {19200, B19200},
+#endif
+#ifdef  B38400
+  {38400, B38400},
+#endif
+#ifdef  EXTA
+  {19200, EXTA},
+#endif
+#ifdef  EXTB
+  {38400, B38400},
+#endif
+#ifdef B57600
+  {57600, B57600},
+#endif
+#ifdef B115200
+  {115200, B115200},
+#endif
+#ifdef B230400
+  {230400, B230400},
+#endif
+  {0, 0},
+};
+
+// Find speed from mapper array 
+static speed_t encode(char *s)
+{
+  struct speed_mapper *sp;
+  long speed = atolx(s);
+
+  if (!speed) return 0;
+  for (sp = speedtab; sp->speed; sp++) if (sp->speed == speed) return sp->code;
+  return (speed_t) -1;
+}
+
+static void get_speed(char *sp)
+{
+  char *ptr;
+
+  TT.sc = 0;
+  while ((ptr = strsep(&sp, ","))) {
+    TT.speeds[TT.sc] = encode(ptr);
+    if (TT.speeds[TT.sc] < 0) perror_exit("Bad Speed");
+    if (++TT.sc > 10) perror_exit("Too many alternate speeds, Max is 10");
+  }
+}
+
+// Parse args and set TERM env. variable
+static void parse_arguments(void)
+{
+  if (isdigit(**toys.optargs)) {
+    get_speed(*toys.optargs);
+    if (*++toys.optargs) TT.tty_name = xmsprintf("%s", *toys.optargs);
+  } else {
+    TT.tty_name = xmsprintf("%s", *toys.optargs);
+    if (*++toys.optargs) get_speed(*toys.optargs);
+  } 
+  if (*++toys.optargs) setenv("TERM", *toys.optargs, 1);
+}
+
+// Get controlling terminal and redirect stdio 
+static void open_tty(void)
+{
+  if (strcmp(TT.tty_name, "-")) {
+    if (*(TT.tty_name) != '/') TT.tty_name = xmsprintf("/dev/%s", TT.tty_name);
+    // Sends SIGHUP to all foreground process if Session leader don't die,Ignore
+    sighandler_t sig = signal(SIGHUP, SIG_IGN); 
+    ioctl(0, TIOCNOTTY, 0); // Giveup if there is any controlling terminal
+    signal(SIGHUP, sig);
+    if (setsid() < 0) { // Seems we are session leader
+      pid_t sid = getpid();
+
+      if(sid != getsid(0)) perror_exit("setsid");
+    }
+    xclose(0);
+    xopen(TT.tty_name, O_RDWR|O_NDELAY);
+    fcntl(0, F_SETFL, fcntl(0, F_GETFL) & ~O_NONBLOCK); // Block read
+    dup2(0, 1);
+    dup2(0, 2);
+    if (ioctl(0, TIOCSCTTY, 1) < 0) perror_msg("ioctl(TIOCSCTTY)");
+    if (!isatty(0)) perror_exit("/dev/%s:Not a character device", TT.tty_name);
+    chown(TT.tty_name, 0, 0); // change ownership, Hope login will change this
+    chmod(TT.tty_name, 0620);
+  } else { // We already have opened TTY
+    if (setsid() < 0) perror_msg("setsid:failed");
+    if ((fcntl(0, F_GETFL) & (O_RDWR|O_RDONLY|O_WRONLY)) != O_RDWR)
+      perror_exit("opened tty don't have read/write permission");
+  }
+}
+
+// Intialise terminal settings
+static void termios_init(void)
+{
+  if (tcgetattr(STDIN_FILENO, &TT.termios) < 0) perror_exit("tcgetattr");
+  // Flush input and output queues, important for modems!
+  tcflush(STDIN_FILENO, TCIOFLUSH); 
+  TT.termios.c_cflag &= (0|CSTOPB|PARENB|PARODD);
+#ifdef CRTSCTS
+  if (toys.optflags & FLAG_h) TT.termios.c_cflag |= CRTSCTS;
+#endif
+  if (toys.optflags & FLAG_L) TT.termios.c_cflag |= CLOCAL;
+  TT.termios.c_cc[VTIME] = 0;
+  TT.termios.c_cc[VMIN] = 1;
+  TT.termios.c_oflag = OPOST|ONLCR;
+  TT.termios.c_cflag |= CS8|CREAD|HUPCL|CBAUDEX;
+  TT.termios.c_iflag = 0;
+  // login will disable echo for passwd.
+  TT.termios.c_lflag |= ISIG|ICANON|ECHO|ECHOE|ECHOK|ECHOKE;
+  TT.termios.c_cc[VINTR] = CTL('C');
+  TT.termios.c_cc[VQUIT] = CTL('\\');
+  TT.termios.c_cc[VEOF] = CTL('D');
+  TT.termios.c_cc[VEOL] = '\n';
+  TT.termios.c_cc[VKILL] = CTL('U');
+  TT.termios.c_cc[VERASE] = CERASE;
+  TT.termios.c_iflag |= ICRNL|IXON|IXOFF;
+  // set non-zero baud rate. Zero baud rate left it unchanged.
+  if (TT.speeds[0] != B0) cfsetspeed(&TT.termios, TT.speeds[0]); 
+  if (tcsetattr(STDIN_FILENO, TCSANOW, &TT.termios) < 0) 
+    perror_exit("tcsetattr");
+}
+
+// Get the baud rate from modems CONNECT mesage, Its of form <junk><BAUD><Junk>
+static void sense_baud(void)
+{
+  int vmin;
+  ssize_t size;
+  char *ptr;
+  speed_t speed;
+
+  vmin = TT.termios.c_cc[VMIN]; // Store old
+  TT.termios.c_cc[VMIN] = 0; // No block even queue is empty.
+  if (tcsetattr(STDIN_FILENO, TCSANOW, &TT.termios) < 0) 
+    perror_exit("tcsetattr");
+  size = readall(STDIN_FILENO, TT.buff, sizeof(TT.buff)-1);
+  if (size > 0) {
+    for (ptr = TT.buff; ptr < TT.buff+size; ptr++) {
+      if (isdigit(*ptr)) {
+        speed = encode(ptr);
+        if (speed > 0) cfsetspeed(&TT.termios,speed);
+        break;
+      }
+    } 
+  }
+  TT.termios.c_cc[VMIN] = vmin; //restore old value
+  if (tcsetattr(STDIN_FILENO, TCSANOW, &TT.termios) < 0)
+    perror_exit("tcsetattr");
+}
+
+// Just prompt for login name 
+void print_prompt(void)
+{
+  char *hostname;
+  struct utsname uts;
+
+  uname(&uts);
+  hostname = xstrdup(uts.nodename);
+  fputs(hostname, stdout);
+  fputs(" Login: ", stdout);
+  fflush(NULL);
+  free(hostname);
+  hostname = NULL;
+}
+
+// Print /etc/isuue with taking care of each escape sequence
+void write_issue(char *file)
+{
+  char buff[20] = {0,};
+  struct utsname u;
+  uname(&u);
+  int size, fd = open(TT.issue_str, O_RDONLY);
+
+  if (fd < 0) return;
+  while ((size = readall(fd, buff, 1)) > 0) {
+    char *ch = buff;
+
+    if (*ch == '\\' || *ch == '%') {
+      if (readall(fd, buff, 1) <= 0) perror_exit("readall");
+      if (*ch == 's') fputs(u.sysname, stdout);
+      if (*ch == 'n'|| *ch == 'h') fputs(u.nodename, stdout);
+      if (*ch == 'r') fputs(u.release, stdout);
+      if (*ch == 'm') fputs(u.machine, stdout);
+      if (*ch == 'l') fputs(TT.tty_name, stdout);
+    } else xputc(*ch);
+  }
+}
+
+// Read login name and print prompt and Issue file. 
+static int read_login_name(void)
+{
+  tcflush(STDIN_FILENO, TCIFLUSH); // Flush pending speed switches
+  int i = 0;
+
+  while (1) { // Option -i will overide -f
+    if (!(toys.optflags & FLAG_i)) write_issue(TT.issue_str); 
+    print_prompt();
+    TT.buff[0] = getchar();
+    if (!TT.buff[0] && TT.sc > 1) return 0; // Switch speed
+    if (TT.buff[0] == '\n') continue;
+    if (TT.buff[0] != '\n')
+      if (!fgets(&TT.buff[1], HOSTNAME_SIZE-1, stdin)) _exit(1);
+    while (i < HOSTNAME_SIZE-1 && isgraph(TT.buff[i])) i++;
+    TT.buff[i] = 0;
+    break;
+  }
+  return 1;
+}
+
+// Put hostname entry in utmp file
+static void utmp_entry(void)
+{
+  struct utmp entry;
+  struct utmp *utp_ptr;
+  pid_t pid = getpid();
+
+  utmpname(_PATH_UTMP);
+  setutent(); // Starts from start
+  while ((utp_ptr = getutent())) 
+    if (utp_ptr->ut_pid == pid && utp_ptr->ut_type >= INIT_PROCESS) break;
+  if (!utp_ptr) { 
+    entry.ut_type = LOGIN_PROCESS;
+    entry.ut_pid = getpid();
+    xstrncpy(entry.ut_line, ttyname(STDIN_FILENO) + 
+        strlen("/dev/"), UT_LINESIZE);
+    time((time_t *)&entry.ut_time);
+    xstrncpy(entry.ut_user, "LOGIN", UT_NAMESIZE);
+    if (strlen(TT.host_str) > UT_HOSTSIZE) 
+      perror_msg("Can't make utmp entry, Host length is greater than UT_HOSTSIZE(256)");
+    else xstrncpy(entry.ut_host, TT.host_str, UT_HOSTSIZE);
+    setutent();
+    pututline(&entry);
+    return;
+  }
+  xstrncpy(entry.ut_line, ttyname(STDIN_FILENO) + strlen("/dev/"), UT_LINESIZE);
+  xstrncpy(entry.ut_user, "LOGIN", UT_NAMESIZE);
+  if (strlen(TT.host_str) > UT_HOSTSIZE) 
+    perror_msg("Can't make utmp entry,Host length is greater than UT_HOSTSIZE(256)");
+  else xstrncpy(entry.ut_host, TT.host_str, UT_HOSTSIZE);
+  time((time_t *)&entry.ut_time);
+  setutent();
+  pututline(&entry);
+}
+
+void getty_main(void)
+{
+  pid_t pid = getpid();
+  char *ptr[2] = {"/bin/login", NULL};
+
+  if (!(toys.optflags & FLAG_f)) TT.issue_str = "/etc/issue";
+  if (toys.optflags & FLAG_l) ptr[0] = TT.login_str;
+  parse_arguments();
+  open_tty();
+  termios_init();
+  tcsetpgrp(STDIN_FILENO, pid);
+  if (toys.optflags & FLAG_H) utmp_entry();
+  if (toys.optflags & FLAG_I) 
+    writeall(STDOUT_FILENO,TT.init_str,strlen(TT.init_str));
+  if (toys.optflags & FLAG_m) sense_baud();
+  if (toys.optflags & FLAG_t) alarm(TT.timeout);
+  if (toys.optflags & FLAG_w) {
+    char ch;
+
+    while (readall(STDIN_FILENO, &ch, 1) != 1)  
+      if (ch == '\n' || ch == '\r') break;
+  }
+  if (!(toys.optflags & FLAG_n)) {
+    int index = 1; // 0th we already set.
+
+    while (1) {
+      int l = read_login_name();
+
+      if (l) break;
+      index = index % TT.sc;
+      cfsetspeed(&TT.termios, TT.speeds[index]); // Select from multiple speeds
+      //Necessary after cfsetspeed
+      if (tcsetattr(STDIN_FILENO, TCSANOW, &TT.termios) < 0) 
+        perror_exit("tcsetattr"); 
+    }
+  }
+  if (toys.optflags & FLAG_n) execlp(*ptr, *ptr ,NULL); 
+  else execlp(*ptr, *ptr, TT.buff, NULL); 
+  perror_exit("error:%d",errno); // exec will return only if error
+}
\ No newline at end of file