#include <windows.h>
#include <stdio.h>
#include <stdlib.h>

/******************************************************************
 *
 *   Função setStartupHandles
 *      Função utilizada para abrir os arquivos que redefinem as 
 *    saídas e entrada padrão de um processo e preencher os respectivos
 *    campos na estrutura STARTUPINFO.
 *
 *   Parâmetros:
 * -  si:
 *    Tipo: STARTUPINFO*
 *    Descrição: Estrutura que informa os dados para inicialização
 *               do novo processo.
 * - inFileName:
 *   Tipo: string
 *   Descrição: Nome do arquivo que redefine a entrada padrão do
 *              processo. Pode conter NULL ou "\0".
 *
 * - outFileName:
 *   Tipo: string
 *   Descrição: Nome do arquivo que redefine a saída padrão do
 *              processo. Pode conter NULL ou "\0".
 *
 * - errorFileName:
 *   Tipo: string
 *   Descrição: Nome do arquivo que redefine a saída de erro do
 *              processo. Pode conter NULL ou "\0".
 *
 *   Valor de retorno:
 *   -  Em caso de erro, uma string com mensagem indicativa do erro.
 *      Em caso de sucesso, NULL.
 *
 ******************************************************************/

static char* setStartupHandles (STARTUPINFO *si, const char *inFileName,
  const char *outFileName, const char *errorFileName ) {

   HANDLE hInFile = GetStdHandle(STD_INPUT_HANDLE) ;
   HANDLE hOutFile = GetStdHandle(STD_OUTPUT_HANDLE) ;
   HANDLE hErrorFile = GetStdHandle(STD_ERROR_HANDLE) ;

   SECURITY_ATTRIBUTES fileHandlesSecurity ;
   fileHandlesSecurity.bInheritHandle = TRUE ;
   fileHandlesSecurity.lpSecurityDescriptor = NULL ;
   fileHandlesSecurity.nLength = sizeof( SECURITY_ATTRIBUTES ) ;

   if (( inFileName != NULL ) && ( strlen( inFileName ) > 0 )) {
      hInFile = CreateFile( inFileName , // Nome do arquivo
                            FILE_WRITE_DATA | FILE_READ_DATA , // Permissões de 
                            FILE_SHARE_READ | FILE_SHARE_WRITE , // Permissões d
                            &fileHandlesSecurity , // Security, com herança de h
                            OPEN_EXISTING , // Abre um arquivo existente
                            FILE_ATTRIBUTE_NORMAL , // Atributos do arquivo
                            NULL // Template
                          ) ;
      if( hInFile == INVALID_HANDLE_VALUE )
      {
         return "Erro ao abrir arquivo de Input para o comando."
                             " Comando Abortado.\n" ;
      }
   }

   if ( ( outFileName != NULL ) && ( strlen( outFileName ) > 0 ) ) {
      hOutFile = CreateFile( outFileName , // Nome do arquivo
                            FILE_WRITE_DATA | FILE_READ_DATA , // Permissões de 
                            FILE_SHARE_READ | FILE_SHARE_WRITE , // Permissões d
                            &fileHandlesSecurity , // Security, com herança de h
                            CREATE_ALWAYS , // Abre um arquivo existente
                            FILE_ATTRIBUTE_NORMAL , // Atributos do arquivo
                            NULL // Template
                          ) ;
      if( hOutFile == INVALID_HANDLE_VALUE )
      {
         return "Erro ao abrir arquivo de Output para o comando."
                             " Comando Abortado.\n" ;
      }
   }
   
   if ( ( errorFileName != NULL ) && ( strlen( errorFileName ) > 0 ) ) {
      if ( ( outFileName == NULL ) ||
        (strcmp( errorFileName , outFileName ) != 0 ))
        {
        hErrorFile = CreateFile( errorFileName , // Nome do arquivo
                            FILE_WRITE_DATA | FILE_READ_DATA , // Permissões de 
                            FILE_SHARE_READ | FILE_SHARE_WRITE , // Permissões d
                            &fileHandlesSecurity , // Security, com herança de h
                            CREATE_ALWAYS , // Abre um arquivo existente
                            FILE_ATTRIBUTE_NORMAL , // Atributos do arquivo
                            NULL // Template
                          ) ;
        if( hErrorFile == INVALID_HANDLE_VALUE )
        {     
          return "Erro ao abrir arquivo de Error para o comando."
                             " Comando Abortado.\n" ;
        }
      }
      else {
        hErrorFile = hOutFile ;
      }
   }

   si->hStdOutput = hOutFile ;
   si->hStdInput = hInFile ;
   si->hStdError = hErrorFile ;
   return NULL;
}

int main( int argc, char *argv[] )
{
   if ( argc < 5 ) {
     printf("Número de parâmetros inválido.\n");
     exit(1);
   }
   char* BashPattern = "bash -c '%s'" ;
   char* BashCommandLine;
   char* CommandLine = argv[1] ;
   char* inFileName = argv[2] ;
   char* outFileName = argv[3] ;
   char* errorFileName = argv[4] ;

   // Settings de segurança para redirecionamento de I/O
   SECURITY_ATTRIBUTES processHandlesSecurity ;
   processHandlesSecurity.bInheritHandle = TRUE ;
   processHandlesSecurity.lpSecurityDescriptor = NULL ;
   processHandlesSecurity.nLength = sizeof( SECURITY_ATTRIBUTES ) ;

   SECURITY_ATTRIBUTES threadHandlesSecurity ;
   threadHandlesSecurity.bInheritHandle = TRUE ;
   threadHandlesSecurity.lpSecurityDescriptor = NULL ;
   threadHandlesSecurity.nLength = sizeof( SECURITY_ATTRIBUTES ) ;

   BashCommandLine = (char*) malloc( sizeof(char) * (strlen(BashPattern) +
                                        strlen(CommandLine) + 1));
   if (BashCommandLine == NULL) {
     printf("Erro ao criar commando para execução.\n" ) ;
     exit(1);
   }
   sprintf(BashCommandLine, BashPattern, CommandLine);

   STARTUPINFO si;
   PROCESS_INFORMATION pi;
   GetStartupInfo(&si);
   si.dwFlags = STARTF_USESTDHANDLES ;
   char* Error=setStartupHandles (&si, inFileName, outFileName,  errorFileName);
   if ( Error ) {
     printf("%s\n", Error);
     exit(1);
   }

   if ( CreateProcess(  NULL , // Application name, não condiz com linha de comando
                        (LPSTR)BashCommandLine , // Linha de comando a ser executada
                        &processHandlesSecurity , // Com herança de handles de processo
                        &threadHandlesSecurity  , // Com herança de handles de Threads
                        TRUE , // Com herança de handles "herdáveis"
                        0 , // Flags de criação
                        NULL , // Flags de Ambiente
                        NULL , // Diretório da aplicação
                        &si , // Dados para inicialização de processos
                        &pi // Estrutura a ser preenchida com handles e ids do processo e main thread
                     ) == TRUE )
   {
      // Onde devemos fazer o free??
      free(BashCommandLine);
   }
   else
   {
      // Onde devemos fazer o free??
      free(BashCommandLine);
      printf( "Erro ao criar processo\n" ) ;
      exit(1);
   }
   /* Espera filho */
   WaitForSingleObject(pi.hThread,INFINITE) ;

   CloseHandle(pi.hThread);
   CloseHandle(si.hStdInput);
   CloseHandle(si.hStdOutput);
   CloseHandle(si.hStdError);
   return 0;
}

