diff options
author | Alasdair G Kergon <agk@redhat.com> | 2006-06-26 03:27:34 -0400 |
---|---|---|
committer | Linus Torvalds <torvalds@g5.osdl.org> | 2006-06-26 12:58:36 -0400 |
commit | 5c6bd75d06db512515a3781aa97e42df2faf0815 (patch) | |
tree | 43d68c6d3174e1ae8a778acd834665c122f8b2a4 /drivers | |
parent | c2ade42dd35466d90aa6fc7cc717f396e165492f (diff) |
[PATCH] dm: prevent removal if open
If you misuse the device-mapper interface (or there's a bug in your userspace
tools) it's possible to end up with 'unlinked' mapped devices that cannot be
removed until you reboot (along with uninterruptible processes).
This patch prevents you from removing a device that is still open.
It introduces dm_lock_for_deletion() which is called when a device is about to
be removed to ensure that nothing has it open and nothing further can open it.
It uses a private open_count for this which also lets us remove one of the
problematic bdget_disk() calls elsewhere.
Signed-off-by: Alasdair G Kergon <agk@redhat.com>
Signed-off-by: Andrew Morton <akpm@osdl.org>
Signed-off-by: Linus Torvalds <torvalds@osdl.org>
Diffstat (limited to 'drivers')
-rw-r--r-- | drivers/md/dm-ioctl.c | 65 | ||||
-rw-r--r-- | drivers/md/dm.c | 32 | ||||
-rw-r--r-- | drivers/md/dm.h | 2 |
3 files changed, 78 insertions, 21 deletions
diff --git a/drivers/md/dm-ioctl.c b/drivers/md/dm-ioctl.c index c826b3e1799a..c6ce13b18747 100644 --- a/drivers/md/dm-ioctl.c +++ b/drivers/md/dm-ioctl.c | |||
@@ -48,7 +48,7 @@ struct vers_iter { | |||
48 | static struct list_head _name_buckets[NUM_BUCKETS]; | 48 | static struct list_head _name_buckets[NUM_BUCKETS]; |
49 | static struct list_head _uuid_buckets[NUM_BUCKETS]; | 49 | static struct list_head _uuid_buckets[NUM_BUCKETS]; |
50 | 50 | ||
51 | static void dm_hash_remove_all(void); | 51 | static void dm_hash_remove_all(int keep_open_devices); |
52 | 52 | ||
53 | /* | 53 | /* |
54 | * Guards access to both hash tables. | 54 | * Guards access to both hash tables. |
@@ -73,7 +73,7 @@ static int dm_hash_init(void) | |||
73 | 73 | ||
74 | static void dm_hash_exit(void) | 74 | static void dm_hash_exit(void) |
75 | { | 75 | { |
76 | dm_hash_remove_all(); | 76 | dm_hash_remove_all(0); |
77 | devfs_remove(DM_DIR); | 77 | devfs_remove(DM_DIR); |
78 | } | 78 | } |
79 | 79 | ||
@@ -260,19 +260,41 @@ static void __hash_remove(struct hash_cell *hc) | |||
260 | free_cell(hc); | 260 | free_cell(hc); |
261 | } | 261 | } |
262 | 262 | ||
263 | static void dm_hash_remove_all(void) | 263 | static void dm_hash_remove_all(int keep_open_devices) |
264 | { | 264 | { |
265 | int i; | 265 | int i, dev_skipped, dev_removed; |
266 | struct hash_cell *hc; | 266 | struct hash_cell *hc; |
267 | struct list_head *tmp, *n; | 267 | struct list_head *tmp, *n; |
268 | 268 | ||
269 | down_write(&_hash_lock); | 269 | down_write(&_hash_lock); |
270 | |||
271 | retry: | ||
272 | dev_skipped = dev_removed = 0; | ||
270 | for (i = 0; i < NUM_BUCKETS; i++) { | 273 | for (i = 0; i < NUM_BUCKETS; i++) { |
271 | list_for_each_safe (tmp, n, _name_buckets + i) { | 274 | list_for_each_safe (tmp, n, _name_buckets + i) { |
272 | hc = list_entry(tmp, struct hash_cell, name_list); | 275 | hc = list_entry(tmp, struct hash_cell, name_list); |
276 | |||
277 | if (keep_open_devices && | ||
278 | dm_lock_for_deletion(hc->md)) { | ||
279 | dev_skipped++; | ||
280 | continue; | ||
281 | } | ||
273 | __hash_remove(hc); | 282 | __hash_remove(hc); |
283 | dev_removed = 1; | ||
274 | } | 284 | } |
275 | } | 285 | } |
286 | |||
287 | /* | ||
288 | * Some mapped devices may be using other mapped devices, so if any | ||
289 | * still exist, repeat until we make no further progress. | ||
290 | */ | ||
291 | if (dev_skipped) { | ||
292 | if (dev_removed) | ||
293 | goto retry; | ||
294 | |||
295 | DMWARN("remove_all left %d open device(s)", dev_skipped); | ||
296 | } | ||
297 | |||
276 | up_write(&_hash_lock); | 298 | up_write(&_hash_lock); |
277 | } | 299 | } |
278 | 300 | ||
@@ -355,7 +377,7 @@ typedef int (*ioctl_fn)(struct dm_ioctl *param, size_t param_size); | |||
355 | 377 | ||
356 | static int remove_all(struct dm_ioctl *param, size_t param_size) | 378 | static int remove_all(struct dm_ioctl *param, size_t param_size) |
357 | { | 379 | { |
358 | dm_hash_remove_all(); | 380 | dm_hash_remove_all(1); |
359 | param->data_size = 0; | 381 | param->data_size = 0; |
360 | return 0; | 382 | return 0; |
361 | } | 383 | } |
@@ -535,7 +557,6 @@ static int __dev_status(struct mapped_device *md, struct dm_ioctl *param) | |||
535 | { | 557 | { |
536 | struct gendisk *disk = dm_disk(md); | 558 | struct gendisk *disk = dm_disk(md); |
537 | struct dm_table *table; | 559 | struct dm_table *table; |
538 | struct block_device *bdev; | ||
539 | 560 | ||
540 | param->flags &= ~(DM_SUSPEND_FLAG | DM_READONLY_FLAG | | 561 | param->flags &= ~(DM_SUSPEND_FLAG | DM_READONLY_FLAG | |
541 | DM_ACTIVE_PRESENT_FLAG); | 562 | DM_ACTIVE_PRESENT_FLAG); |
@@ -545,20 +566,12 @@ static int __dev_status(struct mapped_device *md, struct dm_ioctl *param) | |||
545 | 566 | ||
546 | param->dev = huge_encode_dev(MKDEV(disk->major, disk->first_minor)); | 567 | param->dev = huge_encode_dev(MKDEV(disk->major, disk->first_minor)); |
547 | 568 | ||
548 | if (!(param->flags & DM_SKIP_BDGET_FLAG)) { | 569 | /* |
549 | bdev = bdget_disk(disk, 0); | 570 | * Yes, this will be out of date by the time it gets back |
550 | if (!bdev) | 571 | * to userland, but it is still very useful for |
551 | return -ENXIO; | 572 | * debugging. |
552 | 573 | */ | |
553 | /* | 574 | param->open_count = dm_open_count(md); |
554 | * Yes, this will be out of date by the time it gets back | ||
555 | * to userland, but it is still very useful for | ||
556 | * debugging. | ||
557 | */ | ||
558 | param->open_count = bdev->bd_openers; | ||
559 | bdput(bdev); | ||
560 | } else | ||
561 | param->open_count = -1; | ||
562 | 575 | ||
563 | if (disk->policy) | 576 | if (disk->policy) |
564 | param->flags |= DM_READONLY_FLAG; | 577 | param->flags |= DM_READONLY_FLAG; |
@@ -661,6 +674,7 @@ static int dev_remove(struct dm_ioctl *param, size_t param_size) | |||
661 | { | 674 | { |
662 | struct hash_cell *hc; | 675 | struct hash_cell *hc; |
663 | struct mapped_device *md; | 676 | struct mapped_device *md; |
677 | int r; | ||
664 | 678 | ||
665 | down_write(&_hash_lock); | 679 | down_write(&_hash_lock); |
666 | hc = __find_device_hash_cell(param); | 680 | hc = __find_device_hash_cell(param); |
@@ -673,6 +687,17 @@ static int dev_remove(struct dm_ioctl *param, size_t param_size) | |||
673 | 687 | ||
674 | md = hc->md; | 688 | md = hc->md; |
675 | 689 | ||
690 | /* | ||
691 | * Ensure the device is not open and nothing further can open it. | ||
692 | */ | ||
693 | r = dm_lock_for_deletion(md); | ||
694 | if (r) { | ||
695 | DMWARN("unable to remove open device %s", hc->name); | ||
696 | up_write(&_hash_lock); | ||
697 | dm_put(md); | ||
698 | return r; | ||
699 | } | ||
700 | |||
676 | __hash_remove(hc); | 701 | __hash_remove(hc); |
677 | up_write(&_hash_lock); | 702 | up_write(&_hash_lock); |
678 | dm_put(md); | 703 | dm_put(md); |
diff --git a/drivers/md/dm.c b/drivers/md/dm.c index 952c49c3b9aa..6982f86db6dc 100644 --- a/drivers/md/dm.c +++ b/drivers/md/dm.c | |||
@@ -64,12 +64,14 @@ union map_info *dm_get_mapinfo(struct bio *bio) | |||
64 | #define DMF_SUSPENDED 1 | 64 | #define DMF_SUSPENDED 1 |
65 | #define DMF_FROZEN 2 | 65 | #define DMF_FROZEN 2 |
66 | #define DMF_FREEING 3 | 66 | #define DMF_FREEING 3 |
67 | #define DMF_DELETING 4 | ||
67 | 68 | ||
68 | struct mapped_device { | 69 | struct mapped_device { |
69 | struct rw_semaphore io_lock; | 70 | struct rw_semaphore io_lock; |
70 | struct semaphore suspend_lock; | 71 | struct semaphore suspend_lock; |
71 | rwlock_t map_lock; | 72 | rwlock_t map_lock; |
72 | atomic_t holders; | 73 | atomic_t holders; |
74 | atomic_t open_count; | ||
73 | 75 | ||
74 | unsigned long flags; | 76 | unsigned long flags; |
75 | 77 | ||
@@ -228,12 +230,14 @@ static int dm_blk_open(struct inode *inode, struct file *file) | |||
228 | if (!md) | 230 | if (!md) |
229 | goto out; | 231 | goto out; |
230 | 232 | ||
231 | if (test_bit(DMF_FREEING, &md->flags)) { | 233 | if (test_bit(DMF_FREEING, &md->flags) || |
234 | test_bit(DMF_DELETING, &md->flags)) { | ||
232 | md = NULL; | 235 | md = NULL; |
233 | goto out; | 236 | goto out; |
234 | } | 237 | } |
235 | 238 | ||
236 | dm_get(md); | 239 | dm_get(md); |
240 | atomic_inc(&md->open_count); | ||
237 | 241 | ||
238 | out: | 242 | out: |
239 | spin_unlock(&_minor_lock); | 243 | spin_unlock(&_minor_lock); |
@@ -246,10 +250,35 @@ static int dm_blk_close(struct inode *inode, struct file *file) | |||
246 | struct mapped_device *md; | 250 | struct mapped_device *md; |
247 | 251 | ||
248 | md = inode->i_bdev->bd_disk->private_data; | 252 | md = inode->i_bdev->bd_disk->private_data; |
253 | atomic_dec(&md->open_count); | ||
249 | dm_put(md); | 254 | dm_put(md); |
250 | return 0; | 255 | return 0; |
251 | } | 256 | } |
252 | 257 | ||
258 | int dm_open_count(struct mapped_device *md) | ||
259 | { | ||
260 | return atomic_read(&md->open_count); | ||
261 | } | ||
262 | |||
263 | /* | ||
264 | * Guarantees nothing is using the device before it's deleted. | ||
265 | */ | ||
266 | int dm_lock_for_deletion(struct mapped_device *md) | ||
267 | { | ||
268 | int r = 0; | ||
269 | |||
270 | spin_lock(&_minor_lock); | ||
271 | |||
272 | if (dm_open_count(md)) | ||
273 | r = -EBUSY; | ||
274 | else | ||
275 | set_bit(DMF_DELETING, &md->flags); | ||
276 | |||
277 | spin_unlock(&_minor_lock); | ||
278 | |||
279 | return r; | ||
280 | } | ||
281 | |||
253 | static int dm_blk_getgeo(struct block_device *bdev, struct hd_geometry *geo) | 282 | static int dm_blk_getgeo(struct block_device *bdev, struct hd_geometry *geo) |
254 | { | 283 | { |
255 | struct mapped_device *md = bdev->bd_disk->private_data; | 284 | struct mapped_device *md = bdev->bd_disk->private_data; |
@@ -867,6 +896,7 @@ static struct mapped_device *alloc_dev(int minor) | |||
867 | init_MUTEX(&md->suspend_lock); | 896 | init_MUTEX(&md->suspend_lock); |
868 | rwlock_init(&md->map_lock); | 897 | rwlock_init(&md->map_lock); |
869 | atomic_set(&md->holders, 1); | 898 | atomic_set(&md->holders, 1); |
899 | atomic_set(&md->open_count, 0); | ||
870 | atomic_set(&md->event_nr, 0); | 900 | atomic_set(&md->event_nr, 0); |
871 | 901 | ||
872 | md->queue = blk_alloc_queue(GFP_KERNEL); | 902 | md->queue = blk_alloc_queue(GFP_KERNEL); |
diff --git a/drivers/md/dm.h b/drivers/md/dm.h index 71ddd1e23007..9ebb24b037bc 100644 --- a/drivers/md/dm.h +++ b/drivers/md/dm.h | |||
@@ -123,5 +123,7 @@ void dm_stripe_exit(void); | |||
123 | 123 | ||
124 | void *dm_vcalloc(unsigned long nmemb, unsigned long elem_size); | 124 | void *dm_vcalloc(unsigned long nmemb, unsigned long elem_size); |
125 | union map_info *dm_get_mapinfo(struct bio *bio); | 125 | union map_info *dm_get_mapinfo(struct bio *bio); |
126 | int dm_open_count(struct mapped_device *md); | ||
127 | int dm_lock_for_deletion(struct mapped_device *md); | ||
126 | 128 | ||
127 | #endif | 129 | #endif |