wsh/
wsh/binsrc/
wsh/docs/help/
wsh/docs/old/
wsh/etc/
wsh/src/util/
/*
 *       The WizPort
 *
 * This software is Copyright 1993, David Ljung.
 * Please read the file "docs/0.COPYRIGHT"
 * This version of this software is free for distribution
 * Do not distribute any parts of this package without the documentation.
 * Read the docs before attempting to use this software!
 *
 *      David Ljung
 *      ljung@cae.wisc.edu  (until 8/94)
 *
 *           Permanent Address:            Current (till 8/94):
 *           2257 Clover Lane              310 South Bassett
 *           Geneva, IL. 60134             Madison, WI. 53703
 *           (708) 232-4987                (608) 257-COWS
 *    
 */


#include "wizshell.h"
/*  all should use putenv now
#if defined(NeXT)  /* some machines don't have putenv()!  yeesh. */
/* procedure is declared in wizshell.h */
/*
#include "putenv.c"
#endif
*/

/* GLOBAL VARS */
envir_t envir;
acc_t *read_acc, *write_acc;
acc_t *wd_default, *can_read, *non_file_args;
alias_t *aliases;
int DEBUGON=0;


/* set_prompt
 */
void set_prompt(str)
  char *str;
{
  char *tmp,*pathat;

  tmp=str;
  pathat=NULL;
  while(*tmp!='\0')
    if(*(tmp++)=='%' && *tmp=='/') {
      pathat=tmp-1;
      break;
    }
  FREE(envir.prompt);
  if(envir.prompt2) FREE(envir.prompt2);
  envir.prompt2=NULL;
  if(pathat) {
    MK_STR(envir.prompt,pathat-str+1)
    strncpy(envir.prompt,str,pathat-str);
    envir.prompt[pathat-str]='\0';
    if(envir.prompt2) FREE(envir.prompt2);
    pathat+=2;
    MK_STR(envir.prompt2,strlen(pathat)+1)
    strcpy(envir.prompt2,pathat);
    return;
  }

  MK_STR(envir.prompt,strlen(str)+1)
  strcpy(envir.prompt,str);
}

/* init_envir
 * sets up environment for start of shell
 */
void init_envir(interactive)
  int interactive;
{
  int i;
  char *tmp;
  FILE *rcfp;

  set_prompt("WizShell] ");
  strcpy(WD,envir.home); /* start in home */

  /* do real environment stuff */
  wshputenv("MAIL=");
  MK_STR(tmp,6+BIN_DIR_LEN)
  sprintf(tmp,"PATH=%s",BIN_DIR);
  wshputenv(tmp);
  MK_STR(tmp,12+BIN_DIR_LEN);
  sprintf(tmp,"EDITOR=%s/vi",BIN_DIR);
  wshputenv(tmp);
  MK_STR(tmp,6+strlen(WD))
  sprintf(tmp,"HOME=%s",WD+1);
  wshputenv(tmp);
  MK_STR(tmp,6+strlen(envir.login))
  sprintf(tmp,"USER=%s",envir.login);
  wshputenv(tmp);
  MK_STR(tmp,9+strlen(envir.login))
  sprintf(tmp,"LOGNAME=%s",envir.login);
  wshputenv(tmp);
  MK_STR(tmp,11+strlen(envir.login))
  sprintf(tmp,"WSHLOGIN=%s",envir.login);
  wshputenv(tmp);
  MK_STR(tmp,8+strlen(WSH_FNAME))
  sprintf(tmp,"SHELL=%s",WSH_FNAME);
  wshputenv(tmp);

  if(!cWD(0,interactive)) /* cd to home */
    FATAL("Could not change to home directory")

  /* do history stuff */
  envir.command_num=0;
  envir.current_hist=0;
  envir.history_size=0;
  envir.hist=NULL;
  set_history_size(START_HIST_SIZE);

  /* alias stuff */
  aliases=NULL;

  /* read access files and exceptions */
  get_acc(&write_acc,WRITE_ACC_FILE,envir.access);
  get_acc(&read_acc,READ_ACC_FILE,envir.access);
  get_acc(&wd_default,EXCEPTIONS_FILE,"WD_DEFAULT");
  get_acc(&can_read,EXCEPTIONS_FILE,"CAN_READ");
  get_acc(&non_file_args,EXCEPTIONS_FILE,"NON_FILE_ARGS");

  /* now that we have access, source .wshrc */
  source("/.wshrc.global",0);
  source(".wshrc",1);
}

int cWD(arg,dopwd)
  char *arg;
  int dopwd;
{
  char *tmp,*tmp2;
  struct stat status;
  int result;

  if (!arg) {  /* cd to home */
    if(!(tmp2=getenv("HOME"))) {
      fprintf(stderr,"cd: Can't change to home directory (not set).\n");
      return 0;
    } else {
    MK_STR(tmp,ROOT_DIR_LEN+strlen(tmp2)+3)
    strcpy(tmp,ROOT_DIR);
    *(tmp+ROOT_DIR_LEN-1)='/';
    strcpy(tmp+ROOT_DIR_LEN,tmp2);
    }
  } else tmp=mk_path(arg);

  if(arg) tmp2=arg; else tmp2=tmp+ROOT_DIR_LEN-1;
  if((result=lstat(tmp,&status))==-1) {
    if(errno==ENOTDIR) fprintf(stderr,"Directory doesn't exist\n"); else
    if(errno==ENOENT) fprintf(stderr,"%s: No such file or directory\n",tmp2);else
    if(errno==EACCES) fprintf(stderr,"%s unreadable\n",tmp2);else
    if(errno==ELOOP) fprintf(stderr,"Too many symbolic links\n"); else
    if(errno==ENAMETOOLONG) fprintf(stderr,"Filename too long\n"); else
    fprintf(stderr,"Error in reading path: %s\n",tmp2);
    FREE(tmp);
    return 0;
  }

  if(status.st_mode & S_IFDIR) {
    strcpy(WD,(tmp+ROOT_DIR_LEN-1));
    if(*WD=='/' && *(WD+1)=='\0') *WD='\0';
    if(dopwd)
      if(*WD) fprintf(stderr,"%s\n",WD);
      else fprintf(stderr,"/\n");
#ifndef FROM_ROOT
    chdir(tmp);
#endif
    return 1;
  }
  fprintf(stderr,"%s: Not a directory\n",arg);
  FREE(tmp);
  return 0;
}

/* sources a file.  returns 0 if ok, else -1 if permission denied, 1 if no file
 */
int source(what,ck_acc)
  char *what;
  int ck_acc;  /* check access? */
{
  char *tmp,*tmp2;
  FILE *fp;

  MK_STR(tmp,strlen(what)+1)
  strcpy(tmp,what);
  if(!(tmp=mk_path(tmp))) return; /* mk_path needs to be able to free its arg */
  tmp2=ref_from(tmp);
  FREE(tmp);

  if(ck_acc && !valid_path(write_acc,tmp2) && !valid_path(read_acc,tmp2))
    return -1;
  if(!(fp=fopen(tmp2,"r"))) return 1;

  while(parse_line(0,fp,0));
  fclose(fp);
  return 0;
}

/* justfile
 * looks for '..' and '/' (for /bin/command name and -arglists)
 */
int justfile(com)
  char *com;
{
  while(*com!='\0') {
    if(*com=='/' || (*com=='.' && *(com+1)=='.')) return 0;
    com++;
  }
  return 1;
}

/* str_add
 * copy "add" to end of str up to '\0'
 * CHANGE TO MACRO??
 */
void str_add(str,add)
  char *str,*add;
{
  str+=strlen(str);
  while(*str++=*add++);
}


/* eat_white
 * return pointer to next non-white space
 */
char *eat_white(str)
  char *str;
{
  while(*str==' ' || *str=='\t')
    str++;
  return str;
}

/* do_comm
 * forks and executes the command
 */
int do_comm(comm)
  char *comm[MAX_ARGS];
{
  int pid,result,stat_loc[1];
  struct stat status;

  if((result=lstat(comm[0],&status))==-1) {
    ERROR(comm[0]+BIN_DIR_LEN,"Command not found")
    return;
  }
  pid=fork();
  if (pid==-1) FATAL("Error when forking")
  if (pid) {
    errno=0;
    while(wait(stat_loc)==-1 && errno==EINTR) ; /* if interrupt, wait again */
/*      fprintf(stderr,"%d",*stat_loc);
 *
 *     if(errno==EINTR) fprintf(stderr,"Interrupted\n");
 *     FATAL("Error waiting for child process")
 *   }
 */
  } else {
if(DEBUGON)fprintf(stderr,"command to exec [%s]\n",comm[0]);
    execv(comm[0],comm);
    comm[0]+=BIN_DIR_LEN;
    switch(errno) {
      case EACCES: ERROR(comm[0],"Permission denied") break;
      case ELOOP: ERROR(comm[0],"Too many levels of symbolic links") break;
      case ENOEXEC: ERROR(comm[0],"Not an executable") break;
      default: ERROR(comm[0],"Could not execute") break;
    }
    exit(1);   /* FATAL */
  }
}

/* mk_comm
 * takes a string of tokens with whitespace and makes an array of tokens
 */
int mk_comm(buf,commh)
  char buf[];
  char ***commh;
{
  char **comm;
  char *tmp=buf,*start,*end,ch;
  int i=0,len=0,args=0;

  if(*buf==EOF) return 0;
  /* estimate (max) number of args and kill \r,\n and # */
  while(*tmp!='\0') {
    tmp=eat_white(tmp);
    if(*tmp!='\r' && *tmp!='\n' && *tmp!='#' && *tmp!='\0') {
      args++;
      while(*tmp!=' ' && *tmp!='\t' && *tmp!='\r' &&
            *tmp!='\n' && *tmp!='#' && *tmp!='\0') tmp++;
    }
    if(*tmp=='\r' || *tmp=='\n' || *tmp=='#') *tmp='\0';
  }

  if((comm=(char **) CALLOC(args+2,sizeof(char *)))==NULL)
    FATAL("mk_comm:  Ran out of memory")

  tmp=buf;
  i=0;
  /* Build command array */
  tmp=eat_white(tmp);
  while(*tmp!='\0') {
    start=tmp;
    if(*tmp!='"' && *tmp!='\'' && *tmp!='`' && *tmp!='(') {
      while(*tmp!=' ' && *tmp!='\t' && *tmp!='\0') tmp++;
      end=tmp;
    } else {
      ch=*tmp++;
      if(ch=='(') ch=')';
      start++;
      while(*tmp!=ch && *tmp++!='\0');
      if(*tmp!=ch) {
        fprintf(stderr,"Unmatched %c.\n",ch);
        FREE(comm); return 0;
      }
      end=tmp++;
    } /* parsed quoted string */
    len=end-start+1;
    MK_STR(comm[i],len)
    strncpy(comm[i],start,len-1);
    *(comm[i]+len)='\0';
    i++;
    tmp=eat_white(tmp);
  } /* end while */

  comm[i]=NULL;
  *commh=comm;
  return 1;
}

/* parse_line
 * returns 0 at "quit"
 */
int parse_line(interactive,fd,thisline)
  int interactive;
  FILE *fd;
  char *thisline;
{
  char buffer[MAX_BUF+1],num[10],*line;
  char **comm,**commuse;
  int i,result=0,numargs,numfileargs=0,needtofree=0,*file_args;
  acc_t *acc;

/* this is where the interrupt gets noticed:
  if(envir.gotintr!=-1) fprintf(stderr,"Got a user interrupt!\n");
*/

  if(interactive)
    if(envir.prompt2)
      if(WD[0]!='\0') fprintf(stdout,"%s%s%s",envir.prompt,WD,envir.prompt2);
      else fprintf(stdout,"%s/%s",envir.prompt,envir.prompt2);
    else fprintf(stdout,"%s",envir.prompt);
  if(thisline)
    if(line=replaces(thisline))
      needtofree=1;
    else
      line=thisline;
  else {  /* not thisline */
    clearerr(fd);  /* reset ferror() & feof() */
    errno=0;
    while(fgets(buffer,MAX_BUF,fd)==NULL && errno==EINTR) ;
    if(feof(fd)) {
      if (!interactive) return 0;
      if(envir.loopy_eofs++>0) return 0;
      fprintf(stdout,"\nUse \"logout\" to logout\n");
      clearerr(fd);  /* reset feof() */
      if(feof(fd)) {
        fprintf(stderr,"Agh!  Your clearerr() library call doesn't work!\n");
        fprintf(stderr,"Sorry, but I gotta die.\nLog in again.\n");
        return 0;
      }
      return 1;
    } else
      envir.loopy_eofs=0;

    if(line=replaces(buffer))
      needtofree=1;
    else
      line=buffer;
  }

  if(catchline(line)) return 1; /* catchline adds to history */

  i=mk_comm(line,&comm);
  if(needtofree) FREE(line);
  if(!i || !comm[0]) return 1;

  i=1;
if(DEBUGON)
  while(comm[i])
    fprintf(stderr,"%d: [%s]\n",i,comm[i++]);

  result=catchcomm(comm,interactive);

  if(result==-1) return 0; /* shell is done */
  if(result==2) return 1;  /* caught - don't add to history */

  if(envir.history_size) add_history(comm);
  if(result==1) return 1;  /* caught */

  numargs=1; i=0;
  while(comm[i++])
    numargs++;

  if((commuse=(char **) CALLOC(numargs+3,sizeof(char *)))==NULL)
    FATAL("couldn't make commuse array")
  if((file_args=(int *) CALLOC(numargs+2,sizeof(int)))==NULL)
    FATAL("couldn't make file_args array")

  MK_STR(commuse[0],BIN_DIR_LEN+3+strlen(comm[0]))
  strcpy(commuse[0],BIN_DIR);
  *(commuse[0]+BIN_DIR_LEN-1)='/';
  strcpy(commuse[0]+BIN_DIR_LEN,comm[0]);
  if(!justfile(commuse[0]+BIN_DIR_LEN)) {
    ERROR(commuse[0]+BIN_DIR_LEN,"Cannot have '..' or '/' in command name");
    FREE(commuse); FREE(comm);  return 1;
  }
if(DEBUGON)fprintf(stderr,"COMMAND: %s\n",commuse[0]);

  i=1;
  while(comm[i]) {
    if(*comm[i]=='-') {
      MK_STR(commuse[i],strlen(comm[i])+1)
      strcpy(commuse[i],comm[i]);
      file_args[i]=0;
if(DEBUGON)fprintf(stderr,"FLAGS: %d [%s]\n",i+1,commuse[i]);
      i++;
      continue;
    }
    if(acc=find_token(non_file_args,comm[0])) {
      sprintf(num,"%d",i);
      if(!acc->down || find_token(acc,num)) {
        MK_STR(commuse[i],strlen(comm[i])+1)
        strcpy(commuse[i],comm[i]);
        file_args[i]=0;
if(DEBUGON)fprintf(stderr,"USING(not file): %d [%s]\n",i+1,commuse[i]);
        i++;
        continue;
      }
    }
    if(!(comm[i]=mk_path(comm[i]))) return 1;  /* bad path */
    commuse[i]=ref_from(comm[i]);
    file_args[i]=1;
    numfileargs++;
if(DEBUGON)fprintf(stderr,"USING: %d [%s]\n",i+1,commuse[i]);
    i++;
  }

  numargs--;
  if(!numfileargs && find_token(wd_default,comm[0])) {
if(DEBUGON)fprintf(stderr,"No file args.  Using WD\n");
    MK_STR(comm[numargs],ROOT_DIR_LEN+strlen(WD)+2)
    strcpy(comm[numargs],ROOT_DIR);
    if(*WD) str_add(comm[numargs],WD);
    MK_STR(commuse[numargs],strlen(WD)+2);
    if(*WD) strcpy(commuse[numargs],WD+1);
    else strcpy(commuse[numargs],".");
    file_args[numargs]=1;
    commuse[++numargs]=0;
    comm[numargs]=0;
  }

  if(ck_access(comm,file_args)) do_comm(commuse);

  i=0;
  while(comm[i]) FREE(comm[i++]);
  i=0;
  while(commuse[i]) FREE(commuse[i++]);
  FREE(comm);  FREE(commuse);  FREE(file_args);

  return 1;
}

/*
void handlebreak(sig) 
  int sig;
{
  signal(sig,SIG_DFL);
  signal(sig,SIG_IGN);
  fprintf(stderr,"got ctrl-c\n");
  signal(SIGINT,handlebreak);
  parse_line(1,stdin,0);
}
*/

/* This will be used in the future (as well as SIGUSR2) */
void usersig(sig)
  int sig;
{
  signal(sig,SIG_DFL);  /* reset */
  signal(sig,SIG_IGN);  /* ignore until done */
  /* you could set a flag here and then check at the beginning of parse_line */
  signal(sig,usersig);
}

 
/* Print the usage error message and exit */
void usage() 
{
  fprintf(stderr,"usage: wsh [-i|-l] [sourcefile]\n");
  fprintf(stderr,"  where options are:\n");
  fprintf(stderr,"  -i          interactive shell (default)\n");
  fprintf(stderr,"  -c          (single) following argument is command\n");
  fprintf(stderr,"  -l          login ");
    fprintf(stderr,"(otherwise use WSHLOGIN environment var\n");
  fprintf(stderr,"  sourcefile  file to take commands from ");
    fprintf(stderr,"(must be logged in)\n\n");
  exit(-1);  /* fatal error */
}


/* Parse the options and arguments as seen in usage() */
void parse_args(argc,argv,envp,interactive,getlogin,fh,line)
  int argc;
  char **argv, **envp;
  int *interactive,*getlogin;
  FILE **fh;
  char **line;
{
  int flag,algpos;

  *interactive=1; /* default to interactive */
  *getlogin=0;
  *fh=stdin;      /* default to stdin */
  if(argc==1) return;
  if(argc>3) usage();
  if(argv[1][0]=='-') { /*flags*/
    if(argv[1][1]=='i') *interactive=1; /* default anyways :-) */
    else if(argv[1][1]=='l') *getlogin=1;
    else if(argv[1][1]=='c') { 
      if(argc!=3) usage();
      *line=argv[2];
      *interactive=0;
    }
    else usage(); /* no other flags */
  } else  { /* if not flag, assume source file */
    *interactive=0;
    *getlogin=0;
    if (!(*fh=fopen(argv[1],"r"))) {
      fprintf(stderr,"Could not open file: %s\n",argv[2]);
      usage();
    }
  }
}


/* main
 * do it.
 */
int main(argc, argv, envp)
  int argc;
  char **argv, **envp;
{
  acc_t *tmp;
  int interactive,getlogin,u;
  FILE *fp;
  char *line=0;  /* for shell escapes */

#ifdef FROM_ROOT
  if(chdir(ROOT_DIR)) FATAL("Could not find mud root directory");
#endif

  parse_args(argc,argv,envp,&interactive,&getlogin,&fp,&line);
#ifdef MAX_USERS
  if((u=count_users())>=MAX_USERS) {
    fprintf(stdout,"Too many logins.  Try again later.  Current users:\n",u);
    free_who(print_utmp(read_utmp(0),0));
    return;
  }
  if(u) fprintf(stdout,"Number of users: %d/%d\n",u,MAX_USERS);
  else fprintf(stdout,"No users.  (Maximum: %d)\n",MAX_USERS);
#endif
  if(!login(getlogin)) return;

  if(getlogin) add_uwtmp();

  init_envir(interactive);

  if(interactive) {
    source("/.login.global",0);
    source(".login",1);
  }

  signal(SIGINT, SIG_IGN);
  /* This will be used later, as well as SIGUSR2 */
  signal(SIGUSR1, usersig);
  if(line) parse_line(interactive,0,line);
  else while(parse_line(interactive,fp,0));
  fclose(fp);
  signal(SIGINT, SIG_DFL); 

  if(getlogin) source("~/.logout",1);
  if(interactive) fprintf(stderr,"Exiting WizShell.\n");
}