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