diff options
author | Linus Torvalds <torvalds@ppc970.osdl.org> | 2005-04-16 18:20:36 -0400 |
---|---|---|
committer | Linus Torvalds <torvalds@ppc970.osdl.org> | 2005-04-16 18:20:36 -0400 |
commit | 1da177e4c3f41524e886b7f1b8a0c1fc7321cac2 (patch) | |
tree | 0bba044c4ce775e45a88a51686b5d9f90697ea9d /drivers/md/dm-ioctl.c |
Linux-2.6.12-rc2v2.6.12-rc2
Initial git repository build. I'm not bothering with the full history,
even though we have it. We can create a separate "historical" git
archive of that later if we want to, and in the meantime it's about
3.2GB when imported into git - space that would just make the early
git days unnecessarily complicated, when we don't have a lot of good
infrastructure for it.
Let it rip!
Diffstat (limited to 'drivers/md/dm-ioctl.c')
-rw-r--r-- | drivers/md/dm-ioctl.c | 1416 |
1 files changed, 1416 insertions, 0 deletions
diff --git a/drivers/md/dm-ioctl.c b/drivers/md/dm-ioctl.c new file mode 100644 index 000000000000..ee3c869d9701 --- /dev/null +++ b/drivers/md/dm-ioctl.c | |||
@@ -0,0 +1,1416 @@ | |||
1 | /* | ||
2 | * Copyright (C) 2001, 2002 Sistina Software (UK) Limited. | ||
3 | * Copyright (C) 2004 - 2005 Red Hat, Inc. All rights reserved. | ||
4 | * | ||
5 | * This file is released under the GPL. | ||
6 | */ | ||
7 | |||
8 | #include "dm.h" | ||
9 | |||
10 | #include <linux/module.h> | ||
11 | #include <linux/vmalloc.h> | ||
12 | #include <linux/miscdevice.h> | ||
13 | #include <linux/init.h> | ||
14 | #include <linux/wait.h> | ||
15 | #include <linux/slab.h> | ||
16 | #include <linux/devfs_fs_kernel.h> | ||
17 | #include <linux/dm-ioctl.h> | ||
18 | |||
19 | #include <asm/uaccess.h> | ||
20 | |||
21 | #define DM_DRIVER_EMAIL "dm-devel@redhat.com" | ||
22 | |||
23 | /*----------------------------------------------------------------- | ||
24 | * The ioctl interface needs to be able to look up devices by | ||
25 | * name or uuid. | ||
26 | *---------------------------------------------------------------*/ | ||
27 | struct hash_cell { | ||
28 | struct list_head name_list; | ||
29 | struct list_head uuid_list; | ||
30 | |||
31 | char *name; | ||
32 | char *uuid; | ||
33 | struct mapped_device *md; | ||
34 | struct dm_table *new_map; | ||
35 | }; | ||
36 | |||
37 | struct vers_iter { | ||
38 | size_t param_size; | ||
39 | struct dm_target_versions *vers, *old_vers; | ||
40 | char *end; | ||
41 | uint32_t flags; | ||
42 | }; | ||
43 | |||
44 | |||
45 | #define NUM_BUCKETS 64 | ||
46 | #define MASK_BUCKETS (NUM_BUCKETS - 1) | ||
47 | static struct list_head _name_buckets[NUM_BUCKETS]; | ||
48 | static struct list_head _uuid_buckets[NUM_BUCKETS]; | ||
49 | |||
50 | static void dm_hash_remove_all(void); | ||
51 | |||
52 | /* | ||
53 | * Guards access to both hash tables. | ||
54 | */ | ||
55 | static DECLARE_RWSEM(_hash_lock); | ||
56 | |||
57 | static void init_buckets(struct list_head *buckets) | ||
58 | { | ||
59 | unsigned int i; | ||
60 | |||
61 | for (i = 0; i < NUM_BUCKETS; i++) | ||
62 | INIT_LIST_HEAD(buckets + i); | ||
63 | } | ||
64 | |||
65 | static int dm_hash_init(void) | ||
66 | { | ||
67 | init_buckets(_name_buckets); | ||
68 | init_buckets(_uuid_buckets); | ||
69 | devfs_mk_dir(DM_DIR); | ||
70 | return 0; | ||
71 | } | ||
72 | |||
73 | static void dm_hash_exit(void) | ||
74 | { | ||
75 | dm_hash_remove_all(); | ||
76 | devfs_remove(DM_DIR); | ||
77 | } | ||
78 | |||
79 | /*----------------------------------------------------------------- | ||
80 | * Hash function: | ||
81 | * We're not really concerned with the str hash function being | ||
82 | * fast since it's only used by the ioctl interface. | ||
83 | *---------------------------------------------------------------*/ | ||
84 | static unsigned int hash_str(const char *str) | ||
85 | { | ||
86 | const unsigned int hash_mult = 2654435387U; | ||
87 | unsigned int h = 0; | ||
88 | |||
89 | while (*str) | ||
90 | h = (h + (unsigned int) *str++) * hash_mult; | ||
91 | |||
92 | return h & MASK_BUCKETS; | ||
93 | } | ||
94 | |||
95 | /*----------------------------------------------------------------- | ||
96 | * Code for looking up a device by name | ||
97 | *---------------------------------------------------------------*/ | ||
98 | static struct hash_cell *__get_name_cell(const char *str) | ||
99 | { | ||
100 | struct hash_cell *hc; | ||
101 | unsigned int h = hash_str(str); | ||
102 | |||
103 | list_for_each_entry (hc, _name_buckets + h, name_list) | ||
104 | if (!strcmp(hc->name, str)) | ||
105 | return hc; | ||
106 | |||
107 | return NULL; | ||
108 | } | ||
109 | |||
110 | static struct hash_cell *__get_uuid_cell(const char *str) | ||
111 | { | ||
112 | struct hash_cell *hc; | ||
113 | unsigned int h = hash_str(str); | ||
114 | |||
115 | list_for_each_entry (hc, _uuid_buckets + h, uuid_list) | ||
116 | if (!strcmp(hc->uuid, str)) | ||
117 | return hc; | ||
118 | |||
119 | return NULL; | ||
120 | } | ||
121 | |||
122 | /*----------------------------------------------------------------- | ||
123 | * Inserting, removing and renaming a device. | ||
124 | *---------------------------------------------------------------*/ | ||
125 | static inline char *kstrdup(const char *str) | ||
126 | { | ||
127 | char *r = kmalloc(strlen(str) + 1, GFP_KERNEL); | ||
128 | if (r) | ||
129 | strcpy(r, str); | ||
130 | return r; | ||
131 | } | ||
132 | |||
133 | static struct hash_cell *alloc_cell(const char *name, const char *uuid, | ||
134 | struct mapped_device *md) | ||
135 | { | ||
136 | struct hash_cell *hc; | ||
137 | |||
138 | hc = kmalloc(sizeof(*hc), GFP_KERNEL); | ||
139 | if (!hc) | ||
140 | return NULL; | ||
141 | |||
142 | hc->name = kstrdup(name); | ||
143 | if (!hc->name) { | ||
144 | kfree(hc); | ||
145 | return NULL; | ||
146 | } | ||
147 | |||
148 | if (!uuid) | ||
149 | hc->uuid = NULL; | ||
150 | |||
151 | else { | ||
152 | hc->uuid = kstrdup(uuid); | ||
153 | if (!hc->uuid) { | ||
154 | kfree(hc->name); | ||
155 | kfree(hc); | ||
156 | return NULL; | ||
157 | } | ||
158 | } | ||
159 | |||
160 | INIT_LIST_HEAD(&hc->name_list); | ||
161 | INIT_LIST_HEAD(&hc->uuid_list); | ||
162 | hc->md = md; | ||
163 | hc->new_map = NULL; | ||
164 | return hc; | ||
165 | } | ||
166 | |||
167 | static void free_cell(struct hash_cell *hc) | ||
168 | { | ||
169 | if (hc) { | ||
170 | kfree(hc->name); | ||
171 | kfree(hc->uuid); | ||
172 | kfree(hc); | ||
173 | } | ||
174 | } | ||
175 | |||
176 | /* | ||
177 | * devfs stuff. | ||
178 | */ | ||
179 | static int register_with_devfs(struct hash_cell *hc) | ||
180 | { | ||
181 | struct gendisk *disk = dm_disk(hc->md); | ||
182 | |||
183 | devfs_mk_bdev(MKDEV(disk->major, disk->first_minor), | ||
184 | S_IFBLK | S_IRUSR | S_IWUSR | S_IRGRP, | ||
185 | DM_DIR "/%s", hc->name); | ||
186 | return 0; | ||
187 | } | ||
188 | |||
189 | static int unregister_with_devfs(struct hash_cell *hc) | ||
190 | { | ||
191 | devfs_remove(DM_DIR"/%s", hc->name); | ||
192 | return 0; | ||
193 | } | ||
194 | |||
195 | /* | ||
196 | * The kdev_t and uuid of a device can never change once it is | ||
197 | * initially inserted. | ||
198 | */ | ||
199 | static int dm_hash_insert(const char *name, const char *uuid, struct mapped_device *md) | ||
200 | { | ||
201 | struct hash_cell *cell; | ||
202 | |||
203 | /* | ||
204 | * Allocate the new cells. | ||
205 | */ | ||
206 | cell = alloc_cell(name, uuid, md); | ||
207 | if (!cell) | ||
208 | return -ENOMEM; | ||
209 | |||
210 | /* | ||
211 | * Insert the cell into both hash tables. | ||
212 | */ | ||
213 | down_write(&_hash_lock); | ||
214 | if (__get_name_cell(name)) | ||
215 | goto bad; | ||
216 | |||
217 | list_add(&cell->name_list, _name_buckets + hash_str(name)); | ||
218 | |||
219 | if (uuid) { | ||
220 | if (__get_uuid_cell(uuid)) { | ||
221 | list_del(&cell->name_list); | ||
222 | goto bad; | ||
223 | } | ||
224 | list_add(&cell->uuid_list, _uuid_buckets + hash_str(uuid)); | ||
225 | } | ||
226 | register_with_devfs(cell); | ||
227 | dm_get(md); | ||
228 | dm_set_mdptr(md, cell); | ||
229 | up_write(&_hash_lock); | ||
230 | |||
231 | return 0; | ||
232 | |||
233 | bad: | ||
234 | up_write(&_hash_lock); | ||
235 | free_cell(cell); | ||
236 | return -EBUSY; | ||
237 | } | ||
238 | |||
239 | static void __hash_remove(struct hash_cell *hc) | ||
240 | { | ||
241 | /* remove from the dev hash */ | ||
242 | list_del(&hc->uuid_list); | ||
243 | list_del(&hc->name_list); | ||
244 | unregister_with_devfs(hc); | ||
245 | dm_set_mdptr(hc->md, NULL); | ||
246 | dm_put(hc->md); | ||
247 | if (hc->new_map) | ||
248 | dm_table_put(hc->new_map); | ||
249 | free_cell(hc); | ||
250 | } | ||
251 | |||
252 | static void dm_hash_remove_all(void) | ||
253 | { | ||
254 | int i; | ||
255 | struct hash_cell *hc; | ||
256 | struct list_head *tmp, *n; | ||
257 | |||
258 | down_write(&_hash_lock); | ||
259 | for (i = 0; i < NUM_BUCKETS; i++) { | ||
260 | list_for_each_safe (tmp, n, _name_buckets + i) { | ||
261 | hc = list_entry(tmp, struct hash_cell, name_list); | ||
262 | __hash_remove(hc); | ||
263 | } | ||
264 | } | ||
265 | up_write(&_hash_lock); | ||
266 | } | ||
267 | |||
268 | static int dm_hash_rename(const char *old, const char *new) | ||
269 | { | ||
270 | char *new_name, *old_name; | ||
271 | struct hash_cell *hc; | ||
272 | |||
273 | /* | ||
274 | * duplicate new. | ||
275 | */ | ||
276 | new_name = kstrdup(new); | ||
277 | if (!new_name) | ||
278 | return -ENOMEM; | ||
279 | |||
280 | down_write(&_hash_lock); | ||
281 | |||
282 | /* | ||
283 | * Is new free ? | ||
284 | */ | ||
285 | hc = __get_name_cell(new); | ||
286 | if (hc) { | ||
287 | DMWARN("asked to rename to an already existing name %s -> %s", | ||
288 | old, new); | ||
289 | up_write(&_hash_lock); | ||
290 | kfree(new_name); | ||
291 | return -EBUSY; | ||
292 | } | ||
293 | |||
294 | /* | ||
295 | * Is there such a device as 'old' ? | ||
296 | */ | ||
297 | hc = __get_name_cell(old); | ||
298 | if (!hc) { | ||
299 | DMWARN("asked to rename a non existent device %s -> %s", | ||
300 | old, new); | ||
301 | up_write(&_hash_lock); | ||
302 | kfree(new_name); | ||
303 | return -ENXIO; | ||
304 | } | ||
305 | |||
306 | /* | ||
307 | * rename and move the name cell. | ||
308 | */ | ||
309 | unregister_with_devfs(hc); | ||
310 | |||
311 | list_del(&hc->name_list); | ||
312 | old_name = hc->name; | ||
313 | hc->name = new_name; | ||
314 | list_add(&hc->name_list, _name_buckets + hash_str(new_name)); | ||
315 | |||
316 | /* rename the device node in devfs */ | ||
317 | register_with_devfs(hc); | ||
318 | |||
319 | up_write(&_hash_lock); | ||
320 | kfree(old_name); | ||
321 | return 0; | ||
322 | } | ||
323 | |||
324 | /*----------------------------------------------------------------- | ||
325 | * Implementation of the ioctl commands | ||
326 | *---------------------------------------------------------------*/ | ||
327 | /* | ||
328 | * All the ioctl commands get dispatched to functions with this | ||
329 | * prototype. | ||
330 | */ | ||
331 | typedef int (*ioctl_fn)(struct dm_ioctl *param, size_t param_size); | ||
332 | |||
333 | static int remove_all(struct dm_ioctl *param, size_t param_size) | ||
334 | { | ||
335 | dm_hash_remove_all(); | ||
336 | param->data_size = 0; | ||
337 | return 0; | ||
338 | } | ||
339 | |||
340 | /* | ||
341 | * Round up the ptr to an 8-byte boundary. | ||
342 | */ | ||
343 | #define ALIGN_MASK 7 | ||
344 | static inline void *align_ptr(void *ptr) | ||
345 | { | ||
346 | return (void *) (((size_t) (ptr + ALIGN_MASK)) & ~ALIGN_MASK); | ||
347 | } | ||
348 | |||
349 | /* | ||
350 | * Retrieves the data payload buffer from an already allocated | ||
351 | * struct dm_ioctl. | ||
352 | */ | ||
353 | static void *get_result_buffer(struct dm_ioctl *param, size_t param_size, | ||
354 | size_t *len) | ||
355 | { | ||
356 | param->data_start = align_ptr(param + 1) - (void *) param; | ||
357 | |||
358 | if (param->data_start < param_size) | ||
359 | *len = param_size - param->data_start; | ||
360 | else | ||
361 | *len = 0; | ||
362 | |||
363 | return ((void *) param) + param->data_start; | ||
364 | } | ||
365 | |||
366 | static int list_devices(struct dm_ioctl *param, size_t param_size) | ||
367 | { | ||
368 | unsigned int i; | ||
369 | struct hash_cell *hc; | ||
370 | size_t len, needed = 0; | ||
371 | struct gendisk *disk; | ||
372 | struct dm_name_list *nl, *old_nl = NULL; | ||
373 | |||
374 | down_write(&_hash_lock); | ||
375 | |||
376 | /* | ||
377 | * Loop through all the devices working out how much | ||
378 | * space we need. | ||
379 | */ | ||
380 | for (i = 0; i < NUM_BUCKETS; i++) { | ||
381 | list_for_each_entry (hc, _name_buckets + i, name_list) { | ||
382 | needed += sizeof(struct dm_name_list); | ||
383 | needed += strlen(hc->name) + 1; | ||
384 | needed += ALIGN_MASK; | ||
385 | } | ||
386 | } | ||
387 | |||
388 | /* | ||
389 | * Grab our output buffer. | ||
390 | */ | ||
391 | nl = get_result_buffer(param, param_size, &len); | ||
392 | if (len < needed) { | ||
393 | param->flags |= DM_BUFFER_FULL_FLAG; | ||
394 | goto out; | ||
395 | } | ||
396 | param->data_size = param->data_start + needed; | ||
397 | |||
398 | nl->dev = 0; /* Flags no data */ | ||
399 | |||
400 | /* | ||
401 | * Now loop through filling out the names. | ||
402 | */ | ||
403 | for (i = 0; i < NUM_BUCKETS; i++) { | ||
404 | list_for_each_entry (hc, _name_buckets + i, name_list) { | ||
405 | if (old_nl) | ||
406 | old_nl->next = (uint32_t) ((void *) nl - | ||
407 | (void *) old_nl); | ||
408 | disk = dm_disk(hc->md); | ||
409 | nl->dev = huge_encode_dev(MKDEV(disk->major, disk->first_minor)); | ||
410 | nl->next = 0; | ||
411 | strcpy(nl->name, hc->name); | ||
412 | |||
413 | old_nl = nl; | ||
414 | nl = align_ptr(((void *) ++nl) + strlen(hc->name) + 1); | ||
415 | } | ||
416 | } | ||
417 | |||
418 | out: | ||
419 | up_write(&_hash_lock); | ||
420 | return 0; | ||
421 | } | ||
422 | |||
423 | static void list_version_get_needed(struct target_type *tt, void *needed_param) | ||
424 | { | ||
425 | size_t *needed = needed_param; | ||
426 | |||
427 | *needed += strlen(tt->name); | ||
428 | *needed += sizeof(tt->version); | ||
429 | *needed += ALIGN_MASK; | ||
430 | } | ||
431 | |||
432 | static void list_version_get_info(struct target_type *tt, void *param) | ||
433 | { | ||
434 | struct vers_iter *info = param; | ||
435 | |||
436 | /* Check space - it might have changed since the first iteration */ | ||
437 | if ((char *)info->vers + sizeof(tt->version) + strlen(tt->name) + 1 > | ||
438 | info->end) { | ||
439 | |||
440 | info->flags = DM_BUFFER_FULL_FLAG; | ||
441 | return; | ||
442 | } | ||
443 | |||
444 | if (info->old_vers) | ||
445 | info->old_vers->next = (uint32_t) ((void *)info->vers - | ||
446 | (void *)info->old_vers); | ||
447 | info->vers->version[0] = tt->version[0]; | ||
448 | info->vers->version[1] = tt->version[1]; | ||
449 | info->vers->version[2] = tt->version[2]; | ||
450 | info->vers->next = 0; | ||
451 | strcpy(info->vers->name, tt->name); | ||
452 | |||
453 | info->old_vers = info->vers; | ||
454 | info->vers = align_ptr(((void *) ++info->vers) + strlen(tt->name) + 1); | ||
455 | } | ||
456 | |||
457 | static int list_versions(struct dm_ioctl *param, size_t param_size) | ||
458 | { | ||
459 | size_t len, needed = 0; | ||
460 | struct dm_target_versions *vers; | ||
461 | struct vers_iter iter_info; | ||
462 | |||
463 | /* | ||
464 | * Loop through all the devices working out how much | ||
465 | * space we need. | ||
466 | */ | ||
467 | dm_target_iterate(list_version_get_needed, &needed); | ||
468 | |||
469 | /* | ||
470 | * Grab our output buffer. | ||
471 | */ | ||
472 | vers = get_result_buffer(param, param_size, &len); | ||
473 | if (len < needed) { | ||
474 | param->flags |= DM_BUFFER_FULL_FLAG; | ||
475 | goto out; | ||
476 | } | ||
477 | param->data_size = param->data_start + needed; | ||
478 | |||
479 | iter_info.param_size = param_size; | ||
480 | iter_info.old_vers = NULL; | ||
481 | iter_info.vers = vers; | ||
482 | iter_info.flags = 0; | ||
483 | iter_info.end = (char *)vers+len; | ||
484 | |||
485 | /* | ||
486 | * Now loop through filling out the names & versions. | ||
487 | */ | ||
488 | dm_target_iterate(list_version_get_info, &iter_info); | ||
489 | param->flags |= iter_info.flags; | ||
490 | |||
491 | out: | ||
492 | return 0; | ||
493 | } | ||
494 | |||
495 | |||
496 | |||
497 | static int check_name(const char *name) | ||
498 | { | ||
499 | if (strchr(name, '/')) { | ||
500 | DMWARN("invalid device name"); | ||
501 | return -EINVAL; | ||
502 | } | ||
503 | |||
504 | return 0; | ||
505 | } | ||
506 | |||
507 | /* | ||
508 | * Fills in a dm_ioctl structure, ready for sending back to | ||
509 | * userland. | ||
510 | */ | ||
511 | static int __dev_status(struct mapped_device *md, struct dm_ioctl *param) | ||
512 | { | ||
513 | struct gendisk *disk = dm_disk(md); | ||
514 | struct dm_table *table; | ||
515 | struct block_device *bdev; | ||
516 | |||
517 | param->flags &= ~(DM_SUSPEND_FLAG | DM_READONLY_FLAG | | ||
518 | DM_ACTIVE_PRESENT_FLAG); | ||
519 | |||
520 | if (dm_suspended(md)) | ||
521 | param->flags |= DM_SUSPEND_FLAG; | ||
522 | |||
523 | param->dev = huge_encode_dev(MKDEV(disk->major, disk->first_minor)); | ||
524 | |||
525 | if (!(param->flags & DM_SKIP_BDGET_FLAG)) { | ||
526 | bdev = bdget_disk(disk, 0); | ||
527 | if (!bdev) | ||
528 | return -ENXIO; | ||
529 | |||
530 | /* | ||
531 | * Yes, this will be out of date by the time it gets back | ||
532 | * to userland, but it is still very useful for | ||
533 | * debugging. | ||
534 | */ | ||
535 | param->open_count = bdev->bd_openers; | ||
536 | bdput(bdev); | ||
537 | } else | ||
538 | param->open_count = -1; | ||
539 | |||
540 | if (disk->policy) | ||
541 | param->flags |= DM_READONLY_FLAG; | ||
542 | |||
543 | param->event_nr = dm_get_event_nr(md); | ||
544 | |||
545 | table = dm_get_table(md); | ||
546 | if (table) { | ||
547 | param->flags |= DM_ACTIVE_PRESENT_FLAG; | ||
548 | param->target_count = dm_table_get_num_targets(table); | ||
549 | dm_table_put(table); | ||
550 | } else | ||
551 | param->target_count = 0; | ||
552 | |||
553 | return 0; | ||
554 | } | ||
555 | |||
556 | static int dev_create(struct dm_ioctl *param, size_t param_size) | ||
557 | { | ||
558 | int r; | ||
559 | struct mapped_device *md; | ||
560 | |||
561 | r = check_name(param->name); | ||
562 | if (r) | ||
563 | return r; | ||
564 | |||
565 | if (param->flags & DM_PERSISTENT_DEV_FLAG) | ||
566 | r = dm_create_with_minor(MINOR(huge_decode_dev(param->dev)), &md); | ||
567 | else | ||
568 | r = dm_create(&md); | ||
569 | |||
570 | if (r) | ||
571 | return r; | ||
572 | |||
573 | r = dm_hash_insert(param->name, *param->uuid ? param->uuid : NULL, md); | ||
574 | if (r) { | ||
575 | dm_put(md); | ||
576 | return r; | ||
577 | } | ||
578 | |||
579 | param->flags &= ~DM_INACTIVE_PRESENT_FLAG; | ||
580 | |||
581 | r = __dev_status(md, param); | ||
582 | dm_put(md); | ||
583 | |||
584 | return r; | ||
585 | } | ||
586 | |||
587 | /* | ||
588 | * Always use UUID for lookups if it's present, otherwise use name or dev. | ||
589 | */ | ||
590 | static inline struct hash_cell *__find_device_hash_cell(struct dm_ioctl *param) | ||
591 | { | ||
592 | if (*param->uuid) | ||
593 | return __get_uuid_cell(param->uuid); | ||
594 | else if (*param->name) | ||
595 | return __get_name_cell(param->name); | ||
596 | else | ||
597 | return dm_get_mdptr(huge_decode_dev(param->dev)); | ||
598 | } | ||
599 | |||
600 | static inline struct mapped_device *find_device(struct dm_ioctl *param) | ||
601 | { | ||
602 | struct hash_cell *hc; | ||
603 | struct mapped_device *md = NULL; | ||
604 | |||
605 | down_read(&_hash_lock); | ||
606 | hc = __find_device_hash_cell(param); | ||
607 | if (hc) { | ||
608 | md = hc->md; | ||
609 | dm_get(md); | ||
610 | |||
611 | /* | ||
612 | * Sneakily write in both the name and the uuid | ||
613 | * while we have the cell. | ||
614 | */ | ||
615 | strncpy(param->name, hc->name, sizeof(param->name)); | ||
616 | if (hc->uuid) | ||
617 | strncpy(param->uuid, hc->uuid, sizeof(param->uuid)-1); | ||
618 | else | ||
619 | param->uuid[0] = '\0'; | ||
620 | |||
621 | if (hc->new_map) | ||
622 | param->flags |= DM_INACTIVE_PRESENT_FLAG; | ||
623 | else | ||
624 | param->flags &= ~DM_INACTIVE_PRESENT_FLAG; | ||
625 | } | ||
626 | up_read(&_hash_lock); | ||
627 | |||
628 | return md; | ||
629 | } | ||
630 | |||
631 | static int dev_remove(struct dm_ioctl *param, size_t param_size) | ||
632 | { | ||
633 | struct hash_cell *hc; | ||
634 | |||
635 | down_write(&_hash_lock); | ||
636 | hc = __find_device_hash_cell(param); | ||
637 | |||
638 | if (!hc) { | ||
639 | DMWARN("device doesn't appear to be in the dev hash table."); | ||
640 | up_write(&_hash_lock); | ||
641 | return -ENXIO; | ||
642 | } | ||
643 | |||
644 | __hash_remove(hc); | ||
645 | up_write(&_hash_lock); | ||
646 | param->data_size = 0; | ||
647 | return 0; | ||
648 | } | ||
649 | |||
650 | /* | ||
651 | * Check a string doesn't overrun the chunk of | ||
652 | * memory we copied from userland. | ||
653 | */ | ||
654 | static int invalid_str(char *str, void *end) | ||
655 | { | ||
656 | while ((void *) str < end) | ||
657 | if (!*str++) | ||
658 | return 0; | ||
659 | |||
660 | return -EINVAL; | ||
661 | } | ||
662 | |||
663 | static int dev_rename(struct dm_ioctl *param, size_t param_size) | ||
664 | { | ||
665 | int r; | ||
666 | char *new_name = (char *) param + param->data_start; | ||
667 | |||
668 | if (new_name < (char *) (param + 1) || | ||
669 | invalid_str(new_name, (void *) param + param_size)) { | ||
670 | DMWARN("Invalid new logical volume name supplied."); | ||
671 | return -EINVAL; | ||
672 | } | ||
673 | |||
674 | r = check_name(new_name); | ||
675 | if (r) | ||
676 | return r; | ||
677 | |||
678 | param->data_size = 0; | ||
679 | return dm_hash_rename(param->name, new_name); | ||
680 | } | ||
681 | |||
682 | static int do_suspend(struct dm_ioctl *param) | ||
683 | { | ||
684 | int r = 0; | ||
685 | struct mapped_device *md; | ||
686 | |||
687 | md = find_device(param); | ||
688 | if (!md) | ||
689 | return -ENXIO; | ||
690 | |||
691 | if (!dm_suspended(md)) | ||
692 | r = dm_suspend(md); | ||
693 | |||
694 | if (!r) | ||
695 | r = __dev_status(md, param); | ||
696 | |||
697 | dm_put(md); | ||
698 | return r; | ||
699 | } | ||
700 | |||
701 | static int do_resume(struct dm_ioctl *param) | ||
702 | { | ||
703 | int r = 0; | ||
704 | struct hash_cell *hc; | ||
705 | struct mapped_device *md; | ||
706 | struct dm_table *new_map; | ||
707 | |||
708 | down_write(&_hash_lock); | ||
709 | |||
710 | hc = __find_device_hash_cell(param); | ||
711 | if (!hc) { | ||
712 | DMWARN("device doesn't appear to be in the dev hash table."); | ||
713 | up_write(&_hash_lock); | ||
714 | return -ENXIO; | ||
715 | } | ||
716 | |||
717 | md = hc->md; | ||
718 | dm_get(md); | ||
719 | |||
720 | new_map = hc->new_map; | ||
721 | hc->new_map = NULL; | ||
722 | param->flags &= ~DM_INACTIVE_PRESENT_FLAG; | ||
723 | |||
724 | up_write(&_hash_lock); | ||
725 | |||
726 | /* Do we need to load a new map ? */ | ||
727 | if (new_map) { | ||
728 | /* Suspend if it isn't already suspended */ | ||
729 | if (!dm_suspended(md)) | ||
730 | dm_suspend(md); | ||
731 | |||
732 | r = dm_swap_table(md, new_map); | ||
733 | if (r) { | ||
734 | dm_put(md); | ||
735 | dm_table_put(new_map); | ||
736 | return r; | ||
737 | } | ||
738 | |||
739 | if (dm_table_get_mode(new_map) & FMODE_WRITE) | ||
740 | set_disk_ro(dm_disk(md), 0); | ||
741 | else | ||
742 | set_disk_ro(dm_disk(md), 1); | ||
743 | |||
744 | dm_table_put(new_map); | ||
745 | } | ||
746 | |||
747 | if (dm_suspended(md)) | ||
748 | r = dm_resume(md); | ||
749 | |||
750 | if (!r) | ||
751 | r = __dev_status(md, param); | ||
752 | |||
753 | dm_put(md); | ||
754 | return r; | ||
755 | } | ||
756 | |||
757 | /* | ||
758 | * Set or unset the suspension state of a device. | ||
759 | * If the device already is in the requested state we just return its status. | ||
760 | */ | ||
761 | static int dev_suspend(struct dm_ioctl *param, size_t param_size) | ||
762 | { | ||
763 | if (param->flags & DM_SUSPEND_FLAG) | ||
764 | return do_suspend(param); | ||
765 | |||
766 | return do_resume(param); | ||
767 | } | ||
768 | |||
769 | /* | ||
770 | * Copies device info back to user space, used by | ||
771 | * the create and info ioctls. | ||
772 | */ | ||
773 | static int dev_status(struct dm_ioctl *param, size_t param_size) | ||
774 | { | ||
775 | int r; | ||
776 | struct mapped_device *md; | ||
777 | |||
778 | md = find_device(param); | ||
779 | if (!md) | ||
780 | return -ENXIO; | ||
781 | |||
782 | r = __dev_status(md, param); | ||
783 | dm_put(md); | ||
784 | return r; | ||
785 | } | ||
786 | |||
787 | /* | ||
788 | * Build up the status struct for each target | ||
789 | */ | ||
790 | static void retrieve_status(struct dm_table *table, | ||
791 | struct dm_ioctl *param, size_t param_size) | ||
792 | { | ||
793 | unsigned int i, num_targets; | ||
794 | struct dm_target_spec *spec; | ||
795 | char *outbuf, *outptr; | ||
796 | status_type_t type; | ||
797 | size_t remaining, len, used = 0; | ||
798 | |||
799 | outptr = outbuf = get_result_buffer(param, param_size, &len); | ||
800 | |||
801 | if (param->flags & DM_STATUS_TABLE_FLAG) | ||
802 | type = STATUSTYPE_TABLE; | ||
803 | else | ||
804 | type = STATUSTYPE_INFO; | ||
805 | |||
806 | /* Get all the target info */ | ||
807 | num_targets = dm_table_get_num_targets(table); | ||
808 | for (i = 0; i < num_targets; i++) { | ||
809 | struct dm_target *ti = dm_table_get_target(table, i); | ||
810 | |||
811 | remaining = len - (outptr - outbuf); | ||
812 | if (remaining <= sizeof(struct dm_target_spec)) { | ||
813 | param->flags |= DM_BUFFER_FULL_FLAG; | ||
814 | break; | ||
815 | } | ||
816 | |||
817 | spec = (struct dm_target_spec *) outptr; | ||
818 | |||
819 | spec->status = 0; | ||
820 | spec->sector_start = ti->begin; | ||
821 | spec->length = ti->len; | ||
822 | strncpy(spec->target_type, ti->type->name, | ||
823 | sizeof(spec->target_type)); | ||
824 | |||
825 | outptr += sizeof(struct dm_target_spec); | ||
826 | remaining = len - (outptr - outbuf); | ||
827 | if (remaining <= 0) { | ||
828 | param->flags |= DM_BUFFER_FULL_FLAG; | ||
829 | break; | ||
830 | } | ||
831 | |||
832 | /* Get the status/table string from the target driver */ | ||
833 | if (ti->type->status) { | ||
834 | if (ti->type->status(ti, type, outptr, remaining)) { | ||
835 | param->flags |= DM_BUFFER_FULL_FLAG; | ||
836 | break; | ||
837 | } | ||
838 | } else | ||
839 | outptr[0] = '\0'; | ||
840 | |||
841 | outptr += strlen(outptr) + 1; | ||
842 | used = param->data_start + (outptr - outbuf); | ||
843 | |||
844 | outptr = align_ptr(outptr); | ||
845 | spec->next = outptr - outbuf; | ||
846 | } | ||
847 | |||
848 | if (used) | ||
849 | param->data_size = used; | ||
850 | |||
851 | param->target_count = num_targets; | ||
852 | } | ||
853 | |||
854 | /* | ||
855 | * Wait for a device to report an event | ||
856 | */ | ||
857 | static int dev_wait(struct dm_ioctl *param, size_t param_size) | ||
858 | { | ||
859 | int r; | ||
860 | struct mapped_device *md; | ||
861 | struct dm_table *table; | ||
862 | |||
863 | md = find_device(param); | ||
864 | if (!md) | ||
865 | return -ENXIO; | ||
866 | |||
867 | /* | ||
868 | * Wait for a notification event | ||
869 | */ | ||
870 | if (dm_wait_event(md, param->event_nr)) { | ||
871 | r = -ERESTARTSYS; | ||
872 | goto out; | ||
873 | } | ||
874 | |||
875 | /* | ||
876 | * The userland program is going to want to know what | ||
877 | * changed to trigger the event, so we may as well tell | ||
878 | * him and save an ioctl. | ||
879 | */ | ||
880 | r = __dev_status(md, param); | ||
881 | if (r) | ||
882 | goto out; | ||
883 | |||
884 | table = dm_get_table(md); | ||
885 | if (table) { | ||
886 | retrieve_status(table, param, param_size); | ||
887 | dm_table_put(table); | ||
888 | } | ||
889 | |||
890 | out: | ||
891 | dm_put(md); | ||
892 | return r; | ||
893 | } | ||
894 | |||
895 | static inline int get_mode(struct dm_ioctl *param) | ||
896 | { | ||
897 | int mode = FMODE_READ | FMODE_WRITE; | ||
898 | |||
899 | if (param->flags & DM_READONLY_FLAG) | ||
900 | mode = FMODE_READ; | ||
901 | |||
902 | return mode; | ||
903 | } | ||
904 | |||
905 | static int next_target(struct dm_target_spec *last, uint32_t next, void *end, | ||
906 | struct dm_target_spec **spec, char **target_params) | ||
907 | { | ||
908 | *spec = (struct dm_target_spec *) ((unsigned char *) last + next); | ||
909 | *target_params = (char *) (*spec + 1); | ||
910 | |||
911 | if (*spec < (last + 1)) | ||
912 | return -EINVAL; | ||
913 | |||
914 | return invalid_str(*target_params, end); | ||
915 | } | ||
916 | |||
917 | static int populate_table(struct dm_table *table, | ||
918 | struct dm_ioctl *param, size_t param_size) | ||
919 | { | ||
920 | int r; | ||
921 | unsigned int i = 0; | ||
922 | struct dm_target_spec *spec = (struct dm_target_spec *) param; | ||
923 | uint32_t next = param->data_start; | ||
924 | void *end = (void *) param + param_size; | ||
925 | char *target_params; | ||
926 | |||
927 | if (!param->target_count) { | ||
928 | DMWARN("populate_table: no targets specified"); | ||
929 | return -EINVAL; | ||
930 | } | ||
931 | |||
932 | for (i = 0; i < param->target_count; i++) { | ||
933 | |||
934 | r = next_target(spec, next, end, &spec, &target_params); | ||
935 | if (r) { | ||
936 | DMWARN("unable to find target"); | ||
937 | return r; | ||
938 | } | ||
939 | |||
940 | r = dm_table_add_target(table, spec->target_type, | ||
941 | (sector_t) spec->sector_start, | ||
942 | (sector_t) spec->length, | ||
943 | target_params); | ||
944 | if (r) { | ||
945 | DMWARN("error adding target to table"); | ||
946 | return r; | ||
947 | } | ||
948 | |||
949 | next = spec->next; | ||
950 | } | ||
951 | |||
952 | return dm_table_complete(table); | ||
953 | } | ||
954 | |||
955 | static int table_load(struct dm_ioctl *param, size_t param_size) | ||
956 | { | ||
957 | int r; | ||
958 | struct hash_cell *hc; | ||
959 | struct dm_table *t; | ||
960 | |||
961 | r = dm_table_create(&t, get_mode(param), param->target_count); | ||
962 | if (r) | ||
963 | return r; | ||
964 | |||
965 | r = populate_table(t, param, param_size); | ||
966 | if (r) { | ||
967 | dm_table_put(t); | ||
968 | return r; | ||
969 | } | ||
970 | |||
971 | down_write(&_hash_lock); | ||
972 | hc = __find_device_hash_cell(param); | ||
973 | if (!hc) { | ||
974 | DMWARN("device doesn't appear to be in the dev hash table."); | ||
975 | up_write(&_hash_lock); | ||
976 | return -ENXIO; | ||
977 | } | ||
978 | |||
979 | if (hc->new_map) | ||
980 | dm_table_put(hc->new_map); | ||
981 | hc->new_map = t; | ||
982 | param->flags |= DM_INACTIVE_PRESENT_FLAG; | ||
983 | |||
984 | r = __dev_status(hc->md, param); | ||
985 | up_write(&_hash_lock); | ||
986 | return r; | ||
987 | } | ||
988 | |||
989 | static int table_clear(struct dm_ioctl *param, size_t param_size) | ||
990 | { | ||
991 | int r; | ||
992 | struct hash_cell *hc; | ||
993 | |||
994 | down_write(&_hash_lock); | ||
995 | |||
996 | hc = __find_device_hash_cell(param); | ||
997 | if (!hc) { | ||
998 | DMWARN("device doesn't appear to be in the dev hash table."); | ||
999 | up_write(&_hash_lock); | ||
1000 | return -ENXIO; | ||
1001 | } | ||
1002 | |||
1003 | if (hc->new_map) { | ||
1004 | dm_table_put(hc->new_map); | ||
1005 | hc->new_map = NULL; | ||
1006 | } | ||
1007 | |||
1008 | param->flags &= ~DM_INACTIVE_PRESENT_FLAG; | ||
1009 | |||
1010 | r = __dev_status(hc->md, param); | ||
1011 | up_write(&_hash_lock); | ||
1012 | return r; | ||
1013 | } | ||
1014 | |||
1015 | /* | ||
1016 | * Retrieves a list of devices used by a particular dm device. | ||
1017 | */ | ||
1018 | static void retrieve_deps(struct dm_table *table, | ||
1019 | struct dm_ioctl *param, size_t param_size) | ||
1020 | { | ||
1021 | unsigned int count = 0; | ||
1022 | struct list_head *tmp; | ||
1023 | size_t len, needed; | ||
1024 | struct dm_dev *dd; | ||
1025 | struct dm_target_deps *deps; | ||
1026 | |||
1027 | deps = get_result_buffer(param, param_size, &len); | ||
1028 | |||
1029 | /* | ||
1030 | * Count the devices. | ||
1031 | */ | ||
1032 | list_for_each (tmp, dm_table_get_devices(table)) | ||
1033 | count++; | ||
1034 | |||
1035 | /* | ||
1036 | * Check we have enough space. | ||
1037 | */ | ||
1038 | needed = sizeof(*deps) + (sizeof(*deps->dev) * count); | ||
1039 | if (len < needed) { | ||
1040 | param->flags |= DM_BUFFER_FULL_FLAG; | ||
1041 | return; | ||
1042 | } | ||
1043 | |||
1044 | /* | ||
1045 | * Fill in the devices. | ||
1046 | */ | ||
1047 | deps->count = count; | ||
1048 | count = 0; | ||
1049 | list_for_each_entry (dd, dm_table_get_devices(table), list) | ||
1050 | deps->dev[count++] = huge_encode_dev(dd->bdev->bd_dev); | ||
1051 | |||
1052 | param->data_size = param->data_start + needed; | ||
1053 | } | ||
1054 | |||
1055 | static int table_deps(struct dm_ioctl *param, size_t param_size) | ||
1056 | { | ||
1057 | int r = 0; | ||
1058 | struct mapped_device *md; | ||
1059 | struct dm_table *table; | ||
1060 | |||
1061 | md = find_device(param); | ||
1062 | if (!md) | ||
1063 | return -ENXIO; | ||
1064 | |||
1065 | r = __dev_status(md, param); | ||
1066 | if (r) | ||
1067 | goto out; | ||
1068 | |||
1069 | table = dm_get_table(md); | ||
1070 | if (table) { | ||
1071 | retrieve_deps(table, param, param_size); | ||
1072 | dm_table_put(table); | ||
1073 | } | ||
1074 | |||
1075 | out: | ||
1076 | dm_put(md); | ||
1077 | return r; | ||
1078 | } | ||
1079 | |||
1080 | /* | ||
1081 | * Return the status of a device as a text string for each | ||
1082 | * target. | ||
1083 | */ | ||
1084 | static int table_status(struct dm_ioctl *param, size_t param_size) | ||
1085 | { | ||
1086 | int r; | ||
1087 | struct mapped_device *md; | ||
1088 | struct dm_table *table; | ||
1089 | |||
1090 | md = find_device(param); | ||
1091 | if (!md) | ||
1092 | return -ENXIO; | ||
1093 | |||
1094 | r = __dev_status(md, param); | ||
1095 | if (r) | ||
1096 | goto out; | ||
1097 | |||
1098 | table = dm_get_table(md); | ||
1099 | if (table) { | ||
1100 | retrieve_status(table, param, param_size); | ||
1101 | dm_table_put(table); | ||
1102 | } | ||
1103 | |||
1104 | out: | ||
1105 | dm_put(md); | ||
1106 | return r; | ||
1107 | } | ||
1108 | |||
1109 | /* | ||
1110 | * Pass a message to the target that's at the supplied device offset. | ||
1111 | */ | ||
1112 | static int target_message(struct dm_ioctl *param, size_t param_size) | ||
1113 | { | ||
1114 | int r, argc; | ||
1115 | char **argv; | ||
1116 | struct mapped_device *md; | ||
1117 | struct dm_table *table; | ||
1118 | struct dm_target *ti; | ||
1119 | struct dm_target_msg *tmsg = (void *) param + param->data_start; | ||
1120 | |||
1121 | md = find_device(param); | ||
1122 | if (!md) | ||
1123 | return -ENXIO; | ||
1124 | |||
1125 | r = __dev_status(md, param); | ||
1126 | if (r) | ||
1127 | goto out; | ||
1128 | |||
1129 | if (tmsg < (struct dm_target_msg *) (param + 1) || | ||
1130 | invalid_str(tmsg->message, (void *) param + param_size)) { | ||
1131 | DMWARN("Invalid target message parameters."); | ||
1132 | r = -EINVAL; | ||
1133 | goto out; | ||
1134 | } | ||
1135 | |||
1136 | r = dm_split_args(&argc, &argv, tmsg->message); | ||
1137 | if (r) { | ||
1138 | DMWARN("Failed to split target message parameters"); | ||
1139 | goto out; | ||
1140 | } | ||
1141 | |||
1142 | table = dm_get_table(md); | ||
1143 | if (!table) | ||
1144 | goto out_argv; | ||
1145 | |||
1146 | if (tmsg->sector >= dm_table_get_size(table)) { | ||
1147 | DMWARN("Target message sector outside device."); | ||
1148 | r = -EINVAL; | ||
1149 | goto out_table; | ||
1150 | } | ||
1151 | |||
1152 | ti = dm_table_find_target(table, tmsg->sector); | ||
1153 | if (ti->type->message) | ||
1154 | r = ti->type->message(ti, argc, argv); | ||
1155 | else { | ||
1156 | DMWARN("Target type does not support messages"); | ||
1157 | r = -EINVAL; | ||
1158 | } | ||
1159 | |||
1160 | out_table: | ||
1161 | dm_table_put(table); | ||
1162 | out_argv: | ||
1163 | kfree(argv); | ||
1164 | out: | ||
1165 | param->data_size = 0; | ||
1166 | dm_put(md); | ||
1167 | return r; | ||
1168 | } | ||
1169 | |||
1170 | /*----------------------------------------------------------------- | ||
1171 | * Implementation of open/close/ioctl on the special char | ||
1172 | * device. | ||
1173 | *---------------------------------------------------------------*/ | ||
1174 | static ioctl_fn lookup_ioctl(unsigned int cmd) | ||
1175 | { | ||
1176 | static struct { | ||
1177 | int cmd; | ||
1178 | ioctl_fn fn; | ||
1179 | } _ioctls[] = { | ||
1180 | {DM_VERSION_CMD, NULL}, /* version is dealt with elsewhere */ | ||
1181 | {DM_REMOVE_ALL_CMD, remove_all}, | ||
1182 | {DM_LIST_DEVICES_CMD, list_devices}, | ||
1183 | |||
1184 | {DM_DEV_CREATE_CMD, dev_create}, | ||
1185 | {DM_DEV_REMOVE_CMD, dev_remove}, | ||
1186 | {DM_DEV_RENAME_CMD, dev_rename}, | ||
1187 | {DM_DEV_SUSPEND_CMD, dev_suspend}, | ||
1188 | {DM_DEV_STATUS_CMD, dev_status}, | ||
1189 | {DM_DEV_WAIT_CMD, dev_wait}, | ||
1190 | |||
1191 | {DM_TABLE_LOAD_CMD, table_load}, | ||
1192 | {DM_TABLE_CLEAR_CMD, table_clear}, | ||
1193 | {DM_TABLE_DEPS_CMD, table_deps}, | ||
1194 | {DM_TABLE_STATUS_CMD, table_status}, | ||
1195 | |||
1196 | {DM_LIST_VERSIONS_CMD, list_versions}, | ||
1197 | |||
1198 | {DM_TARGET_MSG_CMD, target_message} | ||
1199 | }; | ||
1200 | |||
1201 | return (cmd >= ARRAY_SIZE(_ioctls)) ? NULL : _ioctls[cmd].fn; | ||
1202 | } | ||
1203 | |||
1204 | /* | ||
1205 | * As well as checking the version compatibility this always | ||
1206 | * copies the kernel interface version out. | ||
1207 | */ | ||
1208 | static int check_version(unsigned int cmd, struct dm_ioctl __user *user) | ||
1209 | { | ||
1210 | uint32_t version[3]; | ||
1211 | int r = 0; | ||
1212 | |||
1213 | if (copy_from_user(version, user->version, sizeof(version))) | ||
1214 | return -EFAULT; | ||
1215 | |||
1216 | if ((DM_VERSION_MAJOR != version[0]) || | ||
1217 | (DM_VERSION_MINOR < version[1])) { | ||
1218 | DMWARN("ioctl interface mismatch: " | ||
1219 | "kernel(%u.%u.%u), user(%u.%u.%u), cmd(%d)", | ||
1220 | DM_VERSION_MAJOR, DM_VERSION_MINOR, | ||
1221 | DM_VERSION_PATCHLEVEL, | ||
1222 | version[0], version[1], version[2], cmd); | ||
1223 | r = -EINVAL; | ||
1224 | } | ||
1225 | |||
1226 | /* | ||
1227 | * Fill in the kernel version. | ||
1228 | */ | ||
1229 | version[0] = DM_VERSION_MAJOR; | ||
1230 | version[1] = DM_VERSION_MINOR; | ||
1231 | version[2] = DM_VERSION_PATCHLEVEL; | ||
1232 | if (copy_to_user(user->version, version, sizeof(version))) | ||
1233 | return -EFAULT; | ||
1234 | |||
1235 | return r; | ||
1236 | } | ||
1237 | |||
1238 | static void free_params(struct dm_ioctl *param) | ||
1239 | { | ||
1240 | vfree(param); | ||
1241 | } | ||
1242 | |||
1243 | static int copy_params(struct dm_ioctl __user *user, struct dm_ioctl **param) | ||
1244 | { | ||
1245 | struct dm_ioctl tmp, *dmi; | ||
1246 | |||
1247 | if (copy_from_user(&tmp, user, sizeof(tmp))) | ||
1248 | return -EFAULT; | ||
1249 | |||
1250 | if (tmp.data_size < sizeof(tmp)) | ||
1251 | return -EINVAL; | ||
1252 | |||
1253 | dmi = (struct dm_ioctl *) vmalloc(tmp.data_size); | ||
1254 | if (!dmi) | ||
1255 | return -ENOMEM; | ||
1256 | |||
1257 | if (copy_from_user(dmi, user, tmp.data_size)) { | ||
1258 | vfree(dmi); | ||
1259 | return -EFAULT; | ||
1260 | } | ||
1261 | |||
1262 | *param = dmi; | ||
1263 | return 0; | ||
1264 | } | ||
1265 | |||
1266 | static int validate_params(uint cmd, struct dm_ioctl *param) | ||
1267 | { | ||
1268 | /* Always clear this flag */ | ||
1269 | param->flags &= ~DM_BUFFER_FULL_FLAG; | ||
1270 | |||
1271 | /* Ignores parameters */ | ||
1272 | if (cmd == DM_REMOVE_ALL_CMD || | ||
1273 | cmd == DM_LIST_DEVICES_CMD || | ||
1274 | cmd == DM_LIST_VERSIONS_CMD) | ||
1275 | return 0; | ||
1276 | |||
1277 | if ((cmd == DM_DEV_CREATE_CMD)) { | ||
1278 | if (!*param->name) { | ||
1279 | DMWARN("name not supplied when creating device"); | ||
1280 | return -EINVAL; | ||
1281 | } | ||
1282 | } else if ((*param->uuid && *param->name)) { | ||
1283 | DMWARN("only supply one of name or uuid, cmd(%u)", cmd); | ||
1284 | return -EINVAL; | ||
1285 | } | ||
1286 | |||
1287 | /* Ensure strings are terminated */ | ||
1288 | param->name[DM_NAME_LEN - 1] = '\0'; | ||
1289 | param->uuid[DM_UUID_LEN - 1] = '\0'; | ||
1290 | |||
1291 | return 0; | ||
1292 | } | ||
1293 | |||
1294 | static int ctl_ioctl(struct inode *inode, struct file *file, | ||
1295 | uint command, ulong u) | ||
1296 | { | ||
1297 | int r = 0; | ||
1298 | unsigned int cmd; | ||
1299 | struct dm_ioctl *param; | ||
1300 | struct dm_ioctl __user *user = (struct dm_ioctl __user *) u; | ||
1301 | ioctl_fn fn = NULL; | ||
1302 | size_t param_size; | ||
1303 | |||
1304 | /* only root can play with this */ | ||
1305 | if (!capable(CAP_SYS_ADMIN)) | ||
1306 | return -EACCES; | ||
1307 | |||
1308 | if (_IOC_TYPE(command) != DM_IOCTL) | ||
1309 | return -ENOTTY; | ||
1310 | |||
1311 | cmd = _IOC_NR(command); | ||
1312 | |||
1313 | /* | ||
1314 | * Check the interface version passed in. This also | ||
1315 | * writes out the kernel's interface version. | ||
1316 | */ | ||
1317 | r = check_version(cmd, user); | ||
1318 | if (r) | ||
1319 | return r; | ||
1320 | |||
1321 | /* | ||
1322 | * Nothing more to do for the version command. | ||
1323 | */ | ||
1324 | if (cmd == DM_VERSION_CMD) | ||
1325 | return 0; | ||
1326 | |||
1327 | fn = lookup_ioctl(cmd); | ||
1328 | if (!fn) { | ||
1329 | DMWARN("dm_ctl_ioctl: unknown command 0x%x", command); | ||
1330 | return -ENOTTY; | ||
1331 | } | ||
1332 | |||
1333 | /* | ||
1334 | * Trying to avoid low memory issues when a device is | ||
1335 | * suspended. | ||
1336 | */ | ||
1337 | current->flags |= PF_MEMALLOC; | ||
1338 | |||
1339 | /* | ||
1340 | * Copy the parameters into kernel space. | ||
1341 | */ | ||
1342 | r = copy_params(user, ¶m); | ||
1343 | if (r) { | ||
1344 | current->flags &= ~PF_MEMALLOC; | ||
1345 | return r; | ||
1346 | } | ||
1347 | |||
1348 | /* | ||
1349 | * FIXME: eventually we will remove the PF_MEMALLOC flag | ||
1350 | * here. However the tools still do nasty things like | ||
1351 | * 'load' while a device is suspended. | ||
1352 | */ | ||
1353 | |||
1354 | r = validate_params(cmd, param); | ||
1355 | if (r) | ||
1356 | goto out; | ||
1357 | |||
1358 | param_size = param->data_size; | ||
1359 | param->data_size = sizeof(*param); | ||
1360 | r = fn(param, param_size); | ||
1361 | |||
1362 | /* | ||
1363 | * Copy the results back to userland. | ||
1364 | */ | ||
1365 | if (!r && copy_to_user(user, param, param->data_size)) | ||
1366 | r = -EFAULT; | ||
1367 | |||
1368 | out: | ||
1369 | free_params(param); | ||
1370 | current->flags &= ~PF_MEMALLOC; | ||
1371 | return r; | ||
1372 | } | ||
1373 | |||
1374 | static struct file_operations _ctl_fops = { | ||
1375 | .ioctl = ctl_ioctl, | ||
1376 | .owner = THIS_MODULE, | ||
1377 | }; | ||
1378 | |||
1379 | static struct miscdevice _dm_misc = { | ||
1380 | .minor = MISC_DYNAMIC_MINOR, | ||
1381 | .name = DM_NAME, | ||
1382 | .devfs_name = "mapper/control", | ||
1383 | .fops = &_ctl_fops | ||
1384 | }; | ||
1385 | |||
1386 | /* | ||
1387 | * Create misc character device and link to DM_DIR/control. | ||
1388 | */ | ||
1389 | int __init dm_interface_init(void) | ||
1390 | { | ||
1391 | int r; | ||
1392 | |||
1393 | r = dm_hash_init(); | ||
1394 | if (r) | ||
1395 | return r; | ||
1396 | |||
1397 | r = misc_register(&_dm_misc); | ||
1398 | if (r) { | ||
1399 | DMERR("misc_register failed for control device"); | ||
1400 | dm_hash_exit(); | ||
1401 | return r; | ||
1402 | } | ||
1403 | |||
1404 | DMINFO("%d.%d.%d%s initialised: %s", DM_VERSION_MAJOR, | ||
1405 | DM_VERSION_MINOR, DM_VERSION_PATCHLEVEL, DM_VERSION_EXTRA, | ||
1406 | DM_DRIVER_EMAIL); | ||
1407 | return 0; | ||
1408 | } | ||
1409 | |||
1410 | void dm_interface_exit(void) | ||
1411 | { | ||
1412 | if (misc_deregister(&_dm_misc) < 0) | ||
1413 | DMERR("misc_deregister failed for control device"); | ||
1414 | |||
1415 | dm_hash_exit(); | ||
1416 | } | ||