servers/procfs/tree.c
/* ProcFS - tree.c - by Alen Stojanov and David van Moolenbroek */

#include "inc.h"

struct proc proc[NR_PROCS + NR_TASKS];
struct mproc mproc[NR_PROCS];
struct fproc fproc[NR_PROCS];

static int nr_pid_entries;

/*===========================================================================*
 *				slot_in_use				     *
 *===========================================================================*/
static int slot_in_use(int slot)
{
	/* Return whether the given slot is in use by a process.
	 */

	/* For kernel tasks, check only the kernel slot. Tasks do not have a
	 * PM/VFS process slot.
	 */
	if (slot < NR_TASKS)
		return (proc[slot].p_rts_flags != RTS_SLOT_FREE);

	/* For regular processes, check only the PM slot. Do not check the
	 * kernel slot, because that would skip zombie processes. The PID check
	 * should be redundant, but if it fails, procfs could crash.
	 */
	return ((mproc[slot - NR_TASKS].mp_flags & IN_USE) &&
		mproc[slot - NR_TASKS].mp_pid != 0);
}

/*===========================================================================*
 *				check_owner				     *
 *===========================================================================*/
static int check_owner(struct inode *node, int slot)
{
	/* Check if the owner user and group ID of the inode are still in sync
	 * the current effective user and group ID of the given process.
	 */
	struct inode_stat stat;

	if (slot < NR_TASKS) return TRUE;

	get_inode_stat(node, &stat);

	return (stat.uid == mproc[slot - NR_TASKS].mp_effuid &&
		stat.gid == mproc[slot - NR_TASKS].mp_effgid);
}

/*===========================================================================*
 *				make_stat				     *
 *===========================================================================*/
static void make_stat(struct inode_stat *stat, int slot, int index)
{
	/* Fill in an inode_stat structure for the given process slot and
	 * per-pid file index (or NO_INDEX for the process subdirectory root).
	 */

	if (index == NO_INDEX)
		stat->mode = DIR_ALL_MODE;
	else
		stat->mode = pid_files[index].mode;

	if (slot < NR_TASKS) {
		stat->uid = SUPER_USER;
		stat->gid = SUPER_USER;
	} else {
		stat->uid = mproc[slot - NR_TASKS].mp_effuid;
		stat->gid = mproc[slot - NR_TASKS].mp_effgid;
	}

	stat->size = 0;
	stat->dev = NO_DEV;
}

/*===========================================================================*
 *				dir_is_pid				     *
 *===========================================================================*/
static int dir_is_pid(struct inode *node)
{
	/* Return whether the given node is a PID directory.
	 */

	return (get_parent_inode(node) == get_root_inode() &&
		get_inode_index(node) != NO_INDEX);
}

/*===========================================================================*
 *				update_proc_table			     *
 *===========================================================================*/
static int update_proc_table(void)
{
	/* Get the process table from the kernel.
	 * Check the magic number in the table entries.
	 */
	int r, slot;

	if ((r = sys_getproctab(proc)) != OK) return r;

	for (slot = 0; slot < NR_PROCS + NR_TASKS; slot++) {
		if (proc[slot].p_magic != PMAGIC) {
			printf("PROCFS: system version mismatch!\n");

			return EINVAL;
		}
	}

	return OK;
}

/*===========================================================================*
 *				update_mproc_table			     *
 *===========================================================================*/
static int update_mproc_table(void)
{
	/* Get the process table from PM.
	 * Check the magic number in the table entries.
	 */
	int r, slot;

	r = getsysinfo(PM_PROC_NR, SI_PROC_TAB, mproc, sizeof(mproc));
	if (r != OK) return r;

	for (slot = 0; slot < NR_PROCS; slot++) {
		if (mproc[slot].mp_magic != MP_MAGIC) {
			printf("PROCFS: PM version mismatch!\n");

			return EINVAL;
		}
	}

	return OK;
}

/*===========================================================================*
 *				update_fproc_table			     *
 *===========================================================================*/
static int update_fproc_table(void)
{
	/* Get the process table from VFS.
	 */

	return getsysinfo(VFS_PROC_NR, SI_PROC_TAB, fproc, sizeof(fproc));
}

/*===========================================================================*
 *				update_tables				     *
 *===========================================================================*/
static int update_tables(void)
{
	/* Get the process tables from the kernel, PM, and VFS.
	 */
	int r;

	if ((r = update_proc_table()) != OK) return r;

	if ((r = update_mproc_table()) != OK) return r;

	if ((r = update_fproc_table()) != OK) return r;

	return OK;
}

/*===========================================================================*
 *				init_tree				     *
 *===========================================================================*/
int init_tree(void)
{
	/* Initialize this module, before VTreeFS is started. As part of the
	 * process, check if we're not compiled against a kernel different from
	 * the one that is running at the moment.
	 */
	int i, r;

	if ((r = update_tables()) != OK)
		return r;

	/* Get the maximum number of entries that we may add to each PID's
	 * directory. We could just default to a large value, but why not get
	 * it right?
	 */
	for (i = 0; pid_files[i].name != NULL; i++);

	nr_pid_entries = i;

	return OK;
}

/*===========================================================================*
 *				out_of_inodes				     *
 *===========================================================================*/
static void out_of_inodes(void)
{
	/* Out of inodes - the NR_INODES value is set too low. We can not do
	 * much, but we might be able to continue with degraded functionality,
	 * so do not panic. If the NR_INODES value is not below the *crucial*
	 * minimum, the symptom of this case will be an incomplete listing of
	 * the main proc directory.
	 */
	static int warned = FALSE;

	if (warned == FALSE) {
		printf("PROCFS: out of inodes!\n");

		warned = TRUE;
	}
}

/*===========================================================================*
 *				construct_pid_dirs			     *
 *===========================================================================*/
static void construct_pid_dirs(void)
{
	/* Regenerate the set of PID directories in the root directory of the
	 * file system. Add new directories and delete old directories as
	 * appropriate; leave unchanged those that should remain the same.
	 *
	 * We have to make two passes. Otherwise, we would trigger a vtreefs
	 * assert when we add an entry for a PID before deleting the previous
	 * entry for that PID. While rare, such rapid PID reuse does occur in
	 * practice.
	 */
	struct inode *root, *node;
	struct inode_stat stat;
	char name[PNAME_MAX+1];
	pid_t pid;
	int i;

	root = get_root_inode();

	/* First pass: delete old entries. */
	for (i = 0; i < NR_PROCS + NR_TASKS; i++) {
		/* Do we already have an inode associated with this slot? */
		node = get_inode_by_index(root, i);
		if (node == NULL)
			continue;

		/* If the process slot is not in use, delete the associated
		 * inode.
		 */
		if (!slot_in_use(i)) {
			delete_inode(node);

			continue;
		}

		/* Otherwise, get the process ID. */
		if (i < NR_TASKS)
			pid = (pid_t) (i - NR_TASKS);
		else
			pid = mproc[i - NR_TASKS].mp_pid;

		/* If there is an old entry, see if the pid matches the current
		 * entry, and the owner is still the same. Otherwise, delete
		 * the old entry first. We reconstruct the entire subtree even
		 * if only the owner changed, for security reasons: if a
		 * process could keep open a file or directory across the owner
		 * change, it might be able to access information it shouldn't.
		 */
		if (pid != (pid_t) get_inode_cbdata(node) ||
				!check_owner(node, i))
			delete_inode(node);
	}

	/* Second pass: add new entries. */
	for (i = 0; i < NR_PROCS + NR_TASKS; i++) {
		/* If the process slot is not in use, skip this slot. */
		if (!slot_in_use(i))
			continue;

		/* If we have an inode associated with this slot, we have
		 * already checked it to be up-to-date above.
		 */
		if (get_inode_by_index(root, i) != NULL)
			continue;

		/* Get the process ID. */
		if (i < NR_TASKS)
			pid = (pid_t) (i - NR_TASKS);
		else
			pid = mproc[i - NR_TASKS].mp_pid;

		/* Add the entry for the process slot. */
		snprintf(name, PNAME_MAX + 1, "%d", pid);

		make_stat(&stat, i, NO_INDEX);

		node = add_inode(root, name, i, &stat, nr_pid_entries,
			(cbdata_t) pid);

		if (node == NULL)
			out_of_inodes();
	}
}

/*===========================================================================*
 *				make_one_pid_entry			     *
 *===========================================================================*/
static void make_one_pid_entry(struct inode *parent, char *name, int slot)
{
	/* Construct one file in a PID directory, if a file with the given name
	 * should exist at all.
	 */
	struct inode *node;
	struct inode_stat stat;
	int i;

	/* Don't readd if it is already there. */
	node = get_inode_by_name(parent, name);
	if (node != NULL)
		return;

	/* Only add the file if it is a known, registered name. */
	for (i = 0; pid_files[i].name != NULL; i++) {
		if (!strcmp(name, pid_files[i].name)) {
			make_stat(&stat, slot, i);

			node = add_inode(parent, name, i, &stat,
				(index_t) 0, (cbdata_t) 0);

			if (node == NULL)
				out_of_inodes();

			break;
		}
	}
}

/*===========================================================================*
 *				make_all_pid_entries			     *
 *===========================================================================*/
static void make_all_pid_entries(struct inode *parent, int slot)
{
	/* Construct all files in a PID directory.
	 */
	struct inode *node;
	struct inode_stat stat;
	int i;

	for (i = 0; pid_files[i].name != NULL; i++) {
		node = get_inode_by_index(parent, i);
		if (node != NULL)
			continue;

		make_stat(&stat, slot, i);

		node = add_inode(parent, pid_files[i].name, i, &stat,
			(index_t) 0, (cbdata_t) 0);

		if (node == NULL)
			out_of_inodes();
	}
}

/*===========================================================================*
 *				construct_pid_entries			     *
 *===========================================================================*/
static void construct_pid_entries(struct inode *parent, char *name)
{
	/* Construct one requested file entry, or all file entries, in a PID
	 * directory.
	 */
	int slot;

	slot = get_inode_index(parent);
	assert(slot >= 0 && slot < NR_TASKS + NR_PROCS);

	/* If this process is already gone, delete the directory now. */
	if (!slot_in_use(slot)) {
		delete_inode(parent);

		return;
	}

	/* If a specific file name is being looked up, see if we have to add
	 * an inode for that file. If the directory contents are being
	 * retrieved, add all files that have not yet been added.
	 */
	if (name != NULL)
		make_one_pid_entry(parent, name, slot);
	else
		make_all_pid_entries(parent, slot);
}

/*===========================================================================*
 *				pid_read				     *
 *===========================================================================*/
static void pid_read(struct inode *node)
{
	/* Data is requested from one of the files in a PID directory. Call the
	 * function that is responsible for generating the data for that file.
	 */
	struct inode *parent;
	int slot, index;

	/* Get the slot number of the process. Note that this currently will
	 * not work for files not in the top-level pid subdirectory.
	 */
	parent = get_parent_inode(node);

	slot = get_inode_index(parent);

	/* Get this file's index number. */
	index = get_inode_index(node);

	/* Call the handler procedure for the file. */
	((void (*) (int)) pid_files[index].data)(slot);
}

/*===========================================================================*
 *				pid_link				     *
 *===========================================================================*/
static int pid_link(struct inode *UNUSED(node), char *ptr, int max)
{
	/* The contents of a symbolic link in a PID directory are requested.
	 * This function is a placeholder for future use.
	 */

	/* Nothing yet. */
	strlcpy(ptr, "", max);

	return OK;
}

/*===========================================================================*
 *				lookup_hook				     *
 *===========================================================================*/
int lookup_hook(struct inode *parent, char *name,
	cbdata_t UNUSED(cbdata))
{
	/* Path name resolution hook, for a specific parent and name pair.
	 * If needed, update our own view of the system first; after that,
	 * determine whether we need to (re)generate certain files.
	 */
	static clock_t last_update = 0;
	clock_t now;
	int r;

	/* Update lazily for lookups, as this gets too expensive otherwise.
	 * Alternative: pull in only PM's table?
	 */
	if ((r = getuptime(&now)) != OK)
		panic(__FILE__, "unable to get uptime", r);

	if (last_update != now) {
		update_tables();

		last_update = now;
	}

	/* If the parent is the root directory, we must now reconstruct all
	 * entries, because some of them might have been garbage collected.
	 * We must update the entire tree at once; if we update individual
	 * entries, we risk name collisions.
	 */
	if (parent == get_root_inode()) {
		construct_pid_dirs();
	}
	/* If the parent is a process directory, we may need to (re)construct
	 * the entry being looked up.
	 */
	else if (dir_is_pid(parent)) {
		/* We might now have deleted our current containing directory;
		 * construct_pid_entries() will take care of this case.
		 */
		construct_pid_entries(parent, name);
	}

	return OK;
}

/*===========================================================================*
 *				getdents_hook				     *
 *===========================================================================*/
int getdents_hook(struct inode *node, cbdata_t UNUSED(cbdata))
{
	/* Directory entry retrieval hook, for potentially all files in a
	 * directory. Make sure that all files that are supposed to be
	 * returned, are actually part of the virtual tree.
	 */

	if (node == get_root_inode()) {
		update_tables();

		construct_pid_dirs();
	} else if (dir_is_pid(node)) {
		construct_pid_entries(node, NULL /*name*/);
	}

	return OK;
}

/*===========================================================================*
 *				read_hook				     *
 *===========================================================================*/
int read_hook(struct inode *node, off_t off, char **ptr,
	size_t *len, cbdata_t cbdata)
{
	/* Regular file read hook. Call the appropriate callback function to
	 * generate and return the data.
	 */

	buf_init(off, *len);

	/* Populate the buffer with the proper content. */
	if (get_inode_index(node) != NO_INDEX) {
		pid_read(node);
	} else {
		((void (*) (void)) cbdata)();
	}

	*len = buf_get(ptr);

	return OK;
}

/*===========================================================================*
 *				rdlink_hook				     *
 *===========================================================================*/
int rdlink_hook(struct inode *node, char *ptr, size_t max,
	cbdata_t UNUSED(cbdata))
{
	/* Symbolic link resolution hook. Not used yet.
	 */
	struct inode *parent;

	/* Get the parent inode. */
	parent = get_parent_inode(node);

	/* If the parent inode is a pid directory, call the pid handler.
	 */
	if (parent != NULL && dir_is_pid(parent))
		pid_link(node, ptr, max);

	return OK;
}
