/*
 * COPYRIGHT (c) International Business Machines Corp. 2001-2017
 *
 * This program is provided under the terms of the Common Public License,
 * version 1.0 (CPL-1.0). Any use, reproduction or distribution for this
 * software constitutes recipient's acceptance of CPL-1.0 terms which can be
 * found in the file LICENSE file or at
 * https://opensource.org/licenses/cpl1.0.php
 */

#include <unistd.h>
#include <errno.h>
#include <signal.h>
#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <fcntl.h>

#include "log.h"
#include "slotmgr.h"
#include "pkcsslotd.h"
#include "err.h"

#define PROC_BASE "/proc"

extern BOOL GCBlockSignals (void);

#if !defined(NOGARBAGE)

#include "garbage_linux.h"

BOOL                IsValidProcessEntry     ( pid_t_64 pid, time_t_64 RegTime );

int                 Stat2Proc               ( int pid, proc_t *p );

 pthread_t    GCThread;                /* Garbage Collection thread's handle */
static BOOL         ThreadRunning = FALSE;   /* If we're already running or not */

#if THREADED
static void        *GCMain                  ( void *Ptr );
static void         GCCancel                ( void *Ptr );
#else
void        *GCMain                  ( void *Ptr );
void         GCCancel                ( void *Ptr );
#endif





/*********************************************************************************
 * StartGCThread -
 *
 *   Entry point that starts the garbage collection thread
 *
 *********************************************************************************/

BOOL StartGCThread ( Slot_Mgr_Shr_t *MemPtr ) {

  int err;

#if !(THREADED)
  return TRUE;
#endif

  if ( ThreadRunning ) {
    DbgLog (DL0, "StartGCThread: Thread already running.");
    return FALSE;
  }

  if ( ( err = pthread_create( &GCThread, NULL, GCMain, ((void *)MemPtr) ) ) != 0 ) {
    DbgLog(DL0,"StartGCThread: pthread_create returned %s (%d; %#x)", SysConst(err), err, err );
    return FALSE;
  }

  ThreadRunning = TRUE;

#ifdef DEV
  // Only development builds
  LogLog ( "StartGCThread: garbage collection thread started as ID %d (%#x) by ID %d (%#x)", GCThread, GCThread, pthread_self(), pthread_self() );
#endif
  return TRUE;

}




/*********************************************************************************
 * StopGCThread -
 *
 *   Entry point which causes the Garbage collection thread to terminate
 *   Waits for the thread to terminate before continuing
 *
 *********************************************************************************/

BOOL StopGCThread ( void *Ptr ) {
  int err;

  void *Status;

#if !(THREADED)
  return TRUE;
#endif
  if ( ! ThreadRunning ) {
    DbgLog(DL0,"StopGCThread was called when the garbage collection thread was not running");
    return FALSE;
  }

  DbgLog(DL0, "StopGCThread: tid %d is stopping the garbage collection thread (tid %d)", pthread_self(), GCThread);

  /* Cause the GC thread to be cancelled */
  if ( ( err = pthread_cancel(GCThread)) != 0 ) {
    DbgLog(DL0,"StopGCThread: pthread_cancel returned %s (%d; %#x)", SysConst(err), err, err );
    return FALSE;
  }

  /* Synchronize with the GC thread (aka: wait for it to terminate) */
  if ( (err = pthread_join ( GCThread, &Status ) ) != 0 ) {
    DbgLog(DL0,"StopGCThread: pthread_join returned %s (%d; %#x)", SysConst(err), err, err );
    return FALSE;
  }

  if ( Status != PTHREAD_CANCELED ) {
    DbgLog(DL0,"Hmm.  Thread was cancelled, but didn't return the appropriate return status");
  }

  ThreadRunning = FALSE;
  return TRUE;

}






/*********************************************************************************
 * GCMain -
 *
 *     The Garbage collection thread's main()
 *     Basically, run until cancelled by another thread
 *
 *********************************************************************************/

void *GCMain ( void *Ptr) {

#if THREADED
  int                           OrigCancelState;
  int                           OrigCancelType;
  int                           LastCancelState;
#endif
  Slot_Mgr_Shr_t               *MemPtr              = (Slot_Mgr_Shr_t *) Ptr;



  ASSERT ( MemPtr != NULL );


  sleep(2); //  SAB  Linux likes to have us delay
// Linux threading model appears to have some issues with regards to
// signals....  Need to look at this FIXME..

  /* setup */
  /* Block the signals that go to the main thread */
  /* FIXME: We probably want to make it so that signals go only to the main thread by default */
// SBADE ....  FIXME... remove the blocking of signals see what happens..
//  GCBlockSignals();


  /* Make it so that we can only be cancelled when we reach a cancellation point */
  /* cancellation points are listed here: http://techlib.austin.ibm.com/techlib/manuals/adoclib/aixprggd/genprogc/termthre.htm#D3A4499176manu */
  /* PTHREAD_CANCEL_DEFERRED should be the default */
#if THREADED
  pthread_setcancelstate ( PTHREAD_CANCEL_ENABLE,  &OrigCancelState );
  pthread_setcanceltype  ( PTHREAD_CANCEL_DEFERRED, &OrigCancelType  );

  /* push cleanup routines */
  pthread_cleanup_push ( GCCancel, MemPtr );
#endif

    DbgLog(DL0, "Garbage collection running... PID %d\n",getpid());
  while ( 1 ) {

    DbgLog(DL0, "Garbage collection running...");

    /* Don't allow cancellations while mucking with shared memory or holding mutexes */
#if THREADED
    pthread_setcancelstate ( PTHREAD_CANCEL_DISABLE, &LastCancelState );

#endif

    CheckForGarbage(MemPtr);

#if THREADED
    /* re-enable cancellations */
    pthread_setcancelstate ( PTHREAD_CANCEL_ENABLE, &LastCancelState );

    /* Test for cancellation by the main thread */
    pthread_testcancel();
#endif

    DbgLog(DL5, "Garbage collection finished.");

    /* now we pause */
    sleep(10);

  } /* end while 1 */


#if THREADED
  /* Yeah, yeah.  Has to be here because some implementations use macros that have to be balanced */
  pthread_cleanup_pop ( 0 );
#endif

  /* return implicitly calls pthread_cancel() */
  /* but it'll never really get executed; pthread_testcancel() implicitly calls pthread_exit() if there's a cancellation pending */
  return NULL;

}






/*********************************************************************************
 * GCCancel -
 *
 *       Cleanup routine called when Garbage collection thread exits/is cancelled
 *
 *********************************************************************************/

void GCCancel ( void *Ptr ) {

  /* Yeah, yeah.  Doesn't do anything, but I had plans */
  DbgLog(DL3, "GCCancel: tid: %d running cleanup routine", pthread_self());

  return;

}






/*********************************************************************************
 * CheckForGarbage -
 *
 *       The routine that actually does cleanup
 *
 *********************************************************************************/

BOOL CheckForGarbage ( Slot_Mgr_Shr_t *MemPtr ) {

  int               SlotIndex;
  int               ProcIndex;
  int               Err;
  BOOL              ValidPid;

  ASSERT( MemPtr != NULL_PTR );
  #ifdef DEV
    DbgLog(DL5, "Thread %d is checking for garbage", pthread_self());
  #endif /* DEV */


  #ifdef DEV
    DbgLog (DL5, "Garbage collection attempting global shared memory lock");
  #endif /* DEV */

  /* Grab the global Shared mem mutex since we might modify global_session_count */

    Err = XProcLock();
  if ( Err != TRUE ) {
    DbgLog (DL0, "Garbage collection: Locking attempt for global shmem mutex returned %s", SysConst(Err) );
    return FALSE;
  }

  #ifdef DEV
  DbgLog ( DL5, "Garbage collection: Got global shared memory lock");
  #endif /* DEV */


  for ( ProcIndex = 0; ProcIndex < NUMBER_PROCESSES_ALLOWED; ProcIndex++ ) {

    Slot_Mgr_Proc_t_64 *pProc = &(MemPtr->proc_table[ProcIndex]);

    ASSERT(pProc != NULL_PTR);

    if ( ! (pProc->inuse) ) {
      continue;
    }

    ValidPid = ( (IsValidProcessEntry ( pProc->proc_id, pProc->reg_time )) && (pProc->proc_id != 0) );


    if ( ( pProc->inuse ) && (! ValidPid ) ) {

      #ifdef DEV
        DbgLog(DL1, "Garbage collection routine found bad entry for pid %d (Index: %d); removing from table", pProc->proc_id, ProcIndex );
      #endif /* DEV */

      /*                         */
      /* Clean up session counts */
      /*                         */
      for ( SlotIndex = 0; SlotIndex < NUMBER_SLOTS_MANAGED; SlotIndex++ ) {

	unsigned int *pGlobalSessions =
		      &(MemPtr->slot_global_sessions[SlotIndex]);
	unsigned int *pProcSessions = &(pProc->slot_session_count[SlotIndex]);

	if ( *pProcSessions > 0 ) {

  	  #ifdef DEV
	    DbgLog ( DL2, "GC: Invalid pid (%d) is holding %d sessions open on slot %d.  Global session count for this slot is %d", pProc->proc_id, *pProcSessions, SlotIndex, *pGlobalSessions );
          #endif /* DEV */

	  if ( *pProcSessions > *pGlobalSessions ) {
	    #ifdef DEV
	      WarnLog ( "Garbage Collection: Illegal values in table for defunct process");
	      DbgLog  ( DL0, "Garbage collection: A process ( Index: %d, pid: %d ) showed %d sessions open on slot %s, but the global count for this slot is only %d",
			ProcIndex, pProc->proc_id, *pProcSessions, SlotIndex, *pGlobalSessions );
	    #endif /* DEV */
	    *pGlobalSessions = 0;
	  } else {
	    *pGlobalSessions -= *pProcSessions;
	  }

	  *pProcSessions = 0;

	} /* end if *pProcSessions */

      } /* end for SlotIndex */


      /*                                      */
      /* NULL out everything except the mutex */
      /*                                      */

      memset( &(pProc->inuse),              '\0', sizeof(pProc->inuse) );
      memset( &(pProc->proc_id),            '\0', sizeof(pProc->proc_id) );
      memset( &(pProc->slotmap),            '\0', sizeof(pProc->slotmap) );
      memset( &(pProc->blocking),           '\0', sizeof(pProc->blocking ));
      memset( &(pProc->error),              '\0', sizeof(pProc->error) );
      memset( &(pProc->slot_session_count), '\0', sizeof(pProc->slot_session_count) );
      memset( &(pProc->reg_time),           '\0', sizeof(pProc->reg_time) );

    } /* end if inuse && ValidPid */

  } /* end for ProcIndex */

  XProcUnLock();
  DbgLog ( DL5, "Garbage collection: Released global shared memory lock");

  return TRUE;

}



/*********************************************************************************
 * Stat2Proc -
 *
 *     Fills a proc_t structure (defined in garbage_linux.h)
 *     with a given pid's stat information found in the /proc/<pid>/stat file
 *
 *********************************************************************************/

int Stat2Proc (int pid, proc_t *p) {

  char buf[800];  // about 40 fields, 64-bit decimal is about 20 chars
  char fbuf[800];  // about 40 fields, 64-bit decimal is about 20 chars
  char *tmp;
  int fd, num;
  //  FILE *fp;

  //  sprintf(buf, "%s/%d/stat", PROC_BASE, pid);
  //  if( (fp = fopen(buf, "r")) == NULL )
  //    return FALSE;

  sprintf(fbuf, "%s/%d/stat", PROC_BASE, pid);
  printf("Buff = %s \n",fbuf); fflush(stdout);
  if( (fd = open(fbuf, O_RDONLY, 0)) == -1)
    return FALSE;

  num = read(fd, buf, 800);

  close(fd);

  if(num < 80)
    return FALSE;

  // buf[num] = '\0';

  tmp = strrchr(buf, ')');                      // split into "PID (cmd" and "<rest>"
  *tmp = '\0';                            // replacing trailing ')' with NULL
  // Tmp now points to the rest of the buffer.
  // buff points to the command...


  /* fill in default values for older kernels */
  p->exit_signal = SIGCHLD;
  p->processor = 0;

  /* now parse the two strings, tmp & buf, separately, skipping the leading "(" */
  memset(p->cmd, 0, sizeof(p->cmd));
  sscanf(buf, "%d (%15c", &p->pid, p->cmd);      // comm[16] in kernel
  num = sscanf(tmp + 2,                         // skip space after ')' as well
	       "%c "
	       "%d %d %d %d %d "
	       "%lu %lu %lu %lu %lu %lu %lu "
	       "%ld %ld %ld %ld %ld %ld "
	       "%lu %lu "
	       "%ld "
	       "%lu %lu %lu %lu %lu %lu "
	       "%*s %*s %*s %*s "               // discard, no RT signals & Linux 2.1 used hex (no use for RT signals)
	       "%lu %lu %lu "
	       "%d %d",
	       &p->state,
	       &p->ppid, &p->pgrp, &p->session, &p->tty, &p->tpgid,
	       &p->flags, &p->min_flt, &p->cmin_flt, &p->maj_flt, &p->cmaj_flt, &p->utime, &p->stime,
	       &p->cutime, &p->cstime, &p->priority, &p->nice, &p->timeout, &p->it_real_value,
	       &p->start_time, &p->vsize,
	       &p->rss,
	       &p->rss_rlim, &p->start_code, &p->end_code, &p->start_stack, &p->kstk_esp, &p->kstk_eip,
	       /*  p->signal, p->blocked, p->sigignore, p->sigcatch,  */ /* can't use */
	       &p->wchan, &p->nswap, &p->cnswap,
	       /* -- Linux 2.0.35 ends here -- */
	       &p->exit_signal, &p->processor  /* 2.2.1 ends with "exit_signal" */
	       /* -- Linux 2.2.8 and 2.3.47 end here -- */
	       );

  /* fprintf(stderr, "Stat2Proc() converted %d fields.\n", num); */
  if (p->tty == 0)
    p->tty = -1;     // the old notty val, updated elsewhere before moving to 0

  p->vsize /= 1024;

  if(num < 30)
    return FALSE;
  if(p->pid != pid)
    return FALSE;

  return TRUE;

}



/*********************************************************************************
 * IsValidProcessEntry -
 *
 *     Checks to see if the process identifed by pid is the same process
 *     that registered with us
 *
 *********************************************************************************/

BOOL IsValidProcessEntry ( pid_t_64 pid, time_t_64 RegTime ) {

  int                    Err;
  int valid;
  proc_t *p;
  proc_t procstore;

  /* If kill(pid, 0) returns -1 and errno/Err = ESRCH, the pid doesn't exist */
  if ( kill(pid, 0) == -1 ) {
    Err = errno;
    if ( Err == ESRCH ) {
      /* The process was not found */
      DbgLog(DL3, "IsValidProcessEntry: PID %d was not found in the process table (kill() returned %s)", pid, SysConst(Err) );
      return FALSE;
    } else {
      /* some other error occurred */
      DbgLog(DL3, "IsValidProcessEntry: kill() returned %s (%d; %#x)", SysConst(Err), Err, Err);
      return FALSE;
    }
  } /* end if kill */

  /* Okay, the process exists, now we see if it's really ours */

#ifdef ALLOCATE
  p = (proc_t *)malloc(sizeof(proc_t));
#else
  p = &procstore;
  memset(p, 0, sizeof(proc_t));
#endif

  if( !(valid = Stat2Proc( (int)pid, p )) )
    return FALSE;

  if ( p->pid == pid ) {
    if ( RegTime >= p->start_time ) {   // checking for matching start times
      return TRUE;
    } else {
      /* p->start_time contains the time at which the process began ??  (22nd element in /proc/<pid>/stat file) */
      DbgLog(DL1, "IsValidProcessEntry: PID %d started at %lu; registered at %ld", pid, p->start_time, RegTime);
      DbgLog(DL4, "IsValidProcessEntry: PID Returned %d flags at %#x; state at %#x", p->pid, p->flags, p->state);
    }
  }

  return FALSE;

}

#endif // NO Garbage
