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