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: stable/11/lib/libefivar/efivar-dp-xlate.c 332123 2018-04-06 18:10:38Z kevans $");
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
59static const char *
60geom_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
72static struct gprovider *
73find_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
108static struct gprovider *
109find_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
128static int
129efi_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	}
246break2:
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
262errout:
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 */
314int
315efivar_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
426static char *
427path_to_file_dp(const char *relpath)
428{
429	char *rv;
430
431	asprintf(&rv, "File(%s)", relpath);
432	return rv;
433}
434
435static char *
436find_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
512static char *
513find_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
527static int
528build_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;
559errout:
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 */
576static int
577efipart_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);
593errout:
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 / ) */
601static int
602dev_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);
625errout:
626	free(efimedia);
627
628	return rv;
629}
630
631/* Handles /path/to/file */
632static int
633path_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);
666errout:
667	free(efimedia);
668	free(rp);
669	if (rv != 0) {
670		free(*dp);
671		*dp = NULL;
672	}
673	return (rv);
674}
675
676int
677efivar_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
716out:
717	geom_deletetree(&mesh);
718	free(modpath);
719
720	return (rv);
721}
722