diff options
Diffstat (limited to 'lib/libzutil/os/linux/zutil_device_path_os.c')
-rw-r--r-- | lib/libzutil/os/linux/zutil_device_path_os.c | 691 |
1 files changed, 0 insertions, 691 deletions
diff --git a/lib/libzutil/os/linux/zutil_device_path_os.c b/lib/libzutil/os/linux/zutil_device_path_os.c deleted file mode 100644 index 8ed062bf9b37..000000000000 --- a/lib/libzutil/os/linux/zutil_device_path_os.c +++ /dev/null @@ -1,691 +0,0 @@ -// SPDX-License-Identifier: CDDL-1.0 -/* - * CDDL HEADER START - * - * The contents of this file are subject to the terms of the - * Common Development and Distribution License (the "License"). - * You may not use this file except in compliance with the License. - * - * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE - * or https://opensource.org/licenses/CDDL-1.0. - * See the License for the specific language governing permissions - * and limitations under the License. - * - * When distributing Covered Code, include this CDDL HEADER in each - * file and include the License file at usr/src/OPENSOLARIS.LICENSE. - * If applicable, add the following below this CDDL HEADER, with the - * fields enclosed by brackets "[]" replaced with your own identifying - * information: Portions Copyright [yyyy] [name of copyright owner] - * - * CDDL HEADER END - */ - -/* - * Copyright (c) 2005, 2010, Oracle and/or its affiliates. All rights reserved. - */ - -#include <ctype.h> -#include <dirent.h> -#include <fcntl.h> -#include <stdio.h> -#include <stdlib.h> -#include <string.h> -#include <sys/efi_partition.h> - -#ifdef HAVE_LIBUDEV -#include <libudev.h> -#endif - -#include <libzutil.h> - -/* - * Append partition suffix to an otherwise fully qualified device path. - * This is used to generate the name the full path as its stored in - * ZPOOL_CONFIG_PATH for whole disk devices. On success the new length - * of 'path' will be returned on error a negative value is returned. - */ -int -zfs_append_partition(char *path, size_t max_len) -{ - int len = strlen(path); - - if ((strncmp(path, UDISK_ROOT, strlen(UDISK_ROOT)) == 0) || - (strncmp(path, ZVOL_ROOT, strlen(ZVOL_ROOT)) == 0)) { - if (len + 6 >= max_len) - return (-1); - - (void) strcat(path, "-part1"); - len += 6; - } else { - if (len + 2 >= max_len) - return (-1); - - if (isdigit(path[len-1])) { - (void) strcat(path, "p1"); - len += 2; - } else { - (void) strcat(path, "1"); - len += 1; - } - } - - return (len); -} - -/* - * Remove partition suffix from a vdev path. Partition suffixes may take three - * forms: "-partX", "pX", or "X", where X is a string of digits. The second - * case only occurs when the suffix is preceded by a digit, i.e. "md0p0" The - * third case only occurs when preceded by a string matching the regular - * expression "^([hsv]|xv)d[a-z]+", i.e. a scsi, ide, virtio or xen disk. - * - * caller must free the returned string - */ -char * -zfs_strip_partition(const char *path) -{ - char *tmp = strdup(path); - char *part = NULL, *d = NULL; - if (!tmp) - return (NULL); - - if ((part = strstr(tmp, "-part")) && part != tmp) { - d = part + 5; - } else if ((part = strrchr(tmp, 'p')) && - part > tmp + 1 && isdigit(*(part-1))) { - d = part + 1; - } else if ((tmp[0] == 'h' || tmp[0] == 's' || tmp[0] == 'v') && - tmp[1] == 'd') { - for (d = &tmp[2]; isalpha(*d); part = ++d) { } - } else if (strncmp("xvd", tmp, 3) == 0) { - for (d = &tmp[3]; isalpha(*d); part = ++d) { } - } - if (part && d && *d != '\0') { - for (; isdigit(*d); d++) { } - if (*d == '\0') - *part = '\0'; - } - - return (tmp); -} - -/* - * Same as zfs_strip_partition, but allows "/dev/" to be in the pathname - * - * path: /dev/sda1 - * returns: /dev/sda - * - * Returned string must be freed. - */ -static char * -zfs_strip_partition_path(const char *path) -{ - char *newpath = strdup(path); - char *sd_offset; - char *new_sd; - - if (!newpath) - return (NULL); - - /* Point to "sda1" part of "/dev/sda1" */ - sd_offset = strrchr(newpath, '/') + 1; - - /* Get our new name "sda" */ - new_sd = zfs_strip_partition(sd_offset); - if (!new_sd) { - free(newpath); - return (NULL); - } - - /* Paste the "sda" where "sda1" was */ - strlcpy(sd_offset, new_sd, strlen(sd_offset) + 1); - - /* Free temporary "sda" */ - free(new_sd); - - return (newpath); -} - -/* - * Strip the unwanted portion of a device path. - */ -const char * -zfs_strip_path(const char *path) -{ - size_t spath_count; - const char *const *spaths = zpool_default_search_paths(&spath_count); - - for (size_t i = 0; i < spath_count; ++i) - if (strncmp(path, spaths[i], strlen(spaths[i])) == 0 && - path[strlen(spaths[i])] == '/') - return (path + strlen(spaths[i]) + 1); - - return (path); -} - -/* - * Read the contents of a sysfs file into an allocated buffer and remove the - * last newline. - * - * This is useful for reading sysfs files that return a single string. Return - * an allocated string pointer on success, NULL otherwise. Returned buffer - * must be freed by the user. - */ -static char * -zfs_read_sysfs_file(char *filepath) -{ - char buf[4096]; /* all sysfs files report 4k size */ - char *str = NULL; - - FILE *fp = fopen(filepath, "r"); - if (fp == NULL) { - return (NULL); - } - if (fgets(buf, sizeof (buf), fp) == buf) { - /* success */ - - /* Remove the last newline (if any) */ - size_t len = strlen(buf); - if (buf[len - 1] == '\n') { - buf[len - 1] = '\0'; - } - str = strdup(buf); - } - - fclose(fp); - - return (str); -} - -/* - * Given a dev name like "nvme0n1", return the full PCI slot sysfs path to - * the drive (in /sys/bus/pci/slots). - * - * For example: - * dev: "nvme0n1" - * returns: "/sys/bus/pci/slots/0" - * - * 'dev' must be an NVMe device. - * - * Returned string must be freed. Returns NULL on error or no sysfs path. - */ -static char * -zfs_get_pci_slots_sys_path(const char *dev_name) -{ - DIR *dp = NULL; - struct dirent *ep; - char *address1 = NULL; - char *address2 = NULL; - char *path = NULL; - char buf[MAXPATHLEN]; - char *tmp; - - /* If they preface 'dev' with a path (like "/dev") then strip it off */ - tmp = strrchr(dev_name, '/'); - if (tmp != NULL) - dev_name = tmp + 1; /* +1 since we want the chr after '/' */ - - if (strncmp("nvme", dev_name, 4) != 0) - return (NULL); - - (void) snprintf(buf, sizeof (buf), "/sys/block/%s/device/address", - dev_name); - - address1 = zfs_read_sysfs_file(buf); - if (!address1) - return (NULL); - - /* - * /sys/block/nvme0n1/device/address format will - * be "0000:01:00.0" while /sys/bus/pci/slots/0/address will be - * "0000:01:00". Just NULL terminate at the '.' so they match. - */ - tmp = strrchr(address1, '.'); - if (tmp != NULL) - *tmp = '\0'; - - dp = opendir("/sys/bus/pci/slots/"); - if (dp == NULL) { - free(address1); - return (NULL); - } - - /* - * Look through all the /sys/bus/pci/slots/ subdirs - */ - while ((ep = readdir(dp))) { - /* - * We only care about directory names that are a single number. - * Sometimes there's other directories like - * "/sys/bus/pci/slots/0-3/" in there - skip those. - */ - if (!zfs_isnumber(ep->d_name)) - continue; - - (void) snprintf(buf, sizeof (buf), - "/sys/bus/pci/slots/%s/address", ep->d_name); - - address2 = zfs_read_sysfs_file(buf); - if (!address2) - continue; - - if (strcmp(address1, address2) == 0) { - /* Addresses match, we're all done */ - free(address2); - if (asprintf(&path, "/sys/bus/pci/slots/%s", - ep->d_name) == -1) { - continue; - } - break; - } - free(address2); - } - - closedir(dp); - free(address1); - - return (path); -} - -/* - * Given a dev name like "sda", return the full enclosure sysfs path to - * the disk. You can also pass in the name with "/dev" prepended - * to it (like /dev/sda). This works for both JBODs and NVMe PCI devices. - * - * For example, disk "sda" in enclosure slot 1: - * dev_name: "sda" - * returns: "/sys/class/enclosure/1:0:3:0/Slot 1" - * - * Or: - * - * dev_name: "nvme0n1" - * returns: "/sys/bus/pci/slots/0" - * - * 'dev' must be a non-devicemapper device. - * - * Returned string must be freed. Returns NULL on error. - */ -char * -zfs_get_enclosure_sysfs_path(const char *dev_name) -{ - DIR *dp = NULL; - struct dirent *ep; - char buf[MAXPATHLEN]; - char *tmp1 = NULL; - char *tmp2 = NULL; - char *tmp3 = NULL; - char *path = NULL; - size_t size; - int tmpsize; - - if (dev_name == NULL) - return (NULL); - - /* If they preface 'dev' with a path (like "/dev") then strip it off */ - tmp1 = strrchr(dev_name, '/'); - if (tmp1 != NULL) - dev_name = tmp1 + 1; /* +1 since we want the chr after '/' */ - - tmpsize = asprintf(&tmp1, "/sys/block/%s/device", dev_name); - if (tmpsize == -1 || tmp1 == NULL) { - tmp1 = NULL; - goto end; - } - - dp = opendir(tmp1); - if (dp == NULL) - goto end; - - /* - * Look though all sysfs entries in /sys/block/<dev>/device for - * the enclosure symlink. - */ - while ((ep = readdir(dp))) { - /* Ignore everything that's not our enclosure_device link */ - if (strstr(ep->d_name, "enclosure_device") == NULL) - continue; - - if (tmp2 != NULL) - free(tmp2); - if (asprintf(&tmp2, "%s/%s", tmp1, ep->d_name) == -1) { - tmp2 = NULL; - break; - } - - size = readlink(tmp2, buf, sizeof (buf)); - - /* Did readlink fail or crop the link name? */ - if (size == -1 || size >= sizeof (buf)) - break; - - /* - * We got a valid link. readlink() doesn't terminate strings - * so we have to do it. - */ - buf[size] = '\0'; - - /* - * Our link will look like: - * - * "../../../../port-11:1:2/..STUFF../enclosure/1:0:3:0/SLOT 1" - * - * We want to grab the "enclosure/1:0:3:0/SLOT 1" part - */ - tmp3 = strstr(buf, "enclosure"); - if (tmp3 == NULL) - break; - - if (path != NULL) - free(path); - if (asprintf(&path, "/sys/class/%s", tmp3) == -1) { - /* If asprintf() fails, 'path' is undefined */ - path = NULL; - break; - } - } - -end: - free(tmp2); - free(tmp1); - - if (dp != NULL) - closedir(dp); - - if (!path) { - /* - * This particular disk isn't in a JBOD. It could be an NVMe - * drive. If so, look up the NVMe device's path in - * /sys/bus/pci/slots/. Within that directory is a 'attention' - * file which controls the NVMe fault LED. - */ - path = zfs_get_pci_slots_sys_path(dev_name); - } - - return (path); -} - -/* - * Allocate and return the underlying device name for a device mapper device. - * - * For example, dm_name = "/dev/dm-0" could return "/dev/sda". Symlinks to a - * DM device (like /dev/disk/by-vdev/A0) are also allowed. - * - * If the DM device has multiple underlying devices (like with multipath - * DM devices), then favor underlying devices that have a symlink back to their - * back to their enclosure device in sysfs. This will be useful for the - * zedlet scripts that toggle the fault LED. - * - * Returns an underlying device name, or NULL on error or no match. If dm_name - * is not a DM device then return NULL. - * - * NOTE: The returned name string must be *freed*. - */ -static char * -dm_get_underlying_path(const char *dm_name) -{ - DIR *dp = NULL; - struct dirent *ep; - char *realp; - char *tmp = NULL; - char *path = NULL; - char *dev_str; - char *first_path = NULL; - char *enclosure_path; - - if (dm_name == NULL) - return (NULL); - - /* dm name may be a symlink (like /dev/disk/by-vdev/A0) */ - realp = realpath(dm_name, NULL); - if (realp == NULL) - return (NULL); - - /* - * If they preface 'dev' with a path (like "/dev") then strip it off. - * We just want the 'dm-N' part. - */ - tmp = strrchr(realp, '/'); - if (tmp != NULL) - dev_str = tmp + 1; /* +1 since we want the chr after '/' */ - else - dev_str = tmp; - - if (asprintf(&tmp, "/sys/block/%s/slaves/", dev_str) == -1) { - tmp = NULL; - goto end; - } - - dp = opendir(tmp); - if (dp == NULL) - goto end; - - /* - * A device-mapper device can have multiple paths to it (multipath). - * Favor paths that have a symlink back to their enclosure device. - * We have to do this since some enclosures may only provide a symlink - * back for one underlying path to a disk and not the other. - * - * If no paths have links back to their enclosure, then just return the - * first path. - */ - while ((ep = readdir(dp))) { - if (ep->d_type != DT_DIR) { /* skip "." and ".." dirs */ - if (!first_path) - first_path = strdup(ep->d_name); - - enclosure_path = - zfs_get_enclosure_sysfs_path(ep->d_name); - - if (!enclosure_path) - continue; - - if (asprintf(&path, "/dev/%s", ep->d_name) == -1) - path = NULL; - free(enclosure_path); - break; - } - } - -end: - if (dp != NULL) - closedir(dp); - free(tmp); - free(realp); - - if (!path && first_path) { - /* - * None of the underlying paths had a link back to their - * enclosure devices. Throw up out hands and return the first - * underlying path. - */ - if (asprintf(&path, "/dev/%s", first_path) == -1) - path = NULL; - } - - free(first_path); - return (path); -} - -/* - * Return B_TRUE if device is a device mapper or multipath device. - * Return B_FALSE if not. - */ -boolean_t -zfs_dev_is_dm(const char *dev_name) -{ - - char *tmp; - tmp = dm_get_underlying_path(dev_name); - if (tmp == NULL) - return (B_FALSE); - - free(tmp); - return (B_TRUE); -} - -/* - * By "whole disk" we mean an entire physical disk (something we can - * label, toggle the write cache on, etc.) as opposed to the full - * capacity of a pseudo-device such as lofi or did. We act as if we - * are labeling the disk, which should be a pretty good test of whether - * it's a viable device or not. Returns B_TRUE if it is and B_FALSE if - * it isn't. - */ -boolean_t -zfs_dev_is_whole_disk(const char *dev_name) -{ - struct dk_gpt *label = NULL; - int fd; - - if ((fd = open(dev_name, O_RDONLY | O_DIRECT | O_CLOEXEC)) < 0) - return (B_FALSE); - - if (efi_alloc_and_init(fd, EFI_NUMPAR, &label) != 0) { - (void) close(fd); - return (B_FALSE); - } - - efi_free(label); - (void) close(fd); - - return (B_TRUE); -} - -/* - * Lookup the underlying device for a device name - * - * Often you'll have a symlink to a device, a partition device, - * or a multipath device, and want to look up the underlying device. - * This function returns the underlying device name. If the device - * name is already the underlying device, then just return the same - * name. If the device is a DM device with multiple underlying devices - * then return the first one. - * - * For example: - * - * 1. /dev/disk/by-id/ata-QEMU_HARDDISK_QM00001 -> ../../sda - * dev_name: /dev/disk/by-id/ata-QEMU_HARDDISK_QM00001 - * returns: /dev/sda - * - * 2. /dev/mapper/mpatha (made up of /dev/sda and /dev/sdb) - * dev_name: /dev/mapper/mpatha - * returns: /dev/sda (first device) - * - * 3. /dev/sda (already the underlying device) - * dev_name: /dev/sda - * returns: /dev/sda - * - * 4. /dev/dm-3 (mapped to /dev/sda) - * dev_name: /dev/dm-3 - * returns: /dev/sda - * - * 5. /dev/disk/by-id/scsi-0QEMU_drive-scsi0-0-0-0-part9 -> ../../sdb9 - * dev_name: /dev/disk/by-id/scsi-0QEMU_drive-scsi0-0-0-0-part9 - * returns: /dev/sdb - * - * 6. /dev/disk/by-uuid/5df030cf-3cd9-46e4-8e99-3ccb462a4e9a -> ../dev/sda2 - * dev_name: /dev/disk/by-uuid/5df030cf-3cd9-46e4-8e99-3ccb462a4e9a - * returns: /dev/sda - * - * Returns underlying device name, or NULL on error or no match. - * - * NOTE: The returned name string must be *freed*. - */ -char * -zfs_get_underlying_path(const char *dev_name) -{ - char *name = NULL; - char *tmp; - - if (dev_name == NULL) - return (NULL); - - tmp = dm_get_underlying_path(dev_name); - - /* dev_name not a DM device, so just un-symlinkize it */ - if (tmp == NULL) - tmp = realpath(dev_name, NULL); - - if (tmp != NULL) { - name = zfs_strip_partition_path(tmp); - free(tmp); - } - - return (name); -} - - -#ifdef HAVE_LIBUDEV - -/* - * A disk is considered a multipath whole disk when: - * DEVNAME key value has "dm-" - * DM_UUID key exists and starts with 'mpath-' - * ID_PART_TABLE_TYPE key does not exist or is not gpt - * ID_FS_LABEL key does not exist (disk isn't labeled) - */ -static boolean_t -is_mpath_udev_sane(struct udev_device *dev) -{ - const char *devname, *type, *uuid, *label; - - devname = udev_device_get_property_value(dev, "DEVNAME"); - type = udev_device_get_property_value(dev, "ID_PART_TABLE_TYPE"); - uuid = udev_device_get_property_value(dev, "DM_UUID"); - label = udev_device_get_property_value(dev, "ID_FS_LABEL"); - - if ((devname != NULL && strncmp(devname, "/dev/dm-", 8) == 0) && - ((type == NULL) || (strcmp(type, "gpt") != 0)) && - ((uuid != NULL) && (strncmp(uuid, "mpath-", 6) == 0)) && - (label == NULL)) { - return (B_TRUE); - } - - return (B_FALSE); -} - -/* - * Check if a disk is a multipath "blank" disk: - * - * 1. The disk has udev values that suggest it's a multipath disk - * 2. The disk is not currently labeled with a filesystem of any type - * 3. There are no partitions on the disk - */ -boolean_t -is_mpath_whole_disk(const char *path) -{ - struct udev *udev; - struct udev_device *dev = NULL; - char nodepath[MAXPATHLEN]; - char *sysname; - - if (realpath(path, nodepath) == NULL) - return (B_FALSE); - sysname = strrchr(nodepath, '/') + 1; - if (strncmp(sysname, "dm-", 3) != 0) - return (B_FALSE); - if ((udev = udev_new()) == NULL) - return (B_FALSE); - if ((dev = udev_device_new_from_subsystem_sysname(udev, "block", - sysname)) == NULL) { - udev_device_unref(dev); - return (B_FALSE); - } - - /* Sanity check some udev values */ - boolean_t is_sane = is_mpath_udev_sane(dev); - udev_device_unref(dev); - - return (is_sane); -} - -#else /* HAVE_LIBUDEV */ - -boolean_t -is_mpath_whole_disk(const char *path) -{ - (void) path; - return (B_FALSE); -} - -#endif /* HAVE_LIBUDEV */ |