diff options
Diffstat (limited to 'drivers/md/dm-ioctl.c')
| -rw-r--r-- | drivers/md/dm-ioctl.c | 632 | 
1 files changed, 456 insertions, 176 deletions
diff --git a/drivers/md/dm-ioctl.c b/drivers/md/dm-ioctl.c index 4b54618b415..51521429fb5 100644 --- a/drivers/md/dm-ioctl.c +++ b/drivers/md/dm-ioctl.c @@ -36,6 +36,14 @@ struct hash_cell {  	struct dm_table *new_map;  }; +/* + * A dummy definition to make RCU happy. + * struct dm_table should never be dereferenced in this file. + */ +struct dm_table { +	int undefined__; +}; +  struct vers_iter {      size_t param_size;      struct dm_target_versions *vers, *old_vers; @@ -49,7 +57,7 @@ struct vers_iter {  static struct list_head _name_buckets[NUM_BUCKETS];  static struct list_head _uuid_buckets[NUM_BUCKETS]; -static void dm_hash_remove_all(int keep_open_devices); +static void dm_hash_remove_all(bool keep_open_devices, bool mark_deferred, bool only_deferred);  /*   * Guards access to both hash tables. @@ -78,7 +86,7 @@ static int dm_hash_init(void)  static void dm_hash_exit(void)  { -	dm_hash_remove_all(0); +	dm_hash_remove_all(false, false, false);  }  /*----------------------------------------------------------------- @@ -128,6 +136,24 @@ static struct hash_cell *__get_uuid_cell(const char *str)  	return NULL;  } +static struct hash_cell *__get_dev_cell(uint64_t dev) +{ +	struct mapped_device *md; +	struct hash_cell *hc; + +	md = dm_get_md(huge_decode_dev(dev)); +	if (!md) +		return NULL; + +	hc = dm_get_mdptr(md); +	if (!hc) { +		dm_put(md); +		return NULL; +	} + +	return hc; +} +  /*-----------------------------------------------------------------   * Inserting, removing and renaming a device.   *---------------------------------------------------------------*/ @@ -224,9 +250,10 @@ static int dm_hash_insert(const char *name, const char *uuid, struct mapped_devi  	return -EBUSY;  } -static void __hash_remove(struct hash_cell *hc) +static struct dm_table *__hash_remove(struct hash_cell *hc)  {  	struct dm_table *table; +	int srcu_idx;  	/* remove from the dev hash */  	list_del(&hc->uuid_list); @@ -235,23 +262,26 @@ static void __hash_remove(struct hash_cell *hc)  	dm_set_mdptr(hc->md, NULL);  	mutex_unlock(&dm_hash_cells_mutex); -	table = dm_get_live_table(hc->md); -	if (table) { +	table = dm_get_live_table(hc->md, &srcu_idx); +	if (table)  		dm_table_event(table); -		dm_table_put(table); -	} +	dm_put_live_table(hc->md, srcu_idx); +	table = NULL;  	if (hc->new_map) -		dm_table_destroy(hc->new_map); +		table = hc->new_map;  	dm_put(hc->md);  	free_cell(hc); + +	return table;  } -static void dm_hash_remove_all(int keep_open_devices) +static void dm_hash_remove_all(bool keep_open_devices, bool mark_deferred, bool only_deferred)  {  	int i, dev_skipped;  	struct hash_cell *hc;  	struct mapped_device *md; +	struct dm_table *t;  retry:  	dev_skipped = 0; @@ -263,16 +293,21 @@ retry:  			md = hc->md;  			dm_get(md); -			if (keep_open_devices && dm_lock_for_deletion(md)) { +			if (keep_open_devices && +			    dm_lock_for_deletion(md, mark_deferred, only_deferred)) {  				dm_put(md);  				dev_skipped++;  				continue;  			} -			__hash_remove(hc); +			t = __hash_remove(hc);  			up_write(&_hash_lock); +			if (t) { +				dm_sync_table(md); +				dm_table_destroy(t); +			}  			dm_put(md);  			if (likely(keep_open_devices))  				dm_destroy(md); @@ -295,19 +330,56 @@ retry:  		DMWARN("remove_all left %d open device(s)", dev_skipped);  } +/* + * Set the uuid of a hash_cell that isn't already set. + */ +static void __set_cell_uuid(struct hash_cell *hc, char *new_uuid) +{ +	mutex_lock(&dm_hash_cells_mutex); +	hc->uuid = new_uuid; +	mutex_unlock(&dm_hash_cells_mutex); + +	list_add(&hc->uuid_list, _uuid_buckets + hash_str(new_uuid)); +} + +/* + * Changes the name of a hash_cell and returns the old name for + * the caller to free. + */ +static char *__change_cell_name(struct hash_cell *hc, char *new_name) +{ +	char *old_name; + +	/* +	 * Rename and move the name cell. +	 */ +	list_del(&hc->name_list); +	old_name = hc->name; + +	mutex_lock(&dm_hash_cells_mutex); +	hc->name = new_name; +	mutex_unlock(&dm_hash_cells_mutex); + +	list_add(&hc->name_list, _name_buckets + hash_str(new_name)); + +	return old_name; +} +  static struct mapped_device *dm_hash_rename(struct dm_ioctl *param,  					    const char *new)  { -	char *new_name, *old_name; +	char *new_data, *old_name = NULL;  	struct hash_cell *hc;  	struct dm_table *table;  	struct mapped_device *md; +	unsigned change_uuid = (param->flags & DM_UUID_FLAG) ? 1 : 0; +	int srcu_idx;  	/*  	 * duplicate new.  	 */ -	new_name = kstrdup(new, GFP_KERNEL); -	if (!new_name) +	new_data = kstrdup(new, GFP_KERNEL); +	if (!new_data)  		return ERR_PTR(-ENOMEM);  	down_write(&_hash_lock); @@ -315,13 +387,19 @@ static struct mapped_device *dm_hash_rename(struct dm_ioctl *param,  	/*  	 * Is new free ?  	 */ -	hc = __get_name_cell(new); +	if (change_uuid) +		hc = __get_uuid_cell(new); +	else +		hc = __get_name_cell(new); +  	if (hc) { -		DMWARN("asked to rename to an already-existing name %s -> %s", +		DMWARN("Unable to change %s on mapped device %s to one that " +		       "already exists: %s", +		       change_uuid ? "uuid" : "name",  		       param->name, new);  		dm_put(hc->md);  		up_write(&_hash_lock); -		kfree(new_name); +		kfree(new_data);  		return ERR_PTR(-EBUSY);  	} @@ -330,31 +408,38 @@ static struct mapped_device *dm_hash_rename(struct dm_ioctl *param,  	 */  	hc = __get_name_cell(param->name);  	if (!hc) { -		DMWARN("asked to rename a non-existent device %s -> %s", -		       param->name, new); +		DMWARN("Unable to rename non-existent device, %s to %s%s", +		       param->name, change_uuid ? "uuid " : "", new);  		up_write(&_hash_lock); -		kfree(new_name); +		kfree(new_data);  		return ERR_PTR(-ENXIO);  	}  	/* -	 * rename and move the name cell. +	 * Does this device already have a uuid?  	 */ -	list_del(&hc->name_list); -	old_name = hc->name; -	mutex_lock(&dm_hash_cells_mutex); -	hc->name = new_name; -	mutex_unlock(&dm_hash_cells_mutex); -	list_add(&hc->name_list, _name_buckets + hash_str(new_name)); +	if (change_uuid && hc->uuid) { +		DMWARN("Unable to change uuid of mapped device %s to %s " +		       "because uuid is already set to %s", +		       param->name, new, hc->uuid); +		dm_put(hc->md); +		up_write(&_hash_lock); +		kfree(new_data); +		return ERR_PTR(-EINVAL); +	} + +	if (change_uuid) +		__set_cell_uuid(hc, new_data); +	else +		old_name = __change_cell_name(hc, new_data);  	/*  	 * Wake up any dm event waiters.  	 */ -	table = dm_get_live_table(hc->md); -	if (table) { +	table = dm_get_live_table(hc->md, &srcu_idx); +	if (table)  		dm_table_event(table); -		dm_table_put(table); -	} +	dm_put_live_table(hc->md, srcu_idx);  	if (!dm_kobject_uevent(hc->md, KOBJ_CHANGE, param->event_nr))  		param->flags |= DM_UEVENT_GENERATED_FLAG; @@ -366,6 +451,11 @@ static struct mapped_device *dm_hash_rename(struct dm_ioctl *param,  	return md;  } +void dm_deferred_remove(void) +{ +	dm_hash_remove_all(true, false, true); +} +  /*-----------------------------------------------------------------   * Implementation of the ioctl commands   *---------------------------------------------------------------*/ @@ -377,7 +467,7 @@ typedef int (*ioctl_fn)(struct dm_ioctl *param, size_t param_size);  static int remove_all(struct dm_ioctl *param, size_t param_size)  { -	dm_hash_remove_all(1); +	dm_hash_remove_all(true, !!(param->flags & DM_DEFERRED_REMOVE), false);  	param->data_size = 0;  	return 0;  } @@ -552,11 +642,14 @@ static int check_name(const char *name)   * _hash_lock without first calling dm_table_put, because dm_table_destroy   * waits for this dm_table_put and could be called under this lock.   */ -static struct dm_table *dm_get_inactive_table(struct mapped_device *md) +static struct dm_table *dm_get_inactive_table(struct mapped_device *md, int *srcu_idx)  {  	struct hash_cell *hc;  	struct dm_table *table = NULL; +	/* increment rcu count, we don't care about the table pointer */ +	dm_get_live_table(md, srcu_idx); +  	down_read(&_hash_lock);  	hc = dm_get_mdptr(md);  	if (!hc || hc->md != md) { @@ -565,8 +658,6 @@ static struct dm_table *dm_get_inactive_table(struct mapped_device *md)  	}  	table = hc->new_map; -	if (table) -		dm_table_get(table);  out:  	up_read(&_hash_lock); @@ -575,10 +666,11 @@ out:  }  static struct dm_table *dm_get_live_or_inactive_table(struct mapped_device *md, -						      struct dm_ioctl *param) +						      struct dm_ioctl *param, +						      int *srcu_idx)  {  	return (param->flags & DM_QUERY_INACTIVE_TABLE_FLAG) ? -		dm_get_inactive_table(md) : dm_get_live_table(md); +		dm_get_inactive_table(md, srcu_idx) : dm_get_live_table(md, srcu_idx);  }  /* @@ -589,6 +681,7 @@ static void __dev_status(struct mapped_device *md, struct dm_ioctl *param)  {  	struct gendisk *disk = dm_disk(md);  	struct dm_table *table; +	int srcu_idx;  	param->flags &= ~(DM_SUSPEND_FLAG | DM_READONLY_FLAG |  			  DM_ACTIVE_PRESENT_FLAG); @@ -596,6 +689,9 @@ static void __dev_status(struct mapped_device *md, struct dm_ioctl *param)  	if (dm_suspended_md(md))  		param->flags |= DM_SUSPEND_FLAG; +	if (dm_test_deferred_remove_flag(md)) +		param->flags |= DM_DEFERRED_REMOVE; +  	param->dev = huge_encode_dev(disk_devt(disk));  	/* @@ -608,26 +704,27 @@ static void __dev_status(struct mapped_device *md, struct dm_ioctl *param)  	param->event_nr = dm_get_event_nr(md);  	param->target_count = 0; -	table = dm_get_live_table(md); +	table = dm_get_live_table(md, &srcu_idx);  	if (table) {  		if (!(param->flags & DM_QUERY_INACTIVE_TABLE_FLAG)) {  			if (get_disk_ro(disk))  				param->flags |= DM_READONLY_FLAG;  			param->target_count = dm_table_get_num_targets(table);  		} -		dm_table_put(table);  		param->flags |= DM_ACTIVE_PRESENT_FLAG;  	} +	dm_put_live_table(md, srcu_idx);  	if (param->flags & DM_QUERY_INACTIVE_TABLE_FLAG) { -		table = dm_get_inactive_table(md); +		int srcu_idx; +		table = dm_get_inactive_table(md, &srcu_idx);  		if (table) {  			if (!(dm_table_get_mode(table) & FMODE_WRITE))  				param->flags |= DM_READONLY_FLAG;  			param->target_count = dm_table_get_num_targets(table); -			dm_table_put(table);  		} +		dm_put_live_table(md, srcu_idx);  	}  } @@ -668,25 +765,45 @@ static int dev_create(struct dm_ioctl *param, size_t param_size)   */  static struct hash_cell *__find_device_hash_cell(struct dm_ioctl *param)  { -	struct mapped_device *md; -	void *mdptr = NULL; +	struct hash_cell *hc = NULL; -	if (*param->uuid) -		return __get_uuid_cell(param->uuid); +	if (*param->uuid) { +		if (*param->name || param->dev) +			return NULL; -	if (*param->name) -		return __get_name_cell(param->name); +		hc = __get_uuid_cell(param->uuid); +		if (!hc) +			return NULL; +	} else if (*param->name) { +		if (param->dev) +			return NULL; -	md = dm_get_md(huge_decode_dev(param->dev)); -	if (!md) -		goto out; +		hc = __get_name_cell(param->name); +		if (!hc) +			return NULL; +	} else if (param->dev) { +		hc = __get_dev_cell(param->dev); +		if (!hc) +			return NULL; +	} else +		return NULL; -	mdptr = dm_get_mdptr(md); -	if (!mdptr) -		dm_put(md); +	/* +	 * Sneakily write in both the name and the uuid +	 * while we have the cell. +	 */ +	strlcpy(param->name, hc->name, sizeof(param->name)); +	if (hc->uuid) +		strlcpy(param->uuid, hc->uuid, sizeof(param->uuid)); +	else +		param->uuid[0] = '\0'; -out: -	return mdptr; +	if (hc->new_map) +		param->flags |= DM_INACTIVE_PRESENT_FLAG; +	else +		param->flags &= ~DM_INACTIVE_PRESENT_FLAG; + +	return hc;  }  static struct mapped_device *find_device(struct dm_ioctl *param) @@ -696,24 +813,8 @@ static struct mapped_device *find_device(struct dm_ioctl *param)  	down_read(&_hash_lock);  	hc = __find_device_hash_cell(param); -	if (hc) { +	if (hc)  		md = hc->md; - -		/* -		 * Sneakily write in both the name and the uuid -		 * while we have the cell. -		 */ -		strlcpy(param->name, hc->name, sizeof(param->name)); -		if (hc->uuid) -			strlcpy(param->uuid, hc->uuid, sizeof(param->uuid)); -		else -			param->uuid[0] = '\0'; - -		if (hc->new_map) -			param->flags |= DM_INACTIVE_PRESENT_FLAG; -		else -			param->flags &= ~DM_INACTIVE_PRESENT_FLAG; -	}  	up_read(&_hash_lock);  	return md; @@ -724,12 +825,13 @@ static int dev_remove(struct dm_ioctl *param, size_t param_size)  	struct hash_cell *hc;  	struct mapped_device *md;  	int r; +	struct dm_table *t;  	down_write(&_hash_lock);  	hc = __find_device_hash_cell(param);  	if (!hc) { -		DMWARN("device doesn't appear to be in the dev hash table."); +		DMDEBUG_LIMIT("device doesn't appear to be in the dev hash table.");  		up_write(&_hash_lock);  		return -ENXIO;  	} @@ -739,17 +841,29 @@ static int dev_remove(struct dm_ioctl *param, size_t param_size)  	/*  	 * Ensure the device is not open and nothing further can open it.  	 */ -	r = dm_lock_for_deletion(md); +	r = dm_lock_for_deletion(md, !!(param->flags & DM_DEFERRED_REMOVE), false);  	if (r) { -		DMWARN("unable to remove open device %s", hc->name); +		if (r == -EBUSY && param->flags & DM_DEFERRED_REMOVE) { +			up_write(&_hash_lock); +			dm_put(md); +			return 0; +		} +		DMDEBUG_LIMIT("unable to remove open device %s", hc->name);  		up_write(&_hash_lock);  		dm_put(md);  		return r;  	} -	__hash_remove(hc); +	t = __hash_remove(hc);  	up_write(&_hash_lock); +	if (t) { +		dm_sync_table(md); +		dm_table_destroy(t); +	} + +	param->flags &= ~DM_DEFERRED_REMOVE; +  	if (!dm_kobject_uevent(md, KOBJ_REMOVE, param->event_nr))  		param->flags |= DM_UEVENT_GENERATED_FLAG; @@ -774,21 +888,24 @@ static int invalid_str(char *str, void *end)  static int dev_rename(struct dm_ioctl *param, size_t param_size)  {  	int r; -	char *new_name = (char *) param + param->data_start; +	char *new_data = (char *) param + param->data_start;  	struct mapped_device *md; +	unsigned change_uuid = (param->flags & DM_UUID_FLAG) ? 1 : 0; -	if (new_name < param->data || -	    invalid_str(new_name, (void *) param + param_size) || -	    strlen(new_name) > DM_NAME_LEN - 1) { -		DMWARN("Invalid new logical volume name supplied."); +	if (new_data < param->data || +	    invalid_str(new_data, (void *) param + param_size) || !*new_data || +	    strlen(new_data) > (change_uuid ? DM_UUID_LEN - 1 : DM_NAME_LEN - 1)) { +		DMWARN("Invalid new mapped device name or uuid string supplied.");  		return -EINVAL;  	} -	r = check_name(new_name); -	if (r) -		return r; +	if (!change_uuid) { +		r = check_name(new_data); +		if (r) +			return r; +	} -	md = dm_hash_rename(param, new_name); +	md = dm_hash_rename(param, new_data);  	if (IS_ERR(md))  		return PTR_ERR(md); @@ -805,6 +922,7 @@ static int dev_set_geometry(struct dm_ioctl *param, size_t param_size)  	struct hd_geometry geometry;  	unsigned long indata[4];  	char *geostr = (char *) param + param->data_start; +	char dummy;  	md = find_device(param);  	if (!md) @@ -816,8 +934,8 @@ static int dev_set_geometry(struct dm_ioctl *param, size_t param_size)  		goto out;  	} -	x = sscanf(geostr, "%lu %lu %lu %lu", indata, -		   indata + 1, indata + 2, indata + 3); +	x = sscanf(geostr, "%lu %lu %lu %lu%c", indata, +		   indata + 1, indata + 2, indata + 3, &dummy);  	if (x != 4) {  		DMWARN("Unable to interpret geometry settings."); @@ -885,7 +1003,7 @@ static int do_resume(struct dm_ioctl *param)  	hc = __find_device_hash_cell(param);  	if (!hc) { -		DMWARN("device doesn't appear to be in the dev hash table."); +		DMDEBUG_LIMIT("device doesn't appear to be in the dev hash table.");  		up_write(&_hash_lock);  		return -ENXIO;  	} @@ -910,6 +1028,7 @@ static int do_resume(struct dm_ioctl *param)  		old_map = dm_swap_table(md, new_map);  		if (IS_ERR(old_map)) { +			dm_sync_table(md);  			dm_table_destroy(new_map);  			dm_put(md);  			return PTR_ERR(old_map); @@ -927,6 +1046,10 @@ static int do_resume(struct dm_ioctl *param)  			param->flags |= DM_UEVENT_GENERATED_FLAG;  	} +	/* +	 * Since dm_swap_table synchronizes RCU, nobody should be in +	 * read-side critical section already. +	 */  	if (old_map)  		dm_table_destroy(old_map); @@ -978,6 +1101,7 @@ static void retrieve_status(struct dm_table *table,  	char *outbuf, *outptr;  	status_type_t type;  	size_t remaining, len, used = 0; +	unsigned status_flags = 0;  	outptr = outbuf = get_result_buffer(param, param_size, &len); @@ -990,6 +1114,7 @@ static void retrieve_status(struct dm_table *table,  	num_targets = dm_table_get_num_targets(table);  	for (i = 0; i < num_targets; i++) {  		struct dm_target *ti = dm_table_get_target(table, i); +		size_t l;  		remaining = len - (outptr - outbuf);  		if (remaining <= sizeof(struct dm_target_spec)) { @@ -1014,14 +1139,19 @@ static void retrieve_status(struct dm_table *table,  		/* Get the status/table string from the target driver */  		if (ti->type->status) { -			if (ti->type->status(ti, type, outptr, remaining)) { -				param->flags |= DM_BUFFER_FULL_FLAG; -				break; -			} +			if (param->flags & DM_NOFLUSH_FLAG) +				status_flags |= DM_STATUS_NOFLUSH_FLAG; +			ti->type->status(ti, type, status_flags, outptr, remaining);  		} else  			outptr[0] = '\0'; -		outptr += strlen(outptr) + 1; +		l = strlen(outptr) + 1; +		if (l == remaining) { +			param->flags |= DM_BUFFER_FULL_FLAG; +			break; +		} + +		outptr += l;  		used = param->data_start + (outptr - outbuf);  		outptr = align_ptr(outptr); @@ -1042,6 +1172,7 @@ static int dev_wait(struct dm_ioctl *param, size_t param_size)  	int r = 0;  	struct mapped_device *md;  	struct dm_table *table; +	int srcu_idx;  	md = find_device(param);  	if (!md) @@ -1062,11 +1193,10 @@ static int dev_wait(struct dm_ioctl *param, size_t param_size)  	 */  	__dev_status(md, param); -	table = dm_get_live_or_inactive_table(md, param); -	if (table) { +	table = dm_get_live_or_inactive_table(md, param, &srcu_idx); +	if (table)  		retrieve_status(table, param, param_size); -		dm_table_put(table); -	} +	dm_put_live_table(md, srcu_idx);  out:  	dm_put(md); @@ -1138,8 +1268,9 @@ static int table_load(struct dm_ioctl *param, size_t param_size)  {  	int r;  	struct hash_cell *hc; -	struct dm_table *t; +	struct dm_table *t, *old_map = NULL;  	struct mapped_device *md; +	struct target_type *immutable_target_type;  	md = find_device(param);  	if (!md) @@ -1147,34 +1278,37 @@ static int table_load(struct dm_ioctl *param, size_t param_size)  	r = dm_table_create(&t, get_mode(param), param->target_count, md);  	if (r) -		goto out; +		goto err; +	/* Protect md->type and md->queue against concurrent table loads. */ +	dm_lock_md_type(md);  	r = populate_table(t, param, param_size); -	if (r) { -		dm_table_destroy(t); -		goto out; +	if (r) +		goto err_unlock_md_type; + +	immutable_target_type = dm_get_immutable_target_type(md); +	if (immutable_target_type && +	    (immutable_target_type != dm_table_get_immutable_target_type(t))) { +		DMWARN("can't replace immutable target type %s", +		       immutable_target_type->name); +		r = -EINVAL; +		goto err_unlock_md_type;  	} -	/* Protect md->type and md->queue against concurrent table loads. */ -	dm_lock_md_type(md);  	if (dm_get_md_type(md) == DM_TYPE_NONE)  		/* Initial table load: acquire type of table. */  		dm_set_md_type(md, dm_table_get_type(t));  	else if (dm_get_md_type(md) != dm_table_get_type(t)) {  		DMWARN("can't change device type after initial table load."); -		dm_table_destroy(t); -		dm_unlock_md_type(md);  		r = -EINVAL; -		goto out; +		goto err_unlock_md_type;  	}  	/* setup md->queue to reflect md's type (may block) */  	r = dm_setup_md_queue(md);  	if (r) {  		DMWARN("unable to set up device queue for new table."); -		dm_table_destroy(t); -		dm_unlock_md_type(md); -		goto out; +		goto err_unlock_md_type;  	}  	dm_unlock_md_type(md); @@ -1183,21 +1317,33 @@ static int table_load(struct dm_ioctl *param, size_t param_size)  	hc = dm_get_mdptr(md);  	if (!hc || hc->md != md) {  		DMWARN("device has been removed from the dev hash table."); -		dm_table_destroy(t);  		up_write(&_hash_lock);  		r = -ENXIO; -		goto out; +		goto err_destroy_table;  	}  	if (hc->new_map) -		dm_table_destroy(hc->new_map); +		old_map = hc->new_map;  	hc->new_map = t;  	up_write(&_hash_lock);  	param->flags |= DM_INACTIVE_PRESENT_FLAG;  	__dev_status(md, param); -out: +	if (old_map) { +		dm_sync_table(md); +		dm_table_destroy(old_map); +	} + +	dm_put(md); + +	return 0; + +err_unlock_md_type: +	dm_unlock_md_type(md); +err_destroy_table: +	dm_table_destroy(t); +err:  	dm_put(md);  	return r; @@ -1207,18 +1353,19 @@ static int table_clear(struct dm_ioctl *param, size_t param_size)  {  	struct hash_cell *hc;  	struct mapped_device *md; +	struct dm_table *old_map = NULL;  	down_write(&_hash_lock);  	hc = __find_device_hash_cell(param);  	if (!hc) { -		DMWARN("device doesn't appear to be in the dev hash table."); +		DMDEBUG_LIMIT("device doesn't appear to be in the dev hash table.");  		up_write(&_hash_lock);  		return -ENXIO;  	}  	if (hc->new_map) { -		dm_table_destroy(hc->new_map); +		old_map = hc->new_map;  		hc->new_map = NULL;  	} @@ -1227,6 +1374,10 @@ static int table_clear(struct dm_ioctl *param, size_t param_size)  	__dev_status(hc->md, param);  	md = hc->md;  	up_write(&_hash_lock); +	if (old_map) { +		dm_sync_table(md); +		dm_table_destroy(old_map); +	}  	dm_put(md);  	return 0; @@ -1276,6 +1427,7 @@ static int table_deps(struct dm_ioctl *param, size_t param_size)  {  	struct mapped_device *md;  	struct dm_table *table; +	int srcu_idx;  	md = find_device(param);  	if (!md) @@ -1283,11 +1435,10 @@ static int table_deps(struct dm_ioctl *param, size_t param_size)  	__dev_status(md, param); -	table = dm_get_live_or_inactive_table(md, param); -	if (table) { +	table = dm_get_live_or_inactive_table(md, param, &srcu_idx); +	if (table)  		retrieve_deps(table, param, param_size); -		dm_table_put(table); -	} +	dm_put_live_table(md, srcu_idx);  	dm_put(md); @@ -1302,6 +1453,7 @@ static int table_status(struct dm_ioctl *param, size_t param_size)  {  	struct mapped_device *md;  	struct dm_table *table; +	int srcu_idx;  	md = find_device(param);  	if (!md) @@ -1309,11 +1461,10 @@ static int table_status(struct dm_ioctl *param, size_t param_size)  	__dev_status(md, param); -	table = dm_get_live_or_inactive_table(md, param); -	if (table) { +	table = dm_get_live_or_inactive_table(md, param, &srcu_idx); +	if (table)  		retrieve_status(table, param, param_size); -		dm_table_put(table); -	} +	dm_put_live_table(md, srcu_idx);  	dm_put(md); @@ -1321,6 +1472,36 @@ static int table_status(struct dm_ioctl *param, size_t param_size)  }  /* + * Process device-mapper dependent messages.  Messages prefixed with '@' + * are processed by the DM core.  All others are delivered to the target. + * Returns a number <= 1 if message was processed by device mapper. + * Returns 2 if message should be delivered to the target. + */ +static int message_for_md(struct mapped_device *md, unsigned argc, char **argv, +			  char *result, unsigned maxlen) +{ +	int r; + +	if (**argv != '@') +		return 2; /* no '@' prefix, deliver to target */ + +	if (!strcasecmp(argv[0], "@cancel_deferred_remove")) { +		if (argc != 1) { +			DMERR("Invalid arguments for @cancel_deferred_remove"); +			return -EINVAL; +		} +		return dm_cancel_deferred_remove(md); +	} + +	r = dm_stats_message(md, argc, argv, result, maxlen); +	if (r < 2) +		return r; + +	DMERR("Unsupported message sent to DM core: %s", argv[0]); +	return -EINVAL; +} + +/*   * 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) @@ -1331,6 +1512,9 @@ static int target_message(struct dm_ioctl *param, size_t param_size)  	struct dm_table *table;  	struct dm_target *ti;  	struct dm_target_msg *tmsg = (void *) param + param->data_start; +	size_t maxlen; +	char *result = get_result_buffer(param, param_size, &maxlen); +	int srcu_idx;  	md = find_device(param);  	if (!md) @@ -1349,10 +1533,19 @@ static int target_message(struct dm_ioctl *param, size_t param_size)  		goto out;  	} -	table = dm_get_live_table(md); -	if (!table) +	if (!argc) { +		DMWARN("Empty message received."); +		goto out_argv; +	} + +	r = message_for_md(md, argc, argv, result, maxlen); +	if (r <= 1)  		goto out_argv; +	table = dm_get_live_table(md, &srcu_idx); +	if (!table) +		goto out_table; +  	if (dm_deleting_md(md)) {  		r = -ENXIO;  		goto out_table; @@ -1370,48 +1563,72 @@ static int target_message(struct dm_ioctl *param, size_t param_size)  	}   out_table: -	dm_table_put(table); +	dm_put_live_table(md, srcu_idx);   out_argv:  	kfree(argv);   out: -	param->data_size = 0; +	if (r >= 0) +		__dev_status(md, param); + +	if (r == 1) { +		param->flags |= DM_DATA_OUT_FLAG; +		if (dm_message_test_buffer_overflow(result, maxlen)) +			param->flags |= DM_BUFFER_FULL_FLAG; +		else +			param->data_size = param->data_start + strlen(result) + 1; +		r = 0; +	} +  	dm_put(md);  	return r;  } +/* + * The ioctl parameter block consists of two parts, a dm_ioctl struct + * followed by a data buffer.  This flag is set if the second part, + * which has a variable size, is not used by the function processing + * the ioctl. + */ +#define IOCTL_FLAGS_NO_PARAMS	1 +  /*-----------------------------------------------------------------   * Implementation of open/close/ioctl on the special char   * device.   *---------------------------------------------------------------*/ -static ioctl_fn lookup_ioctl(unsigned int cmd) +static ioctl_fn lookup_ioctl(unsigned int cmd, int *ioctl_flags)  {  	static struct {  		int cmd; +		int flags;  		ioctl_fn fn;  	} _ioctls[] = { -		{DM_VERSION_CMD, NULL},	/* version is dealt with elsewhere */ -		{DM_REMOVE_ALL_CMD, remove_all}, -		{DM_LIST_DEVICES_CMD, list_devices}, - -		{DM_DEV_CREATE_CMD, dev_create}, -		{DM_DEV_REMOVE_CMD, dev_remove}, -		{DM_DEV_RENAME_CMD, dev_rename}, -		{DM_DEV_SUSPEND_CMD, dev_suspend}, -		{DM_DEV_STATUS_CMD, dev_status}, -		{DM_DEV_WAIT_CMD, dev_wait}, - -		{DM_TABLE_LOAD_CMD, table_load}, -		{DM_TABLE_CLEAR_CMD, table_clear}, -		{DM_TABLE_DEPS_CMD, table_deps}, -		{DM_TABLE_STATUS_CMD, table_status}, - -		{DM_LIST_VERSIONS_CMD, list_versions}, - -		{DM_TARGET_MSG_CMD, target_message}, -		{DM_DEV_SET_GEOMETRY_CMD, dev_set_geometry} +		{DM_VERSION_CMD, 0, NULL}, /* version is dealt with elsewhere */ +		{DM_REMOVE_ALL_CMD, IOCTL_FLAGS_NO_PARAMS, remove_all}, +		{DM_LIST_DEVICES_CMD, 0, list_devices}, + +		{DM_DEV_CREATE_CMD, IOCTL_FLAGS_NO_PARAMS, dev_create}, +		{DM_DEV_REMOVE_CMD, IOCTL_FLAGS_NO_PARAMS, dev_remove}, +		{DM_DEV_RENAME_CMD, 0, dev_rename}, +		{DM_DEV_SUSPEND_CMD, IOCTL_FLAGS_NO_PARAMS, dev_suspend}, +		{DM_DEV_STATUS_CMD, IOCTL_FLAGS_NO_PARAMS, dev_status}, +		{DM_DEV_WAIT_CMD, 0, dev_wait}, + +		{DM_TABLE_LOAD_CMD, 0, table_load}, +		{DM_TABLE_CLEAR_CMD, IOCTL_FLAGS_NO_PARAMS, table_clear}, +		{DM_TABLE_DEPS_CMD, 0, table_deps}, +		{DM_TABLE_STATUS_CMD, 0, table_status}, + +		{DM_LIST_VERSIONS_CMD, 0, list_versions}, + +		{DM_TARGET_MSG_CMD, 0, target_message}, +		{DM_DEV_SET_GEOMETRY_CMD, 0, dev_set_geometry}  	}; -	return (cmd >= ARRAY_SIZE(_ioctls)) ? NULL : _ioctls[cmd].fn; +	if (unlikely(cmd >= ARRAY_SIZE(_ioctls))) +		return NULL; + +	*ioctl_flags = _ioctls[cmd].flags; +	return _ioctls[cmd].fn;  }  /* @@ -1448,32 +1665,94 @@ static int check_version(unsigned int cmd, struct dm_ioctl __user *user)  	return r;  } -static void free_params(struct dm_ioctl *param) +#define DM_PARAMS_KMALLOC	0x0001	/* Params alloced with kmalloc */ +#define DM_PARAMS_VMALLOC	0x0002	/* Params alloced with vmalloc */ +#define DM_WIPE_BUFFER		0x0010	/* Wipe input buffer before returning from ioctl */ + +static void free_params(struct dm_ioctl *param, size_t param_size, int param_flags)  { -	vfree(param); +	if (param_flags & DM_WIPE_BUFFER) +		memset(param, 0, param_size); + +	if (param_flags & DM_PARAMS_KMALLOC) +		kfree(param); +	if (param_flags & DM_PARAMS_VMALLOC) +		vfree(param);  } -static int copy_params(struct dm_ioctl __user *user, struct dm_ioctl **param) +static int copy_params(struct dm_ioctl __user *user, struct dm_ioctl *param_kernel, +		       int ioctl_flags, +		       struct dm_ioctl **param, int *param_flags)  { -	struct dm_ioctl tmp, *dmi; +	struct dm_ioctl *dmi; +	int secure_data; +	const size_t minimum_data_size = sizeof(*param_kernel) - sizeof(param_kernel->data); -	if (copy_from_user(&tmp, user, sizeof(tmp) - sizeof(tmp.data))) +	if (copy_from_user(param_kernel, user, minimum_data_size))  		return -EFAULT; -	if (tmp.data_size < (sizeof(tmp) - sizeof(tmp.data))) +	if (param_kernel->data_size < minimum_data_size)  		return -EINVAL; -	dmi = vmalloc(tmp.data_size); -	if (!dmi) +	secure_data = param_kernel->flags & DM_SECURE_DATA_FLAG; + +	*param_flags = secure_data ? DM_WIPE_BUFFER : 0; + +	if (ioctl_flags & IOCTL_FLAGS_NO_PARAMS) { +		dmi = param_kernel; +		dmi->data_size = minimum_data_size; +		goto data_copied; +	} + +	/* +	 * Try to avoid low memory issues when a device is suspended. +	 * Use kmalloc() rather than vmalloc() when we can. +	 */ +	dmi = NULL; +	if (param_kernel->data_size <= KMALLOC_MAX_SIZE) { +		dmi = kmalloc(param_kernel->data_size, GFP_NOIO | __GFP_NORETRY | __GFP_NOMEMALLOC | __GFP_NOWARN); +		if (dmi) +			*param_flags |= DM_PARAMS_KMALLOC; +	} + +	if (!dmi) { +		unsigned noio_flag; +		noio_flag = memalloc_noio_save(); +		dmi = __vmalloc(param_kernel->data_size, GFP_NOIO | __GFP_REPEAT | __GFP_HIGH | __GFP_HIGHMEM, PAGE_KERNEL); +		memalloc_noio_restore(noio_flag); +		if (dmi) +			*param_flags |= DM_PARAMS_VMALLOC; +	} + +	if (!dmi) { +		if (secure_data && clear_user(user, param_kernel->data_size)) +			return -EFAULT;  		return -ENOMEM; +	} -	if (copy_from_user(dmi, user, tmp.data_size)) { -		vfree(dmi); -		return -EFAULT; +	if (copy_from_user(dmi, user, param_kernel->data_size)) +		goto bad; + +data_copied: +	/* +	 * Abort if something changed the ioctl data while it was being copied. +	 */ +	if (dmi->data_size != param_kernel->data_size) { +		DMERR("rejecting ioctl: data size modified while processing parameters"); +		goto bad;  	} +	/* Wipe the user buffer so we do not return it to userspace */ +	if (secure_data && clear_user(user, param_kernel->data_size)) +		goto bad; +  	*param = dmi;  	return 0; + +bad: +	free_params(dmi, param_kernel->data_size, *param_flags); + +	return -EFAULT;  }  static int validate_params(uint cmd, struct dm_ioctl *param) @@ -1481,6 +1760,8 @@ static int validate_params(uint cmd, struct dm_ioctl *param)  	/* Always clear this flag */  	param->flags &= ~DM_BUFFER_FULL_FLAG;  	param->flags &= ~DM_UEVENT_GENERATED_FLAG; +	param->flags &= ~DM_SECURE_DATA_FLAG; +	param->flags &= ~DM_DATA_OUT_FLAG;  	/* Ignores parameters */  	if (cmd == DM_REMOVE_ALL_CMD || @@ -1508,10 +1789,13 @@ static int validate_params(uint cmd, struct dm_ioctl *param)  static int ctl_ioctl(uint command, struct dm_ioctl __user *user)  {  	int r = 0; +	int ioctl_flags; +	int param_flags;  	unsigned int cmd;  	struct dm_ioctl *uninitialized_var(param);  	ioctl_fn fn = NULL; -	size_t param_size; +	size_t input_param_size; +	struct dm_ioctl param_kernel;  	/* only root can play with this */  	if (!capable(CAP_SYS_ADMIN)) @@ -1536,35 +1820,31 @@ static int ctl_ioctl(uint command, struct dm_ioctl __user *user)  	if (cmd == DM_VERSION_CMD)  		return 0; -	fn = lookup_ioctl(cmd); +	fn = lookup_ioctl(cmd, &ioctl_flags);  	if (!fn) {  		DMWARN("dm_ctl_ioctl: unknown command 0x%x", command);  		return -ENOTTY;  	}  	/* -	 * Trying to avoid low memory issues when a device is -	 * suspended. -	 */ -	current->flags |= PF_MEMALLOC; - -	/*  	 * Copy the parameters into kernel space.  	 */ -	r = copy_params(user, ¶m); - -	current->flags &= ~PF_MEMALLOC; +	r = copy_params(user, ¶m_kernel, ioctl_flags, ¶m, ¶m_flags);  	if (r)  		return r; +	input_param_size = param->data_size;  	r = validate_params(cmd, param);  	if (r)  		goto out; -	param_size = param->data_size;  	param->data_size = sizeof(*param); -	r = fn(param, param_size); +	r = fn(param, input_param_size); + +	if (unlikely(param->flags & DM_BUFFER_FULL_FLAG) && +	    unlikely(ioctl_flags & IOCTL_FLAGS_NO_PARAMS)) +		DMERR("ioctl %d tried to output some data but has IOCTL_FLAGS_NO_PARAMS set", cmd);  	/*  	 * Copy the results back to userland. @@ -1572,8 +1852,8 @@ static int ctl_ioctl(uint command, struct dm_ioctl __user *user)  	if (!r && copy_to_user(user, param, param->data_size))  		r = -EFAULT; - out: -	free_params(param); +out: +	free_params(param, input_param_size, param_flags);  	return r;  }  | 
