
/********************************************
files.c
copyright 1991, 1992.  Michael D. Brennan

This is a source file for mawk, an implementation of
the AWK programming language.

Mawk is distributed without warranty under the terms of
the GNU General Public License, version 2, 1991.
********************************************/

/*$Log: files.c,v $
 * Revision 5.5  1992/12/17  02:48:01  mike
 * 1.1.2d changes for DOS
 *
 * Revision 5.4  1992/07/10  16:10:30  brennan
 * patch2
 * MsDOS: remove useless NO_BINMODE macro
 * get process exit code on in pipes
 *
 * Revision 5.3  1992/04/07  20:21:17  brennan
 * patch 2
 * unbuffered output to a tty
 *
 * Revision 5.2  1992/04/07  16:03:08  brennan
 * patch 2
 * allow same filename for output and input, but use different descriptors
 * E.g. < "/dev/tty" and > "/dev/tty"
 *
 * Revision 5.1  91/12/05  07:56:00  brennan
 * 1.1 pre-release
 * 
*/

/* files.c */

#include "mawk.h"
#include "files.h"
#include "memory.h"
#include "fin.h"

static FILE  *PROTO(tfopen, (char*,char*)) ;
static  void  PROTO(add_to_child_list, (int,int)) ;
static struct child *PROTO(remove_from_child_list, (int)) ;
extern int PROTO(isatty, (int)) ;

#ifdef  V7
#include  <sgtty.h>    /* defines FIOCLEX */
#endif
  

#if  HAVE_FCNTL_H  

#include <fcntl.h>
#define  CLOSE_ON_EXEC(fd)   (void) fcntl(fd, F_SETFD, 1)

#else
#define  CLOSE_ON_EXEC(fd) ioctl(fd, FIOCLEX, (PTR) 0)
#endif


/* We store dynamically created files on a linked linear
   list with move to the front (big surprise)  */

typedef struct file {
struct file *link ;
STRING  *name ;
short type ;
int pid ;  /* we need to wait() when we close a pipe */
           /* holds temp file index under MSDOS */

#if  HAVE_FAKE_PIPES
int inpipe_exit ; 
#endif

PTR   ptr ;  /* FIN*   or  FILE*   */
}  FILE_NODE ;

static FILE_NODE *file_list ;


/* find a file on file_list */
PTR  file_find( sval, type )
  STRING *sval ;
  int type ;
{ register FILE_NODE *p = file_list ;
  FILE_NODE *q = (FILE_NODE *) 0 ;
  char *name = sval->str ;
  char *ostr ;

  while (1)
  {
    if ( !p )   /* open a new one */
    {
      p = ZMALLOC(FILE_NODE) ; 
      switch( p->type = type )
      {
        case  F_TRUNC :
#if MSDOS 
            ostr = (binmode()&2) ? "wb" : "w" ;
#else
            ostr = "w" ;
#endif
            if ( !(p->ptr = (PTR) tfopen(name, ostr)) )
                goto out_failure ;
            break ;

        case  F_APPEND :
#if MSDOS 
            ostr = (binmode()&2) ? "ab" : "a" ;
#else
            ostr = "a" ;
#endif
            if ( !(p->ptr = (PTR) tfopen(name, ostr)) )
                goto out_failure ;
            break ;

        case  F_IN  :
            if ( !(p->ptr = (PTR) FINopen(name, 0)) )
            { zfree(p, sizeof(FILE_NODE)) ; return (PTR) 0 ; }
            break ;

        case  PIPE_OUT :
        case  PIPE_IN :

#if    HAVE_REAL_PIPES || HAVE_FAKE_PIPES 

            if ( !(p->ptr = get_pipe(name, type, &p->pid)) )
                if ( type == PIPE_OUT ) goto out_failure ;
                else
                { zfree(p, sizeof(FILE_NODE) ) ;
                  return (PTR) 0 ;
                }
#else
         rt_error("pipes not supported") ;
#endif
            break ;

#ifdef  DEBUG
        default :
            bozo("bad file type") ;
#endif
      }
      /* successful open */
      p->name = sval ;
      sval->ref_cnt++ ;
      break ; /* while loop */
    }

    /* search is by name and type */
    if ( strcmp(name, p->name->str) == 0 &&
	 ( p->type == type ||
           /* no distinction between F_APPEND and F_TRUNC here */
	   p->type >= F_APPEND && type >= F_APPEND ))

    { /* found */
      if ( !q )  /*at front of list */
          return  p->ptr ;
      /* delete from list for move to front */
      q->link = p->link ;
      break ; /* while loop */
    }
    q = p ; p = p->link ;
  } /* end while loop */

  /* put p at the front of the list */
  p->link = file_list ;
  return  (PTR) (file_list = p)->ptr ;

out_failure:
  errmsg(errno, "cannot open \"%s\" for output", name) ;
  mawk_exit(1) ;

}


/* Close a file and delete it's node from the file_list.
   Walk the whole list, in case a name has two nodes,
   e.g. < "/dev/tty" and > "/dev/tty"
*/

int  file_close( sval )
  STRING *sval ;
{ register FILE_NODE *p = file_list ;
  FILE_NODE *q = (FILE_NODE *) 0 ; /* trails p */
  FILE_NODE *hold ;
  char *name = sval->str ;
  int retval = -1 ;

  while ( p )
        if ( strcmp(name,p->name->str) == 0 ) /* found */
        { 
          switch( p->type )
          {
            case  F_TRUNC :
            case  F_APPEND :    
                (void) fclose((FILE *) p->ptr) ;
		retval = 0 ;
                break ;

            case  PIPE_OUT :
                (void) fclose((FILE *) p->ptr) ;

#if  HAVE_REAL_PIPES
                retval =  wait_for(p->pid) ;
#endif
#if  HAVE_FAKE_PIPES
                retval = close_fake_outpipe(p->name->str,p->pid) ;
#endif
                break ;

            case F_IN  :
                FINclose((FIN *) p->ptr) ;
		retval = 0 ;
                break ;

            case PIPE_IN :
                FINclose((FIN *) p->ptr) ;

#if  HAVE_REAL_PIPES
                retval = wait_for(p->pid) ;
#endif
#if  HAVE_FAKE_PIPES
	      { 
		char xbuff[100] ;
                (void) unlink(tmp_file_name(p->pid,xbuff)) ;
		retval = p->inpipe_exit ;
	      }
#endif
                break ;
          }

          free_STRING(p->name) ;
	  hold = p ;
          if ( q )  q->link = p = p->link ;
          else  file_list = p = p->link ;

          ZFREE(hold) ;
        }
        else { q = p ; p = p->link ; }

  return retval ;
}

/* When we exit, we need to close and wait for all output pipes */


#if   HAVE_REAL_PIPES

void close_out_pipes()
{ register FILE_NODE *p = file_list ;

  while ( p )
  { if ( p->type == PIPE_OUT )
    { (void) fclose((FILE *) p->ptr) ;  (void) wait_for(p->pid) ; }
    p = p->link ;
  }
}

#else
#if  HAVE_FAKE_PIPES  /* pipes are faked with temp files */

void  close_fake_pipes()
{ register FILE_NODE *p = file_list ;
  char xbuff[100] ;

  /* close input pipes first to free descriptors for children */
  while ( p )
  {
    if ( p->type == PIPE_IN )
    { FINclose((FIN *) p->ptr) ;
      (void) unlink(tmp_file_name(p->pid,xbuff)) ; 
    }
    p = p->link ;
  }
  /* doit again */
  p = file_list ;
  while ( p )
  {
    if ( p->type == PIPE_OUT )
    {
      (void) fclose(p->ptr) ;
      (void) close_fake_outpipe(p->name->str,p->pid) ;
    }
    p = p->link ;
  }
}
#endif /* HAVE_FAKE_PIPES */
#endif /* ! HAVE_REAL_PIPES */

/* hardwire to /bin/sh for portability of programs */
char *shell = "/bin/sh" ;

#if  HAVE_REAL_PIPES

PTR get_pipe( name, type, pid_ptr)
  char *name ;
  int type ;
  int *pid_ptr ;
{ int the_pipe[2], local_fd, remote_fd ;

  if ( pipe(the_pipe) == -1 )  return (PTR) 0 ;
  local_fd = the_pipe[type == PIPE_OUT] ;
  remote_fd = the_pipe[type == PIPE_IN ] ;
  /* to keep output ordered correctly */
  fflush(stdout) ; fflush(stderr) ;

  switch( *pid_ptr = fork() )
  { case -1 :  
      (void) close(local_fd) ;
      (void) close(remote_fd) ;
      return (PTR) 0 ;

    case  0 :
        (void) close(local_fd) ;
        (void) close(type == PIPE_IN) ;
        (void) dup( remote_fd ) ;
        (void) close( remote_fd ) ;
        (void) execl(shell, shell, "-c", name, (char *) 0 ) ;
        errmsg(errno, "failed to exec %s -c %s" , shell, name) ;
        fflush(stderr) ;
        _exit(128) ;

    default :
        (void) close(remote_fd) ;
        /* we could deadlock if future child inherit the local fd ,
           set close on exec flag */
        CLOSE_ON_EXEC(local_fd) ;
        break ;
  }

  return  type == PIPE_IN ? (PTR) FINdopen(local_fd, 0) : 
                            (PTR)  fdopen(local_fd, "w")  ;
}
  


/*------------ children ------------------*/

/* we need to wait for children at the end of output pipes to
   complete so we know any files they have created are complete */

/* dead children are kept on this list */

static struct child {
int pid ;
int exit_status ;
struct child *link ;
}  *child_list ;

static  void  add_to_child_list(pid, exit_status)
  int pid, exit_status ;
{ register struct child *p = 
          (struct child *) zmalloc(sizeof(struct child)) ;

  p->pid = pid ; p->exit_status = exit_status ;
  p->link = child_list ; child_list = p ;
}

static struct child *remove_from_child_list(pid)
  int pid ;
{ register struct child *p = child_list ;
  struct child *q = (struct child *) 0 ;

  while ( p )
    if ( p->pid == pid )
    {
        if ( q ) q->link = p->link ;
        else child_list = p->link ;
        break ;
    }
    else { q = p ; p = p->link ; }

  return p ;  /* null return if not in the list */
}
    

/* wait for a specific child to complete and return its 
   exit status 

   If pid is zero, wait for any single child
*/

int wait_for(pid)
  int pid ;
{ int exit_status ;
  struct child *p ;
  int id ;

  if ( pid == 0 )
  {
    id = wait(&exit_status) ;
    add_to_child_list(id, exit_status) ;
  }
  else
  /* see if an earlier wait() caught our child */
  if ( p = remove_from_child_list(pid) ) 
  { exit_status = p->exit_status ;
    ZFREE(p) ;
  }
  else /* need to really wait */
    while ( (id = wait(&exit_status)) != pid )
        if ( id == -1 ) /* can't happen */  bozo("wait_for") ;
        else
        { /* we got the exit status of another child
             put it on the child list and try again */
          add_to_child_list(id, exit_status ) ;
        }

  if ( exit_status & 0xff ) 
       exit_status = 128 + (exit_status & 0xff) ;
  else  exit_status = (exit_status & 0xff00)>>8 ;

  return exit_status ;
}
        
#endif  /* HAVE_REAL_PIPES */
void set_stderr()
{
  file_list = ZMALLOC(FILE_NODE) ;
  file_list->link = (FILE_NODE*) 0 ;
  file_list->type = F_TRUNC ;
  file_list->name = new_STRING("/dev/stderr") ;
  file_list->ptr = (PTR) stderr ;
}

/* fopen() but no buffering to ttys */
static FILE *tfopen(name, mode)
  char *name, *mode ;
{
  FILE *retval = fopen(name,mode) ;

  if ( retval )
  {
    if ( isatty(fileno(retval)) )  setbuf(retval, (char*)0) ;
    else
    {
#if  LM_DOS
       enlarge_output_buffer(retval) ;
#endif
    }
  }
  return retval ;
}

#if  LM_DOS
void enlarge_output_buffer( fp )
  FILE *fp ;
{
  if ( setvbuf(fp, (char*) 0, _IOFBF, BUFFSZ) < 0 )
  {
    errmsg(errno, "setvbuf failed on fileno %d", fileno(fp)) ;
    mawk_exit(1) ;
  }
}
#endif

#if  MSDOS
void
stdout_init()
{
#if  LM_DOS
   if ( ! isatty(1) )  enlarge_output_buffer(stdout) ;
#endif
   if ( binmode() & 2 ) { setmode(1,O_BINARY) ; setmode(2,O_BINARY) ; }
}
#endif /* MSDOS */
