/*

                                   _JNJ`                        
                               .JNMH`                           
                              JMMF`       `;.                   
                            .NMM)          `MN.                 
                            MMM)            (MML                
                           (MMM`             MMML               
                     M  I  D  N  I  G  H  T    C  o  D  E       
                           (NMMF             MHNH               
                            NMML            .MMM                
                             NMML          .NMH                 
                              4MMNL       .#F                   
                               `4HNNL_   `                      
                                   `"""`                        

                          Copyright (C) 2004-2005
       "Ian (Larry) Latter" <ian dot latter at midnightcode dot org>

    This program is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, and mirrored at the Midnight Code web
    site; as at version 2 of the License only.
  
    This program 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
    General Public License for more details.
  
    You should have received a copy of the GNU General Public License
    (version 2) along with this program; if not, write to the Free
    Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
    02111-1307 USA, or see http://midnightcode.org/gplv2.txt

*/

/* midnightcode-process.c :: Midnight Code process library		*/

/* TODO:
         console_open and set_term to be revamped in new console library
*/

#include <stdio.h>
#include <string.h>

#include <stdarg.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/ioctl.h>
#include <sys/wait.h>
#include <signal.h>
#include <errno.h>
#include <termios.h>
#include <fcntl.h>
#include <linux/sockios.h>


#include "midnightcode-parse.h"
#include "midnightcode-process.h"


int
console_open(int mode)
{
        int f, fd = -1;
        int m;
	char console_dev[64] = "/dev/tty1";


        /*
         *      Open device in nonblocking mode.
         */
        m = mode | O_NONBLOCK;

        /*
         *      Retry the open five times.
         */
        for(f = 0; f < 5; f++)
                if ((fd = open(console_dev, m)) >= 0) break;

        if (fd < 0) return fd;

        /*
         *      Set original flags.
         */
        if (m != mode)
                fcntl(fd, F_SETFL, mode);
        return fd;
}


void
set_term(int how, int vt)
{
        struct termios tty;
        int fd;
        int restore_ok = 0;
        char vtname[12];


        if(how !=0)
          printf("TODO: Remove this option .. create console library.\n");

        if ( vt != 0 ) {
                if( vt<1 || vt > 12) {
                        return;
                }
                memset(vtname, 0, 12);
                sprintf(vtname, "/dev/vc/%d", vt);

                if ((fd = open(vtname, O_RDWR, 0777)) < 0) {
                        return;
                }

        } else {

                if ((fd = console_open(O_RDWR|O_NOCTTY)) < 0) {
                        return;
                }
        }

        if (restore_ok == 0) {
                /*
                 *      No old settings - create some resonable defaults.
                 */
                (void) tcgetattr(fd, &tty);

                tty.c_cflag &= CBAUD|CBAUDEX|CSIZE|CSTOPB|PARENB|PARODD;
                tty.c_cflag |= HUPCL|CLOCAL|CREAD;

                tty.c_cc[VINTR]  = 3;   /* ctrl('c') */
                tty.c_cc[VQUIT]  = 28;  /* ctrl('\\') */
                tty.c_cc[VERASE] = 127;
                tty.c_cc[VKILL]  = 24;  /* ctrl('x') */
                tty.c_cc[VEOF]   = 4;   /* ctrl('d') */
                tty.c_cc[VTIME]  = 0;
                tty.c_cc[VMIN]   = 1;
                tty.c_cc[VSTART] = 17;  /* ctrl('q') */
                tty.c_cc[VSTOP]  = 19;  /* ctrl('s') */
                tty.c_cc[VSUSP]  = 26;  /* ctrl('z') */
        }

        /*
         *      Set pre and post processing
         */
        tty.c_iflag = IGNPAR|ICRNL|IXON|IXANY;
        tty.c_oflag = OPOST|ONLCR;
        tty.c_lflag = ISIG|ICANON|ECHO|ECHOCTL|ECHOPRT|ECHOKE;

        /*
         *      Now set the terminal line.
         */
        (void) tcsetattr(fd, TCSADRAIN, &tty);
        (void) close(fd);
}


int
midnightcode_process_execute(char **args) {
  struct sigaction sig_action;
  char execute_buf[4096];
  int  sig_no;
  int  arg_index;


  for(sig_no = 1; sig_no < NSIG; sig_no++) {
    SETSIG(sig_action, sig_no, SIG_DFL, SA_RESTART);
  }

  execvp(args[1], args + 1);

  memset(execute_buf, 0, 4096);
  if(errno == ENOEXEC) {
    args[1] = "/bin/sh";
    args[2] = "-c";
    strcpy(execute_buf, "exec ");
    for(arg_index=0; arg_index <= 64 && args[arg_index] != NULL; arg_index++)
      strncat(execute_buf, args[arg_index],
              sizeof(execute_buf) - strlen(execute_buf) - 1);
    args[3] = execute_buf;
    args[4] = NULL;
    execvp(args[1], args + 1);
  }

  return -1;
}


int
midnightcode_process_execute_construct(int options, int terminal, char **args) {
  sigset_t sig_mask_old, sig_mask_new;
  struct sigaction sig_action;
  pid_t pid, wait_pid, pgrp;
  char terminal_name[12];
  int  status;
  int  console_sock;
  int  ret_i;


  sigemptyset(&sig_mask_new);
  sigaddset(&sig_mask_new, SIGCHLD);
  sigprocmask(SIG_BLOCK, &sig_mask_new, &sig_mask_old);

  switch (pid = fork()) {
    case -1:
      exit(-1);
      break;

    case 0:
      if(options & MIDNIGHTCODE_PROCESS_OPTION_NULLIO ||
         options & MIDNIGHTCODE_PROCESS_OPTION_CONSOLE) {
        close(0);
        close(1);
        close(2);
      }
      sigprocmask(SIG_SETMASK, &sig_mask_old, NULL);
      if(options & MIDNIGHTCODE_PROCESS_OPTION_CONSOLE) {
        setsid();
        if ((console_sock = console_open(O_RDWR|O_NOCTTY)) >= 0) {
          (void)ioctl(console_sock, TIOCSCTTY, 1);
          dup(console_sock);
          dup(console_sock);
        }
        switch (pid = fork()) {
          case -1:
            exit(-1);
            break;

          case 0:
            set_term(0, 0);
            ret_i = midnightcode_process_execute(args);
            exit(ret_i);
            break;

          default:
            SETSIG(sig_action, SIGINT, SIG_IGN, SA_RESTART);
            SETSIG(sig_action, SIGTSTP, SIG_IGN, SA_RESTART);
            SETSIG(sig_action, SIGQUIT, SIG_IGN, SA_RESTART);
            SETSIG(sig_action, SIGCHLD, SIG_DFL, SA_RESTART);
            while((wait_pid = waitpid(pid, &status, 0)) != pid) {
              if (wait_pid < 0 && errno == ECHILD) {
                break;
              }
            }
            pgrp = tcgetpgrp(wait_pid);
            if (pgrp != getpid())
              exit(0);
            switch (pid = fork()) {
              case -1:
                exit(-1);
                break;

              case 0:
                setsid();
                (void)ioctl(console_sock, TIOCSCTTY, 1);
                exit(0);
                break;

              default:
                while((wait_pid = waitpid(pid, &status, 0)) != pid) {
                  if (wait_pid < 0 && errno == ECHILD) {
                    break;
                  }
                }
                exit(0);
                break;
            }
            break;
        }
      } else {
        setsid();
        if(terminal != 0 ) {
          if(terminal<1 || terminal> 12) {
            console_sock = open("/dev/null", O_RDWR);
          } else {
            memset(terminal_name, 0, 12);
            sprintf(terminal_name, "/dev/vc/%d", terminal);
            if ((console_sock = open(terminal_name, O_RDWR, 0777)) < 0) {
              console_sock = open("/dev/null", O_RDWR);
            }
          }
          set_term(0, terminal);
        } else {
          if ((console_sock = console_open(O_RDWR|O_NOCTTY)) < 0) {
            console_sock = open("/dev/null", O_RDWR);
          }
        }
        dup(console_sock);
        dup(console_sock);
      }
      ret_i = midnightcode_process_execute(args);
      exit(ret_i);
      break;

    default:
      sigprocmask(SIG_SETMASK, &sig_mask_old, NULL);
      if(options & MIDNIGHTCODE_PROCESS_OPTION_WAITTERM) {
        while((wait_pid = waitpid(pid, &status, 0)) != pid) {
          if (wait_pid < 0 && errno == ECHILD) {
            break;
          }
        }
      }
      break;
  }
  if(options & MIDNIGHTCODE_PROCESS_OPTION_WAITTERM) {
    if (WIFEXITED(status)) {
      if(options & MIDNIGHTCODE_PROCESS_OPTION_EXITCODE) {
        return(WEXITSTATUS(status));
      } else {
        return 0;
      }
    }
    if(WIFSIGNALED(status)) {
      return(WTERMSIG(status));
    }
  } else {
    if(options & MIDNIGHTCODE_PROCESS_OPTION_GETPID) {
      return pid;
    } else {
      return 0;
    }
  }
  return -1;
}


int
midnightcode_process_parser(char *process_buf, int process_buf_max,
                            char **args) {
  char parse_buf[16384];
  int  arg_index;


  if(midnightcode_parse_any(parse_buf, "~`!$^&*()|{}[];'<>?")) {

    memset(parse_buf, 0, 16384);
    strncpy(parse_buf, process_buf, sizeof(parse_buf) - 1);
    if(strlen(parse_buf) != strlen(process_buf))
      return -1;
    memset(process_buf, 0, process_buf_max);

    if(strlen("exec ") > (process_buf_max - strlen(process_buf)))
      return -1;
    strncpy(process_buf, "exec ", process_buf_max - 1);
    strncat(process_buf, parse_buf,
            process_buf_max - strlen(process_buf) - 1);
    args[1] = "/bin/sh";
    args[2] = "-c";
    args[3] = process_buf;
    args[4] = NULL;
    arg_index = 4;

  } else {

    arg_index = midnightcode_parse_strtoarg(process_buf, process_buf_max,
                                            args, 63);

  }
  args[0] = args[1];


  return arg_index;
}



int
midnightcode_process(char *command_buf, int options, int terminal) {
  char *args[64];
  char process_buf[4096];
  int  ret_i;

  memset(process_buf, 0, 4096);
  strncpy(process_buf, command_buf, sizeof(process_buf) - 1);
  if(strlen(process_buf) != strlen(command_buf))
    return -1;

  if((ret_i = midnightcode_process_parser(process_buf, 4096, args)) < 0)
    return ret_i;

  if((ret_i = midnightcode_process_execute_construct(options, terminal,
                                                     args)) < 0)
    return ret_i;

  return ret_i;
}


