/*
 * Copyright (C) 1994-2021 Altair Engineering, Inc.
 * For more information, contact Altair at www.altair.com.
 *
 * This file is part of both the OpenPBS software ("OpenPBS")
 * and the PBS Professional ("PBS Pro") software.
 *
 * Open Source License Information:
 *
 * OpenPBS is free software. You can redistribute it and/or modify it under
 * the terms of the GNU Affero General Public License as published by the
 * Free Software Foundation, either version 3 of the License, or (at your
 * option) any later version.
 *
 * OpenPBS 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 Affero General Public
 * License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 * Commercial License Information:
 *
 * PBS Pro is commercially licensed software that shares a common core with
 * the OpenPBS software.  For a copy of the commercial license terms and
 * conditions, go to: (http://www.pbspro.com/agreement.html) or contact the
 * Altair Legal Department.
 *
 * Altair's dual-license business model allows companies, individuals, and
 * organizations to create proprietary derivative works of OpenPBS and
 * distribute them - whether embedded or bundled with other software -
 * under a commercial license agreement.
 *
 * Use of Altair's trademarks, including but not limited to "PBS™",
 * "OpenPBS®", "PBS Professional®", and "PBS Pro™" and Altair's logos is
 * subject to Altair's trademark licensing policies.
 */

/**
 * @file    req_register.c
 *
 * @brief
 * 		req_register.c	-	This file hold all the functions dealing with job dependency
 *
 * Included functions are:
 * 	post_run_depend()
 * 	req_register()
 * 	post_doq()
 * 	alter_unreg()
 * 	depend_on_que()
 * 	post_doe()
 * 	depend_on_exec()
 * 	depend_on_term()
 * 	set_depend_hold()
 * 	find_depend()
 * 	make_depend()
 * 	register_dep()
 * 	unregister_dep()
 * 	find_dependjob()
 * 	make_dependjob()
 * 	send_depend_req()
 * 	decode_depend()
 * 	cpy_jobsvr()
 * 	dup_depend()
 * 	encode_depend()
 * 	set_depend()
 * 	comp_depend()
 * 	free_depend()
 * 	build_depend()
 * 	clear_depend()
 * 	del_depend()
 * 	del_depend_job()
 */
#include <pbs_config.h> /* the master config generated by configure */

#include <assert.h>
#include <sys/types.h>
#include <stdlib.h>
#include <ctype.h>
#include <errno.h>
#include <stdio.h>
#include <string.h>
#include <signal.h>
#include "libpbs.h"
#include "list_link.h"
#include "attribute.h"
#include "server_limits.h"
#include "credential.h"
#include "batch_request.h"
#include "job.h"
#include "reservation.h"
#include "queue.h"
#include "server.h"
#include "work_task.h"
#include "pbs_error.h"
#include "log.h"
#include "pbs_nodes.h"
#include "svrfunc.h"
#include "net_connect.h"

/* External functions */

extern int issue_to_svr(char *svr, struct batch_request *, void (*func)(struct work_task *));

/* Local Private Functions */

static void set_depend_hold(job *, attribute *);
static int register_dep(attribute *, struct batch_request *, int, int *);
static int unregister_dep(attribute *, struct batch_request *);
static struct depend *make_depend(int type, attribute *pattr);
static struct depend_job *make_dependjob(struct depend *, char *jobid, char *host);
static void del_depend_job(struct depend_job *pdj);
static int build_depend(attribute *, char *);
static void clear_depend(struct depend *, int type, int exists);
static void del_depend(struct depend *);
static void update_depend(job *, char *, char *, int, int);

/* External Global Data Items */

extern struct server server;
extern char server_name[];
extern char *msg_unkjobid;
extern char *msg_movejob;
extern char *msg_err_malloc;
extern char *msg_illregister;
extern char *msg_registerdel;
extern char *msg_registerrel;
extern char *msg_regrej;
extern char *msg_job_moved;
extern char *msg_depend_runone;
extern char *msg_histdepend;
extern char *msg_historyjobid;

#define DEPEND_ADD 1
#define DEPEND_REMOVE 2
/**
 * @brief
 * 		post_run_depend - this function is called via a work task when a
 *		register dependency "after" is received for a job that is already
 *		running.  We accept the dependency and then turn around and send a
 *		"release" back to the newly registered job to remove its hold.
 *
 * param[in]	pwt	-	work task
 */
static void
post_run_depend(struct work_task *pwt)
{
	job *pjob;

	pjob = (job *) pwt->wt_parm1;
	if (is_jattr_set(pjob, JOB_ATR_depend))
		depend_on_exec(pjob);
	return;
}

/**
 * @brief	A function to add/remove dependency on a given job
 *
 * @param[in]	pjob - Job on which we need perform the operation
 * @param[in]	d_jobid - The job id which needs to be added/removed
 * @param[in]	d_svr
 * @param[in]	op -	operation to perform - DEPEND_ADD/DEPEND_REMOVE
 * @param[in]	type - Dependency type
 *
 * @return	void
 */
static void
update_depend(job *pjob, char *d_jobid, char *d_svr, int op, int type)
{
	job *d_job;
	struct depend *dp;
	struct depend_job *dpj;
	attribute *pattr;

	d_job = find_job(d_jobid);
	if (d_job == NULL)
		return;

	pattr = get_jattr(pjob, JOB_ATR_depend);
	dp = find_depend(type, pattr);
	if (op == DEPEND_ADD) {
		if (dp == NULL) {
			dp = make_depend(JOB_DEPEND_TYPE_RUNONE, pattr);
			if (dp == NULL)
				return;
		}
		dpj = find_dependjob(dp, d_jobid);
		if (dpj)
			return; /* Job dependency already established */
		if (strcmp(pjob->ji_qs.ji_jobid, d_jobid)) {
			dpj = make_dependjob(dp, d_jobid, d_svr);
			post_attr_set(pattr);
			job_save(pjob);
			/* runone dependencies are circular */
			if (type == JOB_DEPEND_TYPE_RUNONE)
				update_depend(d_job, pjob->ji_qs.ji_jobid, d_svr, op, type);
		}
		return;
	} else if (op == DEPEND_REMOVE) {
		if (dp == NULL)
			return;
		dpj = find_dependjob(dp, d_jobid);
		if (dpj == NULL)
			return;
		del_depend_job(dpj);
		if (GET_NEXT(dp->dp_jobs) == 0)
			/* no more dependencies of this type */
			del_depend(dp);

		pattr->at_flags |= ATR_MOD_MCACHE;
		/* runone dependencies are circular */
		if (type == JOB_DEPEND_TYPE_RUNONE)
			update_depend(d_job, pjob->ji_qs.ji_jobid, d_svr, op, type);
		return;
	}
	return;
}

/**
 * @brief
 * 		req_register - process the Register Dependency Request
 * @note
 *		We have an interesting problem here in that the request may well
 *		originate from ourself.  In that case we doen't really reply.
 *
 * @param[in]	preq	-	Register Dependency Request.
 */

void
req_register(struct batch_request *preq)
{
	int made;
	attribute *pattr;
	struct depend *pdep;
	struct depend_job *pdj;
	job *pjob;
	char *ps;
	struct work_task *ptask;
	int rc = 0;
	int revtype;
	int type;
	int is_finished = FALSE;

	/*  make sure request is from a server */

	if (!preq->rq_fromsvr) {
		req_reject(PBSE_IVALREQ, 0, preq);
		return;
	}

	/* find the "parent" job specified in the request */

	if ((pjob = find_job(preq->rq_ind.rq_register.rq_parent)) == NULL) {

		/*
		 * job not found... if server is initializing, it may not
		 * yet recovered, that is not an error.
		 */

		if (get_sattr_long(SVR_ATR_State) != SV_STATE_INIT) {
			log_event(PBSEVENT_DEBUG, PBS_EVENTCLASS_JOB, LOG_INFO,
				  preq->rq_ind.rq_register.rq_parent,
				  msg_unkjobid);
			req_reject(PBSE_UNKJOBID, 0, preq);
		} else {
			reply_ack(preq);
		}
		return;
	}

	if (check_job_state(pjob, JOB_STATE_LTR_FINISHED))
		is_finished = TRUE;

	pattr = get_jattr(pjob, JOB_ATR_depend);
	type = preq->rq_ind.rq_register.rq_dependtype;

	/* more of the server:port fix kludge */

	ps = strchr(preq->rq_ind.rq_register.rq_child, (int) '@');
	if (ps != NULL) {
		(void) strcpy(preq->rq_ind.rq_register.rq_svr, ps + 1);
		*ps = '\0';
	} else {
		(void) strcpy(preq->rq_ind.rq_register.rq_svr, preq->rq_host);
	}

	if (check_job_state(pjob, JOB_STATE_LTR_MOVED)) {
		snprintf(log_buffer, sizeof(log_buffer), "Parent %s%s", msg_movejob, pjob->ji_qs.ji_destin);
		log_event(PBSEVENT_DEBUG | PBSEVENT_SYSTEM | PBSEVENT_ERROR,
			  PBS_EVENTCLASS_REQUEST, LOG_INFO,
			  preq->rq_ind.rq_register.rq_child, log_buffer);
		req_reject(PBSE_JOB_MOVED, 0, preq);
		return;
	}
	switch (preq->rq_ind.rq_register.rq_op) {

			/*
			 * Register a dependency
			 */

		case JOB_DEPEND_OP_REGISTER:
			switch (type) {

				case JOB_DEPEND_TYPE_AFTERSTART:
					if (is_finished == TRUE) {
						rc = PBSE_HISTJOBID;
						break;
					}
					if (get_job_substate(pjob) >= JOB_SUBSTATE_RUNNING) {
						/* Job already running, setup task to send
						 * release back to child and continue with
						 * registration process.
						 */
						ptask = set_task(WORK_Immed, 0, post_run_depend,
								 (void *) pjob);
						if (ptask)
							append_link(&pjob->ji_svrtask,
								    &ptask->wt_linkobj, ptask);
					}
					/* fall through to complete registration */
				case JOB_DEPEND_TYPE_AFTERANY:
				case JOB_DEPEND_TYPE_AFTEROK:
				case JOB_DEPEND_TYPE_AFTERNOTOK:
					/* If the job has already finished, no need to add after dependency */
					if (is_finished == TRUE) {
						if (((type == JOB_DEPEND_TYPE_AFTERNOTOK) && (pjob->ji_qs.ji_un.ji_exect.ji_exitstat == 0)) ||
						    ((type == JOB_DEPEND_TYPE_AFTEROK) && (pjob->ji_qs.ji_un.ji_exect.ji_exitstat != 0)))
							rc = PBSE_HISTDEPEND;
						else
							rc = PBSE_HISTJOBID;
						break;
					}
					rc = register_dep(pattr, preq, type, &made);
					break;
				case JOB_DEPEND_TYPE_BEFORESTART:
				case JOB_DEPEND_TYPE_BEFOREANY:
				case JOB_DEPEND_TYPE_BEFOREOK:
				case JOB_DEPEND_TYPE_BEFORENOTOK:

					/* There is no need to put before dependency on a finished job */
					if (is_finished == TRUE) {
						rc = PBSE_HISTDEPEND;
						break;
					}
					/*
					 * Check job owner for permission, use the real
					 * job owner, not the sending server's name.
					 */

					(void) strcpy(preq->rq_user,
						      preq->rq_ind.rq_register.rq_owner);
					if (svr_chk_owner(preq, pjob)) {
						rc = PBSE_PERM; /* not same user */
					} else {
						/* ok owner, see if job has "on" */
						pdep = find_depend(JOB_DEPEND_TYPE_ON, pattr);
						if (pdep == 0) {
							/* on "on", see if child already registered */
							revtype = type ^ (JOB_DEPEND_TYPE_BEFORESTART -
									  JOB_DEPEND_TYPE_AFTERSTART);
							pdep = find_depend(revtype, pattr);
							if (pdep == 0) {
								/* no "on" and no prior - return error */
								rc = PBSE_BADDEPEND;
							} else {
								pdj = find_dependjob(pdep, preq->rq_ind.rq_register.rq_child);
								if (pdj) {
									/* has prior register, update it */
									(void) strcpy(pdj->dc_svr, preq->rq_ind.rq_register.rq_svr);
								}
							}
						} else if ((rc = register_dep(pattr, preq, type, &made)) == 0) {
							if (made) { /* first time registered */
								if (--pdep->dp_numexp <= 0)
									del_depend(pdep);
							}
						}
					}
					break;
				case JOB_DEPEND_TYPE_RUNONE:
					/*
					 * Check job owner for permission, use the real
					 * job owner, not the sending server's name.
					 */

					strcpy(preq->rq_user, preq->rq_ind.rq_register.rq_owner);
					if (svr_chk_owner(preq, pjob)) {
						rc = PBSE_PERM; /* not same user */
					} else {
						pdep = find_depend(JOB_DEPEND_TYPE_RUNONE, pattr);
						if (pdep) {
							struct depend_job *dj_iter;
							job *pr_job;
							pr_job = find_job(preq->rq_ind.rq_register.rq_child);
							for (dj_iter = (struct depend_job *) GET_NEXT(pdep->dp_jobs);
							     dj_iter != NULL; dj_iter = (struct depend_job *) GET_NEXT(dj_iter->dc_link))
								update_depend(pr_job, dj_iter->dc_child, dj_iter->dc_svr, DEPEND_ADD, JOB_DEPEND_TYPE_RUNONE);
						}
						update_depend(pjob, preq->rq_ind.rq_register.rq_child, preq->rq_ind.rq_register.rq_svr, DEPEND_ADD, JOB_DEPEND_TYPE_RUNONE);
					}
					break;

				default:
					rc = PBSE_IVALREQ;
					break;
			}
			break;

			/*
			 * Release a dependency so job might run
			 */

		case JOB_DEPEND_OP_RELEASE:
			switch (type) {

				case JOB_DEPEND_TYPE_BEFORESTART:
				case JOB_DEPEND_TYPE_BEFOREANY:
				case JOB_DEPEND_TYPE_BEFOREOK:
				case JOB_DEPEND_TYPE_BEFORENOTOK:

					/* predecessor sent release-reduce "on", */
					/* see if this job can now run 		 */
					type ^= (JOB_DEPEND_TYPE_BEFORESTART -
						 JOB_DEPEND_TYPE_AFTERSTART);
					if ((pdep = find_depend(type, pattr)) != NULL) {
						pdj = find_dependjob(pdep,
								     preq->rq_ind.rq_register.rq_child);
						if (pdj) {
							del_depend_job(pdj);
							pattr->at_flags |= ATR_MOD_MCACHE;
							(void) sprintf(log_buffer, msg_registerrel,
								       preq->rq_ind.rq_register.rq_child);
							log_event(PBSEVENT_JOB, PBS_EVENTCLASS_JOB,
								  LOG_INFO,
								  pjob->ji_qs.ji_jobid, log_buffer);

							if (GET_NEXT(pdep->dp_jobs) == 0) {
								/* no more dependencies of this type */
								del_depend(pdep);
								set_depend_hold(pjob, pattr);
							}
							break;
						}
					}
					rc = PBSE_IVALREQ;
					break;
				case JOB_DEPEND_TYPE_RUNONE:
					pdep = find_depend(JOB_DEPEND_TYPE_RUNONE, pattr);
					if (pdep) {
						struct depend_job *dj_iter;
						job *pr_job;
						pr_job = find_job(preq->rq_ind.rq_register.rq_child);
						if (pr_job) {
							for (dj_iter = (struct depend_job *) GET_NEXT(pdep->dp_jobs);
							     dj_iter != NULL; dj_iter = (struct depend_job *) GET_NEXT(dj_iter->dc_link)) {
								update_depend(pr_job, dj_iter->dc_child, dj_iter->dc_svr, DEPEND_REMOVE, JOB_DEPEND_TYPE_RUNONE);
								log_eventf(PBSEVENT_JOB, PBS_EVENTCLASS_JOB,
									   LOG_INFO, pr_job->ji_qs.ji_jobid, msg_registerrel, dj_iter->dc_child);
							}
						}
					}
					update_depend(pjob, preq->rq_ind.rq_register.rq_parent, preq->rq_ind.rq_register.rq_svr, DEPEND_REMOVE, JOB_DEPEND_TYPE_RUNONE);
					log_eventf(PBSEVENT_JOB, PBS_EVENTCLASS_JOB, LOG_INFO,
						   pjob->ji_qs.ji_jobid, msg_registerrel, preq->rq_ind.rq_register.rq_parent);

					if (GET_NEXT(pdep->dp_jobs) == 0) {
						/* no more dependencies of this type */
						del_depend(pdep);
					}
					break;
			}

			break;

		case JOB_DEPEND_OP_READY:
			rc = PBSE_NOSYNCMSTR;
			break;

		case JOB_DEPEND_OP_DELETE:
			(void) sprintf(log_buffer, msg_registerdel,
				       preq->rq_ind.rq_register.rq_child);
			log_event(PBSEVENT_JOB, PBS_EVENTCLASS_JOB, LOG_INFO,
				  pjob->ji_qs.ji_jobid, log_buffer);
			job_abt(pjob, log_buffer);
			/* Since the job is aborted, we can return here itself */
			reply_ack(preq);
			return;

		case JOB_DEPEND_OP_UNREG:
			unregister_dep(pattr, preq);
			set_depend_hold(pjob, pattr);
			break;

		default:
			sprintf(log_buffer, msg_illregister,
				preq->rq_ind.rq_register.rq_parent);
			log_event(PBSEVENT_DEBUG | PBSEVENT_SYSTEM | PBSEVENT_ERROR,
				  PBS_EVENTCLASS_REQUEST, LOG_INFO,
				  preq->rq_host, log_buffer);
			rc = PBSE_IVALREQ;
			break;
			;
	}

	if (rc) {
		req_reject(rc, 0, preq);
	} else {
		job_save_db(pjob);
		reply_ack(preq);
	}
	return;
}

/**
 * @brief
 * 		post_doq (que not dog) - post request/reply processing for depend_on_que
 *		i.e. the sending of register operations.
 *
 * @param[in]	pwt	-	post request/reply
 */

static void
post_doq(struct work_task *pwt)
{
	struct batch_request *preq = (struct batch_request *) pwt->wt_parm1;
	char *jobid = preq->rq_ind.rq_register.rq_child;
	char *msg;
	job *pjob;
	job *ppjob;
	struct depend_job pparent;
	int rc;

	if (preq->rq_reply.brp_code) {
		/* request was rejected */
		if (preq->rq_reply.brp_code == PBSE_HISTJOBID)
			log_eventf(PBSEVENT_JOB, PBS_EVENTCLASS_JOB, LOG_INFO, jobid,
				   "%s %s, dependency satisfied", preq->rq_ind.rq_register.rq_parent, msg_historyjobid);
		else if (preq->rq_reply.brp_code == PBSE_HISTDEPEND)
			log_eventf(PBSEVENT_JOB, PBS_EVENTCLASS_JOB, LOG_INFO, jobid,
				   "%s %s", preq->rq_ind.rq_register.rq_parent, msg_histdepend);
		else
			log_eventf(PBSEVENT_JOB, PBS_EVENTCLASS_JOB, LOG_INFO, jobid,
				   "%s%s", msg_regrej, preq->rq_ind.rq_register.rq_parent);

		pjob = find_job(jobid);
		if ((msg = pbse_to_txt(preq->rq_reply.brp_code)) != NULL) {
			(void) strcat(log_buffer, " ");
			(void) strcat(log_buffer, msg);
		}
		if (pjob) {
			ppjob = find_job(preq->rq_ind.rq_register.rq_parent);
			if (preq->rq_reply.brp_code == PBSE_JOB_MOVED) {
				/* Creating a separate log buffer because if we end up aborting the submitted job
				 * we don't want to change what goes into accounting log via job_abt
				 */
				char log_msg[LOG_BUF_SIZE];
				snprintf(log_msg, sizeof(log_msg), "%s, %s", msg_job_moved,
					 "sending dependency request to remote server");
				log_event(PBSEVENT_JOB, PBS_EVENTCLASS_JOB, LOG_INFO, jobid, log_msg);
				if (ppjob && (check_job_state(ppjob, JOB_STATE_LTR_MOVED)) && (check_job_substate(ppjob, JOB_SUBSTATE_MOVED))) {
					char *destin;
					/* job destination should be <remote queue>@<remote server> */
					destin = strchr(ppjob->ji_qs.ji_destin, (int) '@');
					if (destin != NULL) {
						strncpy(pparent.dc_child, ppjob->ji_qs.ji_jobid, sizeof(pparent.dc_child));
						destin++;
						strncpy(pparent.dc_svr, destin, sizeof(pparent.dc_svr) - (destin - ppjob->ji_qs.ji_destin));
						rc = send_depend_req(pjob, &pparent, preq->rq_ind.rq_register.rq_dependtype,
								     JOB_DEPEND_OP_REGISTER,
								     SYNC_SCHED_HINT_NULL, post_doq);
						if (rc) {
							snprintf(log_msg, sizeof(log_msg), "%s",
								 "Failed to send dependency request to remote server, aborting job");
							log_event(PBSEVENT_JOB, PBS_EVENTCLASS_JOB, LOG_ERR, jobid, log_msg);
							check_block(pjob, log_buffer);
							job_abt(pjob, log_buffer);
						}
					} else {
						/* Ideally if a job is moved, destination can not be empty */
						/* If we come across an empty destination, abort the job */
						check_block(pjob, log_buffer);
						job_abt(pjob, log_buffer);
					}
				} else {
					check_block(pjob, log_buffer);
					job_abt(pjob, log_buffer);
				}
			} else if (preq->rq_reply.brp_code == PBSE_HISTJOBID) {
				if (ppjob) {
					update_depend(pjob, ppjob->ji_qs.ji_jobid, preq->rq_host, DEPEND_REMOVE, preq->rq_ind.rq_register.rq_dependtype);
					/* check and remove system hold if needed */
					set_depend_hold(pjob, get_jattr(pjob, JOB_ATR_depend));
				}

			} else {
				check_block(pjob, log_buffer);
				job_abt(pjob, log_buffer);
			}
		}
	}

	release_req(pwt);
}

/**
 * @brief
 * 		alter_unreg - if required, unregister dependencies on alter of attribute
 *		This is called from depend_on_que() when it is acting as the at_action
 *		routine for the dependency attribute.
 *
 * @param[in]	pjob	-	pointer to job structure
 * @param[in]	old	-	current job dependency attribure
 * @param[out]	new	-	job dependency attribute after alter
 */

static void
alter_unreg(job *pjob, attribute *old, attribute *new)
{
	struct depend *poldd;
	struct depend *pnewd;
	struct depend_job *oldjd;
	int type;

	for (poldd = (struct depend *) GET_NEXT(old->at_val.at_list);
	     poldd;
	     poldd = (struct depend *) GET_NEXT(poldd->dp_link)) {

		type = poldd->dp_type;
		if (type != JOB_DEPEND_TYPE_ON) {

			pnewd = find_depend(type, new);
			oldjd = (struct depend_job *) GET_NEXT(poldd->dp_jobs);
			while (oldjd) {
				if ((pnewd == 0) ||
				    (find_dependjob(pnewd, oldjd->dc_child) == NULL)) {
					(void) send_depend_req(pjob, oldjd, type,
							       JOB_DEPEND_OP_UNREG,
							       SYNC_SCHED_HINT_NULL,
							       release_req);
				}
				oldjd = (struct depend_job *) GET_NEXT(oldjd->dc_link);
			}
		}
	}
}

/**
 * @brief
 * 		depend_on_que - Perform a series of actions if job has a dependency
 *		that needs action when the job is queued into an execution queue.
 * @par
 *		Called from svr_enquejob() when a job enters an
 *		execution queue.  Also  the at_action routine for the attribute.
 *
 * @param[out]	pattr	-	job dependency attribute after alter
 * @param[in]	pobj	-	pointer to job structure
 * @param[in]	amode	-	"actmode" stands for the type of action
 */

int
depend_on_que(attribute *pattr, void *pobj, int mode)
{
	char *b1, *b2;
	struct depend *pdep;
	struct depend_job *pparent;
	int rc;
	int type;
	job *pjob = (job *) pobj;

	if (((mode != ATR_ACTION_ALTER) && (mode != ATR_ACTION_NOOP)) ||
	    (pjob->ji_qhdr == 0) ||
	    (pjob->ji_qhdr->qu_qs.qu_type != QTYPE_Execution))
		return (0);

	if (mode == ATR_ACTION_ALTER) {
		/* if there are dependencies being removed, unregister them */

		alter_unreg(pjob, get_jattr(pjob, JOB_ATR_depend), pattr);
	}

	/* First set a System hold if required */
	set_depend_hold(pjob, pattr);

	/* Check if there are dependencies that require registering */

	pdep = (struct depend *) GET_NEXT(pattr->at_val.at_list);
	while (pdep) {
		type = pdep->dp_type;
		if (type != JOB_DEPEND_TYPE_ON) {

			pparent = (struct depend_job *) GET_NEXT(pdep->dp_jobs);
			while (pparent) {
				if (((b1 = strchr(pparent->dc_child, (int) '[')) != NULL) &&
				    ((b2 = strchr(pparent->dc_child, (int) ']')) != NULL)) {
					if (b2 != b1 + 1)
						return PBSE_IVALREQ;
				}
				if (strcmp(pparent->dc_child, pjob->ji_qs.ji_jobid) == 0) {
					/* parent and child job ids are the same */
					return PBSE_IVALREQ;
				}

				if (type == JOB_DEPEND_TYPE_RUNONE) {
					job *djob = find_job(pparent->dc_child);
					if (djob == NULL)
						return PBSE_IVALREQ;
					/* hold this job if any of the jobs it is dependent on is
					 * either running or already held because of dependency.
					 * pay special attention to array parent jobs because those jobs
					 * may not be in BEGUN state while a subjob might already be running
					 * this is because JOB_STATE_LTR_BEGUN is set only when a mom reports
					 * session id, but there could be a case that a mom is in the process
					 * of reporting a session id and user submits a dependent job.
					 * To avoid such cases, verify that count of queued subjobs is equal
					 * to the number of subjobs
					 */
					else if (check_job_state(djob, JOB_STATE_LTR_RUNNING) ||
						 check_job_state(djob, JOB_STATE_LTR_BEGUN) ||
						 (djob->ji_ajinfo != NULL &&
						  djob->ji_ajinfo->tkm_ct != djob->ji_ajinfo->tkm_subjsct[JOB_STATE_QUEUED]) ||
						 (check_job_state(djob, JOB_STATE_LTR_HELD) &&
						  check_job_substate(djob, JOB_SUBSTATE_DEPNHOLD))) {
						/* If the dependent job is running or has system hold, then put this job on hold too*/
						set_jattr_b_slim(pjob, JOB_ATR_hold, HOLD_s, INCR);
						svr_setjobstate(pjob, JOB_STATE_LTR_HELD, JOB_SUBSTATE_DEPNHOLD);
					}
				}
				rc = send_depend_req(pjob, pparent, type,
						     JOB_DEPEND_OP_REGISTER,
						     SYNC_SCHED_HINT_NULL, post_doq);
				if (rc)
					return rc;
				pparent = (struct depend_job *) GET_NEXT(pparent->dc_link);
			}
		}
		pdep = (struct depend *) GET_NEXT(pdep->dp_link);
	}

	pdep = find_depend(JOB_DEPEND_TYPE_RUNONE, pattr);
	if (pdep != NULL) {
		/* go through all dependent jobs */
		struct depend_job *f_pparent = (struct depend_job *) GET_NEXT(pdep->dp_jobs);
		if (f_pparent == NULL)
			return (0);
		for (; f_pparent != NULL; f_pparent = (struct depend_job *) GET_NEXT(f_pparent->dc_link)) {
			pjob = find_job(f_pparent->dc_child);
			if (pjob == NULL)
				return (0);
			pparent = (struct depend_job *) GET_NEXT(f_pparent->dc_link);
			for (; pparent != NULL; pparent = (struct depend_job *) GET_NEXT(pparent->dc_link)) {
				if (find_dependjob(find_depend(JOB_DEPEND_TYPE_RUNONE, get_jattr(pjob, JOB_ATR_depend)), pparent->dc_child) == NULL) {
					rc = send_depend_req(pjob, pparent, pdep->dp_type,
							     JOB_DEPEND_OP_REGISTER, SYNC_SCHED_HINT_NULL, post_doq);
					if (rc)
						return (rc);
				}
			}
		}
	}
	return (0);
}

/**
 * @brief
 * 		post_doe - Post (reply) processing of requests processing for depend_on_exec
 *
 * @param[in]	pwt	-	work task containing requests to be processed
 */

static void
post_doe(struct work_task *pwt)
{
	struct batch_request *preq = pwt->wt_parm1;
	char *jobid = preq->rq_ind.rq_register.rq_child;
	attribute *pattr;
	struct depend *pdep;
	struct depend_job *pdj;
	job *pjob;

	pjob = find_job(jobid);
	if (pjob) {
		pattr = get_jattr(pjob, JOB_ATR_depend);
		pdep = find_depend(JOB_DEPEND_TYPE_BEFORESTART, pattr);
		if (pdep != NULL) {
			pdj = find_dependjob(pdep, preq->rq_ind.rq_register.rq_parent);
			if (pdj != NULL)
				del_depend_job(pdj);
			if (GET_NEXT(pdep->dp_jobs) == 0) {
				/* no more dependencies of this type */
				del_depend(pdep);
			}
		}
	}
	release_req(pwt);
}

/**
 * @brief
 * 		post_runone - Post (reply) processing of requests processing for run one job
 *
 * @param[in]	pwt	-	work task containing requests to be processed
 */
void
post_runone(struct work_task *pwt)
{
	struct batch_request *preq = pwt->wt_parm1;
	char *jobid = preq->rq_ind.rq_register.rq_child;
	attribute *pattr;
	struct depend *pdep;
	struct depend_job *pdj;
	job *pjob;
	job *del_job;

	pjob = find_job(jobid);
	if (pjob) {
		pattr = get_jattr(pjob, JOB_ATR_depend);
		pdep = find_depend(JOB_DEPEND_TYPE_RUNONE, pattr);
		if (pdep != NULL) {
			pdj = find_dependjob(pdep, preq->rq_ind.rq_register.rq_parent);
			if (pdj != NULL)
				del_depend_job(pdj);
			if (GET_NEXT(pdep->dp_jobs) == 0) {
				/* no more dependencies of this type */
				del_depend(pdep);
			}
			del_job = find_job(preq->rq_ind.rq_register.rq_parent);
			job_abt(del_job, msg_depend_runone);
		}
	}
	release_req(pwt);
}

/**
 * @brief
 * 		depend_on_exec - Perform actions if job has
 *		"beforestart" dependency - send "register-release" to child job; or
 * @note
 *		This function is called from svr_startjob().
 *
 * @param[in]	pjob	-	job
 *
 * @return	error code
 * @retval	0	: success
 */

int
depend_on_exec(job *pjob)
{
	struct depend *pdep;
	struct depend_job *pdj;

	if (pjob == NULL)
		return (0);

	/* If any jobs come after my start, release them */

	pdep = find_depend(JOB_DEPEND_TYPE_BEFORESTART, get_jattr(pjob, JOB_ATR_depend));
	if (pdep) {
		pdj = (struct depend_job *) GET_NEXT(pdep->dp_jobs);
		while (pdj) {
			(void) send_depend_req(pjob, pdj, pdep->dp_type, JOB_DEPEND_OP_RELEASE, SYNC_SCHED_HINT_NULL, post_doe);
			pdj = (struct depend_job *) GET_NEXT(pdj->dc_link);
		}
	}
	return (0);
}

/**
 * @brief
 * 		Helper function that goes through all dependent jobs with runone dependency
 *		on the given job and removes the given job out of their dependency list.
 * @note
 *		This function is called from req_deletejob2().
 *
 * @param[in]	pjob	-	job
 *
 * @return	error code
 * @retval	0	: success
 */
int
depend_runone_remove_dependency(job *pjob)
{
	struct depend *pdep;
	struct depend_job *pdj;
	struct job *d_pjob;

	if (pjob == NULL)
		return (0);

	pdep = find_depend(JOB_DEPEND_TYPE_RUNONE, get_jattr(pjob, JOB_ATR_depend));
	if (pdep) {
		for (pdj = (struct depend_job *) GET_NEXT(pdep->dp_jobs);
		     pdj != NULL; pdj = (struct depend_job *) GET_NEXT(pdj->dc_link)) {
			d_pjob = find_job(pdj->dc_child);
			if (d_pjob) {
				struct depend_job *temp_pdj = NULL;
				attribute *pattr = get_jattr(d_pjob, JOB_ATR_depend);

				temp_pdj = find_dependjob(find_depend(JOB_DEPEND_TYPE_RUNONE, pattr), pjob->ji_qs.ji_jobid);
				if (temp_pdj) {
					del_depend_job(temp_pdj);
					pattr->at_flags |= ATR_MOD_MCACHE;
				}
			}
		}
		del_depend(pdep);
	}
	return (0);
}

/**
 * @brief
 * 		Helper function that goes through all dependent jobs with runone dependency
 *		on the given job and puts all them on system hold.
 * @note
 *		This function is called from req_strtjob2().
 *
 * @param[in]	pjob	-	job
 *
 * @return	error code
 * @retval	0	: success
 */
int
depend_runone_hold_all(job *pjob)
{
	struct depend *pdep;
	struct depend_job *pdj;
	struct job *d_pjob;

	if (pjob == NULL)
		return (0);

	pdep = find_depend(JOB_DEPEND_TYPE_RUNONE, get_jattr(pjob, JOB_ATR_depend));
	if (pdep) {
		for (pdj = (struct depend_job *) GET_NEXT(pdep->dp_jobs);
		     pdj != NULL; pdj = (struct depend_job *) GET_NEXT(pdj->dc_link)) {
			d_pjob = find_job(pdj->dc_child);
			if (d_pjob) {
				set_jattr_b_slim(d_pjob, JOB_ATR_hold, HOLD_s, INCR);
				svr_setjobstate(d_pjob, JOB_STATE_LTR_HELD, JOB_SUBSTATE_HELD);
			}
		}
	}
	return (0);
}

/**
 * @brief
 * 		Helper function that goes through all dependent jobs with runone dependency
 *		on the given job and releases them of system hold.
 * @note
 *		This function is called from req_rerunjob2().
 *
 * @param[in]	pjob	-	job
 *
 * @return	error code
 * @retval	0	: success
 */
int
depend_runone_release_all(job *pjob)
{
	struct depend *pdep;
	struct depend_job *pdj;
	struct job *d_pjob;
	char newstate;
	int newsub;

	if (pjob == NULL)
		return (0);

	pdep = find_depend(JOB_DEPEND_TYPE_RUNONE, get_jattr(pjob, JOB_ATR_depend));
	if (pdep) {
		pdj = (struct depend_job *) GET_NEXT(pdep->dp_jobs);
		for (pdj = (struct depend_job *) GET_NEXT(pdep->dp_jobs);
		     pdj != NULL; pdj = (struct depend_job *) GET_NEXT(pdj->dc_link)) {
			d_pjob = find_job(pdj->dc_child);
			if (d_pjob) {
				set_jattr_b_slim(d_pjob, JOB_ATR_hold, HOLD_s, DECR);
				svr_evaljobstate(d_pjob, &newstate, &newsub, 0);
				svr_setjobstate(d_pjob, newstate, newsub); /* saves job */
			}
		}
	}
	return (0);
}

/**
 * @brief
 * 		depend_on_term - Perform actions if job has "afterany, afterok, afternotok"
 *		dependencies, send "register-release" or "register-delete" as
 *		appropriate.
 * @par
 *		This function is invoked from on_job_exit() in req_jobobit.c.
 *		When there are no depends to deal with, free the attribute and
 *		recall on_job_exit().
 *
 * @param[in]	pjob	-	job
 *
 * @return	error code
 * @retval	0	: success
 */

int
depend_on_term(job *pjob)
{
	int exitstat = pjob->ji_qs.ji_un.ji_exect.ji_exitstat;
	int op;
	struct depend *pdep;
	struct depend_job *pparent;
	int rc;
	int type;

	pdep = (struct depend *) GET_NEXT(get_jattr_list(pjob, JOB_ATR_depend));
	while (pdep) {
		op = -1;
		switch (type = pdep->dp_type) {

				/* for the first three, before... types, release or delete */
				/* next job depending on exit status			   */

			case JOB_DEPEND_TYPE_BEFOREOK:
				if (exitstat == 0)
					op = JOB_DEPEND_OP_RELEASE;
				else
					op = JOB_DEPEND_OP_DELETE;
				break;

			case JOB_DEPEND_TYPE_BEFORENOTOK:
				/* exitstat has defined values in case of user/admin forcefully deletes the job.
				 * In such cases delete the chain on dependency.
				 */
				if (exitstat != 0)
					op = JOB_DEPEND_OP_RELEASE;
				else
					op = JOB_DEPEND_OP_DELETE;
				break;

			case JOB_DEPEND_TYPE_BEFOREANY:
				op = JOB_DEPEND_OP_RELEASE;
				break;

			case JOB_DEPEND_TYPE_RUNONE:
				op = JOB_DEPEND_OP_DELETE;
				break;
			/* This case can only happen when a job with before start
			 * dependency is getting deleted before it even runs.
			 */
			case JOB_DEPEND_TYPE_BEFORESTART:
				op = JOB_DEPEND_OP_DELETE;
				break;
		}
		if (op != -1) {
			/* Check if the job is being deleted. If so, delete the dependency chain only for beforeok dependency */
			if (pjob->ji_terminated == 1) {
				if (type == JOB_DEPEND_TYPE_BEFORENOTOK || type == JOB_DEPEND_TYPE_BEFOREANY)
					op = JOB_DEPEND_OP_RELEASE;
				else
					op = JOB_DEPEND_OP_DELETE;
			}
			/* This function is also called from job_abt when the job is in held state and abort substate.
			 * In case of a held job, release the dependency chain.
			 */
			if (check_job_state(pjob, JOB_STATE_LTR_HELD) && check_job_substate(pjob, JOB_SUBSTATE_ABORT)) {
				op = JOB_DEPEND_OP_DELETE;
				/* In case the job being deleted is a job with runone dependency type
				 * then there is no need to delete other dependent jobs.
				 */
				if (type == JOB_DEPEND_TYPE_RUNONE)
					op = JOB_DEPEND_OP_RELEASE;
			}

			pparent = (struct depend_job *) GET_NEXT(pdep->dp_jobs);
			while (pparent) {
				/* "release" the job to execute */
				rc = send_depend_req(pjob, pparent, type, op,
						     SYNC_SCHED_HINT_NULL, release_req);
				if (rc)
					return rc;
				pparent = (struct depend_job *) GET_NEXT(pparent->dc_link);
			}
		}
		pdep = (struct depend *) GET_NEXT(pdep->dp_link);
	}
	return (0);
}

/**
 * @brief
 * 		set_depend_hold - set a hold on the job required by the type of dependency
 *
 * @param[in]	pjob	-	job
 * @param[in]	pattr	-	attribute structure
 */

static void
set_depend_hold(job *pjob, attribute *pattr)
{
	int loop = 1;
	char newstate;
	int newsubst;
	struct depend *pdp = NULL;
	int substate = -1;

	if (is_attr_set(pattr))
		pdp = (struct depend *) GET_NEXT(pattr->at_val.at_list);
	while (pdp && loop) {
		switch (pdp->dp_type) {
			case JOB_DEPEND_TYPE_AFTERSTART:
			case JOB_DEPEND_TYPE_AFTEROK:
			case JOB_DEPEND_TYPE_AFTERNOTOK:
			case JOB_DEPEND_TYPE_AFTERANY:
				if ((struct depend_job *) GET_NEXT(pdp->dp_jobs))
					substate = JOB_SUBSTATE_DEPNHOLD;
				break;

			case JOB_DEPEND_TYPE_ON:
				if (pdp->dp_numexp)
					substate = JOB_SUBSTATE_DEPNHOLD;
				break;
		}
		pdp = (struct depend *) GET_NEXT(pdp->dp_link);
	}
	if (substate == -1) {

		/* No (more) dependencies, clear system hold and set state */

		if ((check_job_substate(pjob, JOB_SUBSTATE_SYNCHOLD)) ||
		    (check_job_substate(pjob, JOB_SUBSTATE_DEPNHOLD))) {
			set_jattr_b_slim(pjob, JOB_ATR_hold, HOLD_s, DECR);
			svr_evaljobstate(pjob, &newstate, &newsubst, 0);
			svr_setjobstate(pjob, newstate, newsubst);
		}
	} else {

		/* there are dependencies, set system hold accordingly */

		set_jattr_b_slim(pjob, JOB_ATR_hold, HOLD_s, INCR);
		svr_setjobstate(pjob, JOB_STATE_LTR_HELD, substate);
	}
	return;
}

/**
 * @brief
 * 		find_depend - find a dependency struct of a certain type for a job
 *
 * @param[in]	type	-	type of dependency struct
 * @param[in]	pattr	-	attribute structure
 *
 * @return	dependent job structure
 * @retval	NULL	: fail
 */

struct depend *
find_depend(int type, attribute *pattr)
{
	struct depend *pdep = NULL;

	if (is_attr_set(pattr)) {
		pdep = (struct depend *) GET_NEXT(pattr->at_val.at_list);
		while (pdep) {
			if (pdep->dp_type == type)
				break;
			pdep = (struct depend *) GET_NEXT(pdep->dp_link);
		}
	}
	return (pdep);
}

/**
 * @brief
 * 		make_depend - allocate and attach a depend struct to the attribute
 *
 * @param[in]	type	-	type of dependency struct
 * @param[in,out]	pattr	-	attribute structure
 *
 * @return	dependent job structure
 * @retval	NULL	: fail
 */

static struct depend *
make_depend(int type, attribute *pattr)
{
	struct depend *pdep = NULL;

	pdep = (struct depend *) malloc(sizeof(struct depend));
	if (pdep) {
		clear_depend(pdep, type, 0);
		append_link(&pattr->at_val.at_list, &pdep->dp_link, pdep);
		post_attr_set(pattr);
	}
	return (pdep);
}

/**
 * @brief
 * 		register_dep - Some job wants to run before/after the local job, so set up
 *		a dependency on the local job.
 *
 * @param[in,out]	pattr	-	attribute structure
 * @param[in,out]	preq	-	request structure
 * @param[in]	type	-	type of dependency struct
 * @param[out]	made	-	0 if dependency is found else 1
 *
 * @return	error code
 * @retval	0	-	success
 * @reval	PBSE_SYSTEM	- fail
 */

static int
register_dep(attribute *pattr, struct batch_request *preq, int type, int *made)
{
	struct depend *pdep;
	struct depend_job *pdj;

	/* change into the mirror image type */

	type ^= (JOB_DEPEND_TYPE_BEFORESTART - JOB_DEPEND_TYPE_AFTERSTART);
	if ((pdep = find_depend(type, pattr)) == NULL) {
		if ((pdep = make_depend(type, pattr)) == NULL)
			return (PBSE_SYSTEM);
	}
	if ((pdj = find_dependjob(pdep,
				  preq->rq_ind.rq_register.rq_child)) != NULL) {
		(void) strcpy(pdj->dc_svr, preq->rq_ind.rq_register.rq_svr);
		*made = 0;
		return (0);
	} else if ((pdj = make_dependjob(pdep,
					 preq->rq_ind.rq_register.rq_child,
					 preq->rq_ind.rq_register.rq_svr)) ==
		   NULL) {
		return (PBSE_SYSTEM);
	} else
		*made = 1;
	return (0);
}

/**
 * @brief
 * 		unregister_dep - remove a registered dependency
 *		Results from a qalter call to remove existing dependencies
 *
 * @param[in]	pattr	-	attribute structure
 * @param[in]	preq	-	request structure
 *
 * @return	error code
 * @retval	0	: success
 * @retval	PBSE_IVALREQ	: Invalid request, dependency could not find.
 */
static int
unregister_dep(attribute *pattr, struct batch_request *preq)
{
	int type;
	struct depend *pdp;
	struct depend_job *pdjb;

	/* if dependency has mirroring effect, get mirror image of dependency type */
	if (preq->rq_ind.rq_register.rq_dependtype < JOB_DEPEND_TYPE_RUNONE)
		type = preq->rq_ind.rq_register.rq_dependtype ^
		       (JOB_DEPEND_TYPE_BEFORESTART - JOB_DEPEND_TYPE_AFTERSTART);
	else
		type = preq->rq_ind.rq_register.rq_dependtype;

	if (((pdp = find_depend(type, pattr)) == 0) ||
	    ((pdjb = find_dependjob(pdp, preq->rq_ind.rq_register.rq_child)) == NULL))
		return (PBSE_IVALREQ);

	del_depend_job(pdjb);
	return (0);
}

/**
 * @brief
 * 		find_dependjob - find a child dependent job with a certain job id
 *
 * @param[in]	pdep	-	dependent jobs
 * @param[in]	name	-	job id to be matched
 *
 * @return	child dependent job
 * @retval	NULL	: fail
 */

struct depend_job *
find_dependjob(struct depend *pdep, char *name)
{
	struct depend_job *pdj;

	if ((pdep == NULL) || (name == NULL))
		return NULL;

	pdj = (struct depend_job *) GET_NEXT(pdep->dp_jobs);
	while (pdj) {
		if (!strcmp(name, pdj->dc_child))
			break;

		pdj = (struct depend_job *) GET_NEXT(pdj->dc_link);
	}
	return (pdj);
}

/**
 * @brief
 * 		make_dependjob - add a depend_job structure
 *
 * @param[in,out]	pdep	-	ptr to head of depend list
 * @param[in]	jobid	-	child (dependent) job
 * @param[in]	host	-	server owning job
 *
 * @return	child dependent job
 */

static struct depend_job *
make_dependjob(struct depend *pdep, char *jobid, char *host)
{
	struct depend_job *pdj;

	pdj = (struct depend_job *) malloc(sizeof(struct depend_job));
	if (pdj) {

		CLEAR_LINK(pdj->dc_link);
		pdj->dc_state = 0;
		pdj->dc_cost = 0;
		(void) strcpy(pdj->dc_child, jobid);
		(void) strcpy(pdj->dc_svr, host);
		append_link(&pdep->dp_jobs, &pdj->dc_link, pdj);
	}
	return (pdj);
}

/**
 * @brief
 * 		send_depend_req - build and send a Register Dependent request
 *
 * @param[in]	pjob	-	job structure
 * @param[in]	pparent	-	parent job
 * @param[in]	type	-	dependency type
 * @param[in]	op	-	dependent job operation
 * @param[in]	schedhint	-	not used here
 * @param[in]	postfunc	-	call back function to issue_to_svr()
 *
 * @return	error code
 * @retval	0	: success
 * @retval	PBSE_BADHOST	: Unable to perform dependency
 * @retval	PBSE_SYSTEM	: malloc failed
 */

int
send_depend_req(job *pjob, struct depend_job *pparent, int type, int op, int schedhint, void (*postfunc)(struct work_task *))
{
	int i;
	char *pc;
	struct batch_request *preq;

	preq = alloc_br(PBS_BATCH_RegistDep);
	if (preq == NULL) {
		log_err(errno, __func__, msg_err_malloc);
		return (PBSE_SYSTEM);
	}

	if (get_jattr_str(pjob, JOB_ATR_job_owner) == NULL)
		return PBSE_INTERNAL;

	for (i = 0; i < PBS_MAXUSER; ++i) {
		preq->rq_ind.rq_register.rq_owner[i] = get_jattr_str(pjob, JOB_ATR_job_owner)[i];
		if (preq->rq_ind.rq_register.rq_owner[i] == '@')
			break;
	}
	preq->rq_ind.rq_register.rq_owner[i] = '\0';
	strcpy(preq->rq_ind.rq_register.rq_parent, pparent->dc_child);
	strcpy(preq->rq_ind.rq_register.rq_child, pjob->ji_qs.ji_jobid);
	/* Append "@<server_name>" since server's name may not match host name */
	strcat(preq->rq_ind.rq_register.rq_child, "@");
	strcat(preq->rq_ind.rq_register.rq_child, pbs_server_name);
	/* kludge for server:port follows */
	if ((pc = strchr(server_name, (int) ':')) != NULL) {
		strcat(preq->rq_ind.rq_register.rq_child, pc);
	}
	preq->rq_ind.rq_register.rq_dependtype = type;
	preq->rq_ind.rq_register.rq_op = op;
	strcpy(preq->rq_host, pparent->dc_svr); /* for issue_to_svr() */

	preq->rq_ind.rq_register.rq_cost = 0;

	if (issue_to_svr(pparent->dc_svr, preq, postfunc) == -1) {
		sprintf(log_buffer, "Unable to perform dependency with job %s", pparent->dc_child);
		return (PBSE_BADHOST);
	}
	return (0);
}

/*
 * This section contains general function for dependency attributes
 *
 * Each attribute has functions for:
 *	Decoding the value string to the machine representation.
 *	Encoding the internal representation of the attribute to external
 *	Setting the value by =, + or - operators.
 *	Comparing a (decoded) value with the attribute value.
 *	Freeing the space malloc-ed to the attribute value.
 *
 * The prototypes are declared in "attribute.h"
 *
 * ----------------------------------------------------------------------------
 * Attribute functions for attributes of type "dependency".
 *
 * The "encoded" or external form of the value is a string with sub-strings
 * separated by commas and terminated by a null.
 *
 * The "decoded" or internal form is a list of depend (and depend_child)
 * structures, which are defined in job.h.
 * ----------------------------------------------------------------------------
 */

struct dependnames {
	int type;
	char *name;
} dependnames[] = {
	{JOB_DEPEND_TYPE_AFTERSTART, "after"},
	{JOB_DEPEND_TYPE_AFTEROK, "afterok"},
	{JOB_DEPEND_TYPE_AFTERNOTOK, "afternotok"},
	{JOB_DEPEND_TYPE_AFTERANY, "afterany"},
	{JOB_DEPEND_TYPE_BEFORESTART, "before"},
	{JOB_DEPEND_TYPE_BEFOREOK, "beforeok"},
	{JOB_DEPEND_TYPE_BEFORENOTOK, "beforenotok"},
	{JOB_DEPEND_TYPE_BEFOREANY, "beforeany"},
	{JOB_DEPEND_TYPE_ON, "on"},
	{JOB_DEPEND_TYPE_RUNONE, "runone"},
	{-1, NULL}};

/**
 * @brief
 * 		decode_depend - decode a string into an attr of type dependency
 *		String is of form: depend_type:job_id[:job_id:...][,depend_type:job_id]
 *
 * @param[out]	patr	-	an attr of type dependency
 * @param[in]	name	-	attribute name
 * @param[in]	rescn	-	resource name, unused here
 * @param[in]	val	-	attribute value
 *
 * @return	error code
 * @retval	0	: ok
 * @retval	>0	: error
 */

int
decode_depend(attribute *patr, char *name, char *rescn, char *val)
{
	int rc;
	char *valwd;

	if ((val == NULL) || (*val == 0)) {
		free_depend(patr);
		patr->at_flags |= ATR_VFLAG_MODIFY;
		return (0);
	}

	/*
	 * for each sub-string (terminated by comma or new-line),
	 * add a depend or depend_child structure.
	 */
	valwd = parse_comma_string(val);
	while (valwd) {
		if ((rc = build_depend(patr, valwd)) != 0) {
			free_depend(patr);
			return (rc);
		}
		valwd = parse_comma_string(NULL);
	}

	post_attr_set(patr);
	return (0);
}

/**
 * @brief
 * 		cpy_jobsvr() - a version of strcpy() that watches for an embedded colon
 *		and escapes it with a leading blackslash.  This is needed because
 *		the colon is overloaded, both as job_id separator within a set of
 *		depend jobs, and as the server:port separator.  Ugh!
 *
 * @param[in,out]	d	-	destination string
 * @param[in,out]	s	-	source string
 */

static void
cpy_jobsvr(char *d, char *s)
{
	while (*d)
		d++;

	while (*s) {
		if (*s == ':')
			*d++ = '\\';
		*d++ = *s++;
	}
	*d = '\0';
}

/**
 * @brief
 * 		dup_depend - duplicate a dependency (see set_depend())
 *
 * @param[in,out]	pattr	-	attribute structure
 * @param[in]	pd	-	ptr dependency list
 *
 * @return	int
 * @retval	0	: success
 */

static int
dup_depend(attribute *pattr, struct depend *pd)
{
	struct depend *pnwd;
	struct depend_job *poldj;
	struct depend_job *pnwdj;
	int type;

	type = pd->dp_type;
	if ((pnwd = make_depend(type, pattr)) == 0)
		return (-1);

	pnwd->dp_numexp = pd->dp_numexp;
	pnwd->dp_numreg = pd->dp_numreg;
	pnwd->dp_released = pd->dp_released;
	pnwd->dp_numrun = pd->dp_numrun;
	for (poldj = (struct depend_job *) GET_NEXT(pd->dp_jobs); poldj;
	     poldj = (struct depend_job *) GET_NEXT(poldj->dc_link)) {
		if ((pnwdj = make_dependjob(pnwd, poldj->dc_child,
					    poldj->dc_svr)) == 0)
			return (-1);
		pnwdj->dc_state = poldj->dc_state;
		pnwdj->dc_cost = poldj->dc_cost;
	}

	return (0);
}

/**
 * @brief
 * 		encode_depend - encode dependency attr into attrlist entry
 *
 * @param[in]	attr	-	ptr to attribute to encode
 * @param[in,out]	phead	-	ptr to head of attrlist list
 * @param[in]	atname	-	attribute name
 * @param[in]	rsname	-	resource name or null
 * @param[in]	mode	-	ncode mode, unused here
 * @param[out]	rtnl	-	Return ptr to svrattrl
 *
 * @return	error code
 * @retval	>0	: ok, entry created and linked into list
 * @retval	=0	: no value to encode, entry not created
 * @retval	-1	: if error
 */
/*ARGSUSED*/

int
encode_depend(const attribute *attr, pbs_list_head *phead, char *atname, char *rsname, int mode, svrattrl **rtnl)
{
	int ct = 0;
	char cvtbuf[22];
	int numdep = 0;
	struct depend *nxdp;
	struct svrattrl *pal;
	struct depend *pdp;
	struct depend_job *pdjb = NULL;
	struct dependnames *pn;

	if (!attr)
		return (-1);
	if (!(is_attr_set(attr)))
		return (0); /* no values */

	pdp = (struct depend *) GET_NEXT(attr->at_val.at_list);
	if (pdp == NULL)
		return (0);

	/* scan dependencies types to compute needed base size of svrattrl */

	for (nxdp = pdp; nxdp; nxdp = (struct depend *) GET_NEXT(nxdp->dp_link)) {
		if (nxdp->dp_type == JOB_DEPEND_TYPE_ON)
			ct += 30; /* a guess at a reasonable amt of spece */
		else {
			ct += 12; /* for longest type */
			pdjb = (struct depend_job *) GET_NEXT(nxdp->dp_jobs);
			while (pdjb) {
				ct += PBS_MAXSVRJOBID + PBS_MAXSERVERNAME + 3;
				pdjb = (struct depend_job *) GET_NEXT(pdjb->dc_link);
			}
		}
	}

	if ((pal = attrlist_create(atname, rsname, ct)) == NULL) {
		return (-1);
	}
	*pal->al_value = '\0';
	for (nxdp = pdp; nxdp; nxdp = (struct depend *) GET_NEXT(nxdp->dp_link)) {
		if ((nxdp->dp_type != JOB_DEPEND_TYPE_ON) &&
		    !(pdjb = (struct depend_job *) GET_NEXT(nxdp->dp_jobs)))
			continue; /* no value, skip this one */
		if (nxdp != pdp)
			strcat(pal->al_value, ","); /* comma between */
		pn = &dependnames[nxdp->dp_type];
		strcat(pal->al_value, pn->name);
		if (pn->type == JOB_DEPEND_TYPE_ON) {
			sprintf(cvtbuf, ":%d", nxdp->dp_numexp);
			strcat(pal->al_value, cvtbuf);
		} else {
			while (pdjb) {
				strcat(pal->al_value, ":");
				cpy_jobsvr(pal->al_value, pdjb->dc_child);
				if (*pdjb->dc_svr != '\0') {
					strcat(pal->al_value, "@");
					cpy_jobsvr(pal->al_value, pdjb->dc_svr);
				}
				pdjb = (struct depend_job *) GET_NEXT(pdjb->dc_link);
			}
		}
		++numdep;
	}
	if (numdep) {
		/* there are dependencies recorded, added to the list	*/
		pal->al_flags = attr->at_flags;
		append_link(phead, &pal->al_link, pal);
		if (rtnl)
			*rtnl = pal;
		return (1);
	} else {
		/* there are no dependencies, just the base structure,	*/
		/* so remove this svrattrl from ths list		*/
		(void) free(pal);
		if (rtnl)
			*rtnl = NULL;
		return (0);
	}
}

/**
 * @brief
 * 		set_depend - set value of attribute of dependency type to another
 * @par Functionality:
 *		A=B --> set of dependencies in A replaced by set in B
 *		A+B --> dependencies in B added to list in A
 *		A-B --> not defined
 *
 * @param[in,out]	pattr	-	attribute structure
 * @param[in]	new	-	ptr dependency list
 * @param[in]	op	-	operation which needs to be performed
 * 						Ex. SET, INCR, DECR.
 *
 * @return	error code
 * @retval	0	: ok
 * @retval	>0	: error
 */

int
set_depend(attribute *attr, attribute *new, enum batch_op op)
{
	struct depend *pdnew;
	struct depend *pdold;
	int rc;

	assert(attr && new);

	switch (op) {
		case SET:

			/*
			 * if the type of dependency entry already exists, we are
			 * going to replace it, so get rid of the old and dup the new
			 */

			pdnew = (struct depend *) GET_NEXT(new->at_val.at_list);
			while (pdnew) {
				pdold = find_depend(pdnew->dp_type, attr);
				if (pdold)
					del_depend(pdold);
				if ((rc = dup_depend(attr, pdnew)) != 0)
					return (rc);
				pdnew = (struct depend *) GET_NEXT(pdnew->dp_link);
			}
			break;

		case INCR: /* not defined */
		case DECR: /* not defined */
		default:
			return (PBSE_IVALREQ);
	}
	post_attr_set(attr);
	return (0);
}

/*
 * comp_depend - compare two attributes of type dependency
 *	This operation is undefined.
 *
 *	Returns: 0
 *		+1
 *		-1
 */

int
comp_depend(attribute *attr, attribute *with)
{

	return (-1);
}
/**
 * @brief
 * 		free_depend	-	free the dependency link and dependent jobs from the
 * 						list of resources in the attribute structure.
 *
 * @param[in,out]	attr	-	attribute structure which contains the resource list to be freed
 */
void
free_depend(attribute *attr)
{
	struct depend *pdp;
	struct depend_job *pdjb;

	while ((pdp = (struct depend *)
			GET_NEXT(attr->at_val.at_list)) != NULL) {
		while ((pdjb = (struct depend_job *)
				GET_NEXT(pdp->dp_jobs)) != NULL) {
			delete_link(&pdjb->dc_link);
			(void) free(pdjb);
		}
		delete_link(&pdp->dp_link);
		(void) free(pdp);
	}
	/* free any data cached for stats */
	if (attr->at_user_encoded != NULL || attr->at_priv_encoded != NULL) {
		free_svrcache(attr);
	}
	mark_attr_not_set(attr);
}

/**
 * @brief
 * 		build_depend -  build a dependency structure
 * 		parse the string and turn it into a list of depend structures
 *
 * @param[in,out]	attr	-	attribute structure which contains the dependency structure
 * @param[in]	value	-	attribute value which is a set of sub-string (terminated by comma or new-line)
 *
 * @return	error code
 * @retval	0	: success
 * @retval	non-zero	: error number
 */

static int
build_depend(attribute *pattr, char *value)
{
	char fhn[PBS_MAXHOSTNAME + 1];
	struct depend *have[JOB_DEPEND_NUMBER_TYPES];
	int i;
	int numwds;
	struct depend *pd;
	struct depend_job *pdjb;
	struct dependnames *pname;
	char *pwhere;
	char *valwd;
	char *nxwrd;
	int type;

	/*
	 * Map first subword into dependency type.
	 */

	if ((nxwrd = strchr(value, (int) ':')) != NULL)
		*nxwrd++ = '\0';
	else
		/* dependency can never be without ':<value>' */
		return (PBSE_BADATVAL);

	if (*nxwrd == '\0')
		/* dependency can never be without a job-id or a number */
		return (PBSE_BADATVAL);

	for (pname = dependnames; pname->type != -1; pname++)
		if (!strcmp(value, pname->name))
			break;

	if (pname->type == -1)
		return (PBSE_BADATVAL);
	type = pname->type;

	/* what types do we have already? */

	for (i = 0; i < JOB_DEPEND_NUMBER_TYPES; i++)
		have[i] = NULL;
	for (pd = (struct depend *) GET_NEXT(pattr->at_val.at_list);
	     pd; pd = (struct depend *) GET_NEXT(pd->dp_link))
		have[pd->dp_type] = pd;

	if ((pd = have[type]) == NULL) {
		pd = make_depend(type, pattr);
		if (pd == NULL)
			return (PBSE_SYSTEM);
	}

	/* now process the value string */

	numwds = 0;
	while (nxwrd && (*nxwrd != '\0')) {

		numwds++; /* number of arguments */
		valwd = nxwrd;

		/* find end of next word delimited by a : but not a '\:' */

		while (((*nxwrd != ':') || (*(nxwrd - 1) == '\\')) && *nxwrd)
			nxwrd++;
		if (*nxwrd)
			*nxwrd++ = '\0';

		/* now process word (argument) depending on "depend type" */

		if (type == JOB_DEPEND_TYPE_ON) {

			/* a single word argument, a count */

			if (numwds == 1) {
				pd->dp_numexp = strtol(valwd, &pwhere, 10);
				if ((pd->dp_numexp < 1) ||
				    (pwhere && (*pwhere != '\0'))) {
					return (PBSE_BADATVAL);
				}
			} else {
				return (PBSE_BADATVAL);
			}

		} else { /* all other dependency types */

			/* a set of job_id[\:port][@server[\:port]] */

			pdjb = (struct depend_job *) malloc(sizeof(*pdjb));
			if (pdjb) {
				CLEAR_LINK(pdjb->dc_link);
				pdjb->dc_state = 0;
				pdjb->dc_cost = 0;
				pdjb->dc_svr[0] = '\0';
				pwhere = pdjb->dc_child;

				while (*valwd) {
					if (*valwd == '@') { /* switch to @server */
						*pwhere = '\0';
						pwhere = pdjb->dc_svr;
					} else if ((*valwd == '\\') && (*(valwd + 1) == ':')) {
						*pwhere++ = *++valwd; /* skip over '\' */
					} else {
						*pwhere++ = *valwd; /* copy jobid */
					}
					++valwd;
				}
				*pwhere = '\0';

				if (pdjb->dc_svr[0] == '\0') {
					pwhere = strchr(pdjb->dc_child, (int) '.');
					if (pwhere) {
						pwhere++;
						if (strncmp(pwhere, pbs_conf.pbs_server_name, PBS_MAXSERVERNAME) == 0) {
							(void) strcpy(pdjb->dc_svr, pbs_default());
						} else if (get_fullhostname(pwhere, fhn, (sizeof(fhn) - 1)) == 0) {
							(void) strcpy(pdjb->dc_svr, fhn);
						} else {
							(void) free(pdjb);
							return (PBSE_BADATVAL);
						}
					} else {
						(void) free(pdjb);
						return (PBSE_BADATVAL);
					}
				}

				append_link(&pd->dp_jobs, &pdjb->dc_link, pdjb);
			} else {
				return (PBSE_SYSTEM);
			}
		}
	}

	return (0);
}

/**
 * @brief
 * 		clear_depend - clear a single dependency set
 *		If the "exist" flag is set, any depend_job sub-structures are freed.
 *
 * @param[out]	pd	-	a single dependency set
 * @param[in]	type	-	freed dependency type
 * @param[in]	exist	-	If the "exist" flag is set,
 * 							any depend_job sub-structures are freed.
 */

static void
clear_depend(struct depend *pd, int type, int exist)
{
	struct depend_job *pdj;

	if (exist) {
		while ((pdj = (struct depend_job *)
				GET_NEXT(pd->dp_jobs)) != NULL) {
			del_depend_job(pdj);
		}
	} else {
		CLEAR_HEAD(pd->dp_jobs);
		CLEAR_LINK(pd->dp_link);
	}
	pd->dp_type = type;
	pd->dp_numexp = 0;
	pd->dp_numreg = 0;
	pd->dp_released = 0;
	pd->dp_numrun = 0;
}

/**
 * @brief
 * 		del_depend - delete a single dependency set, including any depend_jobs
 *
 * @param[in,out]	pd	-	a single dependency set
 */

static void
del_depend(struct depend *pd)
{
	struct depend_job *pdj;

	while ((pdj = (struct depend_job *) GET_NEXT(pd->dp_jobs)) != NULL) {
		del_depend_job(pdj);
	}
	delete_link(&pd->dp_link);
	(void) free(pd);
}

/**
 * @brief
 * 		del_depend_job - delete a single depend_job structure
 *
 *  @param[in,out]	pdj	-	a single depend_job structure
 */

static void
del_depend_job(struct depend_job *pdj)
{
	delete_link(&pdj->dc_link);
	(void) free(pdj);
}
