780 lines
20 KiB
C
780 lines
20 KiB
C
/*-
|
|
* Copyright (c) 2017 Netflix, Inc.
|
|
*
|
|
* Redistribution and use in source and binary forms, with or without
|
|
* modification, are permitted provided that the following conditions
|
|
* are met:
|
|
* 1. Redistributions of source code must retain the above copyright
|
|
* notice, this list of conditions and the following disclaimer.
|
|
* 2. Redistributions in binary form must reproduce the above copyright
|
|
* notice, this list of conditions and the following disclaimer in the
|
|
* documentation and/or other materials provided with the distribution.
|
|
*
|
|
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
|
|
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
|
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
|
|
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
|
|
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
|
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
|
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
|
|
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
|
|
* SUCH DAMAGE.
|
|
*/
|
|
|
|
#include <sys/cdefs.h>
|
|
#include <sys/param.h>
|
|
#include <sys/ucred.h>
|
|
#include <sys/mount.h>
|
|
|
|
#undef MAX
|
|
#undef MIN
|
|
|
|
#include <assert.h>
|
|
#include <efivar.h>
|
|
#include <errno.h>
|
|
#include <libgeom.h>
|
|
#include <paths.h>
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
|
|
#include "efichar.h"
|
|
|
|
#include "efi-osdep.h"
|
|
#include "efivar-dp.h"
|
|
|
|
#include "uefi-dplib.h"
|
|
|
|
#define MAX_DP_SANITY 4096 /* Biggest device path in bytes */
|
|
#define MAX_DP_TEXT_LEN 4096 /* Longest string rep of dp */
|
|
|
|
#define ValidLen(dp) (DevicePathNodeLength(dp) >= sizeof(EFI_DEVICE_PATH_PROTOCOL) && \
|
|
DevicePathNodeLength(dp) < MAX_DP_SANITY)
|
|
|
|
#define G_PART "PART"
|
|
#define G_LABEL "LABEL"
|
|
#define G_DISK "DISK"
|
|
|
|
static const char *
|
|
geom_pp_attr(struct gmesh *mesh, struct gprovider *pp, const char *attr)
|
|
{
|
|
struct gconfig *conf;
|
|
|
|
LIST_FOREACH(conf, &pp->lg_config, lg_config) {
|
|
if (strcmp(conf->lg_name, attr) != 0)
|
|
continue;
|
|
return (conf->lg_val);
|
|
}
|
|
return (NULL);
|
|
}
|
|
|
|
static struct gprovider *
|
|
find_provider_by_efimedia(struct gmesh *mesh, const char *efimedia)
|
|
{
|
|
struct gclass *classp;
|
|
struct ggeom *gp;
|
|
struct gprovider *pp;
|
|
const char *val;
|
|
|
|
/*
|
|
* Find the partition class so we can search it...
|
|
*/
|
|
LIST_FOREACH(classp, &mesh->lg_class, lg_class) {
|
|
if (strcasecmp(classp->lg_name, G_PART) == 0)
|
|
break;
|
|
}
|
|
if (classp == NULL)
|
|
return (NULL);
|
|
|
|
/*
|
|
* Each geom will have a number of providers, search each
|
|
* one of them for the efimedia that matches.
|
|
*/
|
|
/* XXX just used gpart class since I know it's the only one, but maybe I should search all classes */
|
|
LIST_FOREACH(gp, &classp->lg_geom, lg_geom) {
|
|
LIST_FOREACH(pp, &gp->lg_provider, lg_provider) {
|
|
val = geom_pp_attr(mesh, pp, "efimedia");
|
|
if (val == NULL)
|
|
continue;
|
|
if (strcasecmp(efimedia, val) == 0)
|
|
return (pp);
|
|
}
|
|
}
|
|
|
|
return (NULL);
|
|
}
|
|
|
|
static struct gprovider *
|
|
find_provider_by_name(struct gmesh *mesh, const char *name)
|
|
{
|
|
struct gclass *classp;
|
|
struct ggeom *gp;
|
|
struct gprovider *pp;
|
|
|
|
LIST_FOREACH(classp, &mesh->lg_class, lg_class) {
|
|
LIST_FOREACH(gp, &classp->lg_geom, lg_geom) {
|
|
LIST_FOREACH(pp, &gp->lg_provider, lg_provider) {
|
|
if (strcmp(pp->lg_name, name) == 0)
|
|
return (pp);
|
|
}
|
|
}
|
|
}
|
|
|
|
return (NULL);
|
|
}
|
|
|
|
|
|
static int
|
|
efi_hd_to_unix(struct gmesh *mesh, const_efidp dp, char **dev, char **relpath, char **abspath)
|
|
{
|
|
int rv = 0, n, i;
|
|
const_efidp media, file, walker;
|
|
size_t len, mntlen;
|
|
char buf[MAX_DP_TEXT_LEN];
|
|
char *pwalk, *newdev = NULL;
|
|
struct gprovider *pp, *provider;
|
|
struct statfs *mnt;
|
|
struct gclass *glabel;
|
|
struct ggeom *gp;
|
|
|
|
walker = media = dp;
|
|
*dev = NULL;
|
|
*relpath = NULL;
|
|
|
|
/*
|
|
* Now, we can either have a filepath node next, or the end.
|
|
* Otherwise, it's an error.
|
|
*/
|
|
if (!ValidLen(walker))
|
|
return (EINVAL);
|
|
walker = (const_efidp)NextDevicePathNode(walker);
|
|
if ((uintptr_t)walker - (uintptr_t)dp > MAX_DP_SANITY)
|
|
return (EINVAL);
|
|
if (DevicePathType(walker) == MEDIA_DEVICE_PATH &&
|
|
DevicePathSubType(walker) == MEDIA_FILEPATH_DP)
|
|
file = walker;
|
|
else if (DevicePathType(walker) == MEDIA_DEVICE_PATH &&
|
|
DevicePathType(walker) == END_DEVICE_PATH_TYPE)
|
|
file = NULL;
|
|
else
|
|
return (EINVAL);
|
|
|
|
/*
|
|
* Format this node. We're going to look for it as a efimedia
|
|
* attribute of some geom node. Once we find that node, we use it
|
|
* as the device it comes from, at least provisionally.
|
|
*/
|
|
len = efidp_format_device_path_node(buf, sizeof(buf), media);
|
|
if (len > sizeof(buf))
|
|
return (EINVAL);
|
|
|
|
pp = find_provider_by_efimedia(mesh, buf);
|
|
if (pp == NULL) {
|
|
rv = ENOENT;
|
|
goto errout;
|
|
}
|
|
|
|
/*
|
|
* No file specified, just return the device. Don't even look
|
|
* for a mountpoint. XXX Sane?
|
|
*/
|
|
if (file == NULL)
|
|
goto errout;
|
|
|
|
/*
|
|
* Now extract the relative path. The next node in the device path should
|
|
* be a filesystem node. If not, we have issues.
|
|
*/
|
|
*relpath = efidp_extract_file_path(file);
|
|
if (*relpath == NULL) {
|
|
rv = ENOMEM;
|
|
goto errout;
|
|
}
|
|
for (pwalk = *relpath; *pwalk; pwalk++)
|
|
if (*pwalk == '\\')
|
|
*pwalk = '/';
|
|
|
|
/*
|
|
* To find the absolute path, we have to look for where we're mounted.
|
|
* We only look a little hard, since looking too hard can come up with
|
|
* false positives (imagine a graid, one of whose devices is *dev).
|
|
*/
|
|
n = getfsstat(NULL, 0, MNT_NOWAIT) + 1;
|
|
if (n < 0) {
|
|
rv = errno;
|
|
goto errout;
|
|
}
|
|
mntlen = sizeof(struct statfs) * n;
|
|
mnt = malloc(mntlen);
|
|
n = getfsstat(mnt, mntlen, MNT_NOWAIT);
|
|
if (n < 0) {
|
|
rv = errno;
|
|
goto errout;
|
|
}
|
|
|
|
/*
|
|
* Find glabel, if it exists. It's OK if not: we'll skip searching for
|
|
* labels.
|
|
*/
|
|
LIST_FOREACH(glabel, &mesh->lg_class, lg_class) {
|
|
if (strcmp(glabel->lg_name, G_LABEL) == 0)
|
|
break;
|
|
}
|
|
|
|
provider = pp;
|
|
for (i = 0; i < n; i++) {
|
|
/*
|
|
* Skip all pseudo filesystems. This also skips the real filesytsem
|
|
* of ZFS. There's no EFI designator for ZFS in the standard, so
|
|
* we'll need to invent one, but its decoding will be handled in
|
|
* a separate function.
|
|
*/
|
|
if (strncmp(mnt[i].f_mntfromname, "/dev/", 5) != 0)
|
|
continue;
|
|
|
|
/*
|
|
* First see if it is directly attached
|
|
*/
|
|
if (strcmp(provider->lg_name, mnt[i].f_mntfromname + 5) == 0) {
|
|
newdev = provider->lg_name;
|
|
break;
|
|
}
|
|
|
|
/*
|
|
* Next see if it is attached via one of the physical disk's labels.
|
|
* We can't search directly from the pointers we have for the
|
|
* provider, so we have to cast a wider net for all labels and
|
|
* filter those down to geoms whose name matches the PART provider
|
|
* we found the efimedia attribute on.
|
|
*/
|
|
if (glabel == NULL)
|
|
continue;
|
|
LIST_FOREACH(gp, &glabel->lg_geom, lg_geom) {
|
|
if (strcmp(gp->lg_name, provider->lg_name) != 0) {
|
|
continue;
|
|
}
|
|
LIST_FOREACH(pp, &gp->lg_provider, lg_provider) {
|
|
if (strcmp(pp->lg_name, mnt[i].f_mntfromname + 5) == 0) {
|
|
newdev = pp->lg_name;
|
|
goto break2;
|
|
}
|
|
}
|
|
}
|
|
/* Not the one, try the next mount point */
|
|
}
|
|
break2:
|
|
|
|
/*
|
|
* If nothing better was mounted, then use the provider we found as
|
|
* is. It's the most correct thing we can return in that acse.
|
|
*/
|
|
if (newdev == NULL)
|
|
newdev = provider->lg_name;
|
|
*dev = strdup(newdev);
|
|
if (*dev == NULL) {
|
|
rv = ENOMEM;
|
|
goto errout;
|
|
}
|
|
|
|
/*
|
|
* No mountpoint found, no absolute path possible
|
|
*/
|
|
if (i >= n)
|
|
goto errout;
|
|
|
|
/*
|
|
* Construct absolute path and we're finally done.
|
|
*/
|
|
if (strcmp(mnt[i].f_mntonname, "/") == 0)
|
|
asprintf(abspath, "/%s", *relpath);
|
|
else
|
|
asprintf(abspath, "%s/%s", mnt[i].f_mntonname, *relpath);
|
|
|
|
errout:
|
|
if (rv != 0) {
|
|
free(*dev);
|
|
*dev = NULL;
|
|
free(*relpath);
|
|
*relpath = NULL;
|
|
}
|
|
return (rv);
|
|
}
|
|
|
|
/*
|
|
* Translate the passed in device_path to a unix path via the following
|
|
* algorithm.
|
|
*
|
|
* If dp, dev or path NULL, return EDOOFUS. XXX wise?
|
|
*
|
|
* Set *path = NULL; *dev = NULL;
|
|
*
|
|
* Walk through the device_path until we find either a media device path.
|
|
* Return EINVAL if not found. Return EINVAL if walking dp would
|
|
* land us more than sanity size away from the start (4k).
|
|
*
|
|
* If we find a media descriptor, we search through the geom mesh to see if we
|
|
* can find a matching node. If no match is found in the mesh that matches,
|
|
* return ENXIO.
|
|
*
|
|
* Once we find a matching node, we search to see if there is a filesystem
|
|
* mounted on it. If we find nothing, then search each of the devices that are
|
|
* mounted to see if we can work up the geom tree to find the matching node. if
|
|
* we still can't find anything, *dev = sprintf("/dev/%s", provider_name
|
|
* of the original node we found), but return ENOTBLK.
|
|
*
|
|
* Record the dev of the mountpoint in *dev.
|
|
*
|
|
* Once we find something, check to see if the next node in the device path is
|
|
* the end of list. If so, return the mountpoint.
|
|
*
|
|
* If the next node isn't a File path node, return EFTYPE.
|
|
*
|
|
* Extract the path from the File path node(s). translate any \ file separators
|
|
* to /. Append the result to the mount point. Copy the resulting path into
|
|
* *path. Stat that path. If it is not found, return the errorr from stat.
|
|
*
|
|
* Finally, check to make sure the resulting path is still on the same
|
|
* device. If not, return ENODEV.
|
|
*
|
|
* Otherwise return 0.
|
|
*
|
|
* The dev or full path that's returned is malloced, so needs to be freed when
|
|
* the caller is done about it. Unlike many other functions, we can return data
|
|
* with an error code, so pay attention.
|
|
*/
|
|
int
|
|
efivar_device_path_to_unix_path(const_efidp dp, char **dev, char **relpath, char **abspath)
|
|
{
|
|
const_efidp walker;
|
|
struct gmesh mesh;
|
|
int rv = 0;
|
|
|
|
/*
|
|
* Sanity check args, fail early
|
|
*/
|
|
if (dp == NULL || dev == NULL || relpath == NULL || abspath == NULL)
|
|
return (EDOOFUS);
|
|
|
|
*dev = NULL;
|
|
*relpath = NULL;
|
|
*abspath = NULL;
|
|
|
|
/*
|
|
* Find the first media device path we can. If we go too far,
|
|
* assume the passed in device path is bogus. If we hit the end
|
|
* then we didn't find a media device path, so signal that error.
|
|
*/
|
|
walker = dp;
|
|
if (!ValidLen(walker))
|
|
return (EINVAL);
|
|
while (DevicePathType(walker) != MEDIA_DEVICE_PATH &&
|
|
DevicePathType(walker) != END_DEVICE_PATH_TYPE) {
|
|
walker = (const_efidp)NextDevicePathNode(walker);
|
|
if ((uintptr_t)walker - (uintptr_t)dp > MAX_DP_SANITY)
|
|
return (EINVAL);
|
|
if (!ValidLen(walker))
|
|
return (EINVAL);
|
|
}
|
|
if (DevicePathType(walker) != MEDIA_DEVICE_PATH)
|
|
return (EINVAL);
|
|
|
|
/*
|
|
* There's several types of media paths. We're only interested in the
|
|
* hard disk path, as it's really the only relevant one to booting. The
|
|
* CD path just might also be relevant, and would be easy to add, but
|
|
* isn't supported. A file path too is relevant, but at this stage, it's
|
|
* premature because we're trying to translate a specification for a device
|
|
* and path on that device into a unix path, or at the very least, a
|
|
* geom device : path-on-device.
|
|
*
|
|
* Also, ZFS throws a bit of a monkey wrench in here since it doesn't have
|
|
* a device path type (it creates a new virtual device out of one or more
|
|
* storage devices).
|
|
*
|
|
* For all of them, we'll need to know the geoms, so allocate / free the
|
|
* geom mesh here since it's safer than doing it in each sub-function
|
|
* which may have many error exits.
|
|
*/
|
|
if (geom_gettree(&mesh))
|
|
return (ENOMEM);
|
|
|
|
rv = EINVAL;
|
|
if (DevicePathSubType(walker) == MEDIA_HARDDRIVE_DP)
|
|
rv = efi_hd_to_unix(&mesh, walker, dev, relpath, abspath);
|
|
#ifdef notyet
|
|
else if (is_cdrom_device(walker))
|
|
rv = efi_cdrom_to_unix(&mesh, walker, dev, relpath, abspath);
|
|
else if (is_floppy_device(walker))
|
|
rv = efi_floppy_to_unix(&mesh, walker, dev, relpath, abspath);
|
|
else if (is_zpool_device(walker))
|
|
rv = efi_zpool_to_unix(&mesh, walker, dev, relpath, abspath);
|
|
#endif
|
|
geom_deletetree(&mesh);
|
|
|
|
return (rv);
|
|
}
|
|
|
|
/*
|
|
* Construct the EFI path to a current unix path as follows.
|
|
*
|
|
* The path may be of one of three forms:
|
|
* 1) /path/to/file -- full path to a file. The file need not be present,
|
|
* but /path/to must be. It must reside on a local filesystem
|
|
* mounted on a GPT or MBR partition.
|
|
* 2) //path/to/file -- Shorthand for 'On the EFI partition, \path\to\file'
|
|
* where 'The EFI Partition' is a partition that's type is 'efi'
|
|
* on the same disk that / is mounted from. If there are multiple
|
|
* or no 'efi' parittions on that disk, or / isn't on a disk that
|
|
* we can trace back to a physical device, an error will result
|
|
* 3) [/dev/]geom-name:/path/to/file -- Use the specified partition
|
|
* (and it must be a GPT or MBR partition) with the specified
|
|
* path. The latter is not authenticated.
|
|
* all path forms translate any \ characters to / before further processing.
|
|
* When a file path node is created, all / characters are translated back
|
|
* to \.
|
|
*
|
|
* For paths of the first form:
|
|
* find where the filesystem is mount (either the file directly, or
|
|
* its parent directory).
|
|
* translate any logical device name (eg lable) to a physical one
|
|
* If not possible, return ENXIO
|
|
* If the physical path is unsupported (Eg not on a GPT or MBR disk),
|
|
* return ENXIO
|
|
* Create a media device path node.
|
|
* append the relative path from the mountpoint to the media device node
|
|
* as a file path.
|
|
*
|
|
* For paths matching the second form:
|
|
* find the EFI partition corresponding to the root fileystem.
|
|
* If none found, return ENXIO
|
|
* Create a media device path node for the found partition
|
|
* Append a File Path to the end for the rest of the file.
|
|
*
|
|
* For paths of the third form
|
|
* Translate the geom-name passed in into a physical partition
|
|
* name.
|
|
* Return ENXIO if the translation fails
|
|
* Make a media device path for it
|
|
* append the part after the : as a File path node.
|
|
*/
|
|
|
|
static char *
|
|
path_to_file_dp(const char *relpath)
|
|
{
|
|
char *rv;
|
|
|
|
asprintf(&rv, "File(%s)", relpath);
|
|
return rv;
|
|
}
|
|
|
|
static char *
|
|
find_geom_efi_on_root(struct gmesh *mesh)
|
|
{
|
|
struct statfs buf;
|
|
const char *dev;
|
|
struct gprovider *pp;
|
|
// struct ggeom *disk;
|
|
struct gconsumer *cp;
|
|
|
|
/*
|
|
* Find /'s geom. Assume it's mounted on /dev/ and filter out all the
|
|
* filesystems that aren't.
|
|
*/
|
|
if (statfs("/", &buf) != 0)
|
|
return (NULL);
|
|
dev = buf.f_mntfromname;
|
|
if (*dev != '/' || strncmp(dev, _PATH_DEV, sizeof(_PATH_DEV) - 1) != 0)
|
|
return (NULL);
|
|
dev += sizeof(_PATH_DEV) -1;
|
|
pp = find_provider_by_name(mesh, dev);
|
|
if (pp == NULL)
|
|
return (NULL);
|
|
|
|
/*
|
|
* If the provider is a LABEL, find it's outer PART class, if any. We
|
|
* only operate on partitions.
|
|
*/
|
|
if (strcmp(pp->lg_geom->lg_class->lg_name, G_LABEL) == 0) {
|
|
LIST_FOREACH(cp, &pp->lg_consumers, lg_consumer) {
|
|
if (strcmp(cp->lg_provider->lg_geom->lg_class->lg_name, G_PART) == 0) {
|
|
pp = cp->lg_provider;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
if (strcmp(pp->lg_geom->lg_class->lg_name, G_PART) != 0)
|
|
return (NULL);
|
|
|
|
#if 0
|
|
/* This doesn't work because we can't get the data to walk UP the tree it seems */
|
|
|
|
/*
|
|
* Now that we've found the PART that we have mounted as root, find the
|
|
* first efi typed partition that's a peer, if any.
|
|
*/
|
|
LIST_FOREACH(cp, &pp->lg_consumers, lg_consumer) {
|
|
if (strcmp(cp->lg_provider->lg_geom->lg_class->lg_name, G_DISK) == 0) {
|
|
disk = cp->lg_provider->lg_geom;
|
|
break;
|
|
}
|
|
}
|
|
if (disk == NULL) /* This is very bad -- old nested partitions -- no support ? */
|
|
return (NULL);
|
|
#endif
|
|
|
|
#if 0
|
|
/* This doesn't work because we can't get the data to walk UP the tree it seems */
|
|
|
|
/*
|
|
* With the disk provider, we can look for its consumers to see if any are the proper type.
|
|
*/
|
|
LIST_FOREACH(pp, &disk->lg_consumer, lg_consumer) {
|
|
type = geom_pp_attr(mesh, pp, "type");
|
|
if (type == NULL)
|
|
continue;
|
|
if (strcmp(type, "efi") != 0)
|
|
continue;
|
|
efimedia = geom_pp_attr(mesh, pp, "efimedia");
|
|
if (efimedia == NULL)
|
|
return (NULL);
|
|
return strdup(efimedia);
|
|
}
|
|
#endif
|
|
return (NULL);
|
|
}
|
|
|
|
|
|
static char *
|
|
find_geom_efimedia(struct gmesh *mesh, const char *dev)
|
|
{
|
|
struct gprovider *pp;
|
|
const char *efimedia;
|
|
|
|
pp = find_provider_by_name(mesh, dev);
|
|
if (pp == NULL)
|
|
return (NULL);
|
|
efimedia = geom_pp_attr(mesh, pp, "efimedia");
|
|
|
|
/*
|
|
* If this device doesn't hav an efimedia attribute, see if it is a
|
|
* glabel node, and if so look for the underlying provider to get the
|
|
* efimedia attribute from.
|
|
*/
|
|
if (efimedia == NULL &&
|
|
strcmp(pp->lg_geom->lg_class->lg_name, G_LABEL) == 0)
|
|
efimedia = find_geom_efimedia(mesh, pp->lg_geom->lg_name);
|
|
if (efimedia == NULL)
|
|
return (NULL);
|
|
return strdup(efimedia);
|
|
}
|
|
|
|
static int
|
|
build_dp(const char *efimedia, const char *relpath, efidp *dp)
|
|
{
|
|
char *fp = NULL, *dptxt = NULL, *cp, *rp = NULL;
|
|
int rv = 0;
|
|
efidp out = NULL;
|
|
size_t len;
|
|
|
|
if (relpath != NULL) {
|
|
rp = strdup(relpath);
|
|
for (cp = rp; *cp; cp++)
|
|
if (*cp == '/')
|
|
*cp = '\\';
|
|
fp = path_to_file_dp(rp);
|
|
free(rp);
|
|
if (fp == NULL) {
|
|
rv = ENOMEM;
|
|
goto errout;
|
|
}
|
|
}
|
|
|
|
asprintf(&dptxt, "%s/%s", efimedia, fp == NULL ? "" : fp);
|
|
out = malloc(8192);
|
|
len = efidp_parse_device_path(dptxt, out, 8192);
|
|
if (len > 8192) {
|
|
rv = ENOMEM;
|
|
goto errout;
|
|
}
|
|
if (len == 0) {
|
|
rv = EINVAL;
|
|
goto errout;
|
|
}
|
|
|
|
*dp = out;
|
|
errout:
|
|
if (rv) {
|
|
free(out);
|
|
}
|
|
free(dptxt);
|
|
free(fp);
|
|
|
|
return rv;
|
|
}
|
|
|
|
/* Handles //path/to/file */
|
|
/*
|
|
* Which means: find the disk that has /. Then look for a EFI partition
|
|
* and use that for the efimedia and /path/to/file as relative to that.
|
|
* Not sure how ZFS will work here since we can't easily make the leap
|
|
* to the geom from the zpool.
|
|
*/
|
|
static int
|
|
efipart_to_dp(struct gmesh *mesh, char *path, efidp *dp)
|
|
{
|
|
char *efimedia = NULL;
|
|
int rv;
|
|
|
|
efimedia = find_geom_efi_on_root(mesh);
|
|
#ifdef notyet
|
|
if (efimedia == NULL)
|
|
efimedia = find_efi_on_zfsroot(dev);
|
|
#endif
|
|
if (efimedia == NULL) {
|
|
rv = ENOENT;
|
|
goto errout;
|
|
}
|
|
|
|
rv = build_dp(efimedia, path + 1, dp);
|
|
errout:
|
|
free(efimedia);
|
|
|
|
return rv;
|
|
}
|
|
|
|
/* Handles [/dev/]geom:[/]path/to/file */
|
|
/* Handles zfs-dataset:[/]path/to/file (this may include / ) */
|
|
static int
|
|
dev_path_to_dp(struct gmesh *mesh, char *path, efidp *dp)
|
|
{
|
|
char *relpath, *dev, *efimedia = NULL;
|
|
int rv = 0;
|
|
|
|
relpath = strchr(path, ':');
|
|
assert(relpath != NULL);
|
|
*relpath++ = '\0';
|
|
|
|
dev = path;
|
|
if (strncmp(dev, _PATH_DEV, sizeof(_PATH_DEV) - 1) == 0)
|
|
dev += sizeof(_PATH_DEV) -1;
|
|
|
|
efimedia = find_geom_efimedia(mesh, dev);
|
|
#ifdef notyet
|
|
if (efimedia == NULL)
|
|
find_zfs_efi_media(dev);
|
|
#endif
|
|
if (efimedia == NULL) {
|
|
rv = ENOENT;
|
|
goto errout;
|
|
}
|
|
rv = build_dp(efimedia, relpath, dp);
|
|
errout:
|
|
free(efimedia);
|
|
|
|
return rv;
|
|
}
|
|
|
|
/* Handles /path/to/file */
|
|
/* Handles /dev/foo/bar */
|
|
static int
|
|
path_to_dp(struct gmesh *mesh, char *path, efidp *dp)
|
|
{
|
|
struct statfs buf;
|
|
char *rp = NULL, *ep, *dev, *efimedia = NULL;
|
|
int rv = 0;
|
|
|
|
rp = realpath(path, NULL);
|
|
if (rp == NULL) {
|
|
rv = errno;
|
|
goto errout;
|
|
}
|
|
|
|
if (statfs(rp, &buf) != 0) {
|
|
rv = errno;
|
|
goto errout;
|
|
}
|
|
|
|
dev = buf.f_mntfromname;
|
|
/*
|
|
* If we're fed a raw /dev/foo/bar, then devfs is returned from the
|
|
* statfs call. In that case, use that dev and assume we have a path
|
|
* of nothing.
|
|
*/
|
|
if (strcmp(dev, "devfs") == 0) {
|
|
dev = rp + sizeof(_PATH_DEV) - 1;
|
|
ep = NULL;
|
|
} else {
|
|
if (strncmp(dev, _PATH_DEV, sizeof(_PATH_DEV) - 1) == 0)
|
|
dev += sizeof(_PATH_DEV) - 1;
|
|
ep = rp + strlen(buf.f_mntonname);
|
|
}
|
|
|
|
efimedia = find_geom_efimedia(mesh, dev);
|
|
#ifdef notyet
|
|
if (efimedia == NULL)
|
|
find_zfs_efi_media(dev);
|
|
#endif
|
|
if (efimedia == NULL) {
|
|
rv = ENOENT;
|
|
goto errout;
|
|
}
|
|
|
|
rv = build_dp(efimedia, ep, dp);
|
|
errout:
|
|
free(efimedia);
|
|
free(rp);
|
|
if (rv != 0) {
|
|
free(*dp);
|
|
*dp = NULL;
|
|
}
|
|
return (rv);
|
|
}
|
|
|
|
int
|
|
efivar_unix_path_to_device_path(const char *path, efidp *dp)
|
|
{
|
|
char *modpath = NULL, *cp;
|
|
int rv = ENOMEM;
|
|
struct gmesh mesh;
|
|
|
|
/*
|
|
* Fail early for clearly bogus things
|
|
*/
|
|
if (path == NULL || dp == NULL)
|
|
return (EDOOFUS);
|
|
|
|
/*
|
|
* We'll need the goem mesh to grovel through it to find the
|
|
* efimedia attribute for any devices we find. Grab it here
|
|
* and release it to simplify the error paths out of the
|
|
* subordinate functions
|
|
*/
|
|
if (geom_gettree(&mesh))
|
|
return (errno);
|
|
|
|
/*
|
|
* Convert all \ to /. We'll convert them back again when
|
|
* we encode the file. Boot loaders are expected to cope.
|
|
*/
|
|
modpath = strdup(path);
|
|
if (modpath == NULL)
|
|
goto out;
|
|
for (cp = modpath; *cp; cp++)
|
|
if (*cp == '\\')
|
|
*cp = '/';
|
|
|
|
if (modpath[0] == '/' && modpath[1] == '/') /* Handle //foo/bar/baz */
|
|
rv = efipart_to_dp(&mesh, modpath, dp);
|
|
else if (strchr(modpath, ':')) /* Handle dev:/bar/baz */
|
|
rv = dev_path_to_dp(&mesh, modpath, dp);
|
|
else /* Handle /a/b/c */
|
|
rv = path_to_dp(&mesh, modpath, dp);
|
|
|
|
out:
|
|
geom_deletetree(&mesh);
|
|
free(modpath);
|
|
|
|
return (rv);
|
|
}
|