/*****************************************************************************\
 *  trigger_mgr.c - Event trigger management
 *****************************************************************************
 *  Copyright (C) 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 <jette1@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"

#define _GNU_SOURCE	/* for setresuid() */

#include <ctype.h>
#include <errno.h>
#include <fcntl.h>
#include <grp.h>
#include <pthread.h>
#include <signal.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>

#include "src/common/bitstring.h"
#include "src/common/fd.h"
#include "src/common/list.h"
#include "src/common/slurmdbd_defs.h"
#include "src/common/slurm_protocol_defs.h"
#include "src/common/state_save.h"
#include "src/common/uid.h"
#include "src/common/xmalloc.h"
#include "src/common/xstring.h"

#include "src/slurmctld/locks.h"
#include "src/slurmctld/slurmctld.h"
#include "src/slurmctld/state_save.h"
#include "src/slurmctld/trigger_mgr.h"

#define MAX_PROG_TIME 300	/* maximum run time for program */

/* Change TRIGGER_STATE_VERSION value when changing the state save format */
#define TRIGGER_STATE_VERSION        "PROTOCOL_VERSION"

list_t *trigger_list = NULL;
uint32_t next_trigger_id = 1;
static pthread_mutex_t trigger_mutex = PTHREAD_MUTEX_INITIALIZER;
bitstr_t *trigger_down_nodes_bitmap = NULL;
bitstr_t *trigger_drained_nodes_bitmap = NULL;
bitstr_t *trigger_fail_nodes_bitmap = NULL;
bitstr_t *trigger_up_nodes_bitmap   = NULL;
bitstr_t *trigger_draining_nodes_bitmap = NULL;
bitstr_t *trigger_resume_nodes_bitmap = NULL;
static bool trigger_bb_error = false;
static bool trigger_node_reconfig = false;
static bool trigger_pri_ctld_fail = false;
static bool trigger_pri_ctld_res_op = false;
static bool trigger_pri_ctld_res_ctrl = false;
static bool trigger_pri_ctld_acct_buffer_full = false;
static bool trigger_bu_ctld_fail = false;
static bool trigger_bu_ctld_res_op = false;
static bool trigger_bu_ctld_as_ctrl = false;
static bool trigger_pri_dbd_fail = false;
static bool trigger_pri_dbd_res_op = false;
static bool trigger_pri_db_fail = false;
static bool trigger_pri_db_res_op = false;

/* Current trigger pull states (saved and restored) */
uint8_t ctld_failure = 0;
uint8_t bu_ctld_failure = 0;
uint8_t db_failure = 0;
uint8_t dbd_failure = 0;

typedef struct trig_mgr_info {
	uint32_t child_pid;	/* pid of child process */
	uint16_t flags;		/* TRIGGER_FLAG_* */
	uint32_t trig_id;	/* trigger ID */
	uint16_t res_type;	/* TRIGGER_RES_TYPE_* */
	char *   res_id;	/* node name or job_id (string) */
	bitstr_t *nodes_bitmap;	/* bitmap of requested nodes (if applicable) */
	uint32_t job_id;	/* job ID (if applicable) */
	uint32_t trig_type;	/* TRIGGER_TYPE_* */
	time_t   trig_time;	/* offset (pending) or time stamp (complete) */
	uint32_t user_id;	/* user requesting trigger */
	uint32_t group_id;	/* user's group id */
	char *   program;	/* program to execute */
	uint8_t  state;		/* 0=pending, 1=pulled, 2=completed */

	/* The orig_ fields are used to save  and clone the original values */
	bitstr_t *orig_bitmap;	/* bitmap of requested nodes (if applicable) */
	char *   orig_res_id;	/* original node name or job_id (string) */
	time_t   orig_time;	/* offset (pending) or time stamp (complete) */
} trig_mgr_info_t;

static void _trig_del(void *x)
{
	trig_mgr_info_t *tmp = x;

	xfree(tmp->res_id);
	xfree(tmp->orig_res_id);
	xfree(tmp->program);
	FREE_NULL_BITMAP(tmp->nodes_bitmap);
	FREE_NULL_BITMAP(tmp->orig_bitmap);
	xfree(tmp);
}

static int _trig_offset(uint16_t offset)
{
	static int rc;
	rc  = offset;
	rc -= 0x8000;
	return rc;
}

static void _dump_trigger_msg(char *header, trigger_info_msg_t *msg)
{
	int i;

	if (!(slurm_conf.debug_flags & DEBUG_FLAG_TRIGGERS))
		return;

	info("%s", header);
	if ((msg == NULL) || (msg->record_count == 0)) {
		info("Trigger has no entries");
		return;
	}

	info("INDEX TRIG_ID RES_TYPE RES_ID TRIG_TYPE OFFSET UID PROGRAM");
	for (i=0; i<msg->record_count; i++) {
		info("trigger[%u] %u %s %s %s %d %u %s", i,
		     msg->trigger_array[i].trig_id,
		     trigger_res_type(msg->trigger_array[i].res_type),
		     msg->trigger_array[i].res_id,
		     trigger_type(msg->trigger_array[i].trig_type),
		     _trig_offset(msg->trigger_array[i].offset),
		     msg->trigger_array[i].user_id,
		     msg->trigger_array[i].program);
	}
}

/* Validate trigger program */
static bool _validate_trigger(trig_mgr_info_t *trig_in)
{
	struct stat buf;
	int i, modes;
	char *program = xstrdup(trig_in->program);

	for (i = 0; program[i]; i++) {
		if (isspace(program[i])) {
			program[i] = '\0';
			break;
		}
	}

	if (stat(program, &buf) != 0) {
		info("trigger program %s not found", trig_in->program);
		xfree(program);
		return false;
	}
	xfree(program);

	if (!S_ISREG(buf.st_mode)) {
		info("trigger program %s not a regular file", trig_in->program);
		return false;
	}
	if (buf.st_uid == trig_in->user_id)
		modes =  (buf.st_mode >> 6) & 07;
	else if (buf.st_gid == trig_in->group_id)
		modes =  (buf.st_mode >> 3) & 07;
	else
		modes = buf.st_mode  & 07;
	if (modes & 01)
		return true;

	info("trigger program %s not executable", trig_in->program);
	return false;
}


extern int trigger_pull(trigger_info_msg_t *msg)
{
	int rc = SLURM_SUCCESS;
	list_itr_t *trig_iter;
	trigger_info_t *trig_in;
	trig_mgr_info_t *trig_test;

	if (trigger_list == NULL) {
		trigger_list = list_create(_trig_del);
	}

	/* validate the request, designated trigger must be set */
	_dump_trigger_msg("trigger_pull", msg);
	if (msg->record_count != 1)
		return ESRCH;
	trig_in = msg->trigger_array;

	if ((trig_in->res_type != TRIGGER_RES_TYPE_SLURMCTLD) &&
	    (trig_in->res_type != TRIGGER_RES_TYPE_SLURMDBD)  &&
	    (trig_in->res_type != TRIGGER_RES_TYPE_DATABASE)) {
		return EINVAL;
	}

	/* now look for a valid request */
	trig_iter = list_iterator_create(trigger_list);
	while ((trig_test = list_next(trig_iter))) {
		if ((trig_test->res_type  == trig_in->res_type) &&
   		    (trig_test->trig_type == trig_in->trig_type)) {
			switch(trig_test->trig_type) {
			case TRIGGER_TYPE_PRI_CTLD_ACCT_FULL:
				trigger_primary_ctld_acct_full();
				break;
			case TRIGGER_TYPE_BU_CTLD_FAIL:
				trigger_backup_ctld_fail(trig_in->control_inx);
				break;
			case TRIGGER_TYPE_BU_CTLD_RES_OP:
				trigger_backup_ctld_res_op(trig_in->control_inx);
				break;
			case TRIGGER_TYPE_BU_CTLD_AS_CTRL:
				trigger_backup_ctld_as_ctrl();
				break;
			case TRIGGER_TYPE_PRI_DBD_FAIL:
				trigger_primary_dbd_fail();
				break;
			case TRIGGER_TYPE_PRI_DBD_RES_OP:
				trigger_primary_dbd_res_op();
				break;
			case TRIGGER_TYPE_PRI_DB_FAIL:
				trigger_primary_db_fail();
				break;
			case TRIGGER_TYPE_PRI_DB_RES_OP:
				trigger_primary_db_res_op();
				break;
			default:
				error("trigger_pull call has invalid type: %u",
				      trig_test->trig_type);
				rc = EINVAL;
				break;
			}
		}
	}
	list_iterator_destroy(trig_iter);

	return rc;
}

extern int trigger_clear(uid_t uid, trigger_info_msg_t *msg)
{
	int rc = ESRCH;
	list_itr_t *trig_iter;
	trigger_info_t *trig_in;
	trig_mgr_info_t *trig_test;
	uint32_t job_id = 0;

	slurm_mutex_lock(&trigger_mutex);
	if (trigger_list == NULL)
		trigger_list = list_create(_trig_del);

	/* validate the request, need a job_id and/or trigger_id */
	_dump_trigger_msg("trigger_clear", msg);
	if (msg->record_count != 1)
		goto fini;
	trig_in = msg->trigger_array;
	if (trig_in->res_type == TRIGGER_RES_TYPE_JOB) {
		job_id = (uint32_t) atol(trig_in->res_id);
		if (job_id == 0) {
			rc = ESLURM_INVALID_JOB_ID;
			goto fini;
		}
	} else if ((trig_in->trig_id == 0) && (trig_in->user_id == NO_VAL)) {
		rc = EINVAL;
		goto fini;
	}

	/* now look for a valid request, matching uid */
	trig_iter = list_iterator_create(trigger_list);
	while ((trig_test = list_next(trig_iter))) {
		if (trig_in->trig_id &&
		    (trig_in->trig_id != trig_test->trig_id))
			continue;
		if (job_id && (job_id != trig_test->job_id))
			continue;
		if ((trig_in->user_id != NO_VAL) &&
		    (trig_in->user_id != trig_test->user_id))
			continue;
		if (trig_test->state == 2)	/* wait for proc termination */
			continue;
		if ((trig_test->user_id != (uint32_t) uid) && (uid != 0)) {
			rc = ESLURM_ACCESS_DENIED;
			continue;
		}
		list_delete_item(trig_iter);
		rc = SLURM_SUCCESS;
	}
	list_iterator_destroy(trig_iter);
	schedule_trigger_save();

fini:	slurm_mutex_unlock(&trigger_mutex);
	return rc;
}

extern trigger_info_msg_t * trigger_get(uid_t uid, trigger_info_msg_t *msg)
{
	trigger_info_msg_t *resp_data;
	list_itr_t *trig_iter;
	trigger_info_t *trig_out;
	trig_mgr_info_t *trig_in;
	int recs_written = 0;

	slurm_mutex_lock(&trigger_mutex);
	if (trigger_list == NULL)
		trigger_list = list_create(_trig_del);

	_dump_trigger_msg("trigger_get", NULL);
	resp_data = xmalloc(sizeof(trigger_info_msg_t));
	resp_data->record_count = list_count(trigger_list);
	resp_data->trigger_array = xcalloc(resp_data->record_count,
					   sizeof(trigger_info_t));
	trig_iter = list_iterator_create(trigger_list);
	trig_out = resp_data->trigger_array;
	while ((trig_in = list_next(trig_iter))) {
		/* Note: Filtering currently done by strigger */
		if ((trig_in->state >= 1) &&
		    ((trig_out->flags & TRIGGER_FLAG_PERM) == 0))
			continue;	/* no longer pending */
		trig_out->flags     = trig_in->flags;
		trig_out->trig_id   = trig_in->trig_id;
		trig_out->res_type  = trig_in->res_type;
		trig_out->res_id    = xstrdup(trig_in->res_id);
		trig_out->trig_type = trig_in->trig_type;
		trig_out->offset    = trig_in->trig_time;
		trig_out->user_id   = trig_in->user_id;
		trig_out->program   = xstrdup(trig_in->program);
		trig_out++;
		recs_written++;
	}
	list_iterator_destroy(trig_iter);
	slurm_mutex_unlock(&trigger_mutex);
	resp_data->record_count = recs_written;

	_dump_trigger_msg("trigger_got", resp_data);
	return resp_data;
}

static bool _duplicate_trigger(trigger_info_t *trig_desc)
{
	bool found_dup = false;
	list_itr_t *trig_iter;
	trig_mgr_info_t *trig_rec;

	trig_iter = list_iterator_create(trigger_list);
	while ((trig_rec = list_next(trig_iter))) {
		if ((trig_desc->flags     == trig_rec->flags)      &&
		    (trig_desc->res_type  == trig_rec->res_type)   &&
		    (trig_desc->trig_type == trig_rec->trig_type)  &&
		    (trig_desc->offset    == trig_rec->trig_time)  &&
		    (trig_desc->user_id   == trig_rec->user_id)    &&
		    !xstrcmp(trig_desc->program, trig_rec->program) &&
		    !xstrcmp(trig_desc->res_id, trig_rec->res_id)) {
			found_dup = true;
			break;
		}
	}
	list_iterator_destroy(trig_iter);
	return found_dup;
}

extern int trigger_set(uid_t uid, gid_t gid, trigger_info_msg_t *msg)
{
	int i;
	int rc = SLURM_SUCCESS;
	uint32_t job_id;
	bitstr_t *bitmap = NULL;
	trig_mgr_info_t * trig_add;
	job_record_t *job_ptr;
	/* Read config and job info */
	slurmctld_lock_t job_read_lock =
		{ READ_LOCK, READ_LOCK, NO_LOCK, NO_LOCK, NO_LOCK };

	lock_slurmctld(job_read_lock);
	slurm_mutex_lock(&trigger_mutex);

	if ((slurm_conf.slurm_user_id != 0) &&
	    (slurm_conf.slurm_user_id != uid)) {
		/* If SlurmUser is not root, then it is unable to set the
		 * appropriate user id and group id for the program to be
		 * launched. To prevent the launched program for an arbitrary
		 * user being executed as user SlurmUser, disable all other
		 * users from setting triggers. */
		info("Attempt to set trigger by uid %u != SlurmUser", uid);
		rc = ESLURM_ACCESS_DENIED;
		goto fini;
	}

	if (trigger_list == NULL) {
		trigger_list = list_create(_trig_del);
	} else if ((uid != 0) &&
	           (list_count(trigger_list) >= slurm_conf.max_job_cnt)) {
		rc = EAGAIN;
		goto fini;
	}

	_dump_trigger_msg("trigger_set", msg);
	for (i = 0; i < msg->record_count; i++) {
		if (msg->trigger_array[i].res_type ==
		    TRIGGER_RES_TYPE_JOB) {
			job_id = (uint32_t) atol(
				 msg->trigger_array[i].res_id);
			job_ptr = find_job_record(job_id);
			if (job_ptr == NULL) {
				rc = ESLURM_INVALID_JOB_ID;
				continue;
			}
			if (IS_JOB_FINISHED(job_ptr)) {
				rc = ESLURM_ALREADY_DONE;
				continue;
			}
		} else {
			job_id = 0;
			job_ptr = NULL;
			if ((msg->trigger_array[i].res_id != NULL)   &&
			    (msg->trigger_array[i].res_id[0] != '*') &&
			    (node_name2bitmap(msg->trigger_array[i].res_id,
					      false, &bitmap, NULL))) {
				FREE_NULL_BITMAP(bitmap);
				rc = ESLURM_INVALID_NODE_NAME;
				continue;
			}
		}
		msg->trigger_array[i].user_id = (uint32_t) uid;
		if (_duplicate_trigger(&msg->trigger_array[i])) {
			FREE_NULL_BITMAP(bitmap);
			rc = ESLURM_TRIGGER_DUP;
			continue;
		}
		trig_add = xmalloc(sizeof(trig_mgr_info_t));
		msg->trigger_array[i].trig_id = next_trigger_id;
		trig_add->trig_id = next_trigger_id;
		next_trigger_id++;
		trig_add->flags = msg->trigger_array[i].flags;
		trig_add->res_type = msg->trigger_array[i].res_type;
		if (bitmap) {
			trig_add->nodes_bitmap = bitmap;
			trig_add->orig_bitmap  = bit_copy(bitmap);
			bitmap = NULL;
		}
		trig_add->job_id = job_id;
		if (msg->trigger_array[i].res_id) {
			trig_add->res_id = msg->trigger_array[i].res_id;
			trig_add->orig_res_id = xstrdup(trig_add->res_id);
			msg->trigger_array[i].res_id = NULL; /* moved */
		}
		trig_add->trig_type = msg->trigger_array[i].trig_type;
		trig_add->trig_time = msg->trigger_array[i].offset;
		trig_add->orig_time = msg->trigger_array[i].offset;
		trig_add->user_id   = msg->trigger_array[i].user_id;
		trig_add->group_id  = (uint32_t) gid;
		/* move don't copy "program" */
		trig_add->program = msg->trigger_array[i].program;
		msg->trigger_array[i].program = NULL;
		if (!_validate_trigger(trig_add)) {
			rc = ESLURM_ACCESS_DENIED;
			FREE_NULL_BITMAP(trig_add->nodes_bitmap);
			FREE_NULL_BITMAP(trig_add->orig_bitmap);
			xfree(trig_add->program);
			xfree(trig_add->res_id);
			xfree(trig_add);
			continue;
		}
		list_append(trigger_list, trig_add);
		schedule_trigger_save();
	}

fini:	slurm_mutex_unlock(&trigger_mutex);
	unlock_slurmctld(job_read_lock);
	return rc;
}

extern void trigger_node_down(node_record_t *node_ptr)
{
	xassert(verify_lock(NODE_LOCK, READ_LOCK));

	slurm_mutex_lock(&trigger_mutex);
	if (trigger_down_nodes_bitmap == NULL)
		trigger_down_nodes_bitmap = bit_alloc(node_record_count);
	bit_set(trigger_down_nodes_bitmap, node_ptr->index);
	slurm_mutex_unlock(&trigger_mutex);
}

extern void trigger_node_drained(node_record_t *node_ptr)
{
	xassert(verify_lock(NODE_LOCK, READ_LOCK));

	slurm_mutex_lock(&trigger_mutex);
	if (trigger_drained_nodes_bitmap == NULL)
		trigger_drained_nodes_bitmap = bit_alloc(node_record_count);
	bit_set(trigger_drained_nodes_bitmap, node_ptr->index);
	slurm_mutex_unlock(&trigger_mutex);
}

extern void trigger_node_failing(node_record_t *node_ptr)
{
	xassert(verify_lock(NODE_LOCK, READ_LOCK));

	slurm_mutex_lock(&trigger_mutex);
	if (trigger_fail_nodes_bitmap == NULL)
		trigger_fail_nodes_bitmap = bit_alloc(node_record_count);
	bit_set(trigger_fail_nodes_bitmap, node_ptr->index);
	slurm_mutex_unlock(&trigger_mutex);
}

extern void trigger_node_up(node_record_t *node_ptr)
{
	xassert(verify_lock(NODE_LOCK, READ_LOCK));

	slurm_mutex_lock(&trigger_mutex);
	if (trigger_up_nodes_bitmap == NULL)
		trigger_up_nodes_bitmap = bit_alloc(node_record_count);
	bit_set(trigger_up_nodes_bitmap, node_ptr->index);
	slurm_mutex_unlock(&trigger_mutex);
}

extern void trigger_node_draining(node_record_t *node_ptr)
{
	xassert(verify_lock(NODE_LOCK, READ_LOCK));

	slurm_mutex_lock(&trigger_mutex);
	if (!trigger_draining_nodes_bitmap)
		trigger_draining_nodes_bitmap = bit_alloc(node_record_count);
	bit_set(trigger_draining_nodes_bitmap, node_ptr->index);
	slurm_mutex_unlock(&trigger_mutex);
}

extern void trigger_node_resume(node_record_t *node_ptr)
{
	xassert(verify_lock(NODE_LOCK, READ_LOCK));

	slurm_mutex_lock(&trigger_mutex);
	if (!trigger_resume_nodes_bitmap)
		trigger_resume_nodes_bitmap = bit_alloc(node_record_count);
	bit_set(trigger_resume_nodes_bitmap, node_ptr->index);
	slurm_mutex_unlock(&trigger_mutex);
}

extern void trigger_reconfig(void)
{
	slurmctld_lock_t node_read_lock = { .node = READ_LOCK };

	lock_slurmctld(node_read_lock);
	slurm_mutex_lock(&trigger_mutex);
	trigger_node_reconfig = true;
	slurm_mutex_unlock(&trigger_mutex);
	unlock_slurmctld(node_read_lock);
}

extern void trigger_primary_ctld_fail(void)
{
	slurm_mutex_lock(&trigger_mutex);
	if (ctld_failure != 1) {
		trigger_pri_ctld_fail = true;
		ctld_failure = 1;
	}
	slurm_mutex_unlock(&trigger_mutex);
}

extern void trigger_primary_ctld_res_op(void)
{
	slurm_mutex_lock(&trigger_mutex);
	trigger_pri_ctld_res_op = true;
	ctld_failure = 0;
	slurm_mutex_unlock(&trigger_mutex);
}

extern void trigger_primary_ctld_res_ctrl(void)
{
	slurm_mutex_lock(&trigger_mutex);
	trigger_pri_ctld_res_ctrl = true;
	slurm_mutex_unlock(&trigger_mutex);
}

extern void trigger_primary_ctld_acct_full(void)
{
	slurm_mutex_lock(&trigger_mutex);
	trigger_pri_ctld_acct_buffer_full = true;
	slurm_mutex_unlock(&trigger_mutex);
}

extern void trigger_backup_ctld_fail(int index)
{
	slurm_mutex_lock(&trigger_mutex);
	if (bu_ctld_failure != 1) {
		trigger_bu_ctld_fail = true;
		bu_ctld_failure = 1;
	}
	slurm_mutex_unlock(&trigger_mutex);
}

extern void trigger_backup_ctld_res_op(int index)
{
	slurm_mutex_lock(&trigger_mutex);
	trigger_bu_ctld_res_op = true;
	bu_ctld_failure = 0;
	slurm_mutex_unlock(&trigger_mutex);
}

extern void trigger_backup_ctld_as_ctrl(void)
{
	slurm_mutex_lock(&trigger_mutex);
	trigger_bu_ctld_as_ctrl = true;
	slurm_mutex_unlock(&trigger_mutex);
}

extern void trigger_primary_dbd_fail(void)
{
	slurm_mutex_lock(&trigger_mutex);
	if (dbd_failure != 1) {
		trigger_pri_dbd_fail = true;
		dbd_failure = 1;
	}
	slurm_mutex_unlock(&trigger_mutex);
}

extern void trigger_primary_dbd_res_op(void)
{
	slurm_mutex_lock(&trigger_mutex);
	trigger_pri_dbd_res_op = true;
	dbd_failure = 0;
	slurm_mutex_unlock(&trigger_mutex);
}

extern void trigger_primary_db_fail(void)
{
	slurm_mutex_lock(&trigger_mutex);
	if (db_failure != 1) {
		trigger_pri_db_fail = true;
		db_failure = 1;
	}
	slurm_mutex_unlock(&trigger_mutex);
}

extern void trigger_primary_db_res_op(void)
{
	slurm_mutex_lock(&trigger_mutex);
		trigger_pri_db_res_op = true;
		db_failure = 0;
	slurm_mutex_unlock(&trigger_mutex);
}

extern void trigger_burst_buffer(void)
{
	slurm_mutex_lock(&trigger_mutex);
	trigger_bb_error = true;
	slurm_mutex_unlock(&trigger_mutex);
}

static void _dump_trigger_state(trig_mgr_info_t *trig_ptr, buf_t *buffer)
{
	/* write trigger pull state flags */
	pack8(ctld_failure,    buffer);
	pack8(bu_ctld_failure, buffer);
	pack8(dbd_failure,     buffer);
	pack8(db_failure,      buffer);

	pack16   (trig_ptr->flags,     buffer);
	pack32   (trig_ptr->trig_id,   buffer);
	pack16   (trig_ptr->res_type,  buffer);
	packstr  (trig_ptr->orig_res_id, buffer);  /* restores res_id too */
	/* rebuild nodes_bitmap as needed from res_id */
	/* rebuild job_id as needed from res_id */
	pack32   (trig_ptr->trig_type, buffer);
	pack_time(trig_ptr->orig_time, buffer);    /* restores trig_time too */
	pack32   (trig_ptr->user_id,   buffer);
	pack32   (trig_ptr->group_id,  buffer);
	packstr  (trig_ptr->program,   buffer);
	pack8    (trig_ptr->state,     buffer);
}

static int _load_trigger_state(buf_t *buffer, uint16_t protocol_version)
{
	trig_mgr_info_t *trig_ptr;

	xassert(verify_lock(JOB_LOCK, READ_LOCK));

	trig_ptr = xmalloc(sizeof(trig_mgr_info_t));

	if (protocol_version >= SLURM_MIN_PROTOCOL_VERSION) {
		/* restore trigger pull state flags */
		safe_unpack8(&ctld_failure, buffer);
		safe_unpack8(&bu_ctld_failure, buffer);
		safe_unpack8(&dbd_failure, buffer);
		safe_unpack8(&db_failure, buffer);

		safe_unpack16   (&trig_ptr->flags,     buffer);
		safe_unpack32   (&trig_ptr->trig_id,   buffer);
		safe_unpack16   (&trig_ptr->res_type,  buffer);
		safe_unpackstr(&trig_ptr->res_id, buffer);
		/* rebuild nodes_bitmap as needed from res_id */
		/* rebuild job_id as needed from res_id */
		safe_unpack32   (&trig_ptr->trig_type, buffer);
		safe_unpack_time(&trig_ptr->trig_time, buffer);
		safe_unpack32   (&trig_ptr->user_id,   buffer);
		safe_unpack32   (&trig_ptr->group_id,  buffer);
		safe_unpackstr(&trig_ptr->program, buffer);
		safe_unpack8    (&trig_ptr->state,     buffer);
	} else {
		error("_load_trigger_state: protocol_version "
		      "%hu not supported", protocol_version);
		goto unpack_error;
	}

	if ((trig_ptr->res_type < TRIGGER_RES_TYPE_JOB)  ||
	    (trig_ptr->res_type > TRIGGER_RES_TYPE_OTHER) ||
	    (trig_ptr->state > 2))
		goto unpack_error;
	if (trig_ptr->res_type == TRIGGER_RES_TYPE_JOB) {
		job_record_t *job_ptr;
		trig_ptr->job_id = (uint32_t) atol(trig_ptr->res_id);
		job_ptr = find_job_record(trig_ptr->job_id);
		if (((trig_ptr->job_id == 0) || (job_ptr == NULL)) &&
		    (trig_ptr->state != 2))
			goto unpack_error;
	} else if (trig_ptr->res_type == TRIGGER_RES_TYPE_NODE) {
		trig_ptr->job_id = 0;
		if ((trig_ptr->res_id != NULL)   &&
		    (trig_ptr->res_id[0] != '*') &&
		    (node_name2bitmap(trig_ptr->res_id, false,
				      &trig_ptr->nodes_bitmap, NULL)))
			goto unpack_error;
	}
	if (trig_ptr->nodes_bitmap)
		trig_ptr->orig_bitmap = bit_copy(trig_ptr->nodes_bitmap);
	if (trig_ptr->res_id)
		trig_ptr->orig_res_id = xstrdup(trig_ptr->res_id);
	trig_ptr->orig_time = trig_ptr->trig_time;

	slurm_mutex_lock(&trigger_mutex);
	if (trigger_list == NULL)
		trigger_list = list_create(_trig_del);
	list_append(trigger_list, trig_ptr);
	next_trigger_id = MAX(next_trigger_id, trig_ptr->trig_id + 1);
	slurm_mutex_unlock(&trigger_mutex);

	return SLURM_SUCCESS;

unpack_error:
	error("Incomplete trigger record");
	xfree(trig_ptr->res_id);
	xfree(trig_ptr->program);
	FREE_NULL_BITMAP(trig_ptr->nodes_bitmap);
	xfree(trig_ptr);
	return SLURM_ERROR;
}

extern int trigger_state_save(void)
{
	/* Save high-water mark to avoid buffer growth with copies */
	static uint32_t high_buffer_size = (1024 * 1024);
	int error_code = 0;
	buf_t *buffer = init_buf(high_buffer_size);
	list_itr_t *trig_iter;
	trig_mgr_info_t *trig_in;

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

	/* write individual trigger records */
	slurm_mutex_lock(&trigger_mutex);
	if (trigger_list == NULL)
		trigger_list = list_create(_trig_del);

	trig_iter = list_iterator_create(trigger_list);
	while ((trig_in = list_next(trig_iter)))
		_dump_trigger_state(trig_in, buffer);
	list_iterator_destroy(trig_iter);
	slurm_mutex_unlock(&trigger_mutex);

	error_code = save_buf_to_state("trigger_state", buffer,
				       &high_buffer_size);

	FREE_NULL_BUFFER(buffer);
	return error_code;
}

extern void trigger_state_restore(void)
{
	uint16_t protocol_version = NO_VAL16;
	int trigger_cnt = 0;
	char *state_file;
	buf_t *buffer;
	time_t buf_time;
	char *ver_str = NULL;

	/* read the file */
	xassert(verify_lock(CONF_LOCK, READ_LOCK));

	if (!(buffer = state_save_open("trigger_state", &state_file))) {
		if ((clustername_existed == 1) && (!ignore_state_errors))
			fatal("No trigger state file (%s) to recover",
			      state_file);
		info("No trigger state file (%s) to recover", state_file);
		xfree(state_file);
		return;
	}
	xfree(state_file);

	safe_unpackstr(&ver_str, buffer);
	if (ver_str && !xstrcmp(ver_str, TRIGGER_STATE_VERSION))
		safe_unpack16(&protocol_version, buffer);

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

	safe_unpack_time(&buf_time, buffer);
	if (trigger_list)
		list_flush(trigger_list);
	while (remaining_buf(buffer) > 0) {
		if (_load_trigger_state(buffer, protocol_version) !=
		    SLURM_SUCCESS)
			goto unpack_error;
		trigger_cnt++;
	}
	goto fini;

unpack_error:
	if (!ignore_state_errors)
		fatal("Incomplete trigger data checkpoint file, start with '-i' to ignore this. Warning: using -i will lose the data that can't be recovered.");
	error("Incomplete trigger data checkpoint file");
fini:	verbose("State of %d triggers recovered", trigger_cnt);
	FREE_NULL_BUFFER(buffer);
}

/* Test if the event has been triggered, change trigger state as needed */
static void _trigger_job_event(trig_mgr_info_t *trig_in, time_t now)
{
	job_record_t *job_ptr;
	xassert(verify_lock(JOB_LOCK, READ_LOCK));

	job_ptr = find_job_record(trig_in->job_id);

	if ((trig_in->trig_type & TRIGGER_TYPE_FINI) &&
	    ((job_ptr == NULL) || (IS_JOB_COMPLETED(job_ptr)))) {
		trig_in->state = 1;
		trig_in->trig_time = now + (trig_in->trig_time - 0x8000);
		log_flag(TRIGGERS, "trigger[%u] event for job %u fini",
			 trig_in->trig_id, trig_in->job_id);
		return;
	}

	if (job_ptr == NULL) {
		log_flag(TRIGGERS, "trigger[%u] for defunct job %u",
			 trig_in->trig_id, trig_in->job_id);
		trig_in->state = 2;
		trig_in->trig_time = now;
		return;
	}

	if (!IS_JOB_PENDING(job_ptr) &&
	    (trig_in->trig_type & TRIGGER_TYPE_TIME)) {
		long rem_time = (job_ptr->end_time - now);
		if (rem_time <= (0x8000 - trig_in->trig_time)) {
			trig_in->state = 1;
			trig_in->trig_time = now;
			log_flag(TRIGGERS, "trigger[%u] for job %u time",
				 trig_in->trig_id, trig_in->job_id);
			return;
		}
	}

	if (trig_in->trig_type & TRIGGER_TYPE_DOWN) {
		if (trigger_down_nodes_bitmap &&
		    bit_overlap_any(job_ptr->node_bitmap,
				    trigger_down_nodes_bitmap)) {
			log_flag(TRIGGERS, "trigger[%u] for job %u down",
				 trig_in->trig_id, trig_in->job_id);
			trig_in->state = 1;
			trig_in->trig_time = now +
					    (trig_in->trig_time - 0x8000);
			return;
		}
	}

	if (trig_in->trig_type & TRIGGER_TYPE_FAIL) {
		if (trigger_fail_nodes_bitmap &&
		    bit_overlap_any(job_ptr->node_bitmap,
				    trigger_fail_nodes_bitmap)) {
			log_flag(TRIGGERS, "trigger[%u] for job %u node fail",
				 trig_in->trig_id, trig_in->job_id);
			trig_in->state = 1;
			trig_in->trig_time = now +
					     (trig_in->trig_time - 0x8000);
			return;
		}
	}

	if (trig_in->trig_type & TRIGGER_TYPE_UP) {
		if (trigger_up_nodes_bitmap &&
		    bit_overlap_any(job_ptr->node_bitmap,
				    trigger_up_nodes_bitmap)) {
			trig_in->state = 1;
			trig_in->trig_time = now +
					    (0x8000 - trig_in->trig_time);
			log_flag(TRIGGERS, "trigger[%u] for job %u up",
				 trig_in->trig_id, trig_in->job_id);
			return;
		}
	}
}

static void _trigger_other_event(trig_mgr_info_t *trig_in, time_t now)
{
	if ((trig_in->trig_type & TRIGGER_TYPE_BURST_BUFFER) &&
	    trigger_bb_error) {
		trig_in->state = 1;
		trig_in->trig_time = now;
		log_flag(TRIGGERS, "trigger[%u] for burst buffer",
			 trig_in->trig_id);
		return;
	}
}

static void _trigger_node_event(trig_mgr_info_t *trig_in, time_t now)
{
	xassert(verify_lock(NODE_LOCK, READ_LOCK));

	if ((trig_in->trig_type & TRIGGER_TYPE_DOWN) &&
	    trigger_down_nodes_bitmap                &&
	    (bit_ffs(trigger_down_nodes_bitmap) != -1)) {
		if (trig_in->nodes_bitmap == NULL) {	/* all nodes */
			xfree(trig_in->res_id);
			trig_in->res_id = bitmap2node_name(
					  trigger_down_nodes_bitmap);
			trig_in->state = 1;
		} else if (bit_overlap_any(trig_in->nodes_bitmap,
				           trigger_down_nodes_bitmap)) {
			bit_and(trig_in->nodes_bitmap,
				trigger_down_nodes_bitmap);
			xfree(trig_in->res_id);
			trig_in->res_id = bitmap2node_name(
					 trig_in->nodes_bitmap);
			trig_in->state = 1;
		}
		if (trig_in->state == 1) {
			trig_in->trig_time = now +
					     (trig_in->trig_time - 0x8000);
			log_flag(TRIGGERS, "trigger[%u] for node %s down",
				 trig_in->trig_id, trig_in->res_id);
			return;
		}
	}

	if ((trig_in->trig_type & TRIGGER_TYPE_DRAINED) &&
	    trigger_drained_nodes_bitmap                &&
	    (bit_ffs(trigger_drained_nodes_bitmap) != -1)) {
		if (trig_in->nodes_bitmap == NULL) {	/* all nodes */
			xfree(trig_in->res_id);
			trig_in->res_id = bitmap2node_name(
					  trigger_drained_nodes_bitmap);
			trig_in->state = 1;
		} else if (bit_overlap_any(trig_in->nodes_bitmap,
				           trigger_drained_nodes_bitmap)) {
			bit_and(trig_in->nodes_bitmap,
				trigger_drained_nodes_bitmap);
			xfree(trig_in->res_id);
			trig_in->res_id = bitmap2node_name(
					  trig_in->nodes_bitmap);
			trig_in->state = 1;
		}
		if (trig_in->state == 1) {
			trig_in->trig_time = now +
					     (trig_in->trig_time - 0x8000);
			log_flag(TRIGGERS, "trigger[%u] for node %s drained",
				 trig_in->trig_id, trig_in->res_id);
			return;
		}
	}

	if ((trig_in->trig_type & TRIGGER_TYPE_FAIL) &&
	    trigger_fail_nodes_bitmap                &&
	    (bit_ffs(trigger_fail_nodes_bitmap) != -1)) {
		if (trig_in->nodes_bitmap == NULL) {	/* all nodes */
			xfree(trig_in->res_id);
			trig_in->res_id = bitmap2node_name(
					  trigger_fail_nodes_bitmap);
			trig_in->state = 1;
		} else if (bit_overlap_any(trig_in->nodes_bitmap,
					   trigger_fail_nodes_bitmap)) {
			bit_and(trig_in->nodes_bitmap,
				trigger_fail_nodes_bitmap);
			xfree(trig_in->res_id);
			trig_in->res_id = bitmap2node_name(
					  trig_in->nodes_bitmap);
			trig_in->state = 1;
		}
		if (trig_in->state == 1) {
			trig_in->trig_time = now +
					     (trig_in->trig_time - 0x8000);
			log_flag(TRIGGERS, "trigger[%u] for node %s fail",
				 trig_in->trig_id, trig_in->res_id);
			return;
		}
	}

	if (trig_in->trig_type & TRIGGER_TYPE_IDLE) {
		/* We need to determine which (if any) of these
		 * nodes have been idle for at least the offset time */
		time_t min_idle = now - (trig_in->trig_time - 0x8000);
		int i;
		node_record_t *node_ptr;
		bitstr_t *trigger_idle_node_bitmap;

		trigger_idle_node_bitmap = bit_alloc(node_record_count);
		for (i = 0; (node_ptr = next_node(&i)); i++) {
			if (!IS_NODE_IDLE(node_ptr) ||
			    (node_ptr->last_busy > min_idle))
				continue;
			bit_set(trigger_idle_node_bitmap, node_ptr->index);
		}
		if (trig_in->nodes_bitmap == NULL) {    /* all nodes */
			xfree(trig_in->res_id);
			trig_in->res_id = bitmap2node_name(
					  trigger_idle_node_bitmap);
			trig_in->state = 1;
		} else if (bit_overlap_any(trig_in->nodes_bitmap,
					   trigger_idle_node_bitmap)) {
			bit_and(trig_in->nodes_bitmap,
				trigger_idle_node_bitmap);
			xfree(trig_in->res_id);
			trig_in->res_id = bitmap2node_name(
					  trig_in->nodes_bitmap);
			trig_in->state = 1;
		}
		FREE_NULL_BITMAP(trigger_idle_node_bitmap);
		if (trig_in->state == 1) {
			trig_in->trig_time = now;
			log_flag(TRIGGERS, "trigger[%u] for node %s idle",
				 trig_in->trig_id, trig_in->res_id);
			return;
		}
	}

	if ((trig_in->trig_type & TRIGGER_TYPE_UP) &&
	    trigger_up_nodes_bitmap                &&
	    (bit_ffs(trigger_up_nodes_bitmap) != -1)) {
		if (trig_in->nodes_bitmap == NULL) {	/* all nodes */
			xfree(trig_in->res_id);
			trig_in->res_id = bitmap2node_name(
					  trigger_up_nodes_bitmap);
			trig_in->state = 1;
		} else if (bit_overlap_any(trig_in->nodes_bitmap,
					   trigger_up_nodes_bitmap)) {
			bit_and(trig_in->nodes_bitmap,
				trigger_up_nodes_bitmap);
			xfree(trig_in->res_id);
			trig_in->res_id = bitmap2node_name(
					  trig_in->nodes_bitmap);
			trig_in->state = 1;
		}
		if (trig_in->state == 1) {
			trig_in->trig_time = now +
					     (trig_in->trig_time - 0x8000);
			log_flag(TRIGGERS, "trigger[%u] for node %s up",
				 trig_in->trig_id, trig_in->res_id);
			return;
		}
	}

	if ((trig_in->trig_type & TRIGGER_TYPE_DRAINING) &&
	    trigger_draining_nodes_bitmap &&
	    (bit_ffs(trigger_draining_nodes_bitmap) != -1)) {
		if (!trig_in->nodes_bitmap) { /* all nodes */
			xfree(trig_in->res_id);
			trig_in->res_id =
				bitmap2node_name(trigger_draining_nodes_bitmap);
			trig_in->state = 1;
		} else if (bit_overlap_any(trig_in->nodes_bitmap,
					   trigger_draining_nodes_bitmap)) {
			bit_and(trig_in->nodes_bitmap,
				trigger_draining_nodes_bitmap);
			xfree(trig_in->res_id);
			trig_in->res_id =
				bitmap2node_name(trig_in->nodes_bitmap);
			trig_in->state = 1;
		}
		if (trig_in->state == 1) {
			trig_in->trig_time =
				now + (trig_in->trig_time - 0x8000);
			log_flag(TRIGGERS, "trigger[%u] for node %s draining",
				 trig_in->trig_id, trig_in->res_id);
			return;
		}
	}

	if ((trig_in->trig_type & TRIGGER_TYPE_RESUME) &&
	    trigger_resume_nodes_bitmap &&
	    (bit_ffs(trigger_resume_nodes_bitmap) != -1)) {
		if (!trig_in->nodes_bitmap) { /* all nodes */
			xfree(trig_in->res_id);
			trig_in->res_id =
				bitmap2node_name(trigger_resume_nodes_bitmap);
			trig_in->state = 1;
		} else if (bit_overlap_any(trig_in->nodes_bitmap,
					   trigger_resume_nodes_bitmap)) {
			bit_and(trig_in->nodes_bitmap,
				trigger_resume_nodes_bitmap);
			xfree(trig_in->res_id);
			trig_in->res_id =
				bitmap2node_name(trig_in->nodes_bitmap);
			trig_in->state = 1;
		}
		if (trig_in->state == 1) {
			trig_in->trig_time =
				now + (trig_in->trig_time - 0x8000);
			log_flag(TRIGGERS, "trigger[%u] for node %s resume",
				 trig_in->trig_id, trig_in->res_id);
			return;
		}
	}

	if ((trig_in->trig_type & TRIGGER_TYPE_RECONFIG) &&
	    trigger_node_reconfig) {
		trig_in->state = 1;
		trig_in->trig_time = now + (trig_in->trig_time - 0x8000);
		xfree(trig_in->res_id);
		trig_in->res_id = xstrdup("reconfig");
		log_flag(TRIGGERS, "trigger[%u] for reconfig",
			 trig_in->trig_id);
		return;
	}
}

static void _trigger_slurmctld_event(trig_mgr_info_t *trig_in, time_t now)
{
	if ((trig_in->trig_type & TRIGGER_TYPE_PRI_CTLD_FAIL) &&
	    trigger_pri_ctld_fail) {
		trig_in->state = 1;
		trig_in->trig_time = now + (trig_in->trig_time - 0x8000);
		xfree(trig_in->res_id);
		trig_in->res_id = xstrdup("primary_slurmctld_failure");
		log_flag(TRIGGERS, "trigger[%u] for primary_slurmctld_failure",
			 trig_in->trig_id);
		return;
	}
	if ((trig_in->trig_type & TRIGGER_TYPE_PRI_CTLD_RES_OP) &&
	    trigger_pri_ctld_res_op) {
		trig_in->state = 1;
		trig_in->trig_time = now + (trig_in->trig_time - 0x8000);
		xfree(trig_in->res_id);
		trig_in->res_id =
			xstrdup("primary_slurmctld_resumed_operation");
		log_flag(TRIGGERS, "trigger[%u] for primary_slurmctld_resumed_operation",
			 trig_in->trig_id);
		return;
	}
	if ((trig_in->trig_type & TRIGGER_TYPE_PRI_CTLD_RES_CTRL) &&
	    trigger_pri_ctld_res_ctrl) {
		trig_in->state = 1;
		trig_in->trig_time = now + (trig_in->trig_time - 0x8000);
		xfree(trig_in->res_id);
		trig_in->res_id = xstrdup("primary_slurmctld_resumed_control");
		log_flag(TRIGGERS, "trigger[%u] for primary_slurmctld_resumed_control",
			 trig_in->trig_id);
		return;
	}
	if ((trig_in->trig_type & TRIGGER_TYPE_PRI_CTLD_ACCT_FULL) &&
	    trigger_pri_ctld_acct_buffer_full) {
		trig_in->state = 1;
		trig_in->trig_time = now + (trig_in->trig_time - 0x8000);
		xfree(trig_in->res_id);
		trig_in->res_id = xstrdup("primary_slurmctld_acct_buffer_full");
		log_flag(TRIGGERS, "trigger[%u] for primary_slurmctld_acct_buffer_full",
			 trig_in->trig_id);
		return;
	}
	if ((trig_in->trig_type & TRIGGER_TYPE_BU_CTLD_FAIL) &&
	    trigger_bu_ctld_fail) {
		trig_in->state = 1;
		trig_in->trig_time = now + (trig_in->trig_time - 0x8000);
		xfree(trig_in->res_id);
		trig_in->res_id = xstrdup("backup_slurmctld_failure");
		log_flag(TRIGGERS, "trigger[%u] for backup_slurmctld_failure",
			 trig_in->trig_id);
		return;
	}
	if ((trig_in->trig_type & TRIGGER_TYPE_BU_CTLD_RES_OP) &&
	    trigger_bu_ctld_res_op) {
		trig_in->state = 1;
		trig_in->trig_time = now + (trig_in->trig_time - 0x8000);
		xfree(trig_in->res_id);
		trig_in->res_id = xstrdup("backup_slurmctld_resumed_operation");
		log_flag(TRIGGERS, "trigger[%u] for backup_slurmctld_resumed_operation",
			 trig_in->trig_id);
		return;
	}
	if ((trig_in->trig_type & TRIGGER_TYPE_BU_CTLD_AS_CTRL) &&
	    trigger_bu_ctld_as_ctrl) {
		trig_in->state = 1;
		trig_in->trig_time = now + (trig_in->trig_time - 0x8000);
		xfree(trig_in->res_id);
		trig_in->res_id = xstrdup("backup_slurmctld_assumed_control");
		log_flag(TRIGGERS, "trigger[%u] for bu_slurmctld_assumed_control",
			 trig_in->trig_id);
		return;
	}
}

static void _trigger_slurmdbd_event(trig_mgr_info_t *trig_in, time_t now)
{
	if ((trig_in->trig_type & TRIGGER_TYPE_PRI_DBD_FAIL) &&
	    trigger_pri_dbd_fail) {
		trig_in->state = 1;
		trig_in->trig_time = now + (trig_in->trig_time - 0x8000);
		xfree(trig_in->res_id);
		trig_in->res_id = xstrdup("primary_slurmdbd_failure");
		log_flag(TRIGGERS, "trigger[%u] for primary_slurmcdbd_failure",
			 trig_in->trig_id);
	}
	if ((trig_in->trig_type & TRIGGER_TYPE_PRI_DBD_RES_OP) &&
	    trigger_pri_dbd_res_op) {
		trig_in->state = 1;
		trig_in->trig_time = now + (trig_in->trig_time - 0x8000);
		xfree(trig_in->res_id);
		trig_in->res_id = xstrdup("primary_slurmdbd_resumed_operation");
		log_flag(TRIGGERS, "trigger[%u] for primary_slurmdbd_resumed_operation",
			 trig_in->trig_id);
		return;
	}
}

static void _trigger_database_event(trig_mgr_info_t *trig_in, time_t now)
{
	if ((trig_in->trig_type & TRIGGER_TYPE_PRI_DB_FAIL) &&
	    trigger_pri_db_fail) {
		trig_in->state = 1;
		trig_in->trig_time = now + (trig_in->trig_time - 0x8000);
		xfree(trig_in->res_id);
		trig_in->res_id = xstrdup("primary_database_failure");
		log_flag(TRIGGERS, "trigger[%u] for primary_database_failure",
			 trig_in->trig_id);
		return;
	}
	if ((trig_in->trig_type & TRIGGER_TYPE_PRI_DB_RES_OP) &&
	    trigger_pri_db_res_op) {
		trig_in->state = 1;
		trig_in->trig_time = now + (trig_in->trig_time - 0x8000);
		xfree(trig_in->res_id);
		trig_in->res_id = xstrdup("primary_database_resumed_operation");
		log_flag(TRIGGERS, "trigger[%u] for primary_database_resumed_operation",
			 trig_in->trig_id);
		return;
	}
}

/* Ideally we would use the existing proctrack plugin to prevent any
 * processes from escaping our control, but that plugin is tied
 * to various slurmd data structures. We just the process group ID
 * to kill the spawned program after MAX_PROG_TIME. Since triggers are
 * meant primarily for system administrators rather than users, this
 * may be sufficient. */
static void _trigger_run_program(trig_mgr_info_t *trig_in)
{
	char *tmp, *save_ptr = NULL, *tok;
	char *program, *args[64];
	char *pname, *uname;
	uid_t uid;
	gid_t gid;
	pid_t child_pid;
	int i;

	if (!_validate_trigger(trig_in))
		return;

	tmp = xstrdup(trig_in->program);
	tok = strtok_r(trig_in->program, " ", &save_ptr);
	program = xstrdup(tok);
	pname = strrchr(program, '/');
	if (pname == NULL)
		pname = program;
	else
		pname++;
	args[0] = xstrdup(pname);
	for (i = 1; i < 63; i++) {
		tok = strtok_r(NULL, " ", &save_ptr);
		if (!tok) {
			args[i] = xstrdup(trig_in->res_id);
			break;
		}
		args[i] = xstrdup(tok);
	}
	for (i++; i < 64; i++)
		args[i] = NULL;
	xfree(tmp);

	uid = trig_in->user_id;
	gid = trig_in->group_id;
	uname = uid_to_string(uid);

	child_pid = fork();
	if (child_pid > 0) {
		trig_in->child_pid = child_pid;
	} else if (child_pid == 0) {
		bool run_as_self = (uid == slurm_conf.slurm_user_id);
		closeall(0);
		setpgid(0, 0);
		setsid();
		if ((initgroups(uname, gid) == -1) && !run_as_self) {
			error("trigger: initgroups: %m");
			exit(1);
		}
		if ((setgid(gid) == -1) && !run_as_self){
			error("trigger: setgid: %m");
			exit(1);
		}
		if ((setresuid(uid, uid, -1) == -1) && !run_as_self) {
			error("trigger: setresuid: %m");
			exit(1);
		}
		execv(program, args);
		exit(1);
	} else {
		error("fork: %m");
	}
	xfree(uname);
	xfree(program);
	for (i = 0; i < 64; i++)
		xfree(args[i]);
}

static void _clear_event_triggers(void)
{
	if (trigger_down_nodes_bitmap)
		bit_clear_all(trigger_down_nodes_bitmap);
	if (trigger_drained_nodes_bitmap)
		bit_clear_all(trigger_drained_nodes_bitmap);
	if (trigger_fail_nodes_bitmap)
		bit_clear_all(trigger_fail_nodes_bitmap);
	if (trigger_up_nodes_bitmap)
		bit_clear_all(trigger_up_nodes_bitmap);
	if (trigger_draining_nodes_bitmap)
		bit_clear_all(trigger_draining_nodes_bitmap);
	if (trigger_resume_nodes_bitmap)
		bit_clear_all(trigger_resume_nodes_bitmap);
	trigger_node_reconfig = false;
	trigger_bb_error = false;
	trigger_pri_ctld_fail = false;
	trigger_pri_ctld_res_op = false;
	trigger_pri_ctld_res_ctrl = false;
	trigger_pri_ctld_acct_buffer_full = false;
	trigger_bu_ctld_fail = false;
	trigger_bu_ctld_res_op = false;
	trigger_bu_ctld_as_ctrl = false;
	trigger_pri_dbd_fail = false;
	trigger_pri_dbd_res_op = false;
	trigger_pri_db_fail = false;
	trigger_pri_db_res_op = false;
}

/* Make a copy of a trigger and prepend it on our list */
static void _trigger_clone(trig_mgr_info_t *trig_in)
{
	trig_mgr_info_t *trig_add;

	trig_add = xmalloc(sizeof(trig_mgr_info_t));
	trig_add->flags     = trig_in->flags;
	trig_add->trig_id   = trig_in->trig_id;
	trig_add->res_type  = trig_in->res_type;
	if (trig_in->orig_res_id) {
		trig_add->res_id = xstrdup(trig_in->orig_res_id);
		trig_add->orig_res_id = xstrdup(trig_in->orig_res_id);
	}
	if (trig_in->orig_bitmap) {
		trig_add->nodes_bitmap = bit_copy(trig_in->orig_bitmap);
		trig_add->orig_bitmap  = bit_copy(trig_in->orig_bitmap);
	}
	trig_add->job_id    = trig_in->job_id;
	trig_add->trig_type = trig_in->trig_type;
	trig_add->trig_time = trig_in->orig_time;
	trig_add->orig_time = trig_in->orig_time;
	trig_add->user_id   = trig_in->user_id;
	trig_add->group_id  = trig_in->group_id;
	trig_add->program   = xstrdup(trig_in->program);;
	list_prepend(trigger_list, trig_add);
}

extern void trigger_process(void)
{
	list_itr_t *trig_iter;
	trig_mgr_info_t *trig_in;
	time_t now = time(NULL);
	bool state_change = false;
	pid_t rc;
	int prog_stat;

	slurm_mutex_lock(&trigger_mutex);
	if (trigger_list == NULL)
		trigger_list = list_create(_trig_del);

	trig_iter = list_iterator_create(trigger_list);
	while ((trig_in = list_next(trig_iter))) {
		if (trig_in->state == 0) {
			if (trig_in->res_type == TRIGGER_RES_TYPE_OTHER)
				_trigger_other_event(trig_in, now);
			else if (trig_in->res_type == TRIGGER_RES_TYPE_JOB)
				_trigger_job_event(trig_in, now);
			else if (trig_in->res_type == TRIGGER_RES_TYPE_NODE)
				_trigger_node_event(trig_in, now);
			else if (trig_in->res_type ==
				 TRIGGER_RES_TYPE_SLURMCTLD)
				_trigger_slurmctld_event(trig_in, now);
			else if (trig_in->res_type ==
				 TRIGGER_RES_TYPE_SLURMDBD)
				_trigger_slurmdbd_event(trig_in, now);
			else if (trig_in->res_type ==
				 TRIGGER_RES_TYPE_DATABASE)
			 	_trigger_database_event(trig_in, now);
		}
		if ((trig_in->state == 1) &&
		    (trig_in->trig_time <= now)) {
			log_flag(TRIGGERS, "launching program for trigger[%u]: uid=%u gid=%u program=%s arg=%s",
				 trig_in->trig_id, trig_in->user_id,
				 trig_in->group_id, trig_in->program,
				 trig_in->res_id);
			if (trig_in->flags & TRIGGER_FLAG_PERM) {
				_trigger_clone(trig_in);
			}
			trig_in->state = 2;
			trig_in->trig_time = now;
			state_change = true;
			_trigger_run_program(trig_in);
		} else if ((trig_in->state == 2) &&
			   (difftime(now, trig_in->trig_time) >
			    MAX_PROG_TIME)) {
			if (trig_in->child_pid != 0) {
				killpg(trig_in->child_pid, SIGKILL);
				rc = waitpid(trig_in->child_pid, &prog_stat,
					     WNOHANG);
				if ((rc > 0) && prog_stat) {
					info("trigger uid=%u type=%s:%s "
					     "exit=%u:%u",
					     trig_in->user_id,
					     trigger_res_type(trig_in->res_type),
					     trigger_type(trig_in->trig_type),
					     WIFEXITED(prog_stat),
					     WTERMSIG(prog_stat));
				}
				if ((rc == trig_in->child_pid) ||
				    ((rc == -1) && (errno == ECHILD)))
					trig_in->child_pid = 0;
			}

			if (trig_in->child_pid == 0) {
				log_flag(TRIGGERS, "purging trigger[%u]",
					 trig_in->trig_id);
				list_delete_item(trig_iter);
				state_change = true;
			}
		} else if (trig_in->state == 2) {
			/* Eliminate zombie processes right away.
			 * Purge trigger entry above MAX_PROG_TIME later */
			rc = waitpid(trig_in->child_pid, &prog_stat, WNOHANG);
			if ((rc > 0) && prog_stat) {
				info("trigger uid=%u type=%s:%s exit=%u:%u",
				     trig_in->user_id,
				     trigger_res_type(trig_in->res_type),
				     trigger_type(trig_in->trig_type),
				     WIFEXITED(prog_stat),
				     WTERMSIG(prog_stat));
			}
			if ((rc == trig_in->child_pid) ||
			    ((rc == -1) && (errno == ECHILD)))
				trig_in->child_pid = 0;
		}
	}
	list_iterator_destroy(trig_iter);
	_clear_event_triggers();
	slurm_mutex_unlock(&trigger_mutex);
	if (state_change)
		schedule_trigger_save();
}

/* Free all allocated memory */
extern void trigger_fini(void)
{
	FREE_NULL_LIST(trigger_list);
	FREE_NULL_BITMAP(trigger_down_nodes_bitmap);
	FREE_NULL_BITMAP(trigger_drained_nodes_bitmap);
	FREE_NULL_BITMAP(trigger_fail_nodes_bitmap);
	FREE_NULL_BITMAP(trigger_up_nodes_bitmap);
	FREE_NULL_BITMAP(trigger_draining_nodes_bitmap);
	FREE_NULL_BITMAP(trigger_resume_nodes_bitmap);
}
