/*****************************************************************************\
 *  partition_mgr.c - manage the partition information of slurm
 *	Note: there is a global partition list (part_list) and
 *	time stamp (last_part_update)
 *****************************************************************************
 *  Copyright (C) 2002-2007 The Regents of the University of California.
 *  Copyright (C) 2008-2010 Lawrence Livermore National Security.
 *  Copyright (C) SchedMD LLC.
 *  Produced at Lawrence Livermore National Laboratory (cf, DISCLAIMER).
 *  Written by Morris Jette <jette@llnl.gov> et. al.
 *  CODE-OCEC-09-009. All rights reserved.
 *
 *  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 "config.h"

#include <ctype.h>
#include <errno.h>
#include <grp.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <unistd.h>

#include "src/common/assoc_mgr.h"
#include "src/common/fd.h"
#include "src/common/hostlist.h"
#include "src/common/list.h"
#include "src/common/pack.h"
#include "src/common/part_record.h"
#include "src/common/slurm_protocol_pack.h"
#include "src/common/slurm_resource_info.h"
#include "src/common/state_save.h"
#include "src/common/uid.h"
#include "src/common/xstring.h"

#include "src/interfaces/burst_buffer.h"
#include "src/interfaces/priority.h"
#include "src/interfaces/select.h"
#include "src/interfaces/topology.h"

#include "src/slurmctld/gang.h"
#include "src/slurmctld/groups.h"
#include "src/slurmctld/licenses.h"
#include "src/slurmctld/locks.h"
#include "src/slurmctld/power_save.h"
#include "src/slurmctld/proc_req.h"
#include "src/slurmctld/read_config.h"
#include "src/slurmctld/reservation.h"
#include "src/slurmctld/slurmctld.h"
#include "src/slurmctld/state_save.h"

/* No need to change we always pack SLURM_PROTOCOL_VERSION */
#define PART_STATE_VERSION        "PROTOCOL_VERSION"

typedef struct {
	buf_t *buffer;
	uint32_t parts_packed;
	bool privileged;
	uint16_t protocol_version;
	uint16_t show_flags;
	uid_t uid;
	part_record_t **visible_parts;
} _foreach_pack_part_info_t;

/* Global variables */
list_t *part_list = NULL;		/* partition list */
char *default_part_name = NULL;		/* name of default partition */
part_record_t *default_part_loc = NULL;	/* default partition location */
time_t last_part_update = (time_t) 0;	/* time of last update to partition records */
uint16_t part_max_priority = DEF_PART_MAX_PRIORITY;

static int    _dump_part_state(void *x, void *arg);
static void   _list_delete_part(void *part_entry);
static int    _match_part_ptr(void *part_ptr, void *key);
static void   _unlink_free_nodes(bitstr_t *old_bitmap, part_record_t *part_ptr);

static int _calc_part_tres(void *x, void *arg)
{
	int i, j;
	node_record_t *node_ptr;
	uint64_t *tres_cnt;
	part_record_t *part_ptr = (part_record_t *) x;

	xfree(part_ptr->tres_cnt);
	xfree(part_ptr->tres_fmt_str);
	part_ptr->tres_cnt = xcalloc(slurmctld_tres_cnt, sizeof(uint64_t));
	tres_cnt = part_ptr->tres_cnt;

	/* sum up nodes' tres in the partition. */
	for (i = 0; (node_ptr = next_node_bitmap(part_ptr->node_bitmap, &i));
	     i++) {
		for (j = 0; j < slurmctld_tres_cnt; j++)
			tres_cnt[j] += node_ptr->tres_cnt[j];
	}

	/* Just to be safe, lets do this after the node TRES ;) */
	tres_cnt[TRES_ARRAY_NODE] = part_ptr->total_nodes;

	/* grab the global tres and stick in partition for easy reference. */
	for(i = 0; i < slurmctld_tres_cnt; i++) {
		slurmdb_tres_rec_t *tres_rec = assoc_mgr_tres_array[i];

		if (!xstrcasecmp(tres_rec->type, "bb") ||
		    !xstrcasecmp(tres_rec->type, "license"))
			tres_cnt[i] = tres_rec->count;
	}

	/*
	 * Now figure out the total billing of the partition as the node_ptrs
	 * are configured with the max of all partitions they are in instead of
	 * what is configured on this partition.
	 */
	tres_cnt[TRES_ARRAY_BILLING] = assoc_mgr_tres_weighted(
		tres_cnt, part_ptr->billing_weights,
		slurm_conf.priority_flags, true);

	part_ptr->tres_fmt_str =
		assoc_mgr_make_tres_str_from_array(part_ptr->tres_cnt,
						   TRES_STR_CONVERT_UNITS,
						   true);
	if (part_ptr->qos_ptr) {
		part_ptr->qos_ptr->flags |= QOS_FLAG_PART_QOS;
		assoc_mgr_set_qos_tres_relative_cnt(part_ptr->qos_ptr,
						    part_ptr->tres_cnt);
	}

	return 0;
}

/*
 * Calculate and populate the number of tres' for all partitions.
 */
extern void set_partition_tres(bool assoc_mgr_locked)
{
	assoc_mgr_lock_t locks = {
		.qos = WRITE_LOCK,
		.tres = READ_LOCK,
	};

	xassert(verify_lock(PART_LOCK, WRITE_LOCK));
	xassert(verify_lock(NODE_LOCK, READ_LOCK));

	if (!assoc_mgr_locked)
		assoc_mgr_lock(&locks);
	else {
		xassert(verify_assoc_lock(QOS_LOCK, WRITE_LOCK));
		xassert(verify_assoc_lock(TRES_LOCK, READ_LOCK));
	}

	assoc_mgr_clear_qos_tres_relative_cnt(true);

	list_for_each(part_list, _calc_part_tres, NULL);

	assoc_mgr_set_unset_qos_tres_relative_cnt(true);

	if (!assoc_mgr_locked)
		assoc_mgr_unlock(&locks);
}

/*
 * build_part_bitmap - update the total_cpus, total_nodes, and node_bitmap
 *	for the specified partition, also reset the partition pointers in
 *	the node back to this partition.
 * IN part_ptr - pointer to the partition
 * RET 0 if no error, errno otherwise
 * global: node_record_table_ptr - pointer to global node table
 * NOTE: this does not report nodes defined in more than one partition. this
 *	is checked only upon reading the configuration file, not on an update
 */
extern int build_part_bitmap(part_record_t *part_ptr)
{
	int rc = SLURM_SUCCESS;
	char *this_node_name;
	bitstr_t *old_bitmap;
	node_record_t *node_ptr;
	hostlist_t *host_list, *missing_hostlist = NULL;
	int i;

	part_ptr->total_cpus = 0;
	part_ptr->total_nodes = 0;
	part_ptr->max_cpu_cnt = 0;
	part_ptr->max_core_cnt = 0;

	if (part_ptr->node_bitmap == NULL) {
		part_ptr->node_bitmap = bit_alloc(node_record_count);
		old_bitmap = NULL;
	} else {
		old_bitmap = bit_copy(part_ptr->node_bitmap);
		bit_clear_all(part_ptr->node_bitmap);
	}

	if (!(host_list = nodespec_to_hostlist(part_ptr->orig_nodes, true,
					       &part_ptr->nodesets))) {
		/* Error, restore original bitmap */
		FREE_NULL_BITMAP(part_ptr->node_bitmap);
		part_ptr->node_bitmap = old_bitmap;
		return ESLURM_INVALID_NODE_NAME;
	} else if (!hostlist_count(host_list)) {
		info("%s: No nodes in partition %s", __func__, part_ptr->name);
		/*
		 * Clear "nodes" but leave "orig_nodes" intact.
		 * e.g.
		 * orig_nodes="nodeset1" and all of the nodes in "nodeset1" are
		 * removed. "nodes" should be cleared to show that there are no
		 * nodes in the partition right now. "orig_nodes" needs to stay
		 * intact so that when "nodeset1" nodes come back they are added
		 * to the partition.
		 */
		xfree(part_ptr->nodes);
		_unlink_free_nodes(old_bitmap, part_ptr);
		FREE_NULL_BITMAP(old_bitmap);
		FREE_NULL_HOSTLIST(host_list);
		return 0;
	}

	while ((this_node_name = hostlist_shift(host_list))) {
		node_ptr = find_node_record_no_alias(this_node_name);
		if (node_ptr == NULL) {
			if (!missing_hostlist)
				missing_hostlist =
					hostlist_create(this_node_name);
			else
				hostlist_push_host(missing_hostlist,
						   this_node_name);
			info("%s: invalid node name %s in partition",
			     __func__, this_node_name);
			free(this_node_name);
			rc = ESLURM_INVALID_NODE_NAME;
			continue;
		}
		part_ptr->total_nodes++;
		part_ptr->total_cpus += node_ptr->cpus;
		part_ptr->max_cpu_cnt = MAX(part_ptr->max_cpu_cnt,
					    node_ptr->cpus);
		part_ptr->max_core_cnt = MAX(part_ptr->max_core_cnt,
					     node_ptr->tot_cores);

		for (i = 0; i < node_ptr->part_cnt; i++) {
			if (node_ptr->part_pptr[i] == part_ptr)
				break;
		}
		if (i == node_ptr->part_cnt) { /* Node in new partition */
			node_ptr->part_cnt++;
			xrecalloc(node_ptr->part_pptr, node_ptr->part_cnt,
				  sizeof(part_record_t *));
			node_ptr->part_pptr[node_ptr->part_cnt-1] = part_ptr;
		}
		if (old_bitmap)
			bit_clear(old_bitmap, node_ptr->index);

		bit_set(part_ptr->node_bitmap, node_ptr->index);
		free(this_node_name);
	}
	hostlist_destroy(host_list);

	if ((rc == ESLURM_INVALID_NODE_NAME) && missing_hostlist) {
		/*
		 * Remove missing node from partition nodes so we don't keep
		 * trying to remove them.
		 */
		hostlist_t *hl;
		char *missing_nodes;

		hl = hostlist_create(part_ptr->orig_nodes);
		missing_nodes =
			hostlist_ranged_string_xmalloc(missing_hostlist);
		hostlist_delete(hl, missing_nodes);
		xfree(missing_nodes);
		xfree(part_ptr->orig_nodes);
		part_ptr->orig_nodes = hostlist_ranged_string_xmalloc(hl);
		hostlist_destroy(hl);

	}
	hostlist_destroy(missing_hostlist);
	xfree(part_ptr->nodes);
	part_ptr->nodes = bitmap2node_name(part_ptr->node_bitmap);

	_unlink_free_nodes(old_bitmap, part_ptr);
	last_node_update = time(NULL);
	FREE_NULL_BITMAP(old_bitmap);
	return rc;
}

/* unlink nodes removed from a partition */
static void _unlink_free_nodes(bitstr_t *old_bitmap, part_record_t *part_ptr)
{
	int i, j, k, update_nodes = 0;
	node_record_t *node_ptr;

	if (old_bitmap == NULL)
		return;

	for (i = 0; (node_ptr = next_node_bitmap(old_bitmap, &i)); i++) {
		for (j=0; j<node_ptr->part_cnt; j++) {
			if (node_ptr->part_pptr[j] != part_ptr)
				continue;
			node_ptr->part_cnt--;
			for (k=j; k<node_ptr->part_cnt; k++) {
				node_ptr->part_pptr[k] =
					node_ptr->part_pptr[k+1];
			}
			break;
		}
		update_nodes = 1;
	}

	if (update_nodes)
		last_node_update = time(NULL);
}

/*
 * create_ctld_part_record - create a partition record
 * RET a pointer to the record or NULL if error
 * global: part_list - global partition list
 */
part_record_t *create_ctld_part_record(const char *name)
{
	part_record_t *part_ptr = part_record_create();

	last_part_update = time(NULL);

	part_ptr->name = xstrdup(name);

	list_append(part_list, part_ptr);

	return part_ptr;
}

/* dump_all_part_state - save the state of all partitions to file */
int dump_all_part_state(void)
{
	/* Save high-water mark to avoid buffer growth with copies */
	static uint32_t high_buffer_size = BUF_SIZE;
	/* Locks: Read partition */
	slurmctld_lock_t part_read_lock =
	    { READ_LOCK, NO_LOCK, NO_LOCK, READ_LOCK, NO_LOCK };
	buf_t *buffer = init_buf(high_buffer_size);
	DEF_TIMERS;

	START_TIMER;
	/* write header: time */
	packstr(PART_STATE_VERSION, buffer);
	pack16(SLURM_PROTOCOL_VERSION, buffer);
	pack_time(time(NULL), buffer);

	/* write partition records to buffer */
	lock_slurmctld(part_read_lock);
	list_for_each_ro(part_list, _dump_part_state, buffer);
	unlock_slurmctld(part_read_lock);

	save_buf_to_state("part_state", buffer, &high_buffer_size);

	FREE_NULL_BUFFER(buffer);
	END_TIMER2(__func__);
	return 0;
}

/*
 * _dump_part_state - dump the state of a specific partition to a buffer
 * IN part_ptr - pointer to partition for which information
 *	is requested
 * IN/OUT buffer - location to store data, pointers automatically advanced
 *
 * Note: read by load_all_part_state().
 */
static int _dump_part_state(void *x, void *arg)
{
	part_record_t *part_ptr = (part_record_t *) x;
	buf_t *buffer = (buf_t *) arg;

	xassert(part_ptr);
	xassert(part_ptr->magic == PART_MAGIC);

	if (default_part_loc == part_ptr)
		part_ptr->flags |= PART_FLAG_DEFAULT;
	else
		part_ptr->flags &= (~PART_FLAG_DEFAULT);

	part_record_pack(part_ptr, buffer, SLURM_PROTOCOL_VERSION);

	return 0;
}

/*
 * load_all_part_state - load the partition state from file, recover on
 *	slurmctld restart. execute this after loading the configuration
 *	file data.
 *
 * Note: reads dump from _dump_part_state().
 */
extern int load_all_part_state(uint16_t reconfig_flags)
{
	char *state_file = NULL;
	time_t time;
	part_record_t *part_ptr;
	int error_code = 0, part_cnt = 0;
	buf_t *buffer;
	char *ver_str = NULL;
	uint16_t protocol_version = NO_VAL16;

	xassert(verify_lock(CONF_LOCK, READ_LOCK));

	if (!(reconfig_flags & RECONFIG_KEEP_PART_INFO) &&
	    !(reconfig_flags & RECONFIG_KEEP_PART_STAT)) {
		debug("Restoring partition state from state file disabled");
		return SLURM_SUCCESS;
	}

	/* read the file */
	buffer = state_save_open("part_state", &state_file);
	if (!buffer) {
		info("No partition state file (%s) to recover",
		     state_file);
		xfree(state_file);
		return ENOENT;
	}
	xfree(state_file);

	safe_unpackstr(&ver_str, buffer);
	debug3("Version string in part_state header is %s", ver_str);
	if (ver_str && !xstrcmp(ver_str, PART_STATE_VERSION))
		safe_unpack16(&protocol_version, buffer);

	if (protocol_version == NO_VAL16) {
		if (!ignore_state_errors)
			fatal("Can not recover partition state, data version incompatible, start with '-i' to ignore this. Warning: using -i will lose the data that can't be recovered.");
		error("**********************************************************");
		error("Can not recover partition state, data version incompatible");
		error("**********************************************************");
		xfree(ver_str);
		FREE_NULL_BUFFER(buffer);
		return EFAULT;
	}
	xfree(ver_str);
	safe_unpack_time(&time, buffer);

	while (remaining_buf(buffer) > 0) {
		part_record_t *part_rec_state = NULL;

		if ((error_code = part_record_unpack(&part_rec_state, buffer,
						     protocol_version)))
			goto unpack_error;

		if ((part_rec_state->flags & PART_FLAG_DEFAULT_CLR)   ||
		    (part_rec_state->flags & PART_FLAG_EXC_USER_CLR)  ||
		    (part_rec_state->flags & PART_FLAG_EXC_TOPO_CLR)  ||
		    (part_rec_state->flags & PART_FLAG_HIDDEN_CLR)    ||
		    (part_rec_state->flags & PART_FLAG_NO_ROOT_CLR)   ||
		    (part_rec_state->flags & PART_FLAG_PDOI_CLR)      ||
		    (part_rec_state->flags & PART_FLAG_ROOT_ONLY_CLR) ||
		    (part_rec_state->flags & PART_FLAG_REQ_RESV_CLR)  ||
		    (part_rec_state->flags & PART_FLAG_LLN_CLR)) {
			error("Invalid data for partition %s: flags=%u",
			      part_rec_state->name, part_rec_state->flags);
			error_code = EINVAL;
		}
		/* validity test as possible */
		if (part_rec_state->state_up > PARTITION_UP) {
			error("Invalid data for partition %s: state_up=%u",
			      part_rec_state->name, part_rec_state->state_up);
			error_code = EINVAL;
		}
		if (error_code) {
			error("No more partition data will be processed from "
			      "the checkpoint file");
			part_record_delete(part_rec_state);
			error_code = EINVAL;
			break;
		}

		/* find record and perform update */
		part_ptr = list_find_first(part_list, &list_find_part,
					   part_rec_state->name);
		if (!part_ptr && (reconfig_flags & RECONFIG_KEEP_PART_INFO)) {
			info("%s: partition %s missing from configuration file, creating",
			     __func__, part_rec_state->name);
			part_ptr = create_ctld_part_record(part_rec_state->name);
		} else if (!part_ptr) {
			info("%s: partition %s removed from configuration file, skipping",
			     __func__, part_rec_state->name);
		}

		/* Handle RECONFIG_KEEP_PART_STAT */
		if (part_ptr) {
			part_cnt++;
			part_ptr->state_up = part_rec_state->state_up;
		}

		if (!(reconfig_flags & RECONFIG_KEEP_PART_INFO)) {
			part_record_delete(part_rec_state);
			continue;
		}

		part_ptr->cpu_bind = part_rec_state->cpu_bind;
		part_ptr->flags = part_rec_state->flags;
		if (part_ptr->flags & PART_FLAG_DEFAULT) {
			xfree(default_part_name);
			default_part_name = xstrdup(part_rec_state->name);
			default_part_loc = part_ptr;
		}
		part_ptr->max_time = part_rec_state->max_time;
		part_ptr->default_time = part_rec_state->default_time;
		part_ptr->max_cpus_per_node = part_rec_state->max_cpus_per_node;
		part_ptr->max_cpus_per_socket =
			part_rec_state->max_cpus_per_socket;
		part_ptr->max_nodes = part_rec_state->max_nodes;
		part_ptr->max_nodes_orig = part_rec_state->max_nodes;
		part_ptr->min_nodes = part_rec_state->min_nodes;
		part_ptr->min_nodes_orig = part_rec_state->min_nodes;
		part_ptr->max_share = part_rec_state->max_share;
		part_ptr->grace_time = part_rec_state->grace_time;
		part_ptr->over_time_limit = part_rec_state->over_time_limit;
		if (part_rec_state->preempt_mode != NO_VAL16)
			part_ptr->preempt_mode = part_rec_state->preempt_mode;
		part_ptr->priority_job_factor =
			part_rec_state->priority_job_factor;
		part_ptr->priority_tier = part_rec_state->priority_tier;
		part_ptr->cr_type = part_rec_state->cr_type;

		xfree(part_ptr->allow_accounts);
		part_ptr->allow_accounts = part_rec_state->allow_accounts;
		part_rec_state->allow_accounts = NULL;

		FREE_NULL_LIST(part_ptr->allow_accts_list);
		part_ptr->allow_accts_list =
			accounts_list_build(part_ptr->allow_accounts, false);

		xfree(part_ptr->allow_groups);
		part_ptr->allow_groups   = part_rec_state->allow_groups;
		part_rec_state->allow_groups = NULL;

		xfree(part_ptr->allow_qos);
		part_ptr->allow_qos = part_rec_state->allow_qos;
		part_rec_state->allow_qos = NULL;
		qos_list_build(part_ptr->allow_qos, false, true,
			       &part_ptr->allow_qos_bitstr);

		if (part_rec_state->qos_char) {
			slurmdb_qos_rec_t qos_rec;
			xfree(part_ptr->qos_char);
			part_ptr->qos_char = part_rec_state->qos_char;
			part_rec_state->qos_char = NULL;

			memset(&qos_rec, 0, sizeof(slurmdb_qos_rec_t));
			qos_rec.name = part_ptr->qos_char;
			if ((assoc_mgr_fill_in_qos(acct_db_conn, &qos_rec,
						   accounting_enforce,
						   &part_ptr->qos_ptr,
						   0) != SLURM_SUCCESS) ||
			    !part_ptr->qos_ptr) {
				error("Partition %s has an invalid qos (%s), "
				      "please check your configuration",
				      part_ptr->name, qos_rec.name);
				xfree(part_ptr->qos_char);
			}
		}

		xfree(part_ptr->allow_alloc_nodes);
		part_ptr->allow_alloc_nodes = part_rec_state->allow_alloc_nodes;
		part_rec_state->allow_alloc_nodes = NULL;

		xfree(part_ptr->alternate);
		part_ptr->alternate = part_rec_state->alternate;
		part_rec_state->alternate = NULL;

		xfree(part_ptr->deny_accounts);
		part_ptr->deny_accounts = part_rec_state->deny_accounts;
		part_rec_state->deny_accounts = NULL;
		FREE_NULL_LIST(part_ptr->deny_accts_list);
		part_ptr->deny_accts_list =
			accounts_list_build(part_ptr->deny_accounts, false);

		xfree(part_ptr->deny_qos);
		part_ptr->deny_qos = part_rec_state->deny_qos;
		part_rec_state->deny_qos = NULL;
		qos_list_build(part_ptr->deny_qos, false, true,
			       &part_ptr->deny_qos_bitstr);

		/*
		 * Store saved nodelist in orig_nodes. nodes will be regenerated
		 * from orig_nodes.
		 */
		xfree(part_ptr->nodes);
		xfree(part_ptr->orig_nodes);
		part_ptr->orig_nodes = part_rec_state->nodes;
		part_rec_state->nodes = NULL;

		xfree(part_ptr->topology_name);
		part_ptr->topology_name = part_rec_state->topology_name;
		part_rec_state->topology_name = NULL;

		part_record_delete(part_rec_state);
	}

	info("Recovered state of %d partitions", part_cnt);
	FREE_NULL_BUFFER(buffer);
	return error_code;

unpack_error:
	if (!ignore_state_errors)
		fatal("Incomplete partition data checkpoint file, start with '-i' to ignore this. Warning: using -i will lose the data that can't be recovered.");
	error("Incomplete partition data checkpoint file");
	info("Recovered state of %d partitions", part_cnt);
	FREE_NULL_BUFFER(buffer);
	return EFAULT;
}

/*
 * find_part_record - find a record for partition with specified name
 * IN name - name of the desired partition
 * RET pointer to partition or NULL if not found
 */
part_record_t *find_part_record(char *name)
{
	if (!part_list) {
		error("part_list is NULL");
		return NULL;
	}
	return list_find_first(part_list, &list_find_part, name);
}

/*
 * Create a copy of a job's part_list *partition list
 * IN part_list_src - a job's part_list
 * RET copy of part_list_src, must be freed by caller
 */
extern list_t *part_list_copy(list_t *part_list_src)
{
	part_record_t *part_ptr;
	list_itr_t *iter;
	list_t *part_list_dest = NULL;

	if (!part_list_src)
		return part_list_dest;

	part_list_dest = list_create(NULL);
	iter = list_iterator_create(part_list_src);
	while ((part_ptr = list_next(iter))) {
		list_append(part_list_dest, part_ptr);
	}
	list_iterator_destroy(iter);

	return part_list_dest;
}

/*
 * get_part_list - find record for named partition(s)
 * IN name - partition name(s) in a comma separated list
 * OUT part_ptr_list - sorted list of pointers to the partitions or NULL
 * OUT prim_part_ptr - pointer to the primary partition
 * OUT err_part - All the invalid partition names.
 * OUT first_valid - bool ptr indicating if the first partition in name is valid
 * NOTE: Caller must free the returned list
 * NOTE: Caller must free err_part
 */
extern void get_part_list(char *name, list_t **part_ptr_list,
			  part_record_t **prim_part_ptr, char **err_part,
			  bool *first_valid)
{
	part_record_t *part_ptr;
	char *token, *last = NULL, *tmp_name;
	bool first_iteration = true;

	*part_ptr_list = NULL;
	*prim_part_ptr = NULL;

	if (err_part)
		xfree(*err_part);

	if (first_valid)
		*first_valid = true;

	if (name == NULL)
		return;

	tmp_name = xstrdup(name);
	token = strtok_r(tmp_name, ",", &last);
	while (token) {
		part_ptr = list_find_first(part_list, &list_find_part, token);
		if (part_ptr) {
			if (!(*part_ptr_list))
				*part_ptr_list = list_create(NULL);
			if (!list_find_first(*part_ptr_list, &_match_part_ptr,
					     part_ptr))
				list_append(*part_ptr_list, part_ptr);
		} else {
			if (err_part)
				xstrfmtcat(*err_part, "%s%s",
					   *err_part ? "," : "",
					   token);
			if (first_iteration && first_valid)
				*first_valid = false;
		}
		token = strtok_r(NULL, ",", &last);
		first_iteration = false;
	}

	if (*part_ptr_list) {
		/*
		 * Return the first part_ptr in the list before sorting. On
		 * state load, the first partition in the list is the running
		 * partition -- for multi-partition jobs. Other times it doesn't
		 * matter what the returned part_ptr is because it will be
		 * modified when scheduling the different job_queue_rec_t's.
		 *
		 * The part_ptr_list always needs to be sorted by priority_tier.
		 */
		*prim_part_ptr = list_peek(*part_ptr_list);
		list_sort(*part_ptr_list, priority_sort_part_tier);
		if (list_count(*part_ptr_list) == 1)
			FREE_NULL_LIST(*part_ptr_list);
	}
	xfree(tmp_name);
}

/*
 * Create a global partition list.
 *
 * This should be called before creating any partition entries.
 */
void init_part_conf(void)
{
	last_part_update = time(NULL);

	if (part_list)		/* delete defunct partitions */
		list_flush(part_list);
	else
		part_list = list_create(_list_delete_part);

	xfree(default_part_name);
	default_part_loc = NULL;
}

/*
 * _list_delete_part - delete an entry from the global partition list,
 *	see common/list.h for documentation
 * global: node_record_count - count of nodes in the system
 *         node_record_table_ptr - pointer to global node table
 */
static void _list_delete_part(void *part_entry)
{
	part_record_t *part_ptr;
	node_record_t *node_ptr;
	int i, j, k;

	part_ptr = (part_record_t *) part_entry;

	xassert(part_ptr->magic == PART_MAGIC);
	part_ptr->magic = ~PART_MAGIC;

	for (i = 0; (node_ptr = next_node(&i)); i++) {
		for (j=0; j<node_ptr->part_cnt; j++) {
			if (node_ptr->part_pptr[j] != part_ptr)
				continue;
			node_ptr->part_cnt--;
			for (k=j; k<node_ptr->part_cnt; k++) {
				node_ptr->part_pptr[k] =
					node_ptr->part_pptr[k+1];
			}
			break;
		}
	}

	part_record_delete(part_ptr);
}

/*
 * list_find_part - find an entry in the partition list, see common/list.h
 *	for documentation
 * IN key - partition name
 * RET 1 if matches key, 0 otherwise
 * global- part_list - the global partition list
 */
int list_find_part(void *x, void *key)
{
	part_record_t *part_ptr = (part_record_t *) x;
	char *part = (char *)key;

	return (!xstrcmp(part_ptr->name, part));
}

/*
 * _match_part_ptr - find an entry in the partition list, see common/list.h
 *	for documentation
 * IN key - partition pointer
 * RET 1 if partition pointer matches, 0 otherwise
 */
static int _match_part_ptr(void *part_ptr, void *key)
{
	if (part_ptr == key)
		return 1;

	return 0;
}

/* partition is visible to the user */
static bool _part_is_visible(part_record_t *part_ptr, uid_t uid)
{
	xassert(verify_lock(PART_LOCK, READ_LOCK));
	xassert(uid != 0);

	if (part_ptr->flags & PART_FLAG_HIDDEN)
		return false;
	if (validate_group(part_ptr, uid) == 0)
		return false;

	return true;
}

typedef struct {
	uid_t uid;
	part_record_t **visible_parts;
} build_visible_parts_arg_t;

static int _build_visible_parts_foreach(void *elem, void *x)
{
	part_record_t *part_ptr = elem;
	build_visible_parts_arg_t *arg = x;

	if (_part_is_visible(part_ptr, arg->uid)) {
		*(arg->visible_parts) = part_ptr;
		arg->visible_parts++;
		if (get_log_level() >= LOG_LEVEL_DEBUG3) {
			char *tmp_str = NULL;
			for (int i = 0; arg->visible_parts[i]; i++)
				xstrfmtcat(tmp_str, "%s%s", tmp_str ? "," : "",
					   arg->visible_parts[i]->name);
			debug3("%s: uid:%u visible_parts:%s",
			       __func__, arg->uid, tmp_str);
			xfree(tmp_str);
		}
	}

	return SLURM_SUCCESS;
}

static int _find_part_qos(void *x, void *arg)
{
	part_record_t *part_ptr = x;

	if (part_ptr->qos_ptr == arg)
		return 1;
	return 0;
}

extern part_record_t **build_visible_parts(uid_t uid, bool skip)
{
	part_record_t **visible_parts_save;
	part_record_t **visible_parts;
	build_visible_parts_arg_t args = {0};

	/*
	 * The array of visible parts isn't used for privileged (i.e. operators)
	 * users or when SHOW_ALL is requested, so no need to create list.
	 */
	if (skip)
		return NULL;

	visible_parts = xcalloc(list_count(part_list) + 1,
				sizeof(part_record_t *));
	args.uid = uid;
	args.visible_parts = visible_parts;

	/*
	 * Save start pointer to start of the list so can point to start
	 * after appending to the list.
	 */
	visible_parts_save = visible_parts;
	list_for_each(part_list, _build_visible_parts_foreach, &args);

	return visible_parts_save;
}

extern int part_not_on_list(part_record_t **parts, part_record_t *x)
{
	for (int i = 0; parts[i]; i++) {
		if (parts[i] == x) {
			debug3("%s: partition: %s on visible part list",
			       __func__, x->name);
			return false;
		} else
			debug3("%s: partition: %s not on visible part list",
			       __func__, x->name);
	}
	return true;
}

static int _pack_part(void *object, void *arg)
{
	part_record_t *part_ptr = object;
	_foreach_pack_part_info_t *pack_info = arg;

	xassert(part_ptr->magic == PART_MAGIC);

	if (!(pack_info->show_flags & SHOW_ALL) &&
	    !pack_info->privileged &&
	    part_not_on_list(pack_info->visible_parts, part_ptr))
		return SLURM_SUCCESS;

	pack_part(part_ptr, pack_info->buffer, pack_info->protocol_version);
	pack_info->parts_packed++;

	return SLURM_SUCCESS;
}

/*
 * pack_all_part - dump all partition information for all partitions in
 *	machine independent form (for network transmission)
 * IN show_flags - partition filtering options
 * IN uid - uid of user making request (for partition filtering)
 * global: part_list - global list of partition records
 * OUT buffer
 * NOTE: change slurm_load_part() in api/part_info.c if data format changes
 */
extern buf_t *pack_all_part(uint16_t show_flags, uid_t uid,
			    uint16_t protocol_version)
{
	int tmp_offset;
	time_t now = time(NULL);
	bool privileged = validate_operator(uid);
	_foreach_pack_part_info_t pack_info = {
		.buffer = init_buf(BUF_SIZE),
		.parts_packed = 0,
		.privileged = privileged,
		.protocol_version = protocol_version,
		.show_flags = show_flags,
		.uid = uid,
		.visible_parts = build_visible_parts(uid, privileged),
	};

	/* write header: version and time */
	pack32(0, pack_info.buffer);
	pack_time(now, pack_info.buffer);

	list_for_each_ro(part_list, _pack_part, &pack_info);

	/* put the real record count in the message body header */
	tmp_offset = get_buf_offset(pack_info.buffer);
	set_buf_offset(pack_info.buffer, 0);
	pack32(pack_info.parts_packed, pack_info.buffer);
	set_buf_offset(pack_info.buffer, tmp_offset);

	xfree(pack_info.visible_parts);
	return pack_info.buffer;
}


/*
 * pack_part - dump all configuration information about a specific partition
 *	in machine independent form (for network transmission)
 * IN part_ptr - pointer to partition for which information is requested
 * IN/OUT buffer - buffer in which data is placed, pointers automatically
 *	updated
 * global: default_part_loc - pointer to the default partition
 * NOTE: if you make any changes here be sure to make the corresponding changes
 *	to _unpack_partition_info_members() in common/slurm_protocol_pack.c
 */
void pack_part(part_record_t *part_ptr, buf_t *buffer, uint16_t protocol_version)
{
	if (protocol_version >= SLURM_25_05_PROTOCOL_VERSION) {
		if (default_part_loc == part_ptr)
			part_ptr->flags |= PART_FLAG_DEFAULT;
		else
			part_ptr->flags &= (~PART_FLAG_DEFAULT);

		packstr(part_ptr->name, buffer);
		pack32(part_ptr->cpu_bind, buffer);
		pack32(part_ptr->grace_time, buffer);
		pack32(part_ptr->max_time, buffer);
		pack32(part_ptr->default_time, buffer);
		pack32(part_ptr->max_nodes_orig, buffer);
		pack32(part_ptr->min_nodes_orig, buffer);
		pack32(part_ptr->total_nodes, buffer);
		pack32(part_ptr->total_cpus, buffer);
		pack64(part_ptr->def_mem_per_cpu, buffer);
		pack32(part_ptr->max_cpus_per_node, buffer);
		pack32(part_ptr->max_cpus_per_socket, buffer);
		pack64(part_ptr->max_mem_per_cpu, buffer);

		pack32(part_ptr->flags, buffer);
		pack16(part_ptr->max_share, buffer);
		pack16(part_ptr->over_time_limit, buffer);
		pack16(part_ptr->preempt_mode, buffer);
		pack16(part_ptr->priority_job_factor, buffer);
		pack16(part_ptr->priority_tier, buffer);
		pack16(part_ptr->state_up, buffer);
		pack16(part_ptr->cr_type, buffer);
		pack16(part_ptr->resume_timeout, buffer);
		pack16(part_ptr->suspend_timeout, buffer);
		pack32(part_ptr->suspend_time, buffer);

		packstr(part_ptr->allow_accounts, buffer);
		packstr(part_ptr->allow_groups, buffer);
		packstr(part_ptr->allow_alloc_nodes, buffer);
		packstr(part_ptr->allow_qos, buffer);
		packstr(part_ptr->qos_char, buffer);
		packstr(part_ptr->alternate, buffer);
		packstr(part_ptr->deny_accounts, buffer);
		packstr(part_ptr->deny_qos, buffer);
		packstr(part_ptr->nodes, buffer);
		packstr(part_ptr->nodesets, buffer);
		pack_bit_str_hex(part_ptr->node_bitmap, buffer);
		packstr(part_ptr->billing_weights_str, buffer);
		packstr(part_ptr->topology_name, buffer);
		packstr(part_ptr->tres_fmt_str, buffer);
		(void) slurm_pack_list(part_ptr->job_defaults_list,
				       job_defaults_pack, buffer,
				       protocol_version);
	} else if (protocol_version >= SLURM_MIN_PROTOCOL_VERSION) {
		if (default_part_loc == part_ptr)
			part_ptr->flags |= PART_FLAG_DEFAULT;
		else
			part_ptr->flags &= (~PART_FLAG_DEFAULT);

		packstr(part_ptr->name, buffer);
		pack32(part_ptr->cpu_bind, buffer);
		pack32(part_ptr->grace_time, buffer);
		pack32(part_ptr->max_time, buffer);
		pack32(part_ptr->default_time, buffer);
		pack32(part_ptr->max_nodes_orig, buffer);
		pack32(part_ptr->min_nodes_orig, buffer);
		pack32(part_ptr->total_nodes, buffer);
		pack32(part_ptr->total_cpus, buffer);
		pack64(part_ptr->def_mem_per_cpu, buffer);
		pack32(part_ptr->max_cpus_per_node, buffer);
		pack32(part_ptr->max_cpus_per_socket, buffer);
		pack64(part_ptr->max_mem_per_cpu, buffer);

		pack32(part_ptr->flags, buffer);
		pack16(part_ptr->max_share, buffer);
		pack16(part_ptr->over_time_limit, buffer);
		pack16(part_ptr->preempt_mode, buffer);
		pack16(part_ptr->priority_job_factor, buffer);
		pack16(part_ptr->priority_tier, buffer);
		pack16(part_ptr->state_up, buffer);
		pack16(part_ptr->cr_type, buffer);
		pack16(part_ptr->resume_timeout, buffer);
		pack16(part_ptr->suspend_timeout, buffer);
		pack32(part_ptr->suspend_time, buffer);

		packstr(part_ptr->allow_accounts, buffer);
		packstr(part_ptr->allow_groups, buffer);
		packstr(part_ptr->allow_alloc_nodes, buffer);
		packstr(part_ptr->allow_qos, buffer);
		packstr(part_ptr->qos_char, buffer);
		packstr(part_ptr->alternate, buffer);
		packstr(part_ptr->deny_accounts, buffer);
		packstr(part_ptr->deny_qos, buffer);
		packstr(part_ptr->nodes, buffer);
		packstr(part_ptr->nodesets, buffer);
		pack_bit_str_hex(part_ptr->node_bitmap, buffer);
		packstr(part_ptr->billing_weights_str, buffer);
		packstr(part_ptr->tres_fmt_str, buffer);
		(void)slurm_pack_list(part_ptr->job_defaults_list,
				      job_defaults_pack, buffer,
				      protocol_version);
	} else {
		error("%s: protocol_version %hu not supported",
		      __func__, protocol_version);
	}
}

/*
 * Process string and set partition fields to appropriate values if valid
 *
 * IN billing_weights_str - suggested billing weights
 * IN part_ptr - pointer to partition
 * IN fail - whether the inner function should fatal if the string is invalid.
 * RET return SLURM_ERROR on error, SLURM_SUCCESS otherwise.
 */
extern int set_partition_billing_weights(char *billing_weights_str,
					 part_record_t *part_ptr, bool fail)
{
	double *tmp = NULL;

	if (!billing_weights_str || *billing_weights_str == '\0') {
		/* Clear the weights */
		xfree(part_ptr->billing_weights_str);
		xfree(part_ptr->billing_weights);
	} else {
		if (!(tmp = slurm_get_tres_weight_array(billing_weights_str,
							slurmctld_tres_cnt,
							fail)))
		    return SLURM_ERROR;

		xfree(part_ptr->billing_weights_str);
		xfree(part_ptr->billing_weights);
		part_ptr->billing_weights_str =
			xstrdup(billing_weights_str);
		part_ptr->billing_weights = tmp;
	}

	return SLURM_SUCCESS;
}

/*
 * update_part - create or update a partition's configuration data
 * IN part_desc - description of partition changes
 * IN create_flag - create a new partition
 * RET 0 or an error code
 * global: part_list - list of partition entries
 *	last_part_update - update time of partition records
 */
extern int update_part(update_part_msg_t * part_desc, bool create_flag)
{
	int error_code;
	part_record_t *part_ptr;

	if (part_desc->name == NULL) {
		info("%s: invalid partition name, NULL", __func__);
		return ESLURM_INVALID_PARTITION_NAME;
	}

	error_code = SLURM_SUCCESS;
	part_ptr = list_find_first(part_list, &list_find_part,
				   part_desc->name);

	if (create_flag) {
		if (part_ptr) {
			verbose("%s: Duplicate partition name for create (%s)",
				__func__, part_desc->name);
			return ESLURM_INVALID_PARTITION_NAME;
		}
		info("%s: partition %s being created", __func__,
		     part_desc->name);
		part_ptr = create_ctld_part_record(part_desc->name);
	} else {
		if (!part_ptr) {
			verbose("%s: Update for partition not found (%s)",
				__func__, part_desc->name);
			return ESLURM_INVALID_PARTITION_NAME;
		}
	}

	last_part_update = time(NULL);

	if (part_desc->billing_weights_str &&
	    set_partition_billing_weights(part_desc->billing_weights_str,
					  part_ptr, false)) {
		error_code = ESLURM_INVALID_TRES_BILLING_WEIGHTS;
		goto fini;
	}
	if (part_desc->cpu_bind) {
		char tmp_str[128];
		slurm_sprint_cpu_bind_type(tmp_str, part_desc->cpu_bind);
		info("%s: setting CpuBind to %s for partition %s", __func__,
		     tmp_str, part_desc->name);
		if (part_desc->cpu_bind == CPU_BIND_OFF)
			part_ptr->cpu_bind = 0;
		else
			part_ptr->cpu_bind = part_desc->cpu_bind;
	}

	if (part_desc->max_cpus_per_node != NO_VAL) {
		info("%s: setting MaxCPUsPerNode to %u for partition %s",
		     __func__, part_desc->max_cpus_per_node, part_desc->name);
		part_ptr->max_cpus_per_node = part_desc->max_cpus_per_node;
	}

	if (part_desc->max_cpus_per_socket != NO_VAL) {
		info("%s: setting MaxCPUsPerSocket to %u for partition %s",
		     __func__, part_desc->max_cpus_per_socket, part_desc->name);
		part_ptr->max_cpus_per_socket = part_desc->max_cpus_per_socket;
	}

	if (part_desc->max_time != NO_VAL) {
		info("%s: setting max_time to %u for partition %s", __func__,
		     part_desc->max_time, part_desc->name);
		part_ptr->max_time = part_desc->max_time;
	}

	if ((part_desc->default_time != NO_VAL) &&
	    (part_desc->default_time > part_ptr->max_time)) {
		info("%s: DefaultTime would exceed MaxTime for partition %s",
		     __func__, part_desc->name);
	} else if (part_desc->default_time != NO_VAL) {
		info("%s: setting default_time to %u for partition %s",
		     __func__, part_desc->default_time, part_desc->name);
		part_ptr->default_time = part_desc->default_time;
	}

	if (part_desc->max_nodes != NO_VAL) {
		info("%s: setting max_nodes to %u for partition %s", __func__,
		     part_desc->max_nodes, part_desc->name);
		part_ptr->max_nodes      = part_desc->max_nodes;
		part_ptr->max_nodes_orig = part_desc->max_nodes;
	}

	if (part_desc->min_nodes != NO_VAL) {
		info("%s: setting min_nodes to %u for partition %s", __func__,
		     part_desc->min_nodes, part_desc->name);
		part_ptr->min_nodes      = part_desc->min_nodes;
		part_ptr->min_nodes_orig = part_desc->min_nodes;
	}

	if (part_desc->grace_time != NO_VAL) {
		info("%s: setting grace_time to %u for partition %s", __func__,
		     part_desc->grace_time, part_desc->name);
		part_ptr->grace_time = part_desc->grace_time;
	}

	if (part_desc->flags & PART_FLAG_HIDDEN) {
		info("%s: setting hidden for partition %s", __func__,
		     part_desc->name);
		part_ptr->flags |= PART_FLAG_HIDDEN;
	} else if (part_desc->flags & PART_FLAG_HIDDEN_CLR) {
		info("%s: clearing hidden for partition %s", __func__,
		     part_desc->name);
		part_ptr->flags &= (~PART_FLAG_HIDDEN);
	}

	if (part_desc->flags & PART_FLAG_REQ_RESV) {
		info("%s: setting req_resv for partition %s", __func__,
		     part_desc->name);
		part_ptr->flags |= PART_FLAG_REQ_RESV;
	} else if (part_desc->flags & PART_FLAG_REQ_RESV_CLR) {
		info("%s: clearing req_resv for partition %s", __func__,
		     part_desc->name);
		part_ptr->flags &= (~PART_FLAG_REQ_RESV);
	}

	if (part_desc->flags & PART_FLAG_ROOT_ONLY) {
		info("%s: setting root_only for partition %s", __func__,
		     part_desc->name);
		part_ptr->flags |= PART_FLAG_ROOT_ONLY;
	} else if (part_desc->flags & PART_FLAG_ROOT_ONLY_CLR) {
		info("%s: clearing root_only for partition %s", __func__,
		     part_desc->name);
		part_ptr->flags &= (~PART_FLAG_ROOT_ONLY);
	}

	if (part_desc->flags & PART_FLAG_NO_ROOT) {
		info("%s: setting no_root for partition %s", __func__,
		     part_desc->name);
		part_ptr->flags |= PART_FLAG_NO_ROOT;
	} else if (part_desc->flags & PART_FLAG_NO_ROOT_CLR) {
		info("%s: clearing no_root for partition %s", __func__,
		     part_desc->name);
		part_ptr->flags &= (~PART_FLAG_NO_ROOT);
	}

	if (part_desc->flags & PART_FLAG_PDOI) {
		info("%s: setting PDOI for partition %s", __func__,
		     part_desc->name);
		part_ptr->flags |= PART_FLAG_PDOI;
	} else if (part_desc->flags & PART_FLAG_PDOI_CLR) {
		info("%s: clearing PDOI for partition %s", __func__,
		     part_desc->name);
		part_ptr->flags &= (~PART_FLAG_PDOI);
	}

	if (part_desc->flags & PART_FLAG_EXCLUSIVE_USER) {
		info("%s: setting exclusive_user for partition %s", __func__,
		     part_desc->name);
		part_ptr->flags |= PART_FLAG_EXCLUSIVE_USER;
	} else if (part_desc->flags & PART_FLAG_EXC_USER_CLR) {
		info("%s: clearing exclusive_user for partition %s", __func__,
		     part_desc->name);
		part_ptr->flags &= (~PART_FLAG_EXCLUSIVE_USER);
	}

	if (part_desc->flags & PART_FLAG_EXCLUSIVE_TOPO) {
		info("%s: setting exclusive_topo for partition %s", __func__,
		     part_desc->name);
		part_ptr->flags |= PART_FLAG_EXCLUSIVE_TOPO;
	} else if (part_desc->flags & PART_FLAG_EXC_TOPO_CLR) {
		info("%s: clearing exclusive_topo for partition %s", __func__,
		     part_desc->name);
		part_ptr->flags &= (~PART_FLAG_EXCLUSIVE_TOPO);
	}

	if (part_desc->flags & PART_FLAG_LLN) {
		info("%s: setting LLN for partition %s", __func__,
		     part_desc->name);
		part_ptr->flags |= PART_FLAG_LLN;
	} else if (part_desc->flags & PART_FLAG_LLN_CLR) {
		info("%s: clearing LLN for partition %s", __func__,
		     part_desc->name);
		part_ptr->flags &= (~PART_FLAG_LLN);
	}

	if (part_desc->state_up != NO_VAL16) {
		info("%s: setting state_up to %u for partition %s", __func__,
		     part_desc->state_up, part_desc->name);
		part_ptr->state_up = part_desc->state_up;
	}

	if (part_desc->max_share != NO_VAL16) {
		uint16_t force = part_desc->max_share & SHARED_FORCE;
		uint16_t val = part_desc->max_share & (~SHARED_FORCE);
		char tmp_str[24];
		if (val == 0)
			snprintf(tmp_str, sizeof(tmp_str), "EXCLUSIVE");
		else if (force)
			snprintf(tmp_str, sizeof(tmp_str), "FORCE:%u", val);
		else if (val == 1)
			snprintf(tmp_str, sizeof(tmp_str), "NO");
		else
			snprintf(tmp_str, sizeof(tmp_str), "YES:%u", val);
		info("%s: setting share to %s for partition %s", __func__,
		     tmp_str, part_desc->name);
		part_ptr->max_share = part_desc->max_share;
	}

	if (part_desc->over_time_limit != NO_VAL16) {
		info("%s: setting OverTimeLimit to %u for partition %s",
		     __func__, part_desc->over_time_limit, part_desc->name);
		part_ptr->over_time_limit = part_desc->over_time_limit;
	}

	if (part_desc->preempt_mode != NO_VAL16) {
		if (!(part_desc->preempt_mode & PREEMPT_MODE_GANG)) {
			uint16_t new_mode;

			new_mode =
				part_desc->preempt_mode & (~PREEMPT_MODE_GANG);

			if (new_mode <= PREEMPT_MODE_CANCEL) {
				/*
				 * This is a valid mode, but if GANG was enabled
				 * at cluster level, always leave it set.
				 */
				if ((part_ptr->preempt_mode != NO_VAL16) &&
				    (part_ptr->preempt_mode &
				     PREEMPT_MODE_GANG))
					new_mode = new_mode | PREEMPT_MODE_GANG;
				info("%s: setting preempt_mode to %s for partition %s",
				     __func__,
				     preempt_mode_string(new_mode),
				     part_desc->name);
				part_ptr->preempt_mode = new_mode;
			} else {
				info("%s: invalid preempt_mode %u", __func__, new_mode);
			}
		} else {
			info("%s: PreemptMode=GANG is a cluster-wide option and cannot be set at partition level",
			      __func__);
		}
	}

	if (part_desc->priority_tier != NO_VAL16) {
		bool changed =
			part_ptr->priority_tier != part_desc->priority_tier;
		info("%s: setting PriorityTier to %u for partition %s",
		     __func__, part_desc->priority_tier, part_desc->name);
		part_ptr->priority_tier = part_desc->priority_tier;

		/* Need to resort all job partition lists */
		if (changed)
			sort_all_jobs_partition_lists();
	}

	if (part_desc->priority_job_factor != NO_VAL16) {
		int redo_prio = 0;
		info("%s: setting PriorityJobFactor to %u for partition %s",
		     __func__, part_desc->priority_job_factor, part_desc->name);

		if ((part_ptr->priority_job_factor == part_max_priority) &&
		    (part_desc->priority_job_factor < part_max_priority))
			redo_prio = 2;
		else if (part_desc->priority_job_factor > part_max_priority)
			redo_prio = 1;

		part_ptr->priority_job_factor = part_desc->priority_job_factor;

		/* If the max_priority changes we need to change all
		 * the normalized priorities of all the other
		 * partitions. If not then just set this partition.
		 */
		if (redo_prio) {
			list_itr_t *itr = list_iterator_create(part_list);
			part_record_t *part2 = NULL;

			if (redo_prio == 2) {
				part_max_priority = DEF_PART_MAX_PRIORITY;
				while ((part2 = list_next(itr))) {
					if (part2->priority_job_factor >
					    part_max_priority)
						part_max_priority =
							part2->priority_job_factor;
				}
				list_iterator_reset(itr);
			} else
				part_max_priority = part_ptr->priority_job_factor;

			while ((part2 = list_next(itr))) {
				part2->norm_priority =
					(double)part2->priority_job_factor /
					(double)part_max_priority;
			}
			list_iterator_destroy(itr);
		} else {
			part_ptr->norm_priority =
				(double)part_ptr->priority_job_factor /
				(double)part_max_priority;
		}
	}

	if (part_desc->allow_accounts != NULL) {
		xfree(part_ptr->allow_accounts);
		if ((xstrcasecmp(part_desc->allow_accounts, "ALL") == 0) ||
		    (part_desc->allow_accounts[0] == '\0')) {
			info("%s: setting AllowAccounts to ALL for partition %s",
			     __func__, part_desc->name);
		} else {
			part_ptr->allow_accounts = part_desc->allow_accounts;
			part_desc->allow_accounts = NULL;
			info("%s: setting AllowAccounts to %s for partition %s",
			     __func__, part_ptr->allow_accounts,
			     part_desc->name);
		}
		FREE_NULL_LIST(part_ptr->allow_accts_list);
		part_ptr->allow_accts_list =
			accounts_list_build(part_ptr->allow_accounts, false);
	}

	if (part_desc->allow_groups != NULL) {
		xfree(part_ptr->allow_groups);
		xfree(part_ptr->allow_uids);
		part_ptr->allow_uids_cnt = 0;
		if ((xstrcasecmp(part_desc->allow_groups, "ALL") == 0) ||
		    (part_desc->allow_groups[0] == '\0')) {
			info("%s: setting allow_groups to ALL for partition %s",
			     __func__, part_desc->name);
		} else {
			part_ptr->allow_groups = part_desc->allow_groups;
			part_desc->allow_groups = NULL;
			info("%s: setting allow_groups to %s for partition %s",
			     __func__, part_ptr->allow_groups, part_desc->name);
			part_ptr->allow_uids =
				get_groups_members(part_ptr->allow_groups,
						   &part_ptr->allow_uids_cnt);
			clear_group_cache();
		}
	}

	if (part_desc->allow_qos != NULL) {
		bitstr_t *tmp_allow_qos_bitstr = NULL;
		if (qos_list_build(part_desc->allow_qos, false, false,
				   &tmp_allow_qos_bitstr) != SLURM_SUCCESS) {
			error("%s: invalid qos (%s) given for AllowQOS",
			      __func__, part_desc->allow_qos);
			error_code = ESLURM_INVALID_QOS;
		} else if ((xstrcasecmp(part_desc->allow_qos, "ALL") == 0) ||
			   (part_desc->allow_qos[0] == '\0')) {
			info("%s: setting AllowQOS to ALL for partition %s",
			     __func__, part_desc->name);
			xfree(part_ptr->allow_qos);
			FREE_NULL_BITMAP(part_ptr->allow_qos_bitstr);
		} else {
			xfree(part_ptr->allow_qos);
			part_ptr->allow_qos = part_desc->allow_qos;
			part_desc->allow_qos = NULL;
			FREE_NULL_BITMAP(part_ptr->allow_qos_bitstr);
			part_ptr->allow_qos_bitstr = tmp_allow_qos_bitstr;
			info("%s: setting AllowQOS to %s for partition %s",
			     __func__, part_ptr->allow_qos, part_desc->name);
		}
	}

	if (part_desc->qos_char && part_desc->qos_char[0] == '\0') {
		slurmdb_qos_rec_t *qos = part_ptr->qos_ptr;
		xfree(part_ptr->qos_char);
		part_ptr->qos_ptr = NULL;
		if (qos) {
			assoc_mgr_lock_t locks = {
				.qos = WRITE_LOCK,
				.tres = READ_LOCK,
			};
			assoc_mgr_lock(&locks);
			info("%s: removing partition QOS '%s' from partition '%s'",
			     __func__, qos->name, part_ptr->name);
		        if (!list_find_first(part_list, _find_part_qos, qos))
				qos->flags &= ~QOS_FLAG_PART_QOS;
			/*
			 * Reset relative QOS to the full system cnts
			 */
			if ((qos->flags & QOS_FLAG_RELATIVE) &&
			    !(qos->flags & QOS_FLAG_PART_QOS)) {
				qos->flags &= ~QOS_FLAG_RELATIVE_SET;
				assoc_mgr_set_qos_tres_relative_cnt(qos, NULL);
			}
			assoc_mgr_unlock(&locks);
		}
	} else if (part_desc->qos_char) {
		assoc_mgr_lock_t locks = {
			.qos = WRITE_LOCK,
			.tres = READ_LOCK,
		};
		slurmdb_qos_rec_t qos_rec, *backup_qos_ptr = part_ptr->qos_ptr;
		slurmdb_qos_rec_t *qos = NULL;
		part_record_t *qos_part_ptr = NULL;
		memset(&qos_rec, 0, sizeof(slurmdb_qos_rec_t));
		qos_rec.name = part_desc->qos_char;
		assoc_mgr_lock(&locks);
		if ((assoc_mgr_fill_in_qos(
			    acct_db_conn, &qos_rec, accounting_enforce,
			    (slurmdb_qos_rec_t **)&qos, true)
		     != SLURM_SUCCESS) || !qos) {
			error("%s: invalid qos (%s) given",
			      __func__, qos_rec.name);
			error_code = ESLURM_INVALID_QOS;
			part_ptr->qos_ptr = backup_qos_ptr;
		} else if ((qos->flags & QOS_FLAG_RELATIVE) &&
			   (qos_part_ptr = list_find_first(
				   part_list, _find_part_qos, qos))) {
			error_code = ESLURM_INVALID_RELATIVE_QOS;
			error("%s: %s Partition %s already uses relative QOS (%s).",
			      __func__, slurm_strerror(error_code),
			      qos_part_ptr->name, qos_rec.name);
			part_ptr->qos_ptr = backup_qos_ptr;
		} else {
			info("%s: changing partition QOS from "
			     "%s to %s for partition %s",
			     __func__, part_ptr->qos_char, part_desc->qos_char,
			     part_ptr->name);

			xfree(part_ptr->qos_char);
			part_ptr->qos_char = xstrdup(part_desc->qos_char);
			part_ptr->qos_ptr = qos;
			part_ptr->qos_ptr->flags |= QOS_FLAG_PART_QOS;
			/*
			 * Set a relative QOS' counts based on the partition.
			 */
			if (qos->flags & QOS_FLAG_RELATIVE) {
				qos->flags &= ~QOS_FLAG_RELATIVE_SET;
				assoc_mgr_set_qos_tres_relative_cnt(
					qos, part_ptr->tres_cnt);
			}

			if (backup_qos_ptr) {
				if (!list_find_first(part_list, _find_part_qos,
						     backup_qos_ptr))
					backup_qos_ptr->flags &=
						~QOS_FLAG_PART_QOS;

				/*
				 * Reset relative QOS to the full system cnts
				 */
				if ((backup_qos_ptr->flags &
				     QOS_FLAG_RELATIVE) &&
				    !(backup_qos_ptr->flags &
				      QOS_FLAG_PART_QOS)) {
					backup_qos_ptr->flags &=
						~QOS_FLAG_RELATIVE_SET;
					assoc_mgr_set_qos_tres_relative_cnt(
						backup_qos_ptr, NULL);
				}

			}
		}
		assoc_mgr_unlock(&locks);
	}

	if (part_desc->allow_alloc_nodes != NULL) {
		xfree(part_ptr->allow_alloc_nodes);
		if ((part_desc->allow_alloc_nodes[0] == '\0') ||
		    (xstrcasecmp(part_desc->allow_alloc_nodes, "ALL") == 0)) {
			part_ptr->allow_alloc_nodes = NULL;
			info("%s: setting allow_alloc_nodes to ALL for partition %s",
			     __func__, part_desc->name);
		}
		else {
			part_ptr->allow_alloc_nodes = part_desc->
						      allow_alloc_nodes;
			part_desc->allow_alloc_nodes = NULL;
			info("%s: setting allow_alloc_nodes to %s for partition %s",
			     __func__, part_ptr->allow_alloc_nodes,
			     part_desc->name);
		}
	}
	if (part_desc->alternate != NULL) {
		xfree(part_ptr->alternate);
		if ((xstrcasecmp(part_desc->alternate, "NONE") == 0) ||
		    (part_desc->alternate[0] == '\0'))
			part_ptr->alternate = NULL;
		else
			part_ptr->alternate = xstrdup(part_desc->alternate);
		part_desc->alternate = NULL;
		info("%s: setting alternate to %s for partition %s",
		     __func__, part_ptr->alternate, part_desc->name);
	}

	if (part_desc->def_mem_per_cpu != NO_VAL64) {
		char *key;
		uint32_t value;
		if (part_desc->def_mem_per_cpu & MEM_PER_CPU) {
			key = "DefMemPerCpu";
			value = part_desc->def_mem_per_cpu & (~MEM_PER_CPU);
		} else {
			key = "DefMemPerNode";
			value = part_desc->def_mem_per_cpu;
		}
		info("%s: setting %s to %u for partition %s", __func__,
		     key, value, part_desc->name);
		part_ptr->def_mem_per_cpu = part_desc->def_mem_per_cpu;
	}

	if (part_desc->deny_accounts != NULL) {
		xfree(part_ptr->deny_accounts);
		if (part_desc->deny_accounts[0] == '\0')
			xfree(part_desc->deny_accounts);
		part_ptr->deny_accounts = part_desc->deny_accounts;
		part_desc->deny_accounts = NULL;
		info("%s: setting DenyAccounts to %s for partition %s",
		     __func__, part_ptr->deny_accounts, part_desc->name);
		FREE_NULL_LIST(part_ptr->deny_accts_list);
		part_ptr->deny_accts_list =
			accounts_list_build(part_ptr->deny_accounts, false);
	}
	if (part_desc->allow_accounts && part_desc->deny_accounts) {
		error("%s: Both AllowAccounts and DenyAccounts are defined, DenyAccounts will be ignored",
		      __func__);
	}

	if (part_desc->deny_qos != NULL) {
		bitstr_t *tmp_deny_qos_bitstr = NULL;
		if (qos_list_build(part_desc->deny_qos, false, false,
				   &tmp_deny_qos_bitstr) != SLURM_SUCCESS) {
			error("%s: invalid qos (%s) given for DenyQOS",
			      __func__, part_desc->deny_qos);
			error_code = ESLURM_INVALID_QOS;
		} else {
			xfree(part_ptr->deny_qos);
			if (part_desc->deny_qos[0] != '\0') {
				part_ptr->deny_qos = part_desc->deny_qos;
				part_desc->deny_qos = NULL;
			}
			FREE_NULL_BITMAP(part_ptr->deny_qos_bitstr);
			part_ptr->deny_qos_bitstr = tmp_deny_qos_bitstr;
			info("%s: setting DenyQOS to %s for partition %s",
			     __func__, part_ptr->deny_qos, part_desc->name);
		}
	}

	if (part_desc->allow_qos && part_desc->deny_qos) {
		error("%s: Both AllowQOS and DenyQOS are defined, DenyQOS will be ignored",
		      __func__);
	}

	if (part_desc->max_mem_per_cpu != NO_VAL64) {
		char *key;
		uint32_t value;
		if (part_desc->max_mem_per_cpu & MEM_PER_CPU) {
			key = "MaxMemPerCpu";
			value = part_desc->max_mem_per_cpu & (~MEM_PER_CPU);
		} else {
			key = "MaxMemPerNode";
			value = part_desc->max_mem_per_cpu;
		}
		info("%s: setting %s to %u for partition %s", __func__,
		     key, value, part_desc->name);
		part_ptr->max_mem_per_cpu = part_desc->max_mem_per_cpu;
	}

	if (part_desc->job_defaults_str) {
		list_t *new_job_def_list = NULL;
		if (part_desc->job_defaults_str[0] == '\0') {
			FREE_NULL_LIST(part_ptr->job_defaults_list);
		} else if (job_defaults_list(part_desc->job_defaults_str,
					     &new_job_def_list)
					!= SLURM_SUCCESS) {
			error("%s: Invalid JobDefaults(%s) given",
			      __func__, part_desc->job_defaults_str);
			error_code = ESLURM_INVALID_JOB_DEFAULTS;
		} else {	/* New list successfully built */
			FREE_NULL_LIST(part_ptr->job_defaults_list);
			part_ptr->job_defaults_list = new_job_def_list;
			info("%s: Setting JobDefaults to %s for partition %s",
			      __func__, part_desc->job_defaults_str,
			      part_desc->name);
		}
	}

	if (part_desc->nodes != NULL) {
		assoc_mgr_lock_t assoc_tres_read_lock = {
			.qos = WRITE_LOCK,
			.tres = READ_LOCK,
		};
		int rc;
		char *backup_orig_nodes = xstrdup(part_ptr->orig_nodes);

		if (part_desc->nodes[0] == '\0')
			part_ptr->nodes = NULL;	/* avoid empty string */
		else if ((part_desc->nodes[0] != '+') &&
			 (part_desc->nodes[0] != '-')) {
			xfree(part_ptr->nodes);
			part_ptr->nodes = xstrdup(part_desc->nodes);
		} else {
			char *p, *tmp, *tok, *save_ptr = NULL;
			hostset_t *hs = hostset_create(part_ptr->nodes);

			p = tmp = xstrdup(part_desc->nodes);
			errno = 0;
			while ((tok = node_conf_nodestr_tokenize(p,
								 &save_ptr))) {
				bool plus_minus = false;
				if (tok[0] == '+') {
					hostset_insert(hs, tok + 1);
					plus_minus = true;
				} else if (tok[0] == '-') {
					hostset_delete(hs, tok + 1);
					plus_minus = true;
				}
				/* errno set in hostset functions */
				if (!plus_minus || errno) {
					error("%s: invalid node name %s",
					      __func__, tok);
					xfree(tmp);
					hostset_destroy(hs);
					error_code = ESLURM_INVALID_NODE_NAME;
					goto fini;
				}
				p = NULL;
			}
			xfree(tmp);
			part_ptr->nodes = hostset_ranged_string_xmalloc(hs);
			hostset_destroy(hs);
		}
		xfree(part_ptr->orig_nodes);
		part_ptr->orig_nodes = xstrdup(part_ptr->nodes);

		if ((rc = build_part_bitmap(part_ptr))) {
			error_code = rc;

			if (!create_flag) {
				/* Restore previous nodes */
				xfree(part_ptr->orig_nodes);
				part_ptr->orig_nodes = backup_orig_nodes;

				/*
				 * build_part_bitmap() is destructive of the
				 * partition record. We need to rebuild the
				 * partition record with the original nodelists
				 * and nodesets.
				 */
				(void) build_part_bitmap(part_ptr);
			} else {
				xfree(backup_orig_nodes);
			}
		} else {
			info("%s: setting nodes to %s for partition %s",
			     __func__, part_ptr->nodes, part_desc->name);
			xfree(backup_orig_nodes);

			update_part_nodes_in_resv(part_ptr);
			power_save_set_timeouts(NULL);

			assoc_mgr_lock(&assoc_tres_read_lock);
			if (part_ptr->qos_ptr)
				part_ptr->qos_ptr->flags &= ~QOS_FLAG_RELATIVE_SET;
			_calc_part_tres(part_ptr, NULL);
			assoc_mgr_unlock(&assoc_tres_read_lock);
		}
	} else if (part_ptr->node_bitmap == NULL) {
		/* Newly created partition needs a bitmap, even if empty */
		part_ptr->node_bitmap = bit_alloc(node_record_count);
	}

	if (part_desc->topology_name) {
		char *old_topo_name = part_ptr->topology_name;

		info("%s: Setting Topology to %s for partition %s",
		      __func__, part_desc->topology_name, part_desc->name);

		if (part_desc->topology_name[0] == '\0') {
			part_ptr->topology_name = NULL;
			part_ptr->topology_idx = 0;
			xfree(old_topo_name);
		} else {
			part_ptr->topology_name = part_desc->topology_name;

			if (set_part_topology_idx(part_ptr, NULL)) {
				error("Failed to set part %s's topology to %s",
				      part_ptr->name, part_ptr->topology_name);
				part_ptr->topology_name = old_topo_name;
				error_code = ESLURM_REQUESTED_TOPO_CONFIG_UNAVAILABLE;
			} else {
				part_desc->topology_name = NULL;
				xfree(old_topo_name);
			}
		}
	}

fini:
	if (error_code == SLURM_SUCCESS) {
		if (part_desc->flags & PART_FLAG_DEFAULT) {
			if (default_part_name == NULL) {
				info("%s: setting default partition to %s",
				     __func__, part_desc->name);
			} else if (xstrcmp(default_part_name,
					   part_desc->name) != 0) {
				info("%s: changing default partition from %s to %s",
				     __func__, default_part_name,
				     part_desc->name);
			}
			xfree(default_part_name);
			default_part_name = xstrdup(part_desc->name);
			default_part_loc = part_ptr;
			part_ptr->flags |= PART_FLAG_DEFAULT;
		} else if ((part_desc->flags & PART_FLAG_DEFAULT_CLR) &&
			   (default_part_loc == part_ptr)) {
			info("%s: clearing default partition from %s", __func__,
			     part_desc->name);
			xfree(default_part_name);
			default_part_loc = NULL;
			part_ptr->flags &= (~PART_FLAG_DEFAULT);
		}

		gs_reconfig();
		select_g_reconfigure();		/* notify select plugin too */
	} else if (create_flag) {
		/* Delete the created partition in case of failure */
		list_delete_all(part_list, &list_find_part, part_desc->name);
	}
	return error_code;
}


/*
 * validate_group - validate that the uid is authorized to access the partition
 * IN part_ptr - pointer to a partition
 * IN run_uid - user to run the job as
 * RET 1 if permitted to run, 0 otherwise
 */
extern int validate_group(part_record_t *part_ptr, uid_t run_uid)
{
	static uid_t last_fail_uid = 0;
	static part_record_t *last_fail_part_ptr = NULL;
	static time_t last_fail_time = 0;
	time_t now;
	gid_t primary_gid;
	char *primary_group = NULL;
	char *groups, *saveptr = NULL, *one_group_name;
	int ret = 0;

	if (part_ptr->allow_groups == NULL)
		return 1;	/* all users allowed */
	if (validate_slurm_user(run_uid))
		return 1;	/* super-user can run anywhere */
	if (!part_ptr->allow_uids_cnt)
		return 0;

	for (int i = 0; i < part_ptr->allow_uids_cnt; i++) {
		if (part_ptr->allow_uids[i] == run_uid)
			return 1;
	}

	/* If this user has failed AllowGroups permission check on this
	 * partition in past 5 seconds, then do not test again for performance
	 * reasons. */
	now = time(NULL);
	if ((run_uid == last_fail_uid) &&
	    (part_ptr == last_fail_part_ptr) &&
	    (difftime(now, last_fail_time) < 5)) {
		return 0;
	}

	/*
	 * The allow_uids list is built from the allow_groups list.  If
	 * user/group enumeration has been disabled, it's possible that the
	 * user's primary group is not returned as a member of a group.
	 * Enumeration is problematic if the user/group database is large
	 * (think university-wide central account database or such), as in such
	 * environments enumeration would load the directory servers a lot, so
	 * the recommendation is to have it disabled (e.g. enumerate=False in
	 * sssd.conf). So check explicitly whether the primary group is allowed
	 * as a final resort.
	 * This should (hopefully) not happen that often.
	 */

	/* First figure out the primary GID.  */
	primary_gid = gid_from_uid(run_uid);

	if (primary_gid == (gid_t) -1) {
		error("%s: Could not find passwd entry for uid %u",
		      __func__, run_uid);
		goto fini;
	}

	/* Then use the primary GID to figure out the name of the
	 * group with that GID.  */

	primary_group = gid_to_string_or_null(primary_gid);

	if (!primary_group) {
		error("%s: Could not find group with gid %u",
		      __func__, primary_gid);
		goto fini;
	}

	/* And finally check the name of the primary group against the
	 * list of allowed group names.  */
	groups = xstrdup(part_ptr->allow_groups);
	one_group_name = strtok_r(groups, ",", &saveptr);
	while (one_group_name) {
		if (!xstrcmp(one_group_name, primary_group)) {
			ret = 1;
			break;
		}
		one_group_name = strtok_r(NULL, ",", &saveptr);
	}
	xfree(groups);
	xfree(primary_group);

	if (ret == 1) {
		debug("UID %u added to AllowGroup %s of partition %s",
		      run_uid, primary_group, part_ptr->name);
		part_ptr->allow_uids =
			xrealloc(part_ptr->allow_uids,
				 (sizeof(uid_t) *
				  (part_ptr->allow_uids_cnt + 1)));
		part_ptr->allow_uids[part_ptr->allow_uids_cnt++] = run_uid;
	}

fini:	if (ret == 0) {
		last_fail_uid = run_uid;
		last_fail_part_ptr = part_ptr;
		last_fail_time = now;
	}
	return ret;
}

/*
 * validate_alloc_node - validate that the allocating node
 * is allowed to use this partition
 * IN part_ptr - pointer to a partition
 * IN alloc_node - allocating node of the request
 * RET 1 if permitted to run, 0 otherwise
 */
extern int validate_alloc_node(part_record_t *part_ptr, char *alloc_node)
{
	int status;

 	if (part_ptr->allow_alloc_nodes == NULL)
 		return 1;	/* all allocating nodes allowed */
 	if (alloc_node == NULL)
		return 0;	/* if no allocating node deny */

	hostlist_t *hl = hostlist_create(part_ptr->allow_alloc_nodes);
 	status=hostlist_find(hl,alloc_node);
 	hostlist_destroy(hl);

 	if (status == -1)
		status = 0;
 	else
		status = 1;

 	return status;
}

static int _update_part_uid_access_list(void *x, void *arg)
{
	part_record_t *part_ptr = (part_record_t *)x;
	int *updated = (int *)arg;
	int i = 0;
	uid_t *tmp_uids = part_ptr->allow_uids;
	int tmp_uid_cnt = part_ptr->allow_uids_cnt;

	part_ptr->allow_uids =
		get_groups_members(part_ptr->allow_groups,
				   &part_ptr->allow_uids_cnt);

	if ((!part_ptr->allow_uids) && (!tmp_uids)) {
		/* no changes, because no arrays to compare */
	} else if ((!part_ptr->allow_uids) || (!tmp_uids) ||
		   (part_ptr->allow_uids_cnt != tmp_uid_cnt)) {
		/* creating, removing, or updating list, but sizes mismatch */
		*updated = 1;
	} else {
		/* updating with same size, we need to compare 1 by 1 */
		for (i = 0; i < part_ptr->allow_uids_cnt; i++) {
			if (tmp_uids[i] != part_ptr->allow_uids[i]) {
				*updated = 1;
				break;
			}
		}
	}

	xfree(tmp_uids);
	return 0;
}

static int _find_acct_in_list(void *x, void *arg)
{
	slurmdb_assoc_rec_t *acct_assoc_ptr = x;
	slurmdb_assoc_rec_t *query_assoc_ptr = arg;

	while (query_assoc_ptr) {
		if (acct_assoc_ptr == query_assoc_ptr)
			return 1;
		query_assoc_ptr = query_assoc_ptr->usage->parent_assoc_ptr;
	}
	return 0;
}

/*
 * load_part_uid_allow_list - reload the allow_uid list of partitions
 *	if required (updated group file or force set)
 * IN force - if set then always reload the allow_uid list
 */
void load_part_uid_allow_list(bool force)
{
	static time_t last_update_time;
	int updated = 0;
	time_t temp_time;
	DEF_TIMERS;

	START_TIMER;
	temp_time = get_group_tlm();
	if (!force && (temp_time == last_update_time))
		return;
	debug("Updating partition uid access list");
	last_update_time = temp_time;

	list_for_each(part_list, _update_part_uid_access_list, &updated);

	/* only update last_part_update when changes made to avoid restarting
	 * backfill scheduler unnecessarily */
	if (updated) {
		debug2("%s: list updated, resetting last_part_update time",
		       __func__);
		last_part_update = time(NULL);
	}

	clear_group_cache();
	END_TIMER2(__func__);
}

/* part_fini - free all memory associated with partition records */
void part_fini (void)
{
	FREE_NULL_LIST(part_list);
	default_part_loc = NULL;
}

extern int delete_partition(delete_part_msg_t *part_desc_ptr)
{
	part_record_t *part_ptr;

	part_ptr = find_part_record (part_desc_ptr->name);
	if (part_ptr == NULL)	/* No such partition */
		return ESLURM_INVALID_PARTITION_NAME;

	if (partition_in_use(part_desc_ptr->name))
		return ESLURM_PARTITION_IN_USE;

	if (default_part_loc == part_ptr) {
		error("Deleting default partition %s", part_ptr->name);
		default_part_loc = NULL;
	}
	(void) kill_job_by_part_name(part_desc_ptr->name);
	list_delete_all(part_list, list_find_part, part_desc_ptr->name);
	last_part_update = time(NULL);

	gs_reconfig();
	select_g_reconfigure();		/* notify select plugin too */

	return SLURM_SUCCESS;
}

/*
 * Validate a job's account against the partition's AllowAccounts or
 *	DenyAccounts parameters.
 * IN part_ptr - Partition pointer
 * IN acct - account name
 * in job_ptr - Job pointer or NULL. If set and job can not run, then set the
 *		job's state_desc and state_reason fields
 * RET SLURM_SUCCESS or error code
 */
extern int part_policy_valid_acct(part_record_t *part_ptr, char *acct,
				  job_record_t *job_ptr)
{
	int rc = SLURM_SUCCESS;
	slurmdb_assoc_rec_t *assoc_ptr = NULL;

	xassert(verify_assoc_lock(ASSOC_LOCK, READ_LOCK));

	if (!(accounting_enforce & ACCOUNTING_ENFORCE_ASSOCS))
		return SLURM_SUCCESS;

	if (job_ptr)
		assoc_ptr = job_ptr->assoc_ptr;
	else if (acct) {
		slurmdb_assoc_rec_t assoc_rec = {
			.acct = acct,
			.uid = NO_VAL,
		};
		if (assoc_mgr_fill_in_assoc(
			    acct_db_conn, &assoc_rec,
			    accounting_enforce,
			    &assoc_ptr, true) != SLURM_SUCCESS)
			rc = ESLURM_INVALID_ACCOUNT;
	} else
		rc = ESLURM_INVALID_ACCOUNT;

	if (!assoc_ptr)
		return rc;

	if (part_ptr->allow_accts_list) {
		if (!list_find_first(part_ptr->allow_accts_list,
				     _find_acct_in_list,
				     assoc_ptr))
			rc = ESLURM_INVALID_ACCOUNT;
	} else if (part_ptr->deny_accts_list) {
		if (list_find_first(part_ptr->deny_accts_list,
				    _find_acct_in_list,
				    assoc_ptr))
			rc = ESLURM_INVALID_ACCOUNT;
	}


	return rc;
}

/*
 * Validate a job's QOS against the partition's AllowQOS or DenyQOS parameters.
 * IN part_ptr - Partition pointer
 * IN qos_ptr - QOS pointer
 * IN submit_uid - uid of user issuing the request
 * in job_ptr - Job pointer or NULL. If set and job can not run, then set the
 *		job's state_desc and state_reason fields
 * RET SLURM_SUCCESS or error code
 */
extern int part_policy_valid_qos(part_record_t *part_ptr,
				 slurmdb_qos_rec_t *qos_ptr,
				 uid_t submit_uid,
				 job_record_t *job_ptr)
{
	char *tmp_err = NULL;

	if (part_ptr->allow_qos_bitstr) {
		int match = 0;
		if (!qos_ptr) {
			xstrfmtcat(tmp_err,
				   "Job's QOS not known, so it can't use this partition (%s allows %s)",
				   part_ptr->name, part_ptr->allow_qos);
			info("%s: %s (%pJ submit_uid=%u)",
			     __func__, tmp_err, job_ptr, submit_uid);
			if (job_ptr) {
				xfree(job_ptr->state_desc);
				job_ptr->state_desc = tmp_err;
				job_ptr->state_reason = WAIT_QOS;
				last_job_update = time(NULL);
			} else {
				xfree(tmp_err);
			}
			return ESLURM_INVALID_QOS;
		}
		if ((qos_ptr->id < bit_size(part_ptr->allow_qos_bitstr)) &&
		    bit_test(part_ptr->allow_qos_bitstr, qos_ptr->id))
			match = 1;
		if (match == 0) {
			xstrfmtcat(tmp_err,
				   "Job's QOS not permitted to use this partition (%s allows %s not %s)",
				   part_ptr->name, part_ptr->allow_qos,
				   qos_ptr->name);
			info("%s: %s (%pJ submit_uid=%u)",
			     __func__, tmp_err, job_ptr, submit_uid);
			if (job_ptr) {
				xfree(job_ptr->state_desc);
				job_ptr->state_desc = tmp_err;
				job_ptr->state_reason = WAIT_QOS;
				last_job_update = time(NULL);
			} else {
				xfree(tmp_err);
			}
			return ESLURM_INVALID_QOS;
		}
	} else if (part_ptr->deny_qos_bitstr) {
		int match = 0;
		if (!qos_ptr) {
			debug2("%s: Job's QOS not known, so couldn't check if it was denied or not",
			       __func__);
			return SLURM_SUCCESS;
		}
		if ((qos_ptr->id < bit_size(part_ptr->deny_qos_bitstr)) &&
		    bit_test(part_ptr->deny_qos_bitstr, qos_ptr->id))
			match = 1;
		if (match == 1) {
			xstrfmtcat(tmp_err,
				   "Job's QOS not permitted to use this partition (%s denies %s including %s)",
				   part_ptr->name, part_ptr->deny_qos,
				   qos_ptr->name);
			info("%s: %s (%pJ submit_uid=%u)",
			     __func__, tmp_err, job_ptr, submit_uid);
			if (job_ptr) {
				xfree(job_ptr->state_desc);
				job_ptr->state_desc = tmp_err;
				job_ptr->state_reason = WAIT_QOS;
				last_job_update = time(NULL);
			} else {
				xfree(tmp_err);
			}
			return ESLURM_INVALID_QOS;
		}
	}

	return SLURM_SUCCESS;
}

extern void part_list_update_assoc_lists(void)
{
	/* Write lock on part */
	slurmctld_lock_t part_write_lock = {
		.part = WRITE_LOCK,
	};
	assoc_mgr_lock_t locks = { .assoc = READ_LOCK };

	if (!part_list)
		return;

	lock_slurmctld(part_write_lock);
	assoc_mgr_lock(&locks);
	list_for_each(part_list, part_update_assoc_lists, NULL);
	assoc_mgr_unlock(&locks);
	unlock_slurmctld(part_write_lock);
}

extern int part_update_assoc_lists(void *x, void *arg)
{
	part_record_t *part_ptr = x;

	xassert(verify_assoc_lock(ASSOC_LOCK, READ_LOCK));

	FREE_NULL_LIST(part_ptr->allow_accts_list);
	part_ptr->allow_accts_list =
		accounts_list_build(part_ptr->allow_accounts, true);
	FREE_NULL_LIST(part_ptr->deny_accts_list);
	part_ptr->deny_accts_list =
		accounts_list_build(part_ptr->deny_accounts, true);

	return 0;
}

typedef struct {
	char *names;
	char *pos;
} _foreach_part_names_t;

static int _foreach_part_name_to_xstr(void *x, void *arg)
{
	part_record_t *part_ptr = x;
	_foreach_part_names_t *part_names = arg;

	xstrfmtcatat(part_names->names, &part_names->pos, "%s%s",
		     part_names->names ? "," : "", part_ptr->name);

	return SLURM_SUCCESS;
}

extern char *part_list_to_xstr(list_t *list)
{
	_foreach_part_names_t part_names = {0};

	xassert(list);

	list_for_each(list, _foreach_part_name_to_xstr, &part_names);

	return part_names.names;
}

extern int set_part_topology_idx(void *x, void *arg)
{
	part_record_t *part_ptr = x;

	if (!part_ptr->topology_name)
		part_ptr->topology_idx = 0;
	else if (topology_g_get(TOPO_DATA_TCTX_IDX, part_ptr->topology_name,
				&(part_ptr->topology_idx)))
		return -1;

	return 0;
}
