1326458Simp/*-
2326458Simp * Copyright (c) 2017 Netflix, Inc.
3326458Simp * All rights reserved.
4326458Simp *
5326458Simp * Redistribution and use in source and binary forms, with or without
6326458Simp * modification, are permitted provided that the following conditions
7326458Simp * are met:
8326458Simp * 1. Redistributions of source code must retain the above copyright
9332123Skevans *    notice, this list of conditions and the following disclaimer.
10326458Simp * 2. Redistributions in binary form must reproduce the above copyright
11326458Simp *    notice, this list of conditions and the following disclaimer in the
12326458Simp *    documentation and/or other materials provided with the distribution.
13326458Simp *
14332123Skevans * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
15332123Skevans * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16332123Skevans * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
17332123Skevans * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
18332123Skevans * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
19332123Skevans * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
20332123Skevans * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
21332123Skevans * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
22332123Skevans * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
23332123Skevans * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
24332123Skevans * SUCH DAMAGE.
25326458Simp */
26326458Simp
27326458Simp#include <sys/cdefs.h>
28326458Simp__FBSDID("$FreeBSD: stable/11/lib/libefivar/efivar-dp-xlate.c 332123 2018-04-06 18:10:38Z kevans $");
29326458Simp
30326458Simp#include <sys/param.h>
31326458Simp#include <sys/ucred.h>
32326458Simp#include <sys/mount.h>
33326458Simp
34326458Simp#undef MAX
35326458Simp#undef MIN
36326458Simp
37326458Simp#include <assert.h>
38326458Simp#include <efivar.h>
39326458Simp#include <errno.h>
40326458Simp#include <libgeom.h>
41326458Simp#include <paths.h>
42326458Simp#include <stdio.h>
43326458Simp#include <string.h>
44326458Simp
45326458Simp#include "efichar.h"
46326458Simp
47326458Simp#include "efi-osdep.h"
48326458Simp#include "efivar-dp.h"
49326458Simp
50326458Simp#include "uefi-dplib.h"
51326458Simp
52326458Simp#define MAX_DP_SANITY	4096		/* Biggest device path in bytes */
53326458Simp#define MAX_DP_TEXT_LEN	4096		/* Longest string rep of dp */
54326458Simp
55326458Simp#define	G_PART	"PART"
56326458Simp#define	G_LABEL "LABEL"
57326458Simp#define G_DISK	"DISK"
58326458Simp
59326458Simpstatic const char *
60326458Simpgeom_pp_attr(struct gmesh *mesh, struct gprovider *pp, const char *attr)
61326458Simp{
62326458Simp	struct gconfig *conf;
63326458Simp
64326458Simp	LIST_FOREACH(conf, &pp->lg_config, lg_config) {
65326458Simp		if (strcmp(conf->lg_name, attr) != 0)
66326458Simp			continue;
67326458Simp		return (conf->lg_val);
68326458Simp	}
69326458Simp	return (NULL);
70326458Simp}
71326458Simp
72326458Simpstatic struct gprovider *
73326458Simpfind_provider_by_efimedia(struct gmesh *mesh, const char *efimedia)
74326458Simp{
75326458Simp	struct gclass *classp;
76326458Simp	struct ggeom *gp;
77326458Simp	struct gprovider *pp;
78326458Simp	const char *val;
79326458Simp
80326458Simp	/*
81326458Simp	 * Find the partition class so we can search it...
82326458Simp	 */
83326458Simp	LIST_FOREACH(classp, &mesh->lg_class, lg_class) {
84326458Simp		if (strcasecmp(classp->lg_name, G_PART) == 0)
85326458Simp			break;
86326458Simp	}
87326458Simp	if (classp == NULL)
88326458Simp		return (NULL);
89326458Simp
90326458Simp	/*
91326458Simp	 * Each geom will have a number of providers, search each
92326458Simp	 * one of them for the efimedia that matches.
93326458Simp	 */
94326458Simp	/* XXX just used gpart class since I know it's the only one, but maybe I should search all classes */
95326458Simp	LIST_FOREACH(gp, &classp->lg_geom, lg_geom) {
96326458Simp		LIST_FOREACH(pp, &gp->lg_provider, lg_provider) {
97326458Simp			val = geom_pp_attr(mesh, pp, "efimedia");
98326458Simp			if (val == NULL)
99326458Simp				continue;
100326458Simp			if (strcasecmp(efimedia, val) == 0)
101326458Simp				return (pp);
102326458Simp		}
103326458Simp	}
104326458Simp
105326458Simp	return (NULL);
106326458Simp}
107326458Simp
108326458Simpstatic struct gprovider *
109326458Simpfind_provider_by_name(struct gmesh *mesh, const char *name)
110326458Simp{
111326458Simp	struct gclass *classp;
112326458Simp	struct ggeom *gp;
113326458Simp	struct gprovider *pp;
114326458Simp
115326458Simp	LIST_FOREACH(classp, &mesh->lg_class, lg_class) {
116326458Simp		LIST_FOREACH(gp, &classp->lg_geom, lg_geom) {
117326458Simp			LIST_FOREACH(pp, &gp->lg_provider, lg_provider) {
118326458Simp				if (strcmp(pp->lg_name, name) == 0)
119326458Simp					return (pp);
120326458Simp			}
121326458Simp		}
122326458Simp	}
123326458Simp
124326458Simp	return (NULL);
125326458Simp}
126326458Simp
127326458Simp
128326458Simpstatic int
129326458Simpefi_hd_to_unix(struct gmesh *mesh, const_efidp dp, char **dev, char **relpath, char **abspath)
130326458Simp{
131326458Simp	int rv = 0, n, i;
132326458Simp	const_efidp media, file, walker;
133326458Simp	size_t len, mntlen;
134326458Simp	char buf[MAX_DP_TEXT_LEN];
135326458Simp	char *pwalk;
136326458Simp	struct gprovider *pp, *provider;
137326458Simp	struct gconsumer *cp;
138326458Simp	struct statfs *mnt;
139326458Simp
140326458Simp	walker = media = dp;
141326458Simp
142326458Simp	/*
143326458Simp	 * Now, we can either have a filepath node next, or the end.
144326458Simp	 * Otherwise, it's an error.
145326458Simp	 */
146326458Simp	walker = (const_efidp)NextDevicePathNode(walker);
147326458Simp	if ((uintptr_t)walker - (uintptr_t)dp > MAX_DP_SANITY)
148326458Simp		return (EINVAL);
149326458Simp	if (DevicePathType(walker) ==  MEDIA_DEVICE_PATH &&
150326458Simp	    DevicePathSubType(walker) == MEDIA_FILEPATH_DP)
151326458Simp		file = walker;
152326458Simp	else if (DevicePathType(walker) == MEDIA_DEVICE_PATH &&
153326458Simp	    DevicePathType(walker) == END_DEVICE_PATH_TYPE)
154326458Simp		file = NULL;
155326458Simp	else
156326458Simp		return (EINVAL);
157326458Simp
158326458Simp	/*
159326458Simp	 * Format this node. We're going to look for it as a efimedia
160326458Simp	 * attribute of some geom node. Once we find that node, we use it
161326458Simp	 * as the device it comes from, at least provisionally.
162326458Simp	 */
163326458Simp	len = efidp_format_device_path_node(buf, sizeof(buf), media);
164326458Simp	if (len > sizeof(buf))
165326458Simp		return (EINVAL);
166326458Simp
167326458Simp	pp = find_provider_by_efimedia(mesh, buf);
168326458Simp	if (pp == NULL) {
169326458Simp		rv = ENOENT;
170326458Simp		goto errout;
171326458Simp	}
172326458Simp
173326458Simp	*dev = strdup(pp->lg_name);
174326458Simp	if (*dev == NULL) {
175326458Simp		rv = ENOMEM;
176326458Simp		goto errout;
177326458Simp	}
178326458Simp
179326458Simp	/*
180326458Simp	 * No file specified, just return the device. Don't even look
181326458Simp	 * for a mountpoint. XXX Sane?
182326458Simp	 */
183326458Simp	if (file == NULL)
184326458Simp		goto errout;
185326458Simp
186326458Simp	/*
187326458Simp	 * Now extract the relative path. The next node in the device path should
188326458Simp	 * be a filesystem node. If not, we have issues.
189326458Simp	 */
190326458Simp	*relpath = efidp_extract_file_path(file);
191326458Simp	if (*relpath == NULL) {
192326458Simp		rv = ENOMEM;
193326458Simp		goto errout;
194326458Simp	}
195326458Simp	for (pwalk = *relpath; *pwalk; pwalk++)
196326458Simp		if (*pwalk == '\\')
197326458Simp			*pwalk = '/';
198326458Simp
199326458Simp	/*
200326458Simp	 * To find the absolute path, we have to look for where we're mounted.
201326458Simp	 * We only look a little hard, since looking too hard can come up with
202326458Simp	 * false positives (imagine a graid, one of whose devices is *dev).
203326458Simp	 */
204326458Simp	n = getfsstat(NULL, 0, MNT_NOWAIT) + 1;
205326458Simp	if (n < 0) {
206326458Simp		rv = errno;
207326458Simp		goto errout;
208326458Simp	}
209326458Simp	mntlen = sizeof(struct statfs) * n;
210326458Simp	mnt = malloc(mntlen);
211326458Simp	n = getfsstat(mnt, mntlen, MNT_NOWAIT);
212326458Simp	if (n < 0) {
213326458Simp		rv = errno;
214326458Simp		goto errout;
215326458Simp	}
216326458Simp	provider = pp;
217326458Simp	for (i = 0; i < n; i++) {
218326458Simp		/*
219326458Simp		 * Skip all pseudo filesystems. This also skips the real filesytsem
220326458Simp		 * of ZFS. There's no EFI designator for ZFS in the standard, so
221326458Simp		 * we'll need to invent one, but its decoding will be handled in
222326458Simp		 * a separate function.
223326458Simp		 */
224326458Simp		if (mnt[i].f_mntfromname[0] != '/')
225326458Simp			continue;
226326458Simp
227326458Simp		/*
228326458Simp		 * First see if it is directly attached
229326458Simp		 */
230326458Simp		if (strcmp(provider->lg_name, mnt[i].f_mntfromname + 5) == 0)
231326458Simp			break;
232326458Simp
233326458Simp		/*
234326458Simp		 * Next see if it is attached via one of the physical disk's
235326458Simp		 * labels.
236326458Simp		 */
237326458Simp		LIST_FOREACH(cp, &provider->lg_consumers, lg_consumer) {
238326458Simp			pp = cp->lg_provider;
239326458Simp			if (strcmp(pp->lg_geom->lg_class->lg_name, G_LABEL) != 0)
240326458Simp				continue;
241326458Simp			if (strcmp(g_device_path(pp->lg_name), mnt[i].f_mntfromname) == 0)
242326458Simp				goto break2;
243326458Simp		}
244326458Simp		/* Not the one, try the next mount point */
245326458Simp	}
246326458Simpbreak2:
247326458Simp
248326458Simp	/*
249326458Simp	 * No mountpoint found, no absolute path possible
250326458Simp	 */
251326458Simp	if (i >= n)
252326458Simp		goto errout;
253326458Simp
254326458Simp	/*
255326458Simp	 * Construct absolute path and we're finally done.
256326458Simp	 */
257326458Simp	if (strcmp(mnt[i].f_mntonname, "/") == 0)
258326458Simp		asprintf(abspath, "/%s", *relpath);
259326458Simp	else
260326458Simp		asprintf(abspath, "%s/%s", mnt[i].f_mntonname, *relpath);
261326458Simp
262326458Simperrout:
263326458Simp	if (rv != 0) {
264326458Simp		free(*dev);
265326458Simp		*dev = NULL;
266326458Simp		free(*relpath);
267326458Simp		*relpath = NULL;
268326458Simp	}
269326458Simp	return (rv);
270326458Simp}
271326458Simp
272326458Simp/*
273326458Simp * Translate the passed in device_path to a unix path via the following
274326458Simp * algorithm.
275326458Simp *
276326458Simp * If dp, dev or path NULL, return EDOOFUS. XXX wise?
277326458Simp *
278326458Simp * Set *path = NULL; *dev = NULL;
279326458Simp *
280326458Simp * Walk through the device_path until we find either a media device path.
281326458Simp * Return EINVAL if not found. Return EINVAL if walking dp would
282326458Simp * land us more than sanity size away from the start (4k).
283326458Simp *
284326458Simp * If we find a media descriptor, we search through the geom mesh to see if we
285326458Simp * can find a matching node. If no match is found in the mesh that matches,
286326458Simp * return ENXIO.
287326458Simp *
288326458Simp * Once we find a matching node, we search to see if there is a filesystem
289326458Simp * mounted on it. If we find nothing, then search each of the devices that are
290326458Simp * mounted to see if we can work up the geom tree to find the matching node. if
291326458Simp * we still can't find anything, *dev = sprintf("/dev/%s", provider_name
292326458Simp * of the original node we found), but return ENOTBLK.
293326458Simp *
294326458Simp * Record the dev of the mountpoint in *dev.
295326458Simp *
296326458Simp * Once we find something, check to see if the next node in the device path is
297326458Simp * the end of list. If so, return the mountpoint.
298326458Simp *
299326458Simp * If the next node isn't a File path node, return EFTYPE.
300326458Simp *
301326458Simp * Extract the path from the File path node(s). translate any \ file separators
302326458Simp * to /. Append the result to the mount point. Copy the resulting path into
303326458Simp * *path.  Stat that path. If it is not found, return the errorr from stat.
304326458Simp *
305326458Simp * Finally, check to make sure the resulting path is still on the same
306326458Simp * device. If not, return ENODEV.
307326458Simp *
308326458Simp * Otherwise return 0.
309326458Simp *
310326458Simp * The dev or full path that's returned is malloced, so needs to be freed when
311326458Simp * the caller is done about it. Unlike many other functions, we can return data
312326458Simp * with an error code, so pay attention.
313326458Simp */
314326458Simpint
315326458Simpefivar_device_path_to_unix_path(const_efidp dp, char **dev, char **relpath, char **abspath)
316326458Simp{
317326458Simp	const_efidp walker;
318326458Simp	struct gmesh mesh;
319326458Simp	int rv = 0;
320326458Simp
321326458Simp	/*
322326458Simp	 * Sanity check args, fail early
323326458Simp	 */
324326458Simp	if (dp == NULL || dev == NULL || relpath == NULL || abspath == NULL)
325326458Simp		return (EDOOFUS);
326326458Simp
327326458Simp	*dev = NULL;
328326458Simp	*relpath = NULL;
329326458Simp	*abspath = NULL;
330326458Simp
331326458Simp	/*
332326458Simp	 * Find the first media device path we can. If we go too far,
333326458Simp	 * assume the passed in device path is bogus. If we hit the end
334326458Simp	 * then we didn't find a media device path, so signal that error.
335326458Simp	 */
336326458Simp	walker = dp;
337326458Simp	while (DevicePathType(walker) != MEDIA_DEVICE_PATH &&
338326458Simp	    DevicePathType(walker) != END_DEVICE_PATH_TYPE) {
339326458Simp		walker = (const_efidp)NextDevicePathNode(walker);
340326458Simp		if ((uintptr_t)walker - (uintptr_t)dp > MAX_DP_SANITY)
341326458Simp			return (EINVAL);
342326458Simp	}
343326458Simp	if (DevicePathType(walker) !=  MEDIA_DEVICE_PATH)
344326458Simp		return (EINVAL);
345326458Simp
346326458Simp	/*
347326458Simp	 * There's several types of media paths. We're only interested in the
348326458Simp	 * hard disk path, as it's really the only relevant one to booting. The
349326458Simp	 * CD path just might also be relevant, and would be easy to add, but
350326458Simp	 * isn't supported. A file path too is relevant, but at this stage, it's
351326458Simp	 * premature because we're trying to translate a specification for a device
352326458Simp	 * and path on that device into a unix path, or at the very least, a
353326458Simp	 * geom device : path-on-device.
354326458Simp	 *
355326458Simp	 * Also, ZFS throws a bit of a monkey wrench in here since it doesn't have
356326458Simp	 * a device path type (it creates a new virtual device out of one or more
357326458Simp	 * storage devices).
358326458Simp	 *
359326458Simp	 * For all of them, we'll need to know the geoms, so allocate / free the
360326458Simp	 * geom mesh here since it's safer than doing it in each sub-function
361326458Simp	 * which may have many error exits.
362326458Simp	 */
363326458Simp	if (geom_gettree(&mesh))
364326458Simp		return (ENOMEM);
365326458Simp
366326458Simp	rv = EINVAL;
367326458Simp	if (DevicePathSubType(walker) == MEDIA_HARDDRIVE_DP)
368326458Simp		rv = efi_hd_to_unix(&mesh, walker, dev, relpath, abspath);
369326458Simp#ifdef notyet
370326458Simp	else if (is_cdrom_device(walker))
371326458Simp		rv = efi_cdrom_to_unix(&mesh, walker, dev, relpath, abspath);
372326458Simp	else if (is_floppy_device(walker))
373326458Simp		rv = efi_floppy_to_unix(&mesh, walker, dev, relpath, abspath);
374326458Simp	else if (is_zpool_device(walker))
375326458Simp		rv = efi_zpool_to_unix(&mesh, walker, dev, relpath, abspath);
376326458Simp#endif
377326458Simp	geom_deletetree(&mesh);
378326458Simp
379326458Simp	return (rv);
380326458Simp}
381326458Simp
382326458Simp/*
383326458Simp * Construct the EFI path to a current unix path as follows.
384326458Simp *
385326458Simp * The path may be of one of three forms:
386326458Simp *	1) /path/to/file -- full path to a file. The file need not be present,
387326458Simp *		but /path/to must be. It must reside on a local filesystem
388326458Simp *		mounted on a GPT or MBR partition.
389326458Simp *	2) //path/to/file -- Shorthand for 'On the EFI partition, \path\to\file'
390326458Simp *		where 'The EFI Partition' is a partiton that's type is 'efi'
391326458Simp *		on the same disk that / is mounted from. If there are multiple
392326458Simp *		or no 'efi' parittions on that disk, or / isn't on a disk that
393326458Simp *		we can trace back to a physical device, an error will result
394326458Simp *	3) [/dev/]geom-name:/path/to/file -- Use the specified partition
395326458Simp *		(and it must be a GPT or MBR partition) with the specified
396326458Simp *		path. The latter is not authenticated.
397326458Simp * all path forms translate any \ characters to / before further processing.
398326458Simp * When a file path node is created, all / characters are translated back
399326458Simp * to \.
400326458Simp *
401326458Simp * For paths of the first form:
402326458Simp *	find where the filesystem is mount (either the file directly, or
403326458Simp *		its parent directory).
404326458Simp *	translate any logical device name (eg lable) to a physical one
405326458Simp *	If not possible, return ENXIO
406326458Simp *	If the physical path is unsupported (Eg not on a GPT or MBR disk),
407326458Simp *		return ENXIO
408326458Simp *	Create a media device path node.
409326458Simp *	append the relative path from the mountpoint to the media device node
410326458Simp * 		as a file path.
411326458Simp *
412326458Simp * For paths matching the second form:
413326458Simp *	find the EFI partition corresponding to the root fileystem.
414326458Simp *	If none found, return ENXIO
415326458Simp *	Create a media device path node for the found partition
416326458Simp *	Append a File Path to the end for the rest of the file.
417326458Simp *
418326458Simp * For paths of the third form
419326458Simp *	Translate the geom-name passed in into a physical partition
420326458Simp *		name.
421326458Simp *	Return ENXIO if the translation fails
422326458Simp *	Make a media device path for it
423326458Simp *	append the part after the : as a File path node.
424326458Simp */
425326458Simp
426326458Simpstatic char *
427326458Simppath_to_file_dp(const char *relpath)
428326458Simp{
429326458Simp	char *rv;
430326458Simp
431326458Simp	asprintf(&rv, "File(%s)", relpath);
432326458Simp	return rv;
433326458Simp}
434326458Simp
435326458Simpstatic char *
436326458Simpfind_geom_efi_on_root(struct gmesh *mesh)
437326458Simp{
438326458Simp	struct statfs buf;
439326458Simp	const char *dev;
440326458Simp	struct gprovider *pp;
441326458Simp//	struct ggeom *disk;
442326458Simp	struct gconsumer *cp;
443326458Simp
444326458Simp	/*
445326458Simp	 * Find /'s geom. Assume it's mounted on /dev/ and filter out all the
446326458Simp	 * filesystems that aren't.
447326458Simp	 */
448326458Simp	if (statfs("/", &buf) != 0)
449326458Simp		return (NULL);
450326458Simp	dev = buf.f_mntfromname;
451326458Simp	if (*dev != '/' || strncmp(dev, _PATH_DEV, sizeof(_PATH_DEV) - 1) != 0)
452326458Simp		return (NULL);
453326458Simp	dev += sizeof(_PATH_DEV) -1;
454326458Simp	pp = find_provider_by_name(mesh, dev);
455326458Simp	if (pp == NULL)
456326458Simp		return (NULL);
457326458Simp
458326458Simp	/*
459326458Simp	 * If the provider is a LABEL, find it's outer PART class, if any. We
460326458Simp	 * only operate on partitions.
461326458Simp	 */
462326458Simp	if (strcmp(pp->lg_geom->lg_class->lg_name, G_LABEL) == 0) {
463326458Simp		LIST_FOREACH(cp, &pp->lg_consumers, lg_consumer) {
464326458Simp			if (strcmp(cp->lg_provider->lg_geom->lg_class->lg_name, G_PART) == 0) {
465326458Simp				pp = cp->lg_provider;
466326458Simp				break;
467326458Simp			}
468326458Simp		}
469326458Simp	}
470326458Simp	if (strcmp(pp->lg_geom->lg_class->lg_name, G_PART) != 0)
471326458Simp		return (NULL);
472326458Simp
473326458Simp#if 0
474326458Simp	/* This doesn't work because we can't get the data to walk UP the tree it seems */
475326458Simp
476326458Simp	/*
477326458Simp	 * Now that we've found the PART that we have mounted as root, find the
478326458Simp	 * first efi typed partition that's a peer, if any.
479326458Simp	 */
480326458Simp	LIST_FOREACH(cp, &pp->lg_consumers, lg_consumer) {
481326458Simp		if (strcmp(cp->lg_provider->lg_geom->lg_class->lg_name, G_DISK) == 0) {
482326458Simp			disk = cp->lg_provider->lg_geom;
483326458Simp			break;
484326458Simp		}
485326458Simp	}
486326458Simp	if (disk == NULL)	/* This is very bad -- old nested partitions -- no support ? */
487326458Simp		return (NULL);
488326458Simp#endif
489326458Simp
490326458Simp#if 0
491326458Simp	/* This doesn't work because we can't get the data to walk UP the tree it seems */
492326458Simp
493326458Simp	/*
494326458Simp	 * With the disk provider, we can look for its consumers to see if any are the proper type.
495326458Simp	 */
496326458Simp	LIST_FOREACH(pp, &disk->lg_consumer, lg_consumer) {
497326458Simp		type = geom_pp_attr(mesh, pp, "type");
498326458Simp		if (type == NULL)
499326458Simp			continue;
500326458Simp		if (strcmp(type, "efi") != 0)
501326458Simp			continue;
502326458Simp		efimedia = geom_pp_attr(mesh, pp, "efimedia");
503326458Simp		if (efimedia == NULL)
504326458Simp			return (NULL);
505326458Simp		return strdup(efimedia);
506326458Simp	}
507326458Simp#endif
508326458Simp	return (NULL);
509326458Simp}
510326458Simp
511326458Simp
512326458Simpstatic char *
513326458Simpfind_geom_efimedia(struct gmesh *mesh, const char *dev)
514326458Simp{
515326458Simp	struct gprovider *pp;
516326458Simp	const char *efimedia;
517326458Simp
518326458Simp	pp = find_provider_by_name(mesh, dev);
519326458Simp	if (pp == NULL)
520326458Simp		return (NULL);
521326458Simp	efimedia = geom_pp_attr(mesh, pp, "efimedia");
522326458Simp	if (efimedia == NULL)
523326458Simp		return (NULL);
524326458Simp	return strdup(efimedia);
525326458Simp}
526326458Simp
527326458Simpstatic int
528326458Simpbuild_dp(const char *efimedia, const char *relpath, efidp *dp)
529326458Simp{
530332123Skevans	char *fp, *dptxt = NULL, *cp, *rp;
531326458Simp	int rv = 0;
532332123Skevans	efidp out = NULL;
533326458Simp	size_t len;
534326458Simp
535332123Skevans	rp = strdup(relpath);
536332123Skevans	for (cp = rp; *cp; cp++)
537332123Skevans		if (*cp == '/')
538332123Skevans			*cp = '\\';
539332123Skevans	fp = path_to_file_dp(rp);
540332123Skevans	free(rp);
541326458Simp	if (fp == NULL) {
542326458Simp		rv = ENOMEM;
543326458Simp		goto errout;
544326458Simp	}
545326458Simp
546326458Simp	asprintf(&dptxt, "%s/%s", efimedia, fp);
547326458Simp	out = malloc(8192);
548326458Simp	len = efidp_parse_device_path(dptxt, out, 8192);
549326458Simp	if (len > 8192) {
550326458Simp		rv = ENOMEM;
551326458Simp		goto errout;
552326458Simp	}
553326458Simp	if (len == 0) {
554326458Simp		rv = EINVAL;
555326458Simp		goto errout;
556326458Simp	}
557326458Simp
558326458Simp	*dp = out;
559326458Simperrout:
560326458Simp	if (rv) {
561326458Simp		free(out);
562326458Simp	}
563326458Simp	free(dptxt);
564326458Simp	free(fp);
565326458Simp
566326458Simp	return rv;
567326458Simp}
568326458Simp
569326458Simp/* Handles //path/to/file */
570326458Simp/*
571326458Simp * Which means: find the disk that has /. Then look for a EFI partition
572326458Simp * and use that for the efimedia and /path/to/file as relative to that.
573326458Simp * Not sure how ZFS will work here since we can't easily make the leap
574326458Simp * to the geom from the zpool.
575326458Simp */
576326458Simpstatic int
577326458Simpefipart_to_dp(struct gmesh *mesh, char *path, efidp *dp)
578326458Simp{
579326458Simp	char *efimedia = NULL;
580326458Simp	int rv;
581326458Simp
582326458Simp	efimedia = find_geom_efi_on_root(mesh);
583326458Simp#ifdef notyet
584326458Simp	if (efimedia == NULL)
585326458Simp		efimedia = find_efi_on_zfsroot(dev);
586326458Simp#endif
587326458Simp	if (efimedia == NULL) {
588326458Simp		rv = ENOENT;
589326458Simp		goto errout;
590326458Simp	}
591326458Simp
592326458Simp	rv = build_dp(efimedia, path + 1, dp);
593326458Simperrout:
594326458Simp	free(efimedia);
595326458Simp
596326458Simp	return rv;
597326458Simp}
598326458Simp
599326458Simp/* Handles [/dev/]geom:[/]path/to/file */
600326458Simp/* Handles zfs-dataset:[/]path/to/file (this may include / ) */
601326458Simpstatic int
602326458Simpdev_path_to_dp(struct gmesh *mesh, char *path, efidp *dp)
603326458Simp{
604326458Simp	char *relpath, *dev, *efimedia = NULL;
605326458Simp	int rv = 0;
606326458Simp
607326458Simp	relpath = strchr(path, ':');
608326458Simp	assert(relpath != NULL);
609326458Simp	*relpath++ = '\0';
610326458Simp
611326458Simp	dev = path;
612326458Simp	if (strncmp(dev, _PATH_DEV, sizeof(_PATH_DEV) - 1) == 0)
613326458Simp		dev += sizeof(_PATH_DEV) -1;
614326458Simp
615326458Simp	efimedia = find_geom_efimedia(mesh, dev);
616326458Simp#ifdef notyet
617326458Simp	if (efimedia == NULL)
618326458Simp		find_zfs_efi_media(dev);
619326458Simp#endif
620326458Simp	if (efimedia == NULL) {
621326458Simp		rv = ENOENT;
622326458Simp		goto errout;
623326458Simp	}
624326458Simp	rv = build_dp(efimedia, relpath, dp);
625326458Simperrout:
626326458Simp	free(efimedia);
627326458Simp
628326458Simp	return rv;
629326458Simp}
630326458Simp
631326458Simp/* Handles /path/to/file */
632326458Simpstatic int
633326458Simppath_to_dp(struct gmesh *mesh, char *path, efidp *dp)
634326458Simp{
635326458Simp	struct statfs buf;
636326458Simp	char *rp = NULL, *ep, *dev, *efimedia = NULL;
637326458Simp	int rv = 0;
638326458Simp
639326458Simp	rp = realpath(path, NULL);
640326458Simp	if (rp == NULL) {
641326458Simp		rv = errno;
642326458Simp		goto errout;
643326458Simp	}
644326458Simp
645326458Simp	if (statfs(rp, &buf) != 0) {
646326458Simp		rv = errno;
647326458Simp		goto errout;
648326458Simp	}
649326458Simp
650326458Simp	dev = buf.f_mntfromname;
651326458Simp	if (strncmp(dev, _PATH_DEV, sizeof(_PATH_DEV) - 1) == 0)
652326458Simp		dev += sizeof(_PATH_DEV) -1;
653326458Simp	ep = rp + strlen(buf.f_mntonname);
654326458Simp
655326458Simp	efimedia = find_geom_efimedia(mesh, dev);
656326458Simp#ifdef notyet
657326458Simp	if (efimedia == NULL)
658326458Simp		find_zfs_efi_media(dev);
659326458Simp#endif
660326458Simp	if (efimedia == NULL) {
661326458Simp		rv = ENOENT;
662326458Simp		goto errout;
663326458Simp	}
664326458Simp
665326458Simp	rv = build_dp(efimedia, ep, dp);
666326458Simperrout:
667326458Simp	free(efimedia);
668326458Simp	free(rp);
669326458Simp	if (rv != 0) {
670326458Simp		free(*dp);
671332123Skevans		*dp = NULL;
672326458Simp	}
673326458Simp	return (rv);
674326458Simp}
675326458Simp
676326458Simpint
677326458Simpefivar_unix_path_to_device_path(const char *path, efidp *dp)
678326458Simp{
679326458Simp	char *modpath = NULL, *cp;
680326458Simp	int rv = ENOMEM;
681326458Simp	struct gmesh mesh;
682326458Simp
683326458Simp	/*
684326458Simp	 * Fail early for clearly bogus things
685326458Simp	 */
686326458Simp	if (path == NULL || dp == NULL)
687326458Simp		return (EDOOFUS);
688326458Simp
689326458Simp	/*
690326458Simp	 * We'll need the goem mesh to grovel through it to find the
691326458Simp	 * efimedia attribute for any devices we find. Grab it here
692326458Simp	 * and release it to simplify the error paths out of the
693326458Simp	 * subordinate functions
694326458Simp	 */
695326458Simp	if (geom_gettree(&mesh))
696326458Simp		return (errno);
697326458Simp
698326458Simp	/*
699326458Simp	 * Convert all \ to /. We'll convert them back again when
700326458Simp	 * we encode the file. Boot loaders are expected to cope.
701326458Simp	 */
702326458Simp	modpath = strdup(path);
703326458Simp	if (modpath == NULL)
704326458Simp		goto out;
705326458Simp	for (cp = modpath; *cp; cp++)
706326458Simp		if (*cp == '\\')
707326458Simp			*cp = '/';
708326458Simp
709326458Simp	if (modpath[0] == '/' && modpath[1] == '/')	/* Handle //foo/bar/baz */
710326458Simp		rv = efipart_to_dp(&mesh, modpath, dp);
711326458Simp	else if (strchr(modpath, ':'))			/* Handle dev:/bar/baz */
712326458Simp		rv = dev_path_to_dp(&mesh, modpath, dp);
713326458Simp	else						/* Handle /a/b/c */
714326458Simp		rv = path_to_dp(&mesh, modpath, dp);
715326458Simp
716326458Simpout:
717326458Simp	geom_deletetree(&mesh);
718326458Simp	free(modpath);
719326458Simp
720326458Simp	return (rv);
721326458Simp}
722