xref: /freebsd/lib/libefivar/efivar-dp-xlate.c (revision a25896ca1270e25b657ceaa8d47d5699515f5c25)
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