/*
 *  linux/fs/proc/root.c
 *
 *  Copyright (C) 1991, 1992 Linus Torvalds
 *
 *  proc root directory handling functions
 */

#include <asm/segment.h>

#include <linux/errno.h>
#include <linux/sched.h>
#include <linux/proc_fs.h>
#include <linux/stat.h>
#include <linux/config.h>

/*
 * Offset of the first process in the /proc root directory..
 */
#define FIRST_PROCESS_ENTRY 256

static int proc_root_readdir(struct inode *, struct file *, void *, filldir_t);
static int proc_root_lookup(struct inode *,const char *,int,struct inode **);

/*
 * These are the generic /proc directory operations. They
 * use the in-memory "struct proc_dir_entry" tree to parse
 * the /proc directory.
 *
 * NOTE! The /proc/scsi directory currently does not correctly
 * build up the proc_dir_entry tree, and will show up empty.
 */
static struct file_operations proc_dir_operations = {
	NULL,			/* lseek - default */
	NULL,			/* read - bad */
	NULL,			/* write - bad */
	proc_readdir,		/* readdir */
	NULL,			/* select - default */
	NULL,			/* ioctl - default */
	NULL,			/* mmap */
	NULL,			/* no special open code */
	NULL,			/* no special release code */
	NULL			/* can't fsync */
};

/*
 * proc directories can do almost nothing..
 */
static struct inode_operations proc_dir_inode_operations = {
	&proc_dir_operations,	/* default net directory file-ops */
	NULL,			/* create */
	proc_lookup,		/* lookup */
	NULL,			/* link */
	NULL,			/* unlink */
	NULL,			/* symlink */
	NULL,			/* mkdir */
	NULL,			/* rmdir */
	NULL,			/* mknod */
	NULL,			/* rename */
	NULL,			/* readlink */
	NULL,			/* follow_link */
	NULL,			/* bmap */
	NULL,			/* truncate */
	NULL			/* permission */
};

/*
 * The root /proc directory is special, as it has the
 * <pid> directories. Thus we don't use the generic
 * directory handling functions for that..
 */
static struct file_operations proc_root_operations = {
	NULL,			/* lseek - default */
	NULL,			/* read - bad */
	NULL,			/* write - bad */
	proc_root_readdir,	/* readdir */
	NULL,			/* select - default */
	NULL,			/* ioctl - default */
	NULL,			/* mmap */
	NULL,			/* no special open code */
	NULL,			/* no special release code */
	NULL			/* no fsync */
};

/*
 * proc root can do almost nothing..
 */
static struct inode_operations proc_root_inode_operations = {
	&proc_root_operations,	/* default base directory file-ops */
	NULL,			/* create */
	proc_root_lookup,	/* lookup */
	NULL,			/* link */
	NULL,			/* unlink */
	NULL,			/* symlink */
	NULL,			/* mkdir */
	NULL,			/* rmdir */
	NULL,			/* mknod */
	NULL,			/* rename */
	NULL,			/* readlink */
	NULL,			/* follow_link */
	NULL,			/* bmap */
	NULL,			/* truncate */
	NULL			/* permission */
};

/*
 * This is the root "inode" in the /proc tree..
 */
struct proc_dir_entry proc_root = {
	PROC_ROOT_INO, 5, "/proc",
	S_IFDIR | S_IRUGO | S_IXUGO, 2, 0, 0,
	0, &proc_root_inode_operations,
	NULL, NULL,
	NULL,
	&proc_root, NULL
};

struct proc_dir_entry proc_net = {
	PROC_NET, 3, "net",
	S_IFDIR | S_IRUGO | S_IXUGO, 2, 0, 0,
	0, &proc_dir_inode_operations,
	NULL, NULL,
	NULL,
	NULL, NULL	
};

struct proc_dir_entry proc_scsi = {
	PROC_SCSI, 4, "scsi",
	S_IFDIR | S_IRUGO | S_IXUGO, 2, 0, 0,
	0, &proc_dir_inode_operations,
	NULL, NULL,
	NULL, &proc_root, NULL
};

int proc_register(struct proc_dir_entry * dir, struct proc_dir_entry * dp)
{
	dp->next = dir->subdir;
	dp->parent = dir;
	dir->subdir = dp;
	if (S_ISDIR(dp->mode))
		dir->nlink++;
	return 0;
}

int proc_unregister(struct proc_dir_entry * dir, int ino)
{
	struct proc_dir_entry **p = &dir->subdir, *dp;

	while ((dp = *p) != NULL) {
		if (dp->low_ino == ino) {
			*p = dp->next;
			dp->next = NULL;
			if (S_ISDIR(dp->mode))
				dir->nlink--;
			return 0;
		}
		p = &dp->next;
	}
	return -EINVAL;
}	

/*
 * /proc/self:
 */
static int proc_self_followlink(struct inode * dir, struct inode * inode,
			int flag, int mode, struct inode ** res_inode)
{
	iput(dir);
	*res_inode = proc_get_inode(inode->i_sb, (current->pid << 16) + PROC_PID_INO, &proc_pid);
	iput(inode);
	if (!*res_inode)
		return -ENOENT;
	return 0;
}

static int proc_self_readlink(struct inode * inode, char * buffer, int buflen)
{
	int len;
	char tmp[30];

	iput(inode);
	len = 1 + sprintf(tmp, "%d", current->pid);
	if (buflen < len)
		len = buflen;
	memcpy_tofs(buffer, tmp, len);
	return len;
}

static struct inode_operations proc_self_inode_operations = {
	NULL,			/* no file-ops */
	NULL,			/* create */
	NULL,			/* lookup */
	NULL,			/* link */
	NULL,			/* unlink */
	NULL,			/* symlink */
	NULL,			/* mkdir */
	NULL,			/* rmdir */
	NULL,			/* mknod */
	NULL,			/* rename */
	proc_self_readlink,	/* readlink */
	proc_self_followlink,	/* follow_link */
	NULL,			/* bmap */
	NULL,			/* truncate */
	NULL			/* permission */
};

void proc_root_init(void)
{
	static int done = 0;

	if (done)
		return;
	done = 1;
	proc_base_init();
	proc_register(&proc_root, &(struct proc_dir_entry) {
		PROC_LOADAVG, 7, "loadavg",
		S_IFREG | S_IRUGO, 1, 0, 0,
	});
	proc_register(&proc_root, &(struct proc_dir_entry) {
		PROC_UPTIME, 6, "uptime",
		S_IFREG | S_IRUGO, 1, 0, 0,
	});
	proc_register(&proc_root, &(struct proc_dir_entry) {
		PROC_MEMINFO, 7, "meminfo",
		S_IFREG | S_IRUGO, 1, 0, 0,
	});
	proc_register(&proc_root, &(struct proc_dir_entry) {
		PROC_KMSG, 4, "kmsg",
		S_IFREG | S_IRUSR, 1, 0, 0,
	});
	proc_register(&proc_root, &(struct proc_dir_entry) {
		PROC_VERSION, 7, "version",
		S_IFREG | S_IRUGO, 1, 0, 0,
	});
#ifdef CONFIG_PCI
	proc_register(&proc_root, &(struct proc_dir_entry) {
		PROC_PCI, 3, "pci",
		S_IFREG | S_IRUGO, 1, 0, 0,
	});
#endif
	proc_register(&proc_root, &(struct proc_dir_entry) {
		PROC_CPUINFO, 7, "cpuinfo",
		S_IFREG | S_IRUGO, 1, 0, 0,
	});
	proc_register(&proc_root, &(struct proc_dir_entry) {
		PROC_SELF, 4, "self",
		S_IFLNK | S_IRUGO | S_IWUGO | S_IXUGO, 1, 0, 0,
		64, &proc_self_inode_operations,
	});
	proc_register(&proc_root, &proc_net);

	proc_register(&proc_root, &proc_scsi);

#ifdef CONFIG_DEBUG_MALLOC
	proc_register(&proc_root, &(struct proc_dir_entry) {
		PROC_MALLOC, 6, "malloc",
		S_IFREG | S_IRUGO, 1, 0, 0,
	});
#endif
	proc_register(&proc_root, &(struct proc_dir_entry) {
		PROC_KCORE, 5, "kcore",
		S_IFREG | S_IRUSR, 1, 0, 0,
	});
	proc_register(&proc_root, &(struct proc_dir_entry) {
		PROC_MODULES, 7, "modules",
		S_IFREG | S_IRUGO, 1, 0, 0,
	});
	proc_register(&proc_root, &(struct proc_dir_entry) {
		PROC_STAT, 4, "stat",
		S_IFREG | S_IRUGO, 1, 0, 0,
	});
	proc_register(&proc_root, &(struct proc_dir_entry) {
		PROC_DEVICES, 7, "devices",
		S_IFREG | S_IRUGO, 1, 0, 0,
	});
	proc_register(&proc_root, &(struct proc_dir_entry) {
		PROC_INTERRUPTS, 10,"interrupts",
		S_IFREG | S_IRUGO, 1, 0, 0,
	});
	proc_register(&proc_root, &(struct proc_dir_entry) {
		PROC_FILESYSTEMS, 11,"filesystems",
		S_IFREG | S_IRUGO, 1, 0, 0,
	});
	proc_register(&proc_root, &(struct proc_dir_entry) {
		PROC_KSYMS, 5, "ksyms",
		S_IFREG | S_IRUGO, 1, 0, 0,
	});
	proc_register(&proc_root, &(struct proc_dir_entry) {
		PROC_DMA, 3, "dma",
		S_IFREG | S_IRUGO, 1, 0, 0,
	});
	proc_register(&proc_root, &(struct proc_dir_entry) {
		PROC_IOPORTS, 7, "ioports",
		S_IFREG | S_IRUGO, 1, 0, 0,
	});

	if (prof_shift) {
		proc_register(&proc_root, &(struct proc_dir_entry) {
			PROC_PROFILE, 7, "profile",
			S_IFREG | S_IRUGO | S_IWUSR, 1, 0, 0,
		});
	}
}


int proc_match(int len,const char * name,struct proc_dir_entry * de)
{
	if (!de || !de->low_ino)
		return 0;
	/* "" means "." ---> so paths like "/usr/lib//libc.a" work */
	if (!len && (de->name[0]=='.') && (de->name[1]=='\0'))
		return 1;
	if (de->namelen != len)
		return 0;
	return !memcmp(name, de->name, len);
}

int proc_lookup(struct inode * dir,const char * name, int len,
	struct inode ** result)
{
	struct proc_dir_entry * de;
	int ino;

	*result = NULL;
	if (!dir || !S_ISDIR(dir->i_mode)) {
		iput(dir);
		return -ENOTDIR;
	}

	de = (struct proc_dir_entry *) dir->u.generic_ip;
	if (!de) {
		iput(dir);
		return -EINVAL;
	}

	/* Special case "." and "..": they aren't on the directory list */
	*result = dir;
	if (!len)
		return 0;
	if (name[0] == '.') {
		if (len == 1)
			return 0;
		if (name[1] == '.' && len == 2) {
			struct inode * inode;
			inode = proc_get_inode(dir->i_sb, de->parent->low_ino, de->parent);
			iput(dir);
			if (!inode)
				return -EINVAL;
			*result = inode;
			return 0;
		}
	}

	*result = NULL;
	for (de = de->subdir; de ; de = de->next) {
		if (proc_match(len, name, de))
			break;
	}
	if (!de) {
		iput(dir);
		return -ENOENT;
	}

	ino = de->low_ino | (dir->i_ino & ~(0xffff));

	if (!(*result = proc_get_inode(dir->i_sb, ino, de))) {
		iput(dir);
		return -EINVAL;
	}
	iput(dir);
	return 0;
}

static int proc_root_lookup(struct inode * dir,const char * name, int len,
	struct inode ** result)
{
	unsigned int pid, c;
	int i, ino, retval;

	dir->i_count++;
	retval = proc_lookup(dir, name, len, result);
	if (retval != -ENOENT) {
		iput(dir);
		return retval;
	}
	
	pid = 0;
	while (len-- > 0) {
		c = *name - '0';
		name++;
		if (c > 9) {
			pid = 0;
			break;
		}
		pid *= 10;
		pid += c;
		if (pid & 0xffff0000) {
			pid = 0;
			break;
		}
	}
	for (i = 0 ; i < NR_TASKS ; i++)
		if (task[i] && task[i]->pid == pid)
			break;
	if (!pid || i >= NR_TASKS) {
		iput(dir);
		return -ENOENT;
	}
	ino = (pid << 16) + PROC_PID_INO;
	if (!(*result = proc_get_inode(dir->i_sb, ino, &proc_pid))) {
		iput(dir);
		return -EINVAL;
	}
	iput(dir);
	return 0;
}

/*
 * This returns non-zero if at EOF, so that the /proc
 * root directory can use this and check if it should
 * continue with the <pid> entries..
 *
 * Note that the VFS-layer doesn't care about the return
 * value of the readdir() call, as long as it's non-negative
 * for success..
 */
int proc_readdir(struct inode * inode, struct file * filp,
	void * dirent, filldir_t filldir)
{
	struct proc_dir_entry * de;
	unsigned int ino;
	int i;

	if (!inode || !S_ISDIR(inode->i_mode))
		return -ENOTDIR;
	ino = inode->i_ino;
	de = (struct proc_dir_entry *) inode->u.generic_ip;
	if (!de)
		return -EINVAL;
	i = filp->f_pos;
	switch (i) {
		case 0:
			if (filldir(dirent, ".", 1, i, ino) < 0)
				return 0;
			i++;
			filp->f_pos++;
			/* fall through */
		case 1:
			if (filldir(dirent, "..", 2, i, de->parent->low_ino) < 0)
				return 0;
			i++;
			filp->f_pos++;
			/* fall through */
		default:
			ino &= ~0xffff;
			de = de->subdir;
			i -= 2;
			for (;;) {
				if (!de)
					return 1;
				if (!i)
					break;
				de = de->next;
				i--;
			}

			do {
				if (filldir(dirent, de->name, de->namelen, filp->f_pos, ino | de->low_ino) < 0)
					return 0;
				filp->f_pos++;
				de = de->next;
			} while (de);
	}
	return 1;
}

#define NUMBUF 10

static int proc_root_readdir(struct inode * inode, struct file * filp,
	void * dirent, filldir_t filldir)
{
	char buf[NUMBUF];
	unsigned int nr,pid;
	unsigned long i,j;

	nr = filp->f_pos;
	if (nr < FIRST_PROCESS_ENTRY) {
		int error = proc_readdir(inode, filp, dirent, filldir);
		if (error <= 0)
			return error;
		filp->f_pos = nr = FIRST_PROCESS_ENTRY;
	}

	for (nr -= FIRST_PROCESS_ENTRY; nr < NR_TASKS; nr++, filp->f_pos++) {
		struct task_struct * p = task[nr];

		if (!p || !(pid = p->pid))
			continue;

		j = NUMBUF;
		i = pid;
		do {
			j--;
			buf[j] = '0' + (i % 10);
			i /= 10;
		} while (i);

		if (filldir(dirent, buf+j, NUMBUF-j, filp->f_pos, (pid << 16) + PROC_PID_INO) < 0)
			break;
	}
	return 0;
}
