#include <sys/param.h>
#include <sys/ucred.h>
#include <sys/mount.h>
#include <sys/sysctl.h>
#undef MAX
#undef MIN
#include <assert.h>
#include <efivar.h>
#include <errno.h>
#include <paths.h>
#include <stdio.h>
#include <string.h>
#include <uuid.h>
#include "libefivar_int.h"
#include "efi-osdep.h"
#include "efivar-dp.h"
#include "uefi-dplib.h"
#include "map.h"
#include "gpt.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)
#if 0
#define G_PART "PART"
#define G_LABEL "LABEL"
#define G_DISK "DISK"
#endif
static int
find_slice_by_efimedia(char *buf, char **dev)
{
char disks[1024], efimedia[128], dev_path[256], part[256];
char *disk, *disk_ptr, *s;
size_t len;
int fd;
map_t *m;
uuid_t guid;
struct gpt_ent *ent;
len = sizeof(disks);
if (sysctlbyname("kern.disks", disks, &len, NULL, 0) < 0)
return -1;
disk_ptr = disks;
while ((disk = strsep(&disk_ptr, " ")) != NULL) {
if (disk[0] == '\0')
continue;
if (strncmp(disk, "md", 2) == 0 ||
strncmp(disk, "cd", 2) == 0 ||
strncmp(disk, "acd", 3) == 0 ||
strncmp(disk, "fd", 2) == 0)
continue;
snprintf(dev_path, sizeof(dev_path), "%s%s", _PATH_DEV, disk);
fd = gpt_open(dev_path);
if (fd == -1)
continue;
for (m = map_first(); m != NULL; m = m->map_next) {
if (m->map_index == MAP_NOENTRY ||
m->map_type != MAP_TYPE_GPT_PART)
continue;
ent = m->map_data;
s = NULL;
uuid_dec_le(&ent->ent_uuid, &guid);
uuid_to_string(&guid, &s, NULL);
snprintf(efimedia, sizeof(efimedia),
"HD(%d,GPT,%s,%#jx,%#jx)", m->map_index + 1, s,
(uintmax_t)m->map_start, (uintmax_t)m->map_size);
free(s);
if (strcasecmp(efimedia, buf) == 0) {
gpt_close(fd);
snprintf(part, sizeof(part), "%s%ss%d",
_PATH_DEV, disk, m->map_index);
*dev = strdup(part);
return 0;
}
}
gpt_close(fd);
}
return -1;
}
static int
efi_hd_to_unix(const_efidp dp, char **dev, char **relpath, char **abspath)
{
int error, rv = 0, n, i;
const_efidp media, file, walker;
size_t len, mntlen;
char buf[MAX_DP_TEXT_LEN];
char *pwalk;
struct statfs *mnt;
walker = media = dp;
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);
error = find_slice_by_efimedia(buf, dev);
if (error == -1) {
rv = ENOENT;
goto errout;
}
if (*dev == NULL) {
rv = ENOMEM;
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;
}
for (i = 0; i < n; i++) {
if (mnt[i].f_mntfromname[0] != '/')
continue;
if (strcmp(*dev, mnt[i].f_mntfromname) == 0)
break;
#if 0
LIST_FOREACH(cp, &provider->lg_consumers, lg_consumer) {
pp = cp->lg_provider;
if (strcmp(pp->lg_geom->lg_class->lg_name, G_LABEL) != 0)
continue;
if (strcmp(g_device_path(pp->lg_name), mnt[i].f_mntfromname) == 0)
goto break2;
}
#endif
}
#if 0
break2:
#endif
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;
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);
rv = EINVAL;
if (DevicePathSubType(walker) == MEDIA_HARDDRIVE_DP)
rv = efi_hd_to_unix(walker, dev, relpath, abspath);
#ifdef notyet
else if (is_cdrom_device(walker))
rv = efi_cdrom_to_unix(walker, dev, relpath, abspath);
else if (is_floppy_device(walker))
rv = efi_floppy_to_unix(walker, dev, relpath, abspath);
else if (is_zpool_device(walker))
rv = efi_zpool_to_unix(walker, dev, relpath, abspath);
#endif
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(void)
{
return (NULL);
}
static char *
find_slice_efimedia(const char *slice)
{
char disk_path[1024], disks[1024], efimedia[128];
char *disk, *disk_ptr, *ep, *s;
int fd;
u_long sliceno;
size_t len;
map_t *m;
uuid_t guid;
struct gpt_ent *ent;
len = sizeof(disks);
if (sysctlbyname("kern.disks", disks, &len, NULL, 0) < 0)
return (NULL);
disk_ptr = disks;
while ((disk = strsep(&disk_ptr, " ")) != NULL) {
if (disk[0] == '\0')
continue;
if (strncmp(disk, "md", 2) == 0 ||
strncmp(disk, "cd", 2) == 0 ||
strncmp(disk, "acd", 3) == 0 ||
strncmp(disk, "fd", 2) == 0)
continue;
if (strncmp(slice, disk, strlen(disk)) == 0) {
snprintf(disk_path, sizeof(disk_path), "%s%s",
_PATH_DEV, disk);
sliceno = strtoul(slice + sizeof(disk) + 1, &ep, 10);
if (*ep != 0)
return (NULL);
break;
}
}
if (disk == NULL)
return (NULL);
fd = gpt_open(disk_path);
if (fd == -1)
return (NULL);
for (m = map_first(); m != NULL; m = m->map_next) {
if (m->map_index == MAP_NOENTRY ||
m->map_type != MAP_TYPE_GPT_PART)
continue;
if (m->map_index == sliceno) {
ent = m->map_data;
s = NULL;
uuid_dec_le(&ent->ent_uuid, &guid);
uuid_to_string(&guid, &s, NULL);
snprintf(efimedia, sizeof(efimedia),
"HD(%d,GPT,%s,%#jx,%#jx)", m->map_index + 1, s,
(uintmax_t)m->map_start, (uintmax_t)m->map_size);
free(s);
gpt_close(fd);
return strdup(efimedia);
}
}
gpt_close(fd);
return (NULL);
}
static int
build_dp(const char *efimedia, const char *relpath, efidp *dp)
{
char *fp, *dptxt = NULL, *cp, *rp;
int rv = 0;
efidp out = NULL;
size_t len;
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);
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(char *path, efidp *dp)
{
char *efimedia = NULL;
int rv;
efimedia = find_geom_efi_on_root();
#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(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_slice_efimedia(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(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 (strncmp(dev, _PATH_DEV, sizeof(_PATH_DEV) - 1) == 0)
dev += sizeof(_PATH_DEV) -1;
ep = rp + strlen(buf.f_mntonname);
efimedia = find_slice_efimedia(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;
if (path == NULL || dp == NULL)
return (EDOOFUS);
modpath = strdup(path);
if (modpath == NULL)
return (ENOMEM);
for (cp = modpath; *cp; cp++)
if (*cp == '\\')
*cp = '/';
if (modpath[0] == '/' && modpath[1] == '/')
rv = efipart_to_dp(modpath, dp);
else if (strchr(modpath, ':'))
rv = dev_path_to_dp(modpath, dp);
else
rv = path_to_dp(modpath, dp);
free(modpath);
return (rv);
}