/*******************************************************************************
* Copyright (c) 2000-2021 Ericsson Telecom AB
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v2.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/org/documents/epl-2.0/EPL-2.0.html
*
* Contributors:
*     Zoltan Janos Szabo (Ericsson) - initial architecture design and implementation
*     Roland Gecse (Ericsson) - initial architecture design
*     Akos Cserveni (Ericsson) - Basic AST in compiler, semantic checking
*     Gabor Szalai (Ericsson) – RAW and TEXT codecs
*     Matyas Forstner (Ericsson) - ASN.1 extension of the compiler and BER/CER/DER codecs
*     Kristof Szabados  (Ericsson) - Eclipse Designer, Executor, Titanium UIs
*     Szabolcs Beres (Ericsson) - Eclipse LogViewer
*     Ferenc Kovacs (Ericsson) – log interfaces, big number support, subtype checking
*     Csaba Raduly (Ericsson) – ASN.1 additions, XML encoder/decoder
*     Adam Delic (Ericsson) – template restrictions, try&catch, support of pre-processor directives in Eclipse
*     Krisztian Pandi (Ericsson) – import of imports
*     Peter Dimitrov (Ericsson)- maintenance
*     Balazs Andor Zalanyi (Ericsson) – code splitting
*     Gabor Szalai (Ericsson) – RAW encoding/decoding
*     Jeno Attila Balasko (Ericsson) – tests
*     Csaba Feher (Ericsson) – epoll support
*     Tamas Buti (Ericsson)- maintenance
*     Matyas Ormandi (Ericsson) - maintenance
*     Botond Baranyi (Ericsson) - JSON encoder
*     Arpad Lovassy (Ericsson) - Java Executor API
*     Laszlo Baji (Ericsson) - maintenance
*     Marton Godar (Ericsson) - xsd2ttcn converter
*******************************************************************************/
//
//  File:               PIPEasp_PT.cc
//  Description:        Source code of PIPE testport implementation
//  Rev:                <RnXnn>
//  Prodnr:             CNL 113 334
//  Updated:            2008-06-03
//  Contact:            http://ttcn.ericsson.se
//


#include "PIPEasp_PT.hh"
#include <signal.h> //kill
#include <unistd.h> //pipe
#include <errno.h>  //errno
#include <ctype.h>  //isspace
#include <sys/select.h>  //FD_ZERO
#include <stdio.h>      // sys_errlist
#include <sys/types.h>  //wait
#include <sys/wait.h>   //wait

#ifndef PIPE_BUF_SIZE
#define PIPE_BUF_SIZE 655536
#endif

namespace PIPEasp__PortType {

PIPEasp__PT::PIPEasp__PT(const char *par_port_name)
	: PIPEasp__PT_BASE(par_port_name)
        , lineMode(true)
        , processExecuting(false)
        , binaryMode(false)
        , disableSend(false)
        , processPid(-1)     // pid of the process currently executing
        , processStdin(-1)   // fd of stdin of the process
        , processStdout(-1)  // fd of stdout of the process
        , processStderr(-1)  // fd of stderr of the process
        , processExitCode(0) // exit code of the process
{
  FD_ZERO(&readfds);
  stdout_buffer.clear();
  stderr_buffer.clear();
}

PIPEasp__PT::~PIPEasp__PT()
{
// nothing to do
}

void PIPEasp__PT::set_parameter(const char * /*parameter_name*/,
	const char * /*parameter_value*/)
{
// no config parameters
}

void PIPEasp__PT::Event_Handler(const fd_set *read_fds,
	const fd_set * /*write_fds*/, const fd_set * /*error_fds*/,
	double /*time_since_last_call*/)
{
  log("PIPEasp__PT::Event_Handler called");
  if (processStdout != -1 && FD_ISSET(processStdout, read_fds)) {
    if (!processExecuting) {
      TTCN_warning("Unexpected message from stdout, no command is executing");
    } else {
      log("Incoming stdout message received from process");
    }

    long nBytes;
    int r;

    nBytes = PIPE_BUF_SIZE;
    unsigned char* buffer;
    size_t end_len = nBytes;
    stdout_buffer.get_end(buffer, end_len);
    r = read(processStdout,buffer,(int)nBytes);
    if (r <= 0) {
	log("ttcn_pipe_port: read problem on stdout.\n");
        // close the pipe and remove it from event handler
        close(processStdout);
        FD_CLR(processStdout, &readfds);
        Install_Handler(&readfds, NULL, NULL, 0.0);
        processStdout = -1;

        // check if stderr is closed:
        if (processStderr == -1) {
          // child died
          log("Child might have died.");
          handle_childDeath();
        }
    }
    else {
      log("incoming stdout %ld bytes\n", r);
      stdout_buffer.increase_length(r);
      sendStdout();
    }
    return;
  }
  if (processStderr != -1 && FD_ISSET(processStderr, read_fds)) {
    if (!processExecuting) {
      TTCN_warning("Unexpected message from stderr, no command is executing");
    } else {
      log("Incoming stderr message received from process");
    }

    long nBytes;
    int r;

    nBytes = PIPE_BUF_SIZE;
    unsigned char* buffer;
    size_t end_len = nBytes;
    stderr_buffer.get_end(buffer, end_len);
    r = read(processStderr,buffer,(int)nBytes);
    if (r <= 0) {
	log("ttcn_pipe_port: read problem on stderr.\n");
        // close the pipe and remove it from event handler
        close(processStderr);
        FD_CLR(processStderr, &readfds);
        Install_Handler(&readfds, NULL, NULL, 0.0);
        processStderr = -1;

        // check if stdout is closed:
        if (processStdout == -1) {
          // child died
          log("Child might have died.");
          handle_childDeath();
        }
    }
    else {
      log("incoming stderr %ld bytes\n", r);
      stderr_buffer.increase_length(r);
      sendStderr();
    }
    return;
  }
}

void PIPEasp__PT::user_map(const char * /*system_port*/)
{
// nothing to do
}

void PIPEasp__PT::user_unmap(const char * /*system_port*/)
{
// nothing to do
}

void PIPEasp__PT::user_start()
{
// nothing to do
}

void PIPEasp__PT::user_stop()
{
// nothing to do
}

/*************************************
*  Specific outgoing_send functions
*************************************/
void PIPEasp__PT::outgoing_send(const PIPEasp__Types::ASP__PExecute& send_par) {
  log("PIPEasp__PT::outgoing_send_PExecute called");
  // disable sendStdout, sendStderr until process exits
  if (processExecuting) {
    sendError("Pipe Test Port: Command already executing");
    TTCN_Logger::begin_event(TTCN_DEBUG);
    TTCN_Logger::log_event("PIPE test port (%s): Command already executing. Following ASP is ignored: ", get_name());
    send_par.log();
    TTCN_Logger::end_event();
    return;
  }
  PIPEasp__Types::ASP__PExecuteBackground message_PExecuteBackground;
  // starting command
  message_PExecuteBackground.command() = send_par.command();
  outgoing_send(message_PExecuteBackground);
  // sending input
  PIPEasp__Types::ASP__PStdin message_PStdin;
  message_PStdin.stdin_() = send_par.stdin_();
  outgoing_send(message_PStdin);
  disableSend = true;

  // closing stdin pipe:
  outgoing_send(PIPEasp__Types::ASP__PEndOfInput());

  log("PIPEasp__PT::outgoing_send_PExecute exited");
}

void PIPEasp__PT::outgoing_send(const PIPEasp__Types::ASP__PExecuteBinary& send_par) {
  log("PIPEasp__PT::outgoing_send_PExecuteBinary called");
  // disable sendStdout, sendStderr until process exits
  if (processExecuting) {
    sendError("Pipe Test Port: Command already executing");
    TTCN_Logger::begin_event(TTCN_DEBUG);
    TTCN_Logger::log_event("PIPE test port (%s): Command already executing. Following ASP is ignored: ", get_name());
    send_par.log();
    TTCN_Logger::end_event();
    return;
  }
  PIPEasp__Types::ASP__PExecuteBackground message_PExecuteBackground;
  // starting command
  message_PExecuteBackground.command() = send_par.command();
  outgoing_send(message_PExecuteBackground);
  // sending input
  PIPEasp__Types::ASP__PStdinBinary message_PStdinBinary;
  message_PStdinBinary.stdin_() = send_par.stdin_();
  outgoing_send(message_PStdinBinary);
  disableSend = true;

  // closing stdin pipe:
  outgoing_send(PIPEasp__Types::ASP__PEndOfInput());

  log("PIPEasp__PT::outgoing_send_PExecuteBinary exited");
}

void PIPEasp__PT::outgoing_send(const PIPEasp__Types::ASP__PExecuteBackground& send_par) {
  log("PIPEasp__PT::outgoing_send_PExecuteBackground called");

  if (processExecuting) {
    log("Process already executing. Cannot start new process.");
    sendError("Pipe Test Port: Command already executing");
    TTCN_Logger::begin_event(TTCN_DEBUG);
    TTCN_Logger::log_event("PIPE test port (%s): Command already executing. Following ASP is ignored: ", get_name());
    send_par.log();
    TTCN_Logger::end_event();
    log("PIPEasp__PT::outgoing_send_PExecuteBackground exited");
    return;
  }

  // creating pipes for process
  int pipesStdin[2];
  int pipesStdout[2];
  int pipesStderr[2];

  if (pipe(pipesStdin) != 0) {
      sendError("Pipe Test Port: Cannot create stdin pipe");
      log("PIPEasp__PT::outgoing_send_PExecuteBackground exited");
      return;
  }
  if (pipe(pipesStdout) != 0) {
      sendError("Pipe Test Port: Cannot create stdout pipe");
      log("PIPEasp__PT::outgoing_send_PExecuteBackground exited");
      return;
  }
  if (pipe(pipesStderr) != 0) {
      sendError("Pipe Test Port: Cannot create stderr pipe");
      log("PIPEasp__PT::outgoing_send_PExecuteBackground exited");
      return;
  }

  processStdin = pipesStdin[1];
  processStdout = pipesStdout[0];
  processStderr = pipesStderr[0];

  processPid = fork();
  if (processPid < 0) {
    //
    // Error
    //

    // close the pipes
    close(pipesStdin[0]);
    close(pipesStdout[1]);
    close(pipesStderr[1]);

    close(processStdin);
    close(processStdout);
    close(processStderr);

    sendError("Pipe Test Port: Cannot fork");
  }
  else if (processPid == 0) {

      //
      // Child process
      //

      // close the parent end of the pipes
      close(processStdin);
      close(processStdout);
      close(processStderr);

      /*// set these to the other end of the pipe
      processStdin = pipesStdin[0];
      processStdout = pipesStdout[1];
      processStderr = pipesStderr[1];*/

      int r;
      // redirect pipeStdin to stdin
      r = dup2(pipesStdin[0], 0);
      if (r<0) {
        TTCN_error("Cannot redirect stdin");
        exit(errno);
      }

      // redirect pipeStdout to stdout
      r = dup2(pipesStdout[1], 1);
      if (r<0) {
        TTCN_error("Cannot redirect stdout");
        exit(errno);
      }

      // redirect pipeStderr to stderr
      r = dup2(pipesStderr[1], 2);
      if (r<0) {
        TTCN_error("Cannot redirect stderr");
        exit(errno);
      }

      processExitCode = execCommand((const char*)send_par.command());

      // There is a problem executing the command
      // Exiting...

      fflush(stdout);
      fflush(stderr);

      //closing pipes:
      close(pipesStdin[0]);
      close(pipesStdout[1]);
      close(pipesStderr[1]);

      exit(processExitCode); // end of child process
  }
  else {

    //
    // Parent process
    //

    log("Process started with pid: %d", processPid);
    // close child end of the pipes
    close(pipesStdin[0]);
    close(pipesStdout[1]);
    close(pipesStderr[1]);


    processExecuting = true;

    // install handler for the process pipes:
    //FD_SET(processStdin, &readfds);
    FD_SET(processStdout, &readfds);
    FD_SET(processStderr, &readfds);

    Install_Handler(&readfds, NULL, NULL, 0.0);
 }

  log("PIPEasp__PT::outgoing_send_PExecuteBackground exited");
}

void PIPEasp__PT::outgoing_send(const PIPEasp__Types::ASP__PStdin& send_par) {

  log("PIPEasp__PT::outgoing_send_PStdin called");
  binaryMode = false;
  if (!processExecuting) {
    sendError("Pipe Test Port: No command executing");
    return;
  }
  if (disableSend) {  // process was started with PExecute(Binary)
    sendError("Pipe Test Port: PStdin is not sent: current process is not started with PExecuteBackground!");
    return;
  }

  log("will now write to stdin: '%s'",
		 (const char*)(send_par.stdin_()+((lineMode)?"\n":"")));
  write(processStdin,
	(const char*)(send_par.stdin_()+((lineMode)?"\n":"")),
	send_par.stdin_().lengthof()+((lineMode)?1:0));

  log("PIPEasp__PT::outgoing_send_PStdin exited");
}

void PIPEasp__PT::outgoing_send(const PIPEasp__Types::ASP__PStdinBinary& send_par) {
  log("PIPEasp__PT::outgoing_send_PStdinBinary called");
  binaryMode = true;
  if (!processExecuting) {
    sendError("Pipe Test Port: No command executing");
    return;
  }
  if (disableSend) {  // process was started with PExecute(Binary)
    sendError("Pipe Test Port: PStdinBinary is not sent: current process is not started with PExecuteBackground!");
    return;
  }

  TTCN_Logger::begin_event(TTCN_DEBUG);
  TTCN_Logger::log_event("PIPE test port (%s): will now write binary data to stdin: ", get_name());
  send_par.stdin_().log();
  TTCN_Logger::end_event();

  write(processStdin,
	(const char*)(const unsigned char*)(send_par.stdin_()),
	send_par.stdin_().lengthof());
  log("PIPEasp__PT::outgoing_send_PStdinBinary exited");
}

void PIPEasp__PT::outgoing_send(const PIPEasp__Types::ASP__PKill& send_par) {
  log("PIPEasp__PT::outgoing_send_PKill called");
  if (!processExecuting) {
    // no process is running
    log("No process executing.");
    sendError("Pipe Test Port: No command executing");
    log("PIPEasp__PT::outgoing_send_PKill exited");
    return;
  }

  int signo = (int)send_par.signal();
  if (signo<1 || signo>31) {
    // signo out of range;
    log("Signo out of range.");
    sendError(
      "Pipe Test port: Signal number should be "
      "between 1 and 31");
    log("PIPEasp__PT::outgoing_send_PKill exited");
    return;
  }
  // killing process
  log("Killing process %d, signo: %d", processPid, signo);
  int r = kill(processPid, signo);
  log("Kill process returned %d", r);
  log("PIPEasp__PT::outgoing_send_PKill exited");
}

void PIPEasp__PT::outgoing_send(const PIPEasp__Types::ASP__PLineMode& send_par) {
  log("PIPEasp__PT::outgoing_send_PLineMode called");
  lineMode = (bool)send_par.lineMode();
  log("LineMode is set to %s", (lineMode)?"TRUE":"FALSE");
  log("PIPEasp__PT::outgoing_send_PLineMode exited");
}

void PIPEasp__PT::outgoing_send(const PIPEasp__Types::ASP__PEndOfInput& /*send_par*/) {
  log("PIPEasp__PT::outgoing_send_PEndOfInput called");
  // close stdin pipe
  close(processStdin);
  processStdin = -1;
  log("stdin closed");
  log("PIPEasp__PT::outgoing_send_PEndOfInput exited");
}

/********************************
* Execute the given command
* returns the exitcode of the process
*********************************/
int PIPEasp__PT::execCommand(const char* command) {
  log("PIPEasp__PT::execCommand called");
  log("Executing command: %s", command);

  // with this it is not possible to access the pid of the process
  //return system(command);

  int argc = 0;
  char* argv[1024];

  CHARSTRING temp(0, ""); // empty string
  for (int i = 0; command[i] != 0; i++) {
      if (isspace(command[i])) {
	  argv[argc++] = strdup(temp);
          log("command argument added: %s", (const char*)temp);
	  while (command[i] != '0' && isspace(command[i])) i++;
	  i--;
	  temp = "";
      } else {
	  temp = temp + CHARSTRING(1, command+i);
      }
  }

  if (temp != "") {
      argv[argc++] = strdup(temp);
      log("command argument added: %s", (const char*)temp);
  }

  argv[argc++] = (char*)NULL;

  log("execCommand(%s,%d)\n", argv[0], argc);
  execvp(argv[0],argv);

  fprintf(stderr,"Error executing command %s (%d): %s\n",
    argv[0], errno, strerror(errno));
  fflush(stderr);
//  exit(errno);
  return errno;
}

/***********************************
* if the the child process died, gets
* the exit code and sends it to TTCN
* should be called when stdout/err are closed
************************************/
void PIPEasp__PT::handle_childDeath() {
  log("Getting process status for pid %d...", processPid);
  processExitCode = 0;  // reset the exitcode
  int pid = wait(&processExitCode);
  //waitpid(processPid,&processExitCode, 0);
  if (pid!=processPid) {
    log("other child died with pid: %d, exit code: %d", pid, processExitCode);
    return;
  }

  log("Child process exit status is: %d", processExitCode);
  // send code to TTCN:
  sendExitCode();
  // send result to TTCN
  sendResult();

  // removing fd-s installed for the process:
  Uninstall_Handler(); // no handler is needed
  FD_ZERO(&readfds);
  /*
  // equivalent with:
  //FD_CLR(processStdin, &readfds);
  FD_CLR(processStdout, &readfds);
  FD_CLR(processStderr, &readfds);
  Install_Handler(&readfds, NULL, NULL, 0.0);
  */

  // closing pipes:
  close(processStdin);
  //close(processStdout); // already closed
  //close(processStderr); // already closed

  processStdin = -1;
  //processStdout = -1;
  //processStderr = -1;

  processExecuting = false;
  disableSend = false;
}

/***************************
* Send stdout msg to TTCN
***************************/
void PIPEasp__PT::sendStdout() {
  if (disableSend) return;

  PIPEasp__Types::ASP__PStdout message_PStdout;
  PIPEasp__Types::ASP__PStdoutBinary message_PStdoutBinary;
  if (lineMode && !binaryMode) {
    // send complete lines from buffer
    const unsigned char* pos = stdout_buffer.get_read_data();
    for(unsigned int i=0; i<stdout_buffer.get_read_len(); i++) {
      // not end of line:
      if (pos[i] != '\n') {
        continue;
      }

      // at end of line
      // length of data is i (+1 is for \n and is not sent)
      message_PStdout.stdout_() = CHARSTRING(i, (const char*)pos);

      // send message
      incoming_message(message_PStdout);

      // remove the complete line from buffer,
      // also set i and pos to the beginning of buffer
      stdout_buffer.set_pos(i+1);
      stdout_buffer.cut();
      i = 0;
      pos = stdout_buffer.get_read_data();
    }
  } else {
    // lineMode false or binaryMode true
    if (binaryMode) {
      message_PStdoutBinary.stdout_() =
        OCTETSTRING(stdout_buffer.get_read_len(), stdout_buffer.get_read_data());
      stdout_buffer.clear();
      incoming_message(message_PStdoutBinary);
    }
    else {
      message_PStdout.stdout_() =
        CHARSTRING(stdout_buffer.get_read_len(), (const char*)stdout_buffer.get_read_data());
      stdout_buffer.clear();
      incoming_message(message_PStdout);
    }
//    incoming_message(message);
  }
}


/***************************
* Send stderr msg to TTCN
***************************/
void PIPEasp__PT::sendStderr() {
  if (disableSend) return;

  PIPEasp__Types::ASP__PStderr message_PStderr;
  PIPEasp__Types::ASP__PStderrBinary message_PStderrBinary;
  if (lineMode && !binaryMode) {
    // send complete lines from buffer
    const unsigned char* pos = stderr_buffer.get_read_data();
    for(unsigned int i=0; i<stderr_buffer.get_read_len(); i++) {
      // not end of line:
      if (pos[i] != '\n') {
        continue;
      }

      // at end of line
      // length of data is i (+1 is for \n and is not sent)
      message_PStderr.stderr_() = CHARSTRING(i, (const char*)pos);

      // send message
      incoming_message(message_PStderr);

      // remove the complete line from buffer,
      // also set i and pos to the beginning of buffer
      stderr_buffer.set_pos(i+1);
      stderr_buffer.cut();
      i = 0;
      pos = stderr_buffer.get_read_data();
    }
  } else {
    // lineMode false or binaryMode true
    if (binaryMode) {
      message_PStderrBinary.stderr_() =
        OCTETSTRING(stderr_buffer.get_read_len(), stderr_buffer.get_read_data());
      stderr_buffer.clear();
      incoming_message(message_PStderrBinary);
    }
    else {
      message_PStderr.stderr_() =
        CHARSTRING(stderr_buffer.get_read_len(), (const char*)stderr_buffer.get_read_data());
      stderr_buffer.clear();
      incoming_message(message_PStderr);
    }
//    incoming_message(message);
  }
}


/***************************
* Send exitcode msg to TTCN
***************************/
void PIPEasp__PT::sendExitCode() {
  if (disableSend) return;

  log("Sending ExitCode to TTCN");
  PIPEasp__Types::ASP__PExit message_PExit;
  message_PExit.code() = processExitCode;
  incoming_message(message_PExit);
}


/***************************
* Send error msg to TTCN
***************************/
void PIPEasp__PT::sendError(const char* error_msg) {
  PIPEasp__Types::ASP__PError message_PError;
  message_PError.errorMessage() = error_msg;
  incoming_message(message_PError);
}


/***************************
* Send Result msg to TTCN
***************************/
void PIPEasp__PT::sendResult() {
  if (!disableSend) return; // do not send result if process was started by PExecuteBackground

  log("Sending result to TTCN...");
  PIPEasp__Types::ASP__PResult message_PResult;
  PIPEasp__Types::ASP__PResultBinary message_PResultBinary;
  if (binaryMode) {
    message_PResultBinary.stdout_() =
     OCTETSTRING(stdout_buffer.get_read_len(), stdout_buffer.get_read_data());
    message_PResultBinary.stderr_() =
     OCTETSTRING(stderr_buffer.get_read_len(), stderr_buffer.get_read_data());
    message_PResultBinary.code() = processExitCode;
    incoming_message(message_PResultBinary);
  } else {
    int messageLen = stdout_buffer.get_read_len();
    const char* messageData = (const char*)stdout_buffer.get_read_data();

    if (lineMode && messageData[messageLen-1]=='\n') {
      messageLen--; // remove newline from the end
    }

    message_PResult.stdout_() = CHARSTRING(messageLen, messageData);

    messageLen = stderr_buffer.get_read_len();
    messageData = (const char*)stderr_buffer.get_read_data();

    if (lineMode && messageData[messageLen-1]=='\n') {
      messageLen--; // remove newline from the end
    }

    message_PResult.stderr_() = CHARSTRING(messageLen, messageData);
    message_PResult.code() = processExitCode;
    incoming_message(message_PResult);
  }

  // clearing the buffers
  stdout_buffer.clear();
  stderr_buffer.clear();
  //incoming_message(message);
}


////////////////
// Log function
////////////////
void PIPEasp__PT::log(const char *fmt, ...)
{
  TTCN_Logger::begin_event(TTCN_DEBUG);
  TTCN_Logger::log_event("PIPE test port (%s): ", get_name());
  va_list ap;
  va_start(ap, fmt);
  TTCN_Logger::log_event_va_list(fmt, ap);
  va_end(ap);
  TTCN_Logger::end_event();
}

}//namespace
