Allow 'zpool replace' to use short device names
authorBrian Behlendorf <behlendorf1@llnl.gov>
Wed, 17 Oct 2012 23:58:54 +0000 (16:58 -0700)
committerBrian Behlendorf <behlendorf1@llnl.gov>
Mon, 22 Oct 2012 15:45:58 +0000 (08:45 -0700)
The 'zpool replace' command would fail when given a short name
because unlike on other platforms the short name cannot be
deterministically expanded to a single path.  Multiple path
prefixes must be checked and in addition the partition suffix
for whole disks is determined by the prefix.

To handle this complexity a zfs_strcmp_pathname() function was
added which takes either a short or fully qualified device name.
Short names will be expanded using the prefixes in the default
import search path, or the ZPOOL_IMPORT_PATH environment variable
if it's defined.  All posible expansions are then compared against
the comparison path.  Care is taken to strip redundant slashes to
ensure legitimate matches are not missed.

In the context of this work the existing zfs_resolve_shortname()
function was extended to consider the ZPOOL_IMPORT_PATH when set.
The zfs_append_partition() interface was also simplified to take
only a single buffer.

The vast majority of these changes rework existing Linux specific
code which was originally written to accomidate udev.  However,
there is some minimal cleanup which removes Illumos specific code.
This was done to improve readability but the basic flow and intent
of the upstream code was maintained.

These changes are the logical conclusion of the previos work to
adjust the 'zpool import' search behavior, see commit 44867b6a.

Signed-off-by: Brian Behlendorf <behlendorf1@llnl.gov>
Closes #544
Closes #976

cmd/zpool/zpool_vdev.c
include/libzfs.h
lib/libzfs/libzfs_import.c
lib/libzfs/libzfs_pool.c
lib/libzfs/libzfs_util.c

index 8c4fade..da121f0 100644 (file)
@@ -366,16 +366,18 @@ is_whole_disk(const char *path)
 
 /*
  * This may be a shorthand device path or it could be total gibberish.
- * Check to see if it's a known device in /dev/, /dev/disk/by-id,
- * /dev/disk/by-label, /dev/disk/by-path, /dev/disk/by-uuid,
- * /dev/disk/by-vdev, or /dev/disk/zpool/.  As part of this check, see
- * if we've been given an entire disk (minus the slice number).
+ * Check to see if it is a known device available in zfs_vdev_paths.
+ * As part of this check, see if we've been given an entire disk
+ * (minus the slice number).
  */
 static int
 is_shorthand_path(const char *arg, char *path,
                   struct stat64 *statbuf, boolean_t *wholedisk)
 {
-       if (zfs_resolve_shortname(arg, path, MAXPATHLEN) == 0) {
+       int error;
+
+       error = zfs_resolve_shortname(arg, path, MAXPATHLEN);
+       if (error == 0) {
                *wholedisk = is_whole_disk(path);
                if (*wholedisk || (stat64(path, statbuf) == 0))
                        return (0);
@@ -385,7 +387,7 @@ is_shorthand_path(const char *arg, char *path,
        memset(statbuf, 0, sizeof(*statbuf));
        *wholedisk = B_FALSE;
 
-       return (ENOENT);
+       return (error);
 }
 
 /*
@@ -393,9 +395,9 @@ is_shorthand_path(const char *arg, char *path,
  * device, fill in the device id to make a complete nvlist.  Valid forms for a
  * leaf vdev are:
  *
- *     /dev/xxx        Complete disk path
- *     /xxx            Full path to file
- *     xxx             Shorthand for /dev/disk/yyy/xxx
+ *     /dev/xxx        Complete disk path
+ *     /xxx            Full path to file
+ *     xxx             Shorthand for <zfs_vdev_paths>/xxx
  */
 static nvlist_t *
 make_leaf_vdev(nvlist_t *props, const char *arg, uint64_t is_log)
@@ -959,7 +961,9 @@ make_disks(zpool_handle_t *zhp, nvlist_t *nv)
                 * deletes and recreates the link during which access attempts
                 * will fail with ENOENT.
                 */
-               zfs_append_partition(path, udevpath, sizeof (udevpath));
+               strncpy(udevpath, path, MAXPATHLEN);
+               (void) zfs_append_partition(udevpath, MAXPATHLEN);
+
                if ((strncmp(udevpath, UDISK_ROOT, strlen(UDISK_ROOT)) == 0) &&
                    (lstat64(udevpath, &statbuf) == 0) &&
                    S_ISLNK(statbuf.st_mode))
@@ -983,9 +987,9 @@ make_disks(zpool_handle_t *zhp, nvlist_t *nv)
                }
 
                /*
-                * Update the path to refer to FIRST_SLICE.  The presence of
+                * Update the path to refer to the partition.  The presence of
                 * the 'whole_disk' field indicates to the CLI that we should
-                * chop off the slice number when displaying the device in
+                * chop off the partition number when displaying the device in
                 * future output.
                 */
                verify(nvlist_add_string(nv, ZPOOL_CONFIG_PATH, udevpath) == 0);
index 75e1493..e59350c 100644 (file)
@@ -54,22 +54,11 @@ extern "C" {
 /*
  * Default device paths
  */
+#define        DISK_ROOT               "/dev"
+#define        UDISK_ROOT              "/dev/disk"
 
-#if defined(__sun__) || defined(__sun)
-#define        DISK_ROOT       "/dev/dsk"
-#define        RDISK_ROOT      "/dev/rdsk"
-#define        UDISK_ROOT      RDISK_ROOT
-#define        FIRST_SLICE     "s0"
-#define        BACKUP_SLICE    "s2"
-#endif
-
-#ifdef __linux__
-#define        DISK_ROOT       "/dev"
-#define        RDISK_ROOT      DISK_ROOT
-#define        UDISK_ROOT      "/dev/disk"
-#define        FIRST_SLICE     "1"
-#define        BACKUP_SLICE    ""
-#endif
+#define        DEFAULT_IMPORT_PATH_SIZE        8
+extern char *zpool_default_import_path[DEFAULT_IMPORT_PATH_SIZE];
 
 /*
  * libzfs errors
@@ -658,8 +647,9 @@ extern zfs_handle_t *zfs_path_to_zhandle(libzfs_handle_t *, char *, zfs_type_t);
 extern boolean_t zfs_dataset_exists(libzfs_handle_t *, const char *,
     zfs_type_t);
 extern int zfs_spa_version(zfs_handle_t *, int *);
-extern void zfs_append_partition(const char *path, char *buf, size_t buflen);
+extern int zfs_append_partition(char *path, size_t max_len);
 extern int zfs_resolve_shortname(const char *name, char *path, size_t pathlen);
+extern int zfs_strcmp_pathname(char *name, char *cmp_name, int wholedisk);
 
 /*
  * Mount support functions.
index b5cac60..98dc878 100644 (file)
@@ -984,9 +984,7 @@ err_blkid1:
 }
 #endif /* HAVE_LIBBLKID */
 
-#define DEFAULT_IMPORT_PATH_SIZE       8
-
-static char *
+char *
 zpool_default_import_path[DEFAULT_IMPORT_PATH_SIZE] = {
        "/dev/disk/by-vdev",    /* Custom rules, use first if they exist */
        "/dev/disk/zpool",      /* Custom rules, use first if they exist */
index 2198a82..75e883e 100644 (file)
@@ -1744,10 +1744,11 @@ vdev_to_nvlist_iter(nvlist_t *nv, nvlist_t *search, boolean_t *avail_spare,
                /*
                 * Search for the requested value. Special cases:
                 *
-                * - ZPOOL_CONFIG_PATH for whole disk entries.  These end in with a
-                *   partition suffix "1", "-part1", or "p1".  The suffix is  hidden
-                *   from the user, but included in the string, so this matches around
-                *   it.
+                * - ZPOOL_CONFIG_PATH for whole disk entries.  These end in
+                *   "-part1", or "p1".  The suffix is hidden from the user,
+                *   but included in the string, so this matches around it.
+                * - ZPOOL_CONFIG_PATH for short names zfs_strcmp_shortname()
+                *   is used to check all possible expanded paths.
                 * - looking for a top-level vdev name (i.e. ZPOOL_CONFIG_TYPE).
                 *
                 * Otherwise, all other searches are simple string compares.
@@ -1757,15 +1758,9 @@ vdev_to_nvlist_iter(nvlist_t *nv, nvlist_t *search, boolean_t *avail_spare,
 
                        (void) nvlist_lookup_uint64(nv, ZPOOL_CONFIG_WHOLE_DISK,
                            &wholedisk);
-                       if (wholedisk) {
-                               char buf[MAXPATHLEN];
-
-                               zfs_append_partition(srchval, buf, sizeof (buf));
-                               if (strcmp(val, buf) == 0)
-                                       return (nv);
+                       if (zfs_strcmp_pathname(srchval, val, wholedisk) == 0)
+                               return (nv);
 
-                               break;
-                       }
                } else if (strcmp(srchkey, ZPOOL_CONFIG_TYPE) == 0 && val) {
                        char *type, *idx, *end, *p;
                        uint64_t id, vdev_id;
@@ -1916,7 +1911,6 @@ nvlist_t *
 zpool_find_vdev(zpool_handle_t *zhp, const char *path, boolean_t *avail_spare,
     boolean_t *l2cache, boolean_t *log)
 {
-       char buf[MAXPATHLEN];
        char *end;
        nvlist_t *nvroot, *search, *ret;
        uint64_t guid;
@@ -1928,12 +1922,6 @@ zpool_find_vdev(zpool_handle_t *zhp, const char *path, boolean_t *avail_spare,
                verify(nvlist_add_uint64(search, ZPOOL_CONFIG_GUID, guid) == 0);
        } else if (zpool_vdev_is_interior(path)) {
                verify(nvlist_add_string(search, ZPOOL_CONFIG_TYPE, path) == 0);
-       } else if (path[0] != '/') {
-               if (zfs_resolve_shortname(path, buf, sizeof (buf)) < 0) {
-                       nvlist_free(search);
-                       return (NULL);
-               }
-               verify(nvlist_add_string(search, ZPOOL_CONFIG_PATH, buf) == 0);
        } else {
                verify(nvlist_add_string(search, ZPOOL_CONFIG_PATH, path) == 0);
        }
@@ -3701,7 +3689,7 @@ read_efi_label(nvlist_t *config, diskaddr_t *sb)
        if (nvlist_lookup_string(config, ZPOOL_CONFIG_PATH, &path) != 0)
                return (err);
 
-       (void) snprintf(diskname, sizeof (diskname), "%s%s", RDISK_ROOT,
+       (void) snprintf(diskname, sizeof (diskname), "%s%s", DISK_ROOT,
            strrchr(path, '/'));
        if ((fd = open(diskname, O_RDWR|O_DIRECT)) >= 0) {
                struct dk_gpt *vtoc;
@@ -3839,8 +3827,7 @@ zpool_label_disk(libzfs_handle_t *hdl, zpool_handle_t *zhp, char *name)
                start_block = NEW_START_BLOCK;
        }
 
-       (void) snprintf(path, sizeof (path), "%s/%s%s", RDISK_ROOT, name,
-           BACKUP_SLICE);
+       (void) snprintf(path, sizeof (path), "%s/%s", DISK_ROOT, name);
 
        if ((fd = open(path, O_RDWR|O_DIRECT)) < 0) {
                /*
@@ -3910,9 +3897,11 @@ zpool_label_disk(libzfs_handle_t *hdl, zpool_handle_t *zhp, char *name)
        (void) close(fd);
        efi_free(vtoc);
 
-       /* Wait for the first expected slice to appear. */
-       (void) snprintf(path, sizeof (path), "%s/%s%s%s", DISK_ROOT, name,
-           isdigit(name[strlen(name)-1]) ? "p" : "", FIRST_SLICE);
+       /* Wait for the first expected partition to appear. */
+
+       (void) snprintf(path, sizeof (path), "%s/%s", DISK_ROOT, name);
+       (void) zfs_append_partition(path, MAXPATHLEN);
+
        rval = zpool_label_disk_wait(path, 3000);
        if (rval) {
                zfs_error_aux(hdl, dgettext(TEXT_DOMAIN, "failed to "
index de4bb72..16affd1 100644 (file)
@@ -800,56 +800,165 @@ zfs_path_to_zhandle(libzfs_handle_t *hdl, char *path, zfs_type_t argtype)
 }
 
 /*
- * Given a shorthand device name, check if a file by that name exists in a list
- * of directories under /dev.  If one is found, store its full path in the
- * buffer pointed to by the path argument and return 0, else return -1.  The
- * path buffer must be allocated by the caller.
+ * 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_resolve_shortname(const char *name, char *path, size_t pathlen)
+zfs_append_partition(char *path, size_t max_len)
 {
-       int i, err;
-       char dirs[6][9] = {"by-id", "by-label", "by-path", "by-uuid", "zpool",
-                          "by-vdev"};
-
-       /* /dev/ */
-       (void) snprintf(path, pathlen, "%s/%s", DISK_ROOT, name);
-       err = access(path, F_OK);
-       if (err == 0)
-               return (err);
-
-       /* /dev/mapper/ */
-       (void) snprintf(path, pathlen, "%s/mapper/%s", DISK_ROOT, name);
-       err = access(path, F_OK);
-       if (err == 0)
-               return (err);
-
-       /* /dev/disk/<dirs>/ */
-       for (i = 0; i < 6 && err < 0; i++) {
-               (void) snprintf(path, pathlen, "%s/%s/%s",
-                   UDISK_ROOT, dirs[i], name);
-               err = access(path, F_OK);
+       int len = strlen(path);
+
+       if (strncmp(path, UDISK_ROOT, strlen(UDISK_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 (err);
+       return (len);
 }
 
 /*
- * Append partition suffix to a device path.  This should be used to generate
- * the name of a whole disk as it is stored in the vdev label.  The
- * user-visible names of whole disks do not contain the partition information.
- * Modifies buf which must be allocated by the caller.
+ * 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.
  */
-void
-zfs_append_partition(const char *path, char *buf, size_t buflen)
+int
+zfs_resolve_shortname(const char *name, char *path, size_t len)
 {
-       if (strncmp(path, UDISK_ROOT, strlen(UDISK_ROOT)) == 0)
-               (void) snprintf(buf, buflen, "%s%s%s", path, "-part",
-                       FIRST_SLICE);
-       else
-               (void) snprintf(buf, buflen, "%s%s%s", path,
-                       isdigit(path[strlen(path)-1]) ?  "p" : "",
-                       FIRST_SLICE);
+       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 {
+               for (i = 0; i < DEFAULT_IMPORT_PATH_SIZE && 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(char *name, char *cmp_name, int wholedisk)
+{
+       int path_len, cmp_len, i = 0, error = ENOENT;
+       char *dir, *env, *envdup = NULL;
+       char path_name[MAXPATHLEN];
+
+       cmp_len = strlen(cmp_name);
+       env = getenv("ZPOOL_IMPORT_PATH");
+
+       if (env) {
+               envdup = strdup(env);
+               dir = strtok(envdup, ":");
+       } else {
+               dir =  zpool_default_import_path[i];
+       }
+
+       while (dir) {
+               /* Trim trailing directory slashes from ZPOOL_IMPORT_PATH */
+               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)) {
+                       error = 0;
+                       break;
+               }
+
+               if (env) {
+                       dir = strtok(NULL, ":");
+               } else if (++i < DEFAULT_IMPORT_PATH_SIZE) {
+                       dir = 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(char *name, char *cmp, int wholedisk)
+{
+       int path_len, cmp_len;
+       char path_name[MAXPATHLEN];
+       char cmp_name[MAXPATHLEN];
+       char *dir;
+
+       /* Strip redundant slashes if one exists due to ZPOOL_IMPORT_PATH */
+       memset(cmp_name, 0, MAXPATHLEN);
+       dir = strtok(cmp, "/");
+       while (dir) {
+               strcat(cmp_name, "/");
+               strcat(cmp_name, dir);
+               dir = strtok(NULL, "/");
+       }
+
+       if (name[0] != '/')
+               return zfs_strcmp_shortname(name, cmp_name, wholedisk);
+
+       strncpy(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);
 }
 
 /*