#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 "efivar-dp.h"
#include "uefi-dplib.h"
#define MAX_DP_SANITY 4096
#define MAX_DP_TEXT_LEN 4096
#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;
LIST_FOREACH(classp, &mesh->lg_class, lg_class) {
if (strcasecmp(classp->lg_name, G_PART) == 0)
break;
}
if (classp == NULL)
return (NULL);
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;
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);
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;
}
if (file == NULL)
goto errout;
*relpath = efidp_extract_file_path(file);
if (*relpath == NULL) {
rv = ENOMEM;
goto errout;
}
for (pwalk = *relpath; *pwalk; pwalk++)
if (*pwalk == '\\')
*pwalk = '/';
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;
}
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++) {
if (strncmp(mnt[i].f_mntfromname, "/dev/", 5) != 0)
continue;
if (strcmp(provider->lg_name, mnt[i].f_mntfromname + 5) == 0) {
newdev = provider->lg_name;
break;
}
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;
}
}
}
}
break2:
if (newdev == NULL)
newdev = provider->lg_name;
*dev = strdup(newdev);
if (*dev == NULL) {
rv = ENOMEM;
goto errout;
}
if (i >= n)
goto errout;
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);
}
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;
if (dp == NULL || dev == NULL || relpath == NULL || abspath == NULL)
return (EDOOFUS);
*dev = NULL;
*relpath = NULL;
*abspath = NULL;
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);
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);
}
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 gconsumer *cp;
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 (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
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)
return (NULL);
#endif
#if 0
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 (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;
}
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;
}
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;
}
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 (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;
if (path == NULL || dp == NULL)
return (EDOOFUS);
if (geom_gettree(&mesh))
return (errno);
modpath = strdup(path);
if (modpath == NULL)
goto out;
for (cp = modpath; *cp; cp++)
if (*cp == '\\')
*cp = '/';
if (modpath[0] == '/' && modpath[1] == '/')
rv = efipart_to_dp(&mesh, modpath, dp);
else if (strchr(modpath, ':'))
rv = dev_path_to_dp(&mesh, modpath, dp);
else
rv = path_to_dp(&mesh, modpath, dp);
out:
geom_deletetree(&mesh);
free(modpath);
return (rv);
}