- If two CPUs run register_chrdev_region(major == 0) at the same time they can get the same major. Fix that by extending the lock coverage. - local variable `cd' was leaky on an error path. - Add some API commentary. fs/char_dev.c | 39 ++++++++++++++++++++++++++------------- 1 files changed, 26 insertions(+), 13 deletions(-) diff -puN fs/char_dev.c~register_chrdev_region-leak-fix fs/char_dev.c --- 25/fs/char_dev.c~register_chrdev_region-leak-fix 2003-03-23 00:51:35.000000000 -0800 +++ 25-akpm/fs/char_dev.c 2003-03-23 01:02:14.000000000 -0800 @@ -115,7 +115,15 @@ get_chrfops(unsigned int major, unsigned } /* - * Register a single major with a specified minor range + * Register a single major with a specified minor range. + * + * If major == 0 this functions will dynamically allocate a major and return + * its number. + * + * If major > 0 this function will attempt to reserve the passed range of + * minors and will return zero on success. + * + * Returns a -ve errno on failure. */ int register_chrdev_region(unsigned int major, unsigned int baseminor, int minorct, const char *name, @@ -125,23 +133,27 @@ int register_chrdev_region(unsigned int int ret = 0; int i; + cd = kmalloc(sizeof(struct char_device_struct), GFP_KERNEL); + if (cd == NULL) + return -ENOMEM; + + write_lock_irq(&chrdevs_lock); + /* temporary */ if (major == 0) { - read_lock(&chrdevs_lock); - for (i = ARRAY_SIZE(chrdevs)-1; i > 0; i--) + for (i = ARRAY_SIZE(chrdevs)-1; i > 0; i--) { if (chrdevs[i] == NULL) break; - read_unlock(&chrdevs_lock); + } - if (i == 0) - return -EBUSY; - ret = major = i; + if (i == 0) { + ret = -EBUSY; + goto out; + } + major = i; + ret = major; } - cd = kmalloc(sizeof(struct char_device_struct), GFP_KERNEL); - if (cd == NULL) - return -ENOMEM; - cd->major = major; cd->baseminor = baseminor; cd->minorct = minorct; @@ -150,7 +162,6 @@ int register_chrdev_region(unsigned int i = major_to_index(major); - write_lock_irq(&chrdevs_lock); for (cp = &chrdevs[i]; *cp; cp = &(*cp)->next) if ((*cp)->major > major || ((*cp)->major == major && (*cp)->baseminor >= baseminor)) @@ -162,8 +173,10 @@ int register_chrdev_region(unsigned int cd->next = *cp; *cp = cd; } +out: write_unlock_irq(&chrdevs_lock); - + if (ret < 0) + kfree(cd); return ret; } _