/*
 * @(#) mshell.c - 'MultiShell' for Unix.
 * (c) 1995 Ivan Maidanski <ivmai@chat.ru> http://ivmai.chat.ru
 * Freeware program source. All rights reserved.
 **
 * Language: C
 * Tested with: BSD GNU CC
 * Last modified: 1995-12-19 14:05:00 GMT+03:00
 */

#include <stdio.h>
#include <sys/param.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <fcntl.h>
#include <string.h>
#include <signal.h>
/* #include <stdlib.h> */
/* #include <curses.h> */ /* for terminal input */

#define SHPROMPT "(mshell) "

typedef int pid_t;
typedef int fd_t;
typedef enum {FALSE,TRUE} tbool;

typedef enum
{
 PRGNO,PRGLOAD,PRGRUN,
 PRGEXIT,PRGSTOP,PRGKILL,
 PRGEXITD,PRGSTOPD
} tpstat;

typedef struct tplist
{
 pid_t pid;
 tpstat stat;
 fd_t fin[2],fout[2];
 char *name;
 struct tplist *prev,*next;
} tplist;

static char *sh_commands[]= /* in lower case */
{
 "cd","pwd","exit",
 "task","tasklist","taskeof",
 "deltask","killtask",
 "newshell","help","?",0
};

static char *errmsg[]=
{
 " Error: No enough memory.\n",
 " Error: No memory for Fork.\n",
 " Error: Cannot execute program.\n",
 " Error: No one task loaded.\n",
 " Error: Cannot change current directory.\n",
 " Error: Cannot send this symbol to task.\n",
 " Error: Background mode...\n"
};

static tplist *plist=0;
static tbool breakvar;

tbool ins_new_task(char *name,fd_t fin[2],fd_t fout[2])
{
 tplist *task=(tplist *)malloc(sizeof(tplist));
 if (!task)
  return FALSE;
 task->fin[0]=fin[0];
 task->fin[1]=fin[1];
 task->fout[0]=fout[0];
 task->fout[1]=fout[1];
 task->name=(char *)malloc(strlen(name)+1);
 if (!task->name)
 {
  free((void *)task);
  return FALSE;
 }
 strcpy(task->name,name);
 if (plist)
 {
  task->prev=plist->prev;
  task->next=plist;
  task->next->prev=task->prev->next=task;
 }
  else task->prev=task->next=task;
 plist=task;
 return TRUE;
}

void del_one_task(tplist *ptr)
{
 close(ptr->fin[0]);
 close(ptr->fin[1]);
 close(ptr->fout[0]);
 close(ptr->fout[1]);
 free((void *)ptr->name);
 ptr->prev->next=ptr->next;
 ptr->next->prev=ptr->prev;
 if (ptr==plist)
  plist=ptr->next;
 if (ptr==plist)
  plist=0;
 free((void *)ptr);
}

tpstat get_prog_status(pid_t *pid)
{
 union wait stat;
 *pid=wait3(&stat,WNOHANG | WUNTRACED,0);
 if (*pid>0)
  if (WIFEXITED(stat))
   return PRGEXIT;
   else if (WIFSTOPPED(stat))
    return PRGSTOP;
    else return PRGKILL;
 return PRGNO;
}

tplist *find_prog_pid(pid_t pid)
{
 tplist *ptr=plist;
 if (plist)
  do
   if (ptr->pid==pid)
    return ptr;
   while ((ptr=ptr->next)!=plist);
 return 0;
}

tbool get_term_info(void)
{
 tbool event=FALSE;
 tplist *ptr;
 pid_t pid;
 tpstat stat;
 while ((stat=get_prog_status(&pid))!=PRGNO)
 {
  event=TRUE;
  if ((ptr=find_prog_pid(pid)))
   ptr->stat=(ptr->stat==PRGLOAD) ? PRGNO : stat;
 }
 return event;
}

void through_del_tasks(void)
{
 tplist *ptr;
 do
 {
  ptr=plist;
  if (plist)
   do if (ptr->stat==PRGNO)
   {
    del_one_task(ptr);
    break;
   } while ((ptr=ptr->next)!=plist);
 } while (ptr!=plist);
}

pid_t run_prog(char *name,fd_t fldin,fd_t fldout)
{
 pid_t pid=fork();
 if (!pid)
 {
  char *sh=(char *)getenv("SHELL");
  dup2(fldin,0);
  dup2(fldout,1);
  dup2(fldout,2);
  if (name)
   execl(sh,sh,"-c",name,0);
   else execl(sh,sh,0);
  exit(1);
 }
 return pid;
}

void execprog(char *name)
{
 fd_t fin[2],fout[2];
 if (pipe(fin) || pipe(fout))
  printf(errmsg[0]);
  else if (ins_new_task(name ? name : "Shell...",fin,fout)==FALSE)
  {
   printf(errmsg[0]);
   close(fin[0]);
   close(fin[1]);
   close(fout[0]);
   close(fout[1]);
  }
   else
   {
    fcntl(fout[0],F_SETFL,O_NDELAY);
    if ((plist->pid=run_prog(name,fin[0],fout[1]))<0)
    {
     printf(errmsg[1]);
     del_one_task(plist);
    }
     else plist->stat=PRGRUN;
   }
}

void print_task_list(void)
{
 int n=0;
 tplist *ptr=plist;
 if (plist)
  do printf(" %2d. (%04d) %s\n",++n,ptr->pid,ptr->name);
   while ((ptr=ptr->next)!=plist);
}

void print_term_info(void)
{
 int n=1;
 tplist *ptr=plist;
 if (plist)
  do switch (ptr->stat)
  {
   case PRGKILL:
    printf(" Task killed: %2d. (%04d) %s\n",
           n,ptr->pid,ptr->name);
    ptr->stat=PRGNO;
    break;
   case PRGSTOPD:
    printf(" Task stopped: %2d. (%04d) %s\n",
           n,ptr->pid,ptr->name);
    ptr->stat=PRGNO;
    break;
   case PRGEXITD:
    printf(" Task stopped: %2d. (%04d) %s\n",
           n,ptr->pid,ptr->name);
    ptr->stat=PRGNO;
    break;
   case PRGNO:
    printf(" Cannot load program (%s).\n",ptr->name);
   default:
    break;
  }
   while (n++,(ptr=ptr->next)!=plist);
}

int find_lexem(char **list,char *lexem)
{
 int n;
 for (n=0;list[n];n++)
  if (!strcmp(lexem,list[n])) return n+1;
 return 0;
}

void exit_shell(void)
{
 while (plist)
 {
  kill(plist->pid,SIGKILL);
  del_one_task(plist);
 }
 printf(" Shell exiting...\n\n");
 signal(SIGINT,SIG_IGN);
 close(0);
}

void goto_task(int n)
{
 while (--n)
  plist=plist->next;
 printf(" %s\n",plist->name);
}

void send_symbol(unsigned char ch)
{
 if (write(plist->fin[1],&ch,sizeof(unsigned char))<=0)
  printf(errmsg[5]);
}

int receive_symbol(void)
{
 unsigned char ch;
 if (read(plist->fout[0],&ch,sizeof(unsigned char))<=0)
  return -1;
 return (int)ch;
}

void send_all_symbols(void)
{
 int c;
 if ((c=getchar())==EOF || c=='\x1B')
  breakvar=TRUE;
  else if (plist->fin[1]>=0)
   send_symbol((unsigned char)c);
 if (c=='\x1B')
  getchar();
}

void receive_all_symbols(void)
{
 int c;
 while ((c=receive_symbol())>=0)
  putchar(c);
}

tbool interprete(char *cmd)
{
 int n;
 char *arg=strchr(cmd,' ');
 if (arg) *arg='\0';
 n=find_lexem(sh_commands,cmd);
 if (arg) *arg=' ';
 if (arg)
  do arg++;
   while (*arg==' ' || *arg=='\t');
 switch (n)
 {
  case 1: /* cd */
   if (chdir(arg ? arg : (char *)getenv("HOME")))
    printf(errmsg[4]);
   break;
  case 2: /* pwd */
  {
   char path[MAXPATHLEN];
   printf(" %s\n",getwd(path,MAXPATHLEN));
   break;
  }
  case 3: /* exit */
   if (plist)
   {
    int c;
    printf(" There are unstopped jobs. Exit (y/n)? ");
    if ((c=getchar())!=EOF)
     getchar();
    if (c!=EOF && c!='Y' && c!='y')
     return FALSE;
   }
   return TRUE;
  case 4: /* task */
   if (plist)
    goto_task(arg ? atoi(arg) : 2);
    else printf(errmsg[3]);
   break;
  case 5: /* tasklist */
   if (plist)
    print_task_list();
    else printf(errmsg[3]);
   break;
  case 6: /* taskeof */
   if (plist)
    if (plist->fin[1]>=0)
    {
     close(plist->fin[0]);
     close(plist->fin[1]);
     plist->fin[1]=-1;
    }
     else printf(errmsg[5]);
    else printf(errmsg[3]);
   break;
  case 7: /* deltask */
   if (plist)
    kill(plist->pid,SIGTERM);
    else printf(errmsg[3]);
   break;
  case 8: /* killtask */
   if (plist)
   {
    kill(plist->pid,SIGKILL);
    del_one_task(plist);
   }
    else printf(errmsg[3]);
   break;
  case 9: /* newshell */
   printf("Loading New Command Shell...\n");
   execprog(0);
   break;
  case 10: /* help */
  case 11: /* ? */
   printf("Shell Commands:\n"
          " cd <newdir> - change working directory\n"
          " pwd - show work directory\n"
          " exit - exit from shell\n"
          " task <n> - change current task\n"
          " tasklist - show list of loaded tasks\n"
          " taskeof - send eof-symbol to task\n"
          " deltask - terminate current task\n"
          " killtask - kill current task\n"
          " newshell - load another standard shell\n"
          " help - show this help info\n"
          " ? - show this help info\n"
          "Shell Control symbols:\n"
          " [Escape] (task) - jump to shell\n"
          " [Enter] (monitor) - jump to task\n"
          " [Tab] (monitor) - show tasks list\n"
          " [Ctrl-C] (monitor) - reset monitor\n"
          " [Ctrl-D] (monitor) - exit shell\n"
          "Shell Task Loading:\n"
          " <task file name> - load and run another task\n");
   break;
  default: /* load */
   printf("Loading...\n");
   execprog(cmd);
 }
 return FALSE;
}

tbool task_mode(void)
{
 tbool term;
 printf("\n");
 do
 {
  receive_all_symbols();
  term=get_term_info();
  if (plist->stat==PRGEXIT || plist->stat==PRGSTOP)
  {
   plist->stat=(plist->stat==PRGEXIT) ? PRGEXITD : PRGSTOPD;
   term=TRUE;
  }
  if (term==FALSE && breakvar==FALSE)
   send_all_symbols();
 } while (term==FALSE && breakvar==FALSE);
 return term;
}

int getstr(char *s,int len)
{
 int c,i=0;
 len--;
 do
 {
  if (i<len)
   c=getchar();
   else c='\n';
  if (c!=EOF && c!='\n')
   s[i++]=(char)c;
 } while (c!=EOF && c!='\n');
 s[i]='\0';
 return c;
}

void monitor_mode(void)
{
 tbool quit,term;
 char cmd[MAXPATHLEN];
 do
 {
  breakvar=term=FALSE;
  printf(SHPROMPT);
  if ((quit=getstr(cmd,MAXPATHLEN)==EOF)==TRUE)
   printf("\n");
  if (quit==FALSE && breakvar==FALSE)
   if (*cmd)
    if (*cmd!='\t')
     quit=interprete(cmd);
     else print_task_list();
    else if (plist)
     term=task_mode();
  if (get_term_info()==TRUE || term==TRUE)
   printf("\n" SHPROMPT "\\\n");
  print_term_info();
  through_del_tasks();
 } while (quit==FALSE);
}

void keyboard_break(int sig)
{
 breakvar=TRUE;
 signal(SIGINT,&keyboard_break);
}

void init_shell(void)
{
 if (signal(SIGINT,SIG_IGN)!=SIG_IGN)
  signal(SIGINT,&keyboard_break);
}

void main(void)
{
 printf("Multi Shell v1.0 for UNIX System.\n"
        " Type 'help' for help.\n\n");
 init_shell();
 monitor_mode();
 exit_shell();
}
