2 * Copyright (c) 2017 Netflix, Inc.
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
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.
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
26 #include <sys/cdefs.h>
27 __FBSDID("$FreeBSD$");
29 #include <sys/param.h>
30 #include <sys/ucred.h>
31 #include <sys/mount.h>
46 #include "efi-osdep.h"
47 #include "efivar-dp.h"
49 #include "uefi-dplib.h"
51 #define MAX_DP_SANITY 4096 /* Biggest device path in bytes */
52 #define MAX_DP_TEXT_LEN 4096 /* Longest string rep of dp */
54 #define ValidLen(dp) (DevicePathNodeLength(dp) >= sizeof(EFI_DEVICE_PATH_PROTOCOL) && \
55 DevicePathNodeLength(dp) < MAX_DP_SANITY)
58 #define G_LABEL "LABEL"
62 geom_pp_attr(struct gmesh *mesh, struct gprovider *pp, const char *attr)
66 LIST_FOREACH(conf, &pp->lg_config, lg_config) {
67 if (strcmp(conf->lg_name, attr) != 0)
69 return (conf->lg_val);
74 static struct gprovider *
75 find_provider_by_efimedia(struct gmesh *mesh, const char *efimedia)
77 struct gclass *classp;
83 * Find the partition class so we can search it...
85 LIST_FOREACH(classp, &mesh->lg_class, lg_class) {
86 if (strcasecmp(classp->lg_name, G_PART) == 0)
93 * Each geom will have a number of providers, search each
94 * one of them for the efimedia that matches.
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");
102 if (strcasecmp(efimedia, val) == 0)
110 static struct gprovider *
111 find_provider_by_name(struct gmesh *mesh, const char *name)
113 struct gclass *classp;
115 struct gprovider *pp;
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)
131 efi_hd_to_unix(struct gmesh *mesh, const_efidp dp, char **dev, char **relpath, char **abspath)
134 const_efidp media, file, walker;
136 char buf[MAX_DP_TEXT_LEN];
137 char *pwalk, *newdev = NULL;
138 struct gprovider *pp, *provider;
140 struct gclass *glabel;
148 * Now, we can either have a filepath node next, or the end.
149 * Otherwise, it's an error.
151 if (!ValidLen(walker))
153 walker = (const_efidp)NextDevicePathNode(walker);
154 if ((uintptr_t)walker - (uintptr_t)dp > MAX_DP_SANITY)
156 if (DevicePathType(walker) == MEDIA_DEVICE_PATH &&
157 DevicePathSubType(walker) == MEDIA_FILEPATH_DP)
159 else if (DevicePathType(walker) == MEDIA_DEVICE_PATH &&
160 DevicePathType(walker) == END_DEVICE_PATH_TYPE)
166 * Format this node. We're going to look for it as a efimedia
167 * attribute of some geom node. Once we find that node, we use it
168 * as the device it comes from, at least provisionally.
170 len = efidp_format_device_path_node(buf, sizeof(buf), media);
171 if (len > sizeof(buf))
174 pp = find_provider_by_efimedia(mesh, buf);
181 * No file specified, just return the device. Don't even look
182 * for a mountpoint. XXX Sane?
188 * Now extract the relative path. The next node in the device path should
189 * be a filesystem node. If not, we have issues.
191 *relpath = efidp_extract_file_path(file);
192 if (*relpath == NULL) {
196 for (pwalk = *relpath; *pwalk; pwalk++)
201 * To find the absolute path, we have to look for where we're mounted.
202 * We only look a little hard, since looking too hard can come up with
203 * false positives (imagine a graid, one of whose devices is *dev).
205 n = getfsstat(NULL, 0, MNT_NOWAIT) + 1;
210 mntlen = sizeof(struct statfs) * n;
211 mnt = malloc(mntlen);
212 n = getfsstat(mnt, mntlen, MNT_NOWAIT);
219 * Find glabel, if it exists. It's OK if not: we'll skip searching for
222 LIST_FOREACH(glabel, &mesh->lg_class, lg_class) {
223 if (strcmp(glabel->lg_name, G_LABEL) == 0)
228 for (i = 0; i < n; i++) {
230 * Skip all pseudo filesystems. This also skips the real filesytsem
231 * of ZFS. There's no EFI designator for ZFS in the standard, so
232 * we'll need to invent one, but its decoding will be handled in
233 * a separate function.
235 if (strncmp(mnt[i].f_mntfromname, "/dev/", 5) != 0)
239 * First see if it is directly attached
241 if (strcmp(provider->lg_name, mnt[i].f_mntfromname + 5) == 0) {
242 newdev = provider->lg_name;
247 * Next see if it is attached via one of the physical disk's labels.
248 * We can't search directly from the pointers we have for the
249 * provider, so we have to cast a wider net for all labels and
250 * filter those down to geoms whose name matches the PART provider
251 * we found the efimedia attribute on.
255 LIST_FOREACH(gp, &glabel->lg_geom, lg_geom) {
256 if (strcmp(gp->lg_name, provider->lg_name) != 0) {
259 LIST_FOREACH(pp, &gp->lg_provider, lg_provider) {
260 if (strcmp(pp->lg_name, mnt[i].f_mntfromname + 5) == 0) {
261 newdev = pp->lg_name;
266 /* Not the one, try the next mount point */
271 * If nothing better was mounted, then use the provider we found as
272 * is. It's the most correct thing we can return in that acse.
275 newdev = provider->lg_name;
276 *dev = strdup(newdev);
283 * No mountpoint found, no absolute path possible
289 * Construct absolute path and we're finally done.
291 if (strcmp(mnt[i].f_mntonname, "/") == 0)
292 asprintf(abspath, "/%s", *relpath);
294 asprintf(abspath, "%s/%s", mnt[i].f_mntonname, *relpath);
307 * Translate the passed in device_path to a unix path via the following
310 * If dp, dev or path NULL, return EDOOFUS. XXX wise?
312 * Set *path = NULL; *dev = NULL;
314 * Walk through the device_path until we find either a media device path.
315 * Return EINVAL if not found. Return EINVAL if walking dp would
316 * land us more than sanity size away from the start (4k).
318 * If we find a media descriptor, we search through the geom mesh to see if we
319 * can find a matching node. If no match is found in the mesh that matches,
322 * Once we find a matching node, we search to see if there is a filesystem
323 * mounted on it. If we find nothing, then search each of the devices that are
324 * mounted to see if we can work up the geom tree to find the matching node. if
325 * we still can't find anything, *dev = sprintf("/dev/%s", provider_name
326 * of the original node we found), but return ENOTBLK.
328 * Record the dev of the mountpoint in *dev.
330 * Once we find something, check to see if the next node in the device path is
331 * the end of list. If so, return the mountpoint.
333 * If the next node isn't a File path node, return EFTYPE.
335 * Extract the path from the File path node(s). translate any \ file separators
336 * to /. Append the result to the mount point. Copy the resulting path into
337 * *path. Stat that path. If it is not found, return the errorr from stat.
339 * Finally, check to make sure the resulting path is still on the same
340 * device. If not, return ENODEV.
342 * Otherwise return 0.
344 * The dev or full path that's returned is malloced, so needs to be freed when
345 * the caller is done about it. Unlike many other functions, we can return data
346 * with an error code, so pay attention.
349 efivar_device_path_to_unix_path(const_efidp dp, char **dev, char **relpath, char **abspath)
356 * Sanity check args, fail early
358 if (dp == NULL || dev == NULL || relpath == NULL || abspath == NULL)
366 * Find the first media device path we can. If we go too far,
367 * assume the passed in device path is bogus. If we hit the end
368 * then we didn't find a media device path, so signal that error.
371 if (!ValidLen(walker))
373 while (DevicePathType(walker) != MEDIA_DEVICE_PATH &&
374 DevicePathType(walker) != END_DEVICE_PATH_TYPE) {
375 walker = (const_efidp)NextDevicePathNode(walker);
376 if ((uintptr_t)walker - (uintptr_t)dp > MAX_DP_SANITY)
378 if (!ValidLen(walker))
381 if (DevicePathType(walker) != MEDIA_DEVICE_PATH)
385 * There's several types of media paths. We're only interested in the
386 * hard disk path, as it's really the only relevant one to booting. The
387 * CD path just might also be relevant, and would be easy to add, but
388 * isn't supported. A file path too is relevant, but at this stage, it's
389 * premature because we're trying to translate a specification for a device
390 * and path on that device into a unix path, or at the very least, a
391 * geom device : path-on-device.
393 * Also, ZFS throws a bit of a monkey wrench in here since it doesn't have
394 * a device path type (it creates a new virtual device out of one or more
397 * For all of them, we'll need to know the geoms, so allocate / free the
398 * geom mesh here since it's safer than doing it in each sub-function
399 * which may have many error exits.
401 if (geom_gettree(&mesh))
405 if (DevicePathSubType(walker) == MEDIA_HARDDRIVE_DP)
406 rv = efi_hd_to_unix(&mesh, walker, dev, relpath, abspath);
408 else if (is_cdrom_device(walker))
409 rv = efi_cdrom_to_unix(&mesh, walker, dev, relpath, abspath);
410 else if (is_floppy_device(walker))
411 rv = efi_floppy_to_unix(&mesh, walker, dev, relpath, abspath);
412 else if (is_zpool_device(walker))
413 rv = efi_zpool_to_unix(&mesh, walker, dev, relpath, abspath);
415 geom_deletetree(&mesh);
421 * Construct the EFI path to a current unix path as follows.
423 * The path may be of one of three forms:
424 * 1) /path/to/file -- full path to a file. The file need not be present,
425 * but /path/to must be. It must reside on a local filesystem
426 * mounted on a GPT or MBR partition.
427 * 2) //path/to/file -- Shorthand for 'On the EFI partition, \path\to\file'
428 * where 'The EFI Partition' is a partition that's type is 'efi'
429 * on the same disk that / is mounted from. If there are multiple
430 * or no 'efi' parittions on that disk, or / isn't on a disk that
431 * we can trace back to a physical device, an error will result
432 * 3) [/dev/]geom-name:/path/to/file -- Use the specified partition
433 * (and it must be a GPT or MBR partition) with the specified
434 * path. The latter is not authenticated.
435 * all path forms translate any \ characters to / before further processing.
436 * When a file path node is created, all / characters are translated back
439 * For paths of the first form:
440 * find where the filesystem is mount (either the file directly, or
441 * its parent directory).
442 * translate any logical device name (eg lable) to a physical one
443 * If not possible, return ENXIO
444 * If the physical path is unsupported (Eg not on a GPT or MBR disk),
446 * Create a media device path node.
447 * append the relative path from the mountpoint to the media device node
450 * For paths matching the second form:
451 * find the EFI partition corresponding to the root fileystem.
452 * If none found, return ENXIO
453 * Create a media device path node for the found partition
454 * Append a File Path to the end for the rest of the file.
456 * For paths of the third form
457 * Translate the geom-name passed in into a physical partition
459 * Return ENXIO if the translation fails
460 * Make a media device path for it
461 * append the part after the : as a File path node.
465 path_to_file_dp(const char *relpath)
469 asprintf(&rv, "File(%s)", relpath);
474 find_geom_efi_on_root(struct gmesh *mesh)
478 struct gprovider *pp;
479 // struct ggeom *disk;
480 struct gconsumer *cp;
483 * Find /'s geom. Assume it's mounted on /dev/ and filter out all the
484 * filesystems that aren't.
486 if (statfs("/", &buf) != 0)
488 dev = buf.f_mntfromname;
489 if (*dev != '/' || strncmp(dev, _PATH_DEV, sizeof(_PATH_DEV) - 1) != 0)
491 dev += sizeof(_PATH_DEV) -1;
492 pp = find_provider_by_name(mesh, dev);
497 * If the provider is a LABEL, find it's outer PART class, if any. We
498 * only operate on partitions.
500 if (strcmp(pp->lg_geom->lg_class->lg_name, G_LABEL) == 0) {
501 LIST_FOREACH(cp, &pp->lg_consumers, lg_consumer) {
502 if (strcmp(cp->lg_provider->lg_geom->lg_class->lg_name, G_PART) == 0) {
503 pp = cp->lg_provider;
508 if (strcmp(pp->lg_geom->lg_class->lg_name, G_PART) != 0)
512 /* This doesn't work because we can't get the data to walk UP the tree it seems */
515 * Now that we've found the PART that we have mounted as root, find the
516 * first efi typed partition that's a peer, if any.
518 LIST_FOREACH(cp, &pp->lg_consumers, lg_consumer) {
519 if (strcmp(cp->lg_provider->lg_geom->lg_class->lg_name, G_DISK) == 0) {
520 disk = cp->lg_provider->lg_geom;
524 if (disk == NULL) /* This is very bad -- old nested partitions -- no support ? */
529 /* This doesn't work because we can't get the data to walk UP the tree it seems */
532 * With the disk provider, we can look for its consumers to see if any are the proper type.
534 LIST_FOREACH(pp, &disk->lg_consumer, lg_consumer) {
535 type = geom_pp_attr(mesh, pp, "type");
538 if (strcmp(type, "efi") != 0)
540 efimedia = geom_pp_attr(mesh, pp, "efimedia");
541 if (efimedia == NULL)
543 return strdup(efimedia);
551 find_geom_efimedia(struct gmesh *mesh, const char *dev)
553 struct gprovider *pp;
554 const char *efimedia;
556 pp = find_provider_by_name(mesh, dev);
559 efimedia = geom_pp_attr(mesh, pp, "efimedia");
562 * If this device doesn't hav an efimedia attribute, see if it is a
563 * glabel node, and if so look for the underlying provider to get the
564 * efimedia attribute from.
566 if (efimedia == NULL &&
567 strcmp(pp->lg_geom->lg_class->lg_name, G_LABEL) == 0)
568 efimedia = find_geom_efimedia(mesh, pp->lg_geom->lg_name);
569 if (efimedia == NULL)
571 return strdup(efimedia);
575 build_dp(const char *efimedia, const char *relpath, efidp *dp)
577 char *fp = NULL, *dptxt = NULL, *cp, *rp = NULL;
582 if (relpath != NULL) {
583 rp = strdup(relpath);
584 for (cp = rp; *cp; cp++)
587 fp = path_to_file_dp(rp);
595 asprintf(&dptxt, "%s/%s", efimedia, fp == NULL ? "" : fp);
597 len = efidp_parse_device_path(dptxt, out, 8192);
618 /* Handles //path/to/file */
620 * Which means: find the disk that has /. Then look for a EFI partition
621 * and use that for the efimedia and /path/to/file as relative to that.
622 * Not sure how ZFS will work here since we can't easily make the leap
623 * to the geom from the zpool.
626 efipart_to_dp(struct gmesh *mesh, char *path, efidp *dp)
628 char *efimedia = NULL;
631 efimedia = find_geom_efi_on_root(mesh);
633 if (efimedia == NULL)
634 efimedia = find_efi_on_zfsroot(dev);
636 if (efimedia == NULL) {
641 rv = build_dp(efimedia, path + 1, dp);
648 /* Handles [/dev/]geom:[/]path/to/file */
649 /* Handles zfs-dataset:[/]path/to/file (this may include / ) */
651 dev_path_to_dp(struct gmesh *mesh, char *path, efidp *dp)
653 char *relpath, *dev, *efimedia = NULL;
656 relpath = strchr(path, ':');
657 assert(relpath != NULL);
661 if (strncmp(dev, _PATH_DEV, sizeof(_PATH_DEV) - 1) == 0)
662 dev += sizeof(_PATH_DEV) -1;
664 efimedia = find_geom_efimedia(mesh, dev);
666 if (efimedia == NULL)
667 find_zfs_efi_media(dev);
669 if (efimedia == NULL) {
673 rv = build_dp(efimedia, relpath, dp);
680 /* Handles /path/to/file */
681 /* Handles /dev/foo/bar */
683 path_to_dp(struct gmesh *mesh, char *path, efidp *dp)
686 char *rp = NULL, *ep, *dev, *efimedia = NULL;
689 rp = realpath(path, NULL);
695 if (statfs(rp, &buf) != 0) {
700 dev = buf.f_mntfromname;
702 * If we're fed a raw /dev/foo/bar, then devfs is returned from the
703 * statfs call. In that case, use that dev and assume we have a path
706 if (strcmp(dev, "devfs") == 0) {
707 dev = rp + sizeof(_PATH_DEV) - 1;
710 if (strncmp(dev, _PATH_DEV, sizeof(_PATH_DEV) - 1) == 0)
711 dev += sizeof(_PATH_DEV) - 1;
712 ep = rp + strlen(buf.f_mntonname);
715 efimedia = find_geom_efimedia(mesh, dev);
717 if (efimedia == NULL)
718 find_zfs_efi_media(dev);
720 if (efimedia == NULL) {
725 rv = build_dp(efimedia, ep, dp);
737 efivar_unix_path_to_device_path(const char *path, efidp *dp)
739 char *modpath = NULL, *cp;
744 * Fail early for clearly bogus things
746 if (path == NULL || dp == NULL)
750 * We'll need the goem mesh to grovel through it to find the
751 * efimedia attribute for any devices we find. Grab it here
752 * and release it to simplify the error paths out of the
753 * subordinate functions
755 if (geom_gettree(&mesh))
759 * Convert all \ to /. We'll convert them back again when
760 * we encode the file. Boot loaders are expected to cope.
762 modpath = strdup(path);
765 for (cp = modpath; *cp; cp++)
769 if (modpath[0] == '/' && modpath[1] == '/') /* Handle //foo/bar/baz */
770 rv = efipart_to_dp(&mesh, modpath, dp);
771 else if (strchr(modpath, ':')) /* Handle dev:/bar/baz */
772 rv = dev_path_to_dp(&mesh, modpath, dp);
773 else /* Handle /a/b/c */
774 rv = path_to_dp(&mesh, modpath, dp);
777 geom_deletetree(&mesh);