Commit 68eef3b4 authored by Joe Korty's avatar Joe Korty Committed by Linus Torvalds

[PATCH] Simplify proc/devices and fix early termination regression

Make baby-simple the code for /proc/devices.  Based on the proven design
for /proc/interrupts.

This also fixes the early-termination regression 2.6.16 introduced, as
demonstrated by:

    # dd if=/proc/devices bs=1
    Character devices:
      1 mem
    27+0 records in
    27+0 records out

This should also work (but is untested) when /proc/devices >4096 bytes,
which I believe is what the original 2.6.16 rewrite fixed.

[akpm@osdl.org: cleanups, simplifications]
Signed-off-by: default avatarJoe Korty <joe.korty@ccur.com>
Cc: Neil Horman <nhorman@tuxdriver.com>
Signed-off-by: default avatarAndrew Morton <akpm@osdl.org>
Signed-off-by: default avatarLinus Torvalds <torvalds@osdl.org>
parent a2c348fe
...@@ -17,8 +17,6 @@ ...@@ -17,8 +17,6 @@
#include <linux/buffer_head.h> #include <linux/buffer_head.h>
#include <linux/mutex.h> #include <linux/mutex.h>
#define MAX_PROBE_HASH 255 /* random */
static struct subsystem block_subsys; static struct subsystem block_subsys;
static DEFINE_MUTEX(block_subsys_lock); static DEFINE_MUTEX(block_subsys_lock);
...@@ -31,108 +29,29 @@ static struct blk_major_name { ...@@ -31,108 +29,29 @@ static struct blk_major_name {
struct blk_major_name *next; struct blk_major_name *next;
int major; int major;
char name[16]; char name[16];
} *major_names[MAX_PROBE_HASH]; } *major_names[BLKDEV_MAJOR_HASH_SIZE];
/* index in the above - for now: assume no multimajor ranges */ /* index in the above - for now: assume no multimajor ranges */
static inline int major_to_index(int major) static inline int major_to_index(int major)
{ {
return major % MAX_PROBE_HASH; return major % BLKDEV_MAJOR_HASH_SIZE;
} }
struct blkdev_info { #ifdef CONFIG_PROC_FS
int index;
struct blk_major_name *bd;
};
/* void blkdev_show(struct seq_file *f, off_t offset)
* 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_lock held
*/
void *get_next_blkdev(void *dev)
{ {
struct blkdev_info *info; struct blk_major_name *dp;
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: if (offset < BLKDEV_MAJOR_HASH_SIZE) {
return info;
}
void *acquire_blkdev_list(void)
{
mutex_lock(&block_subsys_lock); mutex_lock(&block_subsys_lock);
return get_next_blkdev(NULL); for (dp = major_names[offset]; dp; dp = dp->next)
} seq_printf(f, "%3d %s\n", dp->major, dp->name);
void release_blkdev_list(void *dev)
{
mutex_unlock(&block_subsys_lock); mutex_unlock(&block_subsys_lock);
kfree(dev);
}
/*
* Count the number of records in the blkdev_list.
* must be called with the block_subsys_lock held
*/
int count_blkdev_list(void)
{
struct blk_major_name *n;
int i, count;
count = 0;
for (i = 0; i < ARRAY_SIZE(major_names); i++) {
for (n = major_names[i]; n; n = n->next)
count++;
} }
return count;
}
/*
* extract the major and name values from a blkdev_info struct
* passed in as a void to *dev. Must be called with
* block_subsys_lock 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;
} }
#endif /* CONFIG_PROC_FS */
int register_blkdev(unsigned int major, const char *name) int register_blkdev(unsigned int major, const char *name)
{ {
......
...@@ -15,6 +15,7 @@ ...@@ -15,6 +15,7 @@
#include <linux/module.h> #include <linux/module.h>
#include <linux/smp_lock.h> #include <linux/smp_lock.h>
#include <linux/devfs_fs_kernel.h> #include <linux/devfs_fs_kernel.h>
#include <linux/seq_file.h>
#include <linux/kobject.h> #include <linux/kobject.h>
#include <linux/kobj_map.h> #include <linux/kobj_map.h>
...@@ -27,8 +28,6 @@ ...@@ -27,8 +28,6 @@
static struct kobj_map *cdev_map; static struct kobj_map *cdev_map;
#define MAX_PROBE_HASH 255 /* random */
static DEFINE_MUTEX(chrdevs_lock); static DEFINE_MUTEX(chrdevs_lock);
static struct char_device_struct { static struct char_device_struct {
...@@ -39,93 +38,29 @@ static struct char_device_struct { ...@@ -39,93 +38,29 @@ static struct char_device_struct {
char name[64]; char name[64];
struct file_operations *fops; struct file_operations *fops;
struct cdev *cdev; /* will die */ struct cdev *cdev; /* will die */
} *chrdevs[MAX_PROBE_HASH]; } *chrdevs[CHRDEV_MAJOR_HASH_SIZE];
/* index in the above */ /* index in the above */
static inline int major_to_index(int major) static inline int major_to_index(int major)
{ {
return major % MAX_PROBE_HASH; return major % CHRDEV_MAJOR_HASH_SIZE;
} }
struct chrdev_info { #ifdef CONFIG_PROC_FS
int index;
struct char_device_struct *cd;
};
void *get_next_chrdev(void *dev) void chrdev_show(struct seq_file *f, off_t offset)
{ {
struct chrdev_info *info; struct char_device_struct *cd;
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) if (offset < CHRDEV_MAJOR_HASH_SIZE) {
{
mutex_lock(&chrdevs_lock); mutex_lock(&chrdevs_lock);
return get_next_chrdev(NULL); for (cd = chrdevs[offset]; cd; cd = cd->next)
} seq_printf(f, "%3d %s\n", cd->major, cd->name);
void release_chrdev_list(void *dev)
{
mutex_unlock(&chrdevs_lock); mutex_unlock(&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)
count++;
} }
return count;
} }
int get_chrdev_info(void *dev, int *major, char **name) #endif /* CONFIG_PROC_FS */
{
struct chrdev_info *info = dev;
if (info->cd == NULL)
return 1;
*major = info->cd->major;
*name = info->cd->name;
return 0;
}
/* /*
* Register a single major with a specified minor range. * Register a single major with a specified minor range.
......
...@@ -249,144 +249,60 @@ static int cpuinfo_open(struct inode *inode, struct file *file) ...@@ -249,144 +249,60 @@ static int cpuinfo_open(struct inode *inode, struct file *file)
return seq_open(file, &cpuinfo_op); return seq_open(file, &cpuinfo_op);
} }
enum devinfo_states { static struct file_operations proc_cpuinfo_operations = {
CHR_HDR, .open = cpuinfo_open,
CHR_LIST, .read = seq_read,
BLK_HDR, .llseek = seq_lseek,
BLK_LIST, .release = seq_release,
DEVINFO_DONE
}; };
struct devinfo_state { static int devinfo_show(struct seq_file *f, void *v)
void *chrdev; {
void *blkdev; int i = *(loff_t *) v;
unsigned int num_records;
unsigned int cur_record; if (i < CHRDEV_MAJOR_HASH_SIZE) {
enum devinfo_states state; if (i == 0)
}; seq_printf(f, "Character devices:\n");
chrdev_show(f, i);
} else {
i -= CHRDEV_MAJOR_HASH_SIZE;
if (i == 0)
seq_printf(f, "\nBlock devices:\n");
blkdev_show(f, i);
}
return 0;
}
static void *devinfo_start(struct seq_file *f, loff_t *pos) static void *devinfo_start(struct seq_file *f, loff_t *pos)
{ {
struct devinfo_state *info = f->private; if (*pos < (BLKDEV_MAJOR_HASH_SIZE + CHRDEV_MAJOR_HASH_SIZE))
return pos;
if (*pos) {
if ((info) && (*pos <= info->num_records))
return info;
return NULL; 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) 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)++;
/*fallthrough*/
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)++; (*pos)++;
break; if (*pos >= (BLKDEV_MAJOR_HASH_SIZE + CHRDEV_MAJOR_HASH_SIZE))
case DEVINFO_DONE: return NULL;
(*pos)++; return 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) static void devinfo_stop(struct seq_file *f, void *v)
{ {
struct devinfo_state *info = f->private; /* Nothing to do */
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 = { static struct seq_operations devinfo_ops = {
.start = devinfo_start, .start = devinfo_start,
.next = devinfo_next, .next = devinfo_next,
.stop = devinfo_stop, .stop = devinfo_stop,
.show = devinfo_show, .show = devinfo_show
}; };
static int devinfo_open(struct inode *inode, struct file *file) static int devinfo_open(struct inode *inode, struct file *filp)
{ {
return seq_open(file, &devinfo_op); return seq_open(filp, &devinfo_ops);
} }
static struct file_operations proc_devinfo_operations = { static struct file_operations proc_devinfo_operations = {
...@@ -396,13 +312,6 @@ static struct file_operations proc_devinfo_operations = { ...@@ -396,13 +312,6 @@ static struct file_operations proc_devinfo_operations = {
.release = seq_release, .release = seq_release,
}; };
static struct file_operations proc_cpuinfo_operations = {
.open = cpuinfo_open,
.read = seq_read,
.llseek = seq_lseek,
.release = seq_release,
};
extern struct seq_operations vmstat_op; extern struct seq_operations vmstat_op;
static int vmstat_open(struct inode *inode, struct file *file) static int vmstat_open(struct inode *inode, struct file *file)
{ {
......
...@@ -1413,6 +1413,7 @@ extern void bd_release_from_disk(struct block_device *, struct gendisk *); ...@@ -1413,6 +1413,7 @@ extern void bd_release_from_disk(struct block_device *, struct gendisk *);
#endif #endif
/* fs/char_dev.c */ /* fs/char_dev.c */
#define CHRDEV_MAJOR_HASH_SIZE 255
extern int alloc_chrdev_region(dev_t *, unsigned, unsigned, const char *); extern int alloc_chrdev_region(dev_t *, unsigned, unsigned, const char *);
extern int register_chrdev_region(dev_t, unsigned, const char *); extern int register_chrdev_region(dev_t, unsigned, const char *);
extern int register_chrdev(unsigned int, const char *, extern int register_chrdev(unsigned int, const char *,
...@@ -1420,25 +1421,17 @@ extern int register_chrdev(unsigned int, const char *, ...@@ -1420,25 +1421,17 @@ extern int register_chrdev(unsigned int, const char *,
extern int unregister_chrdev(unsigned int, const char *); extern int unregister_chrdev(unsigned int, const char *);
extern void unregister_chrdev_region(dev_t, unsigned); extern void unregister_chrdev_region(dev_t, unsigned);
extern int chrdev_open(struct inode *, struct file *); extern int chrdev_open(struct inode *, struct file *);
extern int get_chrdev_list(char *); extern void chrdev_show(struct seq_file *,off_t);
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 */ /* fs/block_dev.c */
#define BLKDEV_MAJOR_HASH_SIZE 255
#define BDEVNAME_SIZE 32 /* Largest string for a blockdev identifier */ #define BDEVNAME_SIZE 32 /* Largest string for a blockdev identifier */
extern const char *__bdevname(dev_t, char *buffer); extern const char *__bdevname(dev_t, char *buffer);
extern const char *bdevname(struct block_device *bdev, char *buffer); extern const char *bdevname(struct block_device *bdev, char *buffer);
extern struct block_device *lookup_bdev(const char *); extern struct block_device *lookup_bdev(const char *);
extern struct block_device *open_bdev_excl(const char *, int, void *); extern struct block_device *open_bdev_excl(const char *, int, void *);
extern void close_bdev_excl(struct block_device *); extern void close_bdev_excl(struct block_device *);
extern void *acquire_blkdev_list(void); extern void blkdev_show(struct seq_file *,off_t);
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); extern void init_special_inode(struct inode *, umode_t, dev_t);
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment