1 /* $NetBSD: device-mapper.c,v 1.61 2020/07/08 15:07:13 thorpej Exp $ */ 2 3 /* 4 * Copyright (c) 2010 The NetBSD Foundation, Inc. 5 * All rights reserved. 6 * 7 * This code is derived from software contributed to The NetBSD Foundation 8 * by Adam Hamsik. 9 * 10 * Redistribution and use in source and binary forms, with or without 11 * modification, are permitted provided that the following conditions 12 * are met: 13 * 1. Redistributions of source code must retain the above copyright 14 * notice, this list of conditions and the following disclaimer. 15 * 2. Redistributions in binary form must reproduce the above copyright 16 * notice, this list of conditions and the following disclaimer in the 17 * documentation and/or other materials provided with the distribution. 18 * 19 * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS 20 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED 21 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 22 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS 23 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 24 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 25 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 26 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 27 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 28 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 29 * POSSIBILITY OF SUCH DAMAGE. 30 */ 31 32 /* 33 * I want to say thank you to all people who helped me with this project. 34 */ 35 36 #include <sys/types.h> 37 #include <sys/param.h> 38 #include <sys/buf.h> 39 #include <sys/conf.h> 40 #include <sys/device.h> 41 #include <sys/dkio.h> 42 #include <sys/disk.h> 43 #include <sys/disklabel.h> 44 #include <sys/ioctl.h> 45 #include <sys/ioccom.h> 46 #include <sys/kmem.h> 47 #include <sys/kauth.h> 48 49 #include "netbsd-dm.h" 50 #include "dm.h" 51 #include "ioconf.h" 52 53 static dev_type_open(dmopen); 54 static dev_type_close(dmclose); 55 static dev_type_read(dmread); 56 static dev_type_write(dmwrite); 57 static dev_type_ioctl(dmioctl); 58 static dev_type_strategy(dmstrategy); 59 static dev_type_size(dmsize); 60 61 /* attach and detach routines */ 62 #ifdef _MODULE 63 static int dmdestroy(void); 64 #endif 65 66 static void dm_doinit(void); 67 68 static int dm_cmd_to_fun(prop_dictionary_t); 69 static int disk_ioctl_switch(dev_t, unsigned long, void *); 70 static void dmminphys(struct buf *); 71 72 /* CF attach/detach functions used for power management */ 73 static int dm_detach(device_t, int); 74 static void dm_attach(device_t, device_t, void *); 75 static int dm_match(device_t, cfdata_t, void *); 76 77 /* ***Variable-definitions*** */ 78 const struct bdevsw dm_bdevsw = { 79 .d_open = dmopen, 80 .d_close = dmclose, 81 .d_strategy = dmstrategy, 82 .d_ioctl = dmioctl, 83 .d_dump = nodump, 84 .d_psize = dmsize, 85 .d_discard = nodiscard, 86 .d_flag = D_DISK | D_MPSAFE 87 }; 88 89 const struct cdevsw dm_cdevsw = { 90 .d_open = dmopen, 91 .d_close = dmclose, 92 .d_read = dmread, 93 .d_write = dmwrite, 94 .d_ioctl = dmioctl, 95 .d_stop = nostop, 96 .d_tty = notty, 97 .d_poll = nopoll, 98 .d_mmap = nommap, 99 .d_kqfilter = nokqfilter, 100 .d_discard = nodiscard, 101 .d_flag = D_DISK | D_MPSAFE 102 }; 103 104 const struct dkdriver dmdkdriver = { 105 .d_strategy = dmstrategy 106 }; 107 108 CFATTACH_DECL3_NEW(dm, 0, 109 dm_match, dm_attach, dm_detach, NULL, NULL, NULL, 110 DVF_DETACH_SHUTDOWN); 111 112 /* 113 * This structure is used to translate command sent to kernel driver in 114 * <key>command</key> 115 * <value></value> 116 * to function which I can call, and if the command is allowed for 117 * non-superusers. 118 */ 119 /* 120 * This array is used to translate cmd to function pointer. 121 * 122 * Interface between libdevmapper and lvm2tools uses different 123 * names for one IOCTL call because libdevmapper do another thing 124 * then. When I run "info" or "mknodes" libdevmapper will send same 125 * ioctl to kernel but will do another things in userspace. 126 * 127 */ 128 static const struct cmd_function { 129 const char *cmd; 130 int (*fn)(prop_dictionary_t); 131 int allowed; 132 } cmd_fn[] = { 133 { .cmd = "version", .fn = NULL, .allowed = 1 }, 134 { .cmd = "targets", .fn = dm_list_versions_ioctl, .allowed = 1 }, 135 { .cmd = "create", .fn = dm_dev_create_ioctl, .allowed = 0 }, 136 { .cmd = "info", .fn = dm_dev_status_ioctl, .allowed = 1 }, 137 { .cmd = "mknodes", .fn = dm_dev_status_ioctl, .allowed = 1 }, 138 { .cmd = "names", .fn = dm_dev_list_ioctl, .allowed = 1 }, 139 { .cmd = "suspend", .fn = dm_dev_suspend_ioctl, .allowed = 0 }, 140 { .cmd = "remove", .fn = dm_dev_remove_ioctl, .allowed = 0 }, 141 { .cmd = "rename", .fn = dm_dev_rename_ioctl, .allowed = 0 }, 142 { .cmd = "resume", .fn = dm_dev_resume_ioctl, .allowed = 0 }, 143 { .cmd = "clear", .fn = dm_table_clear_ioctl, .allowed = 0 }, 144 { .cmd = "deps", .fn = dm_table_deps_ioctl, .allowed = 1 }, 145 { .cmd = "reload", .fn = dm_table_load_ioctl, .allowed = 0 }, 146 { .cmd = "status", .fn = dm_table_status_ioctl, .allowed = 1 }, 147 { .cmd = "table", .fn = dm_table_status_ioctl, .allowed = 1 }, 148 { .cmd = NULL, .fn = NULL, .allowed = 0 }, 149 }; 150 151 #ifdef _MODULE 152 #include <sys/module.h> 153 154 /* Autoconf defines */ 155 CFDRIVER_DECL(dm, DV_DISK, NULL); 156 157 MODULE(MODULE_CLASS_DRIVER, dm, "dk_subr"); 158 159 /* New module handle routine */ 160 static int 161 dm_modcmd(modcmd_t cmd, void *arg) 162 { 163 #ifdef _MODULE 164 int error; 165 devmajor_t bmajor, cmajor; 166 167 error = 0; 168 bmajor = -1; 169 cmajor = -1; 170 171 switch (cmd) { 172 case MODULE_CMD_INIT: 173 error = config_cfdriver_attach(&dm_cd); 174 if (error) 175 break; 176 177 error = config_cfattach_attach(dm_cd.cd_name, &dm_ca); 178 if (error) { 179 aprint_error("%s: unable to register cfattach\n", 180 dm_cd.cd_name); 181 return error; 182 } 183 184 error = devsw_attach(dm_cd.cd_name, &dm_bdevsw, &bmajor, 185 &dm_cdevsw, &cmajor); 186 if (error == EEXIST) 187 error = 0; 188 if (error) { 189 config_cfattach_detach(dm_cd.cd_name, &dm_ca); 190 config_cfdriver_detach(&dm_cd); 191 break; 192 } 193 dm_doinit(); 194 break; 195 case MODULE_CMD_FINI: 196 /* 197 * Disable unloading of dm module if there are any devices 198 * defined in driver. This is probably too strong we need 199 * to disable auto-unload only if there is mounted dm device 200 * present. 201 */ 202 if (dm_dev_counter > 0) 203 return EBUSY; 204 /* race window here */ 205 206 error = dmdestroy(); 207 if (error) 208 break; 209 210 config_cfdriver_detach(&dm_cd); 211 212 devsw_detach(&dm_bdevsw, &dm_cdevsw); 213 break; 214 case MODULE_CMD_STAT: 215 return ENOTTY; 216 default: 217 return ENOTTY; 218 } 219 220 return error; 221 #else 222 return ENOTTY; 223 #endif 224 } 225 #endif /* _MODULE */ 226 227 /* 228 * dm_match: 229 * 230 * Autoconfiguration match function for pseudo-device glue. 231 */ 232 static int 233 dm_match(device_t parent, cfdata_t match, void *aux) 234 { 235 236 /* Pseudo-device; always present. */ 237 return 1; 238 } 239 240 /* 241 * dm_attach: 242 * 243 * Autoconfiguration attach function for pseudo-device glue. 244 */ 245 static void 246 dm_attach(device_t parent, device_t self, void *aux) 247 { 248 249 if (!pmf_device_register(self, NULL, NULL)) 250 aprint_error_dev(self, "couldn't establish power handler\n"); 251 } 252 253 /* 254 * dm_detach: 255 * 256 * Autoconfiguration detach function for pseudo-device glue. 257 * This routine is called by dm_ioctl::dm_dev_remove_ioctl and by autoconf to 258 * remove devices created in device-mapper. 259 */ 260 static int 261 dm_detach(device_t self, int flags) 262 { 263 dm_dev_t *dmv; 264 265 pmf_device_deregister(self); 266 267 /* Detach device from global device list */ 268 if ((dmv = dm_dev_detach(self)) == NULL) 269 return ENOENT; 270 271 /* Destroy active table first. */ 272 dm_table_destroy(&dmv->table_head, DM_TABLE_ACTIVE); 273 274 /* Destroy inactive table if exits, too. */ 275 dm_table_destroy(&dmv->table_head, DM_TABLE_INACTIVE); 276 277 dm_table_head_destroy(&dmv->table_head); 278 279 /* Destroy disk device structure */ 280 disk_detach(dmv->diskp); 281 disk_destroy(dmv->diskp); 282 283 /* Destroy device */ 284 dm_dev_free(dmv); 285 286 /* Decrement device counter After removing device */ 287 atomic_dec_32(&dm_dev_counter); 288 289 return 0; 290 } 291 292 static void 293 dm_doinit(void) 294 { 295 296 dm_target_init(); 297 dm_dev_init(); 298 dm_pdev_init(); 299 } 300 301 /* attach routine */ 302 void 303 dmattach(int n) 304 { 305 int error; 306 307 error = config_cfattach_attach(dm_cd.cd_name, &dm_ca); 308 if (error) 309 aprint_error("%s: unable to register cfattach\n", 310 dm_cd.cd_name); 311 else 312 dm_doinit(); 313 } 314 315 #ifdef _MODULE 316 /* Destroy routine */ 317 static int 318 dmdestroy(void) 319 { 320 int error; 321 322 error = config_cfattach_detach(dm_cd.cd_name, &dm_ca); 323 if (error) 324 return error; 325 326 dm_dev_destroy(); 327 dm_pdev_destroy(); 328 dm_target_destroy(); 329 330 return 0; 331 } 332 #endif /* _MODULE */ 333 334 static int 335 dmopen(dev_t dev, int flags, int mode, struct lwp *l) 336 { 337 338 aprint_debug("dm open routine called %" PRIu32 "\n", minor(dev)); 339 return 0; 340 } 341 342 static int 343 dmclose(dev_t dev, int flags, int mode, struct lwp *l) 344 { 345 346 aprint_debug("dm close routine called %" PRIu32 "\n", minor(dev)); 347 return 0; 348 } 349 350 351 static int 352 dmioctl(dev_t dev, const u_long cmd, void *data, int flag, struct lwp *l) 353 { 354 int r; 355 prop_dictionary_t dm_dict_in; 356 357 aprint_debug("dmioctl called\n"); 358 KASSERT(data != NULL); 359 360 if ((r = disk_ioctl_switch(dev, cmd, data)) == ENOTTY) { 361 struct plistref *pref = (struct plistref *)data; 362 363 switch(cmd) { 364 case NETBSD_DM_IOCTL: 365 aprint_debug("dm NETBSD_DM_IOCTL called\n"); 366 break; 367 default: 368 aprint_debug("dm unknown ioctl called\n"); 369 return ENOTTY; 370 break; /* NOT REACHED */ 371 } 372 373 if ((r = prop_dictionary_copyin_ioctl(pref, cmd, &dm_dict_in)) 374 != 0) 375 return r; 376 377 if ((r = dm_check_version(dm_dict_in)) != 0) 378 goto cleanup_exit; 379 380 /* run ioctl routine */ 381 if ((r = dm_cmd_to_fun(dm_dict_in)) != 0) 382 goto cleanup_exit; 383 384 cleanup_exit: 385 r = prop_dictionary_copyout_ioctl(pref, cmd, dm_dict_in); 386 prop_object_release(dm_dict_in); 387 } 388 389 return r; 390 } 391 392 /* 393 * Translate command sent from libdevmapper to func. 394 */ 395 static int 396 dm_cmd_to_fun(prop_dictionary_t dm_dict) 397 { 398 int i, r; 399 prop_string_t command; 400 401 if ((command = prop_dictionary_get(dm_dict, DM_IOCTL_COMMAND)) == NULL) 402 return EINVAL; 403 404 for (i = 0; cmd_fn[i].cmd != NULL; i++) 405 if (prop_string_equals_string(command, cmd_fn[i].cmd)) 406 break; 407 408 if (!cmd_fn[i].allowed && 409 (r = kauth_authorize_system(kauth_cred_get(), 410 KAUTH_SYSTEM_DEVMAPPER, 0, NULL, NULL, NULL)) != 0) 411 return r; 412 413 if (cmd_fn[i].cmd == NULL) 414 return EINVAL; 415 416 aprint_debug("ioctl %s called %p\n", cmd_fn[i].cmd, cmd_fn[i].fn); 417 if (cmd_fn[i].fn == NULL) 418 return 0; 419 420 return cmd_fn[i].fn(dm_dict); 421 } 422 423 /* 424 * Check for disk specific ioctls. 425 */ 426 static int 427 disk_ioctl_switch(dev_t dev, unsigned long cmd, void *data) 428 { 429 dm_dev_t *dmv; 430 431 /* disk ioctls make sense only on block devices */ 432 if (minor(dev) == 0) 433 return ENOTTY; 434 435 switch(cmd) { 436 case DIOCGWEDGEINFO: 437 { 438 struct dkwedge_info *dkw = (void *) data; 439 440 if ((dmv = dm_dev_lookup(NULL, NULL, minor(dev))) == NULL) 441 return ENODEV; 442 443 aprint_debug("DIOCGWEDGEINFO ioctl called\n"); 444 445 strlcpy(dkw->dkw_devname, dmv->name, 16); 446 strlcpy(dkw->dkw_wname, dmv->name, DM_NAME_LEN); 447 strlcpy(dkw->dkw_parent, dmv->name, 16); 448 449 dkw->dkw_offset = 0; 450 dm_table_disksize(&dmv->table_head, &dkw->dkw_size, NULL); 451 strcpy(dkw->dkw_ptype, DKW_PTYPE_FFS); 452 453 dm_dev_unbusy(dmv); 454 break; 455 } 456 case DIOCGDISKINFO: 457 { 458 struct plistref *pref = (struct plistref *) data; 459 460 if ((dmv = dm_dev_lookup(NULL, NULL, minor(dev))) == NULL) 461 return ENODEV; 462 463 aprint_debug("DIOCGDISKINFO ioctl called\n"); 464 465 if (dmv->diskp->dk_info == NULL) { 466 dm_dev_unbusy(dmv); 467 return ENOTSUP; 468 } else 469 prop_dictionary_copyout_ioctl(pref, cmd, 470 dmv->diskp->dk_info); 471 472 dm_dev_unbusy(dmv); 473 break; 474 } 475 case DIOCCACHESYNC: 476 { 477 dm_table_entry_t *table_en; 478 dm_table_t *tbl; 479 480 if ((dmv = dm_dev_lookup(NULL, NULL, minor(dev))) == NULL) 481 return ENODEV; 482 483 aprint_debug("DIOCCACHESYNC ioctl called\n"); 484 485 /* Select active table */ 486 tbl = dm_table_get_entry(&dmv->table_head, DM_TABLE_ACTIVE); 487 488 /* 489 * Call sync target routine for all table entries. Target sync 490 * routine basically call DIOCCACHESYNC on underlying devices. 491 */ 492 SLIST_FOREACH(table_en, tbl, next) 493 if (table_en->target->sync) 494 table_en->target->sync(table_en); 495 dm_table_release(&dmv->table_head, DM_TABLE_ACTIVE); 496 dm_dev_unbusy(dmv); 497 break; 498 } 499 case DIOCGSECTORSIZE: 500 { 501 unsigned int secsize, *valp = data; 502 503 if ((dmv = dm_dev_lookup(NULL, NULL, minor(dev))) == NULL) 504 return ENODEV; 505 506 aprint_debug("DIOCGSECTORSIZE ioctl called\n"); 507 508 dm_table_disksize(&dmv->table_head, NULL, &secsize); 509 *valp = secsize; 510 511 dm_dev_unbusy(dmv); 512 break; 513 } 514 case DIOCGMEDIASIZE: 515 { 516 off_t *valp = data; 517 uint64_t numsec; 518 519 if ((dmv = dm_dev_lookup(NULL, NULL, minor(dev))) == NULL) 520 return ENODEV; 521 522 aprint_debug("DIOCGMEDIASIZE ioctl called\n"); 523 524 dm_table_disksize(&dmv->table_head, &numsec, NULL); 525 *valp = numsec; 526 527 dm_dev_unbusy(dmv); 528 break; 529 } 530 default: 531 aprint_debug("unknown disk_ioctl called\n"); 532 return ENOTTY; 533 break; /* NOT REACHED */ 534 } 535 536 return 0; 537 } 538 539 /* 540 * Do all IO operations on dm logical devices. 541 */ 542 static void 543 dmstrategy(struct buf *bp) 544 { 545 dm_dev_t *dmv; 546 dm_table_t *tbl; 547 dm_table_entry_t *table_en; 548 struct buf *nestbuf; 549 550 uint64_t buf_start, buf_len, issued_len; 551 uint64_t table_start, table_end; 552 uint64_t start, end; 553 554 buf_start = bp->b_blkno * DEV_BSIZE; 555 buf_len = bp->b_bcount; 556 557 table_end = 0; 558 issued_len = 0; 559 560 if ((dmv = dm_dev_lookup(NULL, NULL, minor(bp->b_dev))) == NULL) { 561 bp->b_error = EIO; 562 bp->b_resid = bp->b_bcount; 563 biodone(bp); 564 return; 565 } 566 567 if (bounds_check_with_mediasize(bp, DEV_BSIZE, 568 dm_table_size(&dmv->table_head)) <= 0) { 569 dm_dev_unbusy(dmv); 570 bp->b_resid = bp->b_bcount; 571 biodone(bp); 572 return; 573 } 574 575 /* 576 * disk(9) is part of device structure and it can't be used without 577 * mutual exclusion, use diskp_mtx until it will be fixed. 578 */ 579 mutex_enter(&dmv->diskp_mtx); 580 disk_busy(dmv->diskp); 581 mutex_exit(&dmv->diskp_mtx); 582 583 /* Select active table */ 584 tbl = dm_table_get_entry(&dmv->table_head, DM_TABLE_ACTIVE); 585 586 /* Nested buffers count down to zero therefore I have 587 to set bp->b_resid to maximal value. */ 588 bp->b_resid = bp->b_bcount; 589 590 /* 591 * Find out what tables I want to select. 592 */ 593 SLIST_FOREACH(table_en, tbl, next) { 594 /* I need number of bytes not blocks. */ 595 table_start = table_en->start * DEV_BSIZE; 596 /* 597 * I have to sub 1 from table_en->length to prevent 598 * off by one error 599 */ 600 table_end = table_start + table_en->length * DEV_BSIZE; 601 start = MAX(table_start, buf_start); 602 end = MIN(table_end, buf_start + buf_len); 603 604 aprint_debug("----------------------------------------\n"); 605 aprint_debug("table_start %010" PRIu64", table_end %010" 606 PRIu64 "\n", table_start, table_end); 607 aprint_debug("buf_start %010" PRIu64", buf_len %010" 608 PRIu64"\n", buf_start, buf_len); 609 aprint_debug("start-buf_start %010"PRIu64", end %010" 610 PRIu64"\n", start - buf_start, end); 611 aprint_debug("start %010" PRIu64", end %010" 612 PRIu64"\n", start, end); 613 aprint_debug("----------------------------------------\n"); 614 615 if (start < end) { 616 /* create nested buffer */ 617 nestbuf = getiobuf(NULL, true); 618 nestiobuf_setup(bp, nestbuf, start - buf_start, 619 end - start); 620 issued_len += end - start; 621 /* I need number of blocks. */ 622 nestbuf->b_blkno = (start - table_start) / DEV_BSIZE; 623 table_en->target->strategy(table_en, nestbuf); 624 } 625 } 626 627 if (issued_len < buf_len) 628 nestiobuf_done(bp, buf_len - issued_len, EINVAL); 629 630 mutex_enter(&dmv->diskp_mtx); 631 disk_unbusy(dmv->diskp, buf_len, bp ? (bp->b_flags & B_READ) : 0); 632 mutex_exit(&dmv->diskp_mtx); 633 634 dm_table_release(&dmv->table_head, DM_TABLE_ACTIVE); 635 dm_dev_unbusy(dmv); 636 } 637 638 639 static int 640 dmread(dev_t dev, struct uio *uio, int flag) 641 { 642 643 return physio(dmstrategy, NULL, dev, B_READ, dmminphys, uio); 644 } 645 646 static int 647 dmwrite(dev_t dev, struct uio *uio, int flag) 648 { 649 650 return physio(dmstrategy, NULL, dev, B_WRITE, dmminphys, uio); 651 } 652 653 static int 654 dmsize(dev_t dev) 655 { 656 dm_dev_t *dmv; 657 uint64_t size; 658 659 if ((dmv = dm_dev_lookup(NULL, NULL, minor(dev))) == NULL) 660 return -ENOENT; 661 662 size = dm_table_size(&dmv->table_head); 663 dm_dev_unbusy(dmv); 664 665 return size; 666 } 667 668 static void 669 dmminphys(struct buf *bp) 670 { 671 672 bp->b_bcount = MIN(bp->b_bcount, MAXPHYS); 673 } 674 675 void 676 dmgetproperties(struct disk *disk, dm_table_head_t *head) 677 { 678 uint64_t numsec; 679 unsigned int secsize; 680 struct disk_geom *dg; 681 682 dm_table_disksize(head, &numsec, &secsize); 683 684 dg = &disk->dk_geom; 685 686 memset(dg, 0, sizeof(*dg)); 687 dg->dg_secperunit = numsec; 688 dg->dg_secsize = secsize; 689 dg->dg_nsectors = 32; 690 dg->dg_ntracks = 64; 691 692 disk_set_info(NULL, disk, "ESDI"); 693 } 694 695 /* 696 * Transform char s to uint64_t offset number. 697 */ 698 uint64_t 699 atoi64(const char *s) 700 { 701 uint64_t n; 702 n = 0; 703 704 while (*s != '\0') { 705 if (!isdigit(*s)) 706 break; 707 708 n = (10 * n) + (*s - '0'); 709 s++; 710 } 711 712 return n; 713 } 714