// SPDX-License-Identifier: CDDL-1.0
/*
 * 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 https://opensource.org/licenses/CDDL-1.0.
 * 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.
 * Copyright (c) 2012, 2017 by Delphix. All rights reserved.
 * Copyright 2015 RackTop Systems.
 * Copyright 2016 Nexenta Systems, Inc.
 */

/*
 * Pool import support functions.
 *
 * To import a pool, we rely on reading the configuration information from the
 * ZFS label of each device.  If we successfully read the label, then we
 * organize the configuration information in the following hierarchy:
 *
 *	pool guid -> toplevel vdev guid -> label txg
 *
 * Duplicate entries matching this same tuple will be discarded.  Once we have
 * examined every device, we pick the best label txg config for each toplevel
 * vdev.  We then arrange these toplevel vdevs into a complete pool config, and
 * update any paths that have changed.  Finally, we attempt to import the pool
 * using our derived config, and record the results.
 */

#include <sys/types.h>
#include <sys/disk.h>
#include <sys/ioctl.h>
#include <sys/stat.h>
#include <sys/sysctl.h>

#include <aio.h>
#include <ctype.h>
#include <dirent.h>
#include <errno.h>
#include <libintl.h>
#include <libgen.h>
#include <stddef.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>

#include <sys/efi_partition.h>
#include <thread_pool.h>
#include <libgeom.h>

#include <sys/vdev_impl.h>

#include <libzutil.h>

#include "zutil_import.h"

/*
 * Update a leaf vdev's persistent device strings
 *
 * - only applies for a dedicated leaf vdev (aka whole disk)
 * - updated during pool create|add|attach|import
 * - used for matching device matching during auto-{online,expand,replace}
 * - stored in a leaf disk config label (i.e. alongside 'path' NVP)
 * - these strings are currently not used in kernel (i.e. for vdev_disk_open)
 *
 * On FreeBSD we currently just strip devid and phys_path to avoid confusion.
 */
void
update_vdev_config_dev_strs(nvlist_t *nv)
{
	(void) nvlist_remove_all(nv, ZPOOL_CONFIG_DEVID);
	(void) nvlist_remove_all(nv, ZPOOL_CONFIG_PHYS_PATH);
}

/*
 * Do not even look at these devices.
 */
static const char * const excluded_devs[] = {
	"nfslock",
	"sequencer",
	"zfs",
};
#define	EXCLUDED_DIR		"/dev/"
#define	EXCLUDED_DIR_LEN	5

void
zpool_open_func(void *arg)
{
	rdsk_node_t *rn = arg;
	struct stat64 statbuf;
	nvlist_t *config;
	size_t i;
	int num_labels;
	int fd;
	off_t mediasize = 0;

	/*
	 * Do not even look at excluded devices.
	 */
	if (strncmp(rn->rn_name, EXCLUDED_DIR, EXCLUDED_DIR_LEN) == 0) {
		char *name = rn->rn_name + EXCLUDED_DIR_LEN;
		for (i = 0; i < nitems(excluded_devs); ++i) {
			const char *excluded_name = excluded_devs[i];
			size_t len = strlen(excluded_name);
			if (strncmp(name, excluded_name, len) == 0) {
				return;
			}
		}
	}

	/*
	 * O_NONBLOCK so we don't hang trying to open things like serial ports.
	 */
	if ((fd = open(rn->rn_name, O_RDONLY|O_NONBLOCK|O_CLOEXEC)) < 0)
		return;

	/*
	 * Ignore failed stats.
	 */
	if (fstat64(fd, &statbuf) != 0)
		goto out;
	/*
	 * We only want regular files, character devs and block devs.
	 */
	if (S_ISREG(statbuf.st_mode)) {
		/* Check if this file is too small to hold a zpool. */
		if (statbuf.st_size < SPA_MINDEVSIZE) {
			goto out;
		}
	} else if (S_ISCHR(statbuf.st_mode) || S_ISBLK(statbuf.st_mode)) {
		/* Check if this device is too small to hold a zpool. */
		if (ioctl(fd, DIOCGMEDIASIZE, &mediasize) != 0 ||
		    mediasize < SPA_MINDEVSIZE) {
			goto out;
		}
	} else {
		goto out;
	}

	if (zpool_read_label(fd, &config, &num_labels) != 0)
		goto out;
	if (num_labels == 0) {
		nvlist_free(config);
		goto out;
	}

	rn->rn_config = config;
	rn->rn_num_labels = num_labels;

	/* TODO: Reuse labelpaths logic from Linux? */
out:
	(void) close(fd);
}

static const char * const
zpool_default_import_path[] = {
	"/dev"
};

const char * const *
zpool_default_search_paths(size_t *count)
{
	*count = nitems(zpool_default_import_path);
	return (zpool_default_import_path);
}

int
zpool_find_import_blkid(libpc_handle_t *hdl, pthread_mutex_t *lock,
    avl_tree_t **slice_cache)
{
	const char *oid = "vfs.zfs.vol.recursive";
	char *end, path[MAXPATHLEN];
	rdsk_node_t *slice;
	struct gmesh mesh;
	struct gclass *mp;
	struct ggeom *gp;
	struct gprovider *pp;
	avl_index_t where;
	int error, value;
	size_t pathleft, size = sizeof (value);
	boolean_t skip_zvols = B_FALSE;

	end = stpcpy(path, "/dev/");
	pathleft = &path[sizeof (path)] - end;

	error = geom_gettree(&mesh);
	if (error != 0)
		return (error);

	if (sysctlbyname(oid, &value, &size, NULL, 0) == 0 && value == 0)
		skip_zvols = B_TRUE;

	*slice_cache = zutil_alloc(hdl, sizeof (avl_tree_t));
	avl_create(*slice_cache, slice_cache_compare, sizeof (rdsk_node_t),
	    offsetof(rdsk_node_t, rn_node));

	LIST_FOREACH(mp, &mesh.lg_class, lg_class) {
		if (skip_zvols && strcmp(mp->lg_name, "ZFS::ZVOL") == 0)
			continue;
		LIST_FOREACH(gp, &mp->lg_geom, lg_geom) {
			LIST_FOREACH(pp, &gp->lg_provider, lg_provider) {
				strlcpy(end, pp->lg_name, pathleft);
				slice = zutil_alloc(hdl, sizeof (rdsk_node_t));
				slice->rn_name = zutil_strdup(hdl, path);
				slice->rn_vdev_guid = 0;
				slice->rn_lock = lock;
				slice->rn_avl = *slice_cache;
				slice->rn_hdl = hdl;
				slice->rn_labelpaths = B_FALSE;
				slice->rn_order = IMPORT_ORDER_DEFAULT;

				pthread_mutex_lock(lock);
				if (avl_find(*slice_cache, slice, &where)) {
					free(slice->rn_name);
					free(slice);
				} else {
					avl_insert(*slice_cache, slice, where);
				}
				pthread_mutex_unlock(lock);
			}
		}
	}

	geom_deletetree(&mesh);

	return (0);
}

int
zfs_dev_flush(int fd)
{
	(void) fd;
	return (0);
}

void
update_vdev_config_dev_sysfs_path(nvlist_t *nv, const char *path,
    const char *key)
{
	(void) nv;
	(void) path;
	(void) key;
}

void
update_vdevs_config_dev_sysfs_path(nvlist_t *config)
{
	(void) config;
}

int
zpool_disk_wait(const char *path)
{

	(void) path;
	return (ENOTSUP);
}
