/*****************************************************************************\
 *  slurmdb_helpers.c - data parsing slurmdb helpers
 *****************************************************************************
 *  Copyright (C) SchedMD LLC.
 *
 *  This file is part of Slurm, a resource management program.
 *  For details, see <https://slurm.schedmd.com/>.
 *  Please also read the included file: DISCLAIMER.
 *
 *  Slurm is free software; you can redistribute it and/or modify it under
 *  the terms of the GNU General Public License as published by the Free
 *  Software Foundation; either version 2 of the License, or (at your option)
 *  any later version.
 *
 *  In addition, as a special exception, the copyright holders give permission
 *  to link the code of portions of this program with the OpenSSL library under
 *  certain conditions as described in each individual source file, and
 *  distribute linked combinations including the two. You must obey the GNU
 *  General Public License in all respects for all of the code used other than
 *  OpenSSL. If you modify file(s) with this exception, you may extend this
 *  exception to your version of the file(s), but you are not obligated to do
 *  so. If you do not wish to do so, delete this exception statement from your
 *  version.  If you delete this exception statement from all source files in
 *  the program, then also delete it here.
 *
 *  Slurm is distributed in the hope that it will be useful, but WITHOUT ANY
 *  WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
 *  FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
 *  details.
 *
 *  You should have received a copy of the GNU General Public License along
 *  with Slurm; if not, write to the Free Software Foundation, Inc.,
 *  51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA.
\*****************************************************************************/

#include "slurm/slurmdb.h"

#include "src/common/data.h"
#include "src/common/list.h"
#include "src/common/log.h"
#include "src/common/net.h"
#include "src/common/read_config.h"
#include "src/common/slurm_protocol_api.h"
#include "src/common/slurmdbd_defs.h"
#include "src/common/uid.h"
#include "src/common/xassert.h"
#include "src/common/xmalloc.h"
#include "src/common/xstring.h"

#include "alloc.h"
#include "events.h"
#include "parsing.h"
#include "slurmdb_helpers.h"

typedef struct {
	char *pos;
	char *str;
} list_to_str_args_t;

static int _slurmdb_query_failed(parse_op_t op, const parser_t *const parser,
				 args_t *args, int rc, const char *source,
				 const char *what, const char *func_name);

extern int db_query_list_funcname(parse_op_t op, data_parser_type_t type,
				  args_t *args, list_t **list,
				  db_list_query_func_t func, void *cond,
				  const char *func_name,
				  const char *func_caller_name)
{
	int rc;
	list_t *l;

	xassert(!*list);

	if (!args->db_conn)
		return ESLURM_DB_CONNECTION;

	errno = 0;
	l = func(args->db_conn, cond);

	if (errno) {
		FREE_NULL_LIST(l);
		rc = on_error(op, type, args, errno, func_name,
			      func_caller_name, "Slurmdb query failed");
	} else if (!l) {
		rc = on_error(op, type, args, ESLURM_REST_INVALID_QUERY,
			      func_name, func_caller_name,
			      "Slurmdbd query unexpectedly failed without a result");
	} else if (!list_count(l)) {
		FREE_NULL_LIST(l);

		rc = on_error(op, type, args, ESLURM_REST_EMPTY_RESULT,
			      func_name, func_caller_name,
			      "Slurmdbd query returned with empty list");
	} else {
		rc = SLURM_SUCCESS;
	}

	if (!rc)
		*list = l;

	return rc;
}

extern int resolve_qos(parse_op_t op, const parser_t *const parser,
		       slurmdb_qos_rec_t **qos_ptr, data_t *src, args_t *args,
		       data_t *parent_path, const char *caller,
		       bool ignore_failure)
{
	slurmdb_qos_rec_t *qos = NULL;
	char *path = NULL;
	int rc = SLURM_SUCCESS;

	xassert(args->magic == MAGIC_ARGS);
	xassert(data_get_type(src));
	xassert(is_fast_mode(args) || data_get_type(parent_path));
	xassert(!*qos_ptr);

	/* find qos by name from global list */
	if (!args->qos_list) {
		rc = ESLURM_REST_EMPTY_RESULT;
		if (!ignore_failure)
			on_error(op, parser->type, args, rc,
				 set_source_path(&path, args, parent_path),
				 caller,
				 "Unable to resolve QOS when there are no QOS");
		goto done;
	}

	if (data_get_type(src) == DATA_TYPE_NULL) {
		/* nothing to resolve */
		return SLURM_SUCCESS;
	} else if (data_get_type(src) == DATA_TYPE_DICT) {
		/* user may have provided entire QOS record */
		const parser_t *const qos_parser =
			find_parser_by_type(DATA_PARSER_QOS);
		slurmdb_qos_rec_t *pqos = alloc_parser_obj(qos_parser);

		xassert(xsize(pqos) == sizeof(*pqos));

		if ((rc = parse(pqos, sizeof(*pqos), qos_parser, src, args,
				parent_path))) {
			if (!ignore_failure)
				on_error(op, parser->type, args, rc,
					 set_source_path(&path, args,
							 parent_path), caller,
					 "Parsing dictionary into QOS failed");
			slurmdb_destroy_qos_rec(pqos);
			goto done;
		}

		xassert(!qos);
		if (pqos->id > 0) {
			if (!(qos = list_find_first(args->qos_list,
						    slurmdb_find_qos_in_list,
						    &pqos->id))) {
				rc = ESLURM_REST_EMPTY_RESULT;
				if (!ignore_failure)
					on_error(op, parser->type, args, rc,
						 __func__,
						 set_source_path(&path, args,
								 parent_path),
						 "Unable to find QOS by given ID#%d",
						 pqos->id);
			}
		} else if (pqos->name) {
			if (!(qos = list_find_first(
				      args->qos_list,
				      slurmdb_find_qos_in_list_by_name,
				      pqos->name))) {
				rc = ESLURM_REST_EMPTY_RESULT;
				if (!ignore_failure)
					on_error(op, parser->type, args, rc,
						 set_source_path(&path, args,
								 parent_path),
						 __func__,
						 "Unable to find QOS by given name: %s",
						 pqos->name);
			}
		} else {
			rc = ESLURM_REST_FAIL_PARSING;
			if (!ignore_failure)
				on_error(op, parser->type, args,
					 ESLURM_REST_FAIL_PARSING,
					 set_source_path(&path, args,
							 parent_path), caller,
					 "Unable to find QOS without ID# or name provided");
		}

		slurmdb_destroy_qos_rec(pqos);
		goto done;
	}

	/* convert to best guessed type */
	(void) data_convert_type(src, DATA_TYPE_NONE);

	if (data_get_type(src) == DATA_TYPE_INT_64) {
		uint64_t qos_id_full = data_get_int(src);
		uint32_t qos_id = qos_id_full;

		if (qos_id_full > INT32_MAX) {
			rc = ESLURM_INVALID_QOS;
			if (!ignore_failure)
				on_error(op, parser->type, args, rc,
					 set_source_path(&path, args,
							 parent_path), caller,
					 "QOS id#%"PRIu64" too large",
					 qos_id_full);
			goto done;
		}

		qos = list_find_first(args->qos_list, slurmdb_find_qos_in_list,
				      &qos_id);
	} else if (data_convert_type(src, DATA_TYPE_STRING) ==
		   DATA_TYPE_STRING) {
		const char *qos_name = data_get_string(src);

		if (!qos_name || !qos_name[0])
			return SLURM_SUCCESS;

		qos = list_find_first(args->qos_list,
				      slurmdb_find_qos_in_list_by_name,
				      (void *) qos_name);
	} else {
		rc = ESLURM_REST_FAIL_PARSING;
		if (ignore_failure)
			on_error(op, parser->type, args, rc,
				 set_source_path(&path, args, parent_path),
				 caller,
				 "QOS resolution failed with unexpected QOS name/id formatted as data type:%s",
				 data_get_type_string(src));
		goto done;
	}

done:
	xfree(path);

	if (rc)
		return rc;

	if (!qos)
		return ESLURM_REST_EMPTY_RESULT;

	*qos_ptr = qos;
	return SLURM_SUCCESS;
}

extern int resolve_assoc(parse_op_t op, const parser_t *const parser,
			 slurmdb_assoc_rec_t **assoc_ptr,
			 slurmdb_assoc_rec_t *key, args_t *args,
			 data_t *parent_path, const char *caller,
			 bool ignore_failure)
{
	slurmdb_assoc_cond_t cond = {
		.flags = ASSOC_COND_FLAG_WITH_DELETED,
	};
	slurmdb_qos_rec_t *qos = NULL;
	list_t *assoc_list = NULL;
	int rc;

	assoc_list = list_create(slurmdb_destroy_assoc_rec);

	cond.acct_list = list_create(xfree_ptr);
	if (key->acct && key->acct[0])
		list_append(cond.acct_list, xstrdup(key->acct));
	else
		list_append(cond.acct_list, xstrdup(""));

	cond.cluster_list = list_create(xfree_ptr);
	if (key->cluster && key->cluster[0])
		list_append(cond.acct_list, xstrdup(key->cluster));
	else
		list_append(cond.acct_list, xstrdup(slurm_conf.cluster_name));

	if ((key->id > 0) && (key->id != NO_VAL)) {
		cond.id_list = list_create(xfree_ptr);
		list_append(cond.id_list, xstrdup_printf("%u", key->id));
	}

	cond.partition_list = list_create(xfree_ptr);
	if (key->partition && key->partition[0])
		list_append(cond.partition_list, xstrdup(key->partition));
	else
		list_append(cond.partition_list, xstrdup(""));

	cond.qos_list = list_create(xfree_ptr);
	if (key->qos_list && !list_is_empty(key->qos_list)) {
		list_append(cond.qos_list, xstrdup(list_peek(key->qos_list)));
	} else if ((key->def_qos_id > 0) && (key->def_qos_id != NO_VAL) &&
		   args->qos_list &&
		   (qos = list_find_first(args->qos_list,
					  slurmdb_find_qos_in_list,
					  &key->def_qos_id))) {
		list_append(cond.partition_list, xstrdup(qos->name));
	} else {
		list_append(cond.partition_list, xstrdup(""));
	}

	cond.user_list = list_create(xfree_ptr);
	if (key->user && key->user[0])
		list_append(cond.partition_list, xstrdup(key->user));
	else
		list_append(cond.partition_list, xstrdup(""));

	if (!(rc = _db_query_list(QUERYING, parser->type, args, &assoc_list,
				  slurmdb_associations_get, &cond)) &&
	    !list_is_empty(assoc_list))
		*assoc_ptr = list_pop(assoc_list);

	FREE_NULL_LIST(assoc_list);

	if (rc)
		return _slurmdb_query_failed(op, parser, args, rc,
					     "slurmdb_associations_get",
					     "Associations", caller);
	return rc;
}

static data_for_each_cmd_t _concat_data_to_str(data_t *data, void *arg)
{
	list_to_str_args_t *args = arg;
	char *flag_str = NULL;

	if (args->str)
		xstrcatat(args->str, &args->pos, ",");

	if (data_get_string_converted(data, &flag_str))
		error("%s: Could not convert data to string", __func__);

	xstrcatat(args->str, &args->pos, flag_str);
	xfree(flag_str);
	return DATA_FOR_EACH_CONT;
}

static char *_data_list_to_str(data_t *data)
{
	list_to_str_args_t args = { 0 };

	xassert(data_get_type(data) == DATA_TYPE_LIST);

	(void) data_list_for_each(data, _concat_data_to_str, &args);
	return args.str;
}

static int _prereqs_placeholder(const parser_t *const parser,
				args_t *args)
{
	if (!args->tres_list && (parser->needs & NEED_TRES))
		args->tres_list = list_create(NULL);
	if (!args->qos_list && (parser->needs & NEED_QOS))
		args->qos_list = list_create(NULL);

	return SLURM_SUCCESS;
}

static char *_needs_to_string(need_t needs, args_t *args)
{
	char *str = NULL;
	data_t *d = data_new();

	DUMP(NEED_PREREQS_FLAGS, needs, d, args);
	str = _data_list_to_str(d);
	FREE_NULL_DATA(d);

	return str;
}

static int _slurmdb_query_failed(parse_op_t op, const parser_t *const parser,
				 args_t *args, int rc, const char *source,
				 const char *what, const char *func_name)
{
	char *needs = _needs_to_string(parser->needs, args);

	on_warn(op, parser->type, args, source, __func__,
		"%s: Unable to query %s from Slurm accounting storage. Could not query the following [%s]: %s",
		func_name, what, needs, slurm_strerror(rc));

	xfree(needs);

	return _prereqs_placeholder(parser, args);
}

extern int load_prereqs_funcname(parse_op_t op, const parser_t *const parser,
				 args_t *args, const char *func_name)
{
	int rc = SLURM_SUCCESS;

	check_parser(parser);
	xassert(args->magic == MAGIC_ARGS);
	xassert((op == PARSING) || (op == DUMPING) || (op == QUERYING));

	if (parser->needs && !slurm_conf.accounting_storage_type) {
		char *needs = _needs_to_string(parser->needs, args);

		on_warn(op, parser->type, args, NULL, __func__,
			"Slurm accounting storage is disabled. Could not query the following: [%s].",
			needs);

		xfree(needs);

		return _prereqs_placeholder(parser, args);
	}

	if (parser->needs && !args->db_conn) {
		errno = SLURM_ERROR;
		if (!(args->db_conn = slurmdb_connection_get(NULL)))
			return _slurmdb_query_failed(op, parser, args, errno,
						     "slurmdb_connection_get",
						     "connection", func_name);
		args->close_db_conn = true;
	}

	if ((parser->needs & NEED_TRES) && !args->tres_list) {
		slurmdb_tres_cond_t cond = {
			.with_deleted = 1,
		};

		if ((rc = _db_query_list(QUERYING, parser->type, args,
					 &args->tres_list, slurmdb_tres_get,
					 &cond))) {
			return _slurmdb_query_failed(op, parser, args, errno,
						     "slurmdb_tres_get", "TRES",
						     func_name);
		}

		log_flag(DATA, "loaded %u TRES for parser 0x%" PRIxPTR,
			 list_count(args->tres_list), (uintptr_t) args);
	}

	if ((parser->needs & NEED_QOS) && !args->qos_list) {
		slurmdb_qos_cond_t cond = {
			.flags = QOS_COND_FLAG_WITH_DELETED,
		};

		if ((rc = _db_query_list(QUERYING, parser->type, args,
					 &args->qos_list, slurmdb_qos_get,
					 &cond))) {
			return _slurmdb_query_failed(op, parser, args, errno,
						     "slurmdb_qos_get", "QOS",
						     func_name);
		}

		log_flag(DATA, "loaded %u QOS for parser 0x%" PRIxPTR,
			 list_count(args->qos_list), (uintptr_t) args);
	}

	return SLURM_SUCCESS;
}

/* checks for mis-matches and rejects on the spot */
#define _match(field, x, y)                          \
	do {                                         \
		/* both null */                      \
		if (!x->field && !y->field)          \
			continue;                    \
		/* only 1 is null */                 \
		if (!x->field != !y->field)          \
			return 0;                    \
		if (xstrcasecmp(x->field, y->field)) \
			return 0;                    \
	} while (0)

extern int compare_assoc(const slurmdb_assoc_rec_t *x,
			 const slurmdb_assoc_rec_t *y)
{
	if ((y->id > 0) && (y->id == x->id)) {
		/*
		 * Always match cluster because multiple clusters may have
		 * different associations with the same id.
		 */
		_match(cluster, x, y);
		return 1;
	}

	_match(acct, x, y);
	_match(cluster, x, y);
	_match(partition, x, y);
	_match(user, x, y);

	return 1;
}

#undef _match

extern int fuzzy_match_tres(slurmdb_tres_rec_t *tres,
			    slurmdb_tres_rec_t *needle)
{
	debug5("Comparing database tres(name:%s, type:%s, id:%u) with requested(name:%s, type:%s, id:%u).",
	       tres->name, tres->type, tres->id, needle->name, needle->type,
	       needle->id);

	if ((needle->id > 0) &&
	    ((needle->id == tres->id) &&
	     (!needle->type || !xstrcasecmp(needle->type, tres->type)) &&
	     (!needle->name || !xstrcasecmp(needle->name, tres->name))))
		return 1;
	if ((!needle->name || !needle->name[0]) &&
	    !xstrcasecmp(needle->type, tres->type))
		return 1;
	else if (!xstrcasecmp(needle->name, tres->name) &&
		 !xstrcasecmp(needle->type, tres->type))
		return 1;
	else
		return 0;
}
