diff options
Diffstat (limited to 'lib')
-rw-r--r-- | lib/libzfs/Makefile.am | 2 | ||||
-rw-r--r-- | lib/libzfs/libzfs_import.c | 38 | ||||
-rw-r--r-- | lib/libzfs/libzfs_pool.c | 200 |
3 files changed, 222 insertions, 18 deletions
diff --git a/lib/libzfs/Makefile.am b/lib/libzfs/Makefile.am index f1260ea71..8e596b001 100644 --- a/lib/libzfs/Makefile.am +++ b/lib/libzfs/Makefile.am @@ -35,7 +35,7 @@ libzfs_la_LIBADD = \ $(top_builddir)/lib/libnvpair/libnvpair.la \ $(top_builddir)/lib/libzpool/libzpool.la -libzfs_la_LIBADD += -lm $(LIBBLKID) $(LIBUDEV) +libzfs_la_LIBADD += -lm $(LIBBLKID) $(LIBUDEV) $(LIBDEVMAPPER) libzfs_la_LDFLAGS = -version-info 2:0:0 EXTRA_DIST = $(libzfs_pc_DATA) $(USER_C) diff --git a/lib/libzfs/libzfs_import.c b/lib/libzfs/libzfs_import.c index edd4e5d58..e76f7432a 100644 --- a/lib/libzfs/libzfs_import.c +++ b/lib/libzfs/libzfs_import.c @@ -172,22 +172,34 @@ zfs_device_get_devid(struct udev_device *dev, char *bufptr, size_t buflen) int zfs_device_get_physical(struct udev_device *dev, char *bufptr, size_t buflen) { - const char *physpath, *value; + const char *physpath = NULL; /* - * Skip indirect multipath device nodes + * Normal disks use ID_PATH for their physical path. Device mapper + * devices are virtual and don't have a physical path. For them we + * use ID_VDEV instead, which is setup via the /etc/vdev_id.conf file. + * ID_VDEV provides a persistent path to a virtual device. If you + * don't have vdev_id.conf setup, you cannot use multipath autoreplace. */ - value = udev_device_get_property_value(dev, "DM_MULTIPATH_DEVICE_PATH"); - if (value != NULL && strcmp(value, "1") == 0) - return (ENODATA); /* skip physical for multipath nodes */ - - physpath = udev_device_get_property_value(dev, "ID_PATH"); - if (physpath != NULL && physpath[0] != '\0') { - (void) strlcpy(bufptr, physpath, buflen); - return (0); + if (!((physpath = udev_device_get_property_value(dev, "ID_PATH")) && + physpath[0])) { + if (!((physpath = + udev_device_get_property_value(dev, "ID_VDEV")) && + physpath[0])) { + return (ENODATA); + } } - return (ENODATA); + (void) strlcpy(bufptr, physpath, buflen); + + return (0); +} + +boolean_t +udev_is_mpath(struct udev_device *dev) +{ + return udev_device_get_property_value(dev, "DM_UUID") && + udev_device_get_property_value(dev, "MPATH_SBIN_PATH"); } /* @@ -200,15 +212,13 @@ zfs_device_get_physical(struct udev_device *dev, char *bufptr, size_t buflen) static boolean_t udev_mpath_whole_disk(struct udev_device *dev) { - const char *devname, *mapname, *type, *uuid; + const char *devname, *type, *uuid; devname = udev_device_get_property_value(dev, "DEVNAME"); - mapname = udev_device_get_property_value(dev, "DM_NAME"); 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) && - (mapname != NULL && strncmp(mapname, "mpath", 5) == 0) && ((type == NULL) || (strcmp(type, "gpt") != 0)) && (uuid != NULL)) { return (B_TRUE); diff --git a/lib/libzfs/libzfs_pool.c b/lib/libzfs/libzfs_pool.c index 68cc5f3ee..ebca76834 100644 --- a/lib/libzfs/libzfs_pool.c +++ b/lib/libzfs/libzfs_pool.c @@ -41,6 +41,9 @@ #include <sys/vtoc.h> #include <sys/zfs_ioctl.h> #include <dlfcn.h> +#if HAVE_LIBDEVMAPPER +#include <libdevmapper.h> +#endif #include "zfs_namecheck.h" #include "zfs_prop.h" @@ -3401,10 +3404,12 @@ set_path(zpool_handle_t *zhp, nvlist_t *nv, const char *path) * caller must free the returned string */ char * -zfs_strip_partition(libzfs_handle_t *hdl, char *path) +zfs_strip_partition(char *path) { - char *tmp = zfs_strdup(hdl, path); + char *tmp = strdup(path); char *part = NULL, *d = NULL; + if (!tmp) + return (NULL); if ((part = strstr(tmp, "-part")) && part != tmp) { d = part + 5; @@ -3422,6 +3427,7 @@ zfs_strip_partition(libzfs_handle_t *hdl, char *path) if (*d == '\0') *part = '\0'; } + return (tmp); } @@ -3544,7 +3550,7 @@ zpool_vdev_name(libzfs_handle_t *hdl, zpool_handle_t *zhp, nvlist_t *nv, */ if (nvlist_lookup_uint64(nv, ZPOOL_CONFIG_WHOLE_DISK, &value) == 0 && value && !(name_flags & VDEV_NAME_PATH)) { - return (zfs_strip_partition(hdl, path)); + return (zfs_strip_partition(path)); } } else { verify(nvlist_lookup_string(nv, ZPOOL_CONFIG_TYPE, &path) == 0); @@ -4310,3 +4316,191 @@ zpool_label_disk(libzfs_handle_t *hdl, zpool_handle_t *zhp, char *name) return (0); } + +#if HAVE_LIBDEVMAPPER +static void libdevmapper_dummy_log(int level, const char *file, int line, + int dm_errno_or_class, const char *f, ...) {} + +/* Disable libdevmapper error logging */ +static void disable_libdevmapper_errors(void) { + dm_log_with_errno_init(libdevmapper_dummy_log); +} +/* Enable libdevmapper error logging */ +static void enable_libdevmapper_errors(void) { + dm_log_with_errno_init(NULL); +} +#endif + +/* + * 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" + * + * dm_name should include the "/dev[/mapper]" prefix. + * + * 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(char *dm_name) +{ + char *name = NULL; +#if HAVE_LIBDEVMAPPER + char *tmp; + struct dm_task *dmt = NULL; + struct dm_tree *dt = NULL; + struct dm_tree_node *root, *child; + void *handle = NULL; + struct dm_info info; + const struct dm_info *child_info; + + /* + * Disable libdevmapper errors. It's entirely possible user is not + * running devmapper, or that dm_name is not a devmapper device. + * That's totally ok, we will just harmlessly and silently return NULL. + */ + disable_libdevmapper_errors(); + + /* + * libdevmapper tutorial + * + * libdevmapper is basically a fancy wrapper for its ioctls. You + * create a "task", fill in the needed info to the task (fill in the + * ioctl fields), then run the task (call the ioctl). + * + * First we need the major/minor number for our DM device. + */ + if (!(dmt = dm_task_create(DM_DEVICE_INFO))) + goto end; + + /* Lookup the name in libdevmapper */ + if (!dm_task_set_name(dmt, dm_name)) { + enable_libdevmapper_errors(); + goto end; + } + + if (!dm_task_run(dmt)) + goto end; + + /* Get DM device's major/minor */ + if (!dm_task_get_info(dmt, &info)) + goto end; + + /* We have major/minor number. Lookup the dm device's children */ + if (!(dt = dm_tree_create())) + goto end; + + /* We add the device into the tree and its children get populated */ + if (!dm_tree_add_dev(dt, info.major, info.minor)) + goto end; + + if (!(root = dm_tree_find_node(dt, 0, 0))) + goto end; + + if (!(child = dm_tree_next_child(&handle, root, 1))) + goto end; + + /* Get child's major/minor numbers */ + if (!(child_info = dm_tree_node_get_info(child))) + goto end; + + if ((asprintf(&tmp, "/dev/block/%d:%d", child_info->major, + child_info->minor) == -1) || !tmp) + goto end; + + /* Further translate /dev/block/ name into the normal name */ + name = realpath(tmp, NULL); + free(tmp); + +end: + if (dmt) + dm_task_destroy(dmt); + if (dt) + dm_tree_free(dt); + enable_libdevmapper_errors(); +#endif /* HAVE_LIBDEVMAPPER */ + + return (name); +} + +/* + * Return 1 if device is a device mapper or multipath device. + * Return 0 if not. + */ +int +dev_is_dm(char *devname) +{ + + char *tmp; + tmp = dm_get_underlying_path(devname); + if (!tmp) + return (0); + + free(tmp); + 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 * +get_underlying_path(libzfs_handle_t *hdl, char *dev_name) +{ + char *name = NULL; + char *tmp; + + if (!dev_name) + return (NULL); + + tmp = dm_get_underlying_path(dev_name); + + /* dev_name not a DM device, so just un-symlinkize it */ + if (!tmp) + tmp = realpath(dev_name, NULL); + + if (tmp) { + name = zfs_strip_partition(tmp); + free(tmp); + } + + return (name); +} |