aboutsummaryrefslogtreecommitdiff
path: root/lib/libzutil/os/linux/zutil_device_path_os.c
diff options
context:
space:
mode:
Diffstat (limited to 'lib/libzutil/os/linux/zutil_device_path_os.c')
-rw-r--r--lib/libzutil/os/linux/zutil_device_path_os.c691
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 */