#ifndef lint
static char *RCSid = "$Id: shell.c,v 1.14 1993/05/10 06:11:51 anders Exp anders $";
#endif

/*
 *  The Regina Rexx Interpreter
 *  Copyright (C) 1992-1994  Anders Christensen <anders@pvv.unit.no>
 *
 *  This library is free software; you can redistribute it and/or
 *  modify it under the terms of the GNU Library General Public
 *  License as published by the Free Software Foundation; either
 *  version 2 of the License, or (at your option) any later version.
 *
 *  This library is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 *  Library General Public License for more details.
 *
 *  You should have received a copy of the GNU Library General Public
 *  License along with this library; if not, write to the Free
 *  Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 */

/*
    This code modified for Win32 port by Ataman Software, Inc. June 29, 1995.
*/

#include "rexx.h"
#include <limits.h>
#include <stdio.h>
#ifdef VMS
# include <time.h>
#elif defined(WIN32)
# include <time.h>
#else
# include <unistd.h>
# include <sys/wait.h>
# include <sys/time.h>
# include <fcntl.h>
#endif
#include <string.h>
#include <ctype.h>
#include <errno.h>
#include <assert.h>
#include <signal.h>

/*
 #if defined(CRAY)
 FILE *fdopen( int fd, char *access ) ;
 #endif
*/

extern char *environments[] ;


void destroyargs( char **args )
{
   char **ccptr ;

   ccptr = args ;
   for (; *ccptr; ccptr++)
      Free( (char*)*ccptr ) ;

   Free( args ) ;
}


int getenvir( streng *envir )
{
   if (envir->len==4 && !strncmp( envir->value, "PATH", 4 ))
      return SUBENVIR_PATH ;
   else if (envir->len==7 && !strncmp( envir->value, "COMMAND", 7 ))
      return SUBENVIR_COMMAND ;
   else 
      return SUBENVIR_SYSTEM ;
}

char **makeargs( streng *string ) 
{
   int i, words=0, j, k ;
   char **ccptr ;

   for (i=0; i<Str_len(string); i++)
      words += ((isgraph(string->value[i])) && 
                (i>=string->len || !isgraph(string->value[i+1]))) ;

   ccptr = Malloc((words+2)*sizeof(char*)) ;
   for (j=1,i=0; j<=words; j++)
   {
      for(k=i; i<Str_len(string) && isgraph(string->value[i]); i++) ;
      strncpy(ccptr[j]=Malloc(i-k+1),&string->value[k],i-k) ;
      (ccptr[j])[i-k] = 0x00 ;
      for (; i<Str_len(string) &&(!isgraph(string->value[i])); i++) ;
   }
   ccptr[j] = NULL ;

   ccptr[0] = ccptr[1] ;
   for (i=strlen(ccptr[0])-1; (i>=0)&&((ccptr[0])[i]!='/'); i--) ;
   strcpy( ccptr[1]=Malloc(strlen(&((ccptr[0])[i+1]))+1), &(ccptr[0])[i+1]) ;

   return ccptr ;
}


#if !defined(VMS) && !defined(DOS) && !defined(WIN32)
int posix_do_command( streng *command, int io_flags, int envir )
{
   char **args ;
   int child, rc, i, fdin[2], fdout[2] ;
   char *cmd ; 
   int in, out, fout ;
   streng *rstring=NULL, *string=NULL ;
   streng *istring=NULL ;
   static char *cbuff=NULL ;
   int pipe_buf ;
   int length, rcode, j, flush, status ;
   streng *result ;
   char flags ;
   int count ;

   in = io_flags & REDIR_INPUT ;
   out = io_flags & REDIR_OUTLIFO ;
   fout = io_flags & REDIR_OUTFIFO ;

   command = Str_ify( command ) ;
   flush = (out!=0) + ((fout!=0)*2) ;

   if ((!in)&&(!out)&&(!fout))
   {
      ;       /* maybe this should not be allowed. ... */
   }

   cmd = NULL ;
   if (envir != SUBENVIR_SYSTEM)
   {
      args = makeargs( command ) ;
      cmd = *args ;
   }
   else 
      args = NULL ;

   fflush( stdout ) ;
   fflush( stderr ) ;

   if ((in)&&(pipe( fdin )))     /* for info from stack to child */
      perror("While opening input pipe") ;

   if (((out)||(fout))&&(pipe( fdout )))   /* for info from child to stack */
      perror("While opening output pipe") ;

   if ((child=fork()))
   {
      signal( SIGPIPE, SIG_IGN ) ;

      if (out || fout)
      {
#ifdef PIPE_BUF
         pipe_buf = PIPE_BUF ;
#else
# ifndef _PC_PIPE_BUF
         /* Some vendors just don't get it right ... sigh! The number */
         /* 512 is my 'invention' ... it might not be what you want */
         pipe_buf = 512 ;
# else
         pipe_buf = fpathconf( fdout[0], _PC_PIPE_BUF ) ;
         assert( pipe_buf > 0 ) ;
# endif
#endif
      }

      if (cbuff==NULL && (out||fout))
         cbuff = Malloc( pipe_buf+1 ) ;

      if (child<0)
   	 exiterror( ERR_SYSTEM_FAILURE ) ;

      if (in)
         close( fdin[0]) ;

      if ((out)||(fout))
         close( fdout[1] ) ;

#if !defined(VMS) && !defined(DOS)
      if ((in)&&((out)||(fout)))
      {
         fcntl( fdin[1], F_SETFL, O_NONBLOCK ) ;
         fcntl( fdout[0], F_SETFL, O_NONBLOCK ) ;
      }
#endif

      for (;(in)||(out)||(fout);) 
      {
         if (in)
         {
            if (((!string)&&(stack_empty())))
            {
               if (close(fdin[1]))
                  perror("During close") ; 
               in = 0 ; 
               if ((out)||(fout))
               {
#if !defined(VMS) && !defined(DOS)
/* 
 * Klugde for OS/2 ... see below
 */
                  flags = fcntl( fdout[0], F_GETFL, NULL ) ;
                  fcntl( fdout[0], F_SETFL, flags & (~O_NONBLOCK)) ;
#endif
               }

            }
            else  /* nothing left in string, but more in the stack */
            {
               if (!string)
               {
                  rstring = string = Str_ify(popline()) ;
                  string->value[length=Str_len(string)] = 0x0a ;
                  string->len++ ;
                  length++ ;
               }
               else
                  length = Str_len(string) ;

               rcode = write( fdin[1], string->value, length ) ;

#ifdef EWOULDBLOCK
               /* POSIX says EAGAIN, but BSD trad. uses EWOULDBLOCK, */
               /* so if EWOULDBLOCK is defined, check for that, too  */
               if ((rcode==(-1))&&((errno==EAGAIN)||(errno==EWOULDBLOCK)))
#else
               if ((rcode==(-1))&&(errno==EAGAIN))
#endif
                  continue ;

               if ((rcode==(-1))&&(errno==EPIPE))
               {    
                  Free( string ) ;
                  for (; (!stack_empty()); )
                     Free( popline() ) ;
                  string = rstring = NULL ;
                  continue ;
               }

               if (rcode==(-1))
               {
                  perror("While writing") ;
                  continue ;
               }

               else if (rcode<length)
               {
/* gdavis@europa.caps.maine.edu suggests the two following lines:   
   (oops, string is not char*) */
/*       string->len = length-rcode ;
 *       memcpy(&(string->value[0]),&(string->value[rcode]),string->len); 
 */
                  string->len -= rcode ;
                  memmove(string->value,&(string->value[rcode]),string->len) ;

/* old code was: */
/*                 string = &string[rcode] ; */
               }
               else
               {
                  assert( rcode==length ) ;
                  if (rstring) 
                     Free( rstring ) ;  
                  rstring = string = NULL ;
               }
            }
         }

         if ((out)||(fout))
         {
            rcode = read( fdout[0], cbuff, pipe_buf ) ;
            if (rcode>0)
               cbuff[rcode] = 0x00 ;

            if ((rcode==(-1))&&(errno==EAGAIN))
               continue ;

            if ((rcode==(0))) 
            {
               close( fdout[0] ) ;
               if (in)
               {
#if !defined(VMS) && !defined(DOS)
/* 
 * OS/2 haven't defined fcntl() correctly, so we try to make it 
 * work, by adding an extra parameter ... sigh! 
 */
                  flags = fcntl( fdin[1], F_GETFL, NULL ) ;
                  fcntl( fdin[1], F_SETFL, flags & (~O_NONBLOCK)) ;
#endif
               }             

               out = fout = 0 ;
               break ;
            }

            for (i=j=0; (i<rcode)&&(cbuff[i]); i++)
            {
               if (cbuff[i]==0x0d)
                  cbuff[i] = ' ' ;

               if (cbuff[i]!=0x0a) 
                  continue ;

               if (istring)
               {
                  result = Str_make( (length=Str_len(istring)) + i-j + 1 ) ;
                  result = Str_cat( result, istring ) ;
                  for (count=0; count<(i-j); count++)
                     result->value[count+length] = cbuff[j+count] ;
/*                result->value[length+count] = 0x00 ;
 *                Free( istring ) ;
 *
 * Previous two lines changed to next to lines, after bugfix from
 *  G. Fuchs <fuchs@vax1.rz.uni-regensburg.dbp.de>, 7th July 92 
 */
                  result->value[result->len=length+count] = 0x00 ;
                  Free( istring ) ; istring=NULL ;
               }
               else
               {
                  result = Str_make( (i-j) + 1 ) ;
                  for (count=0; count<(i-j); count++ )
                     result->value[count] = cbuff[j+count] ;
                  result->value[result->len=(i-j)] = 0x00 ;
               }

               tmp_stack(result, flush==2) ;
               j = i + 1 ;
            }
            assert((j<=i)&&(j>=0)&&(i>=0)) ;
            if ((j<i)&&(!istring)) /* some leftover chars ... */
            {
               istring = Str_make( i-j+1 ) ;
               for (count=0; count<(i-j); count++ )
                  istring->value[count] = cbuff[j+count] ;
               istring->value[ istring->len=(i-j) ] = 0x00 ;
            }
            else if ((j!=i)&&(istring)) /* leftover chars but no lf */
            {
               result = Str_make( (length=Str_len(istring)) + (i-j) + 1 ) ;
               result = Str_cat( result, istring ) ;
               result = Str_catstr( result, cbuff ) ;
               istring = result ;
            }
            else if ((i==j)) /* read ended with a lf */
            {
               assert( !istring ) ;
            }  

         }
      }

      if (flush)
         flush_stack( (flush==2) ) ;

      if (in) {	  
	close( fdin[0] ) ;
	close( fdin[1] ) ;
      }
      if ((out)||(fout)) {
	close( fdout[0] ) ;
	close( fdout[1] ) ;
      }      

#ifdef VMS 
      wait( &status ) ;
      rc = status & 0xff ;
#else
#ifdef NEXT
      /*
       * According to Paul F. Kunz, NeXTSTEP 3.1 Prerelease doesn't have 
       * the waitpid() function, so wait() must be used instead. The 
       * following klugde will remain until NeXTSTEP gets waitpid().
       */
      wait( &status ) ;
#else
# ifdef DOS
      wait( &status ) ;
# else
      waitpid( child, &status, 0 ) ;
# endif
#endif
      if (WIFEXITED(status))
         rc = (signed char) WEXITSTATUS(status) ;
      else if (WIFSIGNALED(status))
         rc = -100 - WTERMSIG(status) ;
      else
         rc = -1 ;
#endif
      signal( SIGPIPE, SIG_DFL ) ;
    }
   
   else
   {
     if (in)
     {
        dup2( fdin[0], 0 ) ;
	close( fdin[0] ) ;
	close( fdin[1] ) ;
     }

     if ((out)||(fout))
     {
        dup2( fdout[1], 1 ) ;
	close( fdout[0] ) ;
	close( fdout[1] ) ;
     }

     for (i=3; i<32; i++) 
       close( i ) ;
     
     if (envir==SUBENVIR_PATH) 
        execvp( cmd, ++args ) ;
     else if (envir==SUBENVIR_COMMAND)
        execv( cmd, ++args ) ;
     else if (envir==SUBENVIR_SYSTEM)
     {
        int foo = system( (Str_ify(command))->value ) ;
#ifdef VMS
        exit (foo) ;
#else
        if (WIFEXITED(foo))
        {
           exit( (signed char)WEXITSTATUS(foo) ) ;
        }
        else if (WIFSIGNALED(foo))
	{ 
           exit( -100 - WTERMSIG(foo) );
        }
        else
        {
           exit( 0 ) ;
        }
#endif

     }
     else
        exit( -1 ) ;

     exit( -2 ) ;  
  }

  if (args)
     destroyargs( args ) ; 

  return rc ;
}
#endif

#ifndef WIN32
streng *run_popen( streng *command, streng *envir )
{
   char *cbuffer ;
   int pipe_buf ;
   streng *nresult=NULL ;
   streng *result=NULL ;
   char **args ;
   char *cmd ;
   int rc, rsize, rcode, i ;
   static int child, status=0, running=0 ;
   struct envir ;
   int length=0 ;
   int envirno ;
   int fd[2], print() ;

   fflush(stdout) ;
   fflush(stderr) ;

   envirno = getenvir( envir ) ;

   /* first, kill any old children, the execution of them must have 
      been interrupted, leaving them un-waited. */
   if (running)
   {
      kill( child, SIGKILL ) ;
#ifdef VMS
      wait( &status ) ;
#else
#ifdef NEXT
      wait( &status ) ;
#else
# ifdef DOS
      wait( &status ) ;
# else
      waitpid( child, &status, WNOHANG ) ;
# endif
#endif
#endif
   }

   pipe(fd) ;
   if ((child=fork())) {
      close(fd[1]) ;
      rsize = 0 ;
#ifdef PIPE_BUF 
      pipe_buf = PIPE_BUF ;
#else
# ifndef _PC_PIPE_BUF
      /* See comment on PIPE_BUF above */
      pipe_buf = 512 ;
# else
      pipe_buf = fpathconf( fd[0], _PC_PIPE_BUF ) ;
# endif 
#endif
      cbuffer = Malloc(pipe_buf+1) ;

      for (;;) {
         rcode = read( fd[0], cbuffer, pipe_buf ) ; 

         for (i=0; (cbuffer[i])&&(i<rcode); i++) 
            if ((cbuffer[i]==0x0a)||(cbuffer[i]==0x0d))
               cbuffer[i] = ' ' ;

         if ((rcode>=0) || ((rcode=(-1)) && (errno==EINTR)))
         {
            nresult = Str_make((length=((result)?(Str_len(result)):0))+rcode+1) ;
            if (result)
            {
               nresult = Str_ncpy( nresult, result, length ) ;
               nresult = Str_ify(nresult) ; 
               Free( result ) ; 
            }
            else
               nresult->len = 0 ;

            if (rcode>=0)
               result = nresult = Str_ncatstr( nresult, cbuffer, rcode ) ;
            else
               result = nresult ;

            result = Str_ify( result ) ;
            if (rcode==0) 
               break ;
         }
         else if (rcode<=0)
         {
            if (rcode<0)
               perror( "While reading from pipe" ) ;
            break ; 
         }
      }
      Free( cbuffer ) ;
      
      close(fd[0]) ;
   
      if (result->value[length+rcode-1] == ' ')
         result->len-- ;
      
#ifdef VMS
      wait( &status ) ;
      rc = status & 0xff ;
#else
#ifdef NEXT
      wait( &status ) ;
#else
# ifdef DOS
      wait( &status ) ;
# else
      waitpid( child, &status, 0 ) ;
# endif
#endif
 
      if (WIFEXITED(status))
         rc = (signed char) WEXITSTATUS(status) ;
      else if (WIFSIGNALED(status))
         rc = -100 - WTERMSIG(status) ;
      else
         rc = -1 ;
#endif
   }
   else 
   {
      dup2(fd[1],1) ;
      close(fd[0]) ;

      /* Let's get rid of stderr ...    quick and dirty */

/*      freopen( "/dev/null", "w", stderr ) ; */

      cmd = NULL ;
      if ((envirno==SUBENVIR_PATH)||(envirno==SUBENVIR_COMMAND))
      {
         args = makeargs( command ) ;
         cmd = *args ;
         args++ ;
      }      
#ifdef lint
      else
         args = NULL ;
#endif   

     if (envirno==SUBENVIR_PATH) 
        execvp( cmd, args ) ;
     else if (envirno==SUBENVIR_COMMAND)
        execv( cmd, args ) ;
     else if (envirno==SUBENVIR_SYSTEM)
     {
#ifdef VMS
        int foo = system( (Str_ify(command))->value ) ;
        exit ( foo ) ;
#else
        int foo = system( (Str_ify(command))->value ) ;
        if (WIFEXITED(foo))
        {
           if (WEXITSTATUS(foo)==1)
              exit( -2 ) ; /* sh returns 2 if cmd could not be executed */
           exit( (signed char)WEXITSTATUS(foo) ) ;
        }
        else if (WIFSIGNALED(foo))
           exit( -100 - WTERMSIG(foo) );
        else
           exit( 0 ) ;
#endif

     }
     else
        exit( -1 ) ;

     exit( -2 ) ;  
   }

/*  should this be set ?  */
   {
      static streng RC_name = { 2, 3, "RC" } ;
      setvalue( &RC_name, int_to_streng(rc) ) ; 
   }
   return(((rc>=0))?result:NULL) ; 
   
}
#endif

