+typedef struct rdsk_node {
+ char *rn_name;
+ int rn_dfd;
+ libzfs_handle_t *rn_hdl;
+ nvlist_t *rn_config;
+ avl_tree_t *rn_avl;
+ avl_node_t rn_node;
+ boolean_t rn_nozpool;
+} rdsk_node_t;
+
+static int
+slice_cache_compare(const void *arg1, const void *arg2)
+{
+ const char *nm1 = ((rdsk_node_t *)arg1)->rn_name;
+ const char *nm2 = ((rdsk_node_t *)arg2)->rn_name;
+ char *nm1slice, *nm2slice;
+ int rv;
+
+ /*
+ * slices zero and two are the most likely to provide results,
+ * so put those first
+ */
+ nm1slice = strstr(nm1, "s0");
+ nm2slice = strstr(nm2, "s0");
+ if (nm1slice && !nm2slice) {
+ return (-1);
+ }
+ if (!nm1slice && nm2slice) {
+ return (1);
+ }
+ nm1slice = strstr(nm1, "s2");
+ nm2slice = strstr(nm2, "s2");
+ if (nm1slice && !nm2slice) {
+ return (-1);
+ }
+ if (!nm1slice && nm2slice) {
+ return (1);
+ }
+
+ rv = strcmp(nm1, nm2);
+ if (rv == 0)
+ return (0);
+ return (rv > 0 ? 1 : -1);
+}
+
+static void
+check_one_slice(avl_tree_t *r, char *diskname, uint_t partno,
+ diskaddr_t size, uint_t blksz)
+{
+ rdsk_node_t tmpnode;
+ rdsk_node_t *node;
+ char sname[MAXNAMELEN];
+
+ tmpnode.rn_name = &sname[0];
+ (void) snprintf(tmpnode.rn_name, MAXNAMELEN, "%s%u",
+ diskname, partno);
+ /*
+ * protect against division by zero for disk labels that
+ * contain a bogus sector size
+ */
+ if (blksz == 0)
+ blksz = DEV_BSIZE;
+ /* too small to contain a zpool? */
+ if ((size < (SPA_MINDEVSIZE / blksz)) &&
+ (node = avl_find(r, &tmpnode, NULL)))
+ node->rn_nozpool = B_TRUE;
+}
+
+static void
+nozpool_all_slices(avl_tree_t *r, const char *sname)
+{
+ char diskname[MAXNAMELEN];
+ char *ptr;
+ int i;
+
+ (void) strncpy(diskname, sname, MAXNAMELEN);
+ if (((ptr = strrchr(diskname, 's')) == NULL) &&
+ ((ptr = strrchr(diskname, 'p')) == NULL))
+ return;
+ ptr[0] = 's';
+ ptr[1] = '\0';
+ for (i = 0; i < NDKMAP; i++)
+ check_one_slice(r, diskname, i, 0, 1);
+ ptr[0] = 'p';
+ for (i = 0; i <= FD_NUMPART; i++)
+ check_one_slice(r, diskname, i, 0, 1);
+}
+
+static void
+check_slices(avl_tree_t *r, int fd, const char *sname)
+{
+ struct extvtoc vtoc;
+ struct dk_gpt *gpt;
+ char diskname[MAXNAMELEN];
+ char *ptr;
+ int i;
+
+ (void) strncpy(diskname, sname, MAXNAMELEN);
+ if ((ptr = strrchr(diskname, 's')) == NULL || !isdigit(ptr[1]))
+ return;
+ ptr[1] = '\0';
+
+ if (read_extvtoc(fd, &vtoc) >= 0) {
+ for (i = 0; i < NDKMAP; i++)
+ check_one_slice(r, diskname, i,
+ vtoc.v_part[i].p_size, vtoc.v_sectorsz);
+ } else if (efi_alloc_and_read(fd, &gpt) >= 0) {
+ /*
+ * on x86 we'll still have leftover links that point
+ * to slices s[9-15], so use NDKMAP instead
+ */
+ for (i = 0; i < NDKMAP; i++)
+ check_one_slice(r, diskname, i,
+ gpt->efi_parts[i].p_size, gpt->efi_lbasize);
+ /* nodes p[1-4] are never used with EFI labels */
+ ptr[0] = 'p';
+ for (i = 1; i <= FD_NUMPART; i++)
+ check_one_slice(r, diskname, i, 0, 1);
+ efi_free(gpt);
+ }
+}
+
+static void
+zpool_open_func(void *arg)
+{
+ rdsk_node_t *rn = arg;
+ struct stat64 statbuf;
+ nvlist_t *config;
+ int fd;
+
+ if (rn->rn_nozpool)
+ return;
+ if ((fd = openat64(rn->rn_dfd, rn->rn_name, O_RDONLY)) < 0) {
+ /* symlink to a device that's no longer there */
+ if (errno == ENOENT)
+ nozpool_all_slices(rn->rn_avl, rn->rn_name);
+ return;
+ }
+ /*
+ * Ignore failed stats. We only want regular
+ * files, character devs and block devs.
+ */
+ if (fstat64(fd, &statbuf) != 0 ||
+ (!S_ISREG(statbuf.st_mode) &&
+ !S_ISCHR(statbuf.st_mode) &&
+ !S_ISBLK(statbuf.st_mode))) {
+ (void) close(fd);
+ return;
+ }
+ /* this file is too small to hold a zpool */
+ if (S_ISREG(statbuf.st_mode) &&
+ statbuf.st_size < SPA_MINDEVSIZE) {
+ (void) close(fd);
+ return;
+ } else if (!S_ISREG(statbuf.st_mode)) {
+ /*
+ * Try to read the disk label first so we don't have to
+ * open a bunch of minor nodes that can't have a zpool.
+ */
+ check_slices(rn->rn_avl, fd, rn->rn_name);
+ }
+
+ if ((zpool_read_label(fd, &config)) != 0) {
+ (void) close(fd);
+ (void) no_memory(rn->rn_hdl);
+ return;
+ }
+ (void) close(fd);
+
+
+ rn->rn_config = config;
+ if (config != NULL) {
+ assert(rn->rn_nozpool == B_FALSE);
+ }
+}
+
+/*
+ * Given a file descriptor, clear (zero) the label information. This function
+ * is currently only used in the appliance stack as part of the ZFS sysevent
+ * module.
+ */
+int
+zpool_clear_label(int fd)
+{
+ struct stat64 statbuf;
+ int l;
+ vdev_label_t *label;
+ uint64_t size;
+
+ if (fstat64(fd, &statbuf) == -1)
+ return (0);
+ size = P2ALIGN_TYPED(statbuf.st_size, sizeof (vdev_label_t), uint64_t);
+
+ if ((label = calloc(sizeof (vdev_label_t), 1)) == NULL)
+ return (-1);
+
+ for (l = 0; l < VDEV_LABELS; l++) {
+ if (pwrite64(fd, label, sizeof (vdev_label_t),
+ label_offset(size, l)) != sizeof (vdev_label_t))
+ return (-1);
+ }
+
+ free(label);
+ return (0);
+}
+