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