// SPDX-License-Identifier: LGPL-2.1+
/*
 * Copyright (c) 2015, Cray Inc, all rights reserved.
 *
 * Copyright (c) 2016, 2017, Intel Corporation.
 */
/*
 * This file is part of Lustre, http://www.lustre.org/
 *
 * Misc LGPL-licenced utility functions for liblustreapi.
 *
 * Author: Frank Zago <fzago@cray.com>
 */

#include <errno.h>
#include <fcntl.h>
#include <stdbool.h>
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/ioctl.h>
#include <string.h>
#include <ctype.h>
#include <unistd.h>
#include <sys/time.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/syscall.h>
#include <libgen.h> /* for dirname() */
#include <lustre/lustreapi.h>
#include <linux/lustre/lustre_ver.h>	/* only until LUSTRE_VERSION_CODE is gone */
#include "lustreapi_internal.h"

/*
 * Indicate whether the liblustreapi_init() constructor below has run or not.
 *
 * This can be used by external programs to ensure that the initialization
 * mechanism has actually worked.
 */
bool liblustreapi_initialized;

/**
 * liblustreapi_init() - Initialize the library once at startup.
 *
 * Initializes the random number generator (random()). Get
 * data from different places in case one of them fails. This
 * is enough to get reasonably random numbers, but is not
 * strong enough to be used for cryptography.
 */
static __attribute__ ((constructor)) void liblustreapi_init(void)
{
	unsigned int	seed;
	struct timeval	tv;
	int		fd;

	seed = syscall(SYS_gettid);

	if (gettimeofday(&tv, NULL) == 0) {
		seed ^= tv.tv_sec;
		seed ^= tv.tv_usec;
	}

	fd = open("/dev/urandom", O_RDONLY | O_NOFOLLOW);
	if (fd >= 0) {
		unsigned int rnumber;
		ssize_t ret;

		ret = read(fd, &rnumber, sizeof(rnumber));
		seed ^= rnumber ^ ret;
		close(fd);
	}

	srandom(seed);
	liblustreapi_initialized = true;
}

/**
 * llapi_get_version_string() - Return release version for the Lustre modules
 * @version: buffer to store build version string [in, out]
 * @version_size: size of \a version
 *
 * Return the release version for the Lustre modules, e.g. 2.6.92.
 * The "version" file in /proc currently returns only the line:
 * lustre: 2.8.52
 *
 * but in the past it also returned more lines that should be ignored:
 * kernel: patchless_client
 * build: v2_6_92_0-gadb3ee4-2.6.32-431.29.2.el6_lustre.g36cd22b.x86_64
 *
 * Return:
 * * %0 on success
 * * %-1 on failure, errno is set
 */
int llapi_get_version_string(char *version, unsigned int version_size)
{
	char buffer[4096];
	char *ptr;
	int rc;

	if (version == NULL || version_size == 0) {
		errno = EINVAL;
		return -1;
	}

	rc = get_lustre_param_value(NULL, NULL, FILTER_BY_NONE, "version",
				    buffer, sizeof(buffer));
	if (rc < 0) {
		errno = -rc;
		return -1;
	}

	ptr = strstr(buffer, "lustre:");
	if (ptr) {
		ptr += strlen("lustre:");
		while (*ptr == ' ' || *ptr == '\t')
			ptr++;
	} else {
		ptr = buffer;
	}
	llapi_chomp_string(ptr);

	if (ptr[0] == '\0') {
		errno = ENODATA;
		return -1;
	}

	if (snprintf(version, version_size, "%s", ptr) >= version_size) {
		errno = EOVERFLOW;
		return -1;
	}
	return 0;
}

#if LUSTRE_VERSION_CODE < OBD_OCD_VERSION(3, 4, 53, 0)
/**
 * llapi_get_version() - Return the build version of the Lustre code.
 * @buffer: temporary buffer to hold version string
 * @buffer_size: length of the @buffer
 * @version: pointer to the start of build version string [out]
 *
 * The **version argument is pointless, so llapi_get_version_string() is
 * better to use in the future, but give users a few versions to fix it.
 *
 * Return:
 * * %0 on success
 * * %negative on failure
 */
int llapi_get_version(char *buffer, int buffer_size, char **version)
{
	static bool printed;
	int rc;

	if (!printed) {
		fprintf(stderr,
			"%s deprecated, use llapi_get_version_string()\n",
			__func__);
		printed = true;
	}

	rc = llapi_get_version_string(buffer, buffer_size);
	/* keep old return style for this legacy function */
	if (rc == -1)
		rc = -errno;
	else
		*version = buffer;

	return rc;
}
#endif /* LUSTRE_VERSION_CODE < OBD_OCD_VERSION(3, 4, 53, 0) */

/*
 * fsname must be specified
 * if poolname is NULL, search tgtname in fsname
 * if poolname is not NULL:
 *  if poolname not found returns errno < 0
 *  if tgtname is NULL, returns 1 if pool is not empty and 0 if pool empty
 *  if tgtname is not NULL, returns 1 if target is in pool and 0 if not
 */
int llapi_search_tgt(const char *fsname, const char *poolname,
		     const char *tgtname, bool is_mdt)
{
	char buffer[PATH_MAX];
	size_t len = 0;
	glob_t param;
	FILE *fd;
	int rc;

	if (fsname && fsname[0] == '\0')
		fsname = NULL;
	if (!fsname) {
		rc = -EINVAL;
		goto out;
	}

	if (poolname && poolname[0] == '\0')
		poolname = NULL;
	if (tgtname) {
		if (tgtname[0] == '\0')
			tgtname = NULL;
		else
			len = strlen(tgtname);
	}

	/* You need one or the other to have something in it */
	if (!poolname && !tgtname) {
		rc = -EINVAL;
		goto out;
	}

	if (poolname) {
		rc = poolpath(&param, fsname, NULL);
		if (!rc) {
			snprintf(buffer, sizeof(buffer) - 1, "%s/%s",
				 param.gl_pathv[0], poolname);
			buffer[sizeof(buffer) - 1] = '\0';
		}
	} else {
		rc = get_lustre_param_path(is_mdt ? "lmv" : "lov", fsname,
					   FILTER_BY_FS_NAME,
					   "target_obd", &param);
		if (!rc) {
			strncpy(buffer, param.gl_pathv[0],
				sizeof(buffer) - 1);
			buffer[sizeof(buffer) - 1] = '\0';
		}
	}
	cfs_free_param_data(&param);
	if (rc)
		goto out;

	fd = fopen(buffer, "r");
	if (!fd) {
		rc = -errno;
		goto out;
	}

	while (fgets(buffer, sizeof(buffer), fd)) {
		if (!poolname) {
			char *ptr;
			/* Search for an tgtname in the list of all targets
			 * Line format is IDX: fsname-OST/MDTxxxx_UUID STATUS */
			ptr = strchr(buffer, ' ');
			if (ptr && strncmp(ptr + 1, tgtname, len) == 0) {
				rc = 1;
				goto out_close;
			}
		} else {
			/* Search for an tgtname in a pool,
			 * (or an existing non-empty pool if no tgtname) */
			if (!tgtname || strncmp(buffer, tgtname, len) == 0) {
				rc = 1;
				goto out_close;
			}
		}
	}
out_close:
	fclose(fd);
out:
	if (rc < 0)
		errno = -rc;
	return rc;
}

int llapi_search_mdt(const char *fsname, const char *poolname,
		     const char *mdtname)
{
	return llapi_search_tgt(fsname, poolname, mdtname, true);
}

int llapi_search_ost(const char *fsname, const char *poolname,
		     const char *ostname)
{
	return llapi_search_tgt(fsname, poolname, ostname, false);
}

/**
 * llapi_root_path_open() - Return the open fd for a given device/path provided
 * @device: buffer holding device or path string
 * @rootfd: file descriptor after successful opening of @device(path) [out]
 *
 * Return:
 * * %0 on success
 * * %negative on failure
 */
int llapi_root_path_open(const char *device, int *rootfd)
{
	int tmp_fd, rc;

	if (*device == '/')
		rc = get_root_path(WANT_FD, NULL, &tmp_fd,
				   (char *)device, -1, NULL, NULL);
	else
		rc = get_root_path(WANT_FD, (char *)device, &tmp_fd,
				   NULL, -1, NULL, NULL);

	if (!rc)
		*rootfd = dup(tmp_fd);

	return rc;
}

/**
 * llapi_rmfid_at() - Call IOCTL to remove file by fid.
 * @fd: valid descriptor of device/path
 * @fa: fid_array struct holding fids
 *
 * Call IOCTL to remove file by fid. The @fd must be valid and @fa(fid_array)
 * struct must allready be populated.
 *
 * Return:
 * * %0 on success
 * * %-errno on failure
 */
int llapi_rmfid_at(int fd, struct fid_array *fa)
{
	return ioctl(fd, LL_IOC_RMFID, fa) ? -errno : 0;
}

int llapi_rmfid(const char *path, struct fid_array *fa)
{
	int rootfd, rc;

	rc = llapi_root_path_open(path, &rootfd);
	if (rc < 0) {
		fprintf(stderr,
			"lfs rmfid: error opening device/fsname '%s': %s\n",
			path, strerror(-rc));
		return -rc;
	}

	rc = llapi_rmfid_at(rootfd, fa);
	close(rootfd);
	if (rc < 0) {
		fprintf(stderr, "lfs rmfid: cannot remove FIDs: %s\n",
			strerror(-rc));
		return rc;
	}

	return rc ? -errno : 0;
}

int llapi_direntry_remove(char *dname)
{
#ifdef LL_IOC_REMOVE_ENTRY
	char *dirpath = NULL;
	char *namepath = NULL;
	char *dir;
	char *filename;
	int fd = -1;
	int rc = 0;

	dirpath = strdup(dname);
	if (!dirpath)
		return -ENOMEM;

	namepath = strdup(dname);
	if (!namepath) {
		rc = -ENOMEM;
		goto out_dirpath;
	}

	filename = basename(namepath);

	dir = dirname(dirpath);

	fd = open(dir, O_DIRECTORY | O_RDONLY);
	if (fd < 0) {
		rc = -errno;
		llapi_error(LLAPI_MSG_ERROR, rc, "unable to open '%s'",
			    filename);
		goto out;
	}

	if (ioctl(fd, LL_IOC_REMOVE_ENTRY, filename))
		llapi_error(LLAPI_MSG_ERROR, errno,
			    "error on ioctl %#lx for '%s' (%d)",
			    (long)LL_IOC_LMV_SETSTRIPE, filename, fd);
	close(fd);
out:
	free(namepath);
out_dirpath:
	free(dirpath);
	return rc;
#else
	return -EOPNOTSUPP;
#endif
}

int llapi_unlink_foreign(char *name)
{
	int fd = -1;
	int rc = 0;

	fd = open(name, O_DIRECTORY | O_RDONLY | O_NOFOLLOW);
	if (fd < 0 && errno != ENOTDIR) {
		rc = -errno;
		llapi_error(LLAPI_MSG_ERROR, rc, "unable to open '%s'", name);
		goto out;
	} else if (errno == ENOTDIR) {
		fd = open(name, O_RDONLY | O_NOFOLLOW);
		if (fd < 0) {
			rc = -errno;
			llapi_error(LLAPI_MSG_ERROR, rc, "unable to open '%s'",
				    name);
			goto out;
		}
	}

	/* allow foreign symlink file/dir to be unlinked */
	if (ioctl(fd, LL_IOC_UNLOCK_FOREIGN)) {
		llapi_error(LLAPI_MSG_ERROR, errno,
			    "error on ioctl %#lx for '%s' (%d)",
			    (long)LL_IOC_UNLOCK_FOREIGN, name, fd);
		rc = -errno;
	}

	/* XXX do not set AT_REMOVEDIR in flags even for a dir, as due to the
	 * hack for foreign symlink it will fail the directory check in
	 * Kernel's syscall code and return ENOTDIR, so treat all as files
	 */
	rc = unlinkat(AT_FDCWD, name, 0);
	if (rc == -1 && errno == EISDIR)
		rc = unlinkat(AT_FDCWD, name, AT_REMOVEDIR);

	if (rc == -1) {
		llapi_error(LLAPI_MSG_ERROR, errno,
			    "error on unlinkat for '%s' (%d)", name, fd);
		rc = -errno;
	}

out:
	if (fd != -1)
		close(fd);
	return rc;
}

int llapi_get_fsname_instance(const char *path, char *fsname, size_t fsname_len,
			      char *instance, size_t instance_len)
{
	struct obd_uuid uuid_buf;
	char *uuid = uuid_buf.uuid;
	char *ptr;
	int rc;

	memset(&uuid_buf, 0, sizeof(uuid_buf));
	rc = llapi_file_get_lov_uuid(path, &uuid_buf);
	if (rc)
		return rc;

	/*
	 * We want to turn fs-foo-clilov-ffff88002738bc00 into 'fs-foo' and
	 * 'ffff88002738bc00' in a portable way that doesn't depend on what is
	 * after "-clilov-" as it may change to a UUID string in the future.
	 * Unfortunately, the "fsname" part may contain a dash, so we can't
	 * just skip to the first dash, and if the "instance" is a UUID in the
	 * future we can't necessarily go to the last dash either.
	 */
	ptr = strstr(uuid, "-clilov-");
	if (!ptr || (!fsname && !instance)) {
		rc = -EINVAL;
		goto out;
	}

	*ptr = '\0';
	ptr += strlen("-clilov-");
	if (instance) {
		snprintf(instance, instance_len, "%s", ptr);
		if (strlen(ptr) >= instance_len)
			rc = -ENAMETOOLONG;
	}

	if (fsname) {
		snprintf(fsname, fsname_len, "%s", uuid);
		if (strlen(uuid) >= fsname_len)
			rc = -ENAMETOOLONG;
	}

out:
	errno = -rc;
	return rc;
}

int llapi_getname(const char *path, char *name, size_t namelen)
{
	char fsname[16];
	char instance[40];
	int rc;

	rc = llapi_get_fsname_instance(path, fsname, sizeof(fsname),
				       instance, sizeof(instance));
	if (rc)
		return rc;

	snprintf(name, namelen, "%s-%s", fsname, instance);
	if (strlen(fsname) + 1 + strlen(instance) >= namelen) {
		rc = -ENAMETOOLONG;
		errno = -rc;
	}

	return rc;
}

int llapi_get_instance(const char *path, char *instance, size_t instance_len)
{
	return llapi_get_fsname_instance(path, NULL, 0, instance, instance_len);
}

int llapi_get_fsname(const char *path, char *fsname, size_t fsname_len)
{
	return llapi_get_fsname_instance(path, fsname, fsname_len, NULL, 0);
}

void llapi_bandwidth_throttle(struct timespec *now, struct timespec *start_time,
			      uint64_t bandwidth_bytes_sec,
			      uint64_t total_bytes_written)
{
	struct timespec diff;
	struct timespec delay = { 0, 0 };
	size_t write_target;
	long long excess;
	int rc;

	if (bandwidth_bytes_sec == 0)
		return;

	diff = timespec_sub(start_time, now);
	write_target = (bandwidth_bytes_sec * diff.tv_sec) +
		       (bandwidth_bytes_sec * diff.tv_nsec / NSEC_PER_SEC);

	excess = (long long)total_bytes_written - (long long)write_target;
	if (excess <= 0)
		return;

	delay.tv_sec = excess / bandwidth_bytes_sec;
	delay.tv_nsec = (excess % bandwidth_bytes_sec) * NSEC_PER_SEC /
			bandwidth_bytes_sec;

	do {
		rc = clock_nanosleep(CLOCK_MONOTONIC, 0, &delay, &delay);
	} while (rc < 0 && errno == EINTR);

	if (rc < 0)
		llapi_error(LLAPI_MSG_ERROR, rc,
			    "errors: delay for bandwidth control failed\n");
}

void llapi_stats_log(struct timespec *now, struct timespec *start_time,
		     struct timespec *last_print, int stats_interval_sec,
		     uint64_t read_bytes, uint64_t write_bytes,
		     uint64_t offset, uint64_t file_size_bytes)
{
	struct timespec diff_print;
	struct timespec diff;

	if (file_size_bytes == 0)
		return;

	diff_print = timespec_sub(last_print, now);
	if (diff_print.tv_sec < stats_interval_sec &&
	    offset != file_size_bytes)
		return;

	diff = timespec_sub(start_time, now);

	llapi_printf(LLAPI_MSG_NORMAL,
		     "- { seconds: %li, rmbps: %5.2g, wmbps: %5.2g, copied: %lu, total: %lu, pct: %lu%% }\n",
		     diff.tv_sec,
		     (double)read_bytes / (ONE_MB * diff.tv_sec +
					  ONE_MB * diff.tv_nsec / NSEC_PER_SEC),
		     (double)write_bytes / (ONE_MB * diff.tv_sec +
					  ONE_MB * diff.tv_nsec / NSEC_PER_SEC),
		     offset / ONE_MB, file_size_bytes / ONE_MB,
		     offset * 100 / file_size_bytes);
	*last_print = *now;
}

int llapi_convert_str2mask(const char *str, const char *(*bit2str)(int bit),
			   __u64 *oldmask, __u64 minmask, __u64 allmask,
			   __u64 defmask)
{
	const char *debugstr;
	__u64 newmask = *oldmask, found = 0;

	/* <str> must be a list of tokens separated by whitespace or comma,
	 * and optionally an operator ('+' or '-').  If an operator
	 * appears first in <str>, '*oldmask' is used as the starting point
	 * (relative), otherwise minmask is used (absolute).  An operator
	 * applies to all following tokens up to the next operator.
	 */
	while (*str != 0) {
		int i, len;
		char op = 0;

		while (isspace(*str) || *str == ',')
			str++;
		if (*str == 0)
			break;
		if (*str == '+' || *str == '-') {
			op = *str++;
			while (isspace(*str))
				str++;
			if (*str == 0)          /* trailing op */
				return -EINVAL;
		} else if (!found)
			newmask = minmask;


		/* find token length */
		for (len = 0; str[len] != 0 && !isspace(str[len]) &&
		     str[len] != '+' && str[len] != '-' && str[len] != ',';
		     len++) {
		}

		/* match token */
		found = 0;
		for (i = 0; i < 32; i++) {
			debugstr = bit2str(i);
			if (debugstr != NULL &&
			    strlen(debugstr) == len &&
			    strncasecmp(str, debugstr, len) == 0) {
				if (op == '-')
					newmask &= ~BIT(i);
				else
					newmask |= BIT(i);
				found = 1;
				break;
			}
		}
		if (!found && len == 3 &&
		    (strncasecmp(str, "ALL", len) == 0)) {
			if (op == '-')
				newmask = minmask;
			else
				newmask = allmask;
			found = 1;
		}
		if (!found && strcasecmp(str, "DEFAULT") == 0) {
			if (op == '-')
				newmask = (newmask & ~defmask) | minmask;
			else if (op == '+')
				newmask |= defmask;
			else
				newmask = defmask;
			found = 1;
		}
		if (!found) {
			llapi_error(LLAPI_MSG_ERROR, -EINVAL,
				    "unknown mask '%.*s'.\n"
				    "mask usage: [+|-]<all|type> ...\n",
				    len, str);
			return -EINVAL;
		}
		str += len;
	}

	*oldmask = newmask;
	return 0;
}

int llapi_convert_mask2str(char *str, int size, __u64 mask,
			   const char *(*bit2str)(int), char sep)
{
	const char *token;
	int len = 0;
	int i;

	if (mask == 0) {                        /* "0" */
		if (size > 0)
			str[0] = '0';
		len = 1;
	} else {                                /* space-separated tokens */
		for (i = 0; i < 64; i++) {
			if ((mask & BIT(i)) == 0)
				continue;

			token = bit2str(i);
			if (!token)             /* unused bit */
				continue;

			if (len > 0) {          /* separator? */
				if (len < size)
					str[len] = sep;
				len++;
			}

			while (*token != 0) {
				if (len < size)
					str[len] = *token;
				token++;
				len++;
			}
		}
	}

	/* terminate 'str' */
	if (len < size)
		str[len++] = '\n';
	if (len < size)
		str[len] = '\0';
	else if (size)
		str[size - 1] = '\0';

	return len;
}
