/* * 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 http://www.opensolaris.org/os/licensing. * 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 #include #include #include #include #include #include #include #include #include #ifdef HAVE_LIBUDEV #include #endif /* * 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); } /* * Given a shorthand device name check if a file by that name exists in any * of the 'zpool_default_import_path' or ZPOOL_IMPORT_PATH directories. If * one is found, store its fully qualified path in the 'path' buffer passed * by the caller and return 0, otherwise return an error. */ int zfs_resolve_shortname(const char *name, char *path, size_t len) { int i, error = -1; char *dir, *env, *envdup; env = getenv("ZPOOL_IMPORT_PATH"); errno = ENOENT; if (env) { envdup = strdup(env); dir = strtok(envdup, ":"); while (dir && error) { (void) snprintf(path, len, "%s/%s", dir, name); error = access(path, F_OK); dir = strtok(NULL, ":"); } free(envdup); } else { const char * const *zpool_default_import_path; size_t count; zpool_default_import_path = zpool_default_search_paths(&count); for (i = 0; i < count && error < 0; i++) { (void) snprintf(path, len, "%s/%s", zpool_default_import_path[i], name); error = access(path, F_OK); } } return (error ? ENOENT : 0); } /* * Given a shorthand device name look for a match against 'cmp_name'. This * is done by checking all prefix expansions using either the default * 'zpool_default_import_paths' or the ZPOOL_IMPORT_PATH environment * variable. Proper partition suffixes will be appended if this is a * whole disk. When a match is found 0 is returned otherwise ENOENT. */ static int zfs_strcmp_shortname(const char *name, const char *cmp_name, int wholedisk) { int path_len, cmp_len, i = 0, error = ENOENT; char *dir, *env, *envdup = NULL; char path_name[MAXPATHLEN]; const char * const *zpool_default_import_path; size_t count; zpool_default_import_path = zpool_default_search_paths(&count); cmp_len = strlen(cmp_name); env = getenv("ZPOOL_IMPORT_PATH"); if (env) { envdup = strdup(env); dir = strtok(envdup, ":"); } else { dir = (char *)zpool_default_import_path[i]; } while (dir) { /* Trim trailing directory slashes from ZPOOL_IMPORT_PATH */ if (env) { while (dir[strlen(dir)-1] == '/') dir[strlen(dir)-1] = '\0'; } path_len = snprintf(path_name, MAXPATHLEN, "%s/%s", dir, name); if (wholedisk) path_len = zfs_append_partition(path_name, MAXPATHLEN); if ((path_len == cmp_len) && strcmp(path_name, cmp_name) == 0) { error = 0; break; } if (env) { dir = strtok(NULL, ":"); } else if (++i < count) { dir = (char *)zpool_default_import_path[i]; } else { dir = NULL; } } if (env) free(envdup); return (error); } /* * Given either a shorthand or fully qualified path name look for a match * against 'cmp'. The passed name will be expanded as needed for comparison * purposes and redundant slashes stripped to ensure an accurate match. */ int zfs_strcmp_pathname(const char *name, const char *cmp, int wholedisk) { int path_len, cmp_len; char path_name[MAXPATHLEN]; char cmp_name[MAXPATHLEN]; char *dir, *dup; /* Strip redundant slashes if one exists due to ZPOOL_IMPORT_PATH */ memset(cmp_name, 0, MAXPATHLEN); dup = strdup(cmp); dir = strtok(dup, "/"); while (dir) { strlcat(cmp_name, "/", sizeof (cmp_name)); strlcat(cmp_name, dir, sizeof (cmp_name)); dir = strtok(NULL, "/"); } free(dup); if (name[0] != '/') return (zfs_strcmp_shortname(name, cmp_name, wholedisk)); (void) strlcpy(path_name, name, MAXPATHLEN); path_len = strlen(path_name); cmp_len = strlen(cmp_name); if (wholedisk) { path_len = zfs_append_partition(path_name, MAXPATHLEN); if (path_len == -1) return (ENOMEM); } if ((path_len != cmp_len) || strcmp(path_name, cmp_name)) return (ENOENT); return (0); } /* * Allocate and return the underlying device name for a device mapper device. * If a device mapper device maps to multiple devices, return the first 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. * * Returns 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; int size; 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; size = asprintf(&tmp, "/sys/block/%s/slaves/", dev_str); if (size == -1 || !tmp) goto end; dp = opendir(tmp); if (dp == NULL) goto end; /* Return first sd* entry in /sys/block/dm-N/slaves/ */ while ((ep = readdir(dp))) { if (ep->d_type != DT_DIR) { /* skip "." and ".." dirs */ size = asprintf(&path, "/dev/%s", ep->d_name); break; } } end: if (dp != NULL) closedir(dp); free(tmp); free(realp); return (path); } /* * Return 1 if device is a device mapper or multipath device. * Return 0 if not. */ int zfs_dev_is_dm(const char *dev_name) { char *tmp; tmp = dm_get_underlying_path(dev_name); if (tmp == NULL) return (0); free(tmp); return (1); } /* * 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. */ int zfs_dev_is_whole_disk(const char *dev_name) { struct dk_gpt *label; int fd; if ((fd = open(dev_name, O_RDONLY | O_DIRECT)) < 0) return (0); if (efi_alloc_and_init(fd, EFI_NUMPAR, &label) != 0) { (void) close(fd); return (0); } efi_free(label); (void) close(fd); return (1); } /* * 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); } /* * 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). * * For example, disk "sda" in enclosure slot 1: * dev: "sda" * returns: "/sys/class/enclosure/1:0:3:0/Slot 1" * * 'dev' must be a non-devicemapper device. * * Returned string must be freed. */ 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) { tmp1 = NULL; /* To make free() at the end a NOP */ goto end; } /* * Look though all sysfs entries in /sys/block//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 (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)) { free(tmp2); tmp2 = NULL; /* To make free() at the end a NOP */ 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 (asprintf(&path, "/sys/class/%s", tmp3) == -1) { /* If asprintf() fails, 'path' is undefined */ path = NULL; break; } if (path == NULL) break; } end: free(tmp2); free(tmp1); if (dp != NULL) closedir(dp); return (path); } /* * 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(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. */ char * zfs_strip_partition_path(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); } #ifdef HAVE_LIBUDEV /* * A disk is considered a multipath whole disk when: * DEVNAME key value has "dm-" * DM_NAME key value has "mpath" prefix * DM_UUID key exists * ID_PART_TABLE_TYPE key does not exist or is not gpt */ static boolean_t udev_mpath_whole_disk(struct udev_device *dev) { const char *devname, *type, *uuid; 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"); if ((devname != NULL && strncmp(devname, "/dev/dm-", 8) == 0) && ((type == NULL) || (strcmp(type, "gpt") != 0)) && (uuid != NULL)) { return (B_TRUE); } return (B_FALSE); } /* * Check if a disk is effectively a multipath whole disk */ boolean_t is_mpath_whole_disk(const char *path) { struct udev *udev; struct udev_device *dev = NULL; char nodepath[MAXPATHLEN]; char *sysname; boolean_t wholedisk = B_FALSE; 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); } wholedisk = udev_mpath_whole_disk(dev); udev_device_unref(dev); return (wholedisk); } #endif