#include "license_pbs.h" /* See here for the software license */
/*
 * This file contains the routines used to send a reply to a client following
 * the processing of a request.  The following routines are provided here:
 *
 * reply_send()  - the main routine, used by all reply senders
 * reply_ack()   - send a basic no error acknowledgement
 * req_reject()  - send a basic error return
 * reply_text()  - send a return with a supplied text string
 * reply_jobid() - used by several requests where the job id must be sent
 * reply_free()  - free the substructure that might hang from a reply
 */

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

#include <stdio.h>
#include <string.h>
#include <errno.h>
#include "libpbs.h"
#include "dis.h"
#include "log.h"
#include "../lib/Liblog/pbs_log.h"
#include "../lib/Liblog/log_event.h"
#include "pbs_error.h"
#include "server_limits.h"
#include "list_link.h"
#include "net_connect.h"
#include "attribute.h"
#include "credential.h"
#include "batch_request.h"
#include "work_task.h"
#include "utils.h"
#include "tcp.h" /* tcp_chan */




/* External Globals */
extern char *msg_daemonname;

#define ERR_MSG_SIZE 127


static void set_err_msg(

  int   code,
  char *msgbuf,
  int   len)

  {
  char *msg = NULL;
  char *msg_tmp;

  /* see if there is an error message associated with the code */

  *msgbuf = '\0';

  if (code == PBSE_SYSTEM)
    {
    snprintf(msgbuf, len, "%s%s", msg_daemonname, pbse_to_txt(PBSE_SYSTEM));

    msg_tmp = strerror(errno);

    if (msg_tmp)
      safe_strncat(msgbuf, strerror(errno), len - strlen(msgbuf));
    else
      safe_strncat(msgbuf, "Unknown error", len - strlen(msgbuf));
    }
  else if (code > PBSE_)
    {
    msg = pbse_to_txt(code);
    }
  else if (code > 0)
    {
    msg = strerror(code);
    }

  if (msg)
    {
    snprintf(msgbuf, len, "%s", msg);
    }

  return;
  }  /* END set_err_msg() */



static int dis_reply_write(

  int                 sfds,    /* I */
  struct batch_reply *preply)  /* I */

  {
  int              rc = PBSE_NONE;
  char             log_buf[LOCAL_LOG_BUF_SIZE];
  struct tcp_chan *chan = NULL;
  
  pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, 0);

  /* setup for DIS over tcp */
  if ((chan = DIS_tcp_setup(sfds)) != NULL)
    {
    /* send message to remote client */
    if ((rc = encode_DIS_reply(chan, preply)) ||
             (rc = DIS_tcp_wflush(chan)))
      {
      sprintf(log_buf, "DIS reply failure, %d", rc);

      log_event(PBSEVENT_SYSTEM, PBS_EVENTCLASS_REQUEST, __func__, log_buf);

      /* don't need to get the lock here because we already have it from process request */
      close_conn(sfds, FALSE);
      }

    DIS_tcp_cleanup(chan);
    }
  
  pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, 0);

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




/*
 * reply_send - Send a reply to a batch request, reply either goes to
 * remote client over the network:
 * Encode the reply to a "presentation element",
 * allocate the presenetation stream and attach to socket,
 * write out reply, and free ps, pe, and isoreply structures.
 *
 * Or the reply is for a request from the local server:
 * locate the work task associated with the request and dispatch it
 *
 * The request (and reply) structures are freed.
 * There are now two functions that do this.
 * reply_send_svr and reply_send_mom
 * reply_send is kept for compatibility purposes
 */

int reply_send(

  struct batch_request *request)  /* I (freed) */

  {
#ifdef PBS_MOM
  return(reply_send_mom(request));
#else
  return(reply_send_svr(request));
#endif
  }

#ifndef PBS_MOM
int reply_send_svr(
  
  struct batch_request *request)  /* I (conditionally freed) */

  {
  int   rc = 0;
  char  log_buf[LOCAL_LOG_BUF_SIZE];
  int   sfds = request->rq_conn;  /* socket */

  /* Handle remote replies - local batch requests no longer create work tasks */
  if ((sfds >= 0) &&
      (sfds != PBS_LOCAL_CONNECTION))
    {
    /* Otherwise, the reply is to be sent to a remote client */

    if (request->rq_noreply != TRUE)
      {
      rc = dis_reply_write(sfds, &request->rq_reply);

      if (LOGLEVEL >= 7)
        {
        sprintf(log_buf, "Reply sent for request type %s on socket %d",
          reqtype_to_txt(request->rq_type),
          sfds);

        log_record(PBSEVENT_JOB, PBS_EVENTCLASS_JOB, __func__, log_buf);
        }
      }
    }

  if (((request->rq_type != PBS_BATCH_AsyModifyJob) && 
       (request->rq_type != PBS_BATCH_AsyrunJob) &&
       (request->rq_type != PBS_BATCH_AsySignalJob)) ||
      (request->rq_noreply == TRUE))
    {
    free_br(request);
    }

  return(rc);
  }  /* END reply_send_svr() */
#endif /* PBS_MOM */




int reply_send_mom(

  struct batch_request *request)  /* I (freed) */

  {
  int      rc = 0;
  int      sfds = request->rq_conn;  /* socket */

  /* determine where the reply should go, remote or local */

  if (sfds == PBS_LOCAL_CONNECTION)
    {
    rc = PBSE_SYSTEM;
    }
  else if (sfds >= 0)
    {
    /* Otherwise, the reply is to be sent to a remote client */
    rc = dis_reply_write(sfds, &request->rq_reply);
    }
  free_br(request);
  return(rc);
  }  /* END reply_send_mom() */



/*
 * reply_free 
 * Free any sub-structures that might hang from the basic batch_reply structure. 
 * NOTE: the reply structure itself IS NOT FREED.
 *
 * @param prep - the batch_reply whose sub-structures we are conditionally freeing.
 */

void reply_free(

  struct batch_reply *prep)

  {
  struct brp_status  *pstat;
  struct brp_status  *pstatx;
  struct brp_select  *psel;
  struct brp_select  *pselx;

  if (prep->brp_choice == BATCH_REPLY_CHOICE_Text)
    {
    if (prep->brp_un.brp_txt.brp_str)
      {
      free(prep->brp_un.brp_txt.brp_str);
      prep->brp_un.brp_txt.brp_str = NULL;
      prep->brp_un.brp_txt.brp_txtlen = 0;
      }

    }
  else if (prep->brp_choice == BATCH_REPLY_CHOICE_Select)
    {
    psel = prep->brp_un.brp_select;

    while (psel)
      {
      pselx = psel->brp_next;
      free(psel);
      psel = pselx;
      }

    }
  else if (prep->brp_choice == BATCH_REPLY_CHOICE_Status)
    {
    pstat = (struct brp_status *)GET_NEXT(prep->brp_un.brp_status);

    while (pstat)
      {
      pstatx = (struct brp_status *)GET_NEXT(pstat->brp_stlink);
      free_attrlist(&pstat->brp_attr);
      free(pstat);
      pstat = pstatx;
      }
    }
  else if (prep->brp_choice == BATCH_REPLY_CHOICE_RescQuery)
    {
    free(prep->brp_un.brp_rescq.brq_avail);
    free(prep->brp_un.brp_rescq.brq_alloc);
    free(prep->brp_un.brp_rescq.brq_resvd);
    free(prep->brp_un.brp_rescq.brq_down);
    }

  prep->brp_choice = BATCH_REPLY_CHOICE_NULL;

  return;
  }  /* END reply_free() */



/*
 * set_reply_type()
 *
 * Sets the batch reply type, freeing any old things as needed
 * @param prep - the batch reply we're setting
 * @param type - the type of reply we're making
 */

void set_reply_type(

  struct batch_reply *prep,
  int                 type)

  {
  // Make sure we don't lose any pointers when we do this
  if (prep->brp_choice != BATCH_REPLY_CHOICE_NULL)
    reply_free(prep);

  prep->brp_choice = type;
  } // END set_reply_type()



/*
 * reply_ack - Send a normal acknowledgement reply to a request
 *
 * Always frees the request structure.
 */

void reply_ack(
    
  struct batch_request *preq)

  {
  set_reply_type(&preq->rq_reply, BATCH_REPLY_CHOICE_NULL);
  preq->rq_reply.brp_code    = PBSE_NONE;
  preq->rq_reply.brp_auxcode = 0;
  reply_send(preq);
  } // END reply_ack()



/*
 * req_reject - create a reject (error) reply for a request and send it
 *
 * Always frees the request structure.
 */

void req_reject(

  int                   code,      /* I */
  int                   aux,       /* I */
  struct batch_request *preq,      /* I */
  const char          *HostName,  /* I (optional) */
  const char          *Msg)       /* I (optional) */

  {
  char msgbuf[ERR_MSG_SIZE + 256 + 1];
  char msgbuf2[ERR_MSG_SIZE + 256 + 1];
  char log_buf[LOCAL_LOG_BUF_SIZE];

  set_err_msg(code, msgbuf, sizeof(msgbuf));

  snprintf(msgbuf2, sizeof(msgbuf2), "%s", msgbuf);

  if ((HostName != NULL) && (*HostName != '\0'))
    {
    snprintf(msgbuf, sizeof(msgbuf), "%s REJHOST=%s",
      msgbuf2,
      HostName);
    
    snprintf(msgbuf2, sizeof(msgbuf2), "%s", msgbuf);
    }

  if ((Msg != NULL) && (*Msg != '\0'))
    {
    snprintf(msgbuf, sizeof(msgbuf), "%s %s%s",
      msgbuf2,
      PBS_MSG_EQUAL,
      Msg);

    /* NOTE: Don't need this last snprintf() unless another message is concatenated. */
    }

  sprintf(log_buf, "Reject reply code=%d(%s), aux=%d, type=%s, from %s@%s",
    code,
    msgbuf,
    aux,
    reqtype_to_txt(preq->rq_type),
    preq->rq_user,
    preq->rq_host);

  log_event(PBSEVENT_DEBUG,PBS_EVENTCLASS_REQUEST,"req_reject",log_buf);

  preq->rq_reply.brp_auxcode = aux;

  reply_text(preq, code, msgbuf);

  return;
  }  /* END req_reject() */




/*
 * reply_badattr - create a reject (error) reply for a request including
 * the name of the bad attribute/resource
 */

void reply_badattr(

  int                   code,
  int                   aux,
  svrattrl             *pal,
  struct batch_request *preq)

  {
  int   i = 1;
  char  msgbuf[ERR_MSG_SIZE+1];

  set_err_msg(code, msgbuf, sizeof(msgbuf));

  while (pal)
    {
    if (i == aux)
      {
      int len = strlen(msgbuf);

      if (pal->al_resc)
        snprintf(msgbuf + len, sizeof(msgbuf) - len, " %s.%s", pal->al_name, pal->al_resc);
      else
        snprintf(msgbuf + len, sizeof(msgbuf) - len, " %s", pal->al_name);

      break;
      }

    pal = (svrattrl *)GET_NEXT(pal->al_link);

    ++i;
    }

  reply_text(preq, code, msgbuf);

  return;
  }  /* END reply_badattr() */




/*
 * reply_text - return a reply with a supplied text string
 */

void reply_text(

  batch_request *preq,
  int            code,
  const char    *text) /* I */

  {
  if ((text != NULL) &&
      (text[0] != '\0'))
    {
    set_reply_type(&preq->rq_reply, BATCH_REPLY_CHOICE_Text);
    preq->rq_reply.brp_un.brp_txt.brp_str    = strdup(text);
    preq->rq_reply.brp_un.brp_txt.brp_txtlen = strlen(text);
    }
  else
    {
    set_reply_type(&preq->rq_reply, BATCH_REPLY_CHOICE_NULL);
    }

  preq->rq_reply.brp_code    = code;
  preq->rq_reply.brp_auxcode = 0;

  reply_send(preq);

  return;
  }  /* END reply_text() */




/*
 * reply_jobid - return a reply with the job id, used by
 * req_queuejob(), req_rdytocommit(), and req_commit()
 *
 * Always frees the request structure.
 */

int reply_jobid(

  struct batch_request *preq,
  char   *jobid,
  int    which)

  {
  set_reply_type(&preq->rq_reply, which);
  preq->rq_reply.brp_code    = 0;
  preq->rq_reply.brp_auxcode = 0;

  snprintf(preq->rq_reply.brp_un.brp_jid, sizeof(preq->rq_reply.brp_un.brp_jid), "%s", jobid);

  return(reply_send(preq));
  }  /* END reply_jobid() */

