/*
 * Copyright (C), 2000-2007 by the monit project group.
 * All Rights Reserved.
 *
 * 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, either version 3 of the License, or
 * (at your option) any later version.
 *
 * 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
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

#include <config.h>

#ifdef HAVE_STDIO_H
#include <stdio.h>
#endif

#ifdef HAVE_STDLIB_H
#include <stdlib.h>
#endif

#ifdef HAVE_ERRNO_H
#include <errno.h>
#endif 

#ifdef HAVE_SIGNAL_H
#include <signal.h>
#endif 

#ifdef HAVE_STRING_H
#include <string.h>
#endif

#ifdef HAVE_STRINGS_H
#include <strings.h>
#endif

#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif

#ifdef HAVE_SYS_WAIT_H
#include <sys/wait.h>
#endif

#ifdef HAVE_SYS_TYPES_H
#include <sys/types.h>
#endif

#ifdef HAVE_SYS_STAT_H
#include <sys/stat.h>
#endif

#ifdef HAVE_FCNTL_H
#include <fcntl.h>
#endif

#include "event.h"
#include "alert.h"
#include "monitor.h"
#include "engine.h"


/**
 *  Function for spawning of a process. This function fork's twice to
 *  avoid creating any zombie processes. Inspired by code from
 *  W. Richard Stevens book, APUE.
 *
 *  @author Jan-Henrik Haukeland, <hauk@tildeslash.com>
 *  @author Peter Holdaway <pholdaway@technocom-wireless.com>
 *
 *  @version \$Id: spawn.c,v 1.50 2007/07/25 12:54:30 hauk Exp $
 *  @file
 */


/* ------------------------------------------------------------- Definitions */


/* Do not exceed 8 bits here */
enum ExitStatus_E {
  setgid_ERROR   = 0x1,
  setuid_ERROR   = 0x2,
  redirect_ERROR = 0x4,
  fork_ERROR     = 0x8
};

typedef struct En {char env[STRLEN]; struct En *next;} *Environment_T;


/* -------------------------------------------------------------- Prototypes */


static void put_monit_environment(Environment_T e);
static void free_monit_environment(Environment_T *e);
static void push_monit_environment(const char *env, Environment_T *list);
static void set_monit_environment(Service_T s, const char *event, Environment_T *e);


/* ------------------------------------------------------------------ Public */


/**
 * Execute the given command. If the execution fails, the wait_start()
 * thread in control.c should notice this and send an alert message.
 * @param P A Service object
 * @param C A Command object
 * @param E An optional event string, specifying why this function was
 * called. May be NULL.
 */
void spawn(Service_T S, Command_T C, const char *E) {

  pid_t pid;
  sigset_t mask;
  sigset_t save;
  int stat_loc= 0;
  int exit_status;
  Environment_T environment= NULL;

  ASSERT(S);
  ASSERT(C);

  if(access(C->arg[0], X_OK) != 0) {
    LogError("Error: Could not execute %s\n", C->arg[0]);
    return;
  }

  /*
   * Block SIGCHLD
   */
  sigemptyset(&mask);
  sigaddset(&mask, SIGCHLD);
  pthread_sigmask(SIG_BLOCK, &mask, &save);

  set_monit_environment(S, E, &environment);

  pid= fork();
  if(pid < 0) {
    LogError("Cannot fork a new process\n");  
    exit(1); 
  }

  if(pid == 0) {
    /*
     * Reset to the original umask so programs will inherit the
     * same file creation mask monit was started with
     */
    umask(Run.umask);

    /*
     * Switch uid/gid if requested
     */
    if(C->has_gid) {
        if(0 != setgid(C->gid)) {
          stat_loc |= setgid_ERROR;
      }
    }
    if(C->has_uid) {
      if(0 != setuid(C->uid)) {
         stat_loc |= setuid_ERROR;
      }
    }

    put_monit_environment(environment);

    if(! Run.isdaemon) {
      int i;
      for(i= 0; i < 3; i++)
        if(close(i) == -1 || open("/dev/null", O_RDWR) != i)
          stat_loc |= redirect_ERROR;
    }

    Util_closeFds();

    setsid();

    pid = fork();
    if(pid < 0) {
      stat_loc |= fork_ERROR;
      _exit(stat_loc);
    }

    if(pid == 0) {
        /*
         * Reset all signals, so the spawned process is *not* created
         * with any inherited SIG_BLOCKs
         */
      sigemptyset(&mask);
      pthread_sigmask(SIG_SETMASK, &mask, NULL);
      signal(SIGINT, SIG_DFL);
      signal(SIGHUP, SIG_DFL);
      signal(SIGTERM, SIG_DFL);
      signal(SIGUSR1, SIG_DFL);
      signal(SIGPIPE, SIG_DFL);
      
      (void) execv(C->arg[0], C->arg);
      _exit(1);
    }

    /* Exit first child and return errors to parent */
    _exit(stat_loc);
  }

  /* Wait for first child - aka second parent, to exit */
  if(waitpid(pid, &stat_loc, 0) != pid) {
      LogError("Waitpid error\n");
  }

  exit_status= WEXITSTATUS(stat_loc);
  if (exit_status & setgid_ERROR)
    LogError("Failed to change gid to '%d' for '%s'\n", C->gid, C->arg[0]);
  if (exit_status & setuid_ERROR)
    LogError("Failed to change uid to '%d' for '%s'\n", C->uid, C->arg[0]);
  if (exit_status & fork_ERROR)
    LogError("Cannot fork a new process for '%s'\n", C->arg[0]);
  if (exit_status & redirect_ERROR)
    LogError("Cannot redirect IO to /dev/null for '%s'\n", C->arg[0]);

  free_monit_environment(&environment);
  ASSERT(environment == NULL);

  /*
   * Restore the signal mask
   */
  pthread_sigmask(SIG_SETMASK, &save, NULL);

  /*
   * We do not need to wait for the second child since we forked twice,
   * the init system-process will wait for it. So we just return
   */
  return ;

} 


/* ----------------------------------------------------------------- Private */


/*
 * Setup the environment with special MONIT_xxx variables. The program
 * executed may use such variable for various purposes.
 */
static void set_monit_environment(Service_T s, 
				  const char *event, 
				  Environment_T *e) {
  
  char buf[STRLEN];
  char date[STRLEN];
  
  Util_getRFC822Date(NULL, date, STRLEN);
  
  snprintf(buf, STRLEN, "MONIT_DATE=%s", date);
  push_monit_environment(buf, e);

  snprintf(buf, STRLEN, "MONIT_SERVICE=%s", s->name);
  push_monit_environment(buf, e);

  snprintf(buf, STRLEN, "MONIT_HOST=%s", Run.localhostname);
  push_monit_environment(buf, e);

  snprintf(buf, STRLEN, "MONIT_EVENT=%s", event?event:"No Event");
  push_monit_environment(buf, e);

  if (s->type == TYPE_PROCESS) {
    snprintf(buf, STRLEN, "MONIT_PROCESS_PID=%d", Util_isProcessRunning(s));
    push_monit_environment(buf, e);

    snprintf(buf, STRLEN, "MONIT_PROCESS_MEMORY=%ld", s->inf->mem_kbyte);
    push_monit_environment(buf, e);

    snprintf(buf, STRLEN, "MONIT_PROCESS_CHILDREN=%d", s->inf->children);
    push_monit_environment(buf, e);

    snprintf(buf, STRLEN, "MONIT_PROCESS_CPU_PERCENT=%d", s->inf->cpu_percent);
    push_monit_environment(buf, e);
  }

}


static void push_monit_environment(char const *env, Environment_T *list) {
  Environment_T e= NULL;
  NEW(e);
  strncpy(e->env, env, STRLEN);
  e->next= *list;
  *list= e;
}


static void put_monit_environment(Environment_T e) {
  while(e != NULL) {
    putenv(e->env);
    e= e->next;
  }
}


static void free_monit_environment(Environment_T *e) {
  if(e&&*e) {
    free_monit_environment(&(*e)->next);
    FREE((*e));
  }
}

