[PATCH] convert /proc/devices to use seq_file interface
authorNeil Horman <nhorman@redhat.com>
Sat, 14 Jan 2006 21:20:38 +0000 (13:20 -0800)
committerLinus Torvalds <torvalds@g5.osdl.org>
Sun, 15 Jan 2006 02:25:19 +0000 (18:25 -0800)
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>
block/genhd.c
fs/char_dev.c
fs/proc/proc_misc.c
include/linux/fs.h

index f1ed83f..db57546 100644 (file)
@@ -38,34 +38,100 @@ static inline int major_to_index(int major)
        return major % MAX_PROBE_HASH;
 }
 
-#ifdef CONFIG_PROC_FS
-/* get block device names in somewhat random order */
-int get_blkdev_list(char *p, int used)
+struct blkdev_info {
+        int index;
+        struct blk_major_name *bd;
+};
+
+/*
+ * iterate over a list of blkdev_info structures.  allows
+ * the major_names array to be iterated over from outside this file
+ * must be called with the block_subsys_sem held
+ */
+void *get_next_blkdev(void *dev)
+{
+        struct blkdev_info *info;
+
+        if (dev == NULL) {
+                info = kmalloc(sizeof(*info), GFP_KERNEL);
+                if (!info)
+                        goto out;
+                info->index=0;
+                info->bd = major_names[info->index];
+                if (info->bd)
+                        goto out;
+        } else {
+                info = dev;
+        }
+
+        while (info->index < ARRAY_SIZE(major_names)) {
+                if (info->bd)
+                        info->bd = info->bd->next;
+                if (info->bd)
+                        goto out;
+                /*
+                 * No devices on this chain, move to the next
+                 */
+                info->index++;
+                info->bd = (info->index < ARRAY_SIZE(major_names)) ?
+                       major_names[info->index] : NULL;
+                if (info->bd)
+                        goto out;
+        }
+
+out:
+        return info;
+}
+
+void *acquire_blkdev_list(void)
+{
+        down(&block_subsys_sem);
+        return get_next_blkdev(NULL);
+}
+
+void release_blkdev_list(void *dev)
+{
+        up(&block_subsys_sem);
+        kfree(dev);
+}
+
+
+/*
+ * Count the number of records in the blkdev_list.
+ * must be called with the block_subsys_sem held
+ */
+int count_blkdev_list(void)
 {
        struct blk_major_name *n;
-       int i, len;
+       int i, count;
 
-       len = snprintf(p, (PAGE_SIZE-used), "\nBlock devices:\n");
+       count = 0;
 
-       down(&block_subsys_sem);
        for (i = 0; i < ARRAY_SIZE(major_names); i++) {
-               for (n = major_names[i]; n; n = n->next) {
-                       /*
-                        * If the curent string plus the 5 extra characters
-                        * in the line would run us off the page, then we're done
-                        */
-                       if ((len + used + strlen(n->name) + 5) >= PAGE_SIZE)
-                               goto page_full;
-                       len += sprintf(p+len, "%3d %s\n",
-                                      n->major, n->name);
-               }
+               for (n = major_names[i]; n; n = n->next)
+                               count++;
        }
-page_full:
-       up(&block_subsys_sem);
 
-       return len;
+       return count;
 }
-#endif
+
+/*
+ * extract the major and name values from a blkdev_info struct
+ * passed in as a void to *dev.  Must be called with
+ * block_subsys_sem held
+ */
+int get_blkdev_info(void *dev, int *major, char **name)
+{
+        struct blkdev_info *info = dev;
+
+        if (info->bd == NULL)
+                return 1;
+
+        *major = info->bd->major;
+        *name = info->bd->name;
+        return 0;
+}
+
 
 int register_blkdev(unsigned int major, const char *name)
 {
index 3b1b1ee..21195c4 100644 (file)
@@ -35,7 +35,7 @@ static struct char_device_struct {
        unsigned int major;
        unsigned int baseminor;
        int minorct;
-       const char *name;
+       char name[64];
        struct file_operations *fops;
        struct cdev *cdev;              /* will die */
 } *chrdevs[MAX_PROBE_HASH];
@@ -46,34 +46,84 @@ static inline int major_to_index(int major)
        return major % MAX_PROBE_HASH;
 }
 
-/* get char device names in somewhat random order */
-int get_chrdev_list(char *page)
-{
+struct chrdev_info {
+       int index;
        struct char_device_struct *cd;
-       int i, len;
+};
 
-       len = sprintf(page, "Character devices:\n");
+void *get_next_chrdev(void *dev)
+{
+       struct chrdev_info *info;
 
+       if (dev == NULL) {
+               info = kmalloc(sizeof(*info), GFP_KERNEL);
+               if (!info)
+                       goto out;
+               info->index=0;
+               info->cd = chrdevs[info->index];
+               if (info->cd)
+                       goto out;
+       } else {
+               info = dev;
+       }
+
+       while (info->index < ARRAY_SIZE(chrdevs)) {
+               if (info->cd)
+                       info->cd = info->cd->next;
+               if (info->cd)
+                       goto out;
+               /*
+                * No devices on this chain, move to the next
+                */
+               info->index++;
+               info->cd = (info->index < ARRAY_SIZE(chrdevs)) ?
+                       chrdevs[info->index] : NULL;
+               if (info->cd)
+                       goto out;
+       }
+
+out:
+       return info;
+}
+
+void *acquire_chrdev_list(void)
+{
        down(&chrdevs_lock);
+       return get_next_chrdev(NULL);
+}
+
+void release_chrdev_list(void *dev)
+{
+       up(&chrdevs_lock);
+       kfree(dev);
+}
+
+
+int count_chrdev_list(void)
+{
+       struct char_device_struct *cd;
+       int i, count;
+
+       count = 0;
+
        for (i = 0; i < ARRAY_SIZE(chrdevs) ; i++) {
-               for (cd = chrdevs[i]; cd; cd = cd->next) {
-                       /*
-                        * if the current name, plus the 5 extra characters
-                        * in the device line for this entry
-                        * would run us off the page, we're done
-                        */
-                       if ((len+strlen(cd->name) + 5) >= PAGE_SIZE)
-                               goto page_full;
-
-
-                       len += sprintf(page+len, "%3d %s\n",
-                                      cd->major, cd->name);
-               }
+               for (cd = chrdevs[i]; cd; cd = cd->next)
+                       count++;
        }
-page_full:
-       up(&chrdevs_lock);
 
-       return len;
+       return count;
+}
+
+int get_chrdev_info(void *dev, int *major, char **name)
+{
+       struct chrdev_info *info = dev;
+
+       if (info->cd == NULL)
+               return 1;
+
+       *major = info->cd->major;
+       *name = info->cd->name;
+       return 0;
 }
 
 /*
@@ -121,7 +171,7 @@ __register_chrdev_region(unsigned int major, unsigned int baseminor,
        cd->major = major;
        cd->baseminor = baseminor;
        cd->minorct = minorct;
-       cd->name = name;
+       strncpy(cd->name,name, 64);
 
        i = major_to_index(major);
 
index 63bf6c0..8f80142 100644 (file)
@@ -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 @@ static int cpuinfo_open(struct inode *inode, struct file *file)
 {
        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 @@ static struct file_operations proc_stat_operations = {
        .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 @@ void __init proc_misc_init(void)
 #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 @@ void __init proc_misc_init(void)
        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);
index d1e370d..552cedf 100644 (file)
@@ -1383,6 +1383,12 @@ extern int register_chrdev(unsigned int, const char *,
 extern int unregister_chrdev(unsigned int, const char *);
 extern void unregister_chrdev_region(dev_t, unsigned);
 extern int chrdev_open(struct inode *, struct file *);
+extern int get_chrdev_list(char *);
+extern void *acquire_chrdev_list(void);
+extern int count_chrdev_list(void);
+extern void *get_next_chrdev(void *);
+extern int get_chrdev_info(void *, int *, char **);
+extern void release_chrdev_list(void *);
 
 /* fs/block_dev.c */
 #define BDEVNAME_SIZE  32      /* Largest string for a blockdev identifier */
@@ -1391,6 +1397,11 @@ extern const char *bdevname(struct block_device *bdev, char *buffer);
 extern struct block_device *lookup_bdev(const char *);
 extern struct block_device *open_bdev_excl(const char *, int, void *);
 extern void close_bdev_excl(struct block_device *);
+extern void *acquire_blkdev_list(void);
+extern int count_blkdev_list(void);
+extern void *get_next_blkdev(void *);
+extern int get_blkdev_info(void *, int *, char **);
+extern void release_blkdev_list(void *);
 
 extern void init_special_inode(struct inode *, umode_t, dev_t);