[PATCH] convert /proc/devices to use seq_file interface

A Christoph suggested that the /proc/devices file be converted to use the
seq_file interface.  This patch does that.

I've obxerved one or two installation that had sufficiently large sans that
they overran the 4k limit on /proc/devices.

Signed-off-by: Neil Horman <nhorman@redhat.com>
Signed-off-by: Andrew Morton <akpm@osdl.org>
Signed-off-by: Linus Torvalds <torvalds@osdl.org>
diff --git a/fs/proc/proc_misc.c b/fs/proc/proc_misc.c
index 63bf6c0..8f80142 100644
--- a/fs/proc/proc_misc.c
+++ b/fs/proc/proc_misc.c
@@ -20,6 +20,7 @@
 #include <linux/time.h>
 #include <linux/kernel.h>
 #include <linux/kernel_stat.h>
+#include <linux/fs.h>
 #include <linux/tty.h>
 #include <linux/string.h>
 #include <linux/mman.h>
@@ -62,7 +63,6 @@
  */
 extern int get_hardware_list(char *);
 extern int get_stram_list(char *);
-extern int get_chrdev_list(char *);
 extern int get_filesystem_list(char *);
 extern int get_exec_domain_list(char *);
 extern int get_dma_list(char *);
@@ -248,6 +248,154 @@
 {
 	return seq_open(file, &cpuinfo_op);
 }
+
+enum devinfo_states {
+	CHR_HDR,
+	CHR_LIST,
+	BLK_HDR,
+	BLK_LIST,
+	DEVINFO_DONE
+};
+
+struct devinfo_state {
+	void *chrdev;
+	void *blkdev;
+	unsigned int num_records;
+	unsigned int cur_record;
+	enum devinfo_states state;
+};
+
+static void *devinfo_start(struct seq_file *f, loff_t *pos)
+{
+	struct devinfo_state *info = f->private;
+
+	if (*pos) {
+		if ((info) && (*pos <= info->num_records))
+			return info;
+		return NULL;
+	}
+	info = kmalloc(sizeof(*info), GFP_KERNEL);
+	f->private = info;
+	info->chrdev = acquire_chrdev_list();
+	info->blkdev = acquire_blkdev_list();
+	info->state = CHR_HDR;
+	info->num_records = count_chrdev_list();
+	info->num_records += count_blkdev_list();
+	info->num_records += 2; /* Character and Block headers */
+	*pos = 1;
+	info->cur_record = *pos;
+	return info;
+}
+
+static void *devinfo_next(struct seq_file *f, void *v, loff_t *pos)
+{
+	int idummy;
+	char *ndummy;
+	struct devinfo_state *info = f->private;
+
+	switch (info->state) {
+		case CHR_HDR:
+			info->state = CHR_LIST;
+			(*pos)++;
+			/*fallthrough*/
+		case CHR_LIST:
+			if (get_chrdev_info(info->chrdev,&idummy,&ndummy)) {
+				/*
+				 * The character dev list is complete
+				 */
+				info->state = BLK_HDR;
+			} else {
+				info->chrdev = get_next_chrdev(info->chrdev);
+			}
+			(*pos)++;
+			break;
+		case BLK_HDR:
+			info->state = BLK_LIST;
+			(*pos)++;
+			break;
+		case BLK_LIST:
+			if (get_blkdev_info(info->blkdev,&idummy,&ndummy)) {
+				/*
+				 * The block dev list is complete
+				 */
+				info->state = DEVINFO_DONE;
+			} else {
+				info->blkdev = get_next_blkdev(info->blkdev);
+			}
+			(*pos)++;
+			break;
+		case DEVINFO_DONE:
+			(*pos)++;
+			info->cur_record = *pos;
+			info = NULL;
+			break;
+		default:
+			break;
+	}
+	if (info)
+		info->cur_record = *pos;
+	return info;
+}
+
+static void devinfo_stop(struct seq_file *f, void *v)
+{
+	struct devinfo_state *info = f->private;
+
+	if (info) {
+		release_chrdev_list(info->chrdev);
+		release_blkdev_list(info->blkdev);
+		f->private = NULL;
+		kfree(info);
+	}
+}
+
+static int devinfo_show(struct seq_file *f, void *arg)
+{
+	int major;
+	char *name;
+	struct devinfo_state *info = f->private;
+
+	switch(info->state) {
+		case CHR_HDR:
+			seq_printf(f,"Character devices:\n");
+			/* fallthrough */
+		case CHR_LIST:
+			if (!get_chrdev_info(info->chrdev,&major,&name))
+				seq_printf(f,"%3d %s\n",major,name);
+			break;
+		case BLK_HDR:
+			seq_printf(f,"\nBlock devices:\n");
+			/* fallthrough */
+		case BLK_LIST:
+			if (!get_blkdev_info(info->blkdev,&major,&name))
+				seq_printf(f,"%3d %s\n",major,name);
+			break;
+		default:
+			break;
+	}
+
+	return 0;
+}
+
+static  struct seq_operations devinfo_op = {
+	.start  = devinfo_start,
+	.next   = devinfo_next,
+	.stop   = devinfo_stop,
+	.show   = devinfo_show,
+};
+
+static int devinfo_open(struct inode *inode, struct file *file)
+{
+	return seq_open(file, &devinfo_op);
+}
+
+static struct file_operations proc_devinfo_operations = {
+	.open		= devinfo_open,
+	.read		= seq_read,
+	.llseek		= seq_lseek,
+	.release	= seq_release,
+};
+
 static struct file_operations proc_cpuinfo_operations = {
 	.open		= cpuinfo_open,
 	.read		= seq_read,
@@ -450,14 +598,6 @@
 	.release	= single_release,
 };
 
-static int devices_read_proc(char *page, char **start, off_t off,
-				 int count, int *eof, void *data)
-{
-	int len = get_chrdev_list(page);
-	len += get_blkdev_list(page+len, len);
-	return proc_calc_metrics(page, start, off, count, eof, len);
-}
-
 /*
  * /proc/interrupts
  */
@@ -582,7 +722,6 @@
 #ifdef CONFIG_STRAM_PROC
 		{"stram",	stram_read_proc},
 #endif
-		{"devices",	devices_read_proc},
 		{"filesystems",	filesystems_read_proc},
 		{"cmdline",	cmdline_read_proc},
 		{"locks",	locks_read_proc},
@@ -598,6 +737,7 @@
 	entry = create_proc_entry("kmsg", S_IRUSR, &proc_root);
 	if (entry)
 		entry->proc_fops = &proc_kmsg_operations;
+	create_seq_entry("devices", 0, &proc_devinfo_operations);
 	create_seq_entry("cpuinfo", 0, &proc_cpuinfo_operations);
 	create_seq_entry("partitions", 0, &proc_partitions_operations);
 	create_seq_entry("stat", 0, &proc_stat_operations);