/*
**++
**  FACILITY:
**      NEWSLOCK
**
**  MODIFICATION HISTORY:
**	V6.1b9	17-Sep-1994     Mark Martinec   mark.martinec@ijs.si
**	  - code cleanup to preserve the read-only nature of string literals
**	    (strategically placed 'const' attribute to string parameters)
**	V6.1b9	21-Mar-1995	Mark Martinec   mark.martinec@ijs.si
**	  - replaced $trnlnm("NEWS_STOP") with news_getenv(...,1)
**	  - if the logical name NEWS_STOP contains text, it is included
**	    with the shutdown message
**	V6.1b11 14-Apr-1997	Mark Martinec   mark.martinec@ijs.si
**	  - replace call to strcpy with safer strnncpy
**	  - change %s to %.<n>s in sprintf to guard against overwriting buffer
**--
**/

#ifdef vaxc
#module NEWSLOCK "V6.2"
#endif

#ifdef NO_SYNC_FILE_SUPPORT
init_lock() { return(1); }

int acquire_exclusive_lock() { return(1); }

#else
#define _NEWSLOCK_C
#define module_name "NEWSLOCK"

#include "newsinclude.h"
#include "newsextern.h"

#if NAKED_INCLUDES
#include brkdef
#include lckdef
#include psldef
#include stsdef
#else
#include <brkdef.h>
#include <lckdef.h>
#include <psldef.h>
#include <stsdef.h>
#endif

#define LOCKNAME	"NEWS_LOCK"
#define NEWS_STOP	"NEWS_STOP"
#define SERVER_REQ      33
#define BLAST_REQ       34
#define ENQ_REQ		35
#define CHECK_LOGICAL_INTERVAL	"0 00:15:00.00"
#define LAST_SHUT		"0 00:05:00.00"
#define ENQ_LOCK		"0 00:00:30.00"
#define ENQCALL_INTERVAL	"0 00:00:01.00"

static int mins_5[2], mins_15[2], secs_30[2];
static char stopped_message[LNM$C_NAMLENGTH+1] = "";

static $DESCRIPTOR_CONST(logical_interval,CHECK_LOGICAL_INTERVAL);
static $DESCRIPTOR_CONST(last_shut,LAST_SHUT);
/* static $DESCRIPTOR_CONST(enqcall_interval,ENQCALL_INTERVAL); */
static $DESCRIPTOR_CONST(enqw_interval,ENQ_LOCK);
static $DESCRIPTOR_CONST(lock_desc, LOCKNAME );                   

/*
 *      Exit Handler Control Block
 */
static struct argument_block {
  int forward_link;
  void (*exit_routine)();
  int arg_count;
  int *status_address;
  int exit_status;
  } exit_block = {0, NULL, 1, &exit_block.exit_status, 0};

typedef volatile struct {
  int lksb_l_status;
  int lksb_l_lock_id;
  int lksb_l_value;
  } LKSB_TYPE;

static LKSB_TYPE lksb;

static void closeoff()
{
  $DESCRIPTOR_CONST(msg, "NEWS System shutdown - forced exit.");
  closefiles();
  lib$put_output(&msg);
  sys$exit(STS$M_INHIB_MSG|SS$_DEADLOCK);
}

static void notify_user(message)
const char *message;
{
  short item = JPI$_TERMINAL;
  long mode = JPI$K_BATCH;
  char terminal_name[64];
  $DESCRIPTOR_CONST(msgdesc,"");
  $DESCRIPTOR_CONST(terminal, terminal_name);

  msgdesc.dsc$a_pointer = message;
  msgdesc.dsc$w_length = strlen(message);
  lib$getjpi(&item, 0, 0, 0, &terminal, &terminal.dsc$w_length);
  item = JPI$_MODE;
  lib$getjpi(&item, 0, 0, &mode, 0, 0);
  if (terminal.dsc$w_length)
    sys$brkthru(0, &msgdesc, &terminal, BRK$C_DEVICE, 0, 0, 
		BRK$M_SCREEN|BRK$M_BOTTOM, 0, 0, 0, 0);
  else
    if (mode >= JPI$K_BATCH)
      lib$put_output(&msgdesc);
}

static void enqw_timeout()
{
  char msgtext[512];

  sys$cantim(SERVER_REQ,0);
  sys$setimr(0,mins_5,closeoff,SERVER_REQ);

  sprintf(msgtext, "NEWS is currently locked - forced exit (%.400s)",
          ((news_lock_alarm == 1) ? "SYSLCK" : stopped_message));
  notify_user(msgtext);
  if (kid_valid)  { 
    /* the 5 minute timer started above will exit this case in 5 minutes */
    smg$cancel_input(&kid);
    return;
    }
  if (!nntp_process)
    sys$exit(STS$M_INHIB_MSG|SS$_DEADLOCK);
}

static int lock_blocking_ast()
{

  if (news_lock_alarm) return(1);
  news_lock_alarm = 1;
  /*
   * Be nice, and let other code have a chance to clean up first by noticing
   * the changed value of news_lock_alarm (or let the user exit).
   */
  notify_user("\07NEWS is shutting down, please exit SOON!\07");
  sys$setimr(0,mins_15,enqw_timeout,BLAST_REQ);
  return 0;
}


/*

 * This routine merely checks to see if the "stop" logical is defined, and
 * returns true (1) if it is, regardless of what it's value is.
 * If the logical name value looks like some text, it is saved.
 */
static int stop_logical_defined()
{
  char *lnm = news_getenv(NEWS_STOP,1);
  if (lnm) strnncpy(stopped_message,sizeof(stopped_message), lnm,-1, 0);
  else *stopped_message = '\0';
  if (strlen(stopped_message) <= 4) strcpy(stopped_message,"LOGNAM");
  return (lnm != NULL);
}

static int check_stop_logical()
{
  if (news_lock_alarm) return(1);

  if (stop_logical_defined()) {
    news_lock_alarm = 2;
    sys$setimr(0,mins_15,enqw_timeout,SERVER_REQ);
    }
  else sys$setimr(0,mins_15,check_stop_logical,SERVER_REQ);
  return 0;
}

static int gotlock_ast()
{
  news_lock_alarm = 0;
  sys$cantim(SERVER_REQ,0);
  sys$wake(0,0);
  return 0;
}

static int notlock_timeout()   /* SERVER_REQ timer request ID */
{
  news_lock_alarm = 1;
  enqw_timeout();
  sys$wake(0,0);
  return 0;
}

static void lock_exit_routine(exit_status)
int *exit_status;
{
  if ((!news_lock_alarm) || (((*exit_status) & 1) == 0))
    return;
  sys$canexh(&exit_block);
  sys$exit(1);
}

int init_lock()
{
  static int init_lock_called = 0;
  int curprivs[2], modprvs[2], item = JPI$_CURPRIV;

  if (init_lock_called++) return(news_lock_alarm);
  sys$bintim(&enqw_interval,secs_30);
  sys$bintim(&logical_interval,mins_15);
  sys$bintim(&last_shut,mins_5);
  exit_block.exit_routine = lock_exit_routine;
  sys$dclexh(&exit_block);
  /*
   * Only mess with the lock stuff, IF we have SYSLCK priv.
   */
  if ((lib$getjpi(&item,0,0,curprivs,0,0) & 1) && (curprivs[0] & PRV$M_SYSLCK)) {
    news_lock_alarm = 1;
    sys$setimr(0,secs_30,notlock_timeout,SERVER_REQ);
    sys$enq(0,LCK$K_PRMODE,&lksb,LCK$M_SYSTEM,&lock_desc,0,
	    gotlock_ast,0,lock_blocking_ast,PSL$C_USER,0);
    sys$hiber();
    /*
     * We now turn off SYSLCK priv since we don't need it for anything else.
     */
    modprvs[0] = PRV$M_SYSLCK;
    modprvs[1] = 0;
    sys$setprv(0,modprvs,0,0);
    /*
     * If we couldn't get the Protected Read Lock, return that fact now.
     */
    if (news_lock_alarm) return(news_lock_alarm);
    }
  /*
   * In the event that someone is still using an explicit DCL level 
   * definition of the NEWS_LOCK logical mechanism, we accomodate that also
   */
  if (stop_logical_defined()) {
    news_lock_alarm = 2;
    enqw_timeout();
    }
  check_stop_logical();
  return(news_lock_alarm);
}

static void
busy_timeout()
{
  sys$cantim(SERVER_REQ, 0);
  sys$deq(lksb.lksb_l_lock_id, 0, PSL$C_USER, LCK$M_CANCEL);
}

static void
busy_message()
{
  $DESCRIPTOR_CONST(msg, "Waiting to syncronize access to NEWS database");
  lib$put_output(&msg);
}

int acquire_exclusive_lock(timeout)
int timeout; /* MAX Seconds to wait for NEWS LOCK */
{
  int status;
  int one_sec = -10000000;/* One Second Delta */
  int delta[2] = {0, 0};

  status = sys$enqw(0,LCK$K_NLMODE,&lksb,LCK$M_SYSTEM,&lock_desc,
		    0,0,0,0,PSL$C_USER,0);
  if (status & 1)
    status = lksb.lksb_l_status;
  if (!(status & 1))
    return(status);
  if (timeout) {
    lib$emul(&timeout, &one_sec, delta, delta);
    sys$setimr(0,delta,busy_timeout,SERVER_REQ);
    timeout = 10;
    delta[0] = delta[1] = 0;
    lib$emul(&timeout, &one_sec, delta, delta);
    sys$setimr(0,delta,busy_message,SERVER_REQ);
  }
  status = sys$enqw(0,LCK$K_EXMODE,&lksb,LCK$M_SYSTEM|LCK$M_CONVERT,&lock_desc,
		    0,0,0,0,PSL$C_USER,0);
  if (status & 1)
    status = lksb.lksb_l_status;
  sys$cantim(SERVER_REQ,0);
  return(status);
}

#endif	/*NO_SYNC_FILE_SUPPORT*/
