/* **********************************************************************
 * *	                                                                *
 * *          Routines for Process Creation/Deletion/Management         *
 * *                                                                    *
 * **********************************************************************
 * File: libs/user/process.c
 *
 *     COPYRIGHT (c) 1994, 1996, 1997, 1988, 1999, 2000, 2003  BY
 *         SIGMET INCORPORATED, WESTFORD MASSACHUSETTS, U.S.A.
 * 
 * THIS SOFTWARE IS FURNISHED UNDER A LICENSE AND MAY BE USED AND  COPIED
 * ONLY  IN  ACCORDANCE WITH  THE  TERMS  OF  SUCH  LICENSE  AND WITH THE
 * INCLUSION OF THE ABOVE COPYRIGHT NOTICE.  THIS SOFTWARE  OR  ANY OTHER
 * COPIES  THEREOF MAY NOT BE PROVIDED OR OTHERWISE MADE AVAILABLE TO ANY
 * OTHER PERSON.  NO TITLE TO AND OWNERSHIP OF  THE  SOFTWARE  IS  HEREBY
 * TRANSFERED.
 */
#include <unistd.h>
#include <errno.h>
#include <limits.h>
#include <pwd.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sched.h>
#include <fcntl.h>
#include <sys/resource.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/wait.h>

#ifdef AIX
#include <sys/id.h>
int seteuid( uid_t ) ;		/* Missing from sys include file !! */
#endif

#ifdef IRIX
int seteuid( uid_t ) ;		/* Missing from sys include file !! */
int setruid( uid_t ) ;		/* Missing from sys include file !! */
#endif

#ifdef LINUX			/* Missing from sys include file !! */
int setresuid( uid_t ruid, uid_t euid, uid_t suid ) ;
#endif

#include "sigtypes.h"
#include "user_lib.h"

/* Local function prototypes */
static MESSAGE sig_run_as(uid_t iDesiredUid_a );

/* ********************************************************
 * *                                                      *
 * *  Creation and Deletion of New Processes and Deamons  *
 * *                                                      *
 * ********************************************************
 */

/* ------------------------------
 * Standard function to create a process on any platform.  No command
 * line arguments are passed.
 */
MESSAGE sig_fork_process
( volatile UINT4 *ipid_a,	/* Process ID returned */
  const char *spath_a,		/* Path of file to execute */
  const char *sname_a )		/* Process name */
{
  MESSAGE istatus ; pid_t ipid ;

  ipid = fork() ;
  if( ipid == -1 ) {
    istatus = errno_to_message_va( errno, 1, "sig_fork_process()" ) ;
  }
  /* Child Process Beginning...
   */
  else if( ipid ==  0 ) {
    execl( spath_a, spath_a, sname_a, (char *)NULL ) ;

    /* If we continue on, then the exec() got an error.  Signal it, and
     * kill this process.
     */
    sig_signal( errno_to_message_va( errno, 1, spath_a ) ) ;
    fflush(0);
    _exit(EXIT_FAILURE) ;
  }
  /* Parent Process Continuing...
   */
  else {
    *ipid_a = (UINT4)ipid ;
    istatus = SS_NORMAL;
  }
  return( istatus ) ;
}

/* ------------------------------
 * Standard function to create a process on any platform.  Command
 * line arguments can be passed into the routine.
 */
int sig_fork_execv
( const char *spath_a,		/* Path of file to execute */
  char * const sargv_a[],	/* Vector of arguments to process */
  MESSAGE *istatus_a )		/* Error status returned here */
{
  pid_t ipid ;

  ipid = fork() ;
  if( ipid == -1 ) 
    {
      *istatus_a = errno_to_message_va( errno, 1, "sig_fork_execv()" ) ;
    }
  /* Child Process Beginning...
   */
  else if( ipid ==  0 )
    {
      execv( spath_a, sargv_a );
      
      /* If we continue on, then the execv() got an error.  Signal it, and
       * kill this process.
       */
      sig_signal( errno_to_message_va( errno, 1, spath_a ) ) ;
      fflush(0);
      _exit(EXIT_FAILURE) ;  /*Do not fire exit handler */
    }
  /* Parent Process Continuing...
   */
  else
    {
      *istatus_a = SS_NORMAL;
    }
  return ipid;
}

/* ------------------------------
 * Standard function to create a daemon process (owned by INIT) on any
 * platform.  No command line arguments are passed.
 */
static volatile UINT1 lSigchldDone_c ;
static void SIGCHLD_handler( int isignal_a )
{
  int status ; while( wait3( &status, WNOHANG, 0 ) > 0 ) ;
  lSigchldDone_c = TRUE ;
}

MESSAGE sig_fork_daemon
( const char *spath_a,		/* Path of file to execute */
  const char *sname_a )		/* Process name */
{
  MESSAGE istatus ; struct sigaction newact, oldact ;

  /* Setup a SIGCHLD handler to catch the signal that we know will be
   * generated when the child process becomes a detached daemon.  Keep
   * the old handler so that we can restore it on exit.
   */
  sigemptyset( &newact.sa_mask ) ;
  newact.sa_handler = SIGCHLD_handler ;
  newact.sa_flags   = SA_RESTART ; lSigchldDone_c = FALSE ;
  if( sigaction( SIGCHLD, &newact, &oldact ) < 0 ) {
    istatus = errno_to_message_va( errno, 1, "sig_fork_daemon/sigaction()" ) ;
  }
  else {
    pid_t ipid = fork() ;
    if( ipid == -1 ) {
      istatus = errno_to_message_va( errno, 1, "sig_fork_process()" ) ;
    }
    else if( ipid ==  0 ) {
      /* Child Process Beginning.  Become a detached daemon, and begin
       * execution of the desired image.
       */
      sig_daemon_init() ;
      execl( spath_a, spath_a, sname_a, (char *)NULL ) ;

      /* If we continue on, then the exec() got an error.  Signal it, and
       * kill this process.
       */
      sig_signal( errno_to_message_va( errno, 1, spath_a ) ) ;
      fflush(0) ; _exit(EXIT_FAILURE) ;
    }
    else {
      /* Parent Process Continuing.  Wait for the SIGCHLD that we know
       * will be coming soon.
       */
      while( ! lSigchldDone_c ) sched_yield() ;
      istatus = SS_NORMAL ;
    }
    /* Restore the original SIGCHLD handler.
     */
    sigaction( SIGCHLD, &oldact, NULL ) ;
  }
  return( istatus ) ;
}

/* ------------------------------
 * Free up a process from inherited state.  Motivated by program 13.1
 * from Advanced Programming in the UNIX Environment.
 */
MESSAGE sig_daemon_init(void)
{
  pid_t pid; int iFdLimit, iFd;

  if( (pid = fork()) < 0 )
    return( errno_to_message( errno ) ) ;
  else if( pid != 0 )
    _exit(0);			/* Parent goes away */

  /* Child continues
   */
  setsid();			/* Become session leader */
  chdir("/");			/* Change working directory */
  umask(0);			/* Clear the umask */

  iFdLimit = sig_open_max();	/* Close all file descriptors */
  for( iFd=0 ; iFd < iFdLimit ; iFd++ ) close(iFd);

  /* Assign standard I/O to /dev/null so that any subsequent user I/O
   * is discarded without generating errors.
   */
  if( STDIN_FILENO  != open( "/dev/null", O_RDWR ) ) return( USER_DAEMON_FD ) ;
  if( STDOUT_FILENO != open( "/dev/null", O_RDWR ) ) return( USER_DAEMON_FD ) ;
  if( STDERR_FILENO != open( "/dev/null", O_RDWR ) ) return( USER_DAEMON_FD ) ;

  return( SS_NORMAL ) ;
}

/* ------------------------------
 * Fork and exec and create pipe(s) to child.
 */
int sig_fork_execv_pipe
( const char *spath_a,       /* Program to execv */
  char * const sargv_a[],    /* Args to execv */
  int *pwrite_pipe_a,        /* Write end of pipe returned here, NULL if not wanted */
  int *pread_pipe_a,         /* Read end of pipe returned here, NULL if not wanted */
  MESSAGE *istatus_a)        /* Error return info */
{
#define MAX_EXECV_ARGS 10
  int ierror, iargc, ireadfd = -1, iwritefd = -1 ;
  pid_t ipid ;
  char sread_pipe[10], swrite_pipe[10], *sargv[MAX_EXECV_ARGS];
  char sarg[10];
  
  if (pwrite_pipe_a)
    {
      int pipe_to_child[2];
      ierror = pipe(pipe_to_child);
      if( ierror ) {
	*istatus_a = errno_to_message_va( errno, 1, spath_a ) ;
	return 0 ;
      }
      *pwrite_pipe_a = pipe_to_child[1];
      ireadfd = dup( pipe_to_child[0]);
      ierror = close( pipe_to_child[0]);  /* Close old read fd */
      if (-1==ierror) perror( "close" );
      sprintf( sread_pipe, "%d", ireadfd );
    }

  if (pread_pipe_a)
    {
      int pipe_from_child[2];
      ierror = pipe(pipe_from_child);
      if( ierror ) {
	*istatus_a = errno_to_message_va( errno, 1, spath_a ) ;
	return 0 ;
      }
      *pread_pipe_a = pipe_from_child[0];
      iwritefd = dup( pipe_from_child[1]);
      ierror = close( pipe_from_child[1]);  /* Close old read fd */
      if (-1==ierror) perror( "close" );
      sprintf( swrite_pipe, "%d", iwritefd );
    }

  iargc = 0;

  /* Needs room for two args and null
   */
  while( sargv_a[iargc] && (iargc<(MAX_EXECV_ARGS-4)))
    {
      sargv[iargc] = sargv_a[iargc];
      iargc++;
    }
  strcpy(sarg, "-pipe"); 
  sargv[iargc++] = sarg;
  if (pwrite_pipe_a) sargv[iargc++] = sread_pipe;
  if (pread_pipe_a)  sargv[iargc++] = swrite_pipe;
  sargv[iargc++] = NULL;

  /* Fork and execv is done here inline so we can close the pipes
   * after the forks.
   */
  ipid = fork() ;
  if( ipid == -1 ) 
    {
      *istatus_a = errno_to_message_va( errno, 1, spath_a ) ;
    }
  /* Child Process Beginning...
   */
  else if( ipid ==  0 )
    {
      if (pwrite_pipe_a) 
	{ ierror = close( *pwrite_pipe_a); if (-1==ierror) perror( "close" ); }
      if (pread_pipe_a) 
	{ ierror = close( *pread_pipe_a);  if (-1==ierror) perror( "close" ); }
      execv( spath_a, sargv );
      
      /* If we continue on, then the execv() got an error.  Signal it,
       * and kill this process.
       */
      sig_signal( errno_to_message_va( errno, 1, spath_a ) ) ;
      fflush(0) ; _exit(EXIT_FAILURE) ;  /* Do not fire exit handlers */
    }
  /* Parent Process Continuing...
   */
  else
    {
      *istatus_a = SS_NORMAL;
    }
  
  if (pwrite_pipe_a) 
    { ierror = close( ireadfd );  if (-1==ierror) perror( "close" ); }
  if (pread_pipe_a) 
    { ierror = close( iwritefd ); if (-1==ierror) perror( "close" ); }

  return ipid;
}

/* ------------------------------
 * Standard function to kill a process on any platform.
 */
MESSAGE sig_kill_process
( UINT4 ipid_a )     /* Process ID to kill */
{
  MESSAGE istatus = SS_NORMAL;

  if( (ipid_a >= 2) && (ipid_a <= 0x7FFFFFFF) )
    {
      if( kill( (pid_t)ipid_a, SIGKILL ) == -1 )
	istatus = errno_to_message_va( errno, 2, "sig_kill_process(%d)", ipid_a ) ;
    }

  return  istatus;
}

/* ------------------------------
 * Standard function to check to see if a process exists on any
 * platform.
 */
UINT2 lsig_process_exists
( volatile const UINT4 ipid_a )	/* Process ID to check for */
{
  UINT2 lexists;
  {
    /* The getpriority() method should work on UNIX too, but a bug in
     * the SGI version of that routine forces a different approach
     * using kill() with a NULL signal value (i.e., a signal that
     * never actually gets sent).
     */
    if( (ipid_a >= 2) && (ipid_a <= 0x7FFFFFFF) ) {
      if( kill( (pid_t)ipid_a, 0 ) == -1 ) {
	lexists = FALSE;
	if( errno != ESRCH )
	  sig_signal( errno_to_message_va
		      ( errno, 2, "lsig_process_exists(%d)", ipid_a ) ) ;
      } else {
	lexists = TRUE;
      }
    } else {      
      lexists = FALSE;
    }
  }

  return lexists;
}

/* ********************************************
 * *                                          *   
 * *  Modify Attributes of Running Processes  *
 * *                                          *
 * ********************************************
 */

/* ------------------------------
 * Set process priority, making as few assumptions as possible.
 */
MESSAGE sig_setpriority( SINT4 ipriority_a ) {
  MESSAGE istatus ;

  if( setpriority( PRIO_PROCESS, 0, ipriority_a ) )
    {
      istatus = sig_run_as_root() ;
      if( istatus == SS_NORMAL ) {
	if( setpriority( PRIO_PROCESS, 0, ipriority_a ) )
	  istatus = errno_to_message_va( errno, 2, "sig_setpriority(%d)", ipriority_a ) ;
	if( sig_run_as_operator() != SS_NORMAL )
	  if( istatus == SS_NORMAL )
	    istatus = errno_to_message_va( errno, 2, "sig_setpriority(%d)", ipriority_a ) ;
      }
    }
  else
    {
      istatus = SS_NORMAL;
    }

  return( istatus ) ;
}

/* ------------------------------
 * Routines follow to switch the effective user id...
 */
#define ROOT_UID 0

#ifdef AIX
static char set_saved_c = FALSE ;
#endif
#ifdef IRIX
static char set_saved_c = FALSE ;
#endif

/* ------------------------------
 * Continue execution with our effective user ID set to "operator".
 * When we become "operator", we write the "saved UID" slot so that we
 * can get back to the initial state.
 */
MESSAGE sig_run_as_operator()
{
  static uid_t operator_uid = -1 ;
  MESSAGE istatus;


  if( operator_uid == -1 )
    {
      struct passwd *pwp ; 
      
      sig_username() ;     /* Snap up before loosing it... */

      pwp = getpwnam( "operator" ) ;
      if( pwp != NULL ) {
	operator_uid = pwp->pw_uid ;
      } else {
	return( USER_CANTUID_OPER ) ;
      }
    }

  istatus = sig_run_as( operator_uid );

  return( istatus ) ;
}

/* ------------------------------
 * Continue execution with our effective user ID set to the original
 * real UID.  When we become the real, we write the "saved UID" slot
 * so that we can get back to other states.
 */
MESSAGE sig_run_as_real()
{
  static uid_t iRealUid = -1 ;
  MESSAGE istatus;

  if( iRealUid == -1 )
    {
      struct passwd *pwp ; 
      const char *sRealUser;
      
      sRealUser = sig_username() ;     /* Get and latch real username... */
      
      pwp = getpwnam( sRealUser ) ;
      if( pwp != NULL ) {
	iRealUid = pwp->pw_uid ;
      } else {
	return( USER_CANTUID_OPER ) ;
      }
    }

  istatus = sig_run_as( iRealUid );

  return( istatus ) ;
}

/* ------------------------------
 * Set the effective UID to that supplied.
 */
static MESSAGE sig_run_as(uid_t iDesiredUid_a )
{
#if defined(HPUX) || defined(LINUX)
  if( setresuid( -1, iDesiredUid_a, ROOT_UID ) )
    if( setresuid( -1, iDesiredUid_a, -1 ) )
      return( USER_CANTBE_OPER ) ;
#endif

#ifdef AIX
  /* On AIX, we have no choice but to alter the real user ID to
   * "operator".  This is because the real and effective ID's must
   * both be changed whenever the saved ID is changed, and the real ID
   * can not be changed back without possibly later preventing the
   * effective ID from being able to go to "operator".
   */
  if (!set_saved_c)
    {
      int ierror = setuidx( ID_REAL | ID_EFFECTIVE | ID_SAVED, ROOT_UID ) ;
      if (ierror) perror( "setuidx root" );
      set_saved_c = TRUE ;
    }
  if( setuidx( ID_REAL|ID_EFFECTIVE, iDesiredUid_a ) ) return( USER_CANTBE_OPER ) ;
#endif

#ifdef IRIX
  /* On IRIX, the only way to set the saved ID is to change them all.
   * However we can then restore just the real ID to its original
   * value.  From then on, we only change the effective ID.
   */
  if( !set_saved_c)
    {
      int ruid ;
      ruid = getuid() ; setuid( ROOT_UID ) ; setruid( ruid ) ;
      set_saved_c = TRUE;
    }
  if( seteuid( iDesiredUid_a ) )
    return( USER_CANTBE_OPER ) ;
#endif

  return( SS_NORMAL ) ;
}

/* ------------------------------
 * Continue execution with our effective user ID set to "root".
 */
MESSAGE sig_run_as_root()
{
  sig_username() ;     /* Snap up before loosing it... */

#if defined(HPUX) || defined(LINUX)
  if( setresuid( -1, ROOT_UID, -1 ) )
    return( USER_CANTBE_ROOT ) ;
#endif

#ifdef AIX
  if (!set_saved_c)
    {
      int ierror = setuidx( ID_REAL | ID_EFFECTIVE | ID_SAVED, ROOT_UID );
      if (ierror) perror( "setuidx root" );
      set_saved_c = TRUE ;
    }
  if( setuidx( ID_REAL|ID_EFFECTIVE, ROOT_UID ) ) return( USER_CANTBE_ROOT ) ;
#endif

#ifdef IRIX
  if( seteuid( ROOT_UID ) )
    return( USER_CANTBE_ROOT ) ;
#endif

  return( SS_NORMAL ) ;
}

/* ------------------------------
 * Get the process name through OS dependent mechanism
 */
void get_process_name
( char *sname_a,
  SINT4 isize_a,		/* Size of name storage */
  int argc_a,
  char **argv_a )
{
  /* Get the process name from the second command argument
   */
  if (argc_a >= 2)
    {
      strncpy( sname_a, argv_a[1], isize_a );
      sname_a[isize_a-1]=0;
    }
  else
    {
      sname_a[0]=0;
    }
}

/* ------------------------------
 * Return a best guess for the maximum number of possible open file
 * descriptors.  Motivated by program 2.3 from Advanced Programming in
 * the UNIX Environment.
 */
int sig_open_max(void)
{
#define OPEN_MAX_GUESS (257)
  int iOpenMax;

#ifdef OPEN_MAX
  iOpenMax = OPEN_MAX;
#else
  errno=0;
  if ((iOpenMax = sysconf(_SC_OPEN_MAX)) < 0)
    {
      if (errno!=0) 
	sig_signal( errno_to_message_va( errno, 1, "sysconfig(_SC_OPEN_MAX)"));
      iOpenMax = OPEN_MAX_GUESS;
    }
#endif
  return iOpenMax;
}
