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