/*******************************************************************************
*	MASTER_BLASTER intelligently (well, ok...selectively) frees up memory  *
*  from user processes.  VMS refuses to free up user memory unless it goes     *
*  into "panic mode", but it is too late then!				       *
*******************************************************************************/

# include	<jpidef.h>
# include	<ssdef.h>
# include	<stdio.h>

# define	SLEEPTIME	5	/* minutes to sleep between cycles   */
# define	TRUE		1	/* logical true */
# define	FALSE		0	/* logical false */
# define	MAXPROCS	200	/* max number of procs handled */

static char	log[] = "master_blaster.log";	/* name of logfile */

struct DESC
{
	unsigned short length;
	unsigned char type;
	unsigned char class;
	char *addr;
};

struct	PROCINFO
{
	int	cpu;			/* CPU time */
	int	flag;			/* General purpose flag */
	int	pid;			/* process identification */
	int	proc_index;		/* process index */
	int	jobtype;		/* job type */
	int	faults;			/* pagefaults */
	int	phys_mem;		/* current memory usage */
	char	username[13];		/* user name */
	int	valid;			/* valid record indicator */
};

struct	ITEM
{
	short	len;			/* buffer length */
	short	item_code;		/* information item requested */
	char	*buf;			/* return buffer */
	int	*rlen;			/* return length */
};

struct	PROCINFO jpi;			/* temporary stash for jpi call */
struct	PROCINFO table_a[MAXPROCS];	/* Actual table A */
struct	PROCINFO table_b[MAXPROCS];	/* Actual table B */

struct	PROCINFO *curr;			/* These two pointers are toggled */
struct	PROCINFO *prev;			     /* to alternate tables A & B */
struct	PROCINFO *curr_begin;		/* Temporary pointer storage */
struct	PROCINFO *prev_begin;		/* Temporary pointer storage */
struct	PROCINFO *temp;			/* Temporary storage during toggle */

struct
{
	struct	ITEM items[7];
	int	endoflist;
} itemlist				=
					{
		/* process id	*/	4 ,JPI$_PID	,&jpi.pid	,0
		/* proc index	*/	,4 ,JPI$_PROC_INDEX,&jpi.proc_index,0
		/* CPU time	*/	,4 ,JPI$_CPUTIM,&jpi.cpu	,0
		/* job type	*/	,4 ,JPI$_JOBTYPE,&jpi.jobtype 	,0
		/* pagefaults	*/	,4 ,JPI$_PAGEFLTS,&jpi.faults	,0
		/* phys mem	*/	,4 ,JPI$_PPGCNT,&jpi.phys_mem	,0
		/* user name	*/	,12,JPI$_USERNAME,jpi.username	,0
					};

FILE	*logfile;			/* file pointer for log file */
short	status;				/* status returned by calls */

int	index_curr;			/* current table index */
int	index_prev;			/* previous table index */
int	counter;			/* general purpose counter */
int	curr_entries;			/* # of entries in current table */

main()
{
/* Initialize the pointers */
	curr = table_a;
	prev = table_b;
	curr_begin = table_a;
	prev_begin = table_b;

/* Ensure that we don't blast anybody the first time out */
	for(prev=prev_begin,counter=0;counter < MAXPROCS;counter++,prev++)
		prev->valid = FALSE;
	prev = prev_begin;

/* Welcome back my friends, to the show that never ends......  EL&P */
	for(;;sleep(SLEEPTIME * 60))
		{
		getjpi_info();			/* load GETJPI data */
		if (curr_entries == 0)		/* Anybody to blast? */
			continue;			/* Nope, so loop */
		blastem();			/* blast the guilty */
		temp = curr_begin;	 	/* toggle   */
		curr_begin = prev_begin; 	/* the      */
		prev_begin = temp;	 	/* pointers */
		}
}

/*******************************************************************************
*	GETJPI_INFO stores information about each processes in the array of
* structures pointed to by curr.  Note that curr is indexed by the getjpi field
* proc_index and a sanity check is done on proc_index so that we do not clobber
* anything.
*******************************************************************************/
int getjpi_info()
{
int	pidcontext;

	itemlist.endoflist =  status = curr_entries = 0;

	/* Zero out validity flags */
	for(curr=curr_begin,counter=0;counter < MAXPROCS;counter++,curr++)
		curr->valid = FALSE;
	curr = curr_begin;

	for(pidcontext = -1;status != SS$_NOMOREPROC;)
		{
		status = sys$getjpiw(1,&pidcontext,0,&itemlist,0,0,0);
		if (status != SS$_NORMAL)
			continue;

		/* Keep interactive and batch processes, skip all others */
		if ((jpi.jobtype != JPI$K_NETWORK) &&
		    (jpi.jobtype != JPI$K_LOCAL) &&
		    (jpi.jobtype != JPI$K_BATCH) &&
		    (jpi.jobtype != JPI$K_DIALUP))
			continue;

		/* Sanity check */
		if ((jpi.proc_index > MAXPROCS) || (jpi.proc_index < 1))
			{
			printf("Encountered Proc_index %d for %08x\n",
				jpi.proc_index, jpi.pid);
			exit(SS$_ABORT);
			}

		/* Set the pointer to the correct PID index */
		curr = curr_begin;
		curr += (jpi.proc_index - 1); /* Element 0 holds proc 1, etc */

		/* Save info */
		curr->pid = jpi.pid;
		curr->cpu = jpi.cpu;
		curr->faults = jpi.faults;
		curr->phys_mem = jpi.phys_mem;
		strncpy(curr->username,jpi.username,12);
		curr->valid = TRUE;
		curr_entries++;
		}
}

int blastem()
{
int	opn_flag = FALSE;
char	*date_time;
int	clock;

	curr = curr_begin;
	prev = prev_begin;

	status = time(&clock);
	date_time = ctime(&clock);
	date_time[19] = '\0';

	for (index_curr=0;index_curr < MAXPROCS;index_curr++,curr++,prev++)
	    {
	    /* Skip this process if;
			o Non-existant or new
			o Not the same process as last time
			o Usercode is "SYSTEM"
			o It has more than 1300 pagefaults/5 min.
			o It was recently blasted or at cli prompt (< 210 pages)
	    */
	    if (((curr->valid != TRUE) || (prev->valid != TRUE)) ||
		(curr->pid != prev->pid) ||
		(strncmp(curr->username,"SYSTEM",6) == 0) ||
		((curr->faults - prev->faults) > 1300) ||
		(curr->phys_mem <= 210))
			continue;

	    /* If process is idle, always blast it.  If it is not idle,
	       blast it if it's hogging memory */
	    if ((curr->cpu - prev->cpu) > 100)
		{		
		if (curr->phys_mem < 900)	/* Is this process a pig? */
			continue;		    /* No, so skip it */
		}

	    if (opn_flag == FALSE)
		{
		logfile = fopen(log,"a");
		opn_flag = TRUE;
		}
	    fprintf(logfile,"%s *Blasted* %s %08x %4d \n",
		date_time,
		curr->username,
		curr->pid,
		curr->phys_mem);

	    blaster(curr->pid);

	    }
	if (opn_flag == TRUE)
		{
		fclose(logfile);
		opn_flag = FALSE;
		}
}
