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