

/*
   ChangeLog
   Build date 08/25/1993
       09/01/1993
           (1) when we can't get ack in WAIT_TIME seconds,I will send
	       A reset signal to chdam.
       09/03/1993
           (1) integrate chdam & ttyserver to a single program
       09/04/1993
           (1) use pseudo terminal to redirect the terminal
	   
*/


#define CHINESE_KERNEL
#include <sys/types.h>
#include <sys/sem.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <sys/syslog.h>
#include <sys/vt.h>
#include <sys/kd.h>
#include <signal.h>
#include <setjmp.h>
#include <unistd.h>
#include <sys/wait.h>
#include <sys/time.h>
#include <termios.h>
#include <stdlib.h>
#include <sys/socket.h>
#include "chmsg.h"
#include "chinese.h"

char copyright[] = {"Chinese TTY simulator\nWrite By Yu-Chung Wang\n"
"I am protected by GPL\n"};


static struct {
  int client_read_pipe;
  int client_write_pipe;
  char devicename[10];
  int isused;
  struct vt_mode vtmode;
  int shellpid,clientpid;
} cons[CONS_NUM];
int outfd[2],infd[2];
static int child_fd,parent_fd;
static FILE *out;
#define D(p)  p


#define master   (cons[curcons].client_read_pipe)
#define slave  (cons[curcons].client_write_pipe)
#define devicename (cons[curcons].devicename)
#define vmode      (cons[curcons].vtmode)
#define used       (cons[curcons].isused) 
#define scrtext    (cons[curcons].scrtext)
#define scrattr    (cons[curcons].scrattr)
#define shellpid   (cons[curcons].shellpid)
#define clientpid  (cons[curcons].clientpid)
#define WAIT_TIME  120

static int semid;
static char ttys[] = {"/dev/tty0"};
static char cins[] = {"/dev/cin0"};
static char couts[]= {"/dev/cout0"};
static unsigned char buf[528]={255,254};
static int ptypid,driverpid;
static int commandpipe;
static jmp_buf albuf,reset;
static int serverpid;
static int programpid;
void open_file();
static void init_fds();
static daemon_init()
{
  int   fd;
  
  /* before start,we will check if there is another ttyserver exist */

  fd = open("/dev/cin",O_WRONLY|O_NDELAY);
  if (fd > 0)
    {
      /* Their is a process that open the /dev/cin for read   */
      /* So we can realize there are another copie(s) active  */
      printf("There is somewaht another copy of process active or\n");
      printf("other process use /dev/cin to operate.The Chinese tty\n");
      printf("Daemon can't start.\n");
      exit(1);
    }
  close(fd);
  out = fopen("/dev/tty8","w");

  if (geteuid() != 0)
    {
      printf("Can't execute in non-root mode\n");
      exit(1);
    }
  chdir("/");
  umask(0);
  if (getppid() == 1)
    return;

  if (fork() != 0)
    exit(0);
  setpgrp();
  if (fork()!=0)
    exit(0);

  printf("\nChinese tty writen by Yu-Ching Wang %s\n",version);
  printf("Use 'kill %d' to kill me\n",getpid());
}

static void sig_init(int signo)
{
  kill(serverpid,SIGINT);
  longjmp(reset,1);
}

static void P()
{
  static struct sembuf p = {0,-1,SEM_UNDO};
  /*
  semop(semid,&p,1);
  */
}
 
static void V()
{
  static struct sembuf v = {0,1,SEM_UNDO};
  /*
  semop(semid,&v,1);
  */
}

void sendcommand(int cmd,int tty,char *sbuf,int len)
{
  int rlen;
  buf[0] = 255;buf[1] = 254;
  buf[2] = len;buf[3] = 0;
  buf[4] = tty;
  buf[5] = cmd;
  P();
  write(outfd[1],buf,6);

  while (len > 0)
    {
      rlen = (len > 255)? 255 :len;
      write(outfd[1],sbuf,rlen);
      sbuf +=rlen;
      len -= rlen;
    }
  
  V();
}

void sys_fatel(char *msg,...)
{
  static char pbuf[262];
  va_list AP;

  va_start(AP,msg);
  vsprintf(pbuf+6,AP,msg);
  pbuf[0] = 255;pbuf[1] = 254;pbuf[2] = 0;pbuf[3] = strlen(pbuf+6);
  pbuf[5] = CCMD_PRINT;pbuf[4] = 0;
  P();
  write(outfd[1],pbuf,*((short *) (pbuf+3))+6);
  pbuf[3] = 0;
  pbuf[5] = CCMD_TEXT;
  write(outfd[1],pbuf,6);
  V();
  write(0,pbuf+6,strlen(pbuf+6));
  va_end(AP);
  exit(0);
}

static void sigquit(int signo)
{
  kill(child_fd,SIGTERM);
  kill(serverpid,SIGTERM);
  exit(0);
}


static char intbuf[4096];
static int bufhead=0,bufend=0;

static void add_write(char *buf,int len)
{
  D(fprintf(out,"Add %d byte\n",len));
  if ((bufend+len)%4095 > bufhead)
    {
      /* in this case ,we write directly to pipe
	 This maybe block,but....
	 */
      while(bufhead != bufend)
	out_buf();
      while(len)
	len -= write(outfd[1],buf,len);
      return;
    }
  if ((bufend+len) > 4095)
    {
      memcpy(intbuf+bufend,buf,4095-bufend);
      buf += 4095-len;
      len -= 4095-len;
      bufend = 0;
      memcpy(intbuf,buf,len);
      bufend = len;
      return;
    }
  memcpy(intbuf+bufend,buf,len);
  bufend += len;
}
out_buf()
{
  int ptr;
  if (bufend < bufhead)
    bufhead += write(outfd[1],intbuf+bufhead,4096-bufhead);
  else
    bufhead += write(outfd[1],intbuf+bufhead,bufend-bufhead);
}

static void sigquit1(intsigno)
{
  kill(programpid,SIGTERM);
  kill(parent_fd,SIGTERM);
  exit(0);
}

/* 
   Don't confuse with the name "waitinloop" ,this procedure is used to
   'READ' from the real program and 'WRITE' it to the chinese daemon.
*/

static fd_set readset,writeset;
static int toclient;
static maxfd;
inline int READ_SELECT(struct timeval *tptr)
{
  int curcons,max,len,ret;
  fd_set rset,wset;
  struct timeval timep;
  static whofirst = 0;

  while(1)
    {
      rset = readset;wset=writeset;timep = *tptr;
      if (bufend != bufhead)
	FD_SET(outfd[1],&wset);
      if (toclient <=0)
	toclient = open("/dev/cin",O_RDONLY);
      if (toclient > 0)
	FD_SET(toclient,&rset);

      if ((len = select(maxfd+1,&rset,&wset,NULL,&timep)) < 0)
	{
	  close(toclient);
	  toclient = 0;
	  init_fds();
	  continue;
	}

      /* if a request to write to screen */
      if (FD_ISSET(outfd[1],&wset))
	  out_buf();

      /* if a request to build a session */
      if (FD_ISSET(toclient,&rset))
	{
	  open_file();
	  init_fds();
	}
      /* if a input from chinese daemon */
      for(curcons=0;curcons<CONS_NUM;curcons++)
	{
	  if (used)
	    {
	      if (FD_ISSET(master,&rset))
		{
		  buf[0] = 255;buf[1] = 254;
		  buf[5] = CCMD_PRINT;
		  buf[4] = curcons;
		  len = read(master,buf+6,10);
		  *((short int *)(buf + 2)) = len;
		  P();
		  add_write(buf,len+6);
		  V();
		  D(fprintf(out,"Write %d bytes\n",len));
		}
	      /* check if there is a shell has die */
	      if (shellpid != 0)
		if ((ret = waitpid(shellpid,NULL,WNOHANG)) != 0)
		  {
		    shellpid = 0;
		    if (ret == -1)
		      {
			fprintf(out,"SHELL DIE ANYWAY\n");
			continue;
		      }
		    sendcommand(CCMD_TEXT,curcons,NULL,0);
		    sendcommand(CCMD_END,curcons,NULL,0);
		    /* Wake up the ccc driver                   */
		    /* the ccc driver is in pause() call        */
		    kill(clientpid,SIGALRM);
		    used = 0;
		    shellpid = clientpid = 0;
		    /* clear up all garbage in psuedo terminal */
		    ioctl(master,TCFLSH,2);
		    init_fds();
		  }
	    }
	}
    }
}

/*
   The pipe in there have three kinds
    (1) pipe link from client program
    (2) pty write from user program shell
    (3) pty read from user program shell
*/


static void init_fds()
{
  int curcons;
  if (toclient)
    close(toclient);
  toclient = open("/dev/cin",O_RDONLY|O_NDELAY);
  FD_ZERO(&readset);FD_ZERO(&writeset);
  
  maxfd = toclient;
  for(curcons=0;curcons<CONS_NUM;curcons++)
    {
      if (used)
	{
	  FD_SET(master,&readset);
	  if (master > maxfd)
	    maxfd = master;
	}
    }
}
int READ_WAIT(struct timeval *timeout)
{
  fd_set readfd;

  FD_ZERO(&readfd);
  FD_SET(infd[0],&readfd);
  FD_SET(commandpipe,&readfd);
  select(commandpipe>infd[0]? commandpipe+1:infd[0]+1,&readfd,NULL,NULL,NULL);
  if (FD_ISSET(commandpipe,&readfd))
    {
      char cc;
      int curcons;
    }
  if (FD_ISSET(infd[0],&readfd))
    return 1;
  return -1;
}
  
void waitinloop()
{
  int fds,len,rlen;
  static int curcons = -1;
  static unsigned char ibuf[5];
  static int ptr=0;
  fd_set readfds,writefds;
  struct timeval timeout;

  /* wait for 0.3 seconds */
  timeout.tv_sec = 10;
  timeout.tv_usec = 0;
  
  init_fds();
  while(1)
    {
      while(1)
	{
	  if (READ_WAIT(&timeout) < 0)
	    break;
	  if (read(infd[0],ibuf,1) != 1)
	    continue;
	  ptr++;	      
	  if (ibuf[0] != 255)
	    break;
	  if (READ_WAIT(&timeout) < 0)
	    break;
	  
	  if (read(infd[0],ibuf+1,1) != 1)
	    break;
	  ptr++;
	  if (ibuf[1] != 254)
	    break;

	  if (READ_WAIT(&timeout) < 0)
	    break;
	  
	  if (read(infd[0],ibuf+2,2) != 2)
	    break;
	  len = *(short int *) (ibuf+2);
	  ptr += 2;
	  if (len > 8192)
	    break;
	  if (read(infd[0],buf,1) != 1)
	    continue;
	  curcons = *buf;
	  D(fprintf(out,"Write %d bytes to TTY%d\n",len,curcons));
	  while(len > 0)
	    {
	      rlen = (len > 10)? 10 : len;
	      len = read(infd[0],buf,rlen);
	      rlen = write(master,buf,rlen);
	      len -= rlen;
	    }
	  ptr = 0;
	}
      /* If ibuf is not empty,it stands for a incomplete head.We 
	 send it as data */
      if (ptr != 0)
	{
	  if (curcons == -1)
	    sys_fatel("No tty set head first");
	  len = write(master,ibuf,ptr);
	  D(fprintf(out,"Write %d bytes to TTY%d\n",len,curcons));
	  ptr = 0;
	}
    }
}

void waitoutloop()
{
  int fds,len,rlen;
  static int curcons = -1;
  static unsigned char ibuf[5];
  static int ptr=0;
  fd_set readfds,writefds;
  struct timeval timeout;

  /* wait for 0.3 seconds */
  timeout.tv_sec = 5;
  timeout.tv_usec = 0;
  
  init_fds();
  READ_SELECT(&timeout);
}


void open_file()
{
  int i,curcons,len,fd;
  struct winsize size;
  char *tmpname;
  char name[25];
  CMSG msg;
  char path[256];
  CHAR ww[2];
  char cc;

  D(fprintf(out,"Enter fork process\n"));
  if (read(toclient,(char *)&msg,sizeof(CMSG)) != sizeof(CMSG))
    {
      close(toclient);
      toclient = -1;
      return;
    }
  if (msg.type == CHTTY_EXIST)
    {
      close(toclient);
      toclient = -1;
      sendcommand(CCMD_CHTTY,msg.tty,NULL,0);
      read(infd[0],buf,1);
      fd = open("/dev/cout",O_WRONLY);
      write(fd,buf,1);
      close(fd);
      return ;
    }
  D(fprintf(out,"read home directory\n"));
  buf[msg.length_of_name] = 0;
  fcntl(toclient,F_SETFL,0);
  if ((len = read(toclient,path,msg.length_of_home)) != msg.length_of_home)
    {
      fprintf(out,"only %d bytes of home path read,should be %d\n(%s)",
	                                   len,msg.length_of_home,
	                                   _sys_errlist[_sys_nerr]);
      close(toclient);
      toclient = -1;
      return;
    }

  close(toclient);
  toclient = -1;
  path[msg.length_of_home] = 0;
  if (msg.tty < 0 || msg.tty >= CONS_NUM)
    return;
  
  /* Start to enable a process which IO is control */
  /* by out PTY piplines                           */
  curcons = msg.tty;
  
  sendcommand(CCMD_START,msg.tty,NULL,0);
  
  if (msg.width_of_screen > MAX_DIMX)
    msg.width_of_screen = MAX_DIMX;
  else if (msg.width_of_screen < MIN_DIMX)
    msg.width_of_screen = MIN_DIMX;
  
  if (msg.lines_of_screen > MAX_DIMY)
    msg.lines_of_screen = MAX_DIMY;
  else if (msg.lines_of_screen < MIN_DIMY)
    msg.lines_of_screen = MIN_DIMY;
  
  /* change the window size of chinese driver */
  ww[0] = msg.width_of_screen;
  ww[1] = msg.lines_of_screen;
  sendcommand(CCMD_SWINSZ,msg.tty,ww,2);
  fprintf(out,"WINDOW SIZE %d %d\n",buf[0],buf[1]);
  sendcommand(CCMD_GRAPH,msg.tty,NULL,0);
  
  if ((shellpid =fork())==0)
    {
      int fd;

      if (setsid()<0)
	{
	  fprintf(out,"Fail to set secttion\n");
	  exit(-1);
	}
      close(0);
      close(1);
      close(2);
      slave = ptys_open(master,devicename);
      if (slave < 0)
	{
	  fprintf(out,"Can't open slave pty\n");
	  close(master);
	  exit(0);
	}
      if (setuid(msg.uid) < 0)
	{
	  fprintf(out,"SETUID FAIL\n");
	  exit(0);
	}
      /* close all opened file descriptor */
      /* In fact,the client ccc should send its stdin & stdout */
      /* to us,but I don't know how to transmit it in Linux ,  */
      /* Any one can tell me?                                  */
      /*
       */  
      /* if path is wrong used root default */
      if (chdir(path) < 0)
	chdir("/");
      /* Enter a new secttion */
      if (setpgid(getpid(),msg.pgrp) < 0)
	{
	  /* I don't know whether this case will happen. */
	  /* But consider it for safty.If somethone can  */
	  /* tell me.thank you                           */
	  setpgid(getpid(),getpid());
	  D(fprintf(out,"SETPGID FAIL\n"));
	}
      
      size.ws_row = msg.lines_of_screen;
      size.ws_col = msg.width_of_screen;
      ioctl(slave,TIOCSWINSZ,(int)&size);
      
      if (dup2(slave,0) != 0)
	  fprintf(out,"DUP2 ERROR\n");
      if (dup2(slave,1) != 1)
	  fprintf(out,"DUP2 ERROR\n");
      if (dup2(slave,2) != 2)
	  fprintf(out,"DUP2 ERROR\n");
      
      write(0,copyright,strlen(copyright));
      /* redefine the enviroment variable TERM to let   */
      /* program use it can get the correct screen size */
      /* In Linux,the consoleG should be added.         */            
      tmpname = getenv("TERM");
      strcpy(name,tmpname);
      strcat(name,"G");
      setenv("TERM",name,1);
      setenv("TERMCAP","/etc/termcapG",1);
      if (execl("/bin/sh","sh",NULL)<0)
	fprintf(out,"Execute error\n");
    }
  else if (shellpid < 0)
    {
      fprintf(out,"fork error in create session\n");
      close(master);
    }
  used = 1;
  clientpid = msg.pid;

  fprintf(out,"A session build\n");
}




main()
{
  int curcons,len;
  int oldf,i;
  int cpipe[2];

  daemon_init();
  D(fprintf(out,"Chinese System 0.2 By Yu-Chung Wang\n"));

  semid = semget(IPC_PRIVATE,1,IPC_CREAT);
  if (semid == -1)
    {
      printf("Semphore can't create\n");
      exit(1);
    }
  if (semctl(semid,0,SETVAL,1) == -1)
    {
      printf("Semphore breakdown\n");
      exit(1);
    }
  /* open a pipe to link to chinese driver */
  if (pipe(outfd)<0)
    {
      fprintf(stderr,"Can't build a link to chinese driver\n");
      exit(1);
    }

  if (pipe(infd)<0)
    {
      fprintf(stderr,"Can't build a link to child ttyserver\n");
      exit(1);
    }
  setsid();
  /* execute chinese driver */

  if ((driverpid = fork())==0)
    {
      close(outfd[1]);
      close(infd[0]);
      if (dup2(outfd[0],0)!=0)
	{
	  fprintf(out,"Can't redirect stdin");
	  exit(1);
	}
      if (dup2(infd[1],1) != 1)
	{
	  fprintf(out,"Can't redirect stdout");
	  exit(1);
	}
      close(outfd[0]);
      close(infd[1]);
      if (execl("/usr/bin/chdam","chdam",NULL)<0)
	{
	  fprintf(out,"Can't exec chinese driver");
	  exit(1);
	}
      exit(-1);
    }
  else
    {
      close(outfd[0]);
      /* wait chdam initial correctly */
    }

  for(curcons=0;curcons<CONS_NUM;curcons++)
    {
      master = ptym_open(devicename);
      used = 0;
    }
  toclient = 0;
  parent_fd = getpid();

  socketpair(AF_UNIX, SOCK_STREAM, 0, cpipe); 
  fcntl(cpipe[0], F_SETFL, O_RDWR );
  fcntl(cpipe[1], F_SETFL, O_RDWR );

  if ((child_fd = fork())==0)
    {
      commandpipe = cpipe[0];
      signal(SIGTERM,sigquit);
      signal(SIGQUIT,sigquit);
      waitinloop();
    }
  else
    {
      commandpipe = cpipe[1];
      signal(SIGTERM,sigquit1);
      signal(SIGQUIT,sigquit1);
      waitoutloop();
    }

  sigquit(0);
}




