/*-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"4142#include "efi-osdep.h"43#include "efivar-dp.h"4445#include "uefi-dplib.h"4647#define MAX_DP_SANITY 4096 /* Biggest device path in bytes */48#define MAX_DP_TEXT_LEN 4096 /* Longest string rep of dp */4950#define ValidLen(dp) (DevicePathNodeLength(dp) >= sizeof(EFI_DEVICE_PATH_PROTOCOL) && \51DevicePathNodeLength(dp) < MAX_DP_SANITY)5253#define G_PART "PART"54#define G_LABEL "LABEL"55#define G_DISK "DISK"5657static const char *58geom_pp_attr(struct gmesh *mesh, struct gprovider *pp, const char *attr)59{60struct gconfig *conf;6162LIST_FOREACH(conf, &pp->lg_config, lg_config) {63if (strcmp(conf->lg_name, attr) != 0)64continue;65return (conf->lg_val);66}67return (NULL);68}6970static struct gprovider *71find_provider_by_efimedia(struct gmesh *mesh, const char *efimedia)72{73struct gclass *classp;74struct ggeom *gp;75struct gprovider *pp;76const char *val;7778/*79* Find the partition class so we can search it...80*/81LIST_FOREACH(classp, &mesh->lg_class, lg_class) {82if (strcasecmp(classp->lg_name, G_PART) == 0)83break;84}85if (classp == NULL)86return (NULL);8788/*89* Each geom will have a number of providers, search each90* one of them for the efimedia that matches.91*/92/* XXX just used gpart class since I know it's the only one, but maybe I should search all classes */93LIST_FOREACH(gp, &classp->lg_geom, lg_geom) {94LIST_FOREACH(pp, &gp->lg_provider, lg_provider) {95val = geom_pp_attr(mesh, pp, "efimedia");96if (val == NULL)97continue;98if (strcasecmp(efimedia, val) == 0)99return (pp);100}101}102103return (NULL);104}105106static struct gprovider *107find_provider_by_name(struct gmesh *mesh, const char *name)108{109struct gclass *classp;110struct ggeom *gp;111struct gprovider *pp;112113LIST_FOREACH(classp, &mesh->lg_class, lg_class) {114LIST_FOREACH(gp, &classp->lg_geom, lg_geom) {115LIST_FOREACH(pp, &gp->lg_provider, lg_provider) {116if (strcmp(pp->lg_name, name) == 0)117return (pp);118}119}120}121122return (NULL);123}124125126static int127efi_hd_to_unix(struct gmesh *mesh, const_efidp dp, char **dev, char **relpath, char **abspath)128{129int rv = 0, n, i;130const_efidp media, file, walker;131size_t len, mntlen;132char buf[MAX_DP_TEXT_LEN];133char *pwalk, *newdev = NULL;134struct gprovider *pp, *provider;135struct statfs *mnt;136struct gclass *glabel;137struct ggeom *gp;138139walker = media = dp;140*dev = NULL;141*relpath = NULL;142143/*144* Now, we can either have a filepath node next, or the end.145* Otherwise, it's an error.146*/147if (!ValidLen(walker))148return (EINVAL);149walker = (const_efidp)NextDevicePathNode(walker);150if ((uintptr_t)walker - (uintptr_t)dp > MAX_DP_SANITY)151return (EINVAL);152if (DevicePathType(walker) == MEDIA_DEVICE_PATH &&153DevicePathSubType(walker) == MEDIA_FILEPATH_DP)154file = walker;155else if (DevicePathType(walker) == MEDIA_DEVICE_PATH &&156DevicePathType(walker) == END_DEVICE_PATH_TYPE)157file = NULL;158else159return (EINVAL);160161/*162* Format this node. We're going to look for it as a efimedia163* attribute of some geom node. Once we find that node, we use it164* as the device it comes from, at least provisionally.165*/166len = efidp_format_device_path_node(buf, sizeof(buf), media);167if (len > sizeof(buf))168return (EINVAL);169170pp = find_provider_by_efimedia(mesh, buf);171if (pp == NULL) {172rv = ENOENT;173goto errout;174}175176/*177* No file specified, just return the device. Don't even look178* for a mountpoint. XXX Sane?179*/180if (file == NULL)181goto errout;182183/*184* Now extract the relative path. The next node in the device path should185* be a filesystem node. If not, we have issues.186*/187*relpath = efidp_extract_file_path(file);188if (*relpath == NULL) {189rv = ENOMEM;190goto errout;191}192for (pwalk = *relpath; *pwalk; pwalk++)193if (*pwalk == '\\')194*pwalk = '/';195196/*197* To find the absolute path, we have to look for where we're mounted.198* We only look a little hard, since looking too hard can come up with199* false positives (imagine a graid, one of whose devices is *dev).200*/201n = getfsstat(NULL, 0, MNT_NOWAIT) + 1;202if (n < 0) {203rv = errno;204goto errout;205}206mntlen = sizeof(struct statfs) * n;207mnt = malloc(mntlen);208n = getfsstat(mnt, mntlen, MNT_NOWAIT);209if (n < 0) {210rv = errno;211goto errout;212}213214/*215* Find glabel, if it exists. It's OK if not: we'll skip searching for216* labels.217*/218LIST_FOREACH(glabel, &mesh->lg_class, lg_class) {219if (strcmp(glabel->lg_name, G_LABEL) == 0)220break;221}222223provider = pp;224for (i = 0; i < n; i++) {225/*226* Skip all pseudo filesystems. This also skips the real filesytsem227* of ZFS. There's no EFI designator for ZFS in the standard, so228* we'll need to invent one, but its decoding will be handled in229* a separate function.230*/231if (strncmp(mnt[i].f_mntfromname, "/dev/", 5) != 0)232continue;233234/*235* First see if it is directly attached236*/237if (strcmp(provider->lg_name, mnt[i].f_mntfromname + 5) == 0) {238newdev = provider->lg_name;239break;240}241242/*243* Next see if it is attached via one of the physical disk's labels.244* We can't search directly from the pointers we have for the245* provider, so we have to cast a wider net for all labels and246* filter those down to geoms whose name matches the PART provider247* we found the efimedia attribute on.248*/249if (glabel == NULL)250continue;251LIST_FOREACH(gp, &glabel->lg_geom, lg_geom) {252if (strcmp(gp->lg_name, provider->lg_name) != 0) {253continue;254}255LIST_FOREACH(pp, &gp->lg_provider, lg_provider) {256if (strcmp(pp->lg_name, mnt[i].f_mntfromname + 5) == 0) {257newdev = pp->lg_name;258goto break2;259}260}261}262/* Not the one, try the next mount point */263}264break2:265266/*267* If nothing better was mounted, then use the provider we found as268* is. It's the most correct thing we can return in that acse.269*/270if (newdev == NULL)271newdev = provider->lg_name;272*dev = strdup(newdev);273if (*dev == NULL) {274rv = ENOMEM;275goto errout;276}277278/*279* No mountpoint found, no absolute path possible280*/281if (i >= n)282goto errout;283284/*285* Construct absolute path and we're finally done.286*/287if (strcmp(mnt[i].f_mntonname, "/") == 0)288asprintf(abspath, "/%s", *relpath);289else290asprintf(abspath, "%s/%s", mnt[i].f_mntonname, *relpath);291292errout:293if (rv != 0) {294free(*dev);295*dev = NULL;296free(*relpath);297*relpath = NULL;298}299return (rv);300}301302/*303* Translate the passed in device_path to a unix path via the following304* algorithm.305*306* If dp, dev or path NULL, return EDOOFUS. XXX wise?307*308* Set *path = NULL; *dev = NULL;309*310* Walk through the device_path until we find either a media device path.311* Return EINVAL if not found. Return EINVAL if walking dp would312* land us more than sanity size away from the start (4k).313*314* If we find a media descriptor, we search through the geom mesh to see if we315* can find a matching node. If no match is found in the mesh that matches,316* return ENXIO.317*318* Once we find a matching node, we search to see if there is a filesystem319* mounted on it. If we find nothing, then search each of the devices that are320* mounted to see if we can work up the geom tree to find the matching node. if321* we still can't find anything, *dev = sprintf("/dev/%s", provider_name322* of the original node we found), but return ENOTBLK.323*324* Record the dev of the mountpoint in *dev.325*326* Once we find something, check to see if the next node in the device path is327* the end of list. If so, return the mountpoint.328*329* If the next node isn't a File path node, return EFTYPE.330*331* Extract the path from the File path node(s). translate any \ file separators332* to /. Append the result to the mount point. Copy the resulting path into333* *path. Stat that path. If it is not found, return the errorr from stat.334*335* Finally, check to make sure the resulting path is still on the same336* device. If not, return ENODEV.337*338* Otherwise return 0.339*340* The dev or full path that's returned is malloced, so needs to be freed when341* the caller is done about it. Unlike many other functions, we can return data342* with an error code, so pay attention.343*/344int345efivar_device_path_to_unix_path(const_efidp dp, char **dev, char **relpath, char **abspath)346{347const_efidp walker;348struct gmesh mesh;349int rv = 0;350351/*352* Sanity check args, fail early353*/354if (dp == NULL || dev == NULL || relpath == NULL || abspath == NULL)355return (EDOOFUS);356357*dev = NULL;358*relpath = NULL;359*abspath = NULL;360361/*362* Find the first media device path we can. If we go too far,363* assume the passed in device path is bogus. If we hit the end364* then we didn't find a media device path, so signal that error.365*/366walker = dp;367if (!ValidLen(walker))368return (EINVAL);369while (DevicePathType(walker) != MEDIA_DEVICE_PATH &&370DevicePathType(walker) != END_DEVICE_PATH_TYPE) {371walker = (const_efidp)NextDevicePathNode(walker);372if ((uintptr_t)walker - (uintptr_t)dp > MAX_DP_SANITY)373return (EINVAL);374if (!ValidLen(walker))375return (EINVAL);376}377if (DevicePathType(walker) != MEDIA_DEVICE_PATH)378return (EINVAL);379380/*381* There's several types of media paths. We're only interested in the382* hard disk path, as it's really the only relevant one to booting. The383* CD path just might also be relevant, and would be easy to add, but384* isn't supported. A file path too is relevant, but at this stage, it's385* premature because we're trying to translate a specification for a device386* and path on that device into a unix path, or at the very least, a387* geom device : path-on-device.388*389* Also, ZFS throws a bit of a monkey wrench in here since it doesn't have390* a device path type (it creates a new virtual device out of one or more391* storage devices).392*393* For all of them, we'll need to know the geoms, so allocate / free the394* geom mesh here since it's safer than doing it in each sub-function395* which may have many error exits.396*/397if (geom_gettree(&mesh))398return (ENOMEM);399400rv = EINVAL;401if (DevicePathSubType(walker) == MEDIA_HARDDRIVE_DP)402rv = efi_hd_to_unix(&mesh, walker, dev, relpath, abspath);403#ifdef notyet404else if (is_cdrom_device(walker))405rv = efi_cdrom_to_unix(&mesh, walker, dev, relpath, abspath);406else if (is_floppy_device(walker))407rv = efi_floppy_to_unix(&mesh, walker, dev, relpath, abspath);408else if (is_zpool_device(walker))409rv = efi_zpool_to_unix(&mesh, walker, dev, relpath, abspath);410#endif411geom_deletetree(&mesh);412413return (rv);414}415416/*417* Construct the EFI path to a current unix path as follows.418*419* The path may be of one of three forms:420* 1) /path/to/file -- full path to a file. The file need not be present,421* but /path/to must be. It must reside on a local filesystem422* mounted on a GPT or MBR partition.423* 2) //path/to/file -- Shorthand for 'On the EFI partition, \path\to\file'424* where 'The EFI Partition' is a partition that's type is 'efi'425* on the same disk that / is mounted from. If there are multiple426* or no 'efi' parittions on that disk, or / isn't on a disk that427* we can trace back to a physical device, an error will result428* 3) [/dev/]geom-name:/path/to/file -- Use the specified partition429* (and it must be a GPT or MBR partition) with the specified430* path. The latter is not authenticated.431* all path forms translate any \ characters to / before further processing.432* When a file path node is created, all / characters are translated back433* to \.434*435* For paths of the first form:436* find where the filesystem is mount (either the file directly, or437* its parent directory).438* translate any logical device name (eg lable) to a physical one439* If not possible, return ENXIO440* If the physical path is unsupported (Eg not on a GPT or MBR disk),441* return ENXIO442* Create a media device path node.443* append the relative path from the mountpoint to the media device node444* as a file path.445*446* For paths matching the second form:447* find the EFI partition corresponding to the root fileystem.448* If none found, return ENXIO449* Create a media device path node for the found partition450* Append a File Path to the end for the rest of the file.451*452* For paths of the third form453* Translate the geom-name passed in into a physical partition454* name.455* Return ENXIO if the translation fails456* Make a media device path for it457* append the part after the : as a File path node.458*/459460static char *461path_to_file_dp(const char *relpath)462{463char *rv;464465asprintf(&rv, "File(%s)", relpath);466return rv;467}468469static char *470find_geom_efi_on_root(struct gmesh *mesh)471{472struct statfs buf;473const char *dev;474struct gprovider *pp;475// struct ggeom *disk;476struct gconsumer *cp;477478/*479* Find /'s geom. Assume it's mounted on /dev/ and filter out all the480* filesystems that aren't.481*/482if (statfs("/", &buf) != 0)483return (NULL);484dev = buf.f_mntfromname;485if (*dev != '/' || strncmp(dev, _PATH_DEV, sizeof(_PATH_DEV) - 1) != 0)486return (NULL);487dev += sizeof(_PATH_DEV) -1;488pp = find_provider_by_name(mesh, dev);489if (pp == NULL)490return (NULL);491492/*493* If the provider is a LABEL, find it's outer PART class, if any. We494* only operate on partitions.495*/496if (strcmp(pp->lg_geom->lg_class->lg_name, G_LABEL) == 0) {497LIST_FOREACH(cp, &pp->lg_consumers, lg_consumer) {498if (strcmp(cp->lg_provider->lg_geom->lg_class->lg_name, G_PART) == 0) {499pp = cp->lg_provider;500break;501}502}503}504if (strcmp(pp->lg_geom->lg_class->lg_name, G_PART) != 0)505return (NULL);506507#if 0508/* This doesn't work because we can't get the data to walk UP the tree it seems */509510/*511* Now that we've found the PART that we have mounted as root, find the512* first efi typed partition that's a peer, if any.513*/514LIST_FOREACH(cp, &pp->lg_consumers, lg_consumer) {515if (strcmp(cp->lg_provider->lg_geom->lg_class->lg_name, G_DISK) == 0) {516disk = cp->lg_provider->lg_geom;517break;518}519}520if (disk == NULL) /* This is very bad -- old nested partitions -- no support ? */521return (NULL);522#endif523524#if 0525/* This doesn't work because we can't get the data to walk UP the tree it seems */526527/*528* With the disk provider, we can look for its consumers to see if any are the proper type.529*/530LIST_FOREACH(pp, &disk->lg_consumer, lg_consumer) {531type = geom_pp_attr(mesh, pp, "type");532if (type == NULL)533continue;534if (strcmp(type, "efi") != 0)535continue;536efimedia = geom_pp_attr(mesh, pp, "efimedia");537if (efimedia == NULL)538return (NULL);539return strdup(efimedia);540}541#endif542return (NULL);543}544545546static char *547find_geom_efimedia(struct gmesh *mesh, const char *dev)548{549struct gprovider *pp;550const char *efimedia;551552pp = find_provider_by_name(mesh, dev);553if (pp == NULL)554return (NULL);555efimedia = geom_pp_attr(mesh, pp, "efimedia");556557/*558* If this device doesn't hav an efimedia attribute, see if it is a559* glabel node, and if so look for the underlying provider to get the560* efimedia attribute from.561*/562if (efimedia == NULL &&563strcmp(pp->lg_geom->lg_class->lg_name, G_LABEL) == 0)564efimedia = find_geom_efimedia(mesh, pp->lg_geom->lg_name);565if (efimedia == NULL)566return (NULL);567return strdup(efimedia);568}569570static int571build_dp(const char *efimedia, const char *relpath, efidp *dp)572{573char *fp = NULL, *dptxt = NULL, *cp, *rp = NULL;574int rv = 0;575efidp out = NULL;576size_t len;577578if (relpath != NULL) {579rp = strdup(relpath);580for (cp = rp; *cp; cp++)581if (*cp == '/')582*cp = '\\';583fp = path_to_file_dp(rp);584free(rp);585if (fp == NULL) {586rv = ENOMEM;587goto errout;588}589}590591asprintf(&dptxt, "%s/%s", efimedia, fp == NULL ? "" : fp);592out = malloc(8192);593len = efidp_parse_device_path(dptxt, out, 8192);594if (len > 8192) {595rv = ENOMEM;596goto errout;597}598if (len == 0) {599rv = EINVAL;600goto errout;601}602603*dp = out;604errout:605if (rv) {606free(out);607}608free(dptxt);609free(fp);610611return rv;612}613614/* Handles //path/to/file */615/*616* Which means: find the disk that has /. Then look for a EFI partition617* and use that for the efimedia and /path/to/file as relative to that.618* Not sure how ZFS will work here since we can't easily make the leap619* to the geom from the zpool.620*/621static int622efipart_to_dp(struct gmesh *mesh, char *path, efidp *dp)623{624char *efimedia = NULL;625int rv;626627efimedia = find_geom_efi_on_root(mesh);628#ifdef notyet629if (efimedia == NULL)630efimedia = find_efi_on_zfsroot(dev);631#endif632if (efimedia == NULL) {633rv = ENOENT;634goto errout;635}636637rv = build_dp(efimedia, path + 1, dp);638errout:639free(efimedia);640641return rv;642}643644/* Handles [/dev/]geom:[/]path/to/file */645/* Handles zfs-dataset:[/]path/to/file (this may include / ) */646static int647dev_path_to_dp(struct gmesh *mesh, char *path, efidp *dp)648{649char *relpath, *dev, *efimedia = NULL;650int rv = 0;651652relpath = strchr(path, ':');653assert(relpath != NULL);654*relpath++ = '\0';655656dev = path;657if (strncmp(dev, _PATH_DEV, sizeof(_PATH_DEV) - 1) == 0)658dev += sizeof(_PATH_DEV) -1;659660efimedia = find_geom_efimedia(mesh, dev);661#ifdef notyet662if (efimedia == NULL)663find_zfs_efi_media(dev);664#endif665if (efimedia == NULL) {666rv = ENOENT;667goto errout;668}669rv = build_dp(efimedia, relpath, dp);670errout:671free(efimedia);672673return rv;674}675676/* Handles /path/to/file */677/* Handles /dev/foo/bar */678static int679path_to_dp(struct gmesh *mesh, char *path, efidp *dp)680{681struct statfs buf;682char *rp = NULL, *ep, *dev, *efimedia = NULL;683int rv = 0;684685rp = realpath(path, NULL);686if (rp == NULL) {687rv = errno;688goto errout;689}690691if (statfs(rp, &buf) != 0) {692rv = errno;693goto errout;694}695696dev = buf.f_mntfromname;697/*698* If we're fed a raw /dev/foo/bar, then devfs is returned from the699* statfs call. In that case, use that dev and assume we have a path700* of nothing.701*/702if (strcmp(dev, "devfs") == 0) {703dev = rp + sizeof(_PATH_DEV) - 1;704ep = NULL;705} else {706if (strncmp(dev, _PATH_DEV, sizeof(_PATH_DEV) - 1) == 0)707dev += sizeof(_PATH_DEV) - 1;708ep = rp + strlen(buf.f_mntonname);709}710711efimedia = find_geom_efimedia(mesh, dev);712#ifdef notyet713if (efimedia == NULL)714find_zfs_efi_media(dev);715#endif716if (efimedia == NULL) {717rv = ENOENT;718goto errout;719}720721rv = build_dp(efimedia, ep, dp);722errout:723free(efimedia);724free(rp);725if (rv != 0) {726free(*dp);727*dp = NULL;728}729return (rv);730}731732int733efivar_unix_path_to_device_path(const char *path, efidp *dp)734{735char *modpath = NULL, *cp;736int rv = ENOMEM;737struct gmesh mesh;738739/*740* Fail early for clearly bogus things741*/742if (path == NULL || dp == NULL)743return (EDOOFUS);744745/*746* We'll need the goem mesh to grovel through it to find the747* efimedia attribute for any devices we find. Grab it here748* and release it to simplify the error paths out of the749* subordinate functions750*/751if (geom_gettree(&mesh))752return (errno);753754/*755* Convert all \ to /. We'll convert them back again when756* we encode the file. Boot loaders are expected to cope.757*/758modpath = strdup(path);759if (modpath == NULL)760goto out;761for (cp = modpath; *cp; cp++)762if (*cp == '\\')763*cp = '/';764765if (modpath[0] == '/' && modpath[1] == '/') /* Handle //foo/bar/baz */766rv = efipart_to_dp(&mesh, modpath, dp);767else if (strchr(modpath, ':')) /* Handle dev:/bar/baz */768rv = dev_path_to_dp(&mesh, modpath, dp);769else /* Handle /a/b/c */770rv = path_to_dp(&mesh, modpath, dp);771772out:773geom_deletetree(&mesh);774free(modpath);775776return (rv);777}778779780