OSDN Git Service

Fix dev->probes intialization test
[android-x86/external-efivar.git] / src / linux.c
index a812b0c..19eb488 100644 (file)
  * Copyright 2012-2015 Red Hat, Inc.
  * Copyright (C) 2001 Dell Computer Corporation <Matt_Domsch@dell.com>
  *
- * This library is free software; you can redistribute it and/or modify it
- * under the terms of the GNU General Public License as published by the
- * Free Software Foundation; either version 2 of the License, or (at your
- * option) any later version.
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of the
+ * License, or (at your option) any later version.
  *
- * This library is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * General Public License for more details.
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see
+ * <http://www.gnu.org/licenses/>.
  *
- * You should have received a copy of the GNU General Public License
- * along with this library.  If not, see <http://www.gnu.org/licenses/>.
  */
+
+#include "fix_coverity.h"
+
 #include <dirent.h>
 #include <errno.h>
 #include <fcntl.h>
 #include <inttypes.h>
 #include <limits.h>
 #include <linux/ethtool.h>
-#include <linux/nvme.h>
+#include <linux/version.h>
 #include <linux/sockios.h>
 #include <net/if.h>
 #include <scsi/scsi.h>
+#include <stdbool.h>
 #include <stdio.h>
 #include <sys/ioctl.h>
 #include <sys/socket.h>
+#include <sys/sysmacros.h>
 #include <sys/types.h>
 #include <sys/param.h>
 #include <sys/stat.h>
 #include <unistd.h>
 
-#include <efivar.h>
-#include <efiboot.h>
+#include "efiboot.h"
 
-#include "dp.h"
-#include "linux.h"
-#include "util.h"
-
-int
-__attribute__((__visibility__ ("hidden")))
-eb_nvme_ns_id(int fd, uint32_t *ns_id)
+int HIDDEN
+find_parent_devpath(const char * const child, char **parent)
 {
-       uint64_t ret = ioctl(fd, NVME_IOCTL_ID, NULL);
-       if ((int)ret < 0)
-               return ret;
-       *ns_id = (uint32_t)ret;
-       return 0;
+        int ret;
+        char *node;
+        char *linkbuf;
+
+        /* strip leading /dev/ */
+        node = strrchr(child, '/');
+        if (!node)
+                return -1;
+        node++;
+
+        /* look up full path symlink */
+        ret = sysfs_readlink(&linkbuf, "class/block/%s", node);
+        if (ret < 0 || !linkbuf)
+                return ret;
+
+        /* strip child */
+        node = strrchr(linkbuf, '/');
+        if (!node)
+                return -1;
+        *node = '\0';
+
+        /* read parent */
+        node = strrchr(linkbuf, '/');
+        if (!node)
+                return -1;
+        *node = '\0';
+        node++;
+
+        /* write out new path */
+        ret = asprintf(parent, "/dev/%s", node);
+        if (ret < 0)
+                return ret;
+
+        return 0;
 }
 
-int
-__attribute__((__visibility__ ("hidden")))
-set_disk_and_part_name(struct disk_info *info)
+int HIDDEN
+set_part_name(struct device *dev, const char * const fmt, ...)
 {
-       char *linkbuf;
-       ssize_t rc;
-
-       rc = sysfs_readlink(&linkbuf, "/sys/dev/block/%"PRIu64":%hhd",
-                     info->major, info->minor);
-       if (rc < 0)
-               return -1;
-
-       char *ultimate;
-       ultimate = strrchr(linkbuf, '/');
-       if (!ultimate) {
-               errno = EINVAL;
-               return -1;
-       }
-       *ultimate = '\0';
-       ultimate++;
-
-       char *penultimate;
-       penultimate = strrchr(linkbuf, '/');
-       if (!penultimate) {
-               errno = EINVAL;
-               return -1;
-       }
-       penultimate++;
-
-       if (!strcmp(penultimate, "block")) {
-               if (!info->disk_name) {
-                       info->disk_name = strdup(ultimate);
-                       if (!info->disk_name)
-                               return -1;
-               }
-               if (!info->part_name) {
-                       rc = asprintf(&info->part_name, "%s%d", info->disk_name,
-                                     info->part);
-                       if (rc < 0)
-                               return -1;
-               }
-       } else {
-               if (!info->disk_name) {
-                       info->disk_name = strdup(penultimate);
-                       if (!info->disk_name)
-                               return -1;
-               }
-               if (!info->part_name) {
-                       info->part_name = strdup(ultimate);
-                       if (!info->part_name)
-                               return -1;
-               }
-       }
-
-       return 0;
+        ssize_t rc;
+        va_list ap;
+        int error;
+
+        if (dev->part <= 0)
+                return 0;
+
+        va_start(ap, fmt);
+        rc = vasprintf(&dev->part_name, fmt, ap);
+        error = errno;
+        va_end(ap);
+        errno = error;
+        if (rc < 0)
+                efi_error("could not allocate memory");
+
+        return rc;
 }
 
-int
-__attribute__((__visibility__ ("hidden")))
-get_partition_number(const char *devpath)
+int HIDDEN
+reset_part_name(struct device *dev)
 {
-       struct stat statbuf = { 0, };
-       int rc;
-       unsigned int maj, min;
-       char *linkbuf;
-       char *partbuf;
-       int ret = -1;
-
-       rc = stat(devpath, &statbuf);
-       if (rc < 0)
-               return -1;
-
-       if (!S_ISBLK(statbuf.st_mode)) {
-               errno = EINVAL;
-               return -1;
-       }
-
-       maj = gnu_dev_major(statbuf.st_rdev);
-       min = gnu_dev_minor(statbuf.st_rdev);
-
-       rc = sysfs_readlink(&linkbuf, "/sys/dev/block/%u:%u", maj, min);
-       if (rc < 0)
-               return -1;
-
-       rc = read_sysfs_file(&partbuf, "/sys/dev/block/%s/partition", linkbuf);
-       if (rc < 0)
-               return -1;
-
-       rc = sscanf(partbuf, "%d\n", &ret);
-       if (rc != 1)
-               return -1;
-       return ret;
+        char *part = NULL;
+        int rc;
+
+        if (dev->part_name) {
+                free(dev->part_name);
+                dev->part_name = NULL;
+        }
+
+        if (dev->part < 1)
+                return 0;
+
+        if (dev->n_probes > 0 &&
+            dev->probes[dev->n_probes-1] &&
+            dev->probes[dev->n_probes-1]->make_part_name) {
+                part = dev->probes[dev->n_probes]->make_part_name(dev);
+                dev->part_name = part;
+                rc = 0;
+        } else {
+                rc = asprintf(&dev->part_name, "%s%d",
+                              dev->disk_name, dev->part);
+                if (rc < 0)
+                        efi_error("could not allocate memory");
+        }
+        return rc;
 }
 
-static int
-sysfs_test_sata(const char *buf, ssize_t size)
+int HIDDEN
+set_part(struct device *dev, int value)
 {
-       if (!strncmp(buf, "ata", MIN(size,3)))
-               return 1;
-       return 0;
-}
+        int rc;
 
-static int
-sysfs_test_sas(const char *buf, ssize_t size, struct disk_info *info)
-{
-       int rc;
-       char *path;
-       struct stat statbuf = { 0, };
-
-       int host;
-       int sz;
-
-       errno = 0;
-       rc = sscanf(buf, "host%d/%n", &host, &sz);
-       if (rc < 1)
-               return (errno == 0) ? 0 : -1;
-
-       rc = asprintfa(&path, "/sys/class/scsi_host/host%d/host_sas_address",
-                       host);
-       if (rc < 0)
-               return -1;
-
-       rc = stat(path, &statbuf);
-       if (rc >= 0)
-               return 1;
-       return 0;
+        if (dev->part == value)
+                return 0;
+
+        dev->part = value;
+        rc = reset_part_name(dev);
+        if (rc < 0)
+                efi_error("reset_part_name() failed");
+
+        return rc;
 }
 
-static ssize_t
-sysfs_sata_get_port_info(uint32_t print_id, struct disk_info *info)
+int HIDDEN
+set_disk_name(struct device *dev, const char * const fmt, ...)
 {
-       DIR *d;
-       struct dirent *de;
-       int saved_errno;
-       uint8_t *buf = NULL;
-       int rc;
-
-       d = opendir("/sys/class/ata_device/");
-       if (!d)
-               return -1;
-
-       while ((de = readdir(d)) != NULL) {
-               uint32_t found_print_id;
-               uint32_t found_pmp;
-               uint32_t found_devno = 0;
-
-               if (!strcmp(de->d_name, ".") || !strcmp(de->d_name, ".."))
-                       continue;
-
-               int rc;
-               rc = sscanf(de->d_name, "dev%d.%d.%d", &found_print_id,
-                           &found_pmp, &found_devno);
-               if (rc == 2) {
-                       found_devno = found_pmp;
-                       found_pmp=0;
-               } else if (rc != 3) {
-                       saved_errno = errno;
-                       closedir(d);
-                       errno = saved_errno;
-                       return -1;
-               }
-               if (found_print_id == print_id) {
-                       info->sata_info.ata_devno = found_devno;
-                       info->sata_info.ata_pmp = found_pmp;
-                       break;
-               }
-       }
-       closedir(d);
-
-       rc = read_sysfs_file(&buf, "/sys/class/ata_port/ata%d/port_no",
-                            print_id);
-       if (rc <= 0)
-               return -1;
-
-       rc = sscanf((char *)buf, "%d", &info->sata_info.ata_port);
-       if (rc != 1)
-               return -1;
-
-       info->sata_info.ata_port -= 1;
-       return 0;
+        ssize_t rc;
+        va_list ap;
+        int error;
+
+        va_start(ap, fmt);
+        rc = vasprintf(&dev->disk_name, fmt, ap);
+        error = errno;
+        va_end(ap);
+        errno = error;
+        if (rc < 0)
+                efi_error("could not allocate memory");
+
+        return rc;
 }
 
-static ssize_t
-sysfs_parse_sata(uint8_t *buf, ssize_t size, ssize_t *off,
-                const char *pbuf, ssize_t psize, ssize_t *poff,
-                struct disk_info *info)
+int HIDDEN
+set_disk_and_part_name(struct device *dev)
 {
-       int psz = 0;
-       int rc;
-
-       *poff = 0;
-       *off = 0;
-
-       uint32_t print_id;
-
-       uint32_t scsi_bus;
-       uint32_t scsi_device;
-       uint32_t scsi_target;
-       uint32_t scsi_lun;
-
-       /* find the ata info:
-        * ata1/host0/target0:0:0/
-        *    ^dev  ^host   x y z
-        */
-       rc = sscanf(pbuf, "ata%d/host%d/target%d:%d:%d/%n",
-                   &print_id, &scsi_bus, &scsi_device, &scsi_target, &scsi_lun,
-                   &psz);
-       if (rc != 5)
-               return -1;
-       *poff += psz;
-
-       /* find the emulated scsi bits (and ignore them)
-        * 0:0:0:0/
-        */
-       uint32_t dummy0, dummy1, dummy2;
-       uint64_t dummy3;
-       rc = sscanf(pbuf+*poff, "%d:%d:%d:%"PRIu64"/%n", &dummy0, &dummy1,
-                   &dummy2, &dummy3, &psz);
-       if (rc != 4)
-               return -1;
-       *poff += psz;
-
-       /* what's left is:
-        * block/sda/sda4
-        */
-       char *disk_name = NULL;
-       char *part_name = NULL;
-       int psz1 = 0;
-       rc = sscanf(pbuf+*poff, "block/%m[^/]%n/%m[^/]%n", &disk_name, &psz,
-                   &part_name, &psz1);
-       if (rc == 1) {
-               rc = asprintf(&part_name, "%s%d", disk_name, info->part);
-               if (rc < 0) {
-                       free(disk_name);
-                       errno = EINVAL;
-                       return -1;
-               }
-               *poff += psz;
-       } else if (rc != 2) {
-               errno = EINVAL;
-               return -1;
-       } else {
-               *poff += psz1;
-       }
-
-       info->sata_info.scsi_bus = scsi_bus;
-       info->sata_info.scsi_device = scsi_device;
-       info->sata_info.scsi_target = scsi_target;
-       info->sata_info.scsi_lun = scsi_lun;
-
-       rc = sysfs_sata_get_port_info(print_id, info);
-       if (rc < 0) {
-               free(disk_name);
-               free(part_name);
-               return -1;
-       }
-
-       if (pbuf[*poff] != '\0') {
-               free(disk_name);
-               free(part_name);
-               errno = EINVAL;
-               return -1;
-       }
-
-       info->disk_name = disk_name;
-       info->part_name = part_name;
-       if (info->interface_type == interface_type_unknown)
-               info->interface_type = sata;
-
-       if (info->interface_type == ata) {
-               *off = efidp_make_atapi(buf, size, info->sata_info.ata_port,
-                                       info->sata_info.ata_pmp,
-                                       info->sata_info.ata_devno);
-       } else {
-               *off = efidp_make_sata(buf, size, info->sata_info.ata_port,
-                                      info->sata_info.ata_pmp,
-                                      info->sata_info.ata_devno);
-       }
-       return *off;
+        /*
+         * results are like such:
+         * maj:min -> ../../devices/pci$PCI_STUFF/$BLOCKDEV_STUFF/block/$DISK/$PART
+         */
+
+        char *ultimate = pathseg(dev->link, -1);
+        char *penultimate = pathseg(dev->link, -2);
+        char *approximate = pathseg(dev->link, -3);
+        char *proximate = pathseg(dev->link, -4);
+
+        errno = 0;
+        debug("dev->disk_name:%p dev->part_name:%p", dev->disk_name, dev->part_name);
+        debug("dev->part:%d", dev->part);
+        debug("ultimate:\"%s\"", ultimate ? : "");
+        debug("penultimate:\"%s\"", penultimate ? : "");
+        debug("approximate:\"%s\"", approximate ? : "");
+        debug("proximate:\"%s\"", proximate ? : "");
+
+        if (ultimate && penultimate &&
+            ((proximate && !strcmp(proximate, "nvme")) ||
+             (approximate && !strcmp(approximate, "block")))) {
+                /*
+                 * 259:1 -> ../../devices/pci0000:00/0000:00:1d.0/0000:05:00.0/nvme/nvme0/nvme0n1/nvme0n1p1
+                 * 8:1 -> ../../devices/pci0000:00/0000:00:17.0/ata2/host1/target1:0:0/1:0:0:0/block/sda/sda1
+                 * 8:33 -> ../../devices/pci0000:00/0000:00:01.0/0000:01:00.0/host4/port-4:0/end_device-4:0/target4:0:0/4:0:0:0/block/sdc/sdc1
+                 * 252:1 -> ../../devices/pci0000:00/0000:00:07.0/virtio2/block/vda/vda1
+                 * 259:3 -> ../../devices/LNXSYSTM:00/LNXSYBUS:00/ACPI0012:00/ndbus0/region11/btt11.0/block/pmem11s/pmem11s1
+                 */
+                set_disk_name(dev, "%s", penultimate);
+                set_part_name(dev, "%s", ultimate);
+                debug("disk:%s part:%s", penultimate, ultimate);
+        } else if (ultimate && approximate && !strcmp(approximate, "nvme")) {
+                /*
+                 * 259:0 -> ../../devices/pci0000:00/0000:00:1d.0/0000:05:00.0/nvme/nvme0/nvme0n1
+                 */
+                set_disk_name(dev, "%s", ultimate);
+                set_part_name(dev, "%sp%d", ultimate, dev->part);
+                debug("disk:%s part:%sp%d", ultimate, ultimate, dev->part);
+        } else if (ultimate && penultimate && !strcmp(penultimate, "block")) {
+                /*
+                 * 253:0 -> ../../devices/virtual/block/dm-0 (... I guess)
+                 * 8:0 -> ../../devices/pci0000:00/0000:00:17.0/ata2/host1/target1:0:0/1:0:0:0/block/sda
+                 * 11:0 -> ../../devices/pci0000:00/0000:00:11.5/ata3/host2/target2:0:0/2:0:0:0/block/sr0
+                 * 8:32 -> ../../devices/pci0000:00/0000:00:01.0/0000:01:00.0/host4/port-4:0/end_device-4:0/target4:0:0/4:0:0:0/block/sdc
+                 * 252:0 -> ../../devices/pci0000:00/0000:00:07.0/virtio2/block/vda
+                 * 259:0 -> ../../devices/LNXSYSTM:00/LNXSYBUS:00/ACPI0012:00/ndbus0/region9/btt9.0/block/pmem9s
+                 * 259:1 -> ../../devices/LNXSYSTM:00/LNXSYBUS:00/ACPI0012:00/ndbus0/region11/btt11.0/block/pmem11s
+                 */
+                set_disk_name(dev, "%s", ultimate);
+                set_part_name(dev, "%s%d", ultimate, dev->part);
+                debug("disk:%s part:%s%d", ultimate, ultimate, dev->part);
+        } else if (ultimate && approximate && !strcmp(approximate, "mtd")) {
+                /*
+                 * 31:0 -> ../../devices/platform/1e000000.palmbus/1e000b00.spi/spi_master/spi32766/spi32766.0/mtd/mtd0/mtdblock0
+                 */
+                set_disk_name(dev, "%s", ultimate);
+                debug("disk:%s", ultimate);
+        }
+
+        return 0;
 }
 
-static ssize_t
-sysfs_parse_sas(uint8_t *buf, ssize_t size, ssize_t *off,
-               const char *pbuf, ssize_t psize, ssize_t *poff,
-               struct disk_info *info)
+static struct dev_probe *dev_probes[] = {
+        /*
+         * pmem needs to be before PCI, so if it provides root it'll
+         * be found first.
+         */
+        &pmem_parser,
+        &acpi_root_parser,
+        &pci_root_parser,
+        &soc_root_parser,
+        &pci_parser,
+        &virtblk_parser,
+        &sas_parser,
+        &sata_parser,
+        &nvme_parser,
+        &ata_parser,
+        &scsi_parser,
+        &i2o_parser,
+        &emmc_parser,
+        NULL
+};
+
+static inline bool
+supports_iface(struct dev_probe *probe, enum interface_type iftype)
 {
-       int rc;
-       int psz = 0;
-       char *filebuf = NULL;
-       uint64_t sas_address;
-
-       *poff = 0;
-       *off = 0;
-
-       /* buf is:
-        * host4/port-4:0/end_device-4:0/target4:0:0/4:0:0:0/block/sdc/sdc1
-        */
-       uint32_t tosser0, tosser1, tosser2;
-
-       /* ignore a bunch of stuff
-        *    host4/port-4:0
-        * or host4/port-4:0:0
-        */
-       rc = sscanf(pbuf+*poff, "host%d/port-%d:%d%n", &tosser0, &tosser1,
-                   &tosser2, &psz);
-       if (rc != 3)
-               return -1;
-       *poff += psz;
-
-       psz = 0;
-       rc = sscanf(pbuf+*poff, ":%d%n", &tosser0, &psz);
-       if (rc != 0 && rc != 1)
-               return -1;
-       *poff += psz;
-
-       /* next:
-        *    /end_device-4:0
-        * or /end_device-4:0:0
-        * awesomely these are the exact same fields that go into port-blah,
-        * but we don't care for now about any of them anyway.
-        */
-       rc = sscanf(pbuf+*poff, "/end_device-%d:%d%n", &tosser0, &tosser1,
-                   &psz);
-       if (rc != 2)
-               return -1;
-       *poff += psz;
-
-       psz = 0;
-       rc = sscanf(pbuf+*poff, ":%d%n", &tosser0, &psz);
-       if (rc != 0 && rc != 1)
-               return -1;
-       *poff += psz;
-
-       /* now:
-        * /target4:0:0/
-        */
-       uint64_t tosser3;
-       rc = sscanf(pbuf+*poff, "/target%d:%d:%"PRIu64"/%n", &tosser0, &tosser1,
-                   &tosser3, &psz);
-       if (rc != 3)
-               return -1;
-       *poff += psz;
-
-       /* now:
-        * %d:%d:%d:%llu/
-        */
-       rc = sscanf(pbuf+*poff, "%d:%d:%d:%"PRIu64"/%n",
-                   &info->sas_info.scsi_bus,
-                   &info->sas_info.scsi_device,
-                   &info->sas_info.scsi_target,
-                   &info->sas_info.scsi_lun, &psz);
-       if (rc != 4)
-               return -1;
-       *poff += psz;
-
-       /* what's left is:
-        * block/sdc/sdc1
-        */
-       char *disk_name = NULL;
-       char *part_name = NULL;
-       rc = sscanf(pbuf+*poff, "block/%m[^/]/%m[^/]%n", &disk_name, &part_name,
-                   &psz);
-       if (rc != 2)
-               return -1;
-       *poff += psz;
-
-       if (pbuf[*poff] != '\0') {
-               free(disk_name);
-               free(part_name);
-               errno = EINVAL;
-               return -1;
-       }
-
-       /*
-        * we also need to get the actual sas_address from someplace...
-        */
-       rc = read_sysfs_file(&filebuf,
-                            "/sys/class/block/%s/device/sas_address",
-                            disk_name);
-       if (rc < 0)
-               return -1;
-
-       rc = sscanf(filebuf, "%"PRIx64, &sas_address);
-       if (rc != 1)
-               return -1;
-
-       info->sas_info.sas_address = sas_address;
-       info->disk_name = disk_name;
-       info->part_name = part_name;
-       info->interface_type = sas;
-
-       *off = efidp_make_sas(buf, size, sas_address);
-       return *off;
+        for (unsigned int i = 0; probe->iftypes[i] != unknown; i++)
+                if (probe->iftypes[i] == iftype)
+                        return true;
+        return false;
 }
 
-static ssize_t
-make_pci_path(uint8_t *buf, ssize_t size, char *pathstr, ssize_t *pathoff)
+void HIDDEN
+device_free(struct device *dev)
 {
-       ssize_t off=0, sz=0;
-       ssize_t poff = 0;
-       int psz;
-       int rc;
-
-       if (pathstr == NULL || pathoff == NULL || pathstr[0] == '\0') {
-               errno = EINVAL;
-               return -1;
-       }
-
-       *pathoff = 0;
-
-       uint16_t root_domain;
-       uint8_t root_bus;
-       uint32_t acpi_hid = 0;
-       uint64_t acpi_uid_int = 0;
-       /*
-        * find the pci root domain and port; they basically look like:
-        * pci0000:00/
-        *    ^d   ^p
-        */
-       rc = sscanf(pathstr+poff, "pci%hx:%hhx/%n", &root_domain,
-                   &root_bus, &psz);
-       if (rc != 2)
-               return -1;
-       poff += psz;
-
-       char *fbuf = NULL;
-       rc = read_sysfs_file(&fbuf,
-                            "/sys/devices/pci%04x:%02x/firmware_node/hid",
-                            root_domain, root_bus);
-       if (rc < 0)
-               return -1;
-
-       uint16_t tmp16 = 0;
-       rc = sscanf((char *)fbuf, "PNP%hx", &tmp16);
-       if (rc != 1)
-               return -1;
-       acpi_hid = EFIDP_EFI_PNP_ID(tmp16);
-
-       errno = 0;
-       fbuf = NULL;
-       int use_uid_str = 0;
-       rc = read_sysfs_file(&fbuf,
-                            "/sys/devices/pci%4x:%02x/firmware_node/uid",
-                            root_domain, root_bus);
-       if (rc <= 0 && errno != ENOENT)
-               return -1;
-       if (rc > 0) {
-               rc = sscanf((char *)fbuf, "%"PRIu64"\n", &acpi_uid_int);
-               if (rc != 1) {
-                       /* kernel uses "%s\n" to print it, so there
-                        * should always be some value and a newline... */
-                       int l = strlen((char *)buf);
-                       if (l >= 1) {
-                               use_uid_str=1;
-                               fbuf[l-1] = '\0';
-                       }
-               }
-       }
-       errno = 0;
-
-       if (use_uid_str) {
-               sz = efidp_make_acpi_hid_ex(buf+off, size?size-off:0,
-                                           acpi_hid, 0, 0, "", (char *)fbuf,
-                                           "");
-       } else {
-               sz = efidp_make_acpi_hid(buf+off, size?size-off:0,
-                                        acpi_hid, acpi_uid_int);
-       }
-       if (sz < 0)
-               return -1;
-       off += sz;
-
-       /* find the pci domain/bus/device/function:
-        * 0000:00:01.0/0000:01:00.0/
-        *              ^d   ^b ^d ^f (of the last one in the series)
-        */
-       int found=0;
-       while (1) {
-               uint16_t domain;
-               uint8_t bus, device, function;
-               rc = sscanf(pathstr+poff, "%hx:%hhx:%hhx.%hhx/%n",
-                           &domain, &bus, &device, &function, &psz);
-               if (rc != 4)
-                       break;
-               found=1;
-               poff += psz;
-
-               sz = efidp_make_pci(buf+off, size?size-off:0, device, function);
-               if (sz < 0)
-                       return -1;
-               off += sz;
-       }
-       if (!found)
-               return -1;
-
-       *pathoff = poff;
-       return off;
+        if (!dev)
+                return;
+        if (dev->link)
+                free(dev->link);
+
+        if (dev->device)
+                free(dev->device);
+
+        if (dev->driver)
+                free(dev->driver);
+
+        if (dev->probes)
+                free(dev->probes);
+
+        if (dev->acpi_root.acpi_hid_str)
+                free(dev->acpi_root.acpi_hid_str);
+        if (dev->acpi_root.acpi_uid_str)
+                free(dev->acpi_root.acpi_uid_str);
+        if (dev->acpi_root.acpi_cid_str)
+                free(dev->acpi_root.acpi_cid_str);
+
+        if (dev->interface_type == network) {
+                if (dev->ifname)
+                        free(dev->ifname);
+        } else {
+                if (dev->disk_name)
+                        free(dev->disk_name);
+                if (dev->part_name)
+                        free(dev->part_name);
+        }
+
+        for (unsigned int i = 0; i < dev->n_pci_devs; i++)
+                if (dev->pci_dev[i].driverlink)
+                        free(dev->pci_dev[i].driverlink);
+
+        if (dev->pci_dev)
+                free(dev->pci_dev);
+
+        memset(dev, 0, sizeof(*dev));
+        free(dev);
 }
 
-int
-__attribute__((__visibility__ ("hidden")))
-make_blockdev_path(uint8_t *buf, ssize_t size, int fd, struct disk_info *info)
+struct device HIDDEN
+*device_get(int fd, int partition)
 {
-       char *linkbuf = NULL;
-       char *driverbuf = NULL;
-       ssize_t off=0, sz=0, loff=0;
-       int lsz = 0;
-       int rc;
-       int found = 0;
-
-       rc = sysfs_readlink(&linkbuf, "/sys/dev/block/%"PRIu64":%u",
-                           info->major, info->minor);
-       if (rc < 0)
-               return -1;
-
-       /*
-        * the sysfs path basically looks like:
-        * ../../devices/pci$PCI_STUFF/$BLOCKDEV_STUFF/block/$DISK/$PART
-        */
-       rc = sscanf(linkbuf+loff, "../../devices/%n", &lsz);
-       if (rc != 0)
-               return -1;
-       loff += lsz;
-
-       ssize_t tmplsz=0;
-       sz = make_pci_path(buf, size, linkbuf+loff, &tmplsz);
-       if (sz < 0)
-               return -1;
-       loff += tmplsz;
-       off += sz;
-
-       char *tmppath = strdupa(linkbuf);
-       if (!tmppath)
-               return -1;
-       tmppath[loff] = '\0';
-       rc = sysfs_readlink(&driverbuf, "/sys/dev/block/%s/driver", tmppath);
-       if (rc < 0)
-               return -1;
-
-       char *driver = strrchr(driverbuf, '/');
-       if (!driver || !*driver)
-               return -1;
-       driver+=1;
-
-       if (!strncmp(driver, "pata_", 5) || !(strcmp(driver, "ata_piix")))
-               info->interface_type = ata;
-
-       if (info->interface_type == interface_type_unknown ||
-           info->interface_type == atapi ||
-           info->interface_type == usb ||
-           info->interface_type == i1394 ||
-           info->interface_type == fibre ||
-           info->interface_type == i2o ||
-           info->interface_type == md) {
-               uint32_t tosser;
-               int tmpoff;
-
-               rc = sscanf(linkbuf+loff, "virtio%x/%n", &tosser, &tmpoff);
-               if (rc < 0) {
-                       return -1;
-               } else if (rc == 1) {
-                       info->interface_type = virtblk;
-                       loff += tmpoff;
-                       found = 1;
-               }
-
-               if (!found) {
-                       uint32_t ns_id=0;
-                       int rc = eb_nvme_ns_id(fd, &ns_id);
-                       if (rc >= 0) {
-                               sz = efidp_make_nvme(buf+off, size?size-off:0,
-                                                    ns_id, NULL);
-                               if (sz < 0)
-                                       return -1;
-
-                               info->interface_type = nvme;
-                               off += sz;
-                               found = 1;
-                       }
-               }
-       }
-
-       /* /dev/sda as SATA looks like:
-        * /sys/dev/block/8:0 -> ../../devices/pci0000:00/0000:00:1f.2/ata1/host0/target0:0:0/0:0:0:0/block/sda
-        */
-       if (!found) {
-               rc = sysfs_test_sata(linkbuf+loff, PATH_MAX-off);
-               if (rc < 0)
-                       return -1;
-               if (!found && rc > 0) {
-                       ssize_t linksz=0;
-                       rc = sysfs_parse_sata(buf+off, size?size-off:0, &sz,
-                                             linkbuf+loff, PATH_MAX-off,
-                                             &linksz, info);
-                       if (rc < 0)
-                               return -1;
-                       loff += linksz;
-                       off += sz;
-                       found = 1;
-               }
-       }
-
-       /* /dev/sdc as SAS looks like:
-        * /sys/dev/block/8:32 -> ../../devices/pci0000:00/0000:00:01.0/0000:01:00.0/host4/port-4:0/end_device-4:0/target4:0:0/4:0:0:0/block/sdc
-        */
-       if (!found) {
-               rc = sysfs_test_sas(linkbuf+loff, PATH_MAX-off, info);
-               if (rc < 0)
-                       return -1;
-               else if (rc > 0) {
-                       ssize_t linksz=0;
-                       rc = sysfs_parse_sas(buf+off, size?size-off:0, &sz,
-                                            linkbuf+loff, PATH_MAX-off,
-                                            &linksz, info);
-                       if (rc < 0)
-                               return -1;
-                       loff += linksz;
-                       off += sz;
-                       found = 1;
-               }
-       }
-
-       if (!found && info->interface_type == scsi) {
-               char *linkbuf;
-
-               rc = sysfs_readlink(&linkbuf, "/sys/class/block/%s/device",
-                             info->disk_name);
-               if (rc < 0)
-                       return 0;
-
-               rc = sscanf(linkbuf, "../../../%d:%d:%d:%"PRIu64,
-                           &info->scsi_info.scsi_bus,
-                           &info->scsi_info.scsi_device,
-                           &info->scsi_info.scsi_target,
-                           &info->scsi_info.scsi_lun);
-               if (rc != 4)
-                       return -1;
-
-               sz = efidp_make_scsi(buf+off, size?size-off:0,
-                                    info->scsi_info.scsi_target,
-                                    info->scsi_info.scsi_lun);
-               if (sz < 0)
-                       return -1;
-               off += sz;
-               found = 1;
-       }
-
-       if (!found) {
-               errno = ENOENT;
-               return -1;
-       }
-
-       return off;
+        struct device *dev;
+        char *linkbuf = NULL, *tmpbuf = NULL;
+        int i = 0;
+        unsigned int n = 0;
+        int rc;
+
+        size_t nmemb = (sizeof(dev_probes)
+                        / sizeof(dev_probes[0])) + 1;
+
+        dev = calloc(1, sizeof(*dev));
+        if (!dev) {
+                efi_error("could not allocate %zd bytes", sizeof(*dev));
+                return NULL;
+        }
+
+        dev->part = partition;
+        debug("partition:%d dev->part:%d", partition, dev->part);
+        dev->probes = calloc(nmemb, sizeof(struct dev_probe *));
+        if (!dev->probes) {
+                efi_error("could not allocate %zd bytes",
+                          nmemb * sizeof(struct dev_probe *));
+                goto err;
+        }
+
+        rc = fstat(fd, &dev->stat);
+        if (rc < 0) {
+                efi_error("fstat(%d) failed", fd);
+                goto err;
+        }
+
+        dev->pci_root.pci_domain = 0xffff;
+        dev->pci_root.pci_bus = 0xff;
+
+        if (S_ISBLK(dev->stat.st_mode)) {
+                dev->major = major(dev->stat.st_rdev);
+                dev->minor = minor(dev->stat.st_rdev);
+        } else if (S_ISREG(dev->stat.st_mode)) {
+                dev->major = major(dev->stat.st_dev);
+                dev->minor = minor(dev->stat.st_dev);
+        } else {
+                efi_error("device is not a block device or regular file");
+                goto err;
+        }
+
+        rc = sysfs_readlink(&linkbuf, "dev/block/%"PRIu64":%"PRIu32,
+                            dev->major, dev->minor);
+        if (rc < 0 || !linkbuf) {
+                efi_error("readlink of /sys/dev/block/%"PRIu64":%"PRIu32" failed",
+                          dev->major, dev->minor);
+                goto err;
+        }
+
+        dev->link = strdup(linkbuf);
+        if (!dev->link) {
+                efi_error("strdup(\"%s\") failed", linkbuf);
+                goto err;
+        }
+        debug("dev->link: %s", dev->link);
+
+        if (dev->part == -1) {
+                rc = read_sysfs_file(&tmpbuf, "dev/block/%s/partition", dev->link);
+                if (rc < 0 || !tmpbuf) {
+                        efi_error("device has no /partition node; not a partition");
+                } else {
+                        rc = sscanf((char *)tmpbuf, "%d\n", &dev->part);
+                        if (rc != 1)
+                                efi_error("couldn't parse partition number for %s", tmpbuf);
+                }
+        }
+
+        rc = set_disk_and_part_name(dev);
+        if (rc < 0) {
+                efi_error("could not set disk and partition names");
+                goto err;
+        }
+        debug("dev->disk_name: %s", dev->disk_name);
+        debug("dev->part_name: %s", dev->part_name);
+
+        rc = sysfs_readlink(&tmpbuf, "block/%s/device", dev->disk_name);
+        if (rc < 0 || !tmpbuf) {
+                efi_error("readlink of /sys/block/%s/device failed",
+                          dev->disk_name);
+                goto err;
+        }
+
+        dev->device = strdup(tmpbuf);
+        if (!dev->device) {
+                efi_error("strdup(\"%s\") failed", tmpbuf);
+                goto err;
+        }
+
+        rc = sysfs_readlink(&tmpbuf, "block/%s/device/driver", dev->disk_name);
+        if (rc < 0 || !tmpbuf) {
+                if (errno == ENOENT) {
+                        /*
+                         * nvme, for example, will have nvme0n1/device point
+                         * at nvme0, and we need to look for device/driver
+                         * there.
+                         */
+                        rc = sysfs_readlink(&tmpbuf,
+                                            "block/%s/device/device/driver",
+                                            dev->disk_name);
+                }
+                if (rc < 0 || !tmpbuf) {
+                        efi_error("readlink of /sys/block/%s/device/driver failed",
+                                  dev->disk_name);
+                        goto err;
+                }
+        }
+
+        linkbuf = pathseg(tmpbuf, -1);
+        if (!linkbuf) {
+                efi_error("could not get segment -1 of \"%s\"", tmpbuf);
+                goto err;
+        }
+
+        dev->driver = strdup(linkbuf);
+        if (!dev->driver) {
+                efi_error("strdup(\"%s\") failed", linkbuf);
+                goto err;
+        }
+
+        const char *current = dev->link;
+        bool needs_root = true;
+        int last_successful_probe = -1;
+
+        debug("searching for device nodes in %s", dev->link);
+        for (i = 0;
+             dev_probes[i] && dev_probes[i]->parse && *current;
+             i++) {
+                struct dev_probe *probe = dev_probes[i];
+                int pos;
+
+                if (!needs_root &&
+                    (probe->flags & DEV_PROVIDES_ROOT)) {
+                        debug("not testing %s because flags is 0x%x",
+                              probe->name, probe->flags);
+                        continue;
+                }
+
+                debug("trying %s", probe->name);
+                pos = probe->parse(dev, current, dev->link);
+                if (pos < 0) {
+                        efi_error("parsing %s failed", probe->name);
+                        goto err;
+                } else if (pos > 0) {
+                        debug("%s matched %s", probe->name, current);
+                        dev->flags |= probe->flags;
+
+                        if (probe->flags & DEV_PROVIDES_HD ||
+                            probe->flags & DEV_PROVIDES_ROOT ||
+                            probe->flags & DEV_ABBREV_ONLY)
+                                needs_root = false;
+
+                        dev->probes[n++] = dev_probes[i];
+                        current += pos;
+                        debug("current:%s", current);
+                        last_successful_probe = i;
+
+                        if (!*current || !strncmp(current, "block/", 6))
+                                break;
+
+                        continue;
+                }
+
+                debug("dev_probes[i+1]: %p dev->interface_type: %d\n",
+                      dev_probes[i+1], dev->interface_type);
+                if (dev_probes[i+1] == NULL && dev->interface_type == unknown) {
+                        pos = 0;
+                        rc = sscanf(current, "%*[^/]/%n", &pos);
+                        if (rc < 0) {
+slash_err:
+                                efi_error("Cannot parse device link segment \"%s\"", current);
+                                goto err;
+                        }
+
+                        while (current[pos] == '/')
+                                pos += 1;
+
+                        if (!current[pos])
+                                goto slash_err;
+
+                        debug("Cannot parse device link segment \"%s\"", current);
+                        debug("Skipping to \"%s\"", current + pos);
+                        debug("This means we can only create abbreviated paths");
+                        dev->flags |= DEV_ABBREV_ONLY;
+                        i = last_successful_probe;
+                        current += pos;
+
+                        if (!*current || !strncmp(current, "block/", 6))
+                                break;
+                }
+        }
+
+        if (dev->interface_type == unknown &&
+            !(dev->flags & DEV_ABBREV_ONLY) &&
+            !strcmp(current, "block/")) {
+                efi_error("unknown storage interface");
+                errno = ENOSYS;
+                goto err;
+        }
+
+        return dev;
+err:
+        device_free(dev);
+        return NULL;
 }
 
-int
-__attribute__((__visibility__ ("hidden")))
-eb_disk_info_from_fd(int fd, struct disk_info *info)
+int HIDDEN
+make_blockdev_path(uint8_t *buf, ssize_t size, struct device *dev)
 {
-       struct stat buf;
-       int rc;
-
-       memset(info, 0, sizeof *info);
-
-       info->pci_root.root_pci_domain = 0xffff;
-       info->pci_root.root_pci_bus = 0xff;
-
-       memset(&buf, 0, sizeof(struct stat));
-       rc = fstat(fd, &buf);
-       if (rc == -1) {
-               perror("stat");
-               return 1;
-       }
-       if (S_ISBLK(buf.st_mode)) {
-               info->major = buf.st_rdev >> 8;
-               info->minor = buf.st_rdev & 0xFF;
-       } else if (S_ISREG(buf.st_mode)) {
-               info->major = buf.st_dev >> 8;
-               info->minor = buf.st_dev & 0xFF;
-       } else {
-               printf("Cannot stat non-block or non-regular file\n");
-               return 1;
-       }
-
-       /* IDE disks can have up to 64 partitions, or 6 bits worth,
-        * and have one bit for the disk number.
-        * This leaves an extra bit at the top.
-        */
-       if (info->major == 3) {
-               info->disknum = (info->minor >> 6) & 1;
-               info->controllernum = (info->major - 3 + 0) + info->disknum;
-               info->interface_type = ata;
-               info->part    = info->minor & 0x3F;
-               return 0;
-       } else if (info->major == 22) {
-               info->disknum = (info->minor >> 6) & 1;
-               info->controllernum = (info->major - 22 + 2) + info->disknum;
-               info->interface_type = ata;
-               info->part    = info->minor & 0x3F;
-               return 0;
-       } else if (info->major >= 33 && info->major <= 34) {
-               info->disknum = (info->minor >> 6) & 1;
-               info->controllernum = (info->major - 33 + 4) + info->disknum;
-               info->interface_type = ata;
-               info->part    = info->minor & 0x3F;
-               return 0;
-       } else if (info->major >= 56 && info->major <= 57) {
-               info->disknum = (info->minor >> 6) & 1;
-               info->controllernum = (info->major - 56 + 8) + info->disknum;
-               info->interface_type = ata;
-               info->part    = info->minor & 0x3F;
-               return 0;
-       } else if (info->major >= 88 && info->major <= 91) {
-               info->disknum = (info->minor >> 6) & 1;
-               info->controllernum = (info->major - 88 + 12) + info->disknum;
-               info->interface_type = ata;
-               info->part    = info->minor & 0x3F;
-               return 0;
-       }
-
-        /* I2O disks can have up to 16 partitions, or 4 bits worth. */
-       if (info->major >= 80 && info->major <= 87) {
-               info->interface_type = i2o;
-               info->disknum = 16*(info->major-80) + (info->minor >> 4);
-               info->part    = (info->minor & 0xF);
-               return 0;
-       }
-
-       /* SCSI disks can have up to 16 partitions, or 4 bits worth
-        * and have one bit for the disk number.
-        */
-       if (info->major == 8) {
-               info->interface_type = scsi;
-               info->disknum = (info->minor >> 4);
-               info->part    = (info->minor & 0xF);
-               return 0;
-       } else  if (info->major >= 65 && info->major <= 71) {
-               info->interface_type = scsi;
-               info->disknum = 16*(info->major-64) + (info->minor >> 4);
-               info->part    = (info->minor & 0xF);
-               return 0;
-       } else  if (info->major >= 128 && info->major <= 135) {
-               info->interface_type = scsi;
-               info->disknum = 16*(info->major-128) + (info->minor >> 4);
-               info->part    = (info->minor & 0xF);
-               return 0;
-       }
-
-       errno = ENOSYS;
-       return -1;
+        ssize_t off = 0;
+
+        debug("entry buf:%p size:%zd", buf, size);
+
+        for (unsigned int i = 0; dev->probes[i] &&
+                                 dev->probes[i]->parse; i++) {
+                struct dev_probe *probe = dev->probes[i];
+                ssize_t sz;
+
+                if (!probe->create)
+                        continue;
+
+                sz = probe->create(dev, buf + off, size ? size - off : 0, 0);
+                if (sz < 0) {
+                        efi_error("could not create %s device path",
+                                  probe->name);
+                        return sz;
+                }
+                off += sz;
+        }
+
+        debug("= %zd", off);
+
+        return off;
 }
 
-static ssize_t
-make_net_pci_path(uint8_t *buf, ssize_t size, const char const *ifname)
+ssize_t HIDDEN
+make_mac_path(uint8_t *buf, ssize_t size, const char * const ifname)
 {
-       char *linkbuf = NULL;
-       ssize_t off=0, sz=0, loff=0;
-       int lsz = 0;
-       int rc;
-
-       rc = sysfs_readlink(&linkbuf, "/sys/class/net/%s", ifname);
-       if (rc < 0)
-               return -1;
-
-       /*
-        * the sysfs path basically looks like:
-        * ../../devices/$PCI_STUFF/net/$IFACE
-        */
-       rc = sscanf(linkbuf+loff, "../../devices/%n", &lsz);
-       if (rc != 0)
-               return -1;
-       loff += lsz;
-
-       ssize_t tmplsz = 0;
-       sz = make_pci_path(buf, size, linkbuf+loff, &tmplsz);
-       if (sz < 0)
-               return -1;
-       off += sz;
-       loff += tmplsz;
-
-       return off;
+        struct ifreq ifr;
+        struct ethtool_drvinfo drvinfo = { 0, };
+        int fd = -1, rc;
+        ssize_t ret = -1, sz, off = 0;
+        char busname[PATH_MAX+1] = "";
+        struct device dev;
+
+        memset(&dev, 0, sizeof (dev));
+        dev.interface_type = network;
+        dev.ifname = strdupa(ifname);
+        if (!dev.ifname)
+                return -1;
+
+        /*
+         * find the device link, which looks like:
+         * ../../devices/$PCI_STUFF/net/$IFACE
+         */
+        rc = sysfs_readlink(&dev.link, "class/net/%s", ifname);
+        if (rc < 0 || !dev.link)
+                goto err;
+
+        memset(&ifr, 0, sizeof (ifr));
+        strncpy(ifr.ifr_name, ifname, IF_NAMESIZE);
+        ifr.ifr_name[IF_NAMESIZE-1] = '\0';
+        drvinfo.cmd = ETHTOOL_GDRVINFO;
+        ifr.ifr_data = (caddr_t)&drvinfo;
+
+        fd = socket(AF_INET, SOCK_DGRAM, 0);
+        if (fd < 0)
+                goto err;
+
+        rc = ioctl(fd, SIOCETHTOOL, &ifr);
+        if (rc < 0)
+                goto err;
+
+        strncpy(busname, drvinfo.bus_info, PATH_MAX);
+
+        rc = ioctl(fd, SIOCGIFHWADDR, &ifr);
+        if (rc < 0)
+                goto err;
+
+        sz = pci_parser.create(&dev, buf, size, off);
+        if (sz < 0)
+                goto err;
+        off += sz;
+
+        sz = efidp_make_mac_addr(buf+off, size?size-off:0,
+                                 ifr.ifr_ifru.ifru_hwaddr.sa_family,
+                                 (uint8_t *)ifr.ifr_ifru.ifru_hwaddr.sa_data,
+                                 sizeof(ifr.ifr_ifru.ifru_hwaddr.sa_data));
+        if (sz < 0)
+                goto err;
+
+        off += sz;
+        ret = off;
+err:
+        if (fd >= 0)
+                close(fd);
+        return ret;
 }
 
-ssize_t
-__attribute__((__visibility__ ("hidden")))
-make_mac_path(uint8_t *buf, ssize_t size, const char const *ifname)
+/************************************************************
+ * get_sector_size
+ * Requires:
+ *  - filedes is an open file descriptor, suitable for reading
+ * Modifies: nothing
+ * Returns:
+ *  sector size, or 512.
+ ************************************************************/
+int UNUSED
+get_sector_size(int filedes)
 {
-       struct ifreq ifr = { 0, };
-       struct ethtool_drvinfo drvinfo = { 0, };
-       int fd, rc;
-       ssize_t ret = -1, sz, off=0;
-       char busname[PATH_MAX+1] = "";
-
-       strncpy(ifr.ifr_name, ifname, IF_NAMESIZE);
-       drvinfo.cmd = ETHTOOL_GDRVINFO;
-       ifr.ifr_data = (caddr_t)&drvinfo;
-
-       fd = socket(AF_INET, SOCK_DGRAM, 0);
-       if (fd < 0)
-               return -1;
-
-       rc = ioctl(fd, SIOCETHTOOL, &ifr);
-       if (rc < 0)
-               goto err;
-
-       strncpy(busname, drvinfo.bus_info, PATH_MAX);
-
-       rc = ioctl(fd, SIOCGIFHWADDR, &ifr);
-       if (rc < 0)
-               goto err;
-
-       sz = make_net_pci_path(buf, size, ifname);
-       if (sz < 0)
-               goto err;
-       off += sz;
-
-       sz = efidp_make_mac_addr(buf+off, size?size-off:0,
-                                ifr.ifr_ifru.ifru_hwaddr.sa_family,
-                                (uint8_t *)ifr.ifr_ifru.ifru_hwaddr.sa_data,
-                                sizeof(ifr.ifr_ifru.ifru_hwaddr.sa_data));
-       if (sz < 0)
-               return -1;
-       off += sz;
-       ret = off;
-err:
-       if (fd >= 0)
-               close(fd);
-       return ret;
+        int rc, sector_size = 512;
+
+        rc = ioctl(filedes, BLKSSZGET, &sector_size);
+        if (rc)
+                sector_size = 512;
+        return sector_size;
 }