Currently when specifying major number as 0, register_blkdev() will try to alloc any unused major number in the range. However, the allocating logic does not accuretaly compare major number with existing entries, so even we have plenty of available major numbers but still might fail with -EBUSY in extreme case. Signed-off-by: Chengguang Xu <cgxu519@xxxxxxx> --- block/genhd.c | 103 ++++++++++++++++++++++++++------------------------ 1 file changed, 54 insertions(+), 49 deletions(-) diff --git a/block/genhd.c b/block/genhd.c index 1dd8fd6613b8..80b788ed17d1 100644 --- a/block/genhd.c +++ b/block/genhd.c @@ -330,6 +330,40 @@ void blkdev_show(struct seq_file *seqf, off_t offset) } #endif /* CONFIG_PROC_FS */ +static int __register_blkdev(unsigned int major, const char *name, + struct blk_major_name *new) +{ + struct blk_major_name *n, *p = NULL; + int index = major_to_index(major); + + for (n = major_names[index]; n; n = n->next) { + if (n->major == major) + return -EBUSY; + p = n; + } + + new->major = major; + if (p == NULL) + major_names[index] = new; + else + p->next = new; + + return 0; +} + +static int alloc_blkdev(unsigned int major, const char *name, + struct blk_major_name *new) +{ + int index; + + for (index = ARRAY_SIZE(major_names) - 1; index; index--) { + if (__register_blkdev(index, name, new) == 0) + return index; + } + + return -EBUSY; +} + /** * register_blkdev - register a new block device * @@ -345,73 +379,44 @@ void blkdev_show(struct seq_file *seqf, off_t offset) * then the function returns zero on success, or a negative error code * - if any unused major number was requested with @major = 0 parameter * then the return value is the allocated major number in range - * [1..BLKDEV_MAJOR_MAX-1] or a negative error code otherwise + * [1..BLKDEV_MAJOR_HASH_SIZE-1] or a negative error code otherwise * * See Documentation/admin-guide/devices.txt for the list of allocated * major numbers. */ int register_blkdev(unsigned int major, const char *name) { - struct blk_major_name **n, *p; - int index, ret = 0; - - mutex_lock(&block_class_lock); - - /* temporary */ - if (major == 0) { - for (index = ARRAY_SIZE(major_names)-1; index > 0; index--) { - if (major_names[index] == NULL) - break; - } - - if (index == 0) { - printk("register_blkdev: failed to get major for %s\n", - name); - ret = -EBUSY; - goto out; - } - major = index; - ret = major; - } + struct blk_major_name *new; + int ret; if (major >= BLKDEV_MAJOR_MAX) { - pr_err("register_blkdev: major requested (%u) is greater than the maximum (%u) for %s\n", - major, BLKDEV_MAJOR_MAX-1, name); - - ret = -EINVAL; - goto out; + pr_err("%s: major requested (%u) is greater than the maximum (%u) for %s\n", + __func__, major, BLKDEV_MAJOR_MAX-1, name); + return -EINVAL; } - p = kmalloc(sizeof(struct blk_major_name), GFP_KERNEL); - if (p == NULL) { - ret = -ENOMEM; - goto out; - } + new = kmalloc(sizeof(struct blk_major_name), GFP_KERNEL); + if (new == NULL) + return -ENOMEM; - p->major = major; - strlcpy(p->name, name, sizeof(p->name)); - p->next = NULL; - index = major_to_index(major); + strlcpy(new->name, name, sizeof(new->name)); + new->next = NULL; - for (n = &major_names[index]; *n; n = &(*n)->next) { - if ((*n)->major == major) - break; - } - if (!*n) - *n = p; + mutex_lock(&block_class_lock); + if (major == 0) + ret = alloc_blkdev(major, name, new); else - ret = -EBUSY; + ret = __register_blkdev(major, name, new); + mutex_unlock(&block_class_lock); if (ret < 0) { - printk("register_blkdev: cannot get major %u for %s\n", - major, name); - kfree(p); + kfree(new); + pr_err("%s: cannot get major for %s, major requested (%u)\n", + __func__, name, major); } -out: - mutex_unlock(&block_class_lock); + return ret; } - EXPORT_SYMBOL(register_blkdev); void unregister_blkdev(unsigned int major, const char *name) -- 2.20.1