/* ProcFS - pid.c - by Alen Stojanov and David van Moolenbroek */

#include "inc.h"

#include <sys/mman.h>
#include <minix/vm.h>

#define S_FRAME_SIZE	4096		/* use malloc if larger than this */
PRIVATE char s_frame[S_FRAME_SIZE];	/* static storage for process frame */
PRIVATE char *frame;			/* pointer to process frame buffer */

FORWARD _PROTOTYPE( void pid_psinfo, (int slot)				);
FORWARD _PROTOTYPE( void pid_cmdline, (int slot)			);
FORWARD _PROTOTYPE( void pid_environ, (int slot)			);
FORWARD _PROTOTYPE( void pid_map, (int slot)				);

/* The files that are dynamically created in each PID directory. The data field
 * contains each file's read function. Subdirectories are not yet supported.
 */
PUBLIC struct file pid_files[] = {
	{ "psinfo",	REG_ALL_MODE,	(data_t) pid_psinfo	},
	{ "cmdline",	REG_ALL_MODE,	(data_t) pid_cmdline	},
	{ "environ",	REG_ALL_MODE,	(data_t) pid_environ	},
	{ "map",	REG_ALL_MODE,	(data_t) pid_map	},
	{ NULL,		0,		(data_t) NULL		}
};

/*===========================================================================*
 *				is_zombie				     *
 *===========================================================================*/
PRIVATE int is_zombie(int slot)
{
	/* Is the given slot a zombie process?
	 */

	return (slot >= NR_TASKS && 
		(mproc[slot - NR_TASKS].mp_flags & (TRACE_ZOMBIE | ZOMBIE)));
}

/*===========================================================================*
 *				pid_psinfo				     *
 *===========================================================================*/
PRIVATE void pid_psinfo(int i)
{
	/* Print information used by ps(1) and top(1).
	 */
	int pi, task, state, type, p_state, f_state;
	char name[PROC_NAME_LEN+1], *p;
	struct vm_usage_info vui;
	pid_t ppid;

	pi = i - NR_TASKS;
	task = proc[i].p_nr < 0;

	/* Get the name of the process. Spaces would mess up the format.. */
	if (task || mproc[i].mp_name[0] == 0)
		strncpy(name, proc[i].p_name, sizeof(name) - 1);
	else
		strncpy(name, mproc[pi].mp_name, sizeof(name) - 1);
	name[sizeof(name) - 1] = 0;
	if ((p = strchr(name, ' ')) != NULL)
		p[0] = 0;

	/* Get the type of the process. */
	if (task)
		type = TYPE_TASK;
	else if (mproc[i].mp_flags & PRIV_PROC)
		type = TYPE_SYSTEM;
	else
		type = TYPE_USER;

	/* Get the state of the process. */
	if (!task) {
		if (is_zombie(i))
			state = STATE_ZOMBIE;	/* zombie */
		else if (mproc[pi].mp_flags & STOPPED)
			state = STATE_STOP;	/* stopped (traced) */
		else if (proc[i].p_rts_flags == 0)
			state = STATE_RUN;	/* in run-queue */
		else if (fp_is_blocked(&fproc[pi]) ||
		(mproc[pi].mp_flags & (WAITING | PAUSED | SIGSUSPENDED)))
			state = STATE_SLEEP;	/* sleeping */
		else
			state = STATE_WAIT;	/* waiting */
	} else {
		if (proc[i].p_rts_flags == 0)
			state = STATE_RUN;	/* in run-queue */
		else
			state = STATE_WAIT;	/* other i.e. waiting */
	}

	/* We assume that even if a process has become a zombie, its kernel
	 * proc entry still contains the old (but valid) information. Currently
	 * this is true, but in the future we may have to filter some fields.
	 */
	buf_printf("%d %c %d %s %c %d %d %lu %lu %lu %lu",
		PSINFO_VERSION,			/* information version */
		type,				/* process type */
		(int) proc[i].p_endpoint,	/* process endpoint */
		name,				/* process name */
		state,				/* process state letter */
		(int) P_BLOCKEDON(&proc[i]),	/* endpt blocked on, or NONE */
		(int) proc[i].p_priority,	/* process priority */
		(long) proc[i].p_user_time,	/* user time */
		(long) proc[i].p_sys_time,	/* system time */
		ex64hi(proc[i].p_cycles),	/* execution cycles */
		ex64lo(proc[i].p_cycles)
	);

	/* If the process is not a kernel task, we add some extra info. */
	if (!task) {
		memset(&vui, 0, sizeof(vui));

		if (!is_zombie(i)) {
			/* We don't care if this fails. It may still return
			 * zero memory usage for processes that don't have a
			 * pagetable, though. Look at vui_total instead.
			 */
			(void) vm_info_usage(proc[i].p_endpoint, &vui);

			if (vui.vui_total == 0L) {
				vui.vui_total =
					(proc[i].p_memmap[T].mem_len +
					proc[i].p_memmap[D].mem_len) <<
					CLICK_SHIFT;
			}
		}

		if (mproc[pi].mp_flags & PAUSED)
			p_state = PSTATE_PAUSED;
		else if (mproc[pi].mp_flags & WAITING)
			p_state = PSTATE_WAITING;
		else if (mproc[pi].mp_flags & SIGSUSPENDED)
			p_state = PSTATE_SIGSUSP;
		else
			p_state = '-';

		if (mproc[pi].mp_parent == pi)
			ppid = NO_PID;
		else
			ppid = mproc[mproc[pi].mp_parent].mp_pid;

		switch (fproc[pi].fp_blocked_on) {
		case FP_BLOCKED_ON_NONE:	f_state = FSTATE_NONE; break;
		case FP_BLOCKED_ON_PIPE:	f_state = FSTATE_PIPE; break;
		case FP_BLOCKED_ON_LOCK:	f_state = FSTATE_LOCK; break;
		case FP_BLOCKED_ON_POPEN:	f_state = FSTATE_POPEN; break;
		case FP_BLOCKED_ON_SELECT:	f_state = FSTATE_SELECT; break;
		case FP_BLOCKED_ON_DOPEN:	f_state = FSTATE_DOPEN; break;
		case FP_BLOCKED_ON_OTHER:	f_state = FSTATE_TASK; break;
		default:			f_state = FSTATE_UNKNOWN;
		}

		buf_printf(" %lu %lu %lu %c %d %u %u %u %d %c %d %u",
			vui.vui_total,			/* total memory */
			vui.vui_common,			/* common memory */
			vui.vui_shared,			/* shared memory */
			p_state,			/* sleep state */
			ppid,				/* parent PID */
			mproc[pi].mp_realuid,		/* real UID */
			mproc[pi].mp_effuid,		/* effective UID */
			mproc[pi].mp_procgrp,		/* process group */
			mproc[pi].mp_nice,		/* nice value */
			f_state,			/* VFS block state */
			(int) (fproc[pi].fp_blocked_on == FP_BLOCKED_ON_OTHER)
				? fproc[pi].fp_task : NONE, /* block proc */
			fproc[pi].fp_tty		/* controlling tty */
		);
	}

	/* always add kernel cycles */
	buf_printf(" %lu %lu %lu %lu",
		ex64hi(proc[i].p_kipc_cycles),
		ex64lo(proc[i].p_kipc_cycles),
		ex64hi(proc[i].p_kcall_cycles),
		ex64lo(proc[i].p_kcall_cycles));

	/* Newline at the end of the file. */
	buf_printf("\n");
}

/*===========================================================================*
 *				put_frame				     *
 *===========================================================================*/
PRIVATE void put_frame(void)
{
	/* If we allocated memory dynamically during a call to get_frame(),
	 * free it up here.
	 */

	if (frame != s_frame)
		free(frame);
}

/*===========================================================================*
 *				get_frame				     *
 *===========================================================================*/
PRIVATE int get_frame(int slot, vir_bytes *basep, vir_bytes *sizep,
	size_t *nargsp)
{
	/* Get the execution frame from the top of the given process's stack.
	 * It may be very large, in which case we temporarily allocate memory
	 * for it (up to a certain size).
	 */
	vir_bytes base, size;
	size_t nargs;

	if (proc[slot].p_nr < 0 || is_zombie(slot))
		return FALSE;

	/* Get the frame base address and size. Limit the size to whatever we
	 * can handle. If our static buffer is not sufficiently large to store
	 * the entire frame, allocate memory dynamically. It is then later
	 * freed by put_frame().
	 */
	base = mproc[slot - NR_TASKS].mp_frame_addr;
	size = mproc[slot - NR_TASKS].mp_frame_len;

	if (size < sizeof(size_t)) return FALSE;

	if (size > ARG_MAX) size = ARG_MAX;

	if (size > sizeof(s_frame)) {
		frame = malloc(size);

		if (frame == NULL)
			return FALSE;
	}
	else frame = s_frame;

	/* Copy in the complete process frame. */
	if (sys_datacopy(proc[slot].p_endpoint, base,
			SELF, (vir_bytes) frame, (phys_bytes) size) != OK) {
		put_frame();

		return FALSE;
	}

	frame[size] = 0; /* terminate any last string */

	nargs = * (size_t *) frame;
	if (nargs < 1 || sizeof(size_t) + sizeof(char *) * (nargs + 1) > size) {
		put_frame();

		return FALSE;
	}

	*basep = base;
	*sizep = size;
	*nargsp = nargs;

	/* The caller now has to called put_frame() to clean up. */
	return TRUE;
}

/*===========================================================================*
 *				pid_cmdline				     *
 *===========================================================================*/
PRIVATE void pid_cmdline(int slot)
{
	/* Dump the process's command line as it is contained in the process
	 * itself. Each argument is terminated with a null character.
	 */
	vir_bytes base, size, ptr;
	size_t i, len, nargs;
	char **argv;

	if (!get_frame(slot, &base, &size, &nargs))
		return;

	argv = (char **) &frame[sizeof(size_t)];

	for (i = 0; i < nargs; i++) {
		ptr = (vir_bytes) argv[i] - base;

		/* Check for bad pointers. */
		if ((long) ptr < 0L || ptr >= size)
			break;

		len = strlen(&frame[ptr]) + 1;

		buf_append(&frame[ptr], len);
	}

	put_frame();
}

/*===========================================================================*
 *				pid_environ				     *
 *===========================================================================*/
PRIVATE void pid_environ(int slot)
{
	/* Dump the process's initial environment as it is contained in the
	 * process itself. Each entry is terminated with a null character.
	 */
	vir_bytes base, size, ptr;
	size_t nargs, off, len;
	char **envp;

	if (!get_frame(slot, &base, &size, &nargs))
		return;

	off = sizeof(size_t) + sizeof(char *) * (nargs + 1);
	envp = (char **) &frame[off];

	for (;;) {
		/* Make sure there is no buffer overrun. */
		if (off + sizeof(char *) > size)
			break;

		ptr = (vir_bytes) *envp;

		/* Stop at the terminating NULL pointer. */
		if (ptr == 0L)
			break;

		ptr -= base;

		/* Check for bad pointers. */
		if ((long) ptr < 0L || ptr >= size)
			break;

		len = strlen(&frame[ptr]) + 1;

		buf_append(&frame[ptr], len);

		off += sizeof(char *);
		envp++;
	}

	put_frame();
}

/*===========================================================================*
 *				dump_regions				     *
 *===========================================================================*/
PRIVATE int dump_regions(int slot)
{
	/* Print the virtual memory regions of a process.
	 */
	struct vm_region_info vri[MAX_VRI_COUNT];
	vir_bytes next;
	int i, r, seg, count;

	count = 0;
	next = 0;

	do {
		r = vm_info_region(proc[slot].p_endpoint, vri, MAX_VRI_COUNT,
			&next);

		if (r < 0)
			return r;

		if (r == 0)
			break;

		for (i = 0; i < r; i++) {
			switch (vri[i].vri_seg) {
			case T: seg = 'T'; break;
			case D: seg = 'D'; break;
			default: seg = '?'; break;
			}

			buf_printf("%c %08lx-%08lx %c%c%c %c\n",
				seg, vri[i].vri_addr,
				vri[i].vri_addr + vri[i].vri_length,
				(vri[i].vri_prot & PROT_READ) ? 'r' : '-',
				(vri[i].vri_prot & PROT_WRITE) ? 'w' : '-',
				(vri[i].vri_prot & PROT_EXEC) ? 'x' : '-',
				(vri[i].vri_flags & MAP_IPC_SHARED) ? 's' : 'p');

			count++;
		}
	} while (r == MAX_VRI_COUNT);

	return count;
}

/*===========================================================================*
 *				dump_segments				     *
 *===========================================================================*/
PRIVATE void dump_segments(int slot)
{
	/* Print the memory segments of a process.
	 */
	int i;

	for (i = 0; i < NR_LOCAL_SEGS; i++) {
		buf_printf("%c %08lx-%08lx %s -\n",
			i == T ? 'T' : 'D',
			proc[slot].p_memmap[i].mem_vir << CLICK_SHIFT,
			(proc[slot].p_memmap[i].mem_vir +
			proc[slot].p_memmap[i].mem_len) << CLICK_SHIFT,
			(i == T) ? "r-x" :
			(proc[slot].p_memmap[T].mem_len == 0) ? "rwx" : "rw-");
	}
}

/*===========================================================================*
 *				pid_map					     *
 *===========================================================================*/
PRIVATE void pid_map(int slot)
{
	/* Print a memory map of the process. Obtain the information from VM if
	 * possible; otherwise fall back on segments from the kernel.
	 */

	/* Zombies have no memory. */
	if (is_zombie(slot))
		return;

	/* Kernel tasks also have no memory. */
	if (proc[slot].p_nr >= 0) {
		if (dump_regions(slot) != 0)
			return;
	}

	/* For kernel tasks, or for processes that have no regions according to
	 * VM, we assume they are not using virtual memory, and we print their
	 * segments instead.
	 */
	dump_segments(slot);
}
