/*
**++
**  FACILITY:
**      NEWSUTIL2
**
**  ABSTRACT:
**      This module contain common support routines used by all other modules
**      of NEWS.
**
**  AUTHOR:
**      Geoff Huston
**
**  COPYRIGHT:
**      Copyright © 1988,1989,1990,1991,1992
**
**  VERSION:
**	V6.1    21-Jul-92	Bill Fenner <wcf@ecl.psu.edu>
**	  - improved error reporting
**	V6.1b7	 8-Aug-1993  bailey@genetics.upenn.edu
**	  - added mem_fail() as part of new mem alloc routines
**	V6.1b8   9-Sep-1993  mark.martinec@ijs.si
**	 - replace cli_catch() with more general news_condition_handler()
**	V6.1b8  24-Jan-1994  mark.martinec@ijs.si
**       - new routine smg_put_chars to simplify calls to smg$put_chars
**         and to avoid numerous problems where nonprintable characters
**         appear in text or headers of news items;
**       - display_brdcst(): special handling for multi-line messages
**         and for BELL characters included in the message;
**       - fudge with 'last_cond' in news_condition_handler to avoid
**	   bug in VMSMAIL (incorrect handling of SS$_UNWIND)
**	V6.1b8  17-Mar-1994  mark.martinec@ijs.si
**	 - modify gen_id() to avoid trying (and failing) to read NEWS.GROUPS
**	   file when run as a client
**	V6.1b8  21-Mar-1994  mark.martinec@ijs.si
**	 - do_quit() and do_abort() now call closefiles_opt() instead
**	   of having their own modified copy of code from closefiles().
**	   This now also handles properly the case where ANU as a client
**	   wanted to close nonexisting ITEM and GROUPS files on Quit.
**	V6.1b8  20-Apr-1994  mark.martinec@ijs.si
**	 - replaced on_error_continue (boolean) with on_error (multivalued)
**	V6.1b8  19-May-1994  mark.martinec@ijs.si
**	 - look for a file or logical name in smg$load_key_defs() corectly
**	V6.1b9  22-May-1994  Charles Bailey  bailey@genetics.upenn.edu
**	 - fix memory usage bug which snuck into 6.1b9 release with another patch
**	V6.1b9  25-May-1994  Pat Rankin  rankin@eql.caltech.edu
**	 - avoid use of `union SMGDEF'; module $SMGDEF is hopelessly mangled
**	V6.1b9   1-Jun-1994  Mark Martinec   mark.martinec@ijs.si
**	 - ignore SMG$_NONBRDMSG messages in display_brdcst()
**	V6.1b9  20-Jun-1994  Mark Martinec   mark.martinec@ijs.si
**	 - don't take MAIL$_TEXT seriously in news_condition_handler()
**	 - introduce variable 'signalled_error_count' in order to limit
**	   the infinite loops through the condition handler
**	V6.1b9  23-Jun-1994  Mark Martinec   mark.martinec@ijs.si
**	  - applied code cleanup patch (gcc) by Pat Rankin (rankin@eql.caltech.edu)
**	V6.1b9	17-Aug-1994     Mark Martinec   mark.martinec@ijs.si
**	  - code cleanup to make it compile under gcc 2.6.0 with full
**	    warnings reporting turned on - with no or very few harmless warnings
**	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)
**	  - in map_items: assure that negative number is not assigned
**	    to unsigned gap->grp_iasize -- this led later to
**	    news_malloc(very_very_large_number) and subsequent crash
**	V6.1b10	30-May-1995	Mark Martinec   mark.martinec@ijs.si
**	  - write routine fit_line to cope with the problem of truncated
**	    'Unread:' count in the 'Group:' header line of the items display
**      V6.1b10 27-Apr-1995  Bob Sloane sloane@kuhub.cc.ukans.edu
**        - added Kent Covert's definitions for left and right arrow keys.
**	V6.1b10 12-Sep-1996	Mark Martinec   mark.martinec@ijs.si
**	  - split module NEWSUTILITY.C into NEWSUTIL[1,2,3].C
**	    because of its size (4800 lines) which made testing tedious.
**	    Also split revision history comments.
**	    NEWSUTIL1 - handles mostly application code, file i/o,
**	                groups and items-related data structures, NNTP, ...
**	    NEWSUTIL2 - more general-purpose routines, formatting,
**	                SMG, error handling, ...
**	    NEWSUTIL3 - support routines needed by NEWS and NNTP_SERVER
**	                (like gen_id() and its support routines)
**	V6.1b11 10-May-1997	Mark Martinec   mark.martinec@ijs.si
**	  - util_dir: allow group name size to be at most GRPNAMELEN_F chars,
**	    thus accepting also the not necessarily '\0'-terminated
**	    newsgrp.grp_name as a parameter.
**	V6.2.0   1-Aug-1997	Mark Martinec   mark.martinec@ijs.si
**	  - minor modifications to init_nonprintable_char and init_nonprintable
**	  - change smg_put_chars() and make_printable_dsc()
**	    to handle underlined text ('___^H^H^H^bla' or '_^Hb_^Hl_^Ha')
**	    as recommended in the son-of-rfc1036 draft:
**	      Reading agents SHOULD recognize underlining and translate it
**	      to the appropriate commands for devices that support it.
**	    The following is not yet handled:
**	      Formfeed signifies a point at which a reading agent SHOULD pause 
**	      and await reader interaction before displaying further text.
**--
**/

#ifdef vaxc
#module NEWSUTIL2 "V6.2"
#endif

#define _NEWSUTIL2_C
#define module_name "NEWSUTIL2"

#if NAKED_INCLUDES
#include stsdef
#include libdef
#include otsdef
#include shrdef
#include strdef
#else
#include <stsdef.h>
#include <libdef.h>
#include <otsdef.h>
#include <shrdef.h>
#include <strdef.h>
#endif

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

/* err_msg is filled by putmsg_action_routine with
  (possibly multi-line) message provided by $PUTMSG;
  lines are separated by '\0';  it is guaranteed
  that even the last (possibly truncated) line is
  terminated by '\0' within the declared array size */
static volatile char err_msg[1024];
static volatile int err_msg_l     = 0;  /* current length of err_msg */
static volatile int err_msg_lines = 0;  /* number of lines in err_msg */


/*
 *  nosysprv
 *
 *  Turn off image installed sysprv - BUT if user already had sysprv,
 *  then leave it on (as there is no change in user functionality)
 */

void nosysprv()
{
  unsigned int authprivs[2], msysprv[2] ;
  int item = JPI$_PROCPRIV;
  int status;

  msysprv[0] = PRV$M_SYSPRV;
  msysprv[1] = 0;
  _c$cks(lib$getjpi(&item,0,0,&authprivs[0],0,0));
  if (!(authprivs[0] & PRV$M_SYSPRV)) _c$cks(sys$setprv(0,msysprv,0,0));
  sysprv_off = 1;
}

/*
 *  sysprv
 *
 *  Turn sysprv on as a temp priv - assumes image was installed with SYSPRV
 */

void sysprv()
{
  unsigned int msysprv[2];
  int status;

  msysprv[0] = PRV$M_SYSPRV;
  msysprv[1] = 0;
  _c$cks(sys$setprv(1,msysprv,0,0));
  sysprv_off = 0;
}

/*
 *  fit_line
 *
 *  Modify the string 'str' to fit into a suggested 'width'
 *  by shrinking/expanding whitespace (glue).
 *  If 'width' < 0 the string width is left at its natural size.
 *
 *  Glue may be given implicitly (spaces in the string) or explicitly
 *  as '[nxc]' where n, x and c are single digits which represent
 *  the glue natural size, stretchability and shrinkability respectively.
 *  The units are 'the width of one character', but for values >5
 *  a progressive scale is used: see array 'unit_factors'.
 *
 *  Unlike in TeX, multiple consecutive spaces are not shrunk into one space.
 *
 *  Returns the difference: resulting_string_length - requested_length
 *  which is normally 0, but may be different if stretchability/shrinkability
 *  constraints of the given string would be exceeded.
 *
 *  The algorithm is a simplified implementation of the glue-setting
 *  algorithm described in: D. Knuth: The TeXbook, chapter "Glue".
 *
 *  (The algorithm used is probably an overkill, but does the tough job
 *  very nicely and may find other uses in ANU-NEWS.  The problem that it
 *  needed to solve in the first place was to fit the 'Group' item header line
 *  in display, which was often truncated not showing the 'Unread:' value.)
 *      May 1995,  Mark Martinec  <mark.martinec@nsc.ijs.si>
 */
int fit_line(str,str_l,str_m,width)
  char *str;  /* string to be modified (not necessarily '\0'-terminated,
                   but guaranteed to end up '\0'-terminated within str_m
                   upon return */
  int str_l;  /* initial string length if known, otherwise -1 */
  int str_m;  /* maximum allocated string size (including final '\0') */
  int width;  /* required final string length (should be <= str_m-1)  */
              /* N.B.: final string may be shorter or longer than width,
                       depending on its stretchability/shrinkability */
{
#define div_rounded(a,b) (((a)>=0 ? (a)+(b)/2 : (a)-(b)/2) / (b))
#define div_floor(a,b)   (tmp_q = (a)/(b), (tmp_q*(b) > (a) ? tmp_q-1 : tmp_q))
#define gunits(n) (10*unit_factors[(unsigned)(n) - (unsigned)'0'])
#define MAX_GLUE 48                               /* max number of glue blobs */

  static const int unit_factors[10] = {0,1,2,3,4,5,10,50,1000,100000};
  static int sfcode_initialized = 0;
  static short sfcode[256];               /* space factors for each character */
  int nonglue_characters, glue_natural_width, entire_natural_width, stretch_by;
  int total_glue_spaces, last_was_glue, j, k, tmp_q;
  int space_factor;     /* controls the default interword glue, normally 1000 */
  int initial_spaces;   /* flag to make initial spaces rigid */
  int glue_l;                 /* number of glue blobs */
    /* 10 glue units represent the width of one character */
  int glue_index[MAX_GLUE];   /* position of glue within the string */
  int glue_size[MAX_GLUE];    /* glue natural size */
  int glue_stretch[MAX_GLUE]; /* glue stretchability */
  int glue_shrink[MAX_GLUE];  /* glue shrinkability */
  char *cp;

  str_l = (str_l>=0) ? str_l : strlen(str);
  str_l = min(str_l,str_m);

  if (!sfcode_initialized) {
    for (k=0; k<256; k++) sfcode[k] = 1000;
    for (k='A'; k<='Z'; k++) sfcode[k] = 999;
    for (k=192; k<=222; k++) sfcode[k] = 999;
    sfcode[')'] = sfcode[']'] = sfcode['}'] = sfcode['"'] = sfcode['\''] = 0;
    sfcode['.'] = sfcode['!'] = sfcode['?'] = 3000;
    sfcode[';'] = 2000; sfcode[','] = 1100; sfcode_initialized = 1;
    }
  initial_spaces = 1; space_factor = 1000;
  nonglue_characters = glue_l = last_was_glue = 0;
  for (j=0,cp=str; j<str_l; ) {     /* parse string into text and glue chunks */
    int gsize, gstretch, gshrink;
    if (*cp != ' ' && *cp != '[') {                 /* save normal characters */
      str[nonglue_characters++] = *cp; last_was_glue = 0;
      if (space_factor<1000 && sfcode[(unsigned)*cp]>1000) space_factor = 1000;
      else if (sfcode[(unsigned)*cp]) space_factor = sfcode[(unsigned)*cp];
      j++; cp++; initial_spaces = 0;
      }
    else {                                                            /* glue */
      if (*cp == ' ') {                      /* space represent implicit glue */
        j++; cp++; gsize = 10; if (space_factor >= 2000) gsize += 3;
        if (initial_spaces) { gstretch = gshrink = 0; }
        else { gstretch = div_rounded(7*space_factor, 1000);
               gshrink  = div_rounded(3*1000, space_factor); }
        }
      else if (*cp == '[') {     /* explicit glue - parse glue syntax:  [nnn] */
        j++; cp++; gsize = gstretch = gshrink = 0;
        if (j<str_l && isdigit(*cp)) { gsize   = gunits(*cp); j++; cp++; }
        if (j<str_l && isdigit(*cp)) { gstretch= gunits(*cp); j++; cp++; }
        if (j<str_l && isdigit(*cp)) { gshrink = gunits(*cp); j++; cp++; }
        while (j<str_l && *cp!=']') { j++; cp++; }
        if    (j<str_l && *cp==']') { j++; cp++; }
        initial_spaces = 0;
        }
      space_factor = 1000;
      if (glue_l >= MAX_GLUE)/* in emergency treat the rest as a rigid string */
        { str[nonglue_characters++] = ' '; last_was_glue = 0; }
      else {
        if (!last_was_glue) {
          glue_l++; glue_index[glue_l-1] = nonglue_characters;
          glue_size[glue_l-1]=glue_stretch[glue_l-1]=glue_shrink[glue_l-1]=0;
          }
        glue_stretch[glue_l-1] += gstretch; glue_shrink[glue_l-1] += gshrink;
        glue_size[glue_l-1] += gsize; last_was_glue = 1;
        }
      }
    }
  glue_natural_width = 0;
  for (k=0; k<glue_l; k++) glue_natural_width += glue_size[k];
  entire_natural_width = 10*nonglue_characters + glue_natural_width;
  if (width < 0) width = div_rounded(entire_natural_width,10);
  width = min(width,str_m-1);

 /* setting (hardening) glue: calculate final glue_size */
  stretch_by = 10*width - entire_natural_width;        /* 0.1 char size units */
  if (stretch_by > 0) {                                            /* stretch */
    int total_stretch = 0;
    for (k=0; k<glue_l; k++) total_stretch += glue_stretch[k];
    if (total_stretch != 0)
      for (k=0; k<glue_l; k++)
        glue_size[k] += div_rounded(stretch_by*glue_stretch[k], total_stretch);
    }
  else if (stretch_by < 0) {                                        /* shrink */
    int total_shrink = 0;
    for (k=0; k<glue_l; k++) total_shrink += glue_shrink[k];
    if (total_shrink != 0)
      for (k=0; k<glue_l; k++)
        glue_size[k] -= min(glue_shrink[k],
                         div_rounded(-stretch_by*glue_shrink[k], total_shrink));
    }

 /* adjust glue size (0.1 ch units) into an integer number of spaces */
  total_glue_spaces = 0;
  for (k=0; k<glue_l; k++) total_glue_spaces += div_floor(glue_size[k],10);
  while (nonglue_characters+total_glue_spaces < width) {
    int fract, fract_at_best = 0, weight, max_weight = -10, best_ind = -1;
    for (k=0; k<glue_l; k++) {
      if ( (fract = glue_size[k] - 10*div_floor(glue_size[k],10)) != 0) {
        weight = fract + (abs(glue_size[k]) > 30 ? 7 : 0);
        if (weight > max_weight)
          { max_weight = weight; fract_at_best = fract; best_ind = k; }
        }
      }
    if (best_ind < 0) break;
    glue_size[best_ind] += (10-fract_at_best); total_glue_spaces++;
    }
 /* format the string inserting whitespace as calculated */
  { int j, k, dst_l = 0, from = 0;
    int ofs = str_m - nonglue_characters;
    memmove(&str[ofs],str,nonglue_characters);    /* flush string fully right */
    for (k=0; k<glue_l; k++) {                       /* insert glue as spaces */
      while (from < glue_index[k]) str[dst_l++] = str[ofs + from++];
      if (glue_size[k] < 0) dst_l = max(0, dst_l+div_floor(glue_size[k],10));
      else { for (j=div_floor(glue_size[k],10); j>=1; j--) str[dst_l++] = ' '; }
      }
    while (from < nonglue_characters) str[dst_l++] = str[ofs + from++];
    str[min(dst_l,str_m-1)] = '\0';
    return dst_l-width;
  }
}

/* auxiliary routine to make_printable_dsc(); */
/* Saves replacement string in a static array and returns pointer to it. */
char *init_nonprintable_char(str)
  char *str;
{
  static int repl_l = 0;
  static char replacement[300]; /* replacement strings for nonprintable chars */
  if (!str) return NULL;
  else {
    char *addr = &replacement[repl_l];
    strnncat(replacement,sizeof(replacement),repl_l,
             str,strlen(str)+1, &repl_l);
    return addr;
    }
}

/* auxiliary routine to make_printable_dsc() */
void init_nonprintable(nonprintable)
  char *nonprintable[256];
{
  int nxt;
#define SS(str) nonprintable[nxt++] = init_nonprintable_char(str);

  { int j; for (j=0; j<sizeof(nonprintable); j++) nonprintable[j] = NULL; }
  nxt = 0;
  SS("NUL");  SS("SOH");  SS("STX");  SS("ETX");
  SS("EOT");  SS("ENQ");  SS("ACK");  SS("BEL");
  SS("BS");   SS(0);      SS("LF");   SS("VT");
  SS("FF");   SS("CR");   SS("SO");   SS("SI");
  SS("DLE");  SS("XON");  SS("DC2");  SS("XOF");
  SS("DC4");  SS("NAK");  SS("SYN");  SS("ETB");
  SS("CAN");  SS("EM");   SS("SUB");  SS("ESC");
  SS("FS");   SS("GS");   SS("RS");   SS("US");
  nxt = 127;  SS("DEL");
  nxt = 128;
  SS("X80");  SS("X81");  SS("X82");  SS("X83");
  SS("IND");  SS("NEL");  SS("SSA");  SS("ESA");
  SS("HTS");  SS("HTJ");  SS("VTS");  SS("PLD");
  SS("PLU");  SS("RI");   SS("SS2");  SS("SS3");
  SS("DCS");  SS("PU1");  SS("PU2");  SS("STS");
  SS("CCH");  SS("MW");   SS("SPA");  SS("EPA");
  SS("X98");  SS("X99");  SS("X9A");  SS("CSI");
  SS("ST");   SS("OSC");  SS("PM");   SS("APC");
  nxt = 255;  SS("XFF");
#undef SS
}

/*
 *  make_printable_dsc
 *
 *  Returns the address of a statically allocated string descriptor,
 *  pointing to a string which is guaranteed to contain only
 *  printable characters (and TABs).
 *  This may be the original string ('text') or a filtered copy of it
 *  in the statically allocated memory.
 */
struct dsc$descriptor_const_s *make_printable_dsc(
  const char *text,
  int *used_l, /* returns the number of characters in text that were processed*/
  int *more,   /* returns 1 if text was only partially processed,
                  0 if nothing left to do */
  int *rend,   /* on input: current SMG rendition;
                  returns: new rendition (like underline) to be
                  applied when printing the not-yet-processed text */
  int *ul,     /* number of underline characters held up at the tail */
  int *req_ul) /* number of following characters to be underlined */
{
  static struct dsc$descriptor_const_s text_dsc =
    {0,DSC$K_DTYPE_T,DSC$K_CLASS_S,0};
  static char *nonprintable[256];  /* array of pointers to replacement strings:
                                      NULL if printable, otherwise
                                      pointer to replacement string */
  static int printable_initialized = 0;
  int any_nonprintable, text_l;
  const char *s;
  char tmp[1024];

  if (!printable_initialized)
    { init_nonprintable(nonprintable); printable_initialized = 1; }
  /* first make a quick check for non-printable characters;
     if all goes well, we can use the original text unmodified
     and save us some work */
  any_nonprintable = 0;
  for (s=text, text_l=0; *s; text_l++)
    if (nonprintable[(unsigned char) *(s++)]) any_nonprintable = 1;
  if (!any_nonprintable && !*ul && !*req_ul) {
    text_dsc.dsc$a_pointer = text; text_dsc.dsc$w_length = text_l;
    *used_l = text_l; *more = 0;
    }
  else {
    int ichr, tmp_l = 0;
    *more = 1;
    for (s=text,*used_l=0; (ichr=(unsigned char)*s); s++,(*used_l)++) {
      if      (*s == '\b' && *ul)      { (*ul)--; (*req_ul)++; }
      else if (*s == '_'  && !*req_ul) { (*ul)++; }
      else if (*req_ul>0 && !(*rend & SMG$M_UNDERLINE))
        { *rend |= SMG$M_UNDERLINE; break; }
      else if (*req_ul==0 && (*rend & SMG$M_UNDERLINE))
        { *rend &= ~SMG$M_UNDERLINE; break; }
      else {
        for (; *ul>0; (*ul)--) {  /* print pending underlines which were
                                     not converted to underline requests */
          if (tmp_l >= sizeof(tmp)) break;
          tmp[tmp_l++] = '_';
          if (*req_ul>0) {
            (*req_ul)--; news_assert(*rend & SMG$M_UNDERLINE);
            if (!*req_ul) { *rend &= ~SMG$M_UNDERLINE; break; }
            }
          }
        if (!nonprintable[ichr]) {
          if (tmp_l >= sizeof(tmp)) break;
          tmp[tmp_l++] = (char) ichr;
          }
        else {
          int j = strlen(nonprintable[ichr]);
          if (tmp_l+2+j > sizeof(tmp)) break;
          tmp[tmp_l++] = '<';
          strcpy(&tmp[tmp_l],nonprintable[ichr]);  tmp_l += j;
          tmp[tmp_l++] = '>';
          }
        if (*req_ul>0) { (*req_ul)--; news_assert(*rend & SMG$M_UNDERLINE); }
        }
      }
    if (!ichr && (*ul || *req_ul)) {
      /* process remaining underlines and underline requests */
      if (*req_ul>0 && !(*rend & SMG$M_UNDERLINE))
        *rend |= SMG$M_UNDERLINE;
      else if (*req_ul==0 && (*rend & SMG$M_UNDERLINE))
        *rend &= ~SMG$M_UNDERLINE;
      else if (*req_ul>0) {
        for (; *req_ul>0; (*req_ul)--) {  /* print pending underline requests */
          if (tmp_l >= sizeof(tmp)) break;
          if (*ul>0) { tmp[tmp_l++] = '_'; (*ul)--; } else tmp[tmp_l++] = ' ';
          }
        }
      else {
        for (; *ul>0; (*ul)--) {  /* print pending underlines which were
                                     not converted to underline requests */
          if (tmp_l >= sizeof(tmp)) break;
          tmp[tmp_l++] = '_';
          }
        }
      }
    if (!ichr && !*ul && !*req_ul) *more = 0;
    text_dsc.dsc$a_pointer = tmp; text_dsc.dsc$w_length = tmp_l;
    }
  return &text_dsc;
}

/*
 * Calls SMG$PUT_CHARS, simplifying parameter passing
 * and taking care that nonprintable characters are not passed to SMG.
 *
 * Numeric parameters are passed by value  (not by ref as for SMG routine),
 * ASCIZ string is passed by reference (not by descriptor).
 */
void smg_put_chars(vdispl_id,text,row,column,flags,rendition)
  int vdispl_id, row, column, flags, rendition;
  const char *text;
{
  struct dsc$descriptor_const_s *text_dsc;
  int status;
  int more;       /* more unprocessed text still in the input buffer */
  int xrend = 0;  /* SMG rendition bits to be set for the unprocessed text
                     (to be applied with the next call) */
  int used_l;     /* length of the processed part of string,
                     as returned by make_printable_dsc() */
  int ul = 0;     /* number of underline characters held up at the tail */
  int req_ul = 0; /* number of following characters to be underlined */

  for (more=1;  more;  text+=used_l) {
    int r = rendition | xrend;      /* take xrend from the previous iteration */
    text_dsc = make_printable_dsc(text, &used_l, &more, &xrend, &ul, &req_ul);
    if (text_dsc->dsc$w_length>0 || row>=0 || column>=0)
      _c$cks(smg$put_chars(&vdispl_id,text_dsc,
                           row<0 ? 0 : &row,  column<0 ? 0 : &column,
                           &flags, &r, 0,0));
    column = -1;    /* next time in the loop continue from the current column */
    }
}


/*
 *  clear_err_line
 *
 *  error line management
 */

void clear_err_line()
{
  int status;
  if (smg_active) _c$cks(smg$erase_line(&trailer_vd,c$ac(3),c$ac(1)));
}


/*
 *  create_keydefs
 *
 *  Create key definitions
 */

void create_keydefs()
{

#define ds(s) c$dsc(s)
#define term c$ac(SMG$M_KEY_TERMINATE)
#define tmne c$ac(SMG$M_KEY_TERMINATE+SMG$M_KEY_NOECHO)
#define gold ds("GOLD")
#define akd(b,c,d,e,f) _c$cks(smg$add_key_def(&keytab,b,c,d,e,f))

  int status;
  unsigned char kit[SMG$C_KEYBOARD_INFO_BLOCK];
#define SMG_B_DEVCLASS 12	/* 1st byte of 4th longword; skip 3*4 bytes */

  _c$cks(smg$create_virtual_keyboard(&kid,0,0,0));
  /* Note: the VMS documenation (V5.x version, at any rate) for the keyboard
     information table argument is wrong.  The argument should be a pointer
     to the block which is supplied by the caller and written by SMG, not a
     pointer to a pointer to the block as the SMG manual claims.	[pr] */
  _c$cks(smg$get_keyboard_attributes(&kid,kit,
                                     c$ac(SMG$C_KEYBOARD_INFO_BLOCK)));
  session_is_interactive = (kit[SMG_B_DEVCLASS] == DC$_TERM);
  _c$cks(smg$create_key_table(&keytab));
  akd(ds("PF1"),0,0,0,gold);
  akd(ds("PF2"),0,term,ds("HELP"),0);
  akd(ds("PF3"),0,term,ds("SKIP"),0);
  akd(ds("PF4"),gold,term,ds("SKIP/FOLLOWUP"),0);
  akd(ds("PF4"),0,term,ds("SKIP/NEWSGROUP"),0);
  akd(ds("KP0"),0,term,ds("READ/NEW"),0);
  akd(ds("KP0"),gold,term,ds("READ/HEADER/NEW"),0);
  akd(ds("KP1"),0,c$ac(0),ds("READ "),0);
  akd(ds("KP1"),gold,c$ac(0),ds("READ/HEADER "),0);
  akd(ds("KP2"),0,tmne,ds("DOWN"),0);
  akd(ds("KP2"),gold,tmne,ds("BOTTOM"),0);
  akd(ds("KP3"),0,term,ds("READ/NEXT"),0);
  akd(ds("KP3"),gold,term,ds("READ/NEXT/HEADER"),0);
  akd(ds("KP4"),0,term,ds("DIR"),0);
  akd(ds("KP5"),0,tmne,ds("UP"),0);
  akd(ds("KP5"),gold,tmne,ds("TOP"),0);
  akd(ds("KP6"),0,term,ds("DIR/REG"),0);
  akd(ds("KP7"),0,term,ds("DIR"),0);
  akd(ds("KP7"),gold,term,ds("PRINT"),0);
  akd(ds("KP8"),0,term,ds("DIR/NEW"),0);
  akd(ds("KP8"),gold,c$ac(0),ds("EXTRACT "),0);
  akd(ds("KP9"),0,term,ds("DIR/REG"),0);
  akd(ds("KP9"),gold,c$ac(0),ds("EXTRACT/ALL "),0);
  akd(ds("MINUS"),0,term,ds("DIR/ALL"),0);
  akd(ds("MINUS"),gold,c$ac(0),ds("EXTRACT/APPEND "),0);
  akd(ds("COMMA"),0,term,ds("DIR/ALL"),0);
  akd(ds("COMMA"),gold,term,ds("SELECT/NEW"),0);
  akd(ds("PERIOD"),0,term,ds("READ/NEW/FOLLOWUP"),0);
  akd(ds("PERIOD"),gold,term,ds("READ/NEW/FOLLOWUP/HEADER"),0);
  akd(ds("HELP"),0,term,ds("HELP"),0);
  akd(ds("FIND"),0,c$ac(0),ds("SEARCH "),0);
  akd(ds("INSERT_HERE"),0,term,ds("REGISTER"),0);
  akd(ds("REMOVE"),0,term,ds("DEREGISTER"),0);
  akd(ds("SELECT"),0,term,ds("SELECT"),0);
  akd(ds("SELECT"),gold,term,ds("CLOSE"),0);
  akd(ds("PREV_SCREEN"),0,tmne,ds("SCUP"),0);
  akd(ds("PREV_SCREEN"),gold,tmne,ds("TOP"),0);
  akd(ds("NEXT_SCREEN"),0,tmne,ds("SCDOWN"),0);
  akd(ds("NEXT_SCREEN"),gold,tmne,ds("BOTTOM"),0);
  akd(ds("CTRLW"),0,tmne,ds("REFRESH"),0);
  akd(ds("LEFT"),0,term,ds("CLOSE"),0);
  akd(ds("RIGHT"),0,term,ds("OPEN"),0);
  akd(ds("F10"),0,term,ds("EXIT"),0);
#if MENU_SUPPORT
  akd(ds("F11"),0,term,ds("Menu"),0);
#endif
  if (keytab) {
    status = smg$load_key_defs(&keytab, c$dsc(News_ini),
                               c$dsc("SYS$LOGIN:NEWS_INI."), c$ac(0));
    if ((status & 1) || (status == RMS$_FNF)) ;
    else report_rms_error_routine(NEWS$_LOADKEYFAIL,status,0,module_name_str,__LINE__);
    }
  c$free_tmp();
#undef ds
#undef term
#undef tmne
#undef gold
#undef akd
}

/*
 *  gendate
 *
 *  generate a reasonable date string!!
 */

static
char fmt_date[26];

char *gendate(i)
  unsigned int i;
{
  if (!i) fmt_date[1] = '\0';
  else {
    strncpy(fmt_date,ctime((time_t *)&i),24);
    fmt_date[1]=fmt_date[8];fmt_date[2]=fmt_date[9];fmt_date[3]='-';
    fmt_date[7]='-';fmt_date[8]=fmt_date[22];fmt_date[9]=fmt_date[23];
    fmt_date[10]='\0';
    }
  return(&fmt_date[1]);
}

/*
 *  check_command
 *
 *  command parser without dispatch
 */

int chk_val = -1;
static char s_param[256];

static int itf()
{
  if (news_context > 1) {
    int i;

    if (!curr_g) return(0);
    if (!(i = item_find(s_param))) return(0);
    cur_set_itm(curr_g,i);
    return(0);
    }
  else if (smg_active && curr_g && ga[curr_g]->grp_display_indx) {
    int num = 1;

    sscanf(s_param,"%d",&num);
    if (num < ga[curr_g]->grp_display_indx) cur_up_grp(ga[curr_g]->grp_display_indx - num,1);
    else if (num > ga[curr_g]->grp_display_indx) cur_down_grp(num - ga[curr_g]->grp_display_indx,1);
    return(0);
    }
  return(do_error(),0);
}

static int dcitm()
{
  if (curr_i <= 0) return(do_error(),0);
  return(do_readfind(s_param,0,0),0);
}

static int check_command(s,s_l)
  const char *s;
  int s_l;  /* actual string length (must not rely on '\0'), or -1 it unknown */
{
  if (s_l < 0) s_l = strlen(s);

  if (s_l > 0  &&  ((*s == '!') || (*s == ';'))) return(chk_val = 0);
  if (s_l == 0 || !*s) return(chk_val = 1);
  if (isdigit(*s)) return(chk_val = 2);
  if (s_l == 1 && *s == '.') return(chk_val = 3);

  { struct dsc$descriptor_const_s *d = (struct dsc$descriptor_const_s *) c$alloc_tmp(8);
    d->dsc$b_dtype = DSC$K_DTYPE_T;  d->dsc$b_class = DSC$K_CLASS_S;
    d->dsc$a_pointer = s; d->dsc$w_length = s_l;
    if (cli$dcl_parse(d,CLICMDTBL,get_input,get_input,c$dsc("NEWS> ")) & 1)
      return(chk_val = 4);
    return(chk_val = 0);
  }
}
/*
 *  do_parse
 *
 *  command parser
 */

int
do_parse(s)
  const char *s;
{
  int parse_val, s_l;

  if (!s) s_l = 0;
  else {
    while (*s && isspace(*s)) s++;
    s_l = strlen(s);
    while (s_l>0 && isspace(s[s_l-1])) --s_l;
    }

  if ((parse_val = chk_val) == -1) parse_val = check_command(s,s_l);
  chk_val = -1;
  if (!parse_val) return(0);

  if (parse_val == 1) {
    /* This procedure is called from two locations: at the main parse loop and within
     * the text display loop. If in the text display loop, then parse is not called
     * with an empty string - hence the parse_val does not need to consider
     * stack unwind operations
     */
    if ((news_context == 1) && (curr_g)) return(do_select("%",curr_g,3),0);

    else if (old_context == 3) {
      if (*profile_endofitm_cmd) return(do_parse(profile_endofitm_cmd));
      else return(do_readnext(0,0,0),0);
/*
           return(do_readfollow(0,0,1),0);
 */
      }
    else {
      if ((curr_g) && (curr_i)) {
        markasread(curr_g,curr_i,1);
        rpush(curr_g,curr_i);
        do_display(0,0,0);
        }
      return(0);
      }
    }
  if (parse_val == 2) {
    /* The input is a digit - this is a stack unwind operation */
    strnncpy(s_param,sizeof(s_param), s,s_l, 0);
    if (news_context > 2) return(unwind_display(OUTER_LOOP,dcitm));
    return(unwind_display(OUTER_LOOP,itf));
    }
  if (parse_val == 3) {
    /* The input is a '.' - this is a stack unwind operation */
    strnncpy(s_param,sizeof(s_param), s,s_l, 0);
    return(unwind_display(OUTER_LOOP,dcitm));
    }
  if (parse_val == 4) return(cli$dispatch());
  return(0);
}

void
write_err_line(s)
  const char *s;
{
  int status;

  err_line(s);
  if (smg_active) {
    _c$cks(smg$end_pasteboard_update(&pid));
    _c$cks(smg$begin_pasteboard_update(&pid));
    }
}

/*
 *  create_pb
 *
 *  create the pasteboard
 */

#ifdef __DECC
#pragma member_alignment save
#pragma nomember_alignment   /* no member alignment - this is a VMS structure */
#endif
struct {
    unsigned long int SMG$L_DEVCHAR;
    unsigned long int SMG$L_DEVDEPEND;
    unsigned long int SMG$L_DEVDEPEND2;
    unsigned char SMG$B_DEVCLASS;
    unsigned char SMG$B_SMG_DEVTYPE;
    unsigned char SMG$B_PHY_DEVTYPE;
    unsigned char SMG$B_ROWS;
    unsigned short int SMG$W_WIDTH;
    unsigned char SMG$B_COLOR;
    unsigned char SMG$B_PARITY;
    unsigned short int SMG$W_SPEED;
    unsigned short int SMG$W_FILL;
    unsigned short int SMG$W_CURSOR_ROW;
    unsigned short int SMG$W_CURSOR_COL;
    unsigned long int SMG$L_CURSOR_DID;
    } pb_info;

/* member alignment is required to assure atomic volatile access */
#ifdef __DECC
#pragma member_alignment
#endif
static volatile struct bmsg {
  char *bm;
  struct bmsg *bnext;
  } *bhead = 0, *btail = 0;

#ifdef __DECC
#pragma member_alignment restore
#endif

static int smg_term = -1;
static volatile int bcount = 0, bbytecount = 0;

int line_width = 80;

int
do_clear_messages()
{
  err_line("Message Buffer cleared");
  while (bhead) {
    btail = bhead ;
    bhead = bhead->bnext;
    news_free((void *) btail->bm);
    news_free((void *) btail);
    }
  btail = 0;
  bcount = bbytecount = 0;
  return(0);
}

void push_brdmsg(str)
  const char *str;
{
  struct bmsg *tmp_bmsg;
  char *tmp_str;
  int str_l = strlen(str);

  if ((tmp_bmsg = (struct bmsg *) news_malloc(sizeof *btail)) &&
      (tmp_str  = (char *) news_malloc(str_l+1))) {
    tmp_bmsg->bnext = 0;  strcpy(tmp_bmsg->bm = tmp_str, str);
    if (!btail) bhead = btail = tmp_bmsg;
    else { btail->bnext = tmp_bmsg; btail = tmp_bmsg; }
    bbytecount += str_l;  ++bcount;
  }
}

/*
 *  push_brdmsg_raw
 *
 *  This routine is functionally equivalent to push_brdmsg()
 *  except that it is ment to be called from the condition
 *  handler and must not produce/signal another error - so it does
 *  its job if possible or gives up silently otherwise.
 *
 *  The use of news_malloc_raw() instead of news_alloc() is intentional !
 *  news_malloc_raw() must not signal exceptions !
 *
 *  See the comment in NEWSRTL.C regarding the use of
 *  raw memory allocation routines.
 */
void push_brdmsg_raw(str)
  char *str;
{
  struct bmsg *tmp_bmsg;
  char *tmp_str;
  int str_l = strlen(str);

  if ((tmp_bmsg = (struct bmsg *) news_malloc_raw(sizeof *btail)) &&
      (tmp_str  = (char *) news_malloc_raw(str_l+1))) {
    tmp_bmsg->bnext = 0;  strcpy(tmp_bmsg->bm = tmp_str, str);
    if (!btail) bhead = btail = tmp_bmsg;
    else { btail->bnext = tmp_bmsg; btail = tmp_bmsg; }
    bbytecount += str_l;  ++bcount;
  }
}

/*
 *  err_line
 *
 *  error line management
 */

void
err_line(s)
  const char *s;
{
#if STRIP_ERR_LINES
  if (!strncmp(s,"Error",5) || !strncmp(s,"\tError",6)) {
    const char *cp = &s[6];

    while (*cp && !isalnum(*cp)) cp++;
    if (*cp) s = cp;
    }
#endif
  push_brdmsg(s);
  if (!smg_active) printf("\t%s\n",s);
  else smg_put_chars(trailer_vd,s,3,1,1,SMG$M_REVERSE);
}

static int
dsm_1()
{
  struct bmsg *b;

  if (!bhead) {
    strcpy(err_oline,"Show Messages - message buffer is empty");
    if (!smg_active) printf("\t%s\n",err_oline);
    else smg_put_chars(trailer_vd,err_oline,3,1,1,SMG$M_REVERSE);
    return(0);
    }
  textptr.str_searching = 1;
  strcpy(textptr.str_target,"#### END of MESSAGE BUFFER ####");
  textptr.str_length = strlen(textptr.str_target);
  textptr.str_case_sensitive = 1;
  start_header(T_DISPLAY_LOOP,SHOW_MESSAGES);
  textptr.str_found = 0;
  sprintf(err_oline,"Message Buffer           Total size: %d messages",bcount);
  put_line(err_oline,0,T_DISPLAY_LOOP);
  end_header(T_DISPLAY_LOOP);
  textptr.text_cline = 0;
  textptr.text_lines = bcount;
  textptr.dispf_stat.st_size = bbytecount;
  for (b = (struct bmsg *) bhead; b; b = b->bnext) {
    textptr.gets_size += strlen(b->bm);
    put_line(b->bm,0,T_DISPLAY_LOOP);
    }
  put_line("#### END of MESSAGE BUFFER ####",0,T_DISPLAY_LOOP);
  put_line("",1,T_DISPLAY_LOOP);
  textptr.str_searching = 0;
  return(0);
}

int
do_show_messages()
{
  return(unwind_display(I_DISPLAY_LOOP,dsm_1));
}

void messages_write(f)
  FILE *f;
{
  struct bmsg *b;
  for (b = (struct bmsg *) bhead; b; b = b->bnext) {
    if (b == bhead && strncmp(b->bm,"ANU News -",10) == 0) ;  /* ignore */
    else {
      putl("# ",f);  _ck_put(f);
      putl(b->bm,f); _ck_put(f);
      fputc('\n',f); _ck_put(f);
      }
    }
}

void display_brdcst(line)	int line;
{
  char bm[2050];
  unsigned short bm_len;
  $DESCRIPTOR(bm_dsc,bm);
  int sts, status;
  int bell_cnt, dst, j, at_the_beginning;

  if (((sts = smg$get_broadcast_message(&pid,&bm_dsc,&bm_len,0)) & 1)
      && (sts != SMG$_NO_MORMSG) && (sts != SMG$_NONBRDMSG)) {
    bm[bm_len] = '\0';

    /* remove and count <BEL> chars and remove <CR> before line terminators */
    bell_cnt = 0; dst = 0;
    for (j=0; j<bm_len; j++) {
      if (bm[j]=='\007') bell_cnt++;
      else if ((bm[j]=='\r') &&
               (bm[j+1]=='\n' || bm[j+1]=='\v' || bm[j+1]=='\f')) /* ignore */;
      else bm[dst++] = bm[j];
      }
    bm[dst] = '\0';  bm_len = dst;

    if (bell_cnt > 3) bell_cnt = 3;
    if (bell_cnt > 0) _c$cks(smg$ring_bell(&trailer_vd,&bell_cnt));

    _c$cks(smg$erase_line(&trailer_vd,&line,c$ac(1)));
    smg_put_chars(trailer_vd,bm,line,1,1,SMG$M_BOLD);
    _c$cks(smg$set_cursor_abs(&trailer_vd,c$ac(1),c$ac(brdcst_col)));

    /* save multi-line messages as several messages */
    for (j=0; j<bm_len; j++) {
      if ((bm[j]=='\n' || bm[j]=='\v' || bm[j]=='\f')) bm[j]='\0';
      }
    at_the_beginning = 1;
    for (j=0; j<bm_len; j++) {
      if (bm[j]=='\0') at_the_beginning = 1;
      else if (at_the_beginning) { push_brdmsg(&bm[j]); at_the_beginning = 0; }
      }
    }
}

void b_trap()
{
  if (brdcst_line) display_brdcst(brdcst_line);
}

int create_pb()
{
  int status;

  if (pid_created) return(1);
  if (smg_term >= 0) return(smg_term);
  _c$cks(smg$create_pasteboard(&pid,0,&devrow,&devcol,c$ac(0)));
  if (devcol) displ_subjlen = max(10, devcol-30);
  pid_created = 1;
  _c$cks(smg$get_pasteboard_attributes(&pid,&pb_info,c$ac(sizeof pb_info)));
  if (pb_info.SMG$B_DEVCLASS != DC$_TERM) {
    printf("\n\tNEWS: Not a terminal - /NOSCREEN mode used\n");
    pid_created = 0;
    }
  else if (pb_info.SMG$B_SMG_DEVTYPE != SMG$K_VTTERMTABLE) {
    printf("\n\tNEWS: Not an SMG supported terminal - /NOSCREEN mode used\n");
    pid_created = 0;
    }
  line_width = pb_info.SMG$W_WIDTH;
  if (!pid_created) _c$cks(smg$delete_pasteboard(&pid,c$ac(0)));
  return(smg_term = pid_created);
}

/*
 *  init_screen
 *
 *  set up the screen display
 */

int init_screen()
{
  int num_grps;

  if (smg_active) return(0);
  if (!create_pb()) {
    printf("\nError: SCREEN - Not an SMG terminal\n");
    return(0);
    }
  set_level(1);
  smg_active = 1;
  num_grps = ga[0]->grp_count;
  c$cks(smg$create_virtual_display(c$rfi(num_grps + 40),c$ac(devcol),&grp_vd,0,0,0));
  gv_size = num_grps + 40;
  c$cks(smg$create_virtual_display(c$ac(3),c$ac(devcol),&grp_header_vd,0,c$ac(SMG$M_REVERSE + SMG$M_BOLD),0));
  c$cks(smg$create_virtual_display(c$ac(3),c$ac(devcol),&itm_header_vd,0,c$ac(SMG$M_REVERSE + SMG$M_BOLD),0));
  c$cks(smg$create_virtual_display(c$ac(3),c$ac(devcol),&trailer_vd,0,0,0));
  c$cks(smg$create_virtual_display(c$rfi(DISP_BUF_SIZE + devrow),c$ac(devcol),&(itmptr.sdid),0,0,0));
  c$cks(smg$create_virtual_display(c$rfi(DISP_BUF_SIZE + devrow),c$ac(devcol),&(textptr.sdid),0,0,0));
  c$cks(smg$set_display_scroll_region(&(itmptr.sdid),c$ac(1),c$ac(DISP_BUF_SIZE)));
  c$cks(smg$set_display_scroll_region(&(textptr.sdid),c$ac(1),c$ac(DISP_BUF_SIZE)));
  c$cks(smg$create_virtual_display(c$rfi(devrow - 3),c$ac(devcol),&(itmptr.shdid),0,0,0));
  c$cks(smg$create_virtual_display(c$rfi(devrow - 3),c$ac(devcol),&(textptr.shdid),0,0,0));

  smg_put_chars(grp_header_vd,"NEWS       NEWSGROUP LISTING",1,4,0,0);
  sprintf(err_oline,"        %-*.*s  Count   Unread",
                    displ_subjlen,displ_subjlen,"Newsgroup");
  smg_put_chars(grp_header_vd,err_oline,3,4,0,0);
  smg_put_chars(itm_header_vd,"NEWS ITEMS",1,4,0,0);
  smg_put_chars(itm_header_vd,"      Title",3,4,0,0);

  c$cks(smg$begin_pasteboard_update(&pid));
  c$cks(smg$erase_pasteboard(&pid));
  do_refresh();
  c$cks(smg$paste_virtual_display(&grp_vd,&pid,c$ac(4),c$ac(1)));
  c$cks(smg$paste_virtual_display(&grp_header_vd,&pid,c$ac(1),c$ac(1)));
  c$cks(smg$paste_virtual_display(&trailer_vd,&pid,c$rfi(devrow - 2),c$ac(1)));

  screen_map_dir();

  chg_mode();
  if (broadcast_trapping_requested) broad_trap(); else no_broad_trap();
  screen_displays_setup = 1;
  return(0);
}

int do_screen()
{
  return(unwind_display(OUTER_LOOP,init_screen));
}

/*
 *  noscreen
 *
 *  Revert to line mode interface
 */

int noscreen()
{
    int g;
    int status;

    if (smg_active) {
        _c$cks(smg$begin_pasteboard_update(&pid));
        no_broad_trap();
        set_level(1);
        for (g = 1; g <= ga_size; ++g) {
            if (ga[g]->grp_iavdsize) {
                _c$cks(smg$delete_virtual_display(&ga[g]->grp_iavd));
                ga[g]->grp_iavdsize = 0;
                }
            }
        _c$cks(smg$delete_virtual_display(&grp_vd));
        gv_size = 0;
        _c$cks(smg$delete_virtual_display(&grp_header_vd));
        _c$cks(smg$delete_virtual_display(&itm_header_vd));
        _c$cks(smg$delete_virtual_display(&trailer_vd));
        _c$cks(smg$delete_virtual_display(&(itmptr.sdid)));
        _c$cks(smg$delete_virtual_display(&(itmptr.shdid)));
        _c$cks(smg$delete_virtual_display(&(textptr.sdid)));
        _c$cks(smg$delete_virtual_display(&(textptr.shdid)));
        while (smg$end_pasteboard_update(&pid) == SMG$_BATSTIPRO);
        reset_mode();
        smg_active = 0;
        screen_displays_setup = 0;
        }
    return(0);
}

int do_noscreen()
{
  return(unwind_display(OUTER_LOOP,noscreen));
}

/*
 *  util_cvrt
 *
 *  Convert a string into standard newsgroup name format
 */

void util_cvrt(result, input)
    char *result;
    const char *input;
{
    int input_l, j;
    char *p;

    while (*input == ' ') input++;
    for (input_l=strlen(input); input_l>0 && input[input_l-1]==' '; input_l--){}
    strnncpy(result,GRPNAMELEN, input,input_l, 0);
    for (j=input_l; j<GRPNAMELEN; j++) result[j] = '\0';
    for (p=result; *p; p++) {
      if (!isgraph(*p)) *p = '_';
      else if (*p>='A' && *p<='Z') *p = *p - 'A' + 'a';
      }
}

/*
 *  util_dir
 *
 *  Convert a newsgroup name to a VMS directory string.
 *
 *  Allow group name size to be at most GRPNAMELEN_F characters,
 *  thus accepting also the not necessarily '\0'-terminated newsgrp.grp_name
 *  as a parameter.
 */

char dir_result[GRPNAMELEN_F + GRPNAMELEN_F + 1];

char *
util_dir(input)
    const char *input;
{
    char *p = dir_result;
    const char *in;
    int j;

    for (in=input, j=0;  j<GRPNAMELEN_F && *in;  in++, j++) {
      if (isalnum(*in) || (*in == '-') || (*in == '.'))
        *p++ = *in;
      else {
        *p++ = '_';
        if     (*in == '_') *p++ = '_';
        else if (*in < '0') *p++ = (*in - '!') + 'A';
        else if (*in < 'A') *p++ = (*in - ':') + '0';
        else if (*in < 'a') *p++ = (*in - '[') + 'P';
        else                *p++ = (*in - '{') + 'V';
        }
      }
    *p = '\0';
    return(dir_result);
}

/*
 *  util_undir
 *
 *  Convert a directory name to a newsgroup string
 */

char undir_result[GRPNAMELEN];

char *
util_undir(input)
    const char *input;
{
    char *p = undir_result;
    const char *in;

    for (in=input; *in; in++) {
      if (*in != '_')
        *p++ = tolower(*in);
      else {
        in++;
        if (!*in) break;
        if      (*in < 'A') *p++ = (*in - '0') + ':';
        else if (*in < 'P') *p++ = (*in - 'A') + '!';
        else if (*in < 'V') *p++ = (*in - 'P') + '[';
        else                *p++ = (*in - 'V') + '{';
        }
      }
    *p = '\0';
    return(undir_result);
}

/*
 *  strip
 *
 *  remove trailing blanks
 */

void
strip(p,n)
    char *p;
    int n;
{
    do {
        n--;
        } while ((n > 0) && (p[n] == ' '));
    p[n+1] = '\0';
}

/*
 *  do_refresh
 *
 *  rewrite the SMG screen
 */

int do_refresh()
{
  int status;

  if (smg_active) {
    _c$cks(smg$end_pasteboard_update(&pid));
    _c$cks(smg$repaint_screen(&pid));
    _c$cks(smg$begin_pasteboard_update(&pid));
    _c$cks(smg$set_keypad_mode(&kid,c$ac(SMG$M_KEYPAD_APPLICATION)));
    }
  return(0);
}

/*
 *  do_error
 *
 *  CLI problem
 */

int do_error()
{
  err_line("Error: News - unrecognised command");
  return(0);
}

/*
 *  do_exit
 *
 *  bye for now
 */

int do_exit()
{
    return(1);
}

/*
 *  do_quit
 *
 *  Close rms files, DONT write the register file, shutdown the screen, and
 *  send any printer output to a nominated queue.
 */

int do_quit()
{
    int status;

    err_line("QUIT will NOT save any REGISTER, READ, MARK, SKIP commands from this session");
    status = get_input(&usr_inp_dsc,c$dsc("Quit? [y]"),&usr_inp_l);
    if (!(status & 1)) return(1);
    if (!(!usr_inp_l || (*usr_inp == 'Y') || (*usr_inp == 'y'))) return(0);
    closefiles_opt(0,1);
    exit(1);
    /*NOTREACHED*/
    return 1;
}

/*
 *  screen_grp_display
 *
 *  NEWSGROUPS command - set the screen to the newsgroup directory
 */

int screen_grp_display()
{
    set_level(1);
    return(0);
}

int do_groups_display()
{
  return(unwind_display(OUTER_LOOP,screen_grp_display));
}

/*
 *  screen_cur_down
 *
 *  Move the cursor down
 */

int screen_cur_down()
{
  char line[132];
  unsigned short line_len;
  int lines;
  $DESCRIPTOR(line_dsc,line);

  if (cli$get_value(c$dsc("LINES"),&line_dsc,&line_len) & 1) {
    line[line_len] = '\0';
    if (sscanf(line,"%d",&lines) != 1) lines = 1;
    }
  else lines = 1;
  if (!lines) return(0);
  if (lines < 0) {
    if (news_context == 1) cur_up_grp(lines,1);
    else {
      set_level(2);
      cur_up_itm(curr_g,lines,1);
      }
    return(0);
    }
  if (news_context == 1) cur_down_grp(lines,1);
  else {
    set_level(2);
    cur_down_itm(curr_g,lines,1);
    }
  return(0);
}

/*
 *  screen_cur_up
 *
 *  move the cursor up by n lines on the screen
 */

int screen_cur_up()
{
    char line[132];
    unsigned short line_len;
    int lines;
    $DESCRIPTOR(line_dsc,line);

    if (cli$get_value(c$dsc("LINES"),&line_dsc,&line_len) & 1) {
        line[line_len] = '\0';
        if (sscanf(line,"%d",&lines) != 1) lines = 1;
        }
    else lines = 1;
    if (!lines) return(0);
    if (lines < 0) {
        if (news_context == 1) cur_down_grp(lines,1);
        else {
            set_level(2);
            cur_down_itm(curr_g,lines,1);
            }
        return(0);
        }
    if (news_context == 1) cur_up_grp(lines,1);
    else {
        set_level(2);
        cur_up_itm(curr_g,lines,1);
        }
    return(0);
}

int do_noop()
{
  return(0);
}

int do_top()
{
  if (news_context == 1) cur_up_grp(1000000,1);
  else {
    set_level(2);
    cur_up_itm(curr_g,1000000,1);
    }
  return(0);
}

int do_bottom()
{
  if (news_context == 1) cur_down_grp(1000000,1);
  else {
    set_level(2);
    cur_down_itm(curr_g,1000000,1);
    }
  return(0);
}

int scdown()
{
  int lines = devrow - 6;

  if (news_context == 1) cur_down_grp(lines,1);
  else {
    set_level(2);
    cur_down_itm(curr_g,lines,1);
    }
  return(0);
}

int scup()
{
  int lines = devrow - 6;

  if (news_context == 1) cur_up_grp(lines,1);
  else {
    set_level(2);
    cur_up_itm(curr_g,lines,1);
    }
  return(0);
}

int
do_abort()
{
    closefiles_opt(0,0);
    exit(1);
    /*NOTREACHED*/
    return 0;
}

/*
 * mem_fail - called by the memory allocation routines when an allocation
 * request fails.  Prompt the user to save NewsRC, clear mem, and unwind
 * to main loop via news_condition_handler.
 */

void *mem_fail(bytesize)
  size_t bytesize;
{
  char reply[16];
  unsigned short int replylen, trm;
  $DESCRIPTOR(replydsc,reply);

#if !SIMPLE_MALLOC
  if (mem_reserve < MEM_RESERVE_SIZE) {
    if (mem_reserve) {
      mem_reserve = 0;
      err_line("Fatal memory shortage - exiting");
      }
    else printf("Fatal memory shortage - exiting\n");
    exit(LIB$_INSVIRMEM);
    }

  /* decrease the temp reserve so err_line(), get_input_dflt(), and mem_reset()
     have space to work.  Hang on to enough to output a fatal error message
     via err_line() if mem_reset fails  */
  mem_reserve = min(1024,MEM_RESERVE_SIZE/2);
#endif
  c$free_tmp();

  err_line("Out of memory - clearing buffered data");

  *reply = 'X';
  while ((*reply != 'Y') && (*reply != 'N')) {
    if (get_input_dflt(&replydsc,c$dsc("Update NewsRC (Y/N)? "),&replylen,
                            c$dsc("Y"),&trm) == RMS$_EOF) *reply = 'N';
    *reply = toupper(*reply);
    }

  if (*reply == 'N') {
    *reply = 'X';
    err_line("'N' will NOT save any REGISTER, READ, MARK, SKIP commands from this session");
    while ((*reply != 'Y') && (*reply != 'N')) {
      if (get_input_dflt(&replydsc,c$dsc("Update NewsRC (Y/N)? "),&replylen,
                              c$dsc("Y"),&trm) == RMS$_EOF) *reply = 'N';
      *reply = toupper(*reply);
      }
    }

  clear_err_line();
  mem_reset(cur_dir_type,(toupper(*reply) == 'Y'));

  mem_reserve = MEM_RESERVE_SIZE;

  /* unwind to the main loop - the handler established there
     must expect this condition value */
  lib$signal(NEWS$_UNWIND,0);

  /* the condition handler set up to unwind, now do it */
  return (void *) 0;

}  /*  end of mem_fail()  */


/*
 *  putmsg_action_routine
 *
 *  Called as the action routine from $PUTMSG.
 *
 *  Appends error message (possibly multi-line) to the global
 *  array 'err_msg' (of length 'err_msg_l'); lines are terminated by '\0'.
 *  Number of lines in err_msg is updated in err_msg_lines;
 */

static int putmsg_action_routine(str_dsc, actprm)
  struct dsc$descriptor_s *str_dsc;
  int actprm;
{
  int j,len,from,upto,offs;
  char c;

  len = str_dsc->dsc$w_length;
  for (offs = from = 0; from < len; offs = 4) {
    upto = min(from+(80-4)-offs, len-1);
    if (upto < len-1) {                      /* try to wrap at word boundary */
      while ((upto > from+20) && (str_dsc->dsc$a_pointer[upto] != ' ')) upto--;
      }
    for (j=0; j<offs; j++)
      if (err_msg_l < (sizeof err_msg)-1) err_msg[err_msg_l++] = ' ';
    while (from <= upto) {
      c = str_dsc->dsc$a_pointer[from++];
      if      (c == '\n') break;  /* stop at newline */
      else if (c == '\r') ;      /* ignore CR */
      else if (err_msg_l < (sizeof err_msg)-1) err_msg[err_msg_l++] = c;
      }
    err_msg[err_msg_l++] = '\0'; err_msg_lines++;
    }
  return 0;     /* do not allow $PUTMSG to write message itself! */
}


/*
 *  news_condition_handler
 *
 *  Condition handler
 *  Also handles NEWS$_UNWIND signal (e.g. from mem_fail() )
 */


static unsigned long int mem_fail_ret()
{
  establish_handler(news_condition_handler);
  return (unsigned long int) mem_fail(0);
}


int news_condition_handler(sigargs_v,mechargs_v)
  void  *sigargs_v;
  void  *mechargs_v;
{
  struct chf$signal_array *sigargs = (struct chf$signal_array *)sigargs_v;
  struct chf$mech_array   *mechargs = (struct chf$mech_array *)mechargs_v;
  static volatile int this_handler_active = 0;
  struct dsc$descriptor_const_s dsc;
  int cond,nargs,last_cond,facility,severity,j,line_no,sts;
  int mailer_syntax_err;  /* mailer signalled a user error (e.g. bad username)*/
  int cli_syntax_err;     /* CLI signalled a command syntax error */
  int will_abort;         /* abort program, instead of trying to continue */
  unsigned short message_options;
  int new_sa[16];         /* copy of the signal array to be passed to $PUTMSG */
  int MAIL$_TEXT       = 0x007e1130;
  int MAIL$_NOSUCHUSR  = 0x007e802a;
  int MAIL$_USERSPEC   = 0x007e8062;
  int MAIL$_LOGLINK    = 0x007e803a;
#define CLI$_FACILITY	3
#define MAIL$_FACILITY	126

  cond  = sigargs->chf$l_sig_name;
  nargs = sigargs->chf$l_sig_args;
  last_cond = nargs<3 ? 0 : ((int *)sigargs)[nargs-2];
#if GNUC_HANDLER_HACK
  if (cond == C$_LONGJMP)
    return(this_handler_active=0,vaxc$unwind_longjmp(sigargs,mechargs));
#endif
  if (cond == SS$_UNWIND || last_cond == SS$_UNWIND)
    return(this_handler_active=0,SS$_CONTINUE);           /* return any value */
/*
 * the last_cond fudge:
 *   The callable mail interface to VMSMAIL has a bug in its condition
 * handler. During handler UNWIND every active condition handler receives
 * a SS$_UNWIND signal to which it should perform internal cleanup and
 * return.  The VMSMAIL's condition handler instead does not recognise
 * SS$_UNWIND, but wraps it into a wrapper signal and resignals it.
 * To prevent a loop this handler recognizes this bug and checks whether
 * the wrapped (last) signal is SS$_UNWIND and ignores it. 
 */
  if (cond == SS$_ASTFLT)return(this_handler_active=0,SS$_RESIGNAL);/*VAXC signals*/
  if (cond == SS$_DEBUG) return(this_handler_active=0,SS$_RESIGNAL);
  if (this_handler_active && cond != NEWS$_UNWIND) return(SS$_RESIGNAL);
  this_handler_active = 1;
  facility = $VMS_STATUS_FAC_NO(cond);
  severity = $VMS_STATUS_SEVERITY(cond);

    /* some day CLI errors should be categorized to user and program errors */
  cli_syntax_err    =  (facility == CLI$_FACILITY);
  mailer_syntax_err = ((facility == MAIL$_FACILITY) &&
                       (cond == MAIL$_TEXT ||
                        cond == MAIL$_NOSUCHUSR ||
                        cond == MAIL$_USERSPEC ||
                        cond == MAIL$_LOGLINK));

  /* try to put an end to an infinite loop through the condition handler
     when UNWIND or CONTINUE are repeatedly causing another error */
  if (!(severity&1) && !cli_syntax_err && !mailer_syntax_err) {
    if (++signalled_error_count > 42 /*any reasonably high number*/ ) {
      /* reclassify on_error: make it less tolerant to errors */
      if (on_error == ON_ERROR_CONTINUE  ||  on_error == ON_ERROR_UNWIND) 
        { on_error = ON_ERROR_ABORT; signalled_error_count = 1; }
      else if (on_error == ON_ERROR_ABORT) abort();
      }
    }

  err_msg_l = err_msg_lines = 0;
  will_abort = 0;
  if (0  /* severity == STS$K_SEVERE */) will_abort = 1;
  else if (cli_syntax_err || mailer_syntax_err) /* do not abort */;
  else if (!(severity&1) || cond == NEWS$_UNWIND)
    if (on_error == ON_ERROR_ABORT) will_abort = 1;

  if (will_abort || cond == NEWS$_UNWIND) ; /* do not want a message */
  else {
    /* obtain a full message text */
    message_options = 0;  /* use process default */
#if STRIP_ERR_LINES
    if (cli_syntax_err) message_options = 0x01; /*message text only*/
#endif
    new_sa[0] = (message_options<<16) | min(16-1,nargs-2);
    for (j = 1; j < 16; j++)
      if (j <= nargs-2) new_sa[j] = ((int *)sigargs)[j]; else new_sa[j] = 0;
    sys$putmsg(new_sa,&putmsg_action_routine,0,0);
    dsc.dsc$b_dtype = DSC$K_DTYPE_T;  dsc.dsc$b_class = DSC$K_CLASS_S;
    }
  if (err_msg_lines) {
    /* write the full message text first */
    for (j=0, line_no=1; j<err_msg_l; j+=(dsc.dsc$w_length+1), line_no++) {
      dsc.dsc$a_pointer = (const char *) &err_msg[j];  push_brdmsg_raw(&err_msg[j]);
      dsc.dsc$w_length = strlen(dsc.dsc$a_pointer);
      if (!smg_active) lib$put_output(&dsc);
      else if (line_no <= 1) {
        /* avoid using dynamic memory allocation (c$* routines) */
        int row = 3, col = 1;
        int flags = SMG$M_ERASE_LINE, rendition = SMG$M_REVERSE;
        sts = smg$put_chars(&trailer_vd,&dsc,&row,&col,&flags,&rendition,0,0);
        if (! (sts & 1)) lib$put_output(&dsc);
        }
      }
    if (smg_active) {    /* flush SMG output */
      int j, smg_batching_depth = 0;
      /* reduce batching depth to zero */
      while ((sts=smg$end_pasteboard_update(&pid)) == SMG$_BATSTIPRO)
        smg_batching_depth++;
      if (sts == SS$_NORMAL) smg_batching_depth++;
      if (sts&1) {
        smg$flush_buffer(&pid);  /* ignore returned status */
        /* restore batching depth */
        for (j = 1; j <= smg_batching_depth; j++) {
          sts = smg$begin_pasteboard_update(&pid);
          if (! (sts&1) ) break;
          }
        }
      }
    }
  if (will_abort) {
    problems_encountered = 1;
    /* change severity to SEVERE and abort with traceback */
    cond = (cond & (~7)) | STS$K_SEVERE;
    sigargs->chf$l_sig_name = cond;
    return(this_handler_active=0,SS$_RESIGNAL);
    }
  else if (cli_syntax_err)
    return(this_handler_active=0,SS$_CONTINUE);/* no panic on syntactic errors*/
  else if (cond == LIB$_INSVIRMEM ||  /* try to free up some memory */
           cond == OTS$_INSVIRMEM ||  /* and continue */
           cond == SHR$_INSVIRMEM ||
           cond == STR$_INSVIRMEM)
    severity = $VMS_STATUS_SEVERITY(cond = mem_fail_ret());

  if (!(severity&1) || cond == NEWS$_UNWIND) {
    if (!mailer_syntax_err) problems_encountered = 1;
    if (on_error == ON_ERROR_CONTINUE && cond != NEWS$_UNWIND) {
      cond = (cond & (~7)) | STS$K_WARNING;
      return(this_handler_active=0,SS$_CONTINUE);
      }
    else {
   /* return 0 to wherever in main() we pick up - probably doesn't matter,
      but FALSE more likely (?) not to be taken as something having been
      completed successfully  */
#ifdef __ALPHA
      if (mechargs->chf$q_mch_depth <= 0) sys$unwind(0,0);
#else
      if (mechargs->chf$l_mch_depth <= 0) sys$unwind(0,0);
#endif
      else {
#ifdef __ALPHA
        mechargs->chf$q_mch_savr0 = 0;
#else
        mechargs->chf$l_mch_savr0 = 0;
#endif
        /* unwind back into main(), rather than exit */
#ifdef __ALPHA
        sys$unwind(&(mechargs->chf$q_mch_depth),0);
#else
        sys$unwind(&(mechargs->chf$l_mch_depth),0);
#endif
        }
      /* status is ignored after a call to $unwind */
      return(this_handler_active=0,SS$_CONTINUE);
      }
    }
  else if (severity == STS$K_SUCCESS ||
           severity == STS$K_INFO)
    return(this_handler_active=0,SS$_CONTINUE);

  return(this_handler_active=0,SS$_RESIGNAL);
}
