diff --git a/lib/libspl/include/libgen.h b/lib/libspl/include/libgen.h index 7c03d81fd..c46d7454e 100644 --- a/lib/libspl/include/libgen.h +++ b/lib/libspl/include/libgen.h @@ -28,6 +28,7 @@ #define _LIBSPL_LIBGEN_H #include +#include_next extern int mkdirp(const char *, mode_t); diff --git a/lib/libzfs/libzfs_import.c b/lib/libzfs/libzfs_import.c index 6203cd19b..435d91fb8 100644 --- a/lib/libzfs/libzfs_import.c +++ b/lib/libzfs/libzfs_import.c @@ -47,6 +47,7 @@ #include #include #include +#include #ifdef HAVE_LIBUDEV #include #include @@ -1676,6 +1677,120 @@ zpool_clear_label(int fd) return (0); } +static void +zpool_find_import_scan_add_slice(libzfs_handle_t *hdl, pthread_mutex_t *lock, + avl_tree_t *cache, char *path, const char *name, int order) +{ + avl_index_t where; + rdsk_node_t *slice; + + slice = zfs_alloc(hdl, sizeof (rdsk_node_t)); + if (asprintf(&slice->rn_name, "%s/%s", path, name) == -1) { + free(slice); + return; + } + slice->rn_vdev_guid = 0; + slice->rn_lock = lock; + slice->rn_avl = cache; + slice->rn_hdl = hdl; + slice->rn_order = order + IMPORT_ORDER_SCAN_OFFSET; + slice->rn_labelpaths = B_FALSE; + + pthread_mutex_lock(lock); + if (avl_find(cache, slice, &where)) { + free(slice->rn_name); + free(slice); + } else { + avl_insert(cache, slice, where); + } + pthread_mutex_unlock(lock); +} + +static int +zpool_find_import_scan_dir(libzfs_handle_t *hdl, pthread_mutex_t *lock, + avl_tree_t *cache, char *dir, int order) +{ + int error; + char path[MAXPATHLEN]; + struct dirent64 *dp; + DIR *dirp; + + if (realpath(dir, path) == NULL) { + error = errno; + if (error == ENOENT) + return (0); + + zfs_error_aux(hdl, strerror(error)); + (void) zfs_error_fmt(hdl, EZFS_BADPATH, dgettext( + TEXT_DOMAIN, "cannot resolve path '%s'"), dir); + return (error); + } + + dirp = opendir(path); + if (dirp == NULL) { + error = errno; + zfs_error_aux(hdl, strerror(error)); + (void) zfs_error_fmt(hdl, EZFS_BADPATH, + dgettext(TEXT_DOMAIN, "cannot open '%s'"), path); + return (error); + } + + while ((dp = readdir64(dirp)) != NULL) { + const char *name = dp->d_name; + if (name[0] == '.' && + (name[1] == 0 || (name[1] == '.' && name[2] == 0))) + continue; + + zpool_find_import_scan_add_slice(hdl, lock, cache, path, name, + order); + } + + (void) closedir(dirp); + return (0); +} + +static int +zpool_find_import_scan_path(libzfs_handle_t *hdl, pthread_mutex_t *lock, + avl_tree_t *cache, char *dir, int order) +{ + int error = 0; + char path[MAXPATHLEN]; + char *d, *b; + char *dpath, *name; + + /* + * Seperate the directory part and last part of the + * path. We do this so that we can get the realpath of + * the directory. We don't get the realpath on the + * whole path because if it's a symlink, we want the + * path of the symlink not where it points to. + */ + d = zfs_strdup(hdl, dir); + b = zfs_strdup(hdl, dir); + dpath = dirname(d); + name = basename(b); + + if (realpath(dpath, path) == NULL) { + error = errno; + if (error == ENOENT) { + error = 0; + goto out; + } + + zfs_error_aux(hdl, strerror(error)); + (void) zfs_error_fmt(hdl, EZFS_BADPATH, dgettext( + TEXT_DOMAIN, "cannot resolve path '%s'"), dir); + goto out; + } + + zpool_find_import_scan_add_slice(hdl, lock, cache, path, name, order); + +out: + free(b); + free(d); + return (error); +} + /* * Scan a list of directories for zfs devices. */ @@ -1694,11 +1809,9 @@ zpool_find_import_scan(libzfs_handle_t *hdl, pthread_mutex_t *lock, offsetof(rdsk_node_t, rn_node)); for (i = 0; i < dirs; i++) { - char path[MAXPATHLEN]; - struct dirent64 *dp; - DIR *dirp; + struct stat sbuf; - if (realpath(dir[i], path) == NULL) { + if (stat(dir[i], &sbuf) != 0) { error = errno; if (error == ENOENT) continue; @@ -1709,39 +1822,20 @@ zpool_find_import_scan(libzfs_handle_t *hdl, pthread_mutex_t *lock, goto error; } - dirp = opendir(path); - if (dirp == NULL) { - error = errno; - zfs_error_aux(hdl, strerror(error)); - (void) zfs_error_fmt(hdl, EZFS_BADPATH, - dgettext(TEXT_DOMAIN, "cannot open '%s'"), path); - goto error; + /* + * If dir[i] is a directory, we walk through it and add all + * the entry to the cache. If it's not a directory, we just + * add it to the cache. + */ + if (S_ISDIR(sbuf.st_mode)) { + if ((error = zpool_find_import_scan_dir(hdl, lock, + cache, dir[i], i)) != 0) + goto error; + } else { + if ((error = zpool_find_import_scan_path(hdl, lock, + cache, dir[i], i)) != 0) + goto error; } - - while ((dp = readdir64(dirp)) != NULL) { - const char *name = dp->d_name; - if (name[0] == '.' && - (name[1] == 0 || (name[1] == '.' && name[2] == 0))) - continue; - - slice = zfs_alloc(hdl, sizeof (rdsk_node_t)); - error = asprintf(&slice->rn_name, "%s/%s", path, name); - if (error == -1) { - free(slice); - continue; - } - slice->rn_vdev_guid = 0; - slice->rn_lock = lock; - slice->rn_avl = cache; - slice->rn_hdl = hdl; - slice->rn_order = i + IMPORT_ORDER_SCAN_OFFSET; - slice->rn_labelpaths = B_FALSE; - pthread_mutex_lock(lock); - avl_add(cache, slice); - pthread_mutex_unlock(lock); - } - - (void) closedir(dirp); } *slice_cache = cache; diff --git a/man/man8/zpool.8 b/man/man8/zpool.8 index 64f570d7b..c69ff8446 100644 --- a/man/man8/zpool.8 +++ b/man/man8/zpool.8 @@ -87,13 +87,13 @@ .Nm .Cm import .Op Fl D -.Op Fl d Ar dir +.Op Fl d Ar dir Ns | Ns device .Nm .Cm import .Fl a .Op Fl DflmN .Op Fl F Oo Fl n Oc Oo Fl T Oc Oo Fl X Oc -.Op Fl c Ar cachefile Ns | Ns Fl d Ar dir +.Op Fl c Ar cachefile Ns | Ns Fl d Ar dir Ns | Ns device .Op Fl o Ar mntopts .Oo Fl o Ar property Ns = Ns Ar value Oc Ns ... .Op Fl R Ar root @@ -101,7 +101,7 @@ .Cm import .Op Fl Dflm .Op Fl F Oo Fl n Oc Oo Fl T Oc Oo Fl X Oc -.Op Fl c Ar cachefile Ns | Ns Fl d Ar dir +.Op Fl c Ar cachefile Ns | Ns Fl d Ar dir Ns | Ns device .Op Fl o Ar mntopts .Oo Fl o Ar property Ns = Ns Ar value Oc Ns ... .Op Fl R Ar root @@ -1137,7 +1137,7 @@ performed. .Nm .Cm import .Op Fl D -.Op Fl d Ar dir +.Op Fl d Ar dir Ns | Ns device .Xc Lists pools available to import. If the @@ -1168,8 +1168,10 @@ pool property. This .Ar cachefile is used instead of searching for devices. -.It Fl d Ar dir -Searches for devices or files in +.It Fl d Ar dir Ns | Ns Ar device +Uses +.Ar device +or searches for devices or files in .Ar dir . The .Fl d @@ -1183,7 +1185,7 @@ Lists destroyed pools only. .Fl a .Op Fl DflmN .Op Fl F Oo Fl n Oc Oo Fl T Oc Oo Fl X Oc -.Op Fl c Ar cachefile Ns | Ns Fl d Ar dir +.Op Fl c Ar cachefile Ns | Ns Fl d Ar dir Ns | Ns device .Op Fl o Ar mntopts .Oo Fl o Ar property Ns = Ns Ar value Oc Ns ... .Op Fl R Ar root @@ -1209,8 +1211,10 @@ pool property. This .Ar cachefile is used instead of searching for devices. -.It Fl d Ar dir -Searches for devices or files in +.It Fl d Ar dir Ns | Ns Ar device +Uses +.Ar device +or searches for devices or files in .Ar dir . The .Fl d @@ -1304,7 +1308,7 @@ health of your pool and should only be used as a last resort. .Cm import .Op Fl Dflm .Op Fl F Oo Fl n Oc Oo Fl t Oc Oo Fl T Oc Oo Fl X Oc -.Op Fl c Ar cachefile Ns | Ns Fl d Ar dir +.Op Fl c Ar cachefile Ns | Ns Fl d Ar dir Ns | Ns device .Op Fl o Ar mntopts .Oo Fl o Ar property Ns = Ns Ar value Oc Ns ... .Op Fl R Ar root @@ -1338,8 +1342,10 @@ pool property. This .Ar cachefile is used instead of searching for devices. -.It Fl d Ar dir -Searches for devices or files in +.It Fl d Ar dir Ns | Ns Ar device +Uses +.Ar device +or searches for devices or files in .Ar dir . The .Fl d diff --git a/tests/runfiles/linux.run b/tests/runfiles/linux.run index af96e6a64..0819623cd 100644 --- a/tests/runfiles/linux.run +++ b/tests/runfiles/linux.run @@ -328,6 +328,7 @@ tests = ['zpool_import_001_pos', 'zpool_import_002_pos', 'zpool_import_006_pos', 'zpool_import_007_pos', 'zpool_import_008_pos', 'zpool_import_009_neg', 'zpool_import_010_pos', 'zpool_import_011_neg', 'zpool_import_012_pos', 'zpool_import_013_neg', 'zpool_import_014_pos', + 'zpool_import_015_pos', 'zpool_import_features_001_pos', 'zpool_import_features_002_neg', 'zpool_import_features_003_pos','zpool_import_missing_001_pos', 'zpool_import_missing_002_pos', diff --git a/tests/zfs-tests/tests/functional/cli_root/zpool_import/Makefile.am b/tests/zfs-tests/tests/functional/cli_root/zpool_import/Makefile.am index fab6e7459..94031b9a7 100644 --- a/tests/zfs-tests/tests/functional/cli_root/zpool_import/Makefile.am +++ b/tests/zfs-tests/tests/functional/cli_root/zpool_import/Makefile.am @@ -17,6 +17,7 @@ dist_pkgdata_SCRIPTS = \ zpool_import_012_pos.ksh \ zpool_import_013_neg.ksh \ zpool_import_014_pos.ksh \ + zpool_import_015_pos.ksh \ zpool_import_all_001_pos.ksh \ zpool_import_features_001_pos.ksh \ zpool_import_features_002_neg.ksh \ diff --git a/tests/zfs-tests/tests/functional/cli_root/zpool_import/zpool_import_015_pos.ksh b/tests/zfs-tests/tests/functional/cli_root/zpool_import/zpool_import_015_pos.ksh new file mode 100755 index 000000000..c984b9bf0 --- /dev/null +++ b/tests/zfs-tests/tests/functional/cli_root/zpool_import/zpool_import_015_pos.ksh @@ -0,0 +1,54 @@ +#!/bin/ksh -p +# +# This file and its contents are supplied under the terms of the +# Common Development and Distribution License ("CDDL"), version 1.0. +# You may only use this file in accordance with the terms of version +# 1.0 of the CDDL. +# +# A full copy of the text of the CDDL should have accompanied this +# source. A copy of the CDDL is also available via the Internet at +# http://www.illumos.org/license/CDDL. +# + +# +# Copyright (c) 2018 by Nutanix. All rights reserved. +# + +. $STF_SUITE/include/libtest.shlib +. $STF_SUITE/tests/functional/cli_root/zpool_import/zpool_import.cfg + +# +# DESCRIPTION: +# Make sure zpool import -d works. +# +# STRATEGY: +# 1. Create test pool A. +# 2. Export pool A. +# 3. Verify 'import -d ' works +# + +verify_runnable "global" + +function cleanup +{ + destroy_pool $TESTPOOL1 + + log_must rm $VDEV0 $VDEV1 + log_must truncate -s $FILE_SIZE $VDEV0 $VDEV1 +} + +log_assert "Pool can be imported with '-d '" +log_onexit cleanup + +log_must zpool create $TESTPOOL1 $VDEV0 $VDEV1 +log_must zpool export $TESTPOOL1 + +log_must zpool import -d $VDEV0 -d $VDEV1 $TESTPOOL1 +log_must zpool export $TESTPOOL1 + +# mix -d and -d +log_must mkdir $DEVICE_DIR/test_dir +log_must ln -s $VDEV0 $DEVICE_DIR/test_dir/disk +log_must zpool import -d $DEVICE_DIR/test_dir -d $VDEV1 $TESTPOOL1 + +log_pass "Pool can be imported with '-d '"