538 lines
15 KiB
C
538 lines
15 KiB
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 = getticks(&now)) != OK)
|
||
|
panic("unable to get uptime: %d", 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;
|
||
|
}
|