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