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