1 /*- 2 * Copyright (c) 2017 Netflix, Inc. 3 * 4 * Redistribution and use in source and binary forms, with or without 5 * modification, are permitted provided that the following conditions 6 * are met: 7 * 1. Redistributions of source code must retain the above copyright 8 * notice, this list of conditions and the following disclaimer. 9 * 2. Redistributions in binary form must reproduce the above copyright 10 * notice, this list of conditions and the following disclaimer in the 11 * documentation and/or other materials provided with the distribution. 12 * 13 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 14 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 15 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 16 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 17 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 18 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 19 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 20 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 21 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 22 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 23 * SUCH DAMAGE. 24 */ 25 26 #include <sys/cdefs.h> 27 __FBSDID("$FreeBSD$"); 28 29 #include <sys/param.h> 30 #include <sys/ucred.h> 31 #include <sys/mount.h> 32 33 #undef MAX 34 #undef MIN 35 36 #include <assert.h> 37 #include <efivar.h> 38 #include <errno.h> 39 #include <libgeom.h> 40 #include <paths.h> 41 #include <stdio.h> 42 #include <string.h> 43 44 #include "efichar.h" 45 46 #include "efi-osdep.h" 47 #include "efivar-dp.h" 48 49 #include "uefi-dplib.h" 50 51 #define MAX_DP_SANITY 4096 /* Biggest device path in bytes */ 52 #define MAX_DP_TEXT_LEN 4096 /* Longest string rep of dp */ 53 54 #define ValidLen(dp) (DevicePathNodeLength(dp) >= sizeof(EFI_DEVICE_PATH_PROTOCOL) && \ 55 DevicePathNodeLength(dp) < MAX_DP_SANITY) 56 57 #define G_PART "PART" 58 #define G_LABEL "LABEL" 59 #define G_DISK "DISK" 60 61 static const char * 62 geom_pp_attr(struct gmesh *mesh, struct gprovider *pp, const char *attr) 63 { 64 struct gconfig *conf; 65 66 LIST_FOREACH(conf, &pp->lg_config, lg_config) { 67 if (strcmp(conf->lg_name, attr) != 0) 68 continue; 69 return (conf->lg_val); 70 } 71 return (NULL); 72 } 73 74 static struct gprovider * 75 find_provider_by_efimedia(struct gmesh *mesh, const char *efimedia) 76 { 77 struct gclass *classp; 78 struct ggeom *gp; 79 struct gprovider *pp; 80 const char *val; 81 82 /* 83 * Find the partition class so we can search it... 84 */ 85 LIST_FOREACH(classp, &mesh->lg_class, lg_class) { 86 if (strcasecmp(classp->lg_name, G_PART) == 0) 87 break; 88 } 89 if (classp == NULL) 90 return (NULL); 91 92 /* 93 * Each geom will have a number of providers, search each 94 * one of them for the efimedia that matches. 95 */ 96 /* XXX just used gpart class since I know it's the only one, but maybe I should search all classes */ 97 LIST_FOREACH(gp, &classp->lg_geom, lg_geom) { 98 LIST_FOREACH(pp, &gp->lg_provider, lg_provider) { 99 val = geom_pp_attr(mesh, pp, "efimedia"); 100 if (val == NULL) 101 continue; 102 if (strcasecmp(efimedia, val) == 0) 103 return (pp); 104 } 105 } 106 107 return (NULL); 108 } 109 110 static struct gprovider * 111 find_provider_by_name(struct gmesh *mesh, const char *name) 112 { 113 struct gclass *classp; 114 struct ggeom *gp; 115 struct gprovider *pp; 116 117 LIST_FOREACH(classp, &mesh->lg_class, lg_class) { 118 LIST_FOREACH(gp, &classp->lg_geom, lg_geom) { 119 LIST_FOREACH(pp, &gp->lg_provider, lg_provider) { 120 if (strcmp(pp->lg_name, name) == 0) 121 return (pp); 122 } 123 } 124 } 125 126 return (NULL); 127 } 128 129 130 static int 131 efi_hd_to_unix(struct gmesh *mesh, const_efidp dp, char **dev, char **relpath, char **abspath) 132 { 133 int rv = 0, n, i; 134 const_efidp media, file, walker; 135 size_t len, mntlen; 136 char buf[MAX_DP_TEXT_LEN]; 137 char *pwalk; 138 struct gprovider *pp, *provider; 139 struct gconsumer *cp; 140 struct statfs *mnt; 141 142 walker = media = dp; 143 144 /* 145 * Now, we can either have a filepath node next, or the end. 146 * Otherwise, it's an error. 147 */ 148 if (!ValidLen(walker)) 149 return (EINVAL); 150 walker = (const_efidp)NextDevicePathNode(walker); 151 if ((uintptr_t)walker - (uintptr_t)dp > MAX_DP_SANITY) 152 return (EINVAL); 153 if (DevicePathType(walker) == MEDIA_DEVICE_PATH && 154 DevicePathSubType(walker) == MEDIA_FILEPATH_DP) 155 file = walker; 156 else if (DevicePathType(walker) == MEDIA_DEVICE_PATH && 157 DevicePathType(walker) == END_DEVICE_PATH_TYPE) 158 file = NULL; 159 else 160 return (EINVAL); 161 162 /* 163 * Format this node. We're going to look for it as a efimedia 164 * attribute of some geom node. Once we find that node, we use it 165 * as the device it comes from, at least provisionally. 166 */ 167 len = efidp_format_device_path_node(buf, sizeof(buf), media); 168 if (len > sizeof(buf)) 169 return (EINVAL); 170 171 pp = find_provider_by_efimedia(mesh, buf); 172 if (pp == NULL) { 173 rv = ENOENT; 174 goto errout; 175 } 176 177 *dev = strdup(pp->lg_name); 178 if (*dev == NULL) { 179 rv = ENOMEM; 180 goto errout; 181 } 182 183 /* 184 * No file specified, just return the device. Don't even look 185 * for a mountpoint. XXX Sane? 186 */ 187 if (file == NULL) 188 goto errout; 189 190 /* 191 * Now extract the relative path. The next node in the device path should 192 * be a filesystem node. If not, we have issues. 193 */ 194 *relpath = efidp_extract_file_path(file); 195 if (*relpath == NULL) { 196 rv = ENOMEM; 197 goto errout; 198 } 199 for (pwalk = *relpath; *pwalk; pwalk++) 200 if (*pwalk == '\\') 201 *pwalk = '/'; 202 203 /* 204 * To find the absolute path, we have to look for where we're mounted. 205 * We only look a little hard, since looking too hard can come up with 206 * false positives (imagine a graid, one of whose devices is *dev). 207 */ 208 n = getfsstat(NULL, 0, MNT_NOWAIT) + 1; 209 if (n < 0) { 210 rv = errno; 211 goto errout; 212 } 213 mntlen = sizeof(struct statfs) * n; 214 mnt = malloc(mntlen); 215 n = getfsstat(mnt, mntlen, MNT_NOWAIT); 216 if (n < 0) { 217 rv = errno; 218 goto errout; 219 } 220 provider = pp; 221 for (i = 0; i < n; i++) { 222 /* 223 * Skip all pseudo filesystems. This also skips the real filesytsem 224 * of ZFS. There's no EFI designator for ZFS in the standard, so 225 * we'll need to invent one, but its decoding will be handled in 226 * a separate function. 227 */ 228 if (mnt[i].f_mntfromname[0] != '/') 229 continue; 230 231 /* 232 * First see if it is directly attached 233 */ 234 if (strcmp(provider->lg_name, mnt[i].f_mntfromname + 5) == 0) 235 break; 236 237 /* 238 * Next see if it is attached via one of the physical disk's 239 * labels. 240 */ 241 LIST_FOREACH(cp, &provider->lg_consumers, lg_consumer) { 242 pp = cp->lg_provider; 243 if (strcmp(pp->lg_geom->lg_class->lg_name, G_LABEL) != 0) 244 continue; 245 if (strcmp(g_device_path(pp->lg_name), mnt[i].f_mntfromname) == 0) 246 goto break2; 247 } 248 /* Not the one, try the next mount point */ 249 } 250 break2: 251 252 /* 253 * No mountpoint found, no absolute path possible 254 */ 255 if (i >= n) 256 goto errout; 257 258 /* 259 * Construct absolute path and we're finally done. 260 */ 261 if (strcmp(mnt[i].f_mntonname, "/") == 0) 262 asprintf(abspath, "/%s", *relpath); 263 else 264 asprintf(abspath, "%s/%s", mnt[i].f_mntonname, *relpath); 265 266 errout: 267 if (rv != 0) { 268 free(*dev); 269 *dev = NULL; 270 free(*relpath); 271 *relpath = NULL; 272 } 273 return (rv); 274 } 275 276 /* 277 * Translate the passed in device_path to a unix path via the following 278 * algorithm. 279 * 280 * If dp, dev or path NULL, return EDOOFUS. XXX wise? 281 * 282 * Set *path = NULL; *dev = NULL; 283 * 284 * Walk through the device_path until we find either a media device path. 285 * Return EINVAL if not found. Return EINVAL if walking dp would 286 * land us more than sanity size away from the start (4k). 287 * 288 * If we find a media descriptor, we search through the geom mesh to see if we 289 * can find a matching node. If no match is found in the mesh that matches, 290 * return ENXIO. 291 * 292 * Once we find a matching node, we search to see if there is a filesystem 293 * mounted on it. If we find nothing, then search each of the devices that are 294 * mounted to see if we can work up the geom tree to find the matching node. if 295 * we still can't find anything, *dev = sprintf("/dev/%s", provider_name 296 * of the original node we found), but return ENOTBLK. 297 * 298 * Record the dev of the mountpoint in *dev. 299 * 300 * Once we find something, check to see if the next node in the device path is 301 * the end of list. If so, return the mountpoint. 302 * 303 * If the next node isn't a File path node, return EFTYPE. 304 * 305 * Extract the path from the File path node(s). translate any \ file separators 306 * to /. Append the result to the mount point. Copy the resulting path into 307 * *path. Stat that path. If it is not found, return the errorr from stat. 308 * 309 * Finally, check to make sure the resulting path is still on the same 310 * device. If not, return ENODEV. 311 * 312 * Otherwise return 0. 313 * 314 * The dev or full path that's returned is malloced, so needs to be freed when 315 * the caller is done about it. Unlike many other functions, we can return data 316 * with an error code, so pay attention. 317 */ 318 int 319 efivar_device_path_to_unix_path(const_efidp dp, char **dev, char **relpath, char **abspath) 320 { 321 const_efidp walker; 322 struct gmesh mesh; 323 int rv = 0; 324 325 /* 326 * Sanity check args, fail early 327 */ 328 if (dp == NULL || dev == NULL || relpath == NULL || abspath == NULL) 329 return (EDOOFUS); 330 331 *dev = NULL; 332 *relpath = NULL; 333 *abspath = NULL; 334 335 /* 336 * Find the first media device path we can. If we go too far, 337 * assume the passed in device path is bogus. If we hit the end 338 * then we didn't find a media device path, so signal that error. 339 */ 340 walker = dp; 341 if (!ValidLen(walker)) 342 return (EINVAL); 343 while (DevicePathType(walker) != MEDIA_DEVICE_PATH && 344 DevicePathType(walker) != END_DEVICE_PATH_TYPE) { 345 walker = (const_efidp)NextDevicePathNode(walker); 346 if ((uintptr_t)walker - (uintptr_t)dp > MAX_DP_SANITY) 347 return (EINVAL); 348 if (!ValidLen(walker)) 349 return (EINVAL); 350 } 351 if (DevicePathType(walker) != MEDIA_DEVICE_PATH) 352 return (EINVAL); 353 354 /* 355 * There's several types of media paths. We're only interested in the 356 * hard disk path, as it's really the only relevant one to booting. The 357 * CD path just might also be relevant, and would be easy to add, but 358 * isn't supported. A file path too is relevant, but at this stage, it's 359 * premature because we're trying to translate a specification for a device 360 * and path on that device into a unix path, or at the very least, a 361 * geom device : path-on-device. 362 * 363 * Also, ZFS throws a bit of a monkey wrench in here since it doesn't have 364 * a device path type (it creates a new virtual device out of one or more 365 * storage devices). 366 * 367 * For all of them, we'll need to know the geoms, so allocate / free the 368 * geom mesh here since it's safer than doing it in each sub-function 369 * which may have many error exits. 370 */ 371 if (geom_gettree(&mesh)) 372 return (ENOMEM); 373 374 rv = EINVAL; 375 if (DevicePathSubType(walker) == MEDIA_HARDDRIVE_DP) 376 rv = efi_hd_to_unix(&mesh, walker, dev, relpath, abspath); 377 #ifdef notyet 378 else if (is_cdrom_device(walker)) 379 rv = efi_cdrom_to_unix(&mesh, walker, dev, relpath, abspath); 380 else if (is_floppy_device(walker)) 381 rv = efi_floppy_to_unix(&mesh, walker, dev, relpath, abspath); 382 else if (is_zpool_device(walker)) 383 rv = efi_zpool_to_unix(&mesh, walker, dev, relpath, abspath); 384 #endif 385 geom_deletetree(&mesh); 386 387 return (rv); 388 } 389 390 /* 391 * Construct the EFI path to a current unix path as follows. 392 * 393 * The path may be of one of three forms: 394 * 1) /path/to/file -- full path to a file. The file need not be present, 395 * but /path/to must be. It must reside on a local filesystem 396 * mounted on a GPT or MBR partition. 397 * 2) //path/to/file -- Shorthand for 'On the EFI partition, \path\to\file' 398 * where 'The EFI Partition' is a partition that's type is 'efi' 399 * on the same disk that / is mounted from. If there are multiple 400 * or no 'efi' parittions on that disk, or / isn't on a disk that 401 * we can trace back to a physical device, an error will result 402 * 3) [/dev/]geom-name:/path/to/file -- Use the specified partition 403 * (and it must be a GPT or MBR partition) with the specified 404 * path. The latter is not authenticated. 405 * all path forms translate any \ characters to / before further processing. 406 * When a file path node is created, all / characters are translated back 407 * to \. 408 * 409 * For paths of the first form: 410 * find where the filesystem is mount (either the file directly, or 411 * its parent directory). 412 * translate any logical device name (eg lable) to a physical one 413 * If not possible, return ENXIO 414 * If the physical path is unsupported (Eg not on a GPT or MBR disk), 415 * return ENXIO 416 * Create a media device path node. 417 * append the relative path from the mountpoint to the media device node 418 * as a file path. 419 * 420 * For paths matching the second form: 421 * find the EFI partition corresponding to the root fileystem. 422 * If none found, return ENXIO 423 * Create a media device path node for the found partition 424 * Append a File Path to the end for the rest of the file. 425 * 426 * For paths of the third form 427 * Translate the geom-name passed in into a physical partition 428 * name. 429 * Return ENXIO if the translation fails 430 * Make a media device path for it 431 * append the part after the : as a File path node. 432 */ 433 434 static char * 435 path_to_file_dp(const char *relpath) 436 { 437 char *rv; 438 439 asprintf(&rv, "File(%s)", relpath); 440 return rv; 441 } 442 443 static char * 444 find_geom_efi_on_root(struct gmesh *mesh) 445 { 446 struct statfs buf; 447 const char *dev; 448 struct gprovider *pp; 449 // struct ggeom *disk; 450 struct gconsumer *cp; 451 452 /* 453 * Find /'s geom. Assume it's mounted on /dev/ and filter out all the 454 * filesystems that aren't. 455 */ 456 if (statfs("/", &buf) != 0) 457 return (NULL); 458 dev = buf.f_mntfromname; 459 if (*dev != '/' || strncmp(dev, _PATH_DEV, sizeof(_PATH_DEV) - 1) != 0) 460 return (NULL); 461 dev += sizeof(_PATH_DEV) -1; 462 pp = find_provider_by_name(mesh, dev); 463 if (pp == NULL) 464 return (NULL); 465 466 /* 467 * If the provider is a LABEL, find it's outer PART class, if any. We 468 * only operate on partitions. 469 */ 470 if (strcmp(pp->lg_geom->lg_class->lg_name, G_LABEL) == 0) { 471 LIST_FOREACH(cp, &pp->lg_consumers, lg_consumer) { 472 if (strcmp(cp->lg_provider->lg_geom->lg_class->lg_name, G_PART) == 0) { 473 pp = cp->lg_provider; 474 break; 475 } 476 } 477 } 478 if (strcmp(pp->lg_geom->lg_class->lg_name, G_PART) != 0) 479 return (NULL); 480 481 #if 0 482 /* This doesn't work because we can't get the data to walk UP the tree it seems */ 483 484 /* 485 * Now that we've found the PART that we have mounted as root, find the 486 * first efi typed partition that's a peer, if any. 487 */ 488 LIST_FOREACH(cp, &pp->lg_consumers, lg_consumer) { 489 if (strcmp(cp->lg_provider->lg_geom->lg_class->lg_name, G_DISK) == 0) { 490 disk = cp->lg_provider->lg_geom; 491 break; 492 } 493 } 494 if (disk == NULL) /* This is very bad -- old nested partitions -- no support ? */ 495 return (NULL); 496 #endif 497 498 #if 0 499 /* This doesn't work because we can't get the data to walk UP the tree it seems */ 500 501 /* 502 * With the disk provider, we can look for its consumers to see if any are the proper type. 503 */ 504 LIST_FOREACH(pp, &disk->lg_consumer, lg_consumer) { 505 type = geom_pp_attr(mesh, pp, "type"); 506 if (type == NULL) 507 continue; 508 if (strcmp(type, "efi") != 0) 509 continue; 510 efimedia = geom_pp_attr(mesh, pp, "efimedia"); 511 if (efimedia == NULL) 512 return (NULL); 513 return strdup(efimedia); 514 } 515 #endif 516 return (NULL); 517 } 518 519 520 static char * 521 find_geom_efimedia(struct gmesh *mesh, const char *dev) 522 { 523 struct gprovider *pp; 524 const char *efimedia; 525 526 pp = find_provider_by_name(mesh, dev); 527 if (pp == NULL) 528 return (NULL); 529 efimedia = geom_pp_attr(mesh, pp, "efimedia"); 530 if (efimedia == NULL) 531 return (NULL); 532 return strdup(efimedia); 533 } 534 535 static int 536 build_dp(const char *efimedia, const char *relpath, efidp *dp) 537 { 538 char *fp, *dptxt = NULL, *cp, *rp; 539 int rv = 0; 540 efidp out = NULL; 541 size_t len; 542 543 rp = strdup(relpath); 544 for (cp = rp; *cp; cp++) 545 if (*cp == '/') 546 *cp = '\\'; 547 fp = path_to_file_dp(rp); 548 free(rp); 549 if (fp == NULL) { 550 rv = ENOMEM; 551 goto errout; 552 } 553 554 asprintf(&dptxt, "%s/%s", efimedia, fp); 555 out = malloc(8192); 556 len = efidp_parse_device_path(dptxt, out, 8192); 557 if (len > 8192) { 558 rv = ENOMEM; 559 goto errout; 560 } 561 if (len == 0) { 562 rv = EINVAL; 563 goto errout; 564 } 565 566 *dp = out; 567 errout: 568 if (rv) { 569 free(out); 570 } 571 free(dptxt); 572 free(fp); 573 574 return rv; 575 } 576 577 /* Handles //path/to/file */ 578 /* 579 * Which means: find the disk that has /. Then look for a EFI partition 580 * and use that for the efimedia and /path/to/file as relative to that. 581 * Not sure how ZFS will work here since we can't easily make the leap 582 * to the geom from the zpool. 583 */ 584 static int 585 efipart_to_dp(struct gmesh *mesh, char *path, efidp *dp) 586 { 587 char *efimedia = NULL; 588 int rv; 589 590 efimedia = find_geom_efi_on_root(mesh); 591 #ifdef notyet 592 if (efimedia == NULL) 593 efimedia = find_efi_on_zfsroot(dev); 594 #endif 595 if (efimedia == NULL) { 596 rv = ENOENT; 597 goto errout; 598 } 599 600 rv = build_dp(efimedia, path + 1, dp); 601 errout: 602 free(efimedia); 603 604 return rv; 605 } 606 607 /* Handles [/dev/]geom:[/]path/to/file */ 608 /* Handles zfs-dataset:[/]path/to/file (this may include / ) */ 609 static int 610 dev_path_to_dp(struct gmesh *mesh, char *path, efidp *dp) 611 { 612 char *relpath, *dev, *efimedia = NULL; 613 int rv = 0; 614 615 relpath = strchr(path, ':'); 616 assert(relpath != NULL); 617 *relpath++ = '\0'; 618 619 dev = path; 620 if (strncmp(dev, _PATH_DEV, sizeof(_PATH_DEV) - 1) == 0) 621 dev += sizeof(_PATH_DEV) -1; 622 623 efimedia = find_geom_efimedia(mesh, dev); 624 #ifdef notyet 625 if (efimedia == NULL) 626 find_zfs_efi_media(dev); 627 #endif 628 if (efimedia == NULL) { 629 rv = ENOENT; 630 goto errout; 631 } 632 rv = build_dp(efimedia, relpath, dp); 633 errout: 634 free(efimedia); 635 636 return rv; 637 } 638 639 /* Handles /path/to/file */ 640 static int 641 path_to_dp(struct gmesh *mesh, char *path, efidp *dp) 642 { 643 struct statfs buf; 644 char *rp = NULL, *ep, *dev, *efimedia = NULL; 645 int rv = 0; 646 647 rp = realpath(path, NULL); 648 if (rp == NULL) { 649 rv = errno; 650 goto errout; 651 } 652 653 if (statfs(rp, &buf) != 0) { 654 rv = errno; 655 goto errout; 656 } 657 658 dev = buf.f_mntfromname; 659 if (strncmp(dev, _PATH_DEV, sizeof(_PATH_DEV) - 1) == 0) 660 dev += sizeof(_PATH_DEV) -1; 661 ep = rp + strlen(buf.f_mntonname); 662 663 efimedia = find_geom_efimedia(mesh, dev); 664 #ifdef notyet 665 if (efimedia == NULL) 666 find_zfs_efi_media(dev); 667 #endif 668 if (efimedia == NULL) { 669 rv = ENOENT; 670 goto errout; 671 } 672 673 rv = build_dp(efimedia, ep, dp); 674 errout: 675 free(efimedia); 676 free(rp); 677 if (rv != 0) { 678 free(*dp); 679 *dp = NULL; 680 } 681 return (rv); 682 } 683 684 int 685 efivar_unix_path_to_device_path(const char *path, efidp *dp) 686 { 687 char *modpath = NULL, *cp; 688 int rv = ENOMEM; 689 struct gmesh mesh; 690 691 /* 692 * Fail early for clearly bogus things 693 */ 694 if (path == NULL || dp == NULL) 695 return (EDOOFUS); 696 697 /* 698 * We'll need the goem mesh to grovel through it to find the 699 * efimedia attribute for any devices we find. Grab it here 700 * and release it to simplify the error paths out of the 701 * subordinate functions 702 */ 703 if (geom_gettree(&mesh)) 704 return (errno); 705 706 /* 707 * Convert all \ to /. We'll convert them back again when 708 * we encode the file. Boot loaders are expected to cope. 709 */ 710 modpath = strdup(path); 711 if (modpath == NULL) 712 goto out; 713 for (cp = modpath; *cp; cp++) 714 if (*cp == '\\') 715 *cp = '/'; 716 717 if (modpath[0] == '/' && modpath[1] == '/') /* Handle //foo/bar/baz */ 718 rv = efipart_to_dp(&mesh, modpath, dp); 719 else if (strchr(modpath, ':')) /* Handle dev:/bar/baz */ 720 rv = dev_path_to_dp(&mesh, modpath, dp); 721 else /* Handle /a/b/c */ 722 rv = path_to_dp(&mesh, modpath, dp); 723 724 out: 725 geom_deletetree(&mesh); 726 free(modpath); 727 728 return (rv); 729 } 730