X-Git-Url: http://git.onelab.eu/?a=blobdiff_plain;f=drivers%2Fmd%2Fdm-ioctl.c;h=8edd6435414df900777cbc78d4bf4988d0be37e1;hb=43bc926fffd92024b46cafaf7350d669ba9ca884;hp=26e07286e21f83da1715df5f99feea067c2feb43;hpb=5273a3df6485dc2ad6aa7ddd441b9a21970f003b;p=linux-2.6.git diff --git a/drivers/md/dm-ioctl.c b/drivers/md/dm-ioctl.c index 26e07286e..8edd64354 100644 --- a/drivers/md/dm-ioctl.c +++ b/drivers/md/dm-ioctl.c @@ -1,5 +1,6 @@ /* * Copyright (C) 2001, 2002 Sistina Software (UK) Limited. + * Copyright (C) 2004 - 2005 Red Hat, Inc. All rights reserved. * * This file is released under the GPL. */ @@ -14,10 +15,11 @@ #include #include #include +#include #include -#define DM_DRIVER_EMAIL "dm@uk.sistina.com" +#define DM_DRIVER_EMAIL "dm-devel@redhat.com" /*----------------------------------------------------------------- * The ioctl interface needs to be able to look up devices by @@ -46,7 +48,7 @@ struct vers_iter { static struct list_head _name_buckets[NUM_BUCKETS]; static struct list_head _uuid_buckets[NUM_BUCKETS]; -void dm_hash_remove_all(void); +static void dm_hash_remove_all(void); /* * Guards access to both hash tables. @@ -61,7 +63,7 @@ static void init_buckets(struct list_head *buckets) INIT_LIST_HEAD(buckets + i); } -int dm_hash_init(void) +static int dm_hash_init(void) { init_buckets(_name_buckets); init_buckets(_uuid_buckets); @@ -69,7 +71,7 @@ int dm_hash_init(void) return 0; } -void dm_hash_exit(void) +static void dm_hash_exit(void) { dm_hash_remove_all(); devfs_remove(DM_DIR); @@ -121,14 +123,6 @@ static struct hash_cell *__get_uuid_cell(const char *str) /*----------------------------------------------------------------- * Inserting, removing and renaming a device. *---------------------------------------------------------------*/ -static inline char *kstrdup(const char *str) -{ - char *r = kmalloc(strlen(str) + 1, GFP_KERNEL); - if (r) - strcpy(r, str); - return r; -} - static struct hash_cell *alloc_cell(const char *name, const char *uuid, struct mapped_device *md) { @@ -138,7 +132,7 @@ static struct hash_cell *alloc_cell(const char *name, const char *uuid, if (!hc) return NULL; - hc->name = kstrdup(name); + hc->name = kstrdup(name, GFP_KERNEL); if (!hc->name) { kfree(hc); return NULL; @@ -148,7 +142,7 @@ static struct hash_cell *alloc_cell(const char *name, const char *uuid, hc->uuid = NULL; else { - hc->uuid = kstrdup(uuid); + hc->uuid = kstrdup(uuid, GFP_KERNEL); if (!hc->uuid) { kfree(hc->name); kfree(hc); @@ -195,7 +189,7 @@ static int unregister_with_devfs(struct hash_cell *hc) * The kdev_t and uuid of a device can never change once it is * initially inserted. */ -int dm_hash_insert(const char *name, const char *uuid, struct mapped_device *md) +static int dm_hash_insert(const char *name, const char *uuid, struct mapped_device *md) { struct hash_cell *cell; @@ -224,6 +218,7 @@ int dm_hash_insert(const char *name, const char *uuid, struct mapped_device *md) } register_with_devfs(cell); dm_get(md); + dm_set_mdptr(md, cell); up_write(&_hash_lock); return 0; @@ -234,19 +229,29 @@ int dm_hash_insert(const char *name, const char *uuid, struct mapped_device *md) return -EBUSY; } -void __hash_remove(struct hash_cell *hc) +static void __hash_remove(struct hash_cell *hc) { + struct dm_table *table; + /* remove from the dev hash */ list_del(&hc->uuid_list); list_del(&hc->name_list); unregister_with_devfs(hc); - dm_put(hc->md); + dm_set_mdptr(hc->md, NULL); + + table = dm_get_table(hc->md); + if (table) { + dm_table_event(table); + dm_table_put(table); + } + if (hc->new_map) dm_table_put(hc->new_map); + dm_put(hc->md); free_cell(hc); } -void dm_hash_remove_all(void) +static void dm_hash_remove_all(void) { int i; struct hash_cell *hc; @@ -262,15 +267,16 @@ void dm_hash_remove_all(void) up_write(&_hash_lock); } -int dm_hash_rename(const char *old, const char *new) +static int dm_hash_rename(const char *old, const char *new) { char *new_name, *old_name; struct hash_cell *hc; + struct dm_table *table; /* * duplicate new. */ - new_name = kstrdup(new); + new_name = kstrdup(new, GFP_KERNEL); if (!new_name) return -ENOMEM; @@ -313,6 +319,15 @@ int dm_hash_rename(const char *old, const char *new) /* rename the device node in devfs */ register_with_devfs(hc); + /* + * Wake up any dm event waiters. + */ + table = dm_get_table(hc->md); + if (table) { + dm_table_event(table); + dm_table_put(table); + } + up_write(&_hash_lock); kfree(old_name); return 0; @@ -377,7 +392,7 @@ static int list_devices(struct dm_ioctl *param, size_t param_size) for (i = 0; i < NUM_BUCKETS; i++) { list_for_each_entry (hc, _name_buckets + i, name_list) { needed += sizeof(struct dm_name_list); - needed += strlen(hc->name); + needed += strlen(hc->name) + 1; needed += ALIGN_MASK; } } @@ -417,12 +432,12 @@ static int list_devices(struct dm_ioctl *param, size_t param_size) return 0; } -static void list_version_get_needed(struct target_type *tt, void *param) +static void list_version_get_needed(struct target_type *tt, void *needed_param) { - int *needed = param; + size_t *needed = needed_param; + *needed += sizeof(struct dm_target_versions); *needed += strlen(tt->name); - *needed += sizeof(tt->version); *needed += ALIGN_MASK; } @@ -517,19 +532,22 @@ static int __dev_status(struct mapped_device *md, struct dm_ioctl *param) if (dm_suspended(md)) param->flags |= DM_SUSPEND_FLAG; - bdev = bdget_disk(disk, 0); - if (!bdev) - return -ENXIO; - param->dev = huge_encode_dev(MKDEV(disk->major, disk->first_minor)); - /* - * Yes, this will be out of date by the time it gets back - * to userland, but it is still very useful ofr - * debugging. - */ - param->open_count = bdev->bd_openers; - bdput(bdev); + if (!(param->flags & DM_SKIP_BDGET_FLAG)) { + bdev = bdget_disk(disk, 0); + if (!bdev) + return -ENXIO; + + /* + * Yes, this will be out of date by the time it gets back + * to userland, but it is still very useful for + * debugging. + */ + param->open_count = bdev->bd_openers; + bdput(bdev); + } else + param->open_count = -1; if (disk->policy) param->flags |= DM_READONLY_FLAG; @@ -579,15 +597,29 @@ static int dev_create(struct dm_ioctl *param, size_t param_size) } /* - * Always use UUID for lookups if it's present, otherwise use name. + * Always use UUID for lookups if it's present, otherwise use name or dev. */ -static inline struct hash_cell *__find_device_hash_cell(struct dm_ioctl *param) +static struct hash_cell *__find_device_hash_cell(struct dm_ioctl *param) { - return *param->uuid ? - __get_uuid_cell(param->uuid) : __get_name_cell(param->name); + struct mapped_device *md; + void *mdptr = NULL; + + if (*param->uuid) + return __get_uuid_cell(param->uuid); + + if (*param->name) + return __get_name_cell(param->name); + + md = dm_get_md(huge_decode_dev(param->dev)); + if (md) { + mdptr = dm_get_mdptr(md); + dm_put(md); + } + + return mdptr; } -static inline struct mapped_device *find_device(struct dm_ioctl *param) +static struct mapped_device *find_device(struct dm_ioctl *param) { struct hash_cell *hc; struct mapped_device *md = NULL; @@ -596,6 +628,7 @@ static inline struct mapped_device *find_device(struct dm_ioctl *param) hc = __find_device_hash_cell(param); if (hc) { md = hc->md; + dm_get(md); /* * Sneakily write in both the name and the uuid @@ -611,8 +644,6 @@ static inline struct mapped_device *find_device(struct dm_ioctl *param) param->flags |= DM_INACTIVE_PRESENT_FLAG; else param->flags &= ~DM_INACTIVE_PRESENT_FLAG; - - dm_get(md); } up_read(&_hash_lock); @@ -670,17 +701,69 @@ static int dev_rename(struct dm_ioctl *param, size_t param_size) return dm_hash_rename(param->name, new_name); } +static int dev_set_geometry(struct dm_ioctl *param, size_t param_size) +{ + int r = -EINVAL, x; + struct mapped_device *md; + struct hd_geometry geometry; + unsigned long indata[4]; + char *geostr = (char *) param + param->data_start; + + md = find_device(param); + if (!md) + return -ENXIO; + + if (geostr < (char *) (param + 1) || + invalid_str(geostr, (void *) param + param_size)) { + DMWARN("Invalid geometry supplied."); + goto out; + } + + x = sscanf(geostr, "%lu %lu %lu %lu", indata, + indata + 1, indata + 2, indata + 3); + + if (x != 4) { + DMWARN("Unable to interpret geometry settings."); + goto out; + } + + if (indata[0] > 65535 || indata[1] > 255 || + indata[2] > 255 || indata[3] > ULONG_MAX) { + DMWARN("Geometry exceeds range limits."); + goto out; + } + + geometry.cylinders = indata[0]; + geometry.heads = indata[1]; + geometry.sectors = indata[2]; + geometry.start = indata[3]; + + r = dm_set_geometry(md, &geometry); + if (!r) + r = __dev_status(md, param); + + param->data_size = 0; + +out: + dm_put(md); + return r; +} + static int do_suspend(struct dm_ioctl *param) { int r = 0; + int do_lockfs = 1; struct mapped_device *md; md = find_device(param); if (!md) return -ENXIO; + if (param->flags & DM_SKIP_LOCKFS_FLAG) + do_lockfs = 0; + if (!dm_suspended(md)) - r = dm_suspend(md); + r = dm_suspend(md, do_lockfs); if (!r) r = __dev_status(md, param); @@ -692,6 +775,7 @@ static int do_suspend(struct dm_ioctl *param) static int do_resume(struct dm_ioctl *param) { int r = 0; + int do_lockfs = 1; struct hash_cell *hc; struct mapped_device *md; struct dm_table *new_map; @@ -717,8 +801,10 @@ static int do_resume(struct dm_ioctl *param) /* Do we need to load a new map ? */ if (new_map) { /* Suspend if it isn't already suspended */ + if (param->flags & DM_SKIP_LOCKFS_FLAG) + do_lockfs = 0; if (!dm_suspended(md)) - dm_suspend(md); + dm_suspend(md, do_lockfs); r = dm_swap_table(md, new_map); if (r) { @@ -850,7 +936,6 @@ static int dev_wait(struct dm_ioctl *param, size_t param_size) int r; struct mapped_device *md; struct dm_table *table; - DECLARE_WAITQUEUE(wq, current); md = find_device(param); if (!md) @@ -859,12 +944,10 @@ static int dev_wait(struct dm_ioctl *param, size_t param_size) /* * Wait for a notification event */ - set_current_state(TASK_INTERRUPTIBLE); - if (!dm_add_wait_queue(md, &wq, param->event_nr)) { - schedule(); - dm_remove_wait_queue(md, &wq); + if (dm_wait_event(md, param->event_nr)) { + r = -ERESTARTSYS; + goto out; } - set_current_state(TASK_RUNNING); /* * The userland program is going to want to know what @@ -951,32 +1034,43 @@ static int table_load(struct dm_ioctl *param, size_t param_size) int r; struct hash_cell *hc; struct dm_table *t; + struct mapped_device *md; - r = dm_table_create(&t, get_mode(param), param->target_count); + md = find_device(param); + if (!md) + return -ENXIO; + + r = dm_table_create(&t, get_mode(param), param->target_count, md); if (r) - return r; + goto out; r = populate_table(t, param, param_size); if (r) { dm_table_put(t); - return r; + goto out; } down_write(&_hash_lock); - hc = __find_device_hash_cell(param); - if (!hc) { - DMWARN("device doesn't appear to be in the dev hash table."); + hc = dm_get_mdptr(md); + if (!hc || hc->md != md) { + DMWARN("device has been removed from the dev hash table."); + dm_table_put(t); up_write(&_hash_lock); - return -ENXIO; + r = -ENXIO; + goto out; } if (hc->new_map) dm_table_put(hc->new_map); hc->new_map = t; + up_write(&_hash_lock); + param->flags |= DM_INACTIVE_PRESENT_FLAG; + r = __dev_status(md, param); + +out: + dm_put(md); - r = __dev_status(hc->md, param); - up_write(&_hash_lock); return r; } @@ -1100,6 +1194,67 @@ static int table_status(struct dm_ioctl *param, size_t param_size) return r; } +/* + * Pass a message to the target that's at the supplied device offset. + */ +static int target_message(struct dm_ioctl *param, size_t param_size) +{ + int r, argc; + char **argv; + struct mapped_device *md; + struct dm_table *table; + struct dm_target *ti; + struct dm_target_msg *tmsg = (void *) param + param->data_start; + + md = find_device(param); + if (!md) + return -ENXIO; + + r = __dev_status(md, param); + if (r) + goto out; + + if (tmsg < (struct dm_target_msg *) (param + 1) || + invalid_str(tmsg->message, (void *) param + param_size)) { + DMWARN("Invalid target message parameters."); + r = -EINVAL; + goto out; + } + + r = dm_split_args(&argc, &argv, tmsg->message); + if (r) { + DMWARN("Failed to split target message parameters"); + goto out; + } + + table = dm_get_table(md); + if (!table) + goto out_argv; + + if (tmsg->sector >= dm_table_get_size(table)) { + DMWARN("Target message sector outside device."); + r = -EINVAL; + goto out_table; + } + + ti = dm_table_find_target(table, tmsg->sector); + if (ti->type->message) + r = ti->type->message(ti, argc, argv); + else { + DMWARN("Target type does not support messages"); + r = -EINVAL; + } + + out_table: + dm_table_put(table); + out_argv: + kfree(argv); + out: + param->data_size = 0; + dm_put(md); + return r; +} + /*----------------------------------------------------------------- * Implementation of open/close/ioctl on the special char * device. @@ -1126,7 +1281,10 @@ static ioctl_fn lookup_ioctl(unsigned int cmd) {DM_TABLE_DEPS_CMD, table_deps}, {DM_TABLE_STATUS_CMD, table_status}, - {DM_LIST_VERSIONS_CMD, list_versions} + {DM_LIST_VERSIONS_CMD, list_versions}, + + {DM_TARGET_MSG_CMD, target_message}, + {DM_DEV_SET_GEOMETRY_CMD, dev_set_geometry} }; return (cmd >= ARRAY_SIZE(_ioctls)) ? NULL : _ioctls[cmd].fn; @@ -1136,7 +1294,7 @@ static ioctl_fn lookup_ioctl(unsigned int cmd) * As well as checking the version compatibility this always * copies the kernel interface version out. */ -static int check_version(unsigned int cmd, struct dm_ioctl *user) +static int check_version(unsigned int cmd, struct dm_ioctl __user *user) { uint32_t version[3]; int r = 0; @@ -1171,7 +1329,7 @@ static void free_params(struct dm_ioctl *param) vfree(param); } -static int copy_params(struct dm_ioctl *user, struct dm_ioctl **param) +static int copy_params(struct dm_ioctl __user *user, struct dm_ioctl **param) { struct dm_ioctl tmp, *dmi; @@ -1205,14 +1363,14 @@ static int validate_params(uint cmd, struct dm_ioctl *param) cmd == DM_LIST_VERSIONS_CMD) return 0; - /* Unless creating, either name or uuid but not both */ - if (cmd != DM_DEV_CREATE_CMD) { - if ((!*param->uuid && !*param->name) || - (*param->uuid && *param->name)) { - DMWARN("one of name or uuid must be supplied, cmd(%u)", - cmd); + if ((cmd == DM_DEV_CREATE_CMD)) { + if (!*param->name) { + DMWARN("name not supplied when creating device"); return -EINVAL; } + } else if ((*param->uuid && *param->name)) { + DMWARN("only supply one of name or uuid, cmd(%u)", cmd); + return -EINVAL; } /* Ensure strings are terminated */ @@ -1228,7 +1386,7 @@ static int ctl_ioctl(struct inode *inode, struct file *file, int r = 0; unsigned int cmd; struct dm_ioctl *param; - struct dm_ioctl *user = (struct dm_ioctl *) u; + struct dm_ioctl __user *user = (struct dm_ioctl __user *) u; ioctl_fn fn = NULL; size_t param_size; @@ -1271,16 +1429,11 @@ static int ctl_ioctl(struct inode *inode, struct file *file, * Copy the parameters into kernel space. */ r = copy_params(user, ¶m); - if (r) { - current->flags &= ~PF_MEMALLOC; - return r; - } - /* - * FIXME: eventually we will remove the PF_MEMALLOC flag - * here. However the tools still do nasty things like - * 'load' while a device is suspended. - */ + current->flags &= ~PF_MEMALLOC; + + if (r) + return r; r = validate_params(cmd, param); if (r) @@ -1298,7 +1451,6 @@ static int ctl_ioctl(struct inode *inode, struct file *file, out: free_params(param); - current->flags &= ~PF_MEMALLOC; return r; }