From e7469c007904a5f20855ee322451ede65e4dc90e Mon Sep 17 00:00:00 2001 From: Alasdair Kergon Date: Mon, 14 Oct 2002 11:07:24 +0000 Subject: [PATCH] Hold devices in a hash table. --- VERSION | 2 +- kernel/Makefile.in | 2 +- kernel/common/dm-hash.c | 445 ++++++++++++++++++++++++++++++++++++++++ kernel/common/dm.c | 422 +++++++++++-------------------------- kernel/common/dm.h | 59 ++++-- kernel/ioctl/dm-ioctl.c | 61 +++--- kernel/ioctl/dm-ioctl.h | 4 +- 7 files changed, 652 insertions(+), 343 deletions(-) create mode 100644 kernel/common/dm-hash.c diff --git a/VERSION b/VERSION index 07a427f..b2922a6 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -0.96.05-cvs (2002-09-24) +0.96.05-cvs (2002-10-14) diff --git a/kernel/Makefile.in b/kernel/Makefile.in index 3c509ba..6cc6087 100644 --- a/kernel/Makefile.in +++ b/kernel/Makefile.in @@ -34,7 +34,7 @@ FS=dmfs-error.c dmfs-lv.c dmfs-root.c dmfs-status.c \ COMMON=dm-linear.c dm-stripe.c \ dm-snapshot.c kcopyd.c \ dm-table.c dm-target.c dm.c dm.h dm-snapshot.h \ - dm-exception-store.c kcopyd.h + dm-exception-store.c kcopyd.h dm-hash.c IOCTL=dm-ioctl.c diff --git a/kernel/common/dm-hash.c b/kernel/common/dm-hash.c new file mode 100644 index 0000000..d411208 --- /dev/null +++ b/kernel/common/dm-hash.c @@ -0,0 +1,445 @@ +/* + * Copyright (C) 2002 Sistina Software (UK) Limited. + * + * This file is released under the GPL. + */ + +/* + * We need to be able to quickly return the struct mapped_device, + * whether it is looked up by name, uuid or by kdev_t. Note that + * multiple major numbers are now supported, so we cannot keep + * things simple by putting them in an array. + * + * Instead this will be implemented as a trio of closely coupled + * hash tables. + */ + +#include +#include +#include + +#include "dm.h" + +struct hash_cell { + struct list_head list; + struct mapped_device *md; +}; + +#define NUM_BUCKETS 64 +#define MASK_BUCKETS (NUM_BUCKETS - 1) +#define HASH_MULT 2654435387U +static struct list_head *_dev_buckets; +static struct list_head *_name_buckets; +static struct list_head *_uuid_buckets; + +/* + * Guards access to all three tables. + */ +static DECLARE_RWSEM(_hash_lock); + + +/*----------------------------------------------------------------- + * Init/exit code + *---------------------------------------------------------------*/ +void dm_hash_exit(void) +{ + if (_dev_buckets) { + kfree(_dev_buckets); + _dev_buckets = NULL; + } + + if (_name_buckets) { + kfree(_name_buckets); + _name_buckets = NULL; + } + + if (_uuid_buckets) { + kfree(_uuid_buckets); + _uuid_buckets = NULL; + } +} + +struct list_head *alloc_buckets(void) +{ + struct list_head *buckets; + unsigned int i, len; + + len = NUM_BUCKETS * sizeof(struct list_head); + buckets = kmalloc(len, GFP_KERNEL); + if (buckets) + for (i = 0; i < NUM_BUCKETS; i++) + INIT_LIST_HEAD(buckets + i); + + return buckets; +} + +int dm_hash_init(void) +{ + _dev_buckets = alloc_buckets(); + if (!_dev_buckets) + goto bad; + + _name_buckets = alloc_buckets(); + if (!_name_buckets) + goto bad; + + _uuid_buckets = alloc_buckets(); + if (!_uuid_buckets) + goto bad; + + return 0; + + bad: + dm_hash_exit(); + return -ENOMEM; +} + + +/*----------------------------------------------------------------- + * Hash functions + *---------------------------------------------------------------*/ +static inline unsigned int hash_dev(kdev_t dev) +{ + return (HASHDEV(dev) * HASH_MULT) & MASK_BUCKETS; +} + +/* + * We're not really concerned with the str hash function being + * fast since it's only used by the ioctl interface. + */ +static unsigned int hash_str(const char *str) +{ + unsigned int h = 0; + + while (*str) + h = (h + (unsigned int) *str++) * HASH_MULT; + + return h & MASK_BUCKETS; +} + + +/*----------------------------------------------------------------- + * Code for looking up the device by kdev_t. + *---------------------------------------------------------------*/ +static struct hash_cell *__get_dev_cell(kdev_t dev) +{ + struct list_head *tmp; + struct hash_cell *hc; + unsigned int h = hash_dev(dev); + + list_for_each (tmp, _dev_buckets + h) { + hc = list_entry(tmp, struct hash_cell, list); + if (kdev_same(hc->md->dev, dev)) + return hc; + } + + return NULL; +} + +struct mapped_device *dm_get_r(kdev_t dev) +{ + struct hash_cell *hc; + struct mapped_device *md = NULL; + + down_read(&_hash_lock); + + hc = __get_dev_cell(dev); + if (!hc) + goto out; + + down_read(&hc->md->lock); + if (hc->md->invalid) { + up_read(&hc->md->lock); + goto out; + } + + md = hc->md; + + out: + up_read(&_hash_lock); + return md; +} + +struct mapped_device *dm_get_w(kdev_t dev) +{ + struct hash_cell *hc; + struct mapped_device *md = NULL; + + down_read(&_hash_lock); + + hc = __get_dev_cell(dev); + if (!hc) + goto out; + + down_write(&hc->md->lock); + if (hc->md->invalid) { + up_write(&hc->md->lock); + goto out; + } + + md = hc->md; + + out: + up_read(&_hash_lock); + return md; +} + + +/*----------------------------------------------------------------- + * Code for looking up a device by name + *---------------------------------------------------------------*/ +static int namecmp(struct mapped_device *md, const char *str, int uuid) +{ + if (!uuid) + return strcmp(md->name, str); + + if (!md->uuid) + return -1; /* never equal */ + + return strcmp(md->uuid, str); +} + +static struct hash_cell *__get_str_cell(const char *str, int uuid) +{ + struct list_head *tmp, *buckets; + struct hash_cell *hc; + unsigned int h = hash_str(str); + + buckets = uuid ? _uuid_buckets : _name_buckets; + list_for_each (tmp, buckets + h) { + hc = list_entry(tmp, struct hash_cell, list); + if (!namecmp(hc->md, str, uuid)) + return hc; + } + + return NULL; +} + +static inline struct mapped_device *get_name(const char *str, int uuid, + int write) +{ + struct hash_cell *hc; + struct mapped_device *md = NULL; + + down_read(&_hash_lock); + + hc = __get_str_cell(str, uuid); + if (!hc) + goto out; + + if (write) + down_write(&hc->md->lock); + else + down_read(&hc->md->lock); + + if (hc->md->invalid) { + if (write) + up_write(&hc->md->lock); + else + up_read(&hc->md->lock); + goto out; + } + + md = hc->md; + + out: + up_read(&_hash_lock); + + return md; +} + +struct mapped_device *dm_get_name_r(const char *name) +{ + return get_name(name, 0, 0); +} + +struct mapped_device *dm_get_name_w(const char *name) +{ + return get_name(name, 0, 1); +} + +struct mapped_device *dm_get_uuid_r(const char *uuid) +{ + return get_name(uuid, 1, 0); +} + +struct mapped_device *dm_get_uuid_w(const char *uuid) +{ + return get_name(uuid, 1, 1); +} + +/*----------------------------------------------------------------- + * Inserting and removing and renaming a device. + *---------------------------------------------------------------*/ +static struct hash_cell *alloc_cell(struct mapped_device *md) +{ + struct hash_cell *hc = kmalloc(sizeof(*hc), GFP_KERNEL); + if (hc) { + INIT_LIST_HEAD(&hc->list); + hc->md = md; + } + + return hc; +} + +/* + * The kdev_t and uuid of a device can never change once it is + * initially inserted. + */ +int dm_hash_insert(struct mapped_device *md) +{ + struct hash_cell *dev_cell, *name_cell, *uuid_cell; + + /* + * Allocate the new cells. + */ + dev_cell = name_cell = uuid_cell = NULL; + if (!(dev_cell = alloc_cell(md)) || + !(name_cell = alloc_cell(md)) || + !(uuid_cell = alloc_cell(md))) { + if (uuid_cell) + kfree(uuid_cell); + if (name_cell) + kfree(name_cell); + if (dev_cell) + kfree(dev_cell); + + return -ENOMEM; + } + + /* + * Insert the cell into all three hash tables. + */ + down_write(&_hash_lock); + if (__get_dev_cell(md->dev)) + goto bad; + + list_add(&dev_cell->list, _dev_buckets + hash_dev(md->dev)); + + if (__get_str_cell(md->name, 0)) { + list_del(&dev_cell->list); + goto bad; + } + list_add(&name_cell->list, _name_buckets + hash_str(md->name)); + + if (md->uuid) { + if (__get_str_cell(md->uuid, 1)) { + list_del(&name_cell->list); + list_del(&dev_cell->list); + goto bad; + } + list_add(&uuid_cell->list, _uuid_buckets + hash_str(md->uuid)); + } + up_write(&_hash_lock); + + if (!md->uuid) + kfree(uuid_cell); + + return 0; + + bad: + up_write(&_hash_lock); + kfree(uuid_cell); + kfree(name_cell); + kfree(dev_cell); + return -EBUSY; +} + +static void dispose_cell(struct hash_cell *hc) +{ + list_del(&hc->list); + kfree(hc); +} + +/* + * md should already have the write lock and md->invalid is already set. + */ +void dm_hash_remove(struct mapped_device *md) +{ + struct hash_cell *hc; + + /* + * Ensure that anything else waiting for the lock gets it and + * promptly releases it because md->invalid has now been set. + * Acquiring _hash_lock exclusively prevents anything else + * starting a search for an md until our md is completely removed. + */ + up_write(&md->lock); + down_write(&_hash_lock); + down_write(&md->lock); + + /* remove from the dev hash */ + hc = __get_dev_cell(md->dev); + if (!hc) + DMWARN("device doesn't appear to be in the dev hash table."); + else + dispose_cell(hc); + + /* remove from the name hash */ + hc = __get_str_cell(md->name, 0); + if (!hc) + DMWARN("device doesn't appear to be in the name hash table."); + else + dispose_cell(hc); + + /* remove from the uuid hash, if it has a uuid */ + if (md->uuid) { + hc = __get_str_cell(md->uuid, 1); + if (!hc) + DMWARN("device doesn't appear to be in the uuid " + "hash table."); + else + dispose_cell(hc); + } + + up_write(&_hash_lock); +} + +int dm_hash_rename(const char *old, const char *new) +{ + char *new_name, *old_name; + struct hash_cell *hc; + + /* + * duplicate new. + */ + new_name = dm_strdup(new); + if (!new_name) + return -ENOMEM; + + down_write(&_hash_lock); + + /* + * Is new free ? + */ + hc = __get_str_cell(new, 0); + if (hc) { + DMWARN("asked to rename to an already existing name %s -> %s", + old, new); + up_write(&_hash_lock); + return -EBUSY; + } + + /* + * Is there such a device as 'old' ? + */ + hc = __get_str_cell(old, 0); + if (!hc) { + DMWARN("asked to rename a non existent device %s -> %s", + old, new); + up_write(&_hash_lock); + return -ENXIO; + } + + /* + * rename and move the name cell. + */ + list_del(&hc->list); + old_name = hc->md->name; + hc->md->name = new_name; + list_add(&hc->list, _name_buckets + hash_str(new_name)); + + up_write(&_hash_lock); + kfree(old_name); + return 0; +} diff --git a/kernel/common/dm.c b/kernel/common/dm.c index 65bae2c..7a35bcc 100644 --- a/kernel/common/dm.c +++ b/kernel/common/dm.c @@ -30,16 +30,6 @@ struct io_hook { static kmem_cache_t *_io_hook_cache; -static struct mapped_device *_devs[MAX_DEVICES]; -static struct rw_semaphore _dev_locks[MAX_DEVICES]; - -/* - * This lock is only held by dm_create and dm_set_name to avoid - * race conditions where someone else may create a device with - * the same name. - */ -static spinlock_t _create_lock = SPIN_LOCK_UNLOCKED; - /* block device arrays */ static int _block_size[MAX_DEVICES]; static int _blksize_size[MAX_DEVICES]; @@ -50,162 +40,10 @@ static devfs_handle_t _dev_dir; static int request(request_queue_t * q, int rw, struct buffer_head *bh); static int dm_user_bmap(struct inode *inode, struct lv_bmap *lvb); -/* - * Protect the mapped_devices referenced from _dev[] - */ -struct mapped_device *dm_get_r(kdev_t dev) -{ - struct mapped_device *md; - int minor = MINOR(dev); - - if (minor >= MAX_DEVICES) - return NULL; - - down_read(_dev_locks + minor); - md = _devs[minor]; - if (!md) - up_read(_dev_locks + minor); - return md; -} - -struct mapped_device *dm_get_w(kdev_t dev) -{ - struct mapped_device *md; - int minor = MINOR(dev); - - if (minor >= MAX_DEVICES) - return NULL; - - down_write(_dev_locks + minor); - md = _devs[minor]; - if (!md) - up_write(_dev_locks + minor); - return md; -} - -static int namecmp(struct mapped_device *md, const char *name, int nametype) -{ - switch (nametype) { - case DM_LOOKUP_BY_NAME: - return strcmp(md->name, name); - break; - - case DM_LOOKUP_BY_UUID: - if (!md->uuid) - return -1; /* never equal */ - - return strcmp(md->uuid, name); - break; - - default: - DMWARN("Unknown comparison type in namecmp: %d", nametype); - BUG(); - } - - return -1; -} - -/* - * The interface (eg, ioctl) will probably access the devices - * through these slow 'by name' locks, this needs improving at - * some point if people start playing with *large* numbers of dm - * devices. - */ -struct mapped_device *dm_get_name_r(const char *name, int nametype) -{ - int i; - struct mapped_device *md; - - for (i = 0; i < MAX_DEVICES; i++) { - md = dm_get_r(mk_kdev(_major, i)); - if (md) { - if (!namecmp(md, name, nametype)) - return md; - - dm_put_r(md); - } - } - - return NULL; -} - -struct mapped_device *dm_get_name_w(const char *name, int nametype) -{ - int i; - struct mapped_device *md; - - /* - * To avoid getting write locks on all the devices we try - * and promote a read lock to a write lock, this can - * fail, in which case we just start again. - */ - - restart: - for (i = 0; i < MAX_DEVICES; i++) { - md = dm_get_r(mk_kdev(_major, i)); - if (!md) - continue; - - if (namecmp(md, name, nametype)) { - dm_put_r(md); - continue; - } - - /* found it */ - dm_put_r(md); - - md = dm_get_w(mk_kdev(_major, i)); - if (!md) - goto restart; - - if (namecmp(md, name, nametype)) { - dm_put_w(md); - goto restart; - } - - return md; - } - - return NULL; -} - -void dm_put_r(struct mapped_device *md) -{ - int minor = MINOR(md->dev); - - if (minor >= MAX_DEVICES) - return; - - up_read(_dev_locks + minor); -} - -void dm_put_w(struct mapped_device *md) -{ - int minor = MINOR(md->dev); - - if (minor >= MAX_DEVICES) - return; - - up_write(_dev_locks + minor); -} - -/* - * Setup and tear down the driver - */ -static __init void init_locks(void) -{ - int i; - - for (i = 0; i < MAX_DEVICES; i++) - init_rwsem(_dev_locks + i); -} - static __init int local_init(void) { int r; - init_locks(); - /* allocate a slab for the io-hooks */ if (!_io_hook_cache && !(_io_hook_cache = kmem_cache_create("dm io hooks", @@ -267,6 +105,7 @@ static struct { } _inits[] = { #define xx(n) {n ## _init, n ## _exit}, xx(local) + xx(dm_hash) xx(dm_target) xx(dm_linear) xx(dm_stripe) @@ -693,52 +532,48 @@ static int dm_user_bmap(struct inode *inode, struct lv_bmap *lvb) } /* - * See if the device with a specific minor # is free. The write - * lock is held when it returns successfully. + * See if the device with a specific minor # is free. Inserts + * the device into the hashes. */ static inline int specific_dev(int minor, struct mapped_device *md) { if (minor >= MAX_DEVICES) { DMWARN("request for a mapped_device beyond MAX_DEVICES (%d)", MAX_DEVICES); - return -1; + return -EINVAL; } - down_write(_dev_locks + minor); - if (_devs[minor]) { + md->dev = mk_kdev(_major, minor); + if (dm_hash_insert(md)) /* in use */ - up_write(_dev_locks + minor); - return -1; - } + return -EBUSY; return minor; } /* - * Find the first free device. Again the write lock is held on - * success. + * Find the first free device. */ static int any_old_dev(struct mapped_device *md) { int i; for (i = 0; i < MAX_DEVICES; i++) - if (specific_dev(i, md) != -1) + if (specific_dev(i, md) >= 0) return i; - return -1; + return -EBUSY; } /* - * Allocate and initialise a blank device. - * Caller must ensure uuid is null-terminated. + * Allocate and initialise a blank device, then insert it into + * the hash tables. Caller must ensure uuid is null-terminated. * Device is returned with a write lock held. */ static struct mapped_device *alloc_dev(const char *name, const char *uuid, int minor) { struct mapped_device *md = kmalloc(sizeof(*md), GFP_KERNEL); - int len; if (!md) { DMWARN("unable to allocate device, out of memory."); @@ -746,37 +581,64 @@ static struct mapped_device *alloc_dev(const char *name, const char *uuid, } memset(md, 0, sizeof(*md)); + init_rwsem(&md->lock); + down_write(&md->lock); /* - * This grabs the write lock if it succeeds. + * Copy in the name. */ - minor = (minor < 0) ? any_old_dev(md) : specific_dev(minor, md); - if (minor < 0) { - kfree(md); - return NULL; - } - - md->dev = mk_kdev(_major, minor); - md->suspended = 0; - - strncpy(md->name, name, sizeof(md->name) - 1); - md->name[sizeof(md->name) - 1] = '\0'; + md->name = dm_strdup(name); + if (!md->name) + goto bad; /* * Copy in the uuid. */ if (uuid && *uuid) { - len = strlen(uuid) + 1; - if (!(md->uuid = kmalloc(len, GFP_KERNEL))) { + md->uuid = dm_strdup(uuid); + if (!md->uuid) { DMWARN("unable to allocate uuid - out of memory."); - kfree(md); - return NULL; + goto bad; } - strcpy(md->uuid, uuid); } + /* + * This will have inserted the device into the hashes iff + * it succeeded. + */ + minor = (minor < 0) ? any_old_dev(md) : specific_dev(minor, md); + if (minor < 0) + goto bad; + + md->suspended = 0; + md->invalid = 0; + md->use_count = 0; + md->deferred = NULL; + + md->pending = (atomic_t) ATOMIC_INIT(0); init_waitqueue_head(&md->wait); return md; + + bad: + if (md->name) + kfree(md->name); + + if (md->uuid) + kfree(md->uuid); + + kfree(md); + return NULL; +} + +static void free_dev(struct mapped_device *md) +{ + dm_hash_remove(md); + kfree(md->name); + + if (md->uuid) + kfree(md->uuid); + + kfree(md); } static int __register_device(struct mapped_device *md) @@ -855,34 +717,9 @@ static void __unbind(struct mapped_device *md) static int check_name(const char *name) { - struct mapped_device *md; - - if (strchr(name, '/') || strlen(name) > DM_NAME_LEN) { + if (strchr(name, '/')) { DMWARN("invalid device name"); - return -1; - } - - md = dm_get_name_r(name, DM_LOOKUP_BY_NAME); - if (md) { - dm_put_r(md); - DMWARN("device name already in use"); - return -1; - } - - return 0; -} - -static int check_uuid(const char *uuid) -{ - struct mapped_device *md; - - if (uuid) { - md = dm_get_name_r(uuid, DM_LOOKUP_BY_UUID); - if (md) { - dm_put_r(md); - DMWARN("device uuid already in use"); - return -1; - } + return -EINVAL; } return 0; @@ -897,86 +734,60 @@ int dm_create(const char *name, const char *uuid, int minor, int ro, int r; struct mapped_device *md; - spin_lock(&_create_lock); - if (check_name(name) || check_uuid(uuid)) { - spin_unlock(&_create_lock); - return -EINVAL; - } + r = check_name(name); + if (r) + return r; md = alloc_dev(name, uuid, minor); - if (!md) { - spin_unlock(&_create_lock); + if (!md) return -ENXIO; - } - minor = MINOR(md->dev); - _devs[minor] = md; r = __register_device(md); if (r) - goto err; + goto bad; r = __bind(md, table); if (r) - goto err; + goto bad; dm_set_ro(md, ro); - - spin_unlock(&_create_lock); dm_put_w(md); return 0; - err: - _devs[minor] = NULL; - if (md->uuid) - kfree(md->uuid); - + bad: dm_put_w(md); - kfree(md); - spin_unlock(&_create_lock); + free_dev(md); return r; } /* * Renames the device. No lock held. */ -int dm_set_name(const char *name, int nametype, const char *newname) +int dm_set_name(const char *name, const char *new_name) { int r; struct mapped_device *md; - spin_lock(&_create_lock); - if (check_name(newname) < 0) { - spin_unlock(&_create_lock); - return -EINVAL; - } - - md = dm_get_name_w(name, nametype); - if (!md) { - spin_unlock(&_create_lock); - return -ENXIO; - } - - r = __unregister_device(md); + r = dm_hash_rename(name, new_name); if (r) - goto out; - - strcpy(md->name, newname); - r = __register_device(md); + return r; - out: + md = dm_get_name_w(new_name); + r = __unregister_device(md); + if (!r) + r = __register_device(md); dm_put_w(md); - spin_unlock(&_create_lock); return r; } /* - * Destructor for the device. You cannot destroy an open - * device. Write lock must be held before calling. - * Caller must dm_put_w(md) then kfree(md) if call was successful. + * Destructor for the device. You cannot destroy an open device. + * Write lock must be held before calling. md will have been + * freed if call was successful. */ int dm_destroy(struct mapped_device *md) { - int minor, r; + int r; if (md->use_count) return -EPERM; @@ -985,13 +796,14 @@ int dm_destroy(struct mapped_device *md) if (r) return r; - minor = MINOR(md->dev); - _devs[minor] = NULL; - __unbind(md); - - if (md->uuid) - kfree(md->uuid); + /* + * Signal that this md is now invalid so that nothing further + * can acquire its lock. + */ + md->invalid = 1; + __unbind(md); + free_dev(md); return 0; } @@ -1011,12 +823,10 @@ void dm_destroy_all(void) continue; r = dm_destroy(md); - dm_put_w(md); - - if (!r) { - kfree(md); + if (r) + dm_put_w(md); + else some_destroyed = 1; - } } } while (some_destroyed); } @@ -1069,70 +879,82 @@ int dm_swap_table(struct mapped_device *md, struct dm_table *table) /* * We need to be able to change a mapping table under a mounted - * filesystem. for example we might want to move some data in + * filesystem. For example we might want to move some data in * the background. Before the table can be swapped with * dm_bind_table, dm_suspend must be called to flush any in * flight buffer_heads and ensure that any further io gets * deferred. Write lock must be held. */ -int dm_suspend(struct mapped_device *md) +int dm_suspend(kdev_t dev) { - int minor = MINOR(md->dev); + struct mapped_device *md; DECLARE_WAITQUEUE(wait, current); - if (md->suspended) + /* + * First we set the suspend flag so no more ios will be + * mapped. + */ + md = dm_get_w(dev); + if (!md) + return -ENXIO; + + if (md->suspended) { + dm_put_w(md); return -EINVAL; + } md->suspended = 1; dm_put_w(md); - /* wait for all the pending io to flush */ + /* + * Then we wait for wait for the already mapped ios to + * complete. + */ + md = dm_get_r(dev); + if (!md) + return -ENXIO; + if (!md->suspended) + return -EINVAL; + add_wait_queue(&md->wait, &wait); current->state = TASK_UNINTERRUPTIBLE; do { - md = dm_get_w(md->dev); - if (!md) { - /* Caller expects to free this lock. Yuck. */ - down_write(_dev_locks + minor); - return -ENXIO; - } - if (!atomic_read(&md->pending)) break; - dm_put_w(md); schedule(); } while (1); current->state = TASK_RUNNING; remove_wait_queue(&md->wait, &wait); + dm_put_r(md); return 0; } -int dm_resume(struct mapped_device *md) +int dm_resume(kdev_t dev) { - int minor = MINOR(md->dev); + struct mapped_device *md; struct deferred_io *def; - if (!md->suspended || !md->map->num_targets) + md = dm_get_w(dev); + if (!md) + return -ENXIO; + + if (!md->suspended || !md->map->num_targets) { + dm_put_w(md); return -EINVAL; + } md->suspended = 0; def = md->deferred; md->deferred = NULL; - dm_put_w(md); + flush_deferred_io(def); run_task_queue(&tq_disk); - if (!dm_get_w(md->dev)) { - /* FIXME: yuck */ - down_write(_dev_locks + minor); - return -ENXIO; - } - return 0; } diff --git a/kernel/common/dm.h b/kernel/common/dm.h index c4fad1b..8ef2416 100644 --- a/kernel/common/dm.h +++ b/kernel/common/dm.h @@ -99,11 +99,14 @@ struct dm_table { * The actual device struct */ struct mapped_device { + struct rw_semaphore lock; + kdev_t dev; - char name[DM_NAME_LEN]; + char *name; char *uuid; int use_count; + int invalid; int suspended; int read_only; @@ -131,30 +134,46 @@ void dm_target_exit(void); */ int split_args(int max, int *argc, char **argv, char *input); -/* dm.c */ -struct mapped_device *dm_get_r(kdev_t dev); -struct mapped_device *dm_get_w(kdev_t dev); +/* + * dm-hash manages the lookup of devices by dev/name/uuid. + */ +int dm_hash_init(void); +void dm_hash_exit(void); + +int dm_hash_insert(struct mapped_device *md); +void dm_hash_remove(struct mapped_device *md); +int dm_hash_rename(const char *old, const char *new); /* - * There are two ways to lookup a device. + * There are three ways to lookup a device: by kdev_t, by name + * and by uuid. A code path (eg an ioctl) should only ever get + * one device at any time. */ -enum { - DM_LOOKUP_BY_NAME, - DM_LOOKUP_BY_UUID -}; +struct mapped_device *dm_get_r(kdev_t dev); +struct mapped_device *dm_get_w(kdev_t dev); -struct mapped_device *dm_get_name_r(const char *name, int nametype); -struct mapped_device *dm_get_name_w(const char *name, int nametype); +struct mapped_device *dm_get_name_r(const char *name); +struct mapped_device *dm_get_name_w(const char *name); -void dm_put_r(struct mapped_device *md); -void dm_put_w(struct mapped_device *md); +struct mapped_device *dm_get_uuid_r(const char *uuid); +struct mapped_device *dm_get_uuid_w(const char *uuid); + +static inline void dm_put_r(struct mapped_device *md) +{ + up_read(&md->lock); +} + +static inline void dm_put_w(struct mapped_device *md) +{ + up_write(&md->lock); +} /* * Call with no lock. */ int dm_create(const char *name, const char *uuid, int minor, int ro, struct dm_table *table); -int dm_set_name(const char *name, int nametype, const char *newname); +int dm_set_name(const char *name, const char *newname); void dm_destroy_all(void); /* @@ -172,8 +191,8 @@ int dm_swap_table(struct mapped_device *md, struct dm_table *t); /* * A device can still be used while suspended, but I/O is deferred. */ -int dm_suspend(struct mapped_device *md); -int dm_resume(struct mapped_device *md); +int dm_suspend(kdev_t dev); +int dm_resume(kdev_t dev); /* dm-table.c */ int dm_table_create(struct dm_table **result, int mode); @@ -214,6 +233,14 @@ static inline int array_too_big(unsigned long fixed, unsigned long obj, return (num > (ULONG_MAX - fixed) / obj); } +static inline char *dm_strdup(const char *str) +{ + char *r = kmalloc(strlen(str) + 1, GFP_KERNEL); + if (r) + strcpy(r, str); + return r; +} + /* * Targets */ diff --git a/kernel/ioctl/dm-ioctl.c b/kernel/ioctl/dm-ioctl.c index 4109bf4..c339b6a 100644 --- a/kernel/ioctl/dm-ioctl.c +++ b/kernel/ioctl/dm-ioctl.c @@ -210,14 +210,18 @@ static void __info(struct mapped_device *md, struct dm_ioctl *param) /* * Always use UUID for lookups if it's present, otherwise use name. */ -static inline char *lookup_name(struct dm_ioctl *param) +static inline struct mapped_device *find_device_r(struct dm_ioctl *param) { - return (*param->uuid) ? param->uuid : param->name; + return (*param->uuid ? + dm_get_uuid_r(param->uuid) : + dm_get_name_r(param->name)); } -static inline int lookup_type(struct dm_ioctl *param) +static inline struct mapped_device *find_device_w(struct dm_ioctl *param) { - return (*param->uuid) ? DM_LOOKUP_BY_UUID : DM_LOOKUP_BY_NAME; + return (*param->uuid ? + dm_get_uuid_w(param->uuid) : + dm_get_name_w(param->name)); } #define ALIGNMENT sizeof(int) @@ -238,7 +242,7 @@ static int info(struct dm_ioctl *param, struct dm_ioctl *user) param->flags = 0; - md = dm_get_name_r(lookup_name(param), lookup_type(param)); + md = find_device_r(param); if (!md) /* * Device not found - returns cleared exists flag. @@ -365,7 +369,7 @@ static int get_status(struct dm_ioctl *param, struct dm_ioctl *user) int ret; char *outbuf = NULL; - md = dm_get_name_r(lookup_name(param), lookup_type(param)); + md = find_device_r(param); if (!md) /* * Device not found - returns cleared exists flag. @@ -408,7 +412,7 @@ static int wait_device_event(struct dm_ioctl *param, struct dm_ioctl *user) struct mapped_device *md; DECLARE_WAITQUEUE(wq, current); - md = dm_get_name_r(lookup_name(param), lookup_type(param)); + md = find_device_r(param); if (!md) /* * Device not found - returns cleared exists flag. @@ -445,7 +449,7 @@ static int dep(struct dm_ioctl *param, struct dm_ioctl *user) size_t len = 0; struct dm_target_deps *deps = NULL; - md = dm_get_name_r(lookup_name(param), lookup_type(param)); + md = find_device_r(param); if (!md) goto out; @@ -500,31 +504,36 @@ static int remove(struct dm_ioctl *param, struct dm_ioctl *user) int r; struct mapped_device *md; - md = dm_get_name_w(lookup_name(param), lookup_type(param)); + md = find_device_w(param); if (!md) return -ENXIO; + /* + * This unlocks and deallocates md. + */ r = dm_destroy(md); - dm_put_w(md); - if (!r) - kfree(md); + if (r) { + dm_put_w(md); + return r; + } - return r; + return 0; } static int suspend(struct dm_ioctl *param, struct dm_ioctl *user) { - int r; struct mapped_device *md; + kdev_t dev; - md = dm_get_name_w(lookup_name(param), lookup_type(param)); + md = find_device_r(param); if (!md) return -ENXIO; - r = (param->flags & DM_SUSPEND_FLAG) ? dm_suspend(md) : dm_resume(md); - dm_put_w(md); + dev = md->dev; + dm_put_r(md); - return r; + return (param->flags & DM_SUSPEND_FLAG) ? + dm_suspend(dev) : dm_resume(dev); } static int reload(struct dm_ioctl *param, struct dm_ioctl *user) @@ -543,7 +552,7 @@ static int reload(struct dm_ioctl *param, struct dm_ioctl *user) return r; } - md = dm_get_name_w(lookup_name(param), lookup_type(param)); + md = find_device_w(param); if (!md) { dm_table_destroy(t); return -ENXIO; @@ -567,14 +576,18 @@ static int rename(struct dm_ioctl *param, struct dm_ioctl *user) { char *newname = (char *) param + param->data_start; + if (!param->name) { + DMWARN("Invalid old logical volume name supplied."); + return -EINVAL; + } + if (valid_str(newname, (void *) param, - (void *) param + param->data_size) || - dm_set_name(lookup_name(param), lookup_type(param), newname)) { + (void *) param + param->data_size)) { DMWARN("Invalid new logical volume name supplied."); return -EINVAL; } - return 0; + return dm_set_name(param->name, newname); } @@ -799,7 +812,9 @@ static int __init dm_devfs_init(void) { return 0; } -/* Create misc character device and link to DM_DIR/control */ +/* + * Create misc character device and link to DM_DIR/control. + */ int __init dm_interface_init(void) { int r; diff --git a/kernel/ioctl/dm-ioctl.h b/kernel/ioctl/dm-ioctl.h index d1a07a7..7830aea 100644 --- a/kernel/ioctl/dm-ioctl.h +++ b/kernel/ioctl/dm-ioctl.h @@ -127,8 +127,8 @@ enum { #define DM_VERSION_MAJOR 1 #define DM_VERSION_MINOR 0 -#define DM_VERSION_PATCHLEVEL 4 -#define DM_VERSION_EXTRA "-ioctl-cvs (2002-09-24)" +#define DM_VERSION_PATCHLEVEL 5 +#define DM_VERSION_EXTRA "-ioctl-cvs (2002-10-14)" /* Status bits */ #define DM_READONLY_FLAG 0x00000001 -- 2.43.5