/
githubmirror
/
efivar
Обзор
Документация
Войти
/
githubmirror
/
efivar
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
39
src/linux.c
716 строк
19 KB
Heinrich Schuchardt
linux: handle non-ACPI systems in device_get()
30 янв 2024, 02:15
30 янв 2024, 02:15
931e97b
Код
Авторство
О чём код?
// SPDX-License-Identifier: LGPL-2.1-or-later /* * libefiboot - library for the manipulation of EFI boot variables * Copyright 2012-2019 Red Hat, Inc. * Copyright (C) 2001 Dell Computer Corporation <Matt_Domsch@dell.com> */ #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/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/mount.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 "efiboot.h" int HIDDEN find_parent_devpath(const char * const child, char **parent) { 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 HIDDEN set_part_name(struct device *dev, const char * const fmt, ...) { 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 HIDDEN reset_part_name(struct device *dev) { 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; } int HIDDEN set_part(struct device *dev, int value) { int rc; 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; } int HIDDEN set_disk_name(struct device *dev, const char * const fmt, ...) { 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; } int HIDDEN set_disk_and_part_name(struct device *dev) { int rc = -1; char *ultimate = pathseg(dev->link, -1); char *penultimate = pathseg(dev->link, -2); char *approximate = pathseg(dev->link, -3); char *proximate = pathseg(dev->link, -4); char *psl5 = pathseg(dev->link, -5); /* * devlinks look something like: * maj:min -> ../../devices/pci$PCI_STUFF/$BLOCKDEV_STUFF/block/$DISK/$PART */ 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 ? : ""); debug("psl5:'%s'", psl5 ? : ""); 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); rc = 0; } 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); rc = 0; } 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); rc = 0; } 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); rc = 0; } else if ((proximate && ultimate && !strcmp(proximate, "nvme-fabrics")) || (approximate && ultimate && !strcmp(approximate, "nvme-subsystem"))) { /* * 259:0 ->../../devices/virtual/nvme-fabrics/ctl/nvme0/nvme0n1 * ^ proximate ^ ultimate * or * 259:5 -> ../../devices/virtual/nvme-subsystem/nvme-subsys0/nvme0n1 * ^ approximate ^ penultimate * ultimate ^ */ set_disk_name(dev, "%s", ultimate); debug("disk:%s", ultimate); rc = 0; } else if ((psl5 && penultimate && ultimate && !strcmp(psl5, "nvme-fabrics")) || (proximate && penultimate && ultimate && !strcmp(proximate, "nvme-subsystem"))) { /* * 259:1 -> ../../devices/virtual/nvme-fabrics/ctl/nvme0/nvme0n1/nvme0n1p1 * ^psl5 ^ penultimate * ultimate ^ * or * 259:6 -> ../../devices/virtual/nvme-subsystem/nvme-subsys0/nvme0n1/nvme0n1p1 * ^ proximate ^ penultimate * ultimate ^ */ set_disk_name(dev, "%s", penultimate); set_part_name(dev, "%s", ultimate); debug("disk:%s part:%s", penultimate, ultimate); rc = 0; } if (rc < 0) efi_error("Could not parse disk name:\"%s\"", dev->link); return rc; } 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, &virtual_root_parser, &pci_parser, &virtblk_parser, &sas_parser, &sata_parser, &nvme_parser, &ata_parser, &scsi_parser, &i2o_parser, &emmc_parser, NULL }; void HIDDEN device_free(struct device *dev) { 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); } static void print_dev_dp_node(struct device *dev, struct dev_probe *probe) { ssize_t dpsz; uint8_t *dp; ssize_t bufsz; uint8_t *buf; ssize_t sz; dpsz = probe->create(dev, NULL, 0, 0); if (dpsz <= 0) return; dp = alloca(dpsz + 4); if (!dp) return; dpsz = probe->create(dev, dp, dpsz, 0); if (dpsz <= 0) return; sz = efidp_make_end_entire(dp + dpsz, 4); if (sz < 0) return; dpsz += sz; bufsz = efidp_format_device_path(NULL, 0, (const_efidp)dp, dpsz); if (bufsz <= 0) return; buf = alloca(bufsz); if (!buf) return; bufsz = efidp_format_device_path(buf, bufsz, (const_efidp)dp, dpsz); if (bufsz <= 0) return; debug("Device path node is %s", buf); } struct device HIDDEN *device_get(int fd, int partition) { 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) { debug("readlink of /sys/block/%s/device failed", dev->disk_name); dev->device = strdup(""); } else { dev->device = strdup(tmpbuf); } if (!dev->device) { efi_error("strdup(\"%s\") failed", tmpbuf); goto err; } /* * So, on a normal disk, you get something like: * /sys/block/sda/device -> ../../0:0:0:0 * /sys/block/sda/device/driver -> ../../../../../../../bus/scsi/drivers/sd * * On a directly attached nvme device you get: * /sys/block/nvme0n1/device -> ../../nvme0 * /sys/block/nvme0n1/device/device -> ../../../0000:6e:00.0 * /sys/block/nvme0n1/device/device/driver -> ../../../../bus/pci/drivers/nvme * * On a fabric-attached nvme device, you get something like: * /sys/block/nvme0n1/device -> ../../nvme0 * /sys/block/nvme0n1/device/device -> ../../ctl * /sys/block/nvme0n1/device/device/device -> ../../../../../0000:6e:00.0 * /sys/block/nvme0n1/device/device/device/driver -> ../../../../../../bus/pci/drivers/nvme-fabrics * * ... I think? I don't have one in front of me. */ char *filepath = NULL; rc = find_device_file(&filepath, "driver", "block/%s", dev->disk_name); if (rc >= 0) { rc = sysfs_readlink(&tmpbuf, "%s", filepath); if (rc < 0 || !tmpbuf) { efi_error("readlink of /sys/%s failed", filepath); goto err; } linkbuf = pathseg(tmpbuf, -1); if (!linkbuf) { efi_error("could not get segment -1 of \"%s\"", tmpbuf); goto err; } dev->driver = strdup(linkbuf); } else { dev->driver = strdup(""); } 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) { debug("parsing %s failed", probe->name); continue; } else if (pos > 0) { char match[pos+1]; strncpy(match, current, pos); match[pos] = '\0'; debug("%s matched '%s'", probe->name, match); dev->flags |= probe->flags; if (probe->flags & DEV_PROVIDES_HD || probe->flags & DEV_PROVIDES_ROOT || probe->flags & DEV_ABBREV_ONLY) needs_root = false; if (probe->create) print_dev_dp_node(dev, probe); dev->probes[n++] = dev_probes[i]; current += pos; if (current[0] == '\0') debug("finished"); else debug("current:'%s'", current); last_successful_probe = i; if (!*current || !strncmp(current, "block/", 6)) break; continue; } debug("dev_probes[%d]: %p dev->interface_type: %d\n", i+1, 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 HIDDEN make_blockdev_path(uint8_t *buf, ssize_t size, struct device *dev) { 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 HIDDEN make_mac_path(uint8_t *buf, ssize_t size, const char * const ifname) { 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; } /************************************************************ * 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; } // vim:fenc=utf-8:tw=75:noet