]> www.pilppa.org Git - linux-2.6-omap-h63xx.git/blobdiff - block/genhd.c
block: fix disk->part[] dereferencing race
[linux-2.6-omap-h63xx.git] / block / genhd.c
index fa32d09fda24c1f840454f3671dd8ace2cd491de..b431d65439429a91cca1e996464ad931d1b3eaf5 100644 (file)
@@ -26,6 +26,158 @@ struct kobject *block_depr;
 
 static struct device_type disk_type;
 
+/**
+ * disk_get_part - get partition
+ * @disk: disk to look partition from
+ * @partno: partition number
+ *
+ * Look for partition @partno from @disk.  If found, increment
+ * reference count and return it.
+ *
+ * CONTEXT:
+ * Don't care.
+ *
+ * RETURNS:
+ * Pointer to the found partition on success, NULL if not found.
+ */
+struct hd_struct *disk_get_part(struct gendisk *disk, int partno)
+{
+       struct hd_struct *part;
+
+       if (unlikely(partno < 1 || partno > disk_max_parts(disk)))
+               return NULL;
+       rcu_read_lock();
+       part = rcu_dereference(disk->__part[partno - 1]);
+       if (part)
+               get_device(&part->dev);
+       rcu_read_unlock();
+
+       return part;
+}
+EXPORT_SYMBOL_GPL(disk_get_part);
+
+/**
+ * disk_part_iter_init - initialize partition iterator
+ * @piter: iterator to initialize
+ * @disk: disk to iterate over
+ * @flags: DISK_PITER_* flags
+ *
+ * Initialize @piter so that it iterates over partitions of @disk.
+ *
+ * CONTEXT:
+ * Don't care.
+ */
+void disk_part_iter_init(struct disk_part_iter *piter, struct gendisk *disk,
+                         unsigned int flags)
+{
+       piter->disk = disk;
+       piter->part = NULL;
+
+       if (flags & DISK_PITER_REVERSE)
+               piter->idx = disk_max_parts(piter->disk) - 1;
+       else
+               piter->idx = 0;
+
+       piter->flags = flags;
+}
+EXPORT_SYMBOL_GPL(disk_part_iter_init);
+
+/**
+ * disk_part_iter_next - proceed iterator to the next partition and return it
+ * @piter: iterator of interest
+ *
+ * Proceed @piter to the next partition and return it.
+ *
+ * CONTEXT:
+ * Don't care.
+ */
+struct hd_struct *disk_part_iter_next(struct disk_part_iter *piter)
+{
+       int inc, end;
+
+       /* put the last partition */
+       disk_put_part(piter->part);
+       piter->part = NULL;
+
+       rcu_read_lock();
+
+       /* determine iteration parameters */
+       if (piter->flags & DISK_PITER_REVERSE) {
+               inc = -1;
+               end = -1;
+       } else {
+               inc = 1;
+               end = disk_max_parts(piter->disk);
+       }
+
+       /* iterate to the next partition */
+       for (; piter->idx != end; piter->idx += inc) {
+               struct hd_struct *part;
+
+               part = rcu_dereference(piter->disk->__part[piter->idx]);
+               if (!part)
+                       continue;
+               if (!(piter->flags & DISK_PITER_INCL_EMPTY) && !part->nr_sects)
+                       continue;
+
+               get_device(&part->dev);
+               piter->part = part;
+               piter->idx += inc;
+               break;
+       }
+
+       rcu_read_unlock();
+
+       return piter->part;
+}
+EXPORT_SYMBOL_GPL(disk_part_iter_next);
+
+/**
+ * disk_part_iter_exit - finish up partition iteration
+ * @piter: iter of interest
+ *
+ * Called when iteration is over.  Cleans up @piter.
+ *
+ * CONTEXT:
+ * Don't care.
+ */
+void disk_part_iter_exit(struct disk_part_iter *piter)
+{
+       disk_put_part(piter->part);
+       piter->part = NULL;
+}
+EXPORT_SYMBOL_GPL(disk_part_iter_exit);
+
+/**
+ * disk_map_sector_rcu - map sector to partition
+ * @disk: gendisk of interest
+ * @sector: sector to map
+ *
+ * Find out which partition @sector maps to on @disk.  This is
+ * primarily used for stats accounting.
+ *
+ * CONTEXT:
+ * RCU read locked.  The returned partition pointer is valid only
+ * while preemption is disabled.
+ *
+ * RETURNS:
+ * Found partition on success, NULL if there's no matching partition.
+ */
+struct hd_struct *disk_map_sector_rcu(struct gendisk *disk, sector_t sector)
+{
+       int i;
+
+       for (i = 0; i < disk_max_parts(disk); i++) {
+               struct hd_struct *part = rcu_dereference(disk->__part[i]);
+
+               if (part && part->start_sect <= sector &&
+                   sector < part->start_sect + part->nr_sects)
+                       return part;
+       }
+       return NULL;
+}
+EXPORT_SYMBOL_GPL(disk_map_sector_rcu);
+
 /*
  * Can be deleted altogether. Later.
  *
@@ -245,10 +397,12 @@ extern struct block_device *bdget_disk(struct gendisk *disk, int partno)
        if (partno == 0)
                devt = disk_devt(disk);
        else {
-               struct hd_struct *part = disk->part[partno - 1];
+               struct hd_struct *part;
 
+               part = disk_get_part(disk, partno);
                if (part && part->nr_sects)
                        devt = part_devt(part);
+               disk_put_part(part);
        }
 
        if (likely(devt != MKDEV(0, 0)))
@@ -270,8 +424,9 @@ void __init printk_all_partitions(void)
        class_dev_iter_init(&iter, &block_class, NULL, &disk_type);
        while ((dev = class_dev_iter_next(&iter))) {
                struct gendisk *disk = dev_to_disk(dev);
+               struct disk_part_iter piter;
+               struct hd_struct *part;
                char buf[BDEVNAME_SIZE];
-               int n;
 
                /*
                 * Don't show empty devices or things that have been
@@ -298,16 +453,13 @@ void __init printk_all_partitions(void)
                        printk(" (driver?)\n");
 
                /* now show the partitions */
-               for (n = 0; n < disk_max_parts(disk); ++n) {
-                       struct hd_struct *part = disk->part[n];
-
-                       if (!part || !part->nr_sects)
-                               continue;
+               disk_part_iter_init(&piter, disk, 0);
+               while ((part = disk_part_iter_next(&piter)))
                        printk("  %02x%02x %10llu %s\n",
                               MAJOR(part_devt(part)), MINOR(part_devt(part)),
                               (unsigned long long)part->nr_sects >> 1,
                               disk_name(disk, part->partno, buf));
-               }
+               disk_part_iter_exit(&piter);
        }
        class_dev_iter_exit(&iter);
 }
@@ -371,7 +523,8 @@ static void *show_partition_start(struct seq_file *seqf, loff_t *pos)
 static int show_partition(struct seq_file *seqf, void *v)
 {
        struct gendisk *sgp = v;
-       int n;
+       struct disk_part_iter piter;
+       struct hd_struct *part;
        char buf[BDEVNAME_SIZE];
 
        /* Don't show non-partitionable removeable devices or empty devices */
@@ -386,17 +539,14 @@ static int show_partition(struct seq_file *seqf, void *v)
                MAJOR(disk_devt(sgp)), MINOR(disk_devt(sgp)),
                (unsigned long long)get_capacity(sgp) >> 1,
                disk_name(sgp, 0, buf));
-       for (n = 0; n < disk_max_parts(sgp); n++) {
-               struct hd_struct *part = sgp->part[n];
-               if (!part)
-                       continue;
-               if (part->nr_sects == 0)
-                       continue;
+
+       disk_part_iter_init(&piter, sgp, 0);
+       while ((part = disk_part_iter_next(&piter)))
                seq_printf(seqf, "%4d  %4d %10llu %s\n",
                           MAJOR(part_devt(part)), MINOR(part_devt(part)),
                           (unsigned long long)part->nr_sects >> 1,
                           disk_name(sgp, part->partno, buf));
-       }
+       disk_part_iter_exit(&piter);
 
        return 0;
 }
@@ -571,7 +721,7 @@ static void disk_release(struct device *dev)
        struct gendisk *disk = dev_to_disk(dev);
 
        kfree(disk->random);
-       kfree(disk->part);
+       kfree(disk->__part);
        free_disk_stats(disk);
        kfree(disk);
 }
@@ -596,8 +746,9 @@ static struct device_type disk_type = {
 static int diskstats_show(struct seq_file *seqf, void *v)
 {
        struct gendisk *gp = v;
+       struct disk_part_iter piter;
+       struct hd_struct *hd;
        char buf[BDEVNAME_SIZE];
-       int n;
 
        /*
        if (&gp->dev.kobj.entry == block_class.devices.next)
@@ -624,12 +775,8 @@ static int diskstats_show(struct seq_file *seqf, void *v)
                jiffies_to_msecs(disk_stat_read(gp, time_in_queue)));
 
        /* now show all non-0 size partitions of it */
-       for (n = 0; n < disk_max_parts(gp); n++) {
-               struct hd_struct *hd = gp->part[n];
-
-               if (!hd || !hd->nr_sects)
-                       continue;
-
+       disk_part_iter_init(&piter, gp, 0);
+       while ((hd = disk_part_iter_next(&piter))) {
                preempt_disable();
                part_round_stats(hd);
                preempt_enable();
@@ -650,6 +797,7 @@ static int diskstats_show(struct seq_file *seqf, void *v)
                           jiffies_to_msecs(part_stat_read(hd, time_in_queue))
                        );
        }
+       disk_part_iter_exit(&piter);
  
        return 0;
 }
@@ -703,12 +851,16 @@ dev_t blk_lookup_devt(const char *name, int partno)
                if (partno == 0)
                        devt = disk_devt(disk);
                else {
-                       struct hd_struct *part = disk->part[partno - 1];
+                       struct hd_struct *part;
 
-                       if (!part || !part->nr_sects)
+                       part = disk_get_part(disk, partno);
+                       if (!part || !part->nr_sects) {
+                               disk_put_part(part);
                                continue;
+                       }
 
                        devt = part_devt(part);
+                       disk_put_part(part);
                }
                break;
        }
@@ -735,9 +887,9 @@ struct gendisk *alloc_disk_node(int minors, int node_id)
                }
                if (minors > 1) {
                        int size = (minors - 1) * sizeof(struct hd_struct *);
-                       disk->part = kmalloc_node(size,
+                       disk->__part = kmalloc_node(size,
                                GFP_KERNEL | __GFP_ZERO, node_id);
-                       if (!disk->part) {
+                       if (!disk->__part) {
                                free_disk_stats(disk);
                                kfree(disk);
                                return NULL;
@@ -798,10 +950,14 @@ EXPORT_SYMBOL(set_device_ro);
 
 void set_disk_ro(struct gendisk *disk, int flag)
 {
-       int i;
+       struct disk_part_iter piter;
+       struct hd_struct *part;
+
        disk->policy = flag;
-       for (i = 0; i < disk_max_parts(disk); i++)
-               if (disk->part[i]) disk->part[i]->policy = flag;
+       disk_part_iter_init(&piter, disk, DISK_PITER_INCL_EMPTY);
+       while ((part = disk_part_iter_next(&piter)))
+               part->policy = flag;
+       disk_part_iter_exit(&piter);
 }
 
 EXPORT_SYMBOL(set_disk_ro);