* 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":%hhu",
- 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 = major(statbuf.st_rdev);
- min = 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;
-static ssize_t
-sysfs_sata_get_port_info(uint32_t print_id, struct disk_info *info)
-{
- DIR *d;
- struct dirent *de;
- 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 == 3) {
- info->sata_info.ata_devno = 0;
- info->sata_info.ata_pmp = found_pmp;
- break;
- } else if (rc == 2) {
- info->sata_info.ata_devno = 0;
- info->sata_info.ata_pmp = 0x8000;
- break;
- } else {
- closedir(d);
- errno = EINVAL;
- return -1;
- }
- }
- 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;
-
- 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_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_name(struct device *dev, const char * const fmt, ...)
{
- 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;
+ 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_sas(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 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;
+ /*
+ * 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
-make_pci_path(uint8_t *buf, ssize_t size, char *pathstr, ssize_t *pathoff)
+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)
{
- 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);
-
- /* Apparently basically nothing can look up a PcieRoot() node,
- * because they just check _CID. So since _CID for the root pretty
- * much always has to be PNP0A03 anyway, just use that no matter
- * what.
- */
- if (acpi_hid == EFIDP_ACPI_PCIE_ROOT_HID)
- acpi_hid = EFIDP_ACPI_PCI_ROOT_HID;
-
- 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;
+ for (unsigned int i = 0; probe->iftypes[i] != unknown; i++)
+ if (probe->iftypes[i] == iftype)
+ return true;
+ return false;
}
-int
-__attribute__((__visibility__ ("hidden")))
-make_blockdev_path(uint8_t *buf, ssize_t size, int fd, struct disk_info *info)
+void HIDDEN
+device_free(struct device *dev)
{
- 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;
+ 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")))
-eb_disk_info_from_fd(int fd, struct disk_info *info)
+struct device HIDDEN
+*device_get(int fd, int partition)
{
- 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;
+ 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;
}
-static ssize_t
-make_net_pci_path(uint8_t *buf, ssize_t size, const char * const ifname)
+int HIDDEN
+make_blockdev_path(uint8_t *buf, ssize_t size, struct device *dev)
{
- 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;
+ 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;
}
-ssize_t
-__attribute__((__visibility__ ("hidden")))
+ssize_t HIDDEN
make_mac_path(uint8_t *buf, ssize_t size, const char * const ifname)
{
- struct ifreq ifr;
- struct ethtool_drvinfo drvinfo = { 0, };
- int fd, rc;
- ssize_t ret = -1, sz, off=0;
- char busname[PATH_MAX+1] = "";
-
- memset(&ifr, 0, sizeof (ifr));
- 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;
+ 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;
+ if (fd >= 0)
+ close(fd);
+ return ret;
+}
+
+/************************************************************
+ * 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)
+{
+ int rc, sector_size = 512;
+
+ rc = ioctl(filedes, BLKSSZGET, §or_size);
+ if (rc)
+ sector_size = 512;
+ return sector_size;
}