/*
 simple shell interpreter for YACC
 $Id: shell.y,v 1.3 97/02/20 04:08:28 kono Exp Locker: kono $
 */

%{

#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/wait.h>
#include <string.h>

void error(char *);
void yyerror(char *);

#define SHELL_DEBUG 0

#define waitpid(pid,a,b) wait4(pid, a, b, 0)


typedef struct node {
    struct node *left;
    struct node *right;
    int type;
    char *value;
    int in,out,err;
} node,*nodeptr;

void free_node(node *d);

int token() ;
int yylex() ;
int in=0, out=1, err=2, arg_count=0;

#define YYSTYPE nodeptr

node *new_node();
void redirect_in(node *,int ,int );
void redirect_out(node *,int ,int );
void redirect_err(node *,int ,int );
void redirect_pipe_in(node *,int ,int ,int );

node * make_file(node *command, char *file, int type);
node * make_pipe(node *left,node *right);
void close_pipes(node *left,node *right);
int search_pipes(node *n,int *pipes,int i);
void find_close_pipes(node *n,int *pipes,int max);
int make_argv(char **com, node *arguments, int i);
char ** make_args(node *command, node *arguments, int arg_count);
void execute(char **com, int in, int out, int err);
void print_node();
char *ptr,*last_ptr,*before;
char *value;
int  last_token;
static void builtin(node * com, node *arg1, node *arg2);

void command( node *d) ;

%}

%left '|' '&'
%left ';'

%%


top : expr { 
	command($1);
	free_node($1);
    }
    ;

expr :  expr '|' expr  { $$ = new_node('|',NULL,$1,$3);  } 
    |   expr '&' expr  { $$ = new_node('&',NULL,$1,$3); }
    |   expr '&'       { $$ = new_node('&',NULL,$1,NULL); }
    |   expr ';' expr  { $$ = new_node(';',NULL,$1,$3); }
    |   expr ';'       { $$ = new_node(';',NULL,$1,NULL); }
    |   expr '>' file  { $$ = make_file($1, $3->value,0); }
    |   expr '<' file  { $$ = make_file($1, $3->value,1); }
    |   expr '>' '>' file  { $$ = make_file($1, $4->value,2); }
    |   file           {$$ = new_node('c',make_args($1,NULL,0),
				      NULL,NULL);}
    |   file           {arg_count=0;} 
        args           {$$ = new_node('c',make_args($1,$3,arg_count),
				      NULL,NULL);}
    |   '(' expr ')'   { $$ = $2; }
    |   '-' file file file { builtin($2,$3,$4); $$ = 0; }
    ;

file  :  'f'      { $$ = new_node('f',value,NULL,NULL); }
args  :  arg      { $$ = $1 ; }
       | args arg { $$ =  new_node('a',NULL,$1,$2);}
arg   :  'f'      { $$ = new_node('a',value,NULL,NULL);arg_count++;}
    ;

%%

node *
new_node( int type, char *value, node *left, node *right) {
    node *d;
    d =  (node *)malloc(sizeof(node));
    d->type = type;
    d->value = value;
    d->left = left;
    d->right = right;
    d->in = 0; d->out = 1; d->err=2;
    return d;
}

void
free_node(node *d) {
    int i;
    if(SHELL_DEBUG) for(i=3;i<32;i++) close(i);
    if (d==0) return;
    if(d->left) {
	free_node(d->left);
    } 
    if(d->right) {
	free_node(d->right);
    } 
    if(d->value) {
	free(d->value);
    }
    free(d);
}

void
command( node *d) {
    int pid;
    int pfd[2];
    int save_in;

    if(!d) return; 
    switch(d->type) {
    case 'c':
      execute((char **)d->value, d->in, d->out, d->err);
      return;
    case '|':
      pipe(pfd);
      if((pid = fork())){
        save_in = dup(0);
        dup2(pfd[0],0);
	close(pfd[1]);
	command(d->right);
  if(SHELL_DEBUG) fprintf(stderr,"waiting %d\n",pid);
        dup2(save_in,0);
	waitpid(pid,NULL,0);
  if(SHELL_DEBUG) fprintf(stderr,"done %d\n",pid);
	return;
      }else{
  if(SHELL_DEBUG) fprintf(stderr,"forked pid %d\n",getpid());
        dup2(pfd[1],1);
	close(pfd[0]);
	command(d->left);
        exit(0);
      }
    case '&':
      if((pid = fork())){
	command(d->left);
  if(SHELL_DEBUG) fprintf(stderr,"waiting %d\n",pid);
	waitpid(pid,NULL,0);
  if(SHELL_DEBUG) fprintf(stderr,"done %d\n",pid);
	return;
      }else{
  if(SHELL_DEBUG) fprintf(stderr,"forked pid %d\n",getpid());
	command(d->right);
        exit(0);
      }
    case ';':
      command(d->left);
      command(d->right);
      return;
    default:
      fprintf(stderr,"error on %c\n",d->type);
      command(d->left);
      command(d->right);
      return;
    }
}

node * 
make_file(node *command, char *file, int type) {
  
  int d = 0;
  
  if(type == 0 || type == 2){
    if(type == 0)
      d = open(file, O_CREAT | O_WRONLY | O_TRUNC, 0666);
    else if(type == 2)
      d = open(file, O_CREAT | O_WRONLY | O_APPEND, 0666);
    redirect_out(command,command->out,d);
  }else{
    d = open(file, O_RDONLY);
    redirect_in(command,command->in,d);
  }
  return(command);
}

  /*
    もし、fork したプロセスの中で、パイプの出力をちゃんと閉じてない
    ものがあると、そのパイプを入力とするプロセスはEOFをもらえずに
    止まってしまう。O_FNDELAY をfcntl すれば止まらなくなるが、
    最初のデータを読み落としたりするので良くない。

    ちゃんとしたclose をおこなえば、O_FNDELAYを使う必要はない。

    make_pipe() という形で、parse時にpipeを作ると、pipeの入り口を
    不用意に増やしてしまう。そうすると、それをすべて閉じるのが
    難しい。したがって、redirectと異なり、pipe はcommand() で
    生成する。
   */

char **
make_args(node *command, node *arguments, int arg_count) {

  int i=0;
  char **com;

  com = (char **) calloc(arg_count+2, sizeof(char *));

  com[0] = command->value;
  i=make_argv(com,arguments,1);
  com[i]=NULL;
  return(com);
}

int
make_argv(char **com, node *arguments, int i) {
   while(arguments) {
     if(arguments->type == 'a' && arguments->value){
       com[i] = arguments->value;
       i++;
     }
       i = make_argv(com, arguments->left, i);
       arguments = arguments->right;   /* tail recursion 末尾再帰 */
   }
   return i;
}

void
redirect_in(node *n,int old,int new) {
   while(n) {
     if(n->type == '|') {
       n = n->left; /* a | b の b の入力は変更しない */
       continue;
     }
     if(n->in == old){
       n->in = new;
     }
     redirect_in(n->left,old,new);
     n = n->right;   /* tail recursion 末尾再帰 */
   }
}


void
redirect_err(node *n,int old,int new)
{
   while(n) {
     if(n->err == old){
       n->err = new;
     }
     redirect_err(n->left,old,new);
     n = n->right;   /* tail recursion 末尾再帰 */
   }
}

void
redirect_out(node *n,int old,int new)
{
   while(n) {
     if(n->type == '|') {
       n = n->right; /* a | b の a の出力は変更しない */
       continue;
     }
     if(n->out == old){
       n->out = new;
     }
     redirect_out(n->left,old,new);
     n = n->right;   /* tail recursion 末尾再帰 */
   }
}


void 
execute(char **com, int in, int out, int err)
{
  int pid;
  int i;

  if((pid=fork())) {		
  if (SHELL_DEBUG) fprintf(stderr,"waiting %d\n",pid);
    waitpid(pid,NULL,0);
  if (SHELL_DEBUG) fprintf(stderr,"done %d\n",pid);
    if(in  != 0) { close(in);}
    if(out != 1) { close(out);}
    if(err != 2) { close(err);}
  } else {
fprintf(stderr,"process %d command %s waiting:", getpid(), *com);
    if(in  != 0) { if(dup2(in,  0)== -1) perror("dup2"); }
    if(out != 1) { if(dup2(out, 1)== -1) perror("dup2"); }
    if(err != 2) { if(dup2(err, 2)== -1) perror("dup2"); }
    /* 同じdescriptor があった時に前のものをcloseしてしまうので 2 phase
       でcloseする必要がある */
    if(in  != 0) { close(in);}
    if(out != 1) { close(out);}
    if(err != 2) { close(err);}
  if (SHELL_DEBUG) fprintf(stderr,"executing %s %d %d %d pid %d\n",com[0],in,out,err,getpid());
    if(0) for(i=3;i<32;i++)close(i);
    execvp(com[0], &com[0]);
    perror(com[0]);
    _exit(0);
  }
}

int
yylex() {
    return token();
}

void 
yyerror(char *s) {
    error(s?s:"error");
}

void
error(char *s) {
    fprintf(stderr,"%s on %s\n",s,last_ptr);
}


int
token() {
    int c,i;
    int length;
    char *file;

    last_ptr = ptr;  /* for error position */
    while((c = *ptr++)<=' '&& c);
    if(!c) {
	last_token = EOF;
	return last_token;
    }
    /* printf("*%c* ",c);  */
    if (c=='#') {       /* comment */ 
	while(*ptr++);
	ptr--;
	last_token = EOF;
	last_ptr = ptr;
	return last_token;
    } else if('-' == c) {
	last_token = c;
	value = NULL;
	return last_token;
    } else if(('a' <= c && c <= 'z') || ('A' <=c && c <= 'Z') || 
	       ('*'<= c && c <= ':') || '~'== c){
        file = ptr - 1;
        while (('a' <= c && c <= 'z') || ('A' <=c && c <= 'Z') ||
               ('*' <= c && c <= ':') || '~' == c) c = *ptr++;
	ptr--;
	length = ptr - file;
	value = (char *)malloc(length+1);
	for(i=0;i<length;i++) value[i]=file[i];
	value[i]='\0';
	last_token = 'f';
	return last_token;
    } else {
	last_token = c;
	value = NULL;
	return last_token;
    }
}

static void
builtin(node * com, node *arg1, node *arg2) {
    printf("buildin command %s %s %s\n", com->value, arg1->value, arg2->value);
    if ( com->value[0]=='k') {
	// signal
	int pid = atoi(arg2->value);
	int sig = atoi(arg1->value);
	printf("kill(%d,%d);\n", pid, sig);
    }
}

int
main() {
    char buf[BUFSIZ];

    for(;;) {
        printf("%s", "% ");
	if(fgets(buf, sizeof(buf), stdin)==NULL) break;
	if(strlen(buf) == 1) continue;
	ptr = buf;
	before = buf;
	yyparse();
    }
    return 0;
}

/* end */
