/*
*         OpenPBS (Portable Batch System) v2.3 Software License
*
* Copyright (c) 1999-2000 Veridian Information Solutions, Inc.
* All rights reserved.
*
* ---------------------------------------------------------------------------
* For a license to use or redistribute the OpenPBS software under conditions
* other than those described below, or to purchase support for this software,
* please contact Veridian Systems, PBS Products Department ("Licensor") at:
*
*    www.OpenPBS.org  +1 650 967-4675                  sales@OpenPBS.org
*                        877 902-4PBS (US toll-free)
* ---------------------------------------------------------------------------
*
* This license covers use of the OpenPBS v2.3 software (the "Software") at
* your site or location, and, for certain users, redistribution of the
* Software to other sites and locations.  Use and redistribution of
* OpenPBS v2.3 in source and binary forms, with or without modification,
* are permitted provided that all of the following conditions are met.
* After December 31, 2001, only conditions 3-6 must be met:
*
* 1. Commercial and/or non-commercial use of the Software is permitted
*    provided a current software registration is on file at www.OpenPBS.org.
*    If use of this software contributes to a publication, product, or
*    service, proper attribution must be given; see www.OpenPBS.org/credit.html
*
* 2. Redistribution in any form is only permitted for non-commercial,
*    non-profit purposes.  There can be no charge for the Software or any
*    software incorporating the Software.  Further, there can be no
*    expectation of revenue generated as a consequence of redistributing
*    the Software.
*
* 3. Any Redistribution of source code must retain the above copyright notice
*    and the acknowledgment contained in paragraph 6, this list of conditions
*    and the disclaimer contained in paragraph 7.
*
* 4. Any Redistribution in binary form must reproduce the above copyright
*    notice and the acknowledgment contained in paragraph 6, this list of
*    conditions and the disclaimer contained in paragraph 7 in the
*    documentation and/or other materials provided with the distribution.
*
* 5. Redistributions in any form must be accompanied by information on how to
*    obtain complete source code for the OpenPBS software and any
*    modifications and/or additions to the OpenPBS software.  The source code
*    must either be included in the distribution or be available for no more
*    than the cost of distribution plus a nominal fee, and all modifications
*    and additions to the Software must be freely redistributable by any party
*    (including Licensor) without restriction.
*
* 6. All advertising materials mentioning features or use of the Software must
*    display the following acknowledgment:
*
*     "This product includes software developed by NASA Ames Research Center,
*     Lawrence Livermore National Laboratory, and Veridian Information
*     Solutions, Inc.
*     Visit www.OpenPBS.org for OpenPBS software support,
*     products, and information."
*
* 7. DISCLAIMER OF WARRANTY
*
* THIS SOFTWARE IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND. ANY EXPRESS
* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
* OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, AND NON-INFRINGEMENT
* ARE EXPRESSLY DISCLAIMED.
*
* IN NO EVENT SHALL VERIDIAN CORPORATION, ITS AFFILIATED COMPANIES, OR THE
* U.S. GOVERNMENT OR ANY OF ITS AGENCIES BE LIABLE FOR ANY DIRECT OR INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* This license will be governed by the laws of the Commonwealth of Virginia,
* without reference to its choice of law rules.
*/

#include <pbs_config.h>   /* the master config generated by configure */

#include <algorithm>
#include <string>
#include <sstream>
#include <vector>
#include <sys/types.h>
#include <ctype.h>
#include <memory.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include "pbs_ifl.h"
#include "list_link.h"
#include "attribute.h"
#include "server_limits.h"
#include "net_connect.h"
#include "pbs_job.h"
#include "pbs_nodes.h"
#include "pbs_error.h"
#include "log.h"
#include "timer.hpp"
#if SYSLOG
#include <syslog.h>
#endif
#include "id_map.hpp"
#include "pbs_helper.h"

extern int LOGLEVEL;
void populate_range_string_from_slot_tracker(const execution_slot_tracker &est, std::string &range_str);


/*
 * This file contains functions for deriving pbs_attribute values from a pbsnode
 * and for updating the "state" (inuse), "node type" (ntype) or "properties"
 * list using the "value" carried in an pbs_attribute.
 *
 * Included are:
 *
 * global:
 * decode_state()  "functions for at_decode func pointer"
 * decode_power_state()
 * decode_ntype()
 * decode_props()
 *
 * encode_state()  "functions for at_encode func pointer"
 * encode_power_state()
 * encode_ntype()
 * encode_props()
 * encode_jobs()
 *
 * set_node_state()  "functions for at_set func pointer"
 * set_power_state()
 * set_node_ntype()
 *
 * node_state()   "functions for at_action func pointer"
 * power_state()
 * node_ntype()
 * node_prop_list()
 *
 * free_prop_attr()  "function for at_free func pointer"
 *
 * local:
 * load_prop()
 * set_nodeflag()
 *
 * The prototypes are declared in "attr_func.h"
 */


/*
 * Set of forward declarations for functions used before defined
 * keeps the compiler happy
*/
static int set_nodeflag(char*, short*);
/* static int load_prop( char*, struct prop* ); */


static struct node_state
  {
  short  bit;
  const char  *name;
  } ns[] =

  {
    {INUSE_UNKNOWN,     ND_state_unknown},
    {INUSE_DOWN,        ND_down},
    {INUSE_OFFLINE,     ND_offline},
    {INUSE_RESERVE,     ND_reserve},
    {INUSE_JOB,         ND_job_exclusive},
    {INUSE_BUSY,        ND_busy},
    {INUSE_NOHIERARCHY, ND_nohierarchy},
    {0,             NULL}
  };




int PNodeStateToString(

  int   SBM,     /* I (state bitmap) */
  char *Buf,     /* O */
  int   BufSize) /* I */

  {
  int len; 

  if (Buf == NULL)
    {
    return(-1);
    }

  BufSize--;

  Buf[0] = '\0';

  if (SBM & (INUSE_DOWN))
    {
    len = strlen(ND_down);

    if (len < BufSize)
      {
      strcat(Buf, ND_down);
      BufSize -= len;
      }
    }

  if (SBM & (INUSE_OFFLINE))
    {
    len = strlen(ND_offline) + 1;

    if (len < BufSize)
      {
      if (Buf[0] != '\0')
        strcat(Buf, ",");
      else
        len--;
  
      strcat(Buf, ND_offline);
      BufSize -= len;
      }
    }

  if (SBM & (INUSE_JOB))
    {
    len = strlen(ND_job_exclusive) + 1;

    if (len < BufSize)
      {
      if (Buf[0] != '\0')
        strcat(Buf, ",");
      else
        len--;
      
      strcat(Buf, ND_job_exclusive);
      BufSize -= len;
      }
    }

  if (SBM & (INUSE_BUSY))
    {
    len = strlen(ND_busy) + 1;

    if (len < BufSize)
      {
      if (Buf[0] != '\0')
        strcat(Buf, ",");
      else
        len--;
      
      strcat(Buf, ND_busy);
      BufSize -= len;
      }
    }

  if (SBM & (INUSE_NOHIERARCHY))
    {
    len = strlen(ND_nohierarchy) + 1;

    if (len < BufSize)
      {
      if (Buf[0] != '\0')
        strcat(Buf, ",");
      else
        len--;

      strcat(Buf, ND_nohierarchy);
      BufSize -= len;
      }
    }


  if (SBM & (INUSE_RESERVE))
    {
    len = strlen(ND_reserve) + 1;

    if (len < BufSize)
      {
      if (Buf[0] != '\0')
        strcat(Buf, ",");
      else
        len--;

      strcat(Buf, ND_reserve);
      BufSize -= len;
      }
    }

  if (SBM & (INUSE_UNKNOWN))
    {
    len = strlen(ND_state_unknown) + 1;

    if (len < BufSize)
      {
      if (Buf[0] != '\0')
        strcat(Buf, ",");
      else
        len--;

      strcat(Buf, ND_state_unknown);
      BufSize -= len;
      }
    }

  if (Buf[0] == '\0')
    {
    snprintf(Buf, BufSize, "%s", ND_free);
    }

  return(0);
  } /* END PNodeStateToString() */




/*
 * encode_state
 * Once the node's "inuse" field is converted to an pbs_attribute,
 * the pbs_attribute can be passed to this function for encoding into
 * an svrattrl structure
 * Returns  <0       an error encountered; value is negative of an error code
 *           0       ok, encode happened and svrattrl created and linked in,
 *       or nothing to encode
 */

int encode_state(

  pbs_attribute *pattr, /*struct pbs_attribute being encoded  */
  tlist_head    *ph, /*head of a list of  "svrattrl" structs 
                       which are to be returned*/
  const char   *aname, /*pbs_attribute's name    */
  const char   *rname, /*resource's name (null if none)  */
  int            UNUSED(mode), /*mode code, unused here   */
  int            UNUSED(perm)) /* only used for resources */

  {
  int       i;
  svrattrl *pal;
  short     state;

  char      state_str[MAX_ENCODE_BFR];

  if (!pattr)
    {
    return -(PBSE_INTERNAL);
    }

  if (!(pattr->at_flags & ATR_VFLAG_SET))
    {
    /* SUCCESS - pbs_attribute not set */

    return(0);
    }

  state = pattr->at_val.at_short & (INUSE_SUBNODE_MASK | INUSE_UNKNOWN);

  if (pattr->at_val.at_short & INUSE_NETWORK_FAIL)
    {
    state |= INUSE_DOWN;
    }

  if (!state)
    {
    strcpy(state_str, ND_free);
    }
  else
    {
    state_str[0] = '\0';

    for (i = 0;ns[i].name;i++)
      {
      if (state & ns[i].bit)
        {
        if (state_str[0] != '\0')
          {
          int len = strlen(state_str);
          snprintf(state_str + len, sizeof(state_str) - len, ",%s", ns[i].name);
          }
        else
          snprintf(state_str, sizeof(state_str), "%s", ns[i].name);
        }
      }
    }

  pal = attrlist_create(aname, rname, (int)strlen(state_str) + 1);

  if (pal == NULL)
    {
    return -(PBSE_SYSTEM);
    }

  strcpy(pal->al_value, state_str);

  pal->al_flags = ATR_VFLAG_SET;

  append_link(ph, &pal->al_link, pal);

  /* SUCCESS */

  return(0);
  }  /* END encode_state */


/*
 * encode_power_state
 * Once the node's power state field is converted to an pbs_attribute,
 * the pbs_attribute can be passed to this function for encoding into
 * an svrattrl structure
 * Returns  <0       an error encountered; value is negative of an error code
 *           0       ok, encode happened and svrattrl created and linked in,
 *       or nothing to encode
 */

int encode_power_state(

  pbs_attribute *pattr, /*struct pbs_attribute being encoded  */
  tlist_head    *ph, /*head of a list of  "svrattrl" structs
                       which are to be returned*/
  const char   *aname, /*pbs_attribute's name    */
  const char   *rname, /*resource's name (null if none)  */
  int            UNUSED(mode), /*mode code, unused here   */
  int            UNUSED(perm)) /* only used for resources */

  {
  svrattrl *pal;

  const char   *state_str = NULL;

  if (!pattr)
    {
    return -(PBSE_INTERNAL);
    }

  if (!(pattr->at_flags & ATR_VFLAG_SET))
    {
    /* SUCCESS - pbs_attribute not set */

    return(0);
    }

  switch(pattr->at_val.at_short)
    {
    case POWER_STATE_RUNNING:
      state_str = ND_running;
      break;
    case POWER_STATE_STANDBY:
      state_str = ND_standby;
      break;
    case POWER_STATE_SUSPEND:
      state_str = ND_suspend;
      break;
    case POWER_STATE_SLEEP:
      state_str = ND_sleep;
      break;
    case POWER_STATE_HIBERNATE:
      state_str = ND_hibernate;
      break;
    case POWER_STATE_SHUTDOWN:
      state_str = ND_shutdown;
      break;
    }

  if(state_str == NULL)
    {
    return -(PBSE_SYSTEM);
    }


  pal = attrlist_create(aname, rname, (int)strlen(state_str) + 1);

  if (pal == NULL)
    {
    return -(PBSE_SYSTEM);
    }

  strcpy(pal->al_value, state_str);

  pal->al_flags = ATR_VFLAG_SET;

  append_link(ph, &pal->al_link, pal);

  /* SUCCESS */

  return(0);
  }  /* END encode_power_state */





/*
 * encode_ntype
 * Once the node's "ntype" field is converted to an pbs_attribute,
 * the pbs_attribute can be passed to this function for encoding into
 * an svrattrl structure
 * Returns  <0       an error encountered; value is negative of an error code
 *           0       ok, encode happened and svrattrl created and linked in,
 *       or nothing to encode
 */

int encode_ntype(
  
  pbs_attribute  *pattr, /*struct pbs_attribute being encoded  */
  tlist_head     *ph,    /*head of a list of  "svrattrl"   */
  const char    *aname, /*pbs_attribute's name    */
  const char    *rname, /*resource's name (null if none)  */
  int             UNUSED(mode),  /*mode code, unused here   */
  int             UNUSED(perm))  /* only used for resources */

  {
  svrattrl *pal;
  short     ntype;
  char      ntype_str[MAX_ENCODE_BFR];

  if (!pattr)
    return -(PBSE_INTERNAL);

  if (!(pattr->at_flags & ATR_VFLAG_SET))
    return (0);      /*nothing to report back*/

  ntype = pattr->at_val.at_short & PBSNODE_NTYPE_MASK;

  if (!ntype)
    strcpy(ntype_str, ND_cluster);

  else
    strcpy(ntype_str, ND_timeshared);

  pal = attrlist_create(aname, rname, (int)strlen(ntype_str) + 1);

  if (pal == (svrattrl *)0)
    return -(PBSE_SYSTEM);

  strcpy(pal->al_value, ntype_str);

  pal->al_flags = ATR_VFLAG_SET;

  append_link(ph, &pal->al_link, pal);

  return (0);             /*success*/
  }




/*
 * encode_jobs
 * Once the node's struct jobinfo pointer is put in the data area of
 * temporary pbs_attribute containing a pointer to the parent node, this
 * function will walk the list of jobs and generate the comma separated
 * list to send back via an svrattrl structure.
 * Returns  <0       an error encountered; value is negative of an error code
 *           0       ok, encode happened and svrattrl created and linked in,
 *       or nothing to encode
 */

int encode_jobs(

  pbs_attribute  *pattr, /*struct pbs_attribute being encoded  */
  tlist_head     *ph,    /*head of a  list of "svrattrl" structs 
                           which are to be returned*/
  const char    *aname, /*pbs_attribute's name    */
  const char    *rname, /*resource's name (null if none)  */
  int             UNUSED(mode),  /*mode code, unused here   */
  int             UNUSED(perm))  /* only used for resources */

  {
  FUNCTION_TIMER
  svrattrl          *pal;

  struct pbsnode    *pnode;
  bool               first = true;
  std::string        job_str;
  std::string        range_str;

  if (pattr == NULL)
    {
    return (-1);
    }

  if (!(pattr->at_flags & ATR_VFLAG_SET) || 
      !(pattr->at_val.at_jinfo))
    {
    return(0);                  /* nothing to report back */
    }

  pnode = pattr->at_val.at_jinfo;
  
  for (int i = 0; i < (int)pnode->nd_job_usages.size(); i++)
    {
    const job_usage_info &jui = pnode->nd_job_usages[i];

    populate_range_string_from_slot_tracker(jui.est, range_str);

    if (first == false)
      job_str += ",";

    job_str += range_str;
    job_str += "/";
    job_str += job_mapper.get_name(jui.internal_job_id);

    first = false;
    }

  if (first == true)
    {
    /* no jobs currently on this node */

    return(0);
    }

  pal = attrlist_create(aname, rname, job_str.length() + 1);

  snprintf(pal->al_value, job_str.length() + 1, "%s", job_str.c_str());

  pal->al_flags = ATR_VFLAG_SET;

  append_link(ph, &pal->al_link, pal);

  return(0);                  /* success */
  }  /* END encode_jobs() */




/*
 * decode_state
 * In this case, the two arguments that get  used are
 * pattr-- it points to an pbs_attribute whose value is a short,
 * and the argument "val".
 * Once the "value" argument, val, is decoded from its form
 * as a string of comma separated substrings, the component
 * values are used to set the appropriate bits in the pbs_attribute's
 * value field.
 */

int decode_state(

  pbs_attribute *pattr,   /* I (modified) */
  const char * UNUSED(name),    /* pbs_attribute name */
  const char * UNUSED(rescn),   /* resource name, unused here */
  const char    *val,     /* pbs_attribute value */
  int          UNUSED(perm))    /* only used for resources */

  {
  int   rc = 0;  /*return code; 0==success*/
  short flag, currflag;
  char  *str;

  char  strbuf[512]; /*should handle most vals*/
  char *sbufp;
  char *ptr = NULL;
  int   slen;

  if (val == NULL)
    {
    return(PBSE_BADNDATVAL);
    }

  /*
   * determine string storage requirement and copy the string "val"
   * to a work buffer area
  */

  slen = strlen(val); /*bufr either on stack or heap*/

  if (slen - 512 < 0)
    {
    sbufp = strbuf;
    }
  else
    {
    if (!(sbufp = (char *)calloc(1, slen + 1)))
      {
      return(PBSE_SYSTEM);
      }
    }

  strcpy(sbufp, val);

  if ((str = parse_comma_string(sbufp,&ptr)) == NULL)
    {
    if (slen >= 512)
      free(sbufp);

    return(rc);
    }

  flag = 0;

  if ((rc = set_nodeflag(str, &flag)) != 0)
    {
    if (slen >= 512)
      free(sbufp);

    return(rc);
    }

  currflag = flag;

  /*calling parse_comma_string with a null ptr continues where*/
  /*last call left off.  The initial comma separated string   */
  /*copy pointed to by sbufp is modified with each func call  */

  while ((str = parse_comma_string(NULL,&ptr)) != NULL)
    {
    if ((rc = set_nodeflag(str, &flag)) != 0)
      break;

    if (((currflag == 0) && flag) || (currflag && (flag == 0)))
      {
      rc = PBSE_MUTUALEX; /*free is mutually exclusive*/

      break;
      }

    currflag = flag;
    }

  if (!rc)
    {
    pattr->at_val.at_short = flag;
    pattr->at_flags |= ATR_VFLAG_SET | ATR_VFLAG_MODIFY;
    }

  if (slen >= 512) /* buffer on heap, not stack */
    free(sbufp);

  return(rc);
  }  /* END decode_state() */


/*
 * decode_power_state
 * In this case, the two arguments that get  used are
 * pattr-- it points to an pbs_attribute whose value is a short,
 * and the argument "val".
 */

int decode_power_state(

  pbs_attribute *pattr,   /* I (modified) */
  const char * UNUSED(name),    /* pbs_attribute name */
  const char * UNUSED(rescn),   /* resource name, unused here */
  const char    *val,     /* pbs_attribute value */
  int          UNUSED(perm))    /* only used for resources */

  {
  int   flag = -1;

  if (val == NULL)
    {
    return(PBSE_BADNDATVAL);
    }

  if(!strcasecmp(val,ND_running))
    {
    flag = POWER_STATE_RUNNING;
    }
  else if(!strcasecmp(val,ND_standby))
    {
    flag = POWER_STATE_STANDBY;
    }
  else if(!strcasecmp(val,ND_suspend))
    {
    flag = POWER_STATE_SUSPEND;
    }
  else if(!strcasecmp(val,ND_sleep))
    {
    flag = POWER_STATE_SLEEP;
    }
  else if(!strcasecmp(val,ND_hibernate))
    {
    flag = POWER_STATE_HIBERNATE;
    }
  else if(!strcasecmp(val,ND_shutdown))
    {
    flag = POWER_STATE_SHUTDOWN;
    }
  if(flag == -1)
    {
    return(PBSE_BADNDATVAL);
    }
  pattr->at_val.at_short = flag;
  pattr->at_flags |= ATR_VFLAG_SET | ATR_VFLAG_MODIFY;

  return(0);
  }  /* END decode_state() */

/* Decode a universal time code */
int decode_utc(
    pbs_attribute *pattr,   /* I (modified) */
    const char   *name,    /* pbs_attribute name */
    const char *rescn,   /* resource name, unused here */
    const char    *val,     /* pbs_attribute value */
    int            perm)    /* only used for resources */

  {
  if(!strcmp(val,"0") || (*val == '\0'))
    {
    return decode_str(pattr,name,rescn,"",perm);
    }
  struct tm tm;
  bool offsetGiven = true;
  memset(&tm,0,sizeof(struct tm));
  tm.tm_isdst = -1; //This tells mktime to calculate the GMT offset and
                    //take daylight savings time into account.
  //Look for the format yyyy-mm-ddThh:mm:ss-hh or
  //                    yyyy-mm-ddThh:mm:ss-hhmm
  char *end = strptime(val,"%Y-%m-%dT%H:%M:%S%z",&tm);
  if ((end == NULL) || (*end != '\0'))
    {
    if (end == NULL)
      {
      memset(&tm,0,sizeof(struct tm));
      tm.tm_isdst = -1;
      offsetGiven = false;
      //Look for the format yyyy-mm-ddThh:mm:ssZ
      end = strptime(val,"%Y-%m-%dT%H:%M:%SZ",&tm);
      if ((end == NULL) || (*end != '\0'))
        {
        return(PBSE_BAD_UTC_FORMAT);
        }
      }
    else
      {
      return(PBSE_BAD_UTC_FORMAT);
      }
    }
  long offset = 0;
  if (offsetGiven)
    {
    offset = tm.tm_gmtoff; //Save the offset, mktime puts in its own.
    }
  time_t givenEpoch = mktime(&tm);
  if (offsetGiven)
    {
    /* This is to take care of the case where the time may be entered in one time zone but the machine running
       Moab may be running in another. If someone enters the time on the east coast with a -04 offset, but the
       Moab machine is running on the west coast we need to adjust the epoch time to reflect what will happen
       on the west coast. Example:
       Time entered 2014-08-20T00:00:00-04
       Machine on west coast will see that as 2014-08-19T20:00:00-08
       */
    givenEpoch += tm.tm_gmtoff; //Take away the calculated offset.
    givenEpoch -= offset;       //Add in the passed in offset.
    }
  else
    {
    givenEpoch += tm.tm_gmtoff; //Take away the calculated offset.
    }
  if(givenEpoch <= time(NULL))
    {
    return(PBSE_BAD_UTC_RANGE);
    }
  return decode_str(pattr,name,rescn,val,perm);
  }

/*
 * decode_ntype
 * In this case, the two arguments that get  used are
 * pattr-- it points to an pbs_attribute whose value is a short,
 * and the argument "val".
 * Although we have only "time-shared" and "cluster" at this
 * point in PBS's evolution, there may come a time when other
 * ntype values are needed.  The one thing that is assumed is
 * that the types are going to be mutually exclusive
 */

int decode_ntype(

  pbs_attribute *pattr,
  const char * UNUSED(name),   /* pbs_attribute name */
  const char * UNUSED(rescn),  /* resource name, unused here */
  const char    *val,    /* pbs_attribute value */
  int          UNUSED(perm))   /* only used for resources */


  {
  int rc = 0;  /*return code; 0==success*/
  short tmp = 0;


  if (val == (char *)0)
    rc = (PBSE_BADNDATVAL);
  else if (!strcmp(val, ND_cluster))
    tmp = NTYPE_CLUSTER;
  else
    rc = (PBSE_BADNDATVAL);


  if (!rc)
    {
    pattr->at_val.at_short = tmp;
    pattr->at_flags |= ATR_VFLAG_SET | ATR_VFLAG_MODIFY;
    }

  return rc;
  }



/*
 * set_node_state - the information in the "short" pbs_attribute, *new, is used to
 *      update the information in the "short" pbs_attribute, *pattr.
 *      the mode of the update is goverened by the argument "op"
 *      (SET,INCR,DECR)
 */

int set_node_state(

  pbs_attribute *pattr,  /*pbs_attribute gets modified    */
  pbs_attribute *new_attr,  /*carries new, modifying info that 
                         got decoded into 'new"*/
  enum           batch_op op)

  {
  int rc = 0;

  assert((pattr != NULL) && (new_attr != NULL) && (new_attr->at_flags & ATR_VFLAG_SET));

  switch (op)
    {

    case SET:

      pattr->at_val.at_short = new_attr->at_val.at_short;

      break;

    case INCR:

      if (pattr->at_val.at_short && new_attr->at_val.at_short == 0)
        {

        rc = PBSE_BADNDATVAL;  /*"free" mutually exclusive*/
        break;
        }

      pattr->at_val.at_short |= new_attr->at_val.at_short;

      break;

    case DECR:

      if (pattr->at_val.at_short && new_attr->at_val.at_short == 0)
        {

        rc = PBSE_BADNDATVAL;  /*"free" mutually exclusive*/
        break;
        }

      pattr->at_val.at_short &= ~new_attr->at_val.at_short;

      break;

    default:
      rc = PBSE_INTERNAL;
    }

  if (!rc)
    pattr->at_flags |= ATR_VFLAG_SET | ATR_VFLAG_MODIFY;

  return rc;
  }

/*
 * set_node_state - the information in the "short" pbs_attribute, *new, is used to
 *      update the information in the "short" pbs_attribute, *pattr.
 *      the mode of the update is goverened by the argument "op"
 *      (SET,INCR,DECR)
 */

int set_power_state(

  pbs_attribute *pattr,  /*pbs_attribute gets modified    */
  pbs_attribute *new_attr,  /*carries new, modifying info that
                         got decoded into 'new"*/
  enum           batch_op op)

  {
  int rc = 0;

  assert((pattr != NULL) && (new_attr != NULL) && (new_attr->at_flags & ATR_VFLAG_SET));

  switch (op)
    {

    case SET:

      pattr->at_val.at_short = new_attr->at_val.at_short;

      break;

    case INCR:

      rc = PBSE_BADNDATVAL; // Only valid operation is set.
      break;

    case DECR:

      rc = PBSE_BADNDATVAL; // Only valid operation is set.
      break;

    default:
      rc = PBSE_INTERNAL;
    }

  if (!rc)
    pattr->at_flags |= ATR_VFLAG_SET | ATR_VFLAG_MODIFY;

  return rc;
  }



/*
 * set_node_ntype - the value entry in pbs_attribute "new" is a short.  It was
 *      generated by the decode routine is used to update the
 *      value portion of the pbs_attribute *pattr
 *      the mode of the update is goverened by the argument "op"
 *      (SET,INCR,DECR)
 */

int set_node_ntype(

  pbs_attribute *pattr,  /*pbs_attribute gets modified    */
  pbs_attribute *new_attr,  /*carries new, modifying info*/
  enum batch_op  op)

  {
  int rc = 0;

  assert(pattr && new_attr && (new_attr->at_flags & ATR_VFLAG_SET));

  switch (op)
    {

    case SET:
      pattr->at_val.at_short = new_attr->at_val.at_short;
      break;

    case INCR:

      if (pattr->at_val.at_short != new_attr->at_val.at_short)
        {

        rc = PBSE_MUTUALEX;  /*types are mutually exclusive*/
        }

      break;

    case DECR:

      if (pattr->at_val.at_short != new_attr->at_val.at_short)
        rc = PBSE_MUTUALEX;  /*types are mutually exclusive*/
      else 
        pattr->at_val.at_short = NTYPE_CLUSTER;

      break;

    default:
      rc = PBSE_INTERNAL;
    }

  if (!rc)
    pattr->at_flags |= ATR_VFLAG_SET | ATR_VFLAG_MODIFY;

  return rc;
  }


/*
 * set_nodeflag - Use the input string's value to set a bit in
 *    the "flags" variable pointed to by pflags
 *    Each call sets one more bit in the flags
 *    variable or it clears the flags variable
 *    in the case where *str is the value "free"
 */

static int set_nodeflag(

  char  *str,
  short *pflag)

  {
  int rc = 0;

  if (*str == '\0')
    {
    return(PBSE_BADNDATVAL);
    }

  if (!strcmp(str, ND_free))
    {
    if(*pflag & INUSE_NOHIERARCHY)
      {
      rc = PBSE_HIERARCHY_NOT_SENT;
      }
    else
      {
      *pflag = 0;
      }
    }
  else if (!strcmp(str, ND_offline))
    *pflag = *pflag | INUSE_OFFLINE;
  else if (!strcmp(str, ND_down))
    *pflag = *pflag | INUSE_DOWN;
  else if (!strcmp(str, ND_reserve))
    *pflag = *pflag | INUSE_RESERVE;
  else
    {
    rc = PBSE_BADNDATVAL;
    }

  return(rc);
  }





/*
 * node_state - Either derive a "state" pbs_attribute from the node
 *  or update node's "inuse" field using the "state"
 *  pbs_attribute.
 */

int node_state(

  pbs_attribute *new_attr, /*derive state into this pbs_attribute*/
  void     *pnode, /*pointer to a pbsnode struct    */
  int      actmode) /*action mode; "NEW" or "ALTER"   */

  {
  int rc = 0;

  struct pbsnode* np;

  np = (struct pbsnode*)pnode; /*because of def of at_action  args*/

  switch (actmode)
    {

    case ATR_ACTION_NEW:  /*derive pbs_attribute*/

      new_attr->at_val.at_short = np->nd_state;

      break;

    case ATR_ACTION_ALTER:

      if(np->nd_state & INUSE_NOHIERARCHY) //Can't change the state until the hierarchy is sent.
        {
        return PBSE_HIERARCHY_NOT_SENT;
        }
      np->nd_state = new_attr->at_val.at_short;

      break;

    default:

      rc = PBSE_INTERNAL;

      break;
    }

  return(rc);
  }


/*
 * node_state - Either derive a "power state" pbs_attribute from the node
 *  or update node's "power state" field using the "state"
 *  pbs_attribute.
 */

int node_power_state(

  pbs_attribute *new_attr, /*derive state into this pbs_attribute*/
  void     *pnode, /*pointer to a pbsnode struct    */
  int      actmode) /*action mode; "NEW" or "ALTER"   */

  {
  int rc = 0;

  struct pbsnode* np;

  np = (struct pbsnode*)pnode; /*because of def of at_action  args*/

  switch (actmode)
    {

    case ATR_ACTION_NEW:  /*derive pbs_attribute*/

      new_attr->at_val.at_short = np->nd_power_state;

      break;

    case ATR_ACTION_ALTER:

      if(np->nd_power_state != new_attr->at_val.at_short)
        {
        if((np->nd_power_state != POWER_STATE_RUNNING)&&(new_attr->at_val.at_short != POWER_STATE_RUNNING))
          {
          return PBSE_INVALID_POWER_STATE_TRANSISTION;
          }
        }
      np->nd_power_state = new_attr->at_val.at_short;

      break;

    default:

      rc = PBSE_INTERNAL;

      break;
    }

  return(rc);
  }

/* change the time to live */

int node_ttl(

  pbs_attribute *new_attr, /*derive state into this pbs_attribute*/
  void     *pnode, /*pointer to a pbsnode struct    */
  int      actmode) /*action mode; "NEW" or "ALTER"   */

  {
  int rc = 0;

  struct pbsnode* np;
  pbs_attribute    temp;

  np = (struct pbsnode*)pnode; /*because of def of at_action  args*/

  switch (actmode)
    {
    case ATR_ACTION_NEW:

      if (np->nd_ttl[0] != '\0')
        {
        temp.at_val.at_str = (char *)np->nd_ttl;
        temp.at_flags = ATR_VFLAG_SET;
        temp.at_type  = ATR_TYPE_STR;

        rc = set_str(new_attr, &temp, SET);
        }
      else
        {
        new_attr->at_val.at_str  = NULL;
        new_attr->at_flags       = 0;
        new_attr->at_type        = ATR_TYPE_STR;
        }
      break;

    case ATR_ACTION_ALTER:

      if (new_attr->at_val.at_str != NULL)
        {
        snprintf(np->nd_ttl, sizeof(np->nd_ttl), "%s", new_attr->at_val.at_str);
        }
      else
        {
        np->nd_ttl[0] = '\0';
        }

      break;

    default:

      rc = PBSE_INTERNAL;

      break;
    }

  return(rc);
  }

/* change a string property on a node */

int node_requestid(

  pbs_attribute *new_attr, /*derive state into this pbs_attribute*/
  void     *pnode, /*pointer to a pbsnode struct    */
  int            actmode) /*action mode; "NEW" or "ALTER"   */

  {
  int rc = 0;

  struct pbsnode *np = (struct pbsnode*)pnode; /*because of def of at_action  args*/

  pbs_attribute    temp;

  switch (actmode)
    {
    case ATR_ACTION_NEW:

      if (np->nd_requestid.size() != 0)
        {
        temp.at_val.at_str = (char *)np->nd_requestid.c_str();
        temp.at_flags = ATR_VFLAG_SET;
        temp.at_type  = ATR_TYPE_STR;

        rc = set_str(new_attr, &temp, SET);
        }
      else
        {
        new_attr->at_val.at_str  = NULL;
        new_attr->at_flags       = 0;
        new_attr->at_type        = ATR_TYPE_STR;
        }
      break;

    case ATR_ACTION_ALTER:

      if(new_attr->at_val.at_str != NULL)
        {
        np->nd_requestid = new_attr->at_val.at_str;
        }
      else
        {
        np->nd_requestid.clear();
        }

      break;

    default:

      rc = PBSE_INTERNAL;

      break;
    }

  return(rc);
  }


int node_acl(
  pbs_attribute *new_attr,     /*derive props into this pbs_attribute*/
  void          *pnode,   /*pointer to a pbsnode struct     */
  int            actmode) /*action mode; "NEW" or "ALTER"   */

  {
  int   rc = 0;

  struct pbsnode  *np;
  pbs_attribute    temp;

  np = (struct pbsnode*)pnode; /*because of at_action arg type*/

  switch (actmode)
    {

    case ATR_ACTION_NEW:

      /* if node has a property list, then copy array_strings    */
      /* into temp to use to setup a copy, otherwise setup empty */

      if (np->nd_acl != NULL)
        {
        /* setup temporary pbs_attribute with the array_strings */
        /* from the node        */

        temp.at_val.at_arst = np->nd_acl;
        temp.at_flags = ATR_VFLAG_SET;
        temp.at_type  = ATR_TYPE_ARST;

        rc = set_arst(new_attr, &temp, SET);
        }
      else
        {
        /* Node has no properties, setup empty pbs_attribute */

        new_attr->at_val.at_arst = 0;
        new_attr->at_flags       = 0;
        new_attr->at_type        = ATR_TYPE_ARST;
        }

      break;

    case ATR_ACTION_ALTER:

      /* update node with new attr_strings */

      np->nd_acl = new_attr->at_val.at_arst;

      break;

    default:

      rc = PBSE_INTERNAL;

      break;
    }  /* END switch(actmode) */

  return(rc);
  }  /* END node_prop_list() */


/*
 * node_ntype - Either derive an "ntype" pbs_attribute from the node
 *  or update node's "ntype" field using the
 *  pbs_attribute's data
 */

int node_ntype(

  pbs_attribute *new_attr,      /*derive ntype into this pbs_attribute*/
  void          *pnode,   /*pointer to a pbsnode struct     */
  int            actmode) /*action mode; "NEW" or "ALTER"   */

  {
  int rc = 0;

  struct pbsnode* np;


  np = (struct pbsnode*)pnode; /*because of def of at_action  args*/

  switch (actmode)
    {

    case ATR_ACTION_NEW:  /*derive pbs_attribute*/
      new_attr->at_val.at_short = np->nd_ntype;
      break;

    case ATR_ACTION_ALTER:
      np->nd_ntype = new_attr->at_val.at_short;
      break;

    default:
      rc = PBSE_INTERNAL;
    }

  return rc;
  }




/*
 * node_prop_list - Either derive a "prop list" pbs_attribute from the node
 *      or update node's prop list from pbs_attribute's prop list.
 */

int node_prop_list(

  pbs_attribute *new_attr,     /*derive props into this pbs_attribute*/
  void          *pnode,   /*pointer to a pbsnode struct     */
  int            actmode) /*action mode; "NEW" or "ALTER"   */

  {
  int   rc = 0;

  struct pbsnode  *np;
  pbs_attribute    temp;

  np = (struct pbsnode*)pnode; /*because of at_action arg type*/

  switch (actmode)
    {

    case ATR_ACTION_NEW:

      /* if node has a property list, then copy array_strings    */
      /* into temp to use to setup a copy, otherwise setup empty */

      if (np->nd_prop != NULL)
        {
        /* setup temporary pbs_attribute with the array_strings */
        /* from the node        */

        temp.at_val.at_arst = np->nd_prop;
        temp.at_flags = ATR_VFLAG_SET;
        temp.at_type  = ATR_TYPE_ARST;

        rc = set_arst(new_attr, &temp, SET);
        }
      else
        {
        /* Node has no properties, setup empty pbs_attribute */

        new_attr->at_val.at_arst = 0;
        new_attr->at_flags       = 0;
        new_attr->at_type        = ATR_TYPE_ARST;
        }

      break;

    case ATR_ACTION_ALTER:

      /* update node with new attr_strings */

      np->nd_prop = new_attr->at_val.at_arst;

      /* update number of properties listed in node */
      /* does not include name and subnode property */

      if (np->nd_prop)
        np->nd_nprops = np->nd_prop->as_usedptr;
      else
        np->nd_nprops = 0;

      break;

    default:

      rc = PBSE_INTERNAL;

      break;
    }  /* END switch(actmode) */

  return(rc);
  }  /* END node_prop_list() */



/*
 * node_status_list - Either derive a "status list" pbs_attribute from the node
 *                 or update node's status list from pbs_attribute's status list.
 */

/*
int node_status_list(

  pbs_attribute *new_attr,      // derive status into this pbs_attribute
  void          *pnode,         // pointer to a pbsnode struct
  int            actmode)       // action mode; "NEW" or "ALTER"

  {
  int              rc = 0;

  struct pbsnode  *np;
  pbs_attribute    temp;

  np = (struct pbsnode *)pnode;    // because of at_action arg type

  switch (actmode)
    {

    case ATR_ACTION_NEW:

      // if node has a status list, then copy array_strings
      // into temp to use to setup a copy, otherwise setup empty

      if (np->nd_status.size() != 0)
        {
        // setup temporary pbs_attribute with the array_strings
        // from the node

        temp.at_val.at_str = np->nd_status;
        temp.at_flags = ATR_VFLAG_SET;
        temp.at_type  = ATR_TYPE_ARST;

        rc = set_arst(new_attr, &temp, SET);
        }
      else
        {
        // node has no properties, setup empty pbs_attribute

        new_attr->at_val.at_arst = NULL;
        new_attr->at_flags       = 0;
        new_attr->at_type        = ATR_TYPE_ARST;
        }

      break;

    case ATR_ACTION_ALTER:

      if (np->nd_status != NULL)
        {
        free(np->nd_status->as_buf);
        free(np->nd_status);

        np->nd_status = NULL;
        }

      // update node with new attr_strings

      np->nd_status = new_attr->at_val.at_arst;

      new_attr->at_val.at_arst = NULL;

      // update number of status items listed in node
      // does not include name and subnode property

      if (np->nd_status != NULL)
        np->nd_nstatus = np->nd_status->as_usedptr;
      else
        np->nd_nstatus = 0;

      break;

    default:

      rc = PBSE_INTERNAL;

      break;
    }  * END switch(actmode) */

//  return(rc);
//  }  /* END node_status_list() */



/*
 * node_gpustatus_list - Either derive a "gpu status list" pbs_attribute from the node
 *                 or update node's status list from pbs_attribute's status list.
 */

int node_gpustatus_list(

  pbs_attribute *new_attr,      /* derive status into this pbs_attribute*/
  void          *pnode,    /* pointer to a pbsnode struct     */
  int            actmode)  /* action mode; "NEW" or "ALTER"   */

  {
  int              rc = 0;

  struct pbsnode *np;
  pbs_attribute   temp;

  np = (struct pbsnode *)pnode;    /* because of at_action arg type */

  switch (actmode)
    {

    case ATR_ACTION_NEW:

      /* if node has a status list, then copy array_strings    */
      /* into temp to use to setup a copy, otherwise setup empty */

      if (np->nd_gpustatus != NULL)
        {
        /* setup temporary pbs_attribute with the array_strings */
        /* from the node                                    */

        temp.at_val.at_arst = np->nd_gpustatus;
        temp.at_flags = ATR_VFLAG_SET;
        temp.at_type  = ATR_TYPE_ARST;

        rc = set_arst(new_attr, &temp, SET);
        }
      else
        {
        /* node has no properties, setup empty pbs_attribute */

        new_attr->at_val.at_arst = NULL;
        new_attr->at_flags       = 0;
        new_attr->at_type        = ATR_TYPE_ARST;
        }

      break;

    case ATR_ACTION_ALTER:

      if (np->nd_gpustatus != NULL)
        {
        free(np->nd_gpustatus->as_buf);
        free(np->nd_gpustatus);

        np->nd_gpustatus = NULL;
        }

      /* update node with new attr_strings */

      np->nd_gpustatus = new_attr->at_val.at_arst;

      new_attr->at_val.at_arst = NULL;

      /* update number of status items listed in node */
      /* does not include name and subnode property */

      if (np->nd_gpustatus != NULL)
        np->nd_ngpustatus = np->nd_gpustatus->as_usedptr;
      else
        np->nd_ngpustatus = 0;

      break;

    default:

      rc = PBSE_INTERNAL;

      break;
    }  /* END switch(actmode) */

  return(rc);
  }  /* END node_gpupstatus_list() */




int node_micstatus_list(

  pbs_attribute *new_attr,      /* derive status into this pbs_attribute*/
  void          *pnode,    /* pointer to a pbsnode struct     */
  int            actmode)  /* action mode; "NEW" or "ALTER"   */

  {
  int              rc = 0;

  struct pbsnode *np;
  pbs_attribute   temp;

  np = (struct pbsnode *)pnode;    /* because of at_action arg type */

  switch (actmode)
    {

    case ATR_ACTION_NEW:

      /* if node has a status list, then copy array_strings    */
      /* into temp to use to setup a copy, otherwise setup empty */

      if (np->nd_micstatus != NULL)
        {
        /* setup temporary pbs_attribute with the array_strings */
        /* from the node                                    */

        temp.at_val.at_arst = np->nd_micstatus;
        temp.at_flags = ATR_VFLAG_SET;
        temp.at_type  = ATR_TYPE_ARST;

        rc = set_arst(new_attr, &temp, SET);
        }
      else
        {
        /* node has no properties, setup empty pbs_attribute */

        new_attr->at_val.at_arst = NULL;
        new_attr->at_flags       = 0;
        new_attr->at_type        = ATR_TYPE_ARST;
        }

      break;

    case ATR_ACTION_ALTER:

      if (np->nd_micstatus != NULL)
        {
        free(np->nd_micstatus->as_buf);
        free(np->nd_micstatus);

        np->nd_micstatus = NULL;
        }

      /* update node with new attr_strings */

      np->nd_micstatus = new_attr->at_val.at_arst;

      new_attr->at_val.at_arst = NULL;

      break;

    default:

      rc = PBSE_INTERNAL;

      break;
    }  /* END switch(actmode) */

  return(rc);
  }  /* END node_micstatus_list() */




/*
 * node_note - Either derive a note pbs_attribute from the node
 *             or update node's note from pbs_attribute's list.
 */

int node_note(

  pbs_attribute *new_attr,      /*derive status into this pbs_attribute*/
  void          *pnode,    /*pointer to a pbsnode struct     */
  int            actmode)  /*action mode; "NEW" or "ALTER"   */

  {
  int              rc = 0;

  struct pbsnode *np;
  pbs_attribute   temp;

  np = (struct pbsnode *)pnode;    /* because of at_action arg type */

  switch (actmode)
    {

    case ATR_ACTION_NEW:

      /* if node has a note, then copy string into temp  */
      /* to use to setup a copy, otherwise setup empty   */

      if (np->nd_note.size() != 0)
        {
        /* setup temporary pbs_attribute with the string from the node */

        temp.at_val.at_str = (char *)np->nd_note.c_str();
        temp.at_flags = ATR_VFLAG_SET;
        temp.at_type  = ATR_TYPE_STR;

        rc = set_note_str(new_attr, &temp, SET);
        }
      else
        {
        /* node has no properties, setup empty pbs_attribute */

        new_attr->at_val.at_str  = NULL;
        new_attr->at_flags       = 0;
        new_attr->at_type        = ATR_TYPE_STR;
        }

      break;

    case ATR_ACTION_ALTER:

      /* update node with new string */
      if (new_attr->at_val.at_str != NULL)
        {
        np->nd_note = new_attr->at_val.at_str;

        free(new_attr->at_val.at_str);
        new_attr->at_val.at_str = NULL;
        }
      else
        np->nd_note.clear();

      break;

    default:

      rc = PBSE_INTERNAL;

      break;
    }  /* END switch(actmode) */

  return(rc);
  }  /* END node_note() */



/*
 * a set_str() wrapper with sanity checks for notes
 */

int set_note_str(

  pbs_attribute *attr,
  pbs_attribute *new_attr,
  enum batch_op  op)

  {
  assert(attr && new_attr && new_attr->at_val.at_str && (new_attr->at_flags & ATR_VFLAG_SET));

  // if newlines are in the note string, remove them
  if (strchr(new_attr->at_val.at_str, '\n') != NULL)
    {
    std::string new_note = new_attr->at_val.at_str;
    
    // remove newline(s) from string                                                                                     
    new_note.erase(std::remove(new_note.begin(), new_note.end(), '\n'), new_note.end());

    // now reassign string
    // remove the old one
    free(new_attr->at_val.at_str);

    // assign the new one
    new_attr->at_val.at_str = strdup(new_note.c_str());
    }

  return(set_str(attr, new_attr, op));
  }  /* END set_note_str() */

/* END attr_node_func.c */

