#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.
 */

#if defined(WIN32)                                      /* MH 10-06-96 */
# include <windows.h>                                   /* MH 10-06-96 */
#endif                                                  /* MH 10-06-96 */

#include "rexx.h"
#include <limits.h>
#include <stdio.h>
#ifdef VMS
# include <time.h>
#else
# ifdef HAVE_UNISTD_H
#  include <unistd.h>
# endif
# if defined(__WATCOMC__) || defined(_MSC_VER)          /* MH 10-06-96 */
#  include <process.h>                                  /* MH 10-06-96 */
# else                                                  /* MH 10-06-96 */
#  include <sys/wait.h>                                 /* MH 10-06-96 */
#  include <sys/time.h>                                 /* MH 10-06-96 */
# endif                                                 /* MH 10-06-96 */
# include <fcntl.h>
#endif
#include <string.h>
#include <ctype.h>
#include <errno.h>
#ifdef HAVE_ASSERT_H
# include <assert.h>
#endif
#include <signal.h>

#if defined(VMS)
# define fork() vfork()
# define posix_do_command vms_do_command
#endif
  
#if defined(DOS) || defined(__WATCOMC__) || defined(_MSC_VER)  /* MH 10-06-96 */
# define posix_do_command dos_do_command
#endif

#if defined(_POSIX_PIPE_BUF) && !defined(PIPE_BUF)
# define PIPE_BUF _POSIX_PIPE_BUF
#endif

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

extern char *environments[] ;


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

   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=0, words=0, j=0, k=0 ;
   char **ccptr=NULL ;

   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) && !defined(__WATCOMC__) && !defined(_MSC_VER) /* MH 10-06-96 */
int posix_do_command( streng *command, int io_flags, int envir )
{
   char **args=NULL ;
   int child=0, rc=0, i=0, fdin[2], fdout[2] ;
   char *cmd=NULL ;
   int in=0, out=0, fout=0 ;
   streng *rstring=NULL, *string=NULL ;
   streng *istring=NULL ;
   static char *cbuff=NULL ;
   int pipe_buf=0 ;
   int length=0, rcode=0, j=0, flush=0, status=0 ;
   streng *result=NULL ;
   char flags=' ' ;
   int count=0 ;

   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)
      {
#if defined(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, 0 )  ;

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

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

      if ((in)&&((out)||(fout)))
      {
         fcntl( fdin[1], F_SETFL, O_NONBLOCK ) ;
         fcntl( fdout[0], F_SETFL, O_NONBLOCK ) ;
      }

      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) && !defined(__WATCOMC__) && !defined(_MSC_VER)  /* MH 10-06-96 */
/* 
 * 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) && !defined(__WATCOMC__) && !defined(_MSC_VER)  /* MH 10-06-96 */
/* 
 * 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 ) ;
               Free_string( istring );  /* bja */
               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] ) ;
      }      
      if ( istring )              /* bja */
         Free_string( istring );  /* bja */

#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

#if defined(WIN32)                                      /* MH 10-06-96 */
streng *run_popen( streng *command, streng *envir )
{
 char *cbuffer=NULL ;
 char *tmpbuf=NULL;
 char comspec[1024] ;
 int pipe_buf = 512;
 streng *nresult=NULL ;
 streng *result=NULL ;
 long rc=0,i=0,chars_read=0;
 int length=0 ;
 int envirno=0 ;

 HANDLE read_handle, write_handle ;
 STARTUPINFO sinfo;
 PROCESS_INFORMATION pinfo;
 SECURITY_ATTRIBUTES sattr;

 fflush(stdout) ;
 fflush(stderr) ;

 envirno = getenvir( envir ) ;

 sattr.nLength = sizeof(sattr);
 sattr.lpSecurityDescriptor = NULL;
 sattr.bInheritHandle = TRUE;

 if (!CreatePipe(&read_handle, &write_handle, &sattr, (DWORD)0))
   {
    return NULL ;
   }

 command = Str_ify(command);

 if (envirno==SUBENVIR_SYSTEM)
   {
    if (GetEnvironmentVariable("COMSPEC", comspec, sizeof(comspec)) == 0)
      {
       strcpy(comspec, "CMD.EXE");
      }
    tmpbuf = Malloc(command->len+strlen(comspec)+20);
/*  sprintf(tmpbuf, "%s /c \"%s\"", comspec, command->value) ;*/
    sprintf(tmpbuf, "%s /c %s", comspec, command->value) ;
   }
 else 
   {
    tmpbuf = Malloc(command->len+1);
    strcpy(tmpbuf, command->value ) ;
   }

 memset(&sinfo, '\0', sizeof(sinfo));

 sinfo.cb = sizeof(sinfo);
 sinfo.dwFlags = STARTF_USESTDHANDLES ;
 sinfo.hStdInput = GetStdHandle( STD_INPUT_HANDLE ) ;
 sinfo.hStdOutput = write_handle ;
 sinfo.hStdError = GetStdHandle( STD_ERROR_HANDLE ) ;

 if (!CreateProcess(NULL, tmpbuf, NULL, NULL, TRUE, 0, NULL, NULL, &sinfo, &pinfo))
   {
    Free( tmpbuf ) ;
    return NULL ;
   }

 Free( tmpbuf ) ;
 CloseHandle(write_handle);
 cbuffer = Malloc(pipe_buf+1) ;

 while(1)
   {
    if (!ReadFile( read_handle, cbuffer, sizeof(cbuffer), (DWORD*)&chars_read, NULL ) )
      {
       if (GetLastError() != ERROR_BROKEN_PIPE)
         {
          Free( cbuffer ) ;
          return NULL;
         }
       chars_read = 0;
      }

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

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

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

    result = Str_ify( result ) ;
    if (chars_read==0) 
       break ;
   }
 Free( cbuffer ) ;
      
 CloseHandle(read_handle);
   
 if (result->value[length-1] == ' ')
   {
    result->len-- ;
    length--;
   }
      
 result = Str_ify( result ) ;
      
 WaitForSingleObject( pinfo.hProcess, INFINITE ) ;

 if (!GetExitCodeProcess(pinfo.hProcess,(DWORD*)&rc ))
    rc = (-1);

 CloseHandle(read_handle);

/*  should this be set ?  */
 {
  static streng RC_name = { 2, 3, "RC" } ;
  setvalue( &RC_name, int_to_streng(rc) ) ; 
 }

 return(((rc>=0))?result:NULL) ; 
}
#elif defined(DOS) || defined(OS2)
streng *run_popen( streng *command, streng *envir )
{
   FILE *fptr = NULL;
   char *cbuffer = NULL;
   streng *result=NULL ;
   streng *outredir=NULL ;
   register int i=0 ;
   int rcode = 0;
   int c=0 ;
   char **args=NULL ;
   char *cmd=NULL ;
   char *outfile=NULL;


   if ((outfile = strdup(tmpnam(NULL))) == NULL)
   {
      perror("While getting temporary out-file name") ;
      return (NULL);
   }
   outredir = Str_make(strlen(outfile)+3+command->len);
   Str_cat(outredir, command );
   Str_catstr(outredir, " > ");
   Str_catstr(outredir, str_trans(outfile,'/','\\'));
   cmd = (Str_ify(outredir))->value;

   rcode = system(cmd);
   if (rcode != -1)
   {
      fptr = fopen(outfile, "r");
      if (fptr != NULL)
      {
         result = Str_make( 500 );
         for (i=0; i < 500; i++)
         {
            c = fgetc(fptr);
            if (c == EOF)
               break;
            if ((c==0x0a)||(c==0x0d))
               result->value[i] = ' ' ;
            else
               result->value[i] = c;
         }
         result->value[i] = 0x00;
         result->len = i;
         fclose(fptr);
      }
   }
   give_a_streng( outredir );
   unlink(outfile);
   free(outfile);

   {
      static streng RC_name = { 2, 3, "RC" } ;
      setvalue( &RC_name, int_to_streng(rcode) ) ;
   }

   return((rcode==-1)?NULL:result);
}
#else
streng *run_popen( streng *command, streng *envir )
{
   char *cbuffer=NULL ;
   int pipe_buf=0 ;
   streng *nresult=NULL ;
   streng *result=NULL ;
   char **args=NULL ;
   char *cmd=NULL ;
   int rc=0, rsize=0, rcode=0, i=0 ;
   static int child=0, status=0, running=0 ;
   int length=0 ;
   int envirno=0 ;
   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 ) ;
#if defined(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 ;
#if defined(PIPE_BUF)
      pipe_buf = PIPE_BUF ;
#else
# if !defined(_PC_PIPE_BUF) || defined(WIN32)           /* MH 10-06-96 */
      /* 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))) /* MH 10-06-96 */
         {
            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-- ;
      
#if defined(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)
     {
#if defined(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                                                  /* MH 10-06-96 */

