/*-1* Copyright (c) 2017 Netflix, Inc.2*3* Redistribution and use in source and binary forms, with or without4* modification, are permitted provided that the following conditions5* are met:6* 1. Redistributions of source code must retain the above copyright7* notice, this list of conditions and the following disclaimer.8* 2. Redistributions in binary form must reproduce the above copyright9* notice, this list of conditions and the following disclaimer in the10* documentation and/or other materials provided with the distribution.11*12* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND13* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE14* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE15* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE16* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL17* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS18* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)19* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT20* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY21* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF22* SUCH DAMAGE.23*/2425#include <sys/param.h>26#include <sys/ucred.h>27#include <sys/mount.h>2829#undef MAX30#undef MIN3132#include <assert.h>33#include <efivar.h>34#include <errno.h>35#include <libgeom.h>36#include <paths.h>37#include <stdio.h>38#include <string.h>3940#include "efichar.h"41#include "efivar-dp.h"42#include "uefi-dplib.h"4344#define MAX_DP_SANITY 4096 /* Biggest device path in bytes */45#define MAX_DP_TEXT_LEN 4096 /* Longest string rep of dp */4647#define ValidLen(dp) (DevicePathNodeLength(dp) >= sizeof(EFI_DEVICE_PATH_PROTOCOL) && \48DevicePathNodeLength(dp) < MAX_DP_SANITY)4950#define G_PART "PART"51#define G_LABEL "LABEL"52#define G_DISK "DISK"5354static const char *55geom_pp_attr(struct gmesh *mesh, struct gprovider *pp, const char *attr)56{57struct gconfig *conf;5859LIST_FOREACH(conf, &pp->lg_config, lg_config) {60if (strcmp(conf->lg_name, attr) != 0)61continue;62return (conf->lg_val);63}64return (NULL);65}6667static struct gprovider *68find_provider_by_efimedia(struct gmesh *mesh, const char *efimedia)69{70struct gclass *classp;71struct ggeom *gp;72struct gprovider *pp;73const char *val;7475/*76* Find the partition class so we can search it...77*/78LIST_FOREACH(classp, &mesh->lg_class, lg_class) {79if (strcasecmp(classp->lg_name, G_PART) == 0)80break;81}82if (classp == NULL)83return (NULL);8485/*86* Each geom will have a number of providers, search each87* one of them for the efimedia that matches.88*/89/* XXX just used gpart class since I know it's the only one, but maybe I should search all classes */90LIST_FOREACH(gp, &classp->lg_geom, lg_geom) {91LIST_FOREACH(pp, &gp->lg_provider, lg_provider) {92val = geom_pp_attr(mesh, pp, "efimedia");93if (val == NULL)94continue;95if (strcasecmp(efimedia, val) == 0)96return (pp);97}98}99100return (NULL);101}102103static struct gprovider *104find_provider_by_name(struct gmesh *mesh, const char *name)105{106struct gclass *classp;107struct ggeom *gp;108struct gprovider *pp;109110LIST_FOREACH(classp, &mesh->lg_class, lg_class) {111LIST_FOREACH(gp, &classp->lg_geom, lg_geom) {112LIST_FOREACH(pp, &gp->lg_provider, lg_provider) {113if (strcmp(pp->lg_name, name) == 0)114return (pp);115}116}117}118119return (NULL);120}121122123static int124efi_hd_to_unix(struct gmesh *mesh, const_efidp dp, char **dev, char **relpath, char **abspath)125{126int rv = 0, n, i;127const_efidp media, file, walker;128size_t len, mntlen;129char buf[MAX_DP_TEXT_LEN];130char *pwalk, *newdev = NULL;131struct gprovider *pp, *provider;132struct statfs *mnt;133struct gclass *glabel;134struct ggeom *gp;135136walker = media = dp;137*dev = NULL;138*relpath = NULL;139140/*141* Now, we can either have a filepath node next, or the end.142* Otherwise, it's an error.143*/144if (!ValidLen(walker))145return (EINVAL);146walker = (const_efidp)NextDevicePathNode(walker);147if ((uintptr_t)walker - (uintptr_t)dp > MAX_DP_SANITY)148return (EINVAL);149if (DevicePathType(walker) == MEDIA_DEVICE_PATH &&150DevicePathSubType(walker) == MEDIA_FILEPATH_DP)151file = walker;152else if (DevicePathType(walker) == MEDIA_DEVICE_PATH &&153DevicePathType(walker) == END_DEVICE_PATH_TYPE)154file = NULL;155else156return (EINVAL);157158/*159* Format this node. We're going to look for it as a efimedia160* attribute of some geom node. Once we find that node, we use it161* as the device it comes from, at least provisionally.162*/163len = efidp_format_device_path_node(buf, sizeof(buf), media);164if (len > sizeof(buf))165return (EINVAL);166167pp = find_provider_by_efimedia(mesh, buf);168if (pp == NULL) {169rv = ENOENT;170goto errout;171}172173/*174* No file specified, just return the device. Don't even look175* for a mountpoint. XXX Sane?176*/177if (file == NULL)178goto errout;179180/*181* Now extract the relative path. The next node in the device path should182* be a filesystem node. If not, we have issues.183*/184*relpath = efidp_extract_file_path(file);185if (*relpath == NULL) {186rv = ENOMEM;187goto errout;188}189for (pwalk = *relpath; *pwalk; pwalk++)190if (*pwalk == '\\')191*pwalk = '/';192193/*194* To find the absolute path, we have to look for where we're mounted.195* We only look a little hard, since looking too hard can come up with196* false positives (imagine a graid, one of whose devices is *dev).197*/198n = getfsstat(NULL, 0, MNT_NOWAIT) + 1;199if (n < 0) {200rv = errno;201goto errout;202}203mntlen = sizeof(struct statfs) * n;204mnt = malloc(mntlen);205n = getfsstat(mnt, mntlen, MNT_NOWAIT);206if (n < 0) {207rv = errno;208goto errout;209}210211/*212* Find glabel, if it exists. It's OK if not: we'll skip searching for213* labels.214*/215LIST_FOREACH(glabel, &mesh->lg_class, lg_class) {216if (strcmp(glabel->lg_name, G_LABEL) == 0)217break;218}219220provider = pp;221for (i = 0; i < n; i++) {222/*223* Skip all pseudo filesystems. This also skips the real filesystem224* of ZFS. There's no EFI designator for ZFS in the standard, so225* we'll need to invent one, but its decoding will be handled in226* a separate function.227*/228if (strncmp(mnt[i].f_mntfromname, "/dev/", 5) != 0)229continue;230231/*232* First see if it is directly attached233*/234if (strcmp(provider->lg_name, mnt[i].f_mntfromname + 5) == 0) {235newdev = provider->lg_name;236break;237}238239/*240* Next see if it is attached via one of the physical disk's labels.241* We can't search directly from the pointers we have for the242* provider, so we have to cast a wider net for all labels and243* filter those down to geoms whose name matches the PART provider244* we found the efimedia attribute on.245*/246if (glabel == NULL)247continue;248LIST_FOREACH(gp, &glabel->lg_geom, lg_geom) {249if (strcmp(gp->lg_name, provider->lg_name) != 0) {250continue;251}252LIST_FOREACH(pp, &gp->lg_provider, lg_provider) {253if (strcmp(pp->lg_name, mnt[i].f_mntfromname + 5) == 0) {254newdev = pp->lg_name;255goto break2;256}257}258}259/* Not the one, try the next mount point */260}261break2:262263/*264* If nothing better was mounted, then use the provider we found as265* is. It's the most correct thing we can return in that acse.266*/267if (newdev == NULL)268newdev = provider->lg_name;269*dev = strdup(newdev);270if (*dev == NULL) {271rv = ENOMEM;272goto errout;273}274275/*276* No mountpoint found, no absolute path possible277*/278if (i >= n)279goto errout;280281/*282* Construct absolute path and we're finally done.283*/284if (strcmp(mnt[i].f_mntonname, "/") == 0)285asprintf(abspath, "/%s", *relpath);286else287asprintf(abspath, "%s/%s", mnt[i].f_mntonname, *relpath);288289errout:290if (rv != 0) {291free(*dev);292*dev = NULL;293free(*relpath);294*relpath = NULL;295}296return (rv);297}298299/*300* Translate the passed in device_path to a unix path via the following301* algorithm.302*303* If dp, dev or path NULL, return EDOOFUS. XXX wise?304*305* Set *path = NULL; *dev = NULL;306*307* Walk through the device_path until we find either a media device path.308* Return EINVAL if not found. Return EINVAL if walking dp would309* land us more than sanity size away from the start (4k).310*311* If we find a media descriptor, we search through the geom mesh to see if we312* can find a matching node. If no match is found in the mesh that matches,313* return ENXIO.314*315* Once we find a matching node, we search to see if there is a filesystem316* mounted on it. If we find nothing, then search each of the devices that are317* mounted to see if we can work up the geom tree to find the matching node. if318* we still can't find anything, *dev = sprintf("/dev/%s", provider_name319* of the original node we found), but return ENOTBLK.320*321* Record the dev of the mountpoint in *dev.322*323* Once we find something, check to see if the next node in the device path is324* the end of list. If so, return the mountpoint.325*326* If the next node isn't a File path node, return EFTYPE.327*328* Extract the path from the File path node(s). translate any \ file separators329* to /. Append the result to the mount point. Copy the resulting path into330* *path. Stat that path. If it is not found, return the error from stat.331*332* Finally, check to make sure the resulting path is still on the same333* device. If not, return ENODEV.334*335* Otherwise return 0.336*337* The dev or full path that's returned is malloced, so needs to be freed when338* the caller is done about it. Unlike many other functions, we can return data339* with an error code, so pay attention.340*/341int342efivar_device_path_to_unix_path(const_efidp dp, char **dev, char **relpath, char **abspath)343{344const_efidp walker;345struct gmesh mesh;346int rv = 0;347348/*349* Sanity check args, fail early350*/351if (dp == NULL || dev == NULL || relpath == NULL || abspath == NULL)352return (EDOOFUS);353354*dev = NULL;355*relpath = NULL;356*abspath = NULL;357358/*359* Find the first media device path we can. If we go too far,360* assume the passed in device path is bogus. If we hit the end361* then we didn't find a media device path, so signal that error.362*/363walker = dp;364if (!ValidLen(walker))365return (EINVAL);366while (DevicePathType(walker) != MEDIA_DEVICE_PATH &&367DevicePathType(walker) != END_DEVICE_PATH_TYPE) {368walker = (const_efidp)NextDevicePathNode(walker);369if ((uintptr_t)walker - (uintptr_t)dp > MAX_DP_SANITY)370return (EINVAL);371if (!ValidLen(walker))372return (EINVAL);373}374if (DevicePathType(walker) != MEDIA_DEVICE_PATH)375return (EINVAL);376377/*378* There's several types of media paths. We're only interested in the379* hard disk path, as it's really the only relevant one to booting. The380* CD path just might also be relevant, and would be easy to add, but381* isn't supported. A file path too is relevant, but at this stage, it's382* premature because we're trying to translate a specification for a device383* and path on that device into a unix path, or at the very least, a384* geom device : path-on-device.385*386* Also, ZFS throws a bit of a monkey wrench in here since it doesn't have387* a device path type (it creates a new virtual device out of one or more388* storage devices).389*390* For all of them, we'll need to know the geoms, so allocate / free the391* geom mesh here since it's safer than doing it in each sub-function392* which may have many error exits.393*/394if (geom_gettree(&mesh))395return (ENOMEM);396397rv = EINVAL;398if (DevicePathSubType(walker) == MEDIA_HARDDRIVE_DP)399rv = efi_hd_to_unix(&mesh, walker, dev, relpath, abspath);400#ifdef notyet401else if (is_cdrom_device(walker))402rv = efi_cdrom_to_unix(&mesh, walker, dev, relpath, abspath);403else if (is_floppy_device(walker))404rv = efi_floppy_to_unix(&mesh, walker, dev, relpath, abspath);405else if (is_zpool_device(walker))406rv = efi_zpool_to_unix(&mesh, walker, dev, relpath, abspath);407#endif408geom_deletetree(&mesh);409410return (rv);411}412413/*414* Construct the EFI path to a current unix path as follows.415*416* The path may be of one of three forms:417* 1) /path/to/file -- full path to a file. The file need not be present,418* but /path/to must be. It must reside on a local filesystem419* mounted on a GPT or MBR partition.420* 2) //path/to/file -- Shorthand for 'On the EFI partition, \path\to\file'421* where 'The EFI Partition' is a partition that's type is 'efi'422* on the same disk that / is mounted from. If there are multiple423* or no 'efi' parittions on that disk, or / isn't on a disk that424* we can trace back to a physical device, an error will result425* 3) [/dev/]geom-name:/path/to/file -- Use the specified partition426* (and it must be a GPT or MBR partition) with the specified427* path. The latter is not authenticated.428* all path forms translate any \ characters to / before further processing.429* When a file path node is created, all / characters are translated back430* to \.431*432* For paths of the first form:433* find where the filesystem is mount (either the file directly, or434* its parent directory).435* translate any logical device name (eg label) to a physical one436* If not possible, return ENXIO437* If the physical path is unsupported (Eg not on a GPT or MBR disk),438* return ENXIO439* Create a media device path node.440* append the relative path from the mountpoint to the media device node441* as a file path.442*443* For paths matching the second form:444* find the EFI partition corresponding to the root filesystem.445* If none found, return ENXIO446* Create a media device path node for the found partition447* Append a File Path to the end for the rest of the file.448*449* For paths of the third form450* Translate the geom-name passed in into a physical partition451* name.452* Return ENXIO if the translation fails453* Make a media device path for it454* append the part after the : as a File path node.455*/456457static char *458path_to_file_dp(const char *relpath)459{460char *rv;461462asprintf(&rv, "File(%s)", relpath);463return rv;464}465466static char *467find_geom_efi_on_root(struct gmesh *mesh)468{469struct statfs buf;470const char *dev;471struct gprovider *pp;472// struct ggeom *disk;473struct gconsumer *cp;474475/*476* Find /'s geom. Assume it's mounted on /dev/ and filter out all the477* filesystems that aren't.478*/479if (statfs("/", &buf) != 0)480return (NULL);481dev = buf.f_mntfromname;482if (*dev != '/' || strncmp(dev, _PATH_DEV, sizeof(_PATH_DEV) - 1) != 0)483return (NULL);484dev += sizeof(_PATH_DEV) -1;485pp = find_provider_by_name(mesh, dev);486if (pp == NULL)487return (NULL);488489/*490* If the provider is a LABEL, find it's outer PART class, if any. We491* only operate on partitions.492*/493if (strcmp(pp->lg_geom->lg_class->lg_name, G_LABEL) == 0) {494LIST_FOREACH(cp, &pp->lg_consumers, lg_consumer) {495if (strcmp(cp->lg_provider->lg_geom->lg_class->lg_name, G_PART) == 0) {496pp = cp->lg_provider;497break;498}499}500}501if (strcmp(pp->lg_geom->lg_class->lg_name, G_PART) != 0)502return (NULL);503504#if 0505/* This doesn't work because we can't get the data to walk UP the tree it seems */506507/*508* Now that we've found the PART that we have mounted as root, find the509* first efi typed partition that's a peer, if any.510*/511LIST_FOREACH(cp, &pp->lg_consumers, lg_consumer) {512if (strcmp(cp->lg_provider->lg_geom->lg_class->lg_name, G_DISK) == 0) {513disk = cp->lg_provider->lg_geom;514break;515}516}517if (disk == NULL) /* This is very bad -- old nested partitions -- no support ? */518return (NULL);519#endif520521#if 0522/* This doesn't work because we can't get the data to walk UP the tree it seems */523524/*525* With the disk provider, we can look for its consumers to see if any are the proper type.526*/527LIST_FOREACH(pp, &disk->lg_consumer, lg_consumer) {528type = geom_pp_attr(mesh, pp, "type");529if (type == NULL)530continue;531if (strcmp(type, "efi") != 0)532continue;533efimedia = geom_pp_attr(mesh, pp, "efimedia");534if (efimedia == NULL)535return (NULL);536return strdup(efimedia);537}538#endif539return (NULL);540}541542543static char *544find_geom_efimedia(struct gmesh *mesh, const char *dev)545{546struct gprovider *pp;547const char *efimedia;548549pp = find_provider_by_name(mesh, dev);550if (pp == NULL)551return (NULL);552efimedia = geom_pp_attr(mesh, pp, "efimedia");553554/*555* If this device doesn't have an efimedia attribute, see if it is a556* glabel node, and if so look for the underlying provider to get the557* efimedia attribute from.558*/559if (efimedia == NULL &&560strcmp(pp->lg_geom->lg_class->lg_name, G_LABEL) == 0)561efimedia = find_geom_efimedia(mesh, pp->lg_geom->lg_name);562if (efimedia == NULL)563return (NULL);564return strdup(efimedia);565}566567static int568build_dp(const char *efimedia, const char *relpath, efidp *dp)569{570char *fp = NULL, *dptxt = NULL, *cp, *rp = NULL;571int rv = 0;572efidp out = NULL;573size_t len;574575if (relpath != NULL) {576rp = strdup(relpath);577for (cp = rp; *cp; cp++)578if (*cp == '/')579*cp = '\\';580fp = path_to_file_dp(rp);581free(rp);582if (fp == NULL) {583rv = ENOMEM;584goto errout;585}586}587588asprintf(&dptxt, "%s/%s", efimedia, fp == NULL ? "" : fp);589out = malloc(8192);590len = efidp_parse_device_path(dptxt, out, 8192);591if (len > 8192) {592rv = ENOMEM;593goto errout;594}595if (len == 0) {596rv = EINVAL;597goto errout;598}599600*dp = out;601errout:602if (rv) {603free(out);604}605free(dptxt);606free(fp);607608return rv;609}610611/* Handles //path/to/file */612/*613* Which means: find the disk that has /. Then look for a EFI partition614* and use that for the efimedia and /path/to/file as relative to that.615* Not sure how ZFS will work here since we can't easily make the leap616* to the geom from the zpool.617*/618static int619efipart_to_dp(struct gmesh *mesh, char *path, efidp *dp)620{621char *efimedia = NULL;622int rv;623624efimedia = find_geom_efi_on_root(mesh);625#ifdef notyet626if (efimedia == NULL)627efimedia = find_efi_on_zfsroot(dev);628#endif629if (efimedia == NULL) {630rv = ENOENT;631goto errout;632}633634rv = build_dp(efimedia, path + 1, dp);635errout:636free(efimedia);637638return rv;639}640641/* Handles [/dev/]geom:[/]path/to/file */642/* Handles zfs-dataset:[/]path/to/file (this may include / ) */643static int644dev_path_to_dp(struct gmesh *mesh, char *path, efidp *dp)645{646char *relpath, *dev, *efimedia = NULL;647int rv = 0;648649relpath = strchr(path, ':');650assert(relpath != NULL);651*relpath++ = '\0';652653dev = path;654if (strncmp(dev, _PATH_DEV, sizeof(_PATH_DEV) - 1) == 0)655dev += sizeof(_PATH_DEV) -1;656657efimedia = find_geom_efimedia(mesh, dev);658#ifdef notyet659if (efimedia == NULL)660find_zfs_efi_media(dev);661#endif662if (efimedia == NULL) {663rv = ENOENT;664goto errout;665}666rv = build_dp(efimedia, relpath, dp);667errout:668free(efimedia);669670return rv;671}672673/* Handles /path/to/file */674/* Handles /dev/foo/bar */675static int676path_to_dp(struct gmesh *mesh, char *path, efidp *dp)677{678struct statfs buf;679char *rp = NULL, *ep, *dev, *efimedia = NULL;680int rv = 0;681682rp = realpath(path, NULL);683if (rp == NULL) {684rv = errno;685goto errout;686}687688if (statfs(rp, &buf) != 0) {689rv = errno;690goto errout;691}692693dev = buf.f_mntfromname;694/*695* If we're fed a raw /dev/foo/bar, then devfs is returned from the696* statfs call. In that case, use that dev and assume we have a path697* of nothing.698*/699if (strcmp(dev, "devfs") == 0) {700dev = rp + sizeof(_PATH_DEV) - 1;701ep = NULL;702} else {703if (strncmp(dev, _PATH_DEV, sizeof(_PATH_DEV) - 1) == 0)704dev += sizeof(_PATH_DEV) - 1;705ep = rp + strlen(buf.f_mntonname);706}707708efimedia = find_geom_efimedia(mesh, dev);709#ifdef notyet710if (efimedia == NULL)711find_zfs_efi_media(dev);712#endif713if (efimedia == NULL) {714rv = ENOENT;715goto errout;716}717718rv = build_dp(efimedia, ep, dp);719errout:720free(efimedia);721free(rp);722if (rv != 0) {723free(*dp);724*dp = NULL;725}726return (rv);727}728729int730efivar_unix_path_to_device_path(const char *path, efidp *dp)731{732char *modpath = NULL, *cp;733int rv = ENOMEM;734struct gmesh mesh;735736/*737* Fail early for clearly bogus things738*/739if (path == NULL || dp == NULL)740return (EDOOFUS);741742/*743* We'll need the goem mesh to grovel through it to find the744* efimedia attribute for any devices we find. Grab it here745* and release it to simplify the error paths out of the746* subordinate functions747*/748if (geom_gettree(&mesh))749return (errno);750751/*752* Convert all \ to /. We'll convert them back again when753* we encode the file. Boot loaders are expected to cope.754*/755modpath = strdup(path);756if (modpath == NULL)757goto out;758for (cp = modpath; *cp; cp++)759if (*cp == '\\')760*cp = '/';761762if (modpath[0] == '/' && modpath[1] == '/') /* Handle //foo/bar/baz */763rv = efipart_to_dp(&mesh, modpath, dp);764else if (strchr(modpath, ':')) /* Handle dev:/bar/baz */765rv = dev_path_to_dp(&mesh, modpath, dp);766else /* Handle /a/b/c */767rv = path_to_dp(&mesh, modpath, dp);768769out:770geom_deletetree(&mesh);771free(modpath);772773return (rv);774}775776777