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
58static const char *
59geom_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
71static struct gprovider *
72find_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
107static struct gprovider *
108find_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
127static int
128efi_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	}
245break2:
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
261errout:
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 */
313int
314efivar_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
425static char *
426path_to_file_dp(const char *relpath)
427{
428	char *rv;
429
430	asprintf(&rv, "File(%s)", relpath);
431	return rv;
432}
433
434static char *
435find_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
511static char *
512find_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
526static int
527build_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;
558errout:
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 */
575static int
576efipart_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);
592errout:
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 / ) */
600static int
601dev_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);
624errout:
625	free(efimedia);
626
627	return rv;
628}
629
630/* Handles /path/to/file */
631static int
632path_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);
665errout:
666	free(efimedia);
667	free(rp);
668	if (rv != 0) {
669		free(*dp);
670		*dp = NULL;
671	}
672	return (rv);
673}
674
675int
676efivar_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
715out:
716	geom_deletetree(&mesh);
717	free(modpath);
718
719	return (rv);
720}
721