xref: /freebsd/lib/libefivar/efivar-dp-xlate.c (revision 5e3190f700637fcfc1a52daeaa4a031fdd2557c7)
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 #include <sys/param.h>
28 #include <sys/ucred.h>
29 #include <sys/mount.h>
30 
31 #undef MAX
32 #undef MIN
33 
34 #include <assert.h>
35 #include <efivar.h>
36 #include <errno.h>
37 #include <libgeom.h>
38 #include <paths.h>
39 #include <stdio.h>
40 #include <string.h>
41 
42 #include "efichar.h"
43 
44 #include "efi-osdep.h"
45 #include "efivar-dp.h"
46 
47 #include "uefi-dplib.h"
48 
49 #define MAX_DP_SANITY	4096		/* Biggest device path in bytes */
50 #define MAX_DP_TEXT_LEN	4096		/* Longest string rep of dp */
51 
52 #define ValidLen(dp) (DevicePathNodeLength(dp) >= sizeof(EFI_DEVICE_PATH_PROTOCOL) && \
53 	    DevicePathNodeLength(dp) < MAX_DP_SANITY)
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, *newdev = NULL;
136 	struct gprovider *pp, *provider;
137 	struct statfs *mnt;
138 	struct gclass *glabel;
139 	struct ggeom *gp;
140 
141 	walker = media = dp;
142 	*dev = NULL;
143 	*relpath = NULL;
144 
145 	/*
146 	 * Now, we can either have a filepath node next, or the end.
147 	 * Otherwise, it's an error.
148 	 */
149 	if (!ValidLen(walker))
150 		return (EINVAL);
151 	walker = (const_efidp)NextDevicePathNode(walker);
152 	if ((uintptr_t)walker - (uintptr_t)dp > MAX_DP_SANITY)
153 		return (EINVAL);
154 	if (DevicePathType(walker) ==  MEDIA_DEVICE_PATH &&
155 	    DevicePathSubType(walker) == MEDIA_FILEPATH_DP)
156 		file = walker;
157 	else if (DevicePathType(walker) == MEDIA_DEVICE_PATH &&
158 	    DevicePathType(walker) == END_DEVICE_PATH_TYPE)
159 		file = NULL;
160 	else
161 		return (EINVAL);
162 
163 	/*
164 	 * Format this node. We're going to look for it as a efimedia
165 	 * attribute of some geom node. Once we find that node, we use it
166 	 * as the device it comes from, at least provisionally.
167 	 */
168 	len = efidp_format_device_path_node(buf, sizeof(buf), media);
169 	if (len > sizeof(buf))
170 		return (EINVAL);
171 
172 	pp = find_provider_by_efimedia(mesh, buf);
173 	if (pp == NULL) {
174 		rv = ENOENT;
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 
216 	/*
217 	 * Find glabel, if it exists. It's OK if not: we'll skip searching for
218 	 * labels.
219 	 */
220 	LIST_FOREACH(glabel, &mesh->lg_class, lg_class) {
221 		if (strcmp(glabel->lg_name, G_LABEL) == 0)
222 			break;
223 	}
224 
225 	provider = pp;
226 	for (i = 0; i < n; i++) {
227 		/*
228 		 * Skip all pseudo filesystems. This also skips the real filesytsem
229 		 * of ZFS. There's no EFI designator for ZFS in the standard, so
230 		 * we'll need to invent one, but its decoding will be handled in
231 		 * a separate function.
232 		 */
233 		if (strncmp(mnt[i].f_mntfromname, "/dev/", 5) != 0)
234 			continue;
235 
236 		/*
237 		 * First see if it is directly attached
238 		 */
239 		if (strcmp(provider->lg_name, mnt[i].f_mntfromname + 5) == 0) {
240 			newdev = provider->lg_name;
241 			break;
242 		}
243 
244 		/*
245 		 * Next see if it is attached via one of the physical disk's labels.
246 		 * We can't search directly from the pointers we have for the
247 		 * provider, so we have to cast a wider net for all labels and
248 		 * filter those down to geoms whose name matches the PART provider
249 		 * we found the efimedia attribute on.
250 		 */
251 		if (glabel == NULL)
252 			continue;
253 		LIST_FOREACH(gp, &glabel->lg_geom, lg_geom) {
254 			if (strcmp(gp->lg_name, provider->lg_name) != 0) {
255 				continue;
256 			}
257 			LIST_FOREACH(pp, &gp->lg_provider, lg_provider) {
258 				if (strcmp(pp->lg_name, mnt[i].f_mntfromname + 5) == 0) {
259 					newdev = pp->lg_name;
260 					goto break2;
261 				}
262 			}
263 		}
264 		/* Not the one, try the next mount point */
265 	}
266 break2:
267 
268 	/*
269 	 * If nothing better was mounted, then use the provider we found as
270 	 * is. It's the most correct thing we can return in that acse.
271 	 */
272 	if (newdev == NULL)
273 		newdev = provider->lg_name;
274 	*dev = strdup(newdev);
275 	if (*dev == NULL) {
276 		rv = ENOMEM;
277 		goto errout;
278 	}
279 
280 	/*
281 	 * No mountpoint found, no absolute path possible
282 	 */
283 	if (i >= n)
284 		goto errout;
285 
286 	/*
287 	 * Construct absolute path and we're finally done.
288 	 */
289 	if (strcmp(mnt[i].f_mntonname, "/") == 0)
290 		asprintf(abspath, "/%s", *relpath);
291 	else
292 		asprintf(abspath, "%s/%s", mnt[i].f_mntonname, *relpath);
293 
294 errout:
295 	if (rv != 0) {
296 		free(*dev);
297 		*dev = NULL;
298 		free(*relpath);
299 		*relpath = NULL;
300 	}
301 	return (rv);
302 }
303 
304 /*
305  * Translate the passed in device_path to a unix path via the following
306  * algorithm.
307  *
308  * If dp, dev or path NULL, return EDOOFUS. XXX wise?
309  *
310  * Set *path = NULL; *dev = NULL;
311  *
312  * Walk through the device_path until we find either a media device path.
313  * Return EINVAL if not found. Return EINVAL if walking dp would
314  * land us more than sanity size away from the start (4k).
315  *
316  * If we find a media descriptor, we search through the geom mesh to see if we
317  * can find a matching node. If no match is found in the mesh that matches,
318  * return ENXIO.
319  *
320  * Once we find a matching node, we search to see if there is a filesystem
321  * mounted on it. If we find nothing, then search each of the devices that are
322  * mounted to see if we can work up the geom tree to find the matching node. if
323  * we still can't find anything, *dev = sprintf("/dev/%s", provider_name
324  * of the original node we found), but return ENOTBLK.
325  *
326  * Record the dev of the mountpoint in *dev.
327  *
328  * Once we find something, check to see if the next node in the device path is
329  * the end of list. If so, return the mountpoint.
330  *
331  * If the next node isn't a File path node, return EFTYPE.
332  *
333  * Extract the path from the File path node(s). translate any \ file separators
334  * to /. Append the result to the mount point. Copy the resulting path into
335  * *path.  Stat that path. If it is not found, return the errorr from stat.
336  *
337  * Finally, check to make sure the resulting path is still on the same
338  * device. If not, return ENODEV.
339  *
340  * Otherwise return 0.
341  *
342  * The dev or full path that's returned is malloced, so needs to be freed when
343  * the caller is done about it. Unlike many other functions, we can return data
344  * with an error code, so pay attention.
345  */
346 int
347 efivar_device_path_to_unix_path(const_efidp dp, char **dev, char **relpath, char **abspath)
348 {
349 	const_efidp walker;
350 	struct gmesh mesh;
351 	int rv = 0;
352 
353 	/*
354 	 * Sanity check args, fail early
355 	 */
356 	if (dp == NULL || dev == NULL || relpath == NULL || abspath == NULL)
357 		return (EDOOFUS);
358 
359 	*dev = NULL;
360 	*relpath = NULL;
361 	*abspath = NULL;
362 
363 	/*
364 	 * Find the first media device path we can. If we go too far,
365 	 * assume the passed in device path is bogus. If we hit the end
366 	 * then we didn't find a media device path, so signal that error.
367 	 */
368 	walker = dp;
369 	if (!ValidLen(walker))
370 		return (EINVAL);
371 	while (DevicePathType(walker) != MEDIA_DEVICE_PATH &&
372 	    DevicePathType(walker) != END_DEVICE_PATH_TYPE) {
373 		walker = (const_efidp)NextDevicePathNode(walker);
374 		if ((uintptr_t)walker - (uintptr_t)dp > MAX_DP_SANITY)
375 			return (EINVAL);
376 		if (!ValidLen(walker))
377 			return (EINVAL);
378 	}
379 	if (DevicePathType(walker) !=  MEDIA_DEVICE_PATH)
380 		return (EINVAL);
381 
382 	/*
383 	 * There's several types of media paths. We're only interested in the
384 	 * hard disk path, as it's really the only relevant one to booting. The
385 	 * CD path just might also be relevant, and would be easy to add, but
386 	 * isn't supported. A file path too is relevant, but at this stage, it's
387 	 * premature because we're trying to translate a specification for a device
388 	 * and path on that device into a unix path, or at the very least, a
389 	 * geom device : path-on-device.
390 	 *
391 	 * Also, ZFS throws a bit of a monkey wrench in here since it doesn't have
392 	 * a device path type (it creates a new virtual device out of one or more
393 	 * storage devices).
394 	 *
395 	 * For all of them, we'll need to know the geoms, so allocate / free the
396 	 * geom mesh here since it's safer than doing it in each sub-function
397 	 * which may have many error exits.
398 	 */
399 	if (geom_gettree(&mesh))
400 		return (ENOMEM);
401 
402 	rv = EINVAL;
403 	if (DevicePathSubType(walker) == MEDIA_HARDDRIVE_DP)
404 		rv = efi_hd_to_unix(&mesh, walker, dev, relpath, abspath);
405 #ifdef notyet
406 	else if (is_cdrom_device(walker))
407 		rv = efi_cdrom_to_unix(&mesh, walker, dev, relpath, abspath);
408 	else if (is_floppy_device(walker))
409 		rv = efi_floppy_to_unix(&mesh, walker, dev, relpath, abspath);
410 	else if (is_zpool_device(walker))
411 		rv = efi_zpool_to_unix(&mesh, walker, dev, relpath, abspath);
412 #endif
413 	geom_deletetree(&mesh);
414 
415 	return (rv);
416 }
417 
418 /*
419  * Construct the EFI path to a current unix path as follows.
420  *
421  * The path may be of one of three forms:
422  *	1) /path/to/file -- full path to a file. The file need not be present,
423  *		but /path/to must be. It must reside on a local filesystem
424  *		mounted on a GPT or MBR partition.
425  *	2) //path/to/file -- Shorthand for 'On the EFI partition, \path\to\file'
426  *		where 'The EFI Partition' is a partition that's type is 'efi'
427  *		on the same disk that / is mounted from. If there are multiple
428  *		or no 'efi' parittions on that disk, or / isn't on a disk that
429  *		we can trace back to a physical device, an error will result
430  *	3) [/dev/]geom-name:/path/to/file -- Use the specified partition
431  *		(and it must be a GPT or MBR partition) with the specified
432  *		path. The latter is not authenticated.
433  * all path forms translate any \ characters to / before further processing.
434  * When a file path node is created, all / characters are translated back
435  * to \.
436  *
437  * For paths of the first form:
438  *	find where the filesystem is mount (either the file directly, or
439  *		its parent directory).
440  *	translate any logical device name (eg lable) to a physical one
441  *	If not possible, return ENXIO
442  *	If the physical path is unsupported (Eg not on a GPT or MBR disk),
443  *		return ENXIO
444  *	Create a media device path node.
445  *	append the relative path from the mountpoint to the media device node
446  * 		as a file path.
447  *
448  * For paths matching the second form:
449  *	find the EFI partition corresponding to the root fileystem.
450  *	If none found, return ENXIO
451  *	Create a media device path node for the found partition
452  *	Append a File Path to the end for the rest of the file.
453  *
454  * For paths of the third form
455  *	Translate the geom-name passed in into a physical partition
456  *		name.
457  *	Return ENXIO if the translation fails
458  *	Make a media device path for it
459  *	append the part after the : as a File path node.
460  */
461 
462 static char *
463 path_to_file_dp(const char *relpath)
464 {
465 	char *rv;
466 
467 	asprintf(&rv, "File(%s)", relpath);
468 	return rv;
469 }
470 
471 static char *
472 find_geom_efi_on_root(struct gmesh *mesh)
473 {
474 	struct statfs buf;
475 	const char *dev;
476 	struct gprovider *pp;
477 //	struct ggeom *disk;
478 	struct gconsumer *cp;
479 
480 	/*
481 	 * Find /'s geom. Assume it's mounted on /dev/ and filter out all the
482 	 * filesystems that aren't.
483 	 */
484 	if (statfs("/", &buf) != 0)
485 		return (NULL);
486 	dev = buf.f_mntfromname;
487 	if (*dev != '/' || strncmp(dev, _PATH_DEV, sizeof(_PATH_DEV) - 1) != 0)
488 		return (NULL);
489 	dev += sizeof(_PATH_DEV) -1;
490 	pp = find_provider_by_name(mesh, dev);
491 	if (pp == NULL)
492 		return (NULL);
493 
494 	/*
495 	 * If the provider is a LABEL, find it's outer PART class, if any. We
496 	 * only operate on partitions.
497 	 */
498 	if (strcmp(pp->lg_geom->lg_class->lg_name, G_LABEL) == 0) {
499 		LIST_FOREACH(cp, &pp->lg_consumers, lg_consumer) {
500 			if (strcmp(cp->lg_provider->lg_geom->lg_class->lg_name, G_PART) == 0) {
501 				pp = cp->lg_provider;
502 				break;
503 			}
504 		}
505 	}
506 	if (strcmp(pp->lg_geom->lg_class->lg_name, G_PART) != 0)
507 		return (NULL);
508 
509 #if 0
510 	/* This doesn't work because we can't get the data to walk UP the tree it seems */
511 
512 	/*
513 	 * Now that we've found the PART that we have mounted as root, find the
514 	 * first efi typed partition that's a peer, if any.
515 	 */
516 	LIST_FOREACH(cp, &pp->lg_consumers, lg_consumer) {
517 		if (strcmp(cp->lg_provider->lg_geom->lg_class->lg_name, G_DISK) == 0) {
518 			disk = cp->lg_provider->lg_geom;
519 			break;
520 		}
521 	}
522 	if (disk == NULL)	/* This is very bad -- old nested partitions -- no support ? */
523 		return (NULL);
524 #endif
525 
526 #if 0
527 	/* This doesn't work because we can't get the data to walk UP the tree it seems */
528 
529 	/*
530 	 * With the disk provider, we can look for its consumers to see if any are the proper type.
531 	 */
532 	LIST_FOREACH(pp, &disk->lg_consumer, lg_consumer) {
533 		type = geom_pp_attr(mesh, pp, "type");
534 		if (type == NULL)
535 			continue;
536 		if (strcmp(type, "efi") != 0)
537 			continue;
538 		efimedia = geom_pp_attr(mesh, pp, "efimedia");
539 		if (efimedia == NULL)
540 			return (NULL);
541 		return strdup(efimedia);
542 	}
543 #endif
544 	return (NULL);
545 }
546 
547 
548 static char *
549 find_geom_efimedia(struct gmesh *mesh, const char *dev)
550 {
551 	struct gprovider *pp;
552 	const char *efimedia;
553 
554 	pp = find_provider_by_name(mesh, dev);
555 	if (pp == NULL)
556 		return (NULL);
557 	efimedia = geom_pp_attr(mesh, pp, "efimedia");
558 
559 	/*
560 	 * If this device doesn't hav an efimedia attribute, see if it is a
561 	 * glabel node, and if so look for the underlying provider to get the
562 	 * efimedia attribute from.
563 	 */
564 	if (efimedia == NULL &&
565 	    strcmp(pp->lg_geom->lg_class->lg_name, G_LABEL) == 0)
566 		efimedia = find_geom_efimedia(mesh, pp->lg_geom->lg_name);
567 	if (efimedia == NULL)
568 		return (NULL);
569 	return strdup(efimedia);
570 }
571 
572 static int
573 build_dp(const char *efimedia, const char *relpath, efidp *dp)
574 {
575 	char *fp = NULL, *dptxt = NULL, *cp, *rp = NULL;
576 	int rv = 0;
577 	efidp out = NULL;
578 	size_t len;
579 
580 	if (relpath != NULL) {
581 		rp = strdup(relpath);
582 		for (cp = rp; *cp; cp++)
583 			if (*cp == '/')
584 				*cp = '\\';
585 		fp = path_to_file_dp(rp);
586 		free(rp);
587 		if (fp == NULL) {
588 			rv = ENOMEM;
589 			goto errout;
590 		}
591 	}
592 
593 	asprintf(&dptxt, "%s/%s", efimedia, fp == NULL ? "" : fp);
594 	out = malloc(8192);
595 	len = efidp_parse_device_path(dptxt, out, 8192);
596 	if (len > 8192) {
597 		rv = ENOMEM;
598 		goto errout;
599 	}
600 	if (len == 0) {
601 		rv = EINVAL;
602 		goto errout;
603 	}
604 
605 	*dp = out;
606 errout:
607 	if (rv) {
608 		free(out);
609 	}
610 	free(dptxt);
611 	free(fp);
612 
613 	return rv;
614 }
615 
616 /* Handles //path/to/file */
617 /*
618  * Which means: find the disk that has /. Then look for a EFI partition
619  * and use that for the efimedia and /path/to/file as relative to that.
620  * Not sure how ZFS will work here since we can't easily make the leap
621  * to the geom from the zpool.
622  */
623 static int
624 efipart_to_dp(struct gmesh *mesh, char *path, efidp *dp)
625 {
626 	char *efimedia = NULL;
627 	int rv;
628 
629 	efimedia = find_geom_efi_on_root(mesh);
630 #ifdef notyet
631 	if (efimedia == NULL)
632 		efimedia = find_efi_on_zfsroot(dev);
633 #endif
634 	if (efimedia == NULL) {
635 		rv = ENOENT;
636 		goto errout;
637 	}
638 
639 	rv = build_dp(efimedia, path + 1, dp);
640 errout:
641 	free(efimedia);
642 
643 	return rv;
644 }
645 
646 /* Handles [/dev/]geom:[/]path/to/file */
647 /* Handles zfs-dataset:[/]path/to/file (this may include / ) */
648 static int
649 dev_path_to_dp(struct gmesh *mesh, char *path, efidp *dp)
650 {
651 	char *relpath, *dev, *efimedia = NULL;
652 	int rv = 0;
653 
654 	relpath = strchr(path, ':');
655 	assert(relpath != NULL);
656 	*relpath++ = '\0';
657 
658 	dev = path;
659 	if (strncmp(dev, _PATH_DEV, sizeof(_PATH_DEV) - 1) == 0)
660 		dev += sizeof(_PATH_DEV) -1;
661 
662 	efimedia = find_geom_efimedia(mesh, dev);
663 #ifdef notyet
664 	if (efimedia == NULL)
665 		find_zfs_efi_media(dev);
666 #endif
667 	if (efimedia == NULL) {
668 		rv = ENOENT;
669 		goto errout;
670 	}
671 	rv = build_dp(efimedia, relpath, dp);
672 errout:
673 	free(efimedia);
674 
675 	return rv;
676 }
677 
678 /* Handles /path/to/file */
679 /* Handles /dev/foo/bar */
680 static int
681 path_to_dp(struct gmesh *mesh, char *path, efidp *dp)
682 {
683 	struct statfs buf;
684 	char *rp = NULL, *ep, *dev, *efimedia = NULL;
685 	int rv = 0;
686 
687 	rp = realpath(path, NULL);
688 	if (rp == NULL) {
689 		rv = errno;
690 		goto errout;
691 	}
692 
693 	if (statfs(rp, &buf) != 0) {
694 		rv = errno;
695 		goto errout;
696 	}
697 
698 	dev = buf.f_mntfromname;
699 	/*
700 	 * If we're fed a raw /dev/foo/bar, then devfs is returned from the
701 	 * statfs call. In that case, use that dev and assume we have a path
702 	 * of nothing.
703 	 */
704 	if (strcmp(dev, "devfs") == 0) {
705 		dev = rp + sizeof(_PATH_DEV) - 1;
706 		ep = NULL;
707 	} else {
708 		if (strncmp(dev, _PATH_DEV, sizeof(_PATH_DEV) - 1) == 0)
709 			dev += sizeof(_PATH_DEV) - 1;
710 		ep = rp + strlen(buf.f_mntonname);
711 	}
712 
713 	efimedia = find_geom_efimedia(mesh, dev);
714 #ifdef notyet
715 	if (efimedia == NULL)
716 		find_zfs_efi_media(dev);
717 #endif
718 	if (efimedia == NULL) {
719 		rv = ENOENT;
720 		goto errout;
721 	}
722 
723 	rv = build_dp(efimedia, ep, dp);
724 errout:
725 	free(efimedia);
726 	free(rp);
727 	if (rv != 0) {
728 		free(*dp);
729 		*dp = NULL;
730 	}
731 	return (rv);
732 }
733 
734 int
735 efivar_unix_path_to_device_path(const char *path, efidp *dp)
736 {
737 	char *modpath = NULL, *cp;
738 	int rv = ENOMEM;
739 	struct gmesh mesh;
740 
741 	/*
742 	 * Fail early for clearly bogus things
743 	 */
744 	if (path == NULL || dp == NULL)
745 		return (EDOOFUS);
746 
747 	/*
748 	 * We'll need the goem mesh to grovel through it to find the
749 	 * efimedia attribute for any devices we find. Grab it here
750 	 * and release it to simplify the error paths out of the
751 	 * subordinate functions
752 	 */
753 	if (geom_gettree(&mesh))
754 		return (errno);
755 
756 	/*
757 	 * Convert all \ to /. We'll convert them back again when
758 	 * we encode the file. Boot loaders are expected to cope.
759 	 */
760 	modpath = strdup(path);
761 	if (modpath == NULL)
762 		goto out;
763 	for (cp = modpath; *cp; cp++)
764 		if (*cp == '\\')
765 			*cp = '/';
766 
767 	if (modpath[0] == '/' && modpath[1] == '/')	/* Handle //foo/bar/baz */
768 		rv = efipart_to_dp(&mesh, modpath, dp);
769 	else if (strchr(modpath, ':'))			/* Handle dev:/bar/baz */
770 		rv = dev_path_to_dp(&mesh, modpath, dp);
771 	else						/* Handle /a/b/c */
772 		rv = path_to_dp(&mesh, modpath, dp);
773 
774 out:
775 	geom_deletetree(&mesh);
776 	free(modpath);
777 
778 	return (rv);
779 }
780