The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
/*
  Copyright (c) 1990-2009 Info-ZIP.  All rights reserved.

  See the accompanying file LICENSE, version 2009-Jan-02 or later
  (the contents of which are also included in unzip.h) for terms of use.
  If, for some reason, all these files are missing, the Info-ZIP license
  also may be found at:  ftp://ftp.info-zip.org/pub/infozip/license.html
*/
/*---------------------------------------------------------------------------

  vms.c                                        Igor Mandrichenko and others

  This file contains routines to extract VMS file attributes from a zipfile
  extra field and create a file with these attributes.  The code was almost
  entirely written by Igor, with a couple of routines by GRR and lots of
  modifications and fixes by Christian Spieler.

  Contains:  check_format()
             open_outfile()
             find_vms_attrs()
             flush()
             close_outfile()
             defer_dir_attribs()
             set_direc_attribs()
             dos_to_unix_time()         (TIMESTAMP only)
             stamp_file()               (TIMESTAMP only)
             vms_msg_text()
             do_wild()
             mapattr()
             mapname()
             checkdir()
             check_for_newer()
             return_VMS
             screensize()
             screenlinewrap()
             version()

  ---------------------------------------------------------------------------*/

#ifdef VMS                      /* VMS only! */

#define UNZIP_INTERNAL

#include "unzip.h"
#include "crc32.h"
#include "vms.h"
#include "vmsdefs.h"

#ifdef MORE
#  include <ttdef.h>
#endif
#include <unixlib.h>

#include <dvidef.h>
#include <ssdef.h>
#include <stsdef.h>

/* Workaround for broken header files of older DECC distributions
 * that are incompatible with the /NAMES=AS_IS qualifier. */
#define lib$getdvi LIB$GETDVI
#define lib$getsyi LIB$GETSYI
#define lib$sys_getmsg LIB$SYS_GETMSG
#include <lib$routines.h>

#ifndef EEXIST
#  include <errno.h>    /* For mkdir() status codes */
#endif

/* On VAX, define Goofy VAX Type-Cast to obviate /standard = vaxc.
   Otherwise, lame system headers on VAX cause compiler warnings.
   (GNU C may define vax but not __VAX.)
*/
#ifdef vax
#  define __VAX 1
#endif

#ifdef __VAX
#  define GVTC (unsigned int)
#else
#  define GVTC
#endif

/* With GNU C, some FAB bits may be declared only as masks, not as
 * structure bits.
 */
#ifdef __GNUC__
#  define OLD_FABDEF 1
#endif

#define ASYNCH_QIO              /* Use asynchronous PK-style QIO writes */

/* buffer size for a single block write (using RMS or QIO WRITEVBLK),
   must be less than 64k and a multiple of 512 ! */
#define BUFS512 (((OUTBUFSIZ>0xFFFF) ? 0xFFFF : OUTBUFSIZ) & (~511))
/* buffer size for record output (RMS limit for max. record size) */
#define BUFSMAXREC 32767
/* allocation size for RMS and QIO output buffers */
#define BUFSALLOC (BUFS512 * 2 > BUFSMAXREC ? BUFS512 * 2 : BUFSMAXREC)
        /* locbuf size */

/* VMS success or warning status */
#define OK(s)   (((s) & STS$M_SUCCESS) != 0)
#define STRICMP(s1, s2) STRNICMP(s1, s2, 2147483647)

/* Interactive inquiry response codes for replace(). */

#define REPL_NO_EXTRACT   0
#define REPL_NEW_VERSION  1
#define REPL_OVERWRITE    2
#define REPL_ERRLV_WARN   256
#define REPL_TASKMASK     255

/* 2008-09-13 CS.
 * Note: In extract.c, there are similar strings "InvalidResponse" and
 * "AssumeNone" defined.  However, as the UI functionality of the VMS
 * "version-aware" query is slightly different from the generic variant,
 * these strings are kept separate for now to allow independent
 * "fine tuning" without affecting the other variant of the
 * "overwrite or ..." user query.
 */
ZCONST char Far InvalidResponse[] =
  "error:  invalid response [%.1s]\n";
ZCONST char Far AssumeNo[] =
  "\n(EOF or read error, treating as \"[N]o extract (all)\" ...)\n";


#ifdef SET_DIR_ATTRIB
/* Structure for holding directory attribute data for final processing
 * after all files are in place.
 */
typedef struct vmsdirattr {
    struct vmsdirattr *next;            /* link to next in (linked) list */
    char *fn;                           /* file (directory) name */

    /* Non-VMS attributes data */
    ulg mod_dos_datetime;               /* G.lrec.last_mod_dos_datetime */
    unsigned perms;                     /* same as min_info.file_attr */

    unsigned xlen;                      /* G.lrec.extra_field_length */
    char buf[1];                        /* data buffer (extra_field, fn) */
} vmsdirattr;
#define VmsAtt(d)  ((vmsdirattr *)d)    /* typecast shortcut */
#endif /* SET_DIR_ATTRIB */

/*
 *   Local static storage
 */
static struct FAB        fileblk;       /* File Access Block */
static struct XABDAT     dattim;        /* date-time XAB */
static struct XABRDT     rdt;           /* revision date-time XAB */
static struct RAB        rab;           /* Record Access Block */
static struct NAM_STRUCT nam;           /* name block */

static struct FAB *outfab = NULL;
static struct RAB *outrab = NULL;
static struct XABFHC *xabfhc = NULL;    /* file header characteristics */
static struct XABDAT *xabdat = NULL;    /* date-time */
static struct XABRDT *xabrdt = NULL;    /* revision date-time */
static struct XABPRO *xabpro = NULL;    /* protection */
static struct XABKEY *xabkey = NULL;    /* key (indexed) */
static struct XABALL *xaball = NULL;    /* allocation */
static struct XAB *first_xab = NULL, *last_xab = NULL;

static int replace_code_all = -1;       /* All-file response for replace(). */

static uch rfm;

static uch locbuf[BUFSALLOC];           /* Space for 2 buffers of BUFS512 */
static unsigned loccnt = 0;
static uch *locptr;
static char got_eol = 0;

struct bufdsc
{
    struct bufdsc *next;
    uch *buf;
    unsigned bufcnt;
};

static struct bufdsc b1, b2, *curbuf;   /* buffer ring for asynchronous I/O */

static int  _flush_blocks(__GPRO__ uch *rawbuf, unsigned size, int final_flag);
static int  _flush_stream(__GPRO__ uch *rawbuf, unsigned size, int final_flag);
static int  _flush_varlen(__GPRO__ uch *rawbuf, unsigned size, int final_flag);
static int  _flush_qio(__GPRO__ uch *rawbuf, unsigned size, int final_flag);
static int  _close_rms(__GPRO);
static int  _close_qio(__GPRO);
#ifdef ASYNCH_QIO
static int  WriteQIO(__GPRO__ uch *buf, unsigned len);
#endif
static int  WriteBuffer(__GPRO__ uch *buf, unsigned len);
static int  WriteRecord(__GPRO__ uch *rec, unsigned len);

static int  (*_flush_routine)(__GPRO__ uch *rawbuf, unsigned size,
                              int final_flag);
static int  (*_close_routine)(__GPRO);

#ifdef SYMLINKS
static int  _read_link_rms(__GPRO__ int byte_count, char *link_text_buf);
#endif /* SYMLINKS */

static void init_buf_ring(void);
static void set_default_datetime_XABs(__GPRO);
static int  create_default_output(__GPRO);
static int  create_rms_output(__GPRO);
static int  create_qio_output(__GPRO);
static int  replace(__GPRO);
static int  replace_rms_newversion(__GPRO);
static int  replace_rms_overwrite(__GPRO);
static int  find_vms_attrs(__GPRO__ int set_date_time);
static void free_up(void);
#ifdef CHECK_VERSIONS
static int  get_vms_version(char *verbuf, int len);
#endif /* CHECK_VERSIONS */
static unsigned find_eol(ZCONST uch *p, unsigned n, unsigned *l);
#ifdef SET_DIR_ATTRIB
static char *vms_path_fixdown(ZCONST char *dir_spec, char *dir_file);
#endif
#ifdef TIMESTAMP
static time_t mkgmtime(struct tm *tm);
static void uxtime2vmstime(time_t utimeval, long int binval[2]);
#endif /* TIMESTAMP */
static int vms_msg_fetch(int status);
static void vms_msg(__GPRO__ ZCONST char *string, int status);


/*
   2005-02-14 SMS.
   Added some ODS5 support:
      Use longer name structures in NAML, where available.
      Locate special characters mindful of "^" escapes.
*/

/* Hex digit table. */

char hex_digit[16] = {
 '0', '1', '2', '3', '4', '5', '6', '7',
 '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'
};


/* Character property table for converting Zip file names to
   (simpler) ODS2 or (escaped) ODS5 extended file names.

   ODS2 valid characters: 0-9 A-Z a-z $ - _

   ODS5 Invalid characters:
      C0 control codes (0x00 to 0x1F inclusive)
      Asterisk (*)
      Question mark (?)

   ODS5 Invalid characters only in VMS V7.2 (which no one runs, right?):
      Double quotation marks (")
      Backslash (\)
      Colon (:)
      Left angle bracket (<)
      Right angle bracket (>)
      Slash (/)
      Vertical bar (|)

   Characters escaped by "^":
      SP  !  "  #  %  &  '  (  )  +  ,  .  :  ;  =
       @  [  \  ]  ^  `  {  |  }  ~

   Either "^_" or "^ " is accepted as a space.  Period (.) is a special
   case.  Note that un-escaped < and > can also confuse a directory
   spec.

   Characters put out as ^xx:
      7F (DEL)
      80-9F (C1 control characters)
      A0 (nonbreaking space)
      FF (Latin small letter y diaeresis)

   Other cases:
      Unicode: "^Uxxxx", where "xxxx" is four hex digits.

   Property table values:
      Normal ODS2           1
      Lower-case ODS2       2
      Period                4
      Space                 8
      ODS5 simple          16
      ODS5 1-char escape   32
      ODS5 hex-hex escape  64
*/

unsigned char char_prop[256] = {

/* NUL SOH STX ETX EOT ENQ ACK BEL   BS  HT  LF  VT  FF  CR  SO  SI */
    0,  0,  0,  0,  0,  0,  0,  0,   0,  0,  0,  0,  0,  0,  0,  0,

/* DLE DC1 DC2 DC3 DC4 NAK SYN ETB  CAN  EM SUB ESC  FS  GS  RS  US */
    0,  0,  0,  0,  0,  0,  0,  0,   0,  0,  0,  0,  0,  0,  0,  0,

/*  SP  !   "   #   $   %   &   '    (   )   *   +   ,   -   .   /  */
    8, 32, 32, 32, 17, 32, 32, 32,  32, 32,  0, 32, 32, 17,  4,  0,

/*  0   1   2   3   4   5   6   7    8   9   :   ;   <   =   >   ?  */
   17, 17, 17, 17, 17, 17, 17, 17,  17, 17, 32, 32, 32, 32, 32, 32,

/*  @   A   B   C   D   E   F   G    H   I   J   K   L   M   N   O  */
   32, 17, 17, 17, 17, 17, 17, 17,  17, 17, 17, 17, 17, 17, 17, 17,

/*  P   Q   R   S   T   U   V   W    X   Y   Z   [   \   ]   ^   _  */
   17, 17, 17, 17, 17, 17, 17, 17,  17, 17, 17, 32, 32, 32, 32, 17,

/*  `   a   b   c   d   e   f   g    h   i   j   k   l   m   n   o  */
   32, 18, 18, 18, 18, 18, 18, 18,  18, 18, 18, 18, 18, 18, 18, 18,

/*  p   q   r   s   t   u   v   w    x   y   z   {   |   }   ~  DEL */
   18, 18, 18, 18, 18, 18, 18, 18,  18, 18, 18, 32, 32, 32, 32, 64,

   64, 64, 64, 64, 64, 64, 64, 64,  64, 64, 64, 64, 64, 64, 64, 64,
   64, 64, 64, 64, 64, 64, 64, 64,  64, 64, 64, 64, 64, 64, 64, 64,
   64, 16, 16, 16, 16, 16, 16, 16,  16, 16, 16, 16, 16, 16, 16, 16,
   16, 16, 16, 16, 16, 16, 16, 16,  16, 16, 16, 16, 16, 16, 16, 16,
   16, 16, 16, 16, 16, 16, 16, 16,  16, 16, 16, 16, 16, 16, 16, 16,
   16, 16, 16, 16, 16, 16, 16, 16,  16, 16, 16, 16, 16, 16, 16, 16,
   16, 16, 16, 16, 16, 16, 16, 16,  16, 16, 16, 16, 16, 16, 16, 16,
   16, 16, 16, 16, 16, 16, 16, 16,  16, 16, 16, 16, 16, 16, 16, 64
};


/* 2004-11-23 SMS.
 *
 *       get_rms_defaults().
 *
 *    Get user-specified values from (DCL) SET RMS_DEFAULT.  FAB/RAB
 *    items of particular interest are:
 *
 *       fab$w_deq         default extension quantity (blocks) (write).
 *       rab$b_mbc         multi-block count.
 *       rab$b_mbf         multi-buffer count (used with rah and wbh).
 */

#define DIAG_FLAG (uO.vflag >= 3)

/* Default RMS parameter values.
 * The default extend quantity (deq) should not matter much here, as the
 * initial allocation should always be set according to the known file
 * size, and no extension should be needed.
 */

#define RMS_DEQ_DEFAULT 16384   /* About 1/4 the max (65535 blocks). */
#define RMS_MBC_DEFAULT 127     /* The max, */
#define RMS_MBF_DEFAULT 2       /* Enough to enable rah and wbh. */

/* GETJPI item descriptor structure. */
typedef struct
{
    short buf_len;
    short itm_cod;
    void *buf;
    int *ret_len;
} jpi_item_t;

/* Durable storage */
static int rms_defaults_known = 0;

/* JPI item buffers. */
static unsigned short rms_ext;
static char rms_mbc;
static unsigned char rms_mbf;

/* Active RMS item values. */
unsigned short rms_ext_active;
char rms_mbc_active;
unsigned char rms_mbf_active;

/* GETJPI item lengths. */
static int rms_ext_len;         /* Should come back 2. */
static int rms_mbc_len;         /* Should come back 1. */
static int rms_mbf_len;         /* Should come back 1. */

/* Desperation attempts to define unknown macros.  Probably doomed.
 * If these get used, expect sys$getjpiw() to return %x00000014 =
 * %SYSTEM-F-BADPARAM, bad parameter value.
 * They keep compilers with old header files quiet, though.
 */
#ifndef JPI$_RMS_EXTEND_SIZE
#  define JPI$_RMS_EXTEND_SIZE 542
#endif /* ndef JPI$_RMS_EXTEND_SIZE */

#ifndef JPI$_RMS_DFMBC
#  define JPI$_RMS_DFMBC 535
#endif /* ndef JPI$_RMS_DFMBC */

#ifndef JPI$_RMS_DFMBFSDK
#  define JPI$_RMS_DFMBFSDK 536
#endif /* ndef JPI$_RMS_DFMBFSDK */

/* GETJPI item descriptor set. */

struct
{
    jpi_item_t rms_ext_itm;
    jpi_item_t rms_mbc_itm;
    jpi_item_t rms_mbf_itm;
    int term;
} jpi_itm_lst =
     { { 2, JPI$_RMS_EXTEND_SIZE, &rms_ext, &rms_ext_len },
       { 1, JPI$_RMS_DFMBC, &rms_mbc, &rms_mbc_len },
       { 1, JPI$_RMS_DFMBFSDK, &rms_mbf, &rms_mbf_len },
       0
     };

static int get_rms_defaults()
{
    int sts;

    /* Get process RMS_DEFAULT values. */

    sts = sys$getjpiw(0, 0, 0, &jpi_itm_lst, 0, 0, 0);
    if ((sts & STS$M_SEVERITY) != STS$K_SUCCESS)
    {
        /* Failed.  Don't try again. */
        rms_defaults_known = -1;
    }
    else
    {
        /* Fine, but don't come back. */
        rms_defaults_known = 1;
    }

    /* Limit the active values according to the RMS_DEFAULT values. */

    if (rms_defaults_known > 0)
    {
        /* Set the default values. */
        rms_ext_active = RMS_DEQ_DEFAULT;
        rms_mbc_active = RMS_MBC_DEFAULT;
        rms_mbf_active = RMS_MBF_DEFAULT;

        /* Default extend quantity.  Use the user value, if set. */
        if (rms_ext > 0)
        {
            rms_ext_active = rms_ext;
        }

        /* Default multi-block count.  Use the user value, if set. */
        if (rms_mbc > 0)
        {
            rms_mbc_active = rms_mbc;
        }

        /* Default multi-buffer count.  Use the user value, if set. */
        if (rms_mbf > 0)
        {
            rms_mbf_active = rms_mbf;
        }
    }

    if (DIAG_FLAG)
    {
        fprintf(stderr, "Get RMS defaults.  getjpi sts = %%x%08x.\n", sts);

        if (rms_defaults_known > 0)
        {
            fprintf(stderr,
              "               Default: deq = %6d, mbc = %3d, mbf = %3d.\n",
              rms_ext, rms_mbc, rms_mbf);
        }
    }
    return sts;
}


int check_format(__G)
    __GDEF
{
    int rtype;
    int sts;
    struct FAB fab;
#ifdef NAML$C_MAXRSS
    struct NAML nam;
#endif

    fab = cc$rms_fab;                   /* Initialize FAB. */

#ifdef NAML$C_MAXRSS

    nam = cc$rms_naml;                  /* Initialize NAML. */
    fab.fab$l_naml = &nam;              /* Point FAB to NAML. */

    fab.fab$l_dna = (char *) -1;    /* Using NAML for default name. */
    fab.fab$l_fna = (char *) -1;    /* Using NAML for file name. */

#endif /* NAML$C_MAXRSS */

    FAB_OR_NAML(fab, nam).FAB_OR_NAML_FNA = G.zipfn;
    FAB_OR_NAML(fab, nam).FAB_OR_NAML_FNS = strlen(G.zipfn);

    if (ERR(sts = sys$open(&fab)))
    {
        Info(slide, 1, ((char *)slide, "\n\
     error:  cannot open zipfile [ %s ].\n",
          FnFilter1(G.zipfn)));
        vms_msg(__G__ "     sys$open() error: ", sts);
        return PK_ERR;
    }
    rtype = fab.fab$b_rfm;
    sys$close(&fab);

    if (rtype == FAB$C_VAR || rtype == FAB$C_VFC)
    {
        Info(slide, 1, ((char *)slide, "\n\
     Error:  zipfile is in variable-length record format.  Please\n\
     run \"bilf l %s\" to convert the zipfile to stream-LF\n\
     record format.  (BILF is available at various VMS archives.)\n\n",
          FnFilter1(G.zipfn)));
        return PK_ERR;
    }

    return PK_COOL;
}



#define PRINTABLE_FORMAT(x)      ( (x) == FAB$C_VAR     \
                                || (x) == FAB$C_STMLF   \
                                || (x) == FAB$C_STMCR   \
                                || (x) == FAB$C_STM     )

/* VMS extra field types */
#define VAT_NONE    0
#define VAT_IZ      1   /* old Info-ZIP format */
#define VAT_PK      2   /* PKWARE format */

/*
 *  open_outfile() assignments:
 *
 *  VMS attributes ?        create_xxx      _flush_xxx
 *  ----------------        ----------      ----------
 *  not found               'default'       text mode ?
 *                                          yes -> 'stream'
 *                                          no  -> 'block'
 *
 *  yes, in IZ format       'rms'           uO.cflag ?
 *                                          yes -> switch (fab.rfm)
 *                                              VAR  -> 'varlen'
 *                                              STM* -> 'stream'
 *                                              default -> 'block'
 *                                          no -> 'block'
 *
 *  yes, in PK format       'qio'           uO.cflag ?
 *                                          yes -> switch (pka_rattr)
 *                                              VAR  -> 'varlen'
 *                                              STM* -> 'stream'
 *                                              default -> 'block'
 *                                          no -> 'qio'
 *
 *  "text mode" == G.pInfo->textmode || (uO.cflag && !uO.bflag)
 *  (simplified, for complete expression see create_default_output() code)
 */

/* The VMS version of open_outfile() supports special return codes:
 *      OPENOUT_OK            a file has been opened normally
 *      OPENOUT_FAILED        the file open process failed
 *      OPENOUT_SKIPOK        file open skipped at user request, err level OK
 *      OPENOUT_SKIPWARN      file open skipped at user request, err level WARN
 */
int open_outfile(__G)
    __GDEF
{
    /* Get process RMS_DEFAULT values, if not already done. */
    if (rms_defaults_known == 0)
    {
        get_rms_defaults();
    }

    switch (find_vms_attrs(__G__ (uO.D_flag <= 1)))
    {
        case VAT_NONE:
        default:
            return  create_default_output(__G);
        case VAT_IZ:
            return  create_rms_output(__G);
        case VAT_PK:
            return  create_qio_output(__G);
    }
}

static void init_buf_ring()
{
    locptr = &locbuf[0];
    loccnt = 0;

    b1.buf = &locbuf[0];
    b1.bufcnt = 0;
    b1.next = &b2;
    b2.buf = &locbuf[BUFS512];
    b2.bufcnt = 0;
    b2.next = &b1;
    curbuf = &b1;
}


/* Static data storage for time conversion: */

/*   string constants for month names */
static ZCONST char *month[] =
            {"JAN", "FEB", "MAR", "APR", "MAY", "JUN",
             "JUL", "AUG", "SEP", "OCT", "NOV", "DEC"};

/*   buffer for time string */
static char timbuf[24];         /* length = first entry in "date_str" + 1 */

/*   fixed-length string descriptor for timbuf: */
static ZCONST struct dsc$descriptor date_str =
            {sizeof(timbuf)-1, DSC$K_DTYPE_T, DSC$K_CLASS_S, timbuf};


static void set_default_datetime_XABs(__GPRO)
{
    unsigned yr, mo, dy, hh, mm, ss;
#ifdef USE_EF_UT_TIME
    iztimes z_utime;
    struct tm *t;

    if (G.extra_field &&
#ifdef IZ_CHECK_TZ
        G.tz_is_valid &&
#endif
        (ef_scan_for_izux(G.extra_field, G.lrec.extra_field_length, 0,
                          G.lrec.last_mod_dos_datetime, &z_utime, NULL)
         & EB_UT_FL_MTIME))
        t = localtime(&(z_utime.mtime));
    else
        t = (struct tm *)NULL;
    if (t != (struct tm *)NULL)
    {
        yr = t->tm_year + 1900;
        mo = t->tm_mon;
        dy = t->tm_mday;
        hh = t->tm_hour;
        mm = t->tm_min;
        ss = t->tm_sec;
    }
    else
    {
        yr = ((G.lrec.last_mod_dos_datetime >> 25) & 0x7f) + 1980;
        mo = ((G.lrec.last_mod_dos_datetime >> 21) & 0x0f) - 1;
        dy = (G.lrec.last_mod_dos_datetime >> 16) & 0x1f;
        hh = (G.lrec.last_mod_dos_datetime >> 11) & 0x1f;
        mm = (G.lrec.last_mod_dos_datetime >> 5) & 0x3f;
        ss = (G.lrec.last_mod_dos_datetime << 1) & 0x3e;
    }
#else /* !USE_EF_UT_TIME */

    yr = ((G.lrec.last_mod_dos_datetime >> 25) & 0x7f) + 1980;
    mo = ((G.lrec.last_mod_dos_datetime >> 21) & 0x0f) - 1;
    dy = (G.lrec.last_mod_dos_datetime >> 16) & 0x1f;
    hh = (G.lrec.last_mod_dos_datetime >> 11) & 0x1f;
    mm = (G.lrec.last_mod_dos_datetime >> 5) & 0x3f;
    ss = (G.lrec.last_mod_dos_datetime << 1) & 0x1f;
#endif /* ?USE_EF_UT_TIME */

    dattim = cc$rms_xabdat;     /* fill XABs with default values */
    rdt = cc$rms_xabrdt;
    sprintf(timbuf, "%02u-%3s-%04u %02u:%02u:%02u.00",
            dy, month[mo], yr, hh, mm, ss);
    sys$bintim(&date_str, &dattim.xab$q_cdt);
    memcpy(&rdt.xab$q_rdt, &dattim.xab$q_cdt, sizeof(rdt.xab$q_rdt));
}


/* The following return codes are supported:
 *      OPENOUT_OK            a file has been opened normally
 *      OPENOUT_FAILED        the file open process failed
 *      OPENOUT_SKIPOK        file open skipped at user request, err level OK
 *      OPENOUT_SKIPWARN      file open skipped at user request, err level WARN
 */
static int create_default_output(__GPRO)
{
    int ierr;
    int text_output, bin_fixed;

    /* Extract the file in text format (Variable_length by default,
     * Stream_LF with "-S" (/TEXT = STMLF), when
     *  a) explicitly requested by the user (through the -a option),
     *     and it is not a symbolic link,
     * or
     *  b) piping to SYS$OUTPUT, unless "binary" piping was requested
     *     by the user (through the -b option).
     */
    text_output = (G.pInfo->textmode
#ifdef SYMLINKS
                   && !G.symlnk
#endif
                  ) ||
                  (uO.cflag &&
                   (!uO.bflag || (!(uO.bflag - 1) && G.pInfo->textfile)));
    /* Use fixed length 512 byte record format for disk file when
     *  a) explicitly requested by the user (-b option),
     * and
     *  b) it is not a symbolic link,
     * and
     *  c) it is not extracted in text mode.
     */
    bin_fixed = !text_output &&
#ifdef SYMLINKS
                !G.symlnk &&
#endif
                (uO.bflag != 0) && ((uO.bflag != 1) || !G.pInfo->textfile);

    rfm = FAB$C_STMLF;  /* Default, stream-LF format from VMS or UNIX */

    if (!uO.cflag)              /* Redirect output */
    {
        rab = cc$rms_rab;               /* Initialize RAB. */
        fileblk = cc$rms_fab;           /* Initialize FAB. */

        fileblk.fab$l_xab = NULL;       /* No XABs. */
        rab.rab$l_fab = &fileblk;       /* Point RAB to FAB. */

        outfab = &fileblk;              /* Set pointers used elsewhere. */
        outrab = &rab;

        if (text_output && (!uO.S_flag))
        {   /* Default format for output `real' text file */
            fileblk.fab$b_rfm = FAB$C_VAR;      /* variable length records */
            fileblk.fab$b_rat = FAB$M_CR;       /* implied (CR) carriage ctrl */
        }
        else if (bin_fixed)
        {   /* Default format for output `real' binary file */
            fileblk.fab$b_rfm = FAB$C_FIX;      /* fixed length records */
            fileblk.fab$w_mrs = 512;            /* record size 512 bytes */
            fileblk.fab$b_rat = 0;              /* no carriage ctrl */
        }
        else
        {   /* Default format for output misc (bin or text) file */
            fileblk.fab$b_rfm = FAB$C_STMLF;    /* stream-LF record format */
            fileblk.fab$b_rat = FAB$M_CR;       /* implied (CR) carriage ctrl */
        }

#ifdef NAML$C_MAXRSS

        nam = CC_RMS_NAM;               /* Initialize NAML. */
        fileblk.FAB_NAM = &nam;         /* Point FAB to NAML. */

        fileblk.fab$l_dna = (char *) -1;    /* Using NAML for default name. */
        fileblk.fab$l_fna = (char *) -1;    /* Using NAML for file name. */

#endif /* NAML$C_MAXRSS */

        FAB_OR_NAML(fileblk, nam).FAB_OR_NAML_FNA = G.filename;
        FAB_OR_NAML(fileblk, nam).FAB_OR_NAML_FNS = strlen(G.filename);

        /* Prepare date-time XABs, unless user requests not to. */
        if (uO.D_flag <= 1) {
            set_default_datetime_XABs(__G);
            dattim.xab$l_nxt = fileblk.fab$l_xab;
            fileblk.fab$l_xab = (void *) &dattim;
        }

/* 2005-02-14 SMS.  What does this mean?  ----vvvvvvvvvvvvvvvvvvvvvvvvvvv */
        fileblk.fab$w_ifi = 0;  /* Clear IFI. It may be nonzero after ZIP */
        fileblk.fab$b_fac = FAB$M_BRO | FAB$M_PUT;  /* {block|record} output */
#ifdef SYMLINKS
        if (G.symlnk)
            /* Symlink file is read back to retrieve the link text. */
            fileblk.fab$b_fac |= FAB$M_GET;
#endif

        /* 2004-11-23 SMS.
         * If RMS_DEFAULT values have been determined, and have not been
         * set by the user, then set some FAB/RAB parameters for faster
         * output.  User-specified RMS_DEFAULT values override the
         * built-in default values, so if the RMS_DEFAULT values could
         * not be determined, then these (possibly unwise) values could
         * not be overridden, and hence will not be set.  Honestly,
         * this seems to be excessively cautious, but only old VMS
         * versions will be affected.
         */

        /* If RMS_DEFAULT (and adjusted active) values are available,
         * then set the FAB/RAB parameters.  If RMS_DEFAULT values are
         * not available, then suffer with the default behavior.
         */
        if (rms_defaults_known > 0)
        {
            /* Set the FAB/RAB parameters accordingly. */
            fileblk.fab$w_deq = rms_ext_active;
            rab.rab$b_mbc = rms_mbc_active;
            rab.rab$b_mbf = rms_mbf_active;

#ifdef OLD_FABDEF

            /* Truncate at EOF on close, as we may over-extend. */
            fileblk.fab$l_fop |= FAB$M_TEF ;

            /* If using multiple buffers, enable write-behind. */
            if (rms_mbf_active > 1)
            {
                rab.rab$l_rop |= RAB$M_WBH;
            }
        }

        /* Set the initial file allocation according to the file
         * size.  Also set the "sequential access only" flag, as
         * otherwise, on a file system with highwater marking
         * enabled, allocating space for a large file may lock the
         * disk for a long time (minutes).
         */
        fileblk.fab$l_alq = (unsigned) (G.lrec.ucsize+ 511)/ 512;
        fileblk.fab$l_fop |= FAB$M_SQO;

#else /* !OLD_FABDEF */

            /* Truncate at EOF on close, as we may over-extend. */
            fileblk.fab$v_tef = 1;

            /* If using multiple buffers, enable write-behind. */
            if (rms_mbf_active > 1)
            {
                rab.rab$v_wbh = 1;
            }
        }

        /* Set the initial file allocation according to the file
         * size.  Also set the "sequential access only" flag, as
         * otherwise, on a file system with highwater marking
         * enabled, allocating space for a large file may lock the
         * disk for a long time (minutes).
         */
        fileblk.fab$l_alq = (unsigned) (G.lrec.ucsize+ 511)/ 512;
        fileblk.fab$v_sqo = 1;

#endif /* ?OLD_FABDEF */

        ierr = sys$create(outfab);
        if (ierr == RMS$_FEX)
        {
            /* File exists.
             * Consider command-line options, or ask the user what to do.
             */
            ierr = replace(__G);
            switch (ierr & REPL_TASKMASK)
            {
                case REPL_NO_EXTRACT:   /* No extract. */
                    free_up();
                    return ((ierr & REPL_ERRLV_WARN)
                            ? OPENOUT_SKIPWARN : OPENOUT_SKIPOK);
                case REPL_NEW_VERSION:  /* Create a new version. */
                    ierr = replace_rms_newversion(__G);
                    break;
                case REPL_OVERWRITE:    /* Overwrite the existing file. */
                    ierr = replace_rms_overwrite(__G);
                    break;
            }
        }

        if (ERR(ierr))
        {
            char buf[NAM_MAXRSS + 128]; /* Name length + message length. */

            sprintf(buf, "[ Cannot create ($create) output file %s ]\n",
              G.filename);
            vms_msg(__G__ buf, ierr);
            if (fileblk.fab$l_stv != 0)
            {
                vms_msg(__G__ "", fileblk.fab$l_stv);
            }
            free_up();
            return OPENOUT_FAILED;
        }

        if (!text_output)
        {
            rab.rab$l_rop |= (RAB$M_BIO | RAB$M_ASY);
        }
        rab.rab$b_rac = RAB$C_SEQ;

        if ((ierr = sys$connect(&rab)) != RMS$_NORMAL)
        {
#ifdef DEBUG
            vms_msg(__G__ "create_default_output: sys$connect failed.\n", ierr);
            if (fileblk.fab$l_stv != 0)
            {
                vms_msg(__G__ "", fileblk.fab$l_stv);
            }
#endif
            Info(slide, 1, ((char *)slide,
                 "Cannot create ($connect) output file:  %s\n",
                 FnFilter1(G.filename)));
            free_up();
            return OPENOUT_FAILED;
        }
    }                   /* end if (!uO.cflag) */

    init_buf_ring();

    _flush_routine = text_output ? got_eol=0,_flush_stream : _flush_blocks;
    _close_routine = _close_rms;
    return OPENOUT_OK;
}



/* The following return codes are supported:
 *      OPENOUT_OK            a file has been opened normally
 *      OPENOUT_FAILED        the file open process failed
 *      OPENOUT_SKIPOK        file open skipped at user request, err level OK
 *      OPENOUT_SKIPWARN      file open skipped at user request, err level WARN
 */
static int create_rms_output(__GPRO)
{
    int ierr;
    int text_output;

    /* extract the file in text (variable-length) format, when
     * piping to SYS$OUTPUT, unless "binary" piping was requested
     * by the user (through the -b option); the "-a" option is
     * ignored when extracting zip entries with VMS attributes saved
     */
    text_output = uO.cflag &&
                  (!uO.bflag || (!(uO.bflag - 1) && G.pInfo->textfile));

    rfm = outfab->fab$b_rfm;    /* Use record format from VMS extra field */

    if (uO.cflag)               /* SYS$OUTPUT */
    {
        if (text_output && !PRINTABLE_FORMAT(rfm))
        {
            Info(slide, 1, ((char *)slide,
               "[ File %s has illegal record format to put to screen ]\n",
               FnFilter1(G.filename)));
            free_up();
            return OPENOUT_FAILED;
        }
    }
    else                        /* File output */
    {
        rab = cc$rms_rab;               /* Initialize RAB. */

        /* The output FAB has already been initialized with the values
         * found in the Zip file's "VMS attributes" extra field.
         */

#ifdef NAML$C_MAXRSS

        nam = CC_RMS_NAM;               /* Initialize NAML. */
        outfab->FAB_NAM = &nam;         /* Point FAB to NAML. */

        outfab->fab$l_dna = (char *) -1;    /* Using NAML for default name. */
        outfab->fab$l_fna = (char *) -1;    /* Using NAML for file name. */

#endif /* NAML$C_MAXRSS */

        FAB_OR_NAML(*outfab, nam).FAB_OR_NAML_FNA = G.filename;
        FAB_OR_NAML(*outfab, nam).FAB_OR_NAML_FNS = strlen(G.filename);

        /* Prepare date-time XABs, unless user requests not to. */
        if (uO.D_flag <= 1) {
            /* If no XAB date/time, use attributes from non-VMS fields. */
            if (!(xabdat && xabrdt))
            {
                set_default_datetime_XABs(__G);

                if (xabdat == NULL)
                {
                    dattim.xab$l_nxt = outfab->fab$l_xab;
                    outfab->fab$l_xab = (void *) &dattim;
                }
            }
        }
/* 2005-02-14 SMS.  What does this mean?  ----vvvvvvvvvvvvvvvvvvvvvvvvvvv */
        outfab->fab$w_ifi = 0;  /* Clear IFI. It may be nonzero after ZIP */
        outfab->fab$b_fac = FAB$M_BIO | FAB$M_PUT;      /* block-mode output */
#ifdef SYMLINKS
        /* 2007-02-28 SMS.
         * VMS/RMS symlink properties will be restored naturally when
         * the link file is recreated this way, so there's no need to do
         * the deferred symlink post-processing step for this file.
         * Therefore, clear the pInfo->symlink flag here, and the symlink
         * "close file" processor will only display the link text.
         */
        if (G.symlnk) {
            G.pInfo->symlink = 0;
            if (QCOND2) {
                /* Symlink file is read back to display the link text. */
                outfab->fab$b_fac |= FAB$M_GET;
            }
        }
#endif /* SYMLINKS */

        /* 2004-11-23 SMS.
         * Set the "sequential access only" flag, as otherwise, on a
         * file system with highwater marking enabled, allocating space
         * for a large file may lock the disk for a long time (minutes).
         */
#ifdef OLD_FABDEF
        outfab-> fab$l_fop |= FAB$M_SQO;
#else /* !OLD_FABDEF */
        outfab-> fab$v_sqo = 1;
#endif /* ?OLD_FABDEF */

        ierr = sys$create(outfab);
        if (ierr == RMS$_FEX)
        {
            /* File exists.
             * Consider command-line options, or ask the user what to do.
             */
            ierr = replace(__G);
            switch (ierr & REPL_TASKMASK)
            {
                case REPL_NO_EXTRACT:   /* No extract. */
                    free_up();
                    return ((ierr & REPL_ERRLV_WARN)
                            ? OPENOUT_SKIPWARN : OPENOUT_SKIPOK);
                case REPL_NEW_VERSION:  /* Create a new version. */
                    ierr = replace_rms_newversion(__G);
                    break;
                case REPL_OVERWRITE:    /* Overwrite the existing file. */
                    ierr = replace_rms_overwrite(__G);
                    break;
            }
        }

        if (ERR(ierr))
        {
            char buf[NAM_MAXRSS + 128]; /* Name length + message length. */

            sprintf(buf, "[ Cannot create ($create) output file %s ]\n",
              G.filename);
            vms_msg(__G__ buf, ierr);
            if (outfab->fab$l_stv != 0)
            {
                vms_msg(__G__ "", outfab->fab$l_stv);
            }
            free_up();
            return OPENOUT_FAILED;
        }

        if (outfab->fab$b_org & (FAB$C_REL | FAB$C_IDX)) {
            /* relative and indexed files require explicit allocation */
            ierr = sys$extend(outfab);
            if (ERR(ierr))
            {
                char buf[NAM_MAXRSS + 128];    /* Name length + msg length. */

                sprintf(buf, "[ Cannot allocate space for %s ]\n", G.filename);
                vms_msg(__G__ buf, ierr);
                if (outfab->fab$l_stv != 0)
                {
                    vms_msg(__G__ "", outfab->fab$l_stv);
                }
                free_up();
                return OPENOUT_FAILED;
            }
        }

        outrab = &rab;
        rab.rab$l_fab = outfab;
        {
            rab.rab$l_rop |= (RAB$M_BIO | RAB$M_ASY);
        }
        rab.rab$b_rac = RAB$C_SEQ;

        if ((ierr = sys$connect(outrab)) != RMS$_NORMAL)
        {
#ifdef DEBUG
            vms_msg(__G__ "create_rms_output: sys$connect failed.\n", ierr);
            if (outfab->fab$l_stv != 0)
            {
                vms_msg(__G__ "", outfab->fab$l_stv);
            }
#endif
            Info(slide, 1, ((char *)slide,
                 "Cannot create ($connect) output file:  %s\n",
                 FnFilter1(G.filename)));
            free_up();
            return OPENOUT_FAILED;
        }
    }                   /* end if (!uO.cflag) */

    init_buf_ring();

    if ( text_output )
        switch (rfm)
        {
            case FAB$C_VAR:
                _flush_routine = _flush_varlen;
                break;
            case FAB$C_STM:
            case FAB$C_STMCR:
            case FAB$C_STMLF:
                _flush_routine = _flush_stream;
                got_eol = 0;
                break;
            default:
                _flush_routine = _flush_blocks;
                break;
        }
    else
        _flush_routine = _flush_blocks;
    _close_routine = _close_rms;
    return OPENOUT_OK;
}



static  int pka_devchn;
static  int pka_io_pending;
static  unsigned pka_vbn;

/* IOSB for QIO[W] read and write operations. */
#if defined(__DECC) || defined(__DECCXX)
#pragma __member_alignment __save
#pragma __nomember_alignment
#endif /* __DECC || __DECCXX */
static struct
{
    unsigned short  status;
    unsigned int    count;      /* Unaligned ! */
    unsigned short  dummy;
} pka_io_iosb;
#if defined(__DECC) || defined(__DECCXX)
#pragma __member_alignment __restore
#endif /* __DECC || __DECCXX */

/* IOSB for QIO[W] miscellaneous ACP operations. */
static struct
{
    unsigned short  status;
    unsigned short  dummy;
    unsigned int    count;
} pka_acp_iosb;

static struct fibdef    pka_fib;
static struct atrdef    pka_atr[VMS_MAX_ATRCNT];
static int              pka_idx;
static ulg              pka_uchar;
static struct fatdef    pka_rattr;

/* Directory attribute storage, descriptor (list). */
static struct atrdef pka_recattr[2] =
 { { sizeof(pka_rattr), ATR$C_RECATTR, GVTC &pka_rattr},        /* RECATTR. */
   { 0, 0, 0 }                                          /* List terminator. */
 };

static struct dsc$descriptor    pka_fibdsc =
{   sizeof(pka_fib), DSC$K_DTYPE_Z, DSC$K_CLASS_S, (void *) &pka_fib  };

static struct dsc$descriptor_s  pka_devdsc =
{   0, DSC$K_DTYPE_T, DSC$K_CLASS_S, &nam.NAM_DVI[1]  };

static struct dsc$descriptor_s  pka_fnam =
{   0, DSC$K_DTYPE_T, DSC$K_CLASS_S, NULL  };

/* Expanded and resultant name storage. */
static char exp_nam[NAM_MAXRSS];
static char res_nam[NAM_MAXRSS];

/* Special ODS5-QIO-compatible name storage. */
#ifdef NAML$C_MAXRSS
static char sys_nam[NAML$C_MAXRSS];     /* Probably need less here. */
#endif /* NAML$C_MAXRSS */

#define PK_PRINTABLE_RECTYP(x)   ( (x) == FAT$C_VARIABLE \
                                || (x) == FAT$C_STREAMLF \
                                || (x) == FAT$C_STREAMCR \
                                || (x) == FAT$C_STREAM   )


/* The following return codes are supported:
 *      OPENOUT_OK            a file has been opened normally
 *      OPENOUT_FAILED        the file open process failed
 *      OPENOUT_SKIPOK        file open skipped at user request, err level OK
 *      OPENOUT_SKIPWARN      file open skipped at user request, err level WARN
 */
static int create_qio_output(__GPRO)
{
    int status;
    int i;
    int text_output;

    /* extract the file in text (variable-length) format, when
     * piping to SYS$OUTPUT, unless "binary" piping was requested
     * by the user (through the -b option); the "-a" option is
     * ignored when extracting zip entries with VMS attributes saved
     */
    text_output = uO.cflag &&
                  (!uO.bflag || (!(uO.bflag - 1) && G.pInfo->textfile));

    if ( uO.cflag )
    {
        int rtype;

        if (text_output)
        {
            rtype = pka_rattr.fat$v_rtype;
            if (!PK_PRINTABLE_RECTYP(rtype))
            {
                Info(slide, 1, ((char *)slide,
                   "[ File %s has illegal record format to put to screen ]\n",
                   FnFilter1(G.filename)));
                return OPENOUT_FAILED;
            }
        }
        else
            /* force "block I/O" for binary piping mode */
            rtype = FAT$C_UNDEFINED;

        init_buf_ring();

        switch (rtype)
        {
            case FAT$C_VARIABLE:
                _flush_routine = _flush_varlen;
                break;
            case FAT$C_STREAM:
            case FAT$C_STREAMCR:
            case FAT$C_STREAMLF:
                _flush_routine = _flush_stream;
                got_eol = 0;
                break;
            default:
                _flush_routine = _flush_blocks;
                break;
        }
        _close_routine = _close_rms;
    }
    else                        /* !(uO.cflag) : redirect output */
    {
        fileblk = cc$rms_fab;           /* Initialize FAB. */
        nam = CC_RMS_NAM;               /* Initialize NAM[L]. */
        fileblk.FAB_NAM = &nam;         /* Point FAB to NAM[L]. */

#ifdef NAML$C_MAXRSS

        fileblk.fab$l_dna = (char *) -1;    /* Using NAML for default name. */
        fileblk.fab$l_fna = (char *) -1;    /* Using NAML for file name. */

        /* Special ODS5-QIO-compatible name storage. */
        nam.naml$l_filesys_name = sys_nam;
        nam.naml$l_filesys_name_alloc = sizeof(sys_nam);

#endif /* NAML$C_MAXRSS */

        /* VMS-format file name, derived from archive. */
        FAB_OR_NAML(fileblk, nam).FAB_OR_NAML_FNA = G.filename;
        FAB_OR_NAML(fileblk, nam).FAB_OR_NAML_FNS = strlen(G.filename);

        /* Expanded and resultant name storage. */
        nam.NAM_ESA = exp_nam;
        nam.NAM_ESS = sizeof(exp_nam);
        nam.NAM_RSA = res_nam;
        nam.NAM_RSS = sizeof(res_nam);

        if ( ERR(status = sys$parse(&fileblk)) )
        {
            vms_msg(__G__ "create_qio_output: sys$parse failed.\n", status);
            return OPENOUT_FAILED;
        }

        pka_devdsc.dsc$w_length = (unsigned short)nam.NAM_DVI[0];

        if ( ERR(status = sys$assign(&pka_devdsc, &pka_devchn, 0, 0)) )
        {
            vms_msg(__G__ "create_qio_output: sys$assign failed.\n", status);
            return OPENOUT_FAILED;
        }

#ifdef NAML$C_MAXRSS

        /* Enable fancy name characters.  Note that "fancy" here does
           not include Unicode, for which there's no support elsewhere.
        */
        pka_fib.fib$v_names_8bit = 1;
        pka_fib.fib$b_name_format_in = FIB$C_ISL1;

        /* ODS5 Extended names used as input to QIO have peculiar
           encoding (perhaps to minimize storage?), so the special
           filesys_name result (typically containing fewer carets) must
           be used here.
        */
        pka_fnam.dsc$a_pointer = nam.naml$l_filesys_name;
        pka_fnam.dsc$w_length = nam.naml$l_filesys_name_size;

#else /* !NAML$C_MAXRSS */

        /* Extract only the name.type;version.
           2005-02-14 SMS.
           Note: In old code, the version in the name here was retained
           only if -V (uO.V_flag, so that there might be an explicit
           version number in the archive (or perhaps not)), but the
           version should already have been stripped before this in
           adj_file_name_odsX(), and sys$parse() here should always
           return a good version number which may be used as-is.  If
           not, here's where to fix the (new) problem.  Note that the
           ODS5-compatible code uses the whole thing in filesys_name,
           too, and that's critical for proper interpretation of funny
           names.  (Omitting the ";" can cause trouble, so it should
           certainly be kept, even if the version digits are removed
           here.)
        */

        pka_fnam.dsc$a_pointer = nam.NAM_L_NAME;
        pka_fnam.dsc$w_length =
          nam.NAM_B_NAME + nam.NAM_B_TYPE + nam.NAM_B_VER;

#if 0
        pka_fnam.dsc$w_length = nam.NAM_B_NAME + nam.NAM_B_TYPE;
        if ( uO.V_flag /* keep versions */ )
            pka_fnam.dsc$w_length += nam.NAM_B_VER;
#endif /* 0 */

#endif /* ?NAML$C_MAXRSS */

        /* Move the directory ID from the NAM[L] to the FIB.
           Clear the FID in the FIB, as we're using the name.
        */
        for (i = 0; i < 3; i++)
        {
            pka_fib.FIB$W_DID[i] = nam.NAM_DID[i];
            pka_fib.FIB$W_FID[i] = 0;
        }

        /* 2004-11-23 SMS.
         * Set the "sequential access only" flag, as otherwise, on a
         * file system with highwater marking enabled, allocating space
         * for a large file may lock the disk for a long time (minutes).
         * (The "no other readers" flag is also required, if you want
         * the "sequential access only" flag to have any effect.)
         */
        pka_fib.FIB$L_ACCTL = FIB$M_WRITE | FIB$M_SEQONLY | FIB$M_NOREAD;

        /* Allocate space for the file */
        pka_fib.FIB$W_EXCTL = FIB$M_EXTEND;
        if ( pka_uchar & FCH$M_CONTIG )
            pka_fib.FIB$W_EXCTL |= FIB$M_ALCON | FIB$M_FILCON;
        if ( pka_uchar & FCH$M_CONTIGB )
            pka_fib.FIB$W_EXCTL |= FIB$M_ALCONB;

#define SWAPW(x)        ( (((x)>>16)&0xFFFF) + ((x)<<16) )

        pka_fib.fib$l_exsz = SWAPW(pka_rattr.fat$l_hiblk);

        status = sys$qiow(0,                /* event flag */
                          pka_devchn,       /* channel */
                          IO$_CREATE|IO$M_CREATE|IO$M_ACCESS, /* funct */
                          &pka_acp_iosb,    /* IOSB */
                          0,                /* AST address */
                          0,                /* AST parameter */
                          &pka_fibdsc,      /* P1 = File Info Block */
                          &pka_fnam,        /* P2 = File name (descr) */
                          0,                /* P3 (= Resulting name len) */
                          0,                /* P4 (= Resulting name descr) */
                          pka_atr,          /* P5 = Attribute descr */
                          0);               /* P6 (not used) */

        if ( !ERR(status) )
            status = pka_acp_iosb.status;

        if ( status == SS$_DUPFILENAME )
        {
            /* File exists.  Prepare to ask user what to do. */

            /* Arrange to store the resultant file spec (with new
             * version?) where the message code will find it.
             */
            short res_nam_len;
            struct dsc$descriptor_s  res_nam_dscr =
              { 0, DSC$K_DTYPE_T, DSC$K_CLASS_S, NULL };

            res_nam_dscr.dsc$a_pointer = G.filename;
            res_nam_dscr.dsc$w_length = sizeof(G.filename);

            /* File exists.
             * Consider command-line options, or ask the user what to do.
             */
            status = replace(__G);
            switch (status & REPL_TASKMASK)
            {
                case REPL_NO_EXTRACT:   /* No extract. */
                    free_up();
                    return ((status & REPL_ERRLV_WARN)
                            ? OPENOUT_SKIPWARN : OPENOUT_SKIPOK);
                case REPL_NEW_VERSION:  /* Create a new version. */
                    pka_fib.FIB$W_NMCTL |= FIB$M_NEWVER;
                    break;
                case REPL_OVERWRITE:    /* Overwrite the existing file. */
                    pka_fib.FIB$W_NMCTL |= FIB$M_SUPERSEDE;
                    break;
            }

            /* Retry file creation with new (user-specified) policy. */
            status = sys$qiow(0,                /* event flag */
                              pka_devchn,       /* channel */
                              IO$_CREATE|IO$M_CREATE|IO$M_ACCESS, /* funct */
                              &pka_acp_iosb,    /* IOSB */
                              0,                /* AST address */
                              0,                /* AST parameter */
                              &pka_fibdsc,      /* P1 = File Info Block */
                              &pka_fnam,        /* P2 = File name (descr) */
                              &res_nam_len,     /* P3 = Resulting name len */
                              &res_nam_dscr,    /* P4 = Resulting name descr */
                              pka_atr,          /* P5 = Attribute descr */
                              0);               /* P6 (not used) */

            if ( !ERR(status) )
                status = pka_acp_iosb.status;

            if (res_nam_len > 0)
            {
                /* NUL-terminate the resulting file spec. */
                G.filename[res_nam_len] = '\0';
            }

            /* Clear any user-specified version policy flags
             * (for the next file to be processed).
             */
            pka_fib.FIB$W_NMCTL &= (~(FIB$M_NEWVER| FIB$M_SUPERSEDE));
        }

        if ( ERR(status) )
        {
            char buf[NAM_MAXRSS + 128]; /* Name length + message length. */

            sprintf(buf, "[ Cannot create (QIO) output file %s ]\n",
              G.filename);
            vms_msg(__G__ buf, status);
            sys$dassgn(pka_devchn);
            return OPENOUT_FAILED;
        }

#ifdef ASYNCH_QIO
        init_buf_ring();
        pka_io_pending = FALSE;
#else
        locptr = locbuf;
        loccnt = 0;
#endif
        pka_vbn = 1;
        _flush_routine = _flush_qio;
        _close_routine = _close_qio;
    }                   /* end if (!uO.cflag) */
    return OPENOUT_OK;
}


/* 2008-07-23 SMS.
 * Segregated user query function from file re-open functions/code.
 *
 * There was no code in create_qio_output() to deal with an
 * SS$_DUPFILENAME condition, leading to ugly run-time failures, and its
 * requirements differ from those of the RMS (non-QIO) functions,
 * create_default_output() and create_rms_output().
 *
 * Whether it makes sense to have a second layer of VMS-specific
 * querying after the generic UnZip query in extract.c:
 * extract_or_test_entrylist() is another question, but changing that
 * looks more scary than just getting the VMS-specific stuff to work
 * right (better?).
 */

/* "File exists" handler(s).  Ask user about further action. */

/* RMS create new version. */
static int replace_rms_newversion(__GPRO)
{
    int ierr;
    struct NAM_STRUCT nam;

    nam = CC_RMS_NAM;           /* Initialize local NAM[L] block. */
    outfab->FAB_NAM = &nam;     /* Point FAB to local NAM[L]. */

    /* Arrange to store the resultant file spec (with new version), so
     * that we can extract the actual file version from it, for later
     * use in the "extracting:/inflating:/..."  message (G.filename).
     */
    nam.NAM_RSA = res_nam;
    nam.NAM_RSS = sizeof(res_nam);

#ifdef NAML$C_MAXRSS

    outfab->fab$l_dna = (char *) -1;    /* Using NAML for default name. */
    outfab->fab$l_fna = (char *) -1;    /* Using NAML for file name. */

#endif /* NAML$C_MAXRSS */

    FAB_OR_NAML(*outfab, nam).FAB_OR_NAML_FNA = G.filename;
    FAB_OR_NAML(*outfab, nam).FAB_OR_NAML_FNS = strlen(G.filename);

    /* Maximize version number. */
    outfab->fab$l_fop |= FAB$M_MXV;

    /* Create the new-version file. */
    ierr = sys$create(outfab);

    if (nam.NAM_RSL > 0)
    {
        /* File spec version pointers.
         * Versions must exist, so a simple right-to-left search for ";"
         * should work, even on ODS5 extended file specs.
         */
        char *semi_col_orig;
        char *semi_col_res;

        /* NUL-terminate the (complete) resultant file spec. */
        res_nam[nam.NAM_RSL] = '\0';

        /* Find the versions (";") in the original and resultant file specs. */
        semi_col_orig = strrchr(G.filename, ';');
        semi_col_res = strrchr(res_nam, ';');

        if ((semi_col_orig != NULL) && (semi_col_res != NULL))
        {
            /* Transfer the resultant version to the original file spec. */
            strcpy((semi_col_orig + 1), (semi_col_res + 1));
        }
    }
    return ierr;
}


/* RMS overwrite original version. */
static int replace_rms_overwrite(__GPRO)
{
    /* Supersede existing file. */
    outfab->fab$l_fop |= FAB$M_SUP;
    /* Create (overwrite) the original-version file. */
    return sys$create(outfab);
}


/* Main query function to ask user how to handle an existing file
 * (unless command-line options already specify what to do).
 */
static int replace(__GPRO)
{
    char answ[10];
    int replace_code;

    if (replace_code_all >= 0)
    {
        /* Use the previous all-file response. */
        replace_code = replace_code_all;
    }
    else if (uO.overwrite_none)
    {
        /* "-n".  Do not extract this (or any) file. */
        replace_code = replace_code_all = REPL_NO_EXTRACT;
    }
    else if (uO.overwrite_all == 1)
    {
        /* "-o".  Create a new version of this (or any) file. */
        replace_code = replace_code_all = REPL_NEW_VERSION;
    }
    else if (uO.overwrite_all > 1)
    {
        /* "-oo".  Overwrite (supersede) this (or any) existing file. */
        replace_code = replace_code_all = REPL_OVERWRITE;
    }
    else
    {
        replace_code = -1;
        do
        {
            /* Request, accept, and decode a response. */
            Info(slide, 0x81, ((char *)slide,
              "%s exists:  new [v]ersion, [o]verwrite, or [n]o extract?\n\
  (Uppercase response [V,O,N] => Do same for all files): ",
              FnFilter1(G.filename)));
            fflush(stderr);

            if (fgets(answ, sizeof(answ), stdin) == (char *)NULL)
            {
                Info(slide, 1, ((char *)slide, AssumeNo));
                /* Handle the NULL answer as "N",
                 * do not extract any existing files.  */
                replace_code_all = REPL_NO_EXTRACT;
                /* Set a warning indicator. */
                replace_code = REPL_NO_EXTRACT | REPL_ERRLV_WARN;
                /* We are finished, break out of the query loop. */
                break;
            }

            /* Strip off a trailing newline, to avoid corrupt
             * complaints when displaying the answer.
             */
            if (answ[strlen(answ) - 1] == '\n')
                answ[strlen(answ) - 1] = '\0';

            /* Extra newline to avoid having the extracting:/inflating:/...:
             * message overwritten by the next query.
             */
            Info(slide, 1, ((char *)slide, "\n"));

            /* Interpret response.  Store upper-case answer for future use. */
            switch (answ[0])
            {
                case 'N':
                    replace_code_all = REPL_NO_EXTRACT;
                case 'n':
                    /* Do not extract this file. */
                    replace_code = REPL_NO_EXTRACT;
                    break;
                case 'O':
                    replace_code_all = REPL_OVERWRITE;
                case 'o':
                    /* Overwrite (supersede) this existing file. */
                    replace_code = REPL_OVERWRITE;
                    break;
                case 'V':
                    replace_code_all = REPL_NEW_VERSION;
                case 'v':
                    /* Create a new version of this file. */
                    replace_code = REPL_NEW_VERSION;
                    break;
                default:
                    /* Invalid response.  Try again. */
                    Info(slide, 1, ((char *)slide, InvalidResponse, answ));
            }
        } while (replace_code < 0);
    }
    return replace_code;
}



#define W(p)    (*(unsigned short*)(p))
#define L(p)    (*(unsigned long*)(p))
#define EQL_L(a, b)     ( L(a) == L(b) )
#define EQL_W(a, b)     ( W(a) == W(b) )

/*
 * Function find_vms_attrs() scans the ZIP entry extra field, if any,
 * and looks for VMS attribute records.  Various date-time attributes
 * are ignored if set_date_time is FALSE (typically for a directory).
 *
 * For a set of IZ records, a FAB and various XABs are created and
 * chained together.
 *
 * For a PK record, the pka_atr[] attribute descriptor array is
 * populated.
 *
 * The return value is a VAT_* value, according to the type of extra
 * field attribute data found.
 */
static int find_vms_attrs(__GPRO__ int set_date_time)
{
    uch *scan = G.extra_field;
    struct  EB_header *hdr;
    int len;
    int type=VAT_NONE;

    outfab = NULL;
    xabfhc = NULL;
    xabdat = NULL;
    xabrdt = NULL;
    xabpro = NULL;
    first_xab = last_xab = NULL;

    if (scan == NULL)
        return VAT_NONE;
    len = G.lrec.extra_field_length;

#define LINK(p) {/* Link xaballs and xabkeys into chain */      \
                if ( first_xab == NULL )                \
                        first_xab = (void *) p;         \
                if ( last_xab != NULL )                 \
                        last_xab->xab$l_nxt = (void *) p;       \
                last_xab = (void *) p;                  \
                p->xab$l_nxt = NULL;                    \
        }
    /* End of macro LINK */

    while (len > 0)
    {
        hdr = (struct EB_header *)scan;
        if (EQL_W(&hdr->tag, IZ_SIGNATURE))
        {
            /*
             *  Info-ZIP-style extra block decoding.
             */
            uch *blk;
            unsigned siz;
            uch *block_id;

            type = VAT_IZ;

            siz = hdr->size;
            blk = (uch *)(&hdr->data[0]);
            block_id = (uch *)(&((struct IZ_block *)hdr)->bid);

            if (EQL_L(block_id, FABSIG)) {
                outfab = (struct FAB *)extract_izvms_block(__G__ blk,
                  siz, NULL, (uch *)&cc$rms_fab, FABL);
            } else if (EQL_L(block_id, XALLSIG)) {
                xaball = (struct XABALL *)extract_izvms_block(__G__ blk,
                  siz, NULL, (uch *)&cc$rms_xaball, XALLL);
                LINK(xaball);
            } else if (EQL_L(block_id, XKEYSIG)) {
                xabkey = (struct XABKEY *)extract_izvms_block(__G__ blk,
                  siz, NULL, (uch *)&cc$rms_xabkey, XKEYL);
                LINK(xabkey);
            } else if (EQL_L(block_id, XFHCSIG)) {
                xabfhc = (struct XABFHC *) extract_izvms_block(__G__ blk,
                  siz, NULL, (uch *)&cc$rms_xabfhc, XFHCL);
            } else if (EQL_L(block_id, XDATSIG)) {
                if (set_date_time) {
                    xabdat = (struct XABDAT *) extract_izvms_block(__G__ blk,
                      siz, NULL, (uch *)&cc$rms_xabdat, XDATL);
                }
            } else if (EQL_L(block_id, XRDTSIG)) {
                if (set_date_time) {
                    xabrdt = (struct XABRDT *) extract_izvms_block(__G__ blk,
                      siz, NULL, (uch *)&cc$rms_xabrdt, XRDTL);
                }
            } else if (EQL_L(block_id, XPROSIG)) {
                xabpro = (struct XABPRO *) extract_izvms_block(__G__ blk,
                  siz, NULL, (uch *)&cc$rms_xabpro, XPROL);
            } else if (EQL_L(block_id, VERSIG)) {
#ifdef CHECK_VERSIONS
                char verbuf[80];
                unsigned verlen = 0;
                uch *vers;
                char *m;

                get_vms_version(verbuf, sizeof(verbuf));
                vers = extract_izvms_block(__G__ blk, siz,
                                           &verlen, NULL, 0);
                if ((m = strrchr((char *) vers, '-')) != NULL)
                    *m = '\0';  /* Cut out release number */
                if (strcmp(verbuf, (char *) vers) && uO.qflag < 2)
                {
                    Info(slide, 0, ((char *)slide,
                         "[ Warning: VMS version mismatch."));

                    Info(slide, 0, ((char *)slide,
                         "   This version %s --", verbuf));
                    strncpy(verbuf, (char *) vers, verlen);
                    verbuf[verlen] = '\0';
                    Info(slide, 0, ((char *)slide,
                         " version made by %s ]\n", verbuf));
                }
                free(vers);
#endif /* CHECK_VERSIONS */
            } else {
                Info(slide, 1, ((char *)slide,
                     "[ Warning: Unknown block signature %s ]\n",
                     block_id));
            }
        }
        else if (hdr->tag == PK_SIGNATURE)
        {
            /*
             *  PKWARE-style extra block decoding.
             */
            struct  PK_header   *blk;
            register byte   *scn;
            register int    len;

            type = VAT_PK;

            blk = (struct PK_header *)hdr;
            len = blk->size - (PK_HEADER_SIZE - EB_HEADSIZE);
            scn = (byte *)(&blk->data);
            pka_idx = 0;

            if (blk->crc32 != crc32(CRCVAL_INITIAL, scn, (extent)len))
            {
                Info(slide, 1, ((char *)slide,
                  "[ Warning: CRC error, discarding PKWARE extra field ]\n"));
                len = 0;
                type = VAT_NONE;
            }

            while (len > PK_FLDHDR_SIZE)
            {
                register struct  PK_field  *fld;
                int skip=0;

                fld = (struct PK_field *)scn;
                switch(fld->tag)
                {
                    case ATR$C_UCHAR:
                        pka_uchar = L(&fld->value);
                        break;
                    case ATR$C_RECATTR:
                        pka_rattr = *(struct fatdef *)(&fld->value);
                        break;
                    case ATR$C_UIC:
                    case ATR$C_ADDACLENT:
                        skip = !uO.X_flag;
                        break;
                    case ATR$C_CREDATE:
                    case ATR$C_REVDATE:
                    case ATR$C_EXPDATE:
                    case ATR$C_BAKDATE:
                    case ATR$C_ASCDATES:
                        skip = (set_date_time == FALSE);
                        break;
                }

                if ( !skip )
                {
                    pka_atr[pka_idx].atr$w_size = fld->size;
                    pka_atr[pka_idx].atr$w_type = fld->tag;
                    pka_atr[pka_idx].atr$l_addr = GVTC &fld->value;
                    ++pka_idx;
                }
                len -= fld->size + PK_FLDHDR_SIZE;
                scn += fld->size + PK_FLDHDR_SIZE;
            }
            pka_atr[pka_idx].atr$w_size = 0;    /* End of list */
            pka_atr[pka_idx].atr$w_type = 0;
            pka_atr[pka_idx].atr$l_addr = 0; /* NULL when DECC VAX gets fixed */
        }
        len -= hdr->size + EB_HEADSIZE;
        scan += hdr->size + EB_HEADSIZE;
    }

    if ( type == VAT_IZ )
    {
        if (outfab != NULL)
        {
            /* Do not link XABPRO or XABRDT now.
             * Leave them for sys$close() resp. set_direc_attribs().
             */
            outfab->fab$l_xab = NULL;
            if (xabfhc != NULL)
            {
                xabfhc->xab$l_nxt = outfab->fab$l_xab;
                outfab->fab$l_xab = (void *) xabfhc;
            }
            if (xabdat != NULL)
            {
                xabdat->xab$l_nxt = outfab->fab$l_xab;
                outfab->fab$l_xab = (void *) xabdat;
            }
            if (first_xab != NULL)      /* Link xaball,xabkey subchain */
            {
                last_xab->xab$l_nxt = outfab->fab$l_xab;
                outfab->fab$l_xab = (void *) first_xab;
            }
        }
        else
            type = VAT_NONE;
    }
    return type;
}



static void free_up()
{
    /*
     * Free up all allocated XABs.
     */
    if (xabdat != NULL) free(xabdat);
    if (xabpro != NULL) free(xabpro);
    if (xabrdt != NULL) free(xabrdt);
    if (xabfhc != NULL) free(xabfhc);
    while (first_xab != NULL)
    {
        struct XAB *x;

        x = (struct XAB *) first_xab->xab$l_nxt;
        free(first_xab);
        first_xab = x;
    }
    /* Free FAB storage, if not the static one. */
    if (outfab != NULL && outfab != &fileblk)
        free(outfab);
}



#ifdef CHECK_VERSIONS

static int get_vms_version(verbuf, len)
    char *verbuf;
    int len;
{
    int i = SYI$_VERSION;
    int verlen = 0;
    struct dsc$descriptor version;
    char *m;

    version.dsc$a_pointer = verbuf;
    version.dsc$w_length  = len - 1;
    version.dsc$b_dtype   = DSC$K_DTYPE_B;
    version.dsc$b_class   = DSC$K_CLASS_S;

    if (ERR(lib$getsyi(&i, 0, &version, &verlen, 0, 0)) || verlen == 0)
        return 0;

    /* Cut out trailing spaces "V5.4-3   " -> "V5.4-3" */
    for (m = verbuf + verlen, i = verlen - 1; i > 0 && verbuf[i] == ' '; --i)
        --m;
    *m = '\0';

    /* Cut out release number "V5.4-3" -> "V5.4" */
    if ((m = strrchr(verbuf, '-')) != NULL)
        *m = '\0';
    return strlen(verbuf) + 1;  /* Transmit ending '\0' too */
}

#endif /* CHECK_VERSIONS */



/* flush contents of output buffer */
int flush(__G__ rawbuf, size, unshrink)    /* return PK-type error code */
    __GDEF
    uch *rawbuf;
    ulg size;
    int unshrink;
{
    G.crc32val = crc32(G.crc32val, rawbuf, (extent)size);
    if (uO.tflag)
        return PK_COOL; /* Do not output. Update CRC only */
    else
        return (*_flush_routine)(__G__ rawbuf, size, 0);
}



static int _flush_blocks(__G__ rawbuf, size, final_flag)
                                                /* Asynchronous version */
    __GDEF
    uch *rawbuf;
    unsigned size;
    int final_flag;   /* 1 if this is the final flushout */
{
    int status;
    unsigned off = 0;

    while (size > 0)
    {
        if (curbuf->bufcnt < BUFS512)
        {
            unsigned ncpy;

            ncpy = size > (BUFS512 - curbuf->bufcnt) ?
                   (BUFS512 - curbuf->bufcnt) : size;
            memcpy(curbuf->buf + curbuf->bufcnt, rawbuf + off, ncpy);
            size -= ncpy;
            curbuf->bufcnt += ncpy;
            off += ncpy;
        }
        if (curbuf->bufcnt == BUFS512)
        {
            status = WriteBuffer(__G__ curbuf->buf, curbuf->bufcnt);
            if (status)
                return status;
            curbuf = curbuf->next;
            curbuf->bufcnt = 0;
        }
    }

    return (final_flag && (curbuf->bufcnt > 0)) ?
        WriteBuffer(__G__ curbuf->buf, curbuf->bufcnt) :
        PK_COOL;
}



#ifdef ASYNCH_QIO
static int WriteQIO(__G__ buf, len)
    __GDEF
    uch *buf;
    unsigned len;
{
    int status;

    if (pka_io_pending) {
        status = sys$synch(0, &pka_io_iosb);
        if (!ERR(status))
            status = pka_io_iosb.status;
        if (ERR(status))
        {
            vms_msg(__G__ "[ WriteQIO: sys$synch found I/O failure ]\n",
                    status);
            return PK_DISK;
        }
        pka_io_pending = FALSE;
    }
    /*
     *   Put content of buffer as a single VB
     */
    status = sys$qio(0, pka_devchn, IO$_WRITEVBLK,
                     &pka_io_iosb, 0, 0,
                     buf, len, pka_vbn,
                     0, 0, 0);
    if (ERR(status))
    {
        vms_msg(__G__ "[ WriteQIO: sys$qio failed ]\n", status);
        return PK_DISK;
    }
    pka_io_pending = TRUE;
    pka_vbn += (len>>9);

    return PK_COOL;
}

/*
   2004-10-01 SMS.  Changed to clear the extra byte written out by qio()
   and sys$write() when an odd byte count is incremented to the next
   even value, either explicitly (qio), or implicitly (sys$write), on
   the theory that a reliable NUL beats left-over garbage.  Alpha and
   VAX object files seem frequently to have even more than one byte of
   extra junk past EOF, so this may not help them.
*/

static int _flush_qio(__G__ rawbuf, size, final_flag)
                                                /* Asynchronous version */
    __GDEF
    uch *rawbuf;
    unsigned size;
    int final_flag;   /* 1 if this is the final flushout */
{
    int status;
    unsigned off = 0;

    while (size > 0)
    {
        if (curbuf->bufcnt < BUFS512)
        {
            unsigned ncpy;

            ncpy = size > (BUFS512 - curbuf->bufcnt) ?
                   (BUFS512 - curbuf->bufcnt) : size;
            memcpy(curbuf->buf + curbuf->bufcnt, rawbuf + off, ncpy);
            size -= ncpy;
            curbuf->bufcnt += ncpy;
            off += ncpy;
        }
        if (curbuf->bufcnt == BUFS512)
        {
            status = WriteQIO(__G__ curbuf->buf, curbuf->bufcnt);
            if (status)
                return status;
            curbuf = curbuf->next;
            curbuf->bufcnt = 0;
        }
    }

    if (final_flag && (curbuf->bufcnt > 0))
    {
        unsigned bufcnt_even;

        /* Round up to an even byte count. */
        bufcnt_even = (curbuf->bufcnt+1) & (~1);
        /* If there is one, clear the extra byte. */
        if (bufcnt_even > curbuf->bufcnt)
            curbuf->buf[curbuf->bufcnt] = '\0';

        return WriteQIO(curbuf->buf, bufcnt_even);
    }
    else
    {
        return PK_COOL;
    }
}

#else /* !ASYNCH_QIO */

static int _flush_qio(__G__ rawbuf, size, final_flag)
    __GDEF
    uch *rawbuf;
    unsigned size;
    int final_flag;   /* 1 if this is the final flushout */
{
    int status;
    uch *out_ptr=rawbuf;

    if ( final_flag )
    {
        if ( loccnt > 0 )
        {
            unsigned loccnt_even;

            /* Round up to an even byte count. */
            loccnt_even = (loccnt+1) & (~1);
            /* If there is one, clear the extra byte. */
            if (loccnt_even > loccnt)
                locbuf[loccnt] = '\0';

            status = sys$qiow(0, pka_devchn, IO$_WRITEVBLK,
                              &pka_io_iosb, 0, 0,
                              locbuf,
                              loccnt_even,
                              pka_vbn,
                              0, 0, 0);
            if (!ERR(status))
                status = pka_io_iosb.status;
            if (ERR(status))
            {
                vms_msg(__G__ "[ Write QIO failed ]\n", status);
                return PK_DISK;
            }
        }
        return PK_COOL;
    }

    if ( loccnt > 0 )
    {
        /*
         *   Fill local buffer upto 512 bytes then put it out
         */
        unsigned ncpy;

        ncpy = 512-loccnt;
        if ( ncpy > size )
            ncpy = size;

        memcpy(locptr, out_ptr, ncpy);
        locptr += ncpy;
        loccnt += ncpy;
        size -= ncpy;
        out_ptr += ncpy;
        if ( loccnt == 512 )
        {
            status = sys$qiow(0, pka_devchn, IO$_WRITEVBLK,
                              &pka_io_iosb, 0, 0,
                              locbuf, loccnt, pka_vbn,
                              0, 0, 0);
            if (!ERR(status))
                status = pka_io_iosb.status;
            if (ERR(status))
            {
                vms_msg(__G__ "[ Write QIO failed ]\n", status);
                return PK_DISK;
            }

            pka_vbn++;
            loccnt = 0;
            locptr = locbuf;
        }
    }

    if ( size >= 512 )
    {
        unsigned nblk, put_cnt;

        /*
         *   Put rest of buffer as a single VB
         */
        put_cnt = (nblk = size>>9)<<9;
        status = sys$qiow(0, pka_devchn, IO$_WRITEVBLK,
                          &pka_io_iosb, 0, 0,
                          out_ptr, put_cnt, pka_vbn,
                          0, 0, 0);
        if (!ERR(status))
            status = pka_io_iosb.status;
        if (ERR(status))
        {
            vms_msg(__G__ "[ Write QIO failed ]\n", status);
            return PK_DISK;
        }

        pka_vbn += nblk;
        out_ptr += put_cnt;
        size -= put_cnt;
    }

    if ( size > 0 )
    {
        memcpy(locptr, out_ptr, size);
        loccnt += size;
        locptr += size;
    }

    return PK_COOL;
}
#endif /* ?ASYNCH_QIO */



/*
 * The routine _flush_varlen() requires: "(size & 1) == 0"
 * (The variable-length record algorithm assumes an even byte-count!)
 */
static int _flush_varlen(__G__ rawbuf, size, final_flag)
    __GDEF
    uch *rawbuf;
    unsigned size;
    int final_flag;
{
    unsigned nneed;
    unsigned reclen;
    uch *inptr=rawbuf;

    /*
     * Flush local buffer
     */

    if ( loccnt > 0 )           /* incomplete record left from previous call */
    {
        reclen = *(ush*)locbuf;
        nneed = reclen + 2 - loccnt;
        if ( nneed > size )
        {
            if ( size+loccnt > BUFSMAXREC )
            {
                char buf[80];
                Info(buf, 1, (buf,
                     "[ Record too long (%u bytes) ]\n", reclen));
                return PK_DISK;
            }
            memcpy(locbuf+loccnt, inptr, size);
            loccnt += size;
            size = 0;
        }
        else
        {
            memcpy(locbuf+loccnt, inptr, nneed);
            loccnt += nneed;
            size -= nneed;
            inptr += nneed;
            if ( reclen & 1 )
            {
                size--;
                inptr++;
            }
            if ( WriteRecord(__G__ locbuf+2, reclen) )
                return PK_DISK;
            loccnt = 0;
        }
    }
    /*
     * Flush incoming records
     */
    while (size > 0)
    {
        reclen = *(ush*)inptr;
        if ( reclen+2 <= size )
        {
            if (WriteRecord(__G__ inptr+2, reclen))
                return PK_DISK;
            size -= 2+reclen;
            inptr += 2+reclen;
            if ( reclen & 1 )
            {
                --size;
                ++inptr;
            }
        }
        else
        {
            memcpy(locbuf, inptr, size);
            loccnt = size;
            size = 0;
        }

    }
    /*
     * Final flush rest of local buffer
     */
    if ( final_flag && loccnt > 0 )
    {
        char buf[80];

        Info(buf, 1, (buf,
             "[ Warning, incomplete record of length %u ]\n",
             (unsigned)*(ush*)locbuf));
        if ( WriteRecord(__G__ locbuf+2, loccnt-2) )
            return PK_DISK;
    }
    return PK_COOL;
}



/*
 *   Routine _flush_stream breaks decompressed stream into records
 *   depending on format of the stream (fab->rfm, G.pInfo->textmode, etc.)
 *   and puts out these records. It also handles CR LF sequences.
 *   Should be used when extracting *text* files.
 */

#define VT      0x0B
#define FF      0x0C

/* The file is from MSDOS/OS2/NT -> handle CRLF as record end, throw out ^Z */

/* GRR NOTES:  cannot depend on hostnum!  May have "flip'd" file or re-zipped
 * a Unix file, etc. */

#ifdef USE_ORIG_DOS
# define ORG_DOS \
          (G.pInfo->hostnum==FS_FAT_    \
        || G.pInfo->hostnum==FS_HPFS_   \
        || G.pInfo->hostnum==FS_NTFS_)
#else
# define ORG_DOS    1
#endif

/* Record delimiters */
#ifdef undef
#define RECORD_END(c, f)                                                \
(    ( ORG_DOS || G.pInfo->textmode ) && c==CTRLZ                       \
  || ( f == FAB$C_STMLF && c==LF )                                      \
  || ( f == FAB$C_STMCR || ORG_DOS || G.pInfo->textmode ) && c==CR      \
  || ( f == FAB$C_STM && (c==CR || c==LF || c==FF || c==VT) )           \
)
#else
#   define  RECORD_END(c, f)   ((c) == LF || (c) == (CR))
#endif

static unsigned find_eol(p, n, l)
/*
 *  Find first CR, LF, CR/LF or LF/CR in string 'p' of length 'n'.
 *  Return offset of the sequence found or 'n' if not found.
 *  If found, return in '*l' length of the sequence (1 or 2) or
 *  zero if sequence end not seen, i.e. CR or LF is last char
 *  in the buffer.
 */
    ZCONST uch *p;
    unsigned n;
    unsigned *l;
{
    unsigned off = n;
    ZCONST uch *q;

    *l = 0;

    for (q=p ; n > 0 ; --n, ++q)
        if ( RECORD_END(*q, rfm) )
        {
            off = q-p;
            break;
        }

    if ( n > 1 )
    {
        *l = 1;
        if ( ( q[0] == CR && q[1] == LF ) || ( q[0] == LF && q[1] == CR ) )
            *l = 2;
    }

    return off;
}

/* Record delimiters that must be put out */
#define PRINT_SPEC(c)   ( (c)==FF || (c)==VT )



static int _flush_stream(__G__ rawbuf, size, final_flag)
    __GDEF
    uch *rawbuf;
    unsigned size;
    int final_flag; /* 1 if this is the final flushout */
{
    int rest;
    unsigned end = 0, start = 0;

    if (size == 0 && loccnt == 0)
        return PK_COOL;         /* Nothing to do ... */

    if ( final_flag )
    {
        unsigned recsize;

        /*
         * This is flush only call. size must be zero now.
         * Just eject everything we have in locbuf.
         */
        recsize = loccnt - (got_eol ? 1 : 0);
        /*
         *  If the last char of file was ^Z ( end-of-file in MSDOS ),
         *  we will see it now.
         */
        if ( recsize==1 && locbuf[0] == CTRLZ )
            return PK_COOL;

        return WriteRecord(__G__ locbuf, recsize);
    }


    if ( loccnt > 0 )
    {
        /* Find end of record partially saved in locbuf */

        unsigned recsize;
        int complete=0;

        if ( got_eol )
        {
            recsize = loccnt - 1;
            complete = 1;

            if ( (got_eol == CR && rawbuf[0] == LF) ||
                 (got_eol == LF && rawbuf[0] == CR) )
                end = 1;

            got_eol = 0;
        }
        else
        {
            unsigned eol_len;
            unsigned eol_off;

            eol_off = find_eol(rawbuf, size, &eol_len);

            if ( loccnt+eol_off > BUFSMAXREC )
            {
                /*
                 *  No room in locbuf. Dump it and clear
                 */
                char buf[80];           /* CANNOT use slide for Info() */

                recsize = loccnt;
                start = 0;
                Info(buf, 1, (buf,
                     "[ Warning: Record too long (%u) ]\n", loccnt+eol_off));
                complete = 1;
                end = 0;
            }
            else
            {
                if ( eol_off >= size )
                {
                    end = size;
                    complete = 0;
                }
                else if ( eol_len == 0 )
                {
                    got_eol = rawbuf[eol_off];
                    end = size;
                    complete = 0;
                }
                else
                {
                    memcpy(locptr, rawbuf, eol_off);
                    recsize = loccnt + eol_off;
                    locptr += eol_off;
                    loccnt += eol_off;
                    end = eol_off + eol_len;
                    complete = 1;
                }
            }
        }

        if ( complete )
        {
            if (WriteRecord(__G__ locbuf, recsize))
                return PK_DISK;
            loccnt = 0;
            locptr = locbuf;
        }
    }                           /* end if ( loccnt ) */

    for (start = end; start < size && end < size; )
    {
        unsigned eol_off, eol_len;

        got_eol = 0;

#ifdef undef
        if (uO.cflag)
            /* skip CR's at the beginning of record */
            while (start < size && rawbuf[start] == CR)
                ++start;
#endif

        if ( start >= size )
            continue;

        /* Find record end */
        end = start+(eol_off = find_eol(rawbuf+start, size-start, &eol_len));

        if ( end >= size )
            continue;

        if ( eol_len > 0 )
        {
            if ( WriteRecord(__G__ rawbuf+start, end-start) )
                return PK_DISK;
            start = end + eol_len;
        }
        else
        {
            got_eol = rawbuf[end];
            end = size;
            continue;
        }
    }

    rest = size - start;

    if (rest > 0)
    {
        if ( rest > BUFSMAXREC )
        {
            unsigned recsize;
            char buf[80];               /* CANNOT use slide for Info() */

            recsize = rest - (got_eol ? 1 : 0 );
            Info(buf, 1, (buf,
                 "[ Warning: Record too long (%u) ]\n", recsize));
            got_eol = 0;
            return WriteRecord(__G__ rawbuf+start, recsize);
        }
        else
        {
            memcpy(locptr, rawbuf + start, rest);
            locptr += rest;
            loccnt += rest;
        }
    }
    return PK_COOL;
}



static int WriteBuffer(__G__ buf, len)
    __GDEF
    uch *buf;
    unsigned len;
{
    int status;

    if (uO.cflag)
    {
        (void)(*G.message)((zvoid *)&G, buf, len, 0);
    }
    else
    {
        status = sys$wait(outrab);
        if (ERR(status))
        {
            vms_msg(__G__ "[ WriteBuffer: sys$wait failed ]\n", status);
            if (outrab->rab$l_stv != 0)
            {
                vms_msg(__G__ "", outrab->rab$l_stv);
            }
        }

        /* If odd byte count, then this must be the final record.
           Clear the extra byte past EOF to help keep the file clean.
        */
        if (len & 1)
            buf[len] = '\0';

        outrab->rab$w_rsz = len;
        outrab->rab$l_rbf = (char *) buf;

        if (ERR(status = sys$write(outrab)))
        {
            vms_msg(__G__ "[ WriteBuffer: sys$write failed ]\n", status);
            if (outrab->rab$l_stv != 0)
            {
                vms_msg(__G__ "", outrab->rab$l_stv);
            }
            return PK_DISK;
        }
    }
    return PK_COOL;
}



static int WriteRecord(__G__ rec, len)
    __GDEF
    uch *rec;
    unsigned len;
{
    int status;

    if (uO.cflag)
    {
        (void)(*G.message)((zvoid *)&G, rec, len, 0);
        (void)(*G.message)((zvoid *)&G, (uch *) ("\n"), 1, 0);
    }
    else
    {
        if (ERR(status = sys$wait(outrab)))
        {
            vms_msg(__G__ "[ WriteRecord: sys$wait failed ]\n", status);
            if (outrab->rab$l_stv != 0)
            {
                vms_msg(__G__ "", outrab->rab$l_stv);
            }
        }
        outrab->rab$w_rsz = len;
        outrab->rab$l_rbf = (char *) rec;

        if (ERR(status = sys$put(outrab)))
        {
            vms_msg(__G__ "[ WriteRecord: sys$put failed ]\n", status);
            if (outrab->rab$l_stv != 0)
            {
                vms_msg(__G__ "", outrab->rab$l_stv);
            }
            return PK_DISK;
        }
    }
    return PK_COOL;
}



#ifdef SYMLINKS
/* Read symlink text from a still-open rms file. */

static int _read_link_rms(int byte_count, char *link_text_buf)
{
    /* Use RMS to read the link text into the user's buffer.
     * Rewind, then read byte count = byte_count.
     * NUL-terminate the link text.
     *
     * $WAIT may be pointless if not async, but $WAIT elsewhere seems
     * to be used unconditionally, so what do I know?
     */
    int sts;
    int bytes_read;

    /* Clear the bytes-read count. */
    bytes_read = 0;

    /* Wait for anything pending. */
    sts = sys$wait(outrab);
    {
        /* Rewind. */
        sts = sys$rewind(outrab);
        if (!ERR(sts))
        {
            /* Wait for $REWIND. */
            sts = sys$wait(outrab);
            if (!ERR(sts))
            {
                /* Read the link text. */
                outrab->rab$w_usz = byte_count;
                outrab->rab$l_ubf = link_text_buf;
                sts = sys$read(outrab);
                if (!ERR(sts))
                {
                    /* Wait for $READ. */
                    sts = sys$wait(outrab);

                    if (!ERR(sts))
                        /* Set the resultant byte count. */
                        bytes_read = outrab->rab$w_rsz;
                }
            }
        }
    }

    /* NUL-terminate the link text. */
    link_text_buf[bytes_read] = '\0';

    return sts;
}

#endif /* SYMLINKS */



void close_outfile(__G)
    __GDEF
{
    int status;

    status = (*_flush_routine)(__G__ NULL, 0, 1);
    if (status)
        return /* PK_DISK */;
    if (uO.cflag)
        return /* PK_COOL */;   /* Don't close stdout */
    /* return */ (*_close_routine)(__G);
}



static int _close_rms(__GPRO)
{
    int status;
    struct XABPRO pro;
    int retcode = PK_OK;

#ifdef SYMLINKS

/*----------------------------------------------------------------------
    UNIX description:
    If symbolic links are supported, allocate storage for a symlink
    control structure, put the uncompressed "data" and other required
    info in it, and add the structure to the "deferred symlinks" chain.
    Since we know it's a symbolic link to start with, we shouldn't have
    to worry about overflowing unsigned ints with unsigned longs.
----------------------------------------------------------------------*/

    if (G.symlnk) {
        extent ucsize = (extent)G.lrec.ucsize;

        /* 2007-03-03 SMS.
         * If the symlink is already a symlink (restored with VMS/RMS
         * symlink attributes), then read the link text from the file,
         * and close the file (using the appropriate methods), and then
         * return.
         */
        if (G.pInfo->symlink == 0)
        {
            if (QCOND2)
            {
                /* Link text storage. */
                char* link_target = malloc(ucsize + 1);

                if (link_target == NULL)
                {
                    Info(slide, 0x201, ((char *)slide,
                      "warning:  cannot show symlink (%s) target, no mem\n",
                      FnFilter1(G.filename)));
                      retcode = PK_MEM;
                }
                else
                {
                    /* Read the link text. */
                    status = _read_link_rms(ucsize, link_target);

                    if (ERR(status))
                    {
                        Info(slide, 0x201, ((char *)slide,
                          "warning:  error reading symlink text: %s\n",
                          strerror(EVMSERR, status)));
                        retcode = PK_DISK;
                    }
                    else
                    {
                        Info(slide, 0, ((char *)slide, "-> %s ",
                          FnFilter1(link_target)));
                    }

                    free(link_target);
                }
            }
        }
        else
        {
            extent slnk_entrysize;
            slinkentry *slnk_entry;

            /* It's a symlink in need of post-processing. */
            /* Size of the symlink entry is the sum of
             *  (struct size (includes 1st '\0') + 1 additional trailing '\0'),
             *  system specific attribute data size (might be 0),
             *  and the lengths of name and link target.
             */
            slnk_entrysize = (sizeof(slinkentry) + 1) +
                             ucsize + strlen(G.filename);

            if (slnk_entrysize < ucsize) {
                Info(slide, 0x201, ((char *)slide,
                  "warning:  symbolic link (%s) failed: mem alloc overflow\n",
                  FnFilter1(G.filename)));
                retcode = PK_ERR;
            }
            else
            {
                if ((slnk_entry = (slinkentry *)malloc(slnk_entrysize))
                    == NULL) {
                    Info(slide, 0x201, ((char *)slide,
                      "warning:  symbolic link (%s) failed, no mem\n",
                      FnFilter1(G.filename)));
                    retcode = PK_MEM;
                }
                else
                {
                    slnk_entry->next = NULL;
                    slnk_entry->targetlen = ucsize;
                    /* don't set attributes for symlinks */
                    slnk_entry->attriblen = 0;
                    slnk_entry->target = slnk_entry->buf;
                    slnk_entry->fname = slnk_entry->target + ucsize + 1;
                    strcpy(slnk_entry->fname, G.filename);

                    /* Read the link text using the appropriate method. */
                    status = _read_link_rms(ucsize, slnk_entry->target);

                    if (ERR(status))
                    {
                        Info(slide, 0x201, ((char *)slide,
                          "warning:  error reading symlink text (rms): %s\n",
                          strerror(EVMSERR, status)));
                        free(slnk_entry);
                        retcode = PK_DISK;
                    }
                    else
                    {
                        if (QCOND2)
                            Info(slide, 0, ((char *)slide, "-> %s ",
                              FnFilter1(slnk_entry->target)));

                        /* Add this symlink record to the list of
                           deferred symlinks. */
                        if (G.slink_last != NULL)
                            G.slink_last->next = slnk_entry;
                        else
                            G.slink_head = slnk_entry;
                        G.slink_last = slnk_entry;
                    }
                }
            }
        }
    }
#endif /* SYMLINKS */

    /* Link XABRDT, XABDAT, and (optionally) XABPRO. */
    if (xabrdt != NULL)
    {
        xabrdt->xab$l_nxt = NULL;
        outfab->fab$l_xab = (void *) xabrdt;
    }
    else
    {
        rdt.xab$l_nxt = NULL;
        outfab->fab$l_xab = (void *) &rdt;
    }
    if (xabdat != NULL)
    {
        xabdat->xab$l_nxt = outfab->fab$l_xab;
        outfab->fab$l_xab = (void *)xabdat;
    }

    if (xabpro != NULL)
    {
        if ( !uO.X_flag )
            xabpro->xab$l_uic = 0;    /* Use default (user's) uic */
        xabpro->xab$l_nxt = outfab->fab$l_xab;
        outfab->fab$l_xab = (void *) xabpro;
    }
    else
    {
        pro = cc$rms_xabpro;
        pro.xab$w_pro = G.pInfo->file_attr;
        pro.xab$l_nxt = outfab->fab$l_xab;
        outfab->fab$l_xab = (void *) &pro;
    }

    status = sys$wait(outrab);
    if (ERR(status))
    {
        vms_msg(__G__ "[ _close_rms: sys$wait failed ]\n", status);
        if (outrab->rab$l_stv != 0)
        {
            vms_msg(__G__ "", outrab->rab$l_stv);
        }
    }

    status = sys$close(outfab);
#ifdef DEBUG
    if (ERR(status))
    {
        vms_msg(__G__
          "\r[ Warning: cannot set owner/protection/time attributes ]\n",
          status);
        if (outfab->fab$l_stv != 0)
        {
            vms_msg(__G__ "", outfab->fab$l_stv);
        }
        retcode = PK_WARN;
    }
#endif
    free_up();
    return retcode;
}



static int _close_qio(__GPRO)
{
    int status;

    pka_fib.FIB$L_ACCTL =
        FIB$M_WRITE | FIB$M_NOTRUNC ;
    pka_fib.FIB$W_EXCTL = 0;

    pka_fib.FIB$W_FID[0] =
    pka_fib.FIB$W_FID[1] =
    pka_fib.FIB$W_FID[2] =
    pka_fib.FIB$W_DID[0] =
    pka_fib.FIB$W_DID[1] =
    pka_fib.FIB$W_DID[2] = 0;

#ifdef ASYNCH_QIO
    if (pka_io_pending) {
        status = sys$synch(0, &pka_io_iosb);
        if (!ERR(status))
            status = pka_io_iosb.status;
        if (ERR(status))
        {
            vms_msg(__G__ "[ _close_qio: sys$synch found I/O failure ]\n",
                    status);
        }
        pka_io_pending = FALSE;
    }
#endif /* ASYNCH_QIO */

#ifdef SYMLINKS
    if (G.symlnk && QCOND2)
    {
        /* Read back the symlink target specification for display purpose. */
        extent ucsize = (extent)G.lrec.ucsize;
        char *link_target;   /* Link text storage. */

        if ((link_target = malloc(ucsize + 1)) == NULL)
        {
            Info(slide, 0x201, ((char *)slide,
              "warning:  cannot show symlink (%s) target, no mem\n",
              FnFilter1(G.filename)));
        }
        else
        {
            unsigned bytes_read = 0;

            status = sys$qiow(0,                /* event flag */
                              pka_devchn,       /* channel */
                              IO$_READVBLK,     /* function */
                              &pka_io_iosb,     /* IOSB */
                              0,                /* AST address */
                              0,                /* AST parameter */
                              link_target,      /* P1 = buffer address */
                              ucsize,           /* P2 = requested byte count */
                              1,                /* P3 = VBN (1 = first) */
                              0,                /* P4 (not used) */
                              0,                /* P5 (not used) */
                              0);               /* P6 (not used) */

            if (!ERR(status))
                /* Final status. */
                status = pka_io_iosb.status;

            /* Set the resultant byte count. */
            if (!ERR(status))
                bytes_read = pka_io_iosb.count;

            /* NUL-terminate the link text. */
            link_target[bytes_read] = '\0';

            if (ERR(status))
            {
                Info(slide, 0x201, ((char *)slide,
                  "warning:  error reading symlink text (qio): %s\n",
                  strerror(EVMSERR, status)));
            }
            else
            {
                Info(slide, 0, ((char *)slide, "-> %s ",
                  FnFilter1(link_target)));
            }

            free(link_target);

        }
    }
#endif /* SYMLINKS */

    status = sys$qiow(0, pka_devchn, IO$_DEACCESS, &pka_acp_iosb,
                      0, 0,
                      &pka_fibdsc, 0, 0, 0,
                      pka_atr, 0);

    sys$dassgn(pka_devchn);
    if ( !ERR(status) )
        status = pka_acp_iosb.status;
    if ( ERR(status) )
    {
        vms_msg(__G__ "[ Deaccess QIO failed ]\n", status);
        return PK_DISK;
    }
    return PK_COOL;
}



#ifdef SET_DIR_ATTRIB

/*
 * 2006-10-04 SMS.
 * vms_path_fixdown().
 *
 * Convert VMS directory spec to VMS directory file name.  That is,
 * change "dev:[a.b.c.e]" to "dev:[a.b.c]e.DIR;1".  The result (always
 * larger than the source) is returned in the user's buffer.
 */

#define DIR_TYPE_VER ".DIR;1"

static char *vms_path_fixdown(ZCONST char *dir_spec, char *dir_file)
{
    char dir_close;
    char dir_open;
    unsigned i;
    unsigned dir_spec_len;

    dir_spec_len = strlen(dir_spec);
    if (dir_spec_len == 0) return NULL;
    i = dir_spec_len - 1;
    dir_close = dir_spec[i];

    /* Identify the directory delimiters (which must exist). */
    if (dir_close == ']')
    {
        dir_open = '[';
    }
    else if (dir_close == '>')
    {
        dir_open = '<';
    }
    else
    {
        return NULL;
    }

    /* Find the beginning of the last directory name segment. */
    while ((i > 0) && ((dir_spec[i - 1] == '^') ||
           ((dir_spec[i] != '.') && (dir_spec[i] != dir_open))))
    {
        i--;
    }

    /* Form the directory file name from the pieces. */
    if (dir_spec[i] == dir_open)
    {
        /* Top-level directory. */
        sprintf(dir_file, "%.*s000000%c%.*s%s",
          /*  "dev:[" "000000" "]" */
          (i + 1), dir_spec, dir_close,
          /*  "a" ".DIR;1" */
          (dir_spec_len - i - 2), (dir_spec + i + 1), DIR_TYPE_VER);
    }
    else
    {
        /* Non-top-level directory. */
        sprintf(dir_file, "%.*s%c%.*s%s",
          /*  "dev:[a.b.c" "]" */
          i, dir_spec, dir_close,
          /*  "e" ".DIR;1" */
          (dir_spec_len - i - 2), (dir_spec + i + 1), DIR_TYPE_VER);
    }
    return dir_file;
} /* end function vms_path_fixdown(). */



/* Save directory attributes (as the archive's extra field). */

/* 2006-12-13 SMS.
 * This could probably be made more efficient by analyzing the data
 * here, extracting the important data, and saving only what's needed.
 * Given the existing code, it seemed simpler to save them all here, and
 * deal with what's important in set_direc_attribs().
 */

int defer_dir_attribs(__G__ pd)
    __GDEF
    direntry **pd;
{
    vmsdirattr *d_entry;
    unsigned fnlen;
    unsigned xlen;

    /* Allocate space to save the file (directory) name, the extra
     * block, and all the other data needed by the extra-block data
     * scanner functions.  If that works, save the data.
     */
    fnlen = strlen(G.filename);
    xlen = G.lrec.extra_field_length;
    d_entry = (vmsdirattr *) malloc(sizeof(vmsdirattr) + fnlen + xlen);
    *pd = (direntry *) d_entry;
    if (d_entry == (vmsdirattr *) NULL)
    {
        return PK_MEM;
    }

    /* Save extra block length and data. */
    d_entry->xlen = xlen;
    memcpy(d_entry->buf, G.extra_field, xlen);

    /* Set pointer to file (directory) name. */
    d_entry->fn = d_entry->buf + xlen;

    /* Save file (directory) name. */
    strcpy(d_entry->fn, G.filename);
    /* Strip the closing ']' char, to allow proper sorting. */
    d_entry->fn[fnlen - 1] = '\0';

    /* Save generic permission data from mapattr(). */
    d_entry->perms = G.pInfo->file_attr;

    /* Save G.lrec.last_mod_dos_datetime. */
    d_entry->mod_dos_datetime = G.lrec.last_mod_dos_datetime;

    return PK_OK;
} /* end function defer_dir_attribs() */



int set_direc_attribs(__G__ d)
    __GDEF
    direntry *d;
{
    uch *sav_ef_ptr;
    int i;
    int status;
    int type;
    ush attr;
    struct XABPRO pro;
    char dir_name[NAM_MAXRSS + 1];
    char warnmsg[NAM_MAXRSS + 128]; /* Name length + message length. */
    int retcode = PK_OK;

    /* Re-append the closing ']' character which has been stripped in
     * defer_dir_attribs() for compatibility with generic sorting code.
     */
    strcat(VmsAtt(d)->fn, "]");

    /* Convert "[a.b.c]" form into "[a.b]c.DIR;1" */
    vms_path_fixdown(VmsAtt(d)->fn, dir_name);

    /* Dummy up critical global (G) data from the preserved directory
     * attribute data.
     */
    sav_ef_ptr = G.extra_field;
    G.extra_field = (uch *)((VmsAtt(d)->xlen > 0) ? VmsAtt(d)->buf : NULL);
    G.lrec.extra_field_length = VmsAtt(d)->xlen;

    /* Extract the VMS file attributes from the preserved attribute
     * data, if they exist, and restore the date-time stamps.
     */
    type = find_vms_attrs(__G__ (uO.D_flag <= 0));

    if (outfab == NULL)
    {
        /* Default and PK schemes need a FAB.  (IZ supplies one.)
         * In a degenerate case, this could be the first use of fileblk,
         * so we assume that we need to initialize it.
         */
        fileblk = cc$rms_fab;           /* Initialize FAB. */
        outfab = &fileblk;              /* Set pointer used elsewhere. */
    }

    /* Arrange FAB-NAM[L] for file (directory) access. */
    if (type != VAT_NONE)
    {
        if (type == VAT_IZ)
        {
            /* Make an attribute descriptor list for the VMS creation and
             * revision dates (which were stored in the IZ XABs by
             * find_vms_attrs()).
             */
            pka_idx = 0;

            if (xabrdt != NULL)
            {
                /* Revision date-time from XABRDT. */
                pka_atr[pka_idx].atr$w_size = 8;
                pka_atr[pka_idx].atr$w_type = ATR$C_REVDATE;
                pka_atr[pka_idx].atr$l_addr = GVTC &xabrdt->xab$q_rdt;
                ++pka_idx;
            }
            if (xabdat != NULL)
            {
                /* Trust the XABRDT value for revision date. */
                if (xabrdt == NULL)
                {
                    /* Revision date-time from XABDAT. */
                    pka_atr[pka_idx].atr$w_size = 8;
                    pka_atr[pka_idx].atr$w_type = ATR$C_REVDATE;
                    pka_atr[pka_idx].atr$l_addr = GVTC &xabdat->xab$q_rdt;
                    ++pka_idx;
                }
                /* Creation date-time from XABDAT. */
                pka_atr[pka_idx].atr$w_size = 8;
                pka_atr[pka_idx].atr$w_type = ATR$C_CREDATE;
                pka_atr[pka_idx].atr$l_addr = GVTC &xabdat->xab$q_cdt;
                ++pka_idx;
            }
            if (xabpro != NULL)
            {
                if ( uO.X_flag ) {
                    pka_atr[pka_idx].atr$w_size = 4;
                    pka_atr[pka_idx].atr$w_type = ATR$C_UIC;
                    pka_atr[pka_idx].atr$l_addr = GVTC &xabpro->xab$l_uic;
                    ++pka_idx;
                }
                attr = xabpro->xab$w_pro;
            }
            else
            {
                /* Revoke directory Delete permission for all. */
                attr = VmsAtt(d)->perms
                      | (((1<< XAB$V_NODEL)<< XAB$V_SYS)|
                         ((1<< XAB$V_NODEL)<< XAB$V_OWN)|
                         ((1<< XAB$V_NODEL)<< XAB$V_GRP)|
                         ((1<< XAB$V_NODEL)<< XAB$V_WLD));
            }
            pka_atr[pka_idx].atr$w_size = 2;
            pka_atr[pka_idx].atr$w_type = ATR$C_FPRO;
            pka_atr[pka_idx].atr$l_addr = GVTC &attr;
            ++pka_idx;
        }
    }
    else
    {
        /* No VMS attribute data were found.  Prepare to assemble
         * non-VMS attribute data.
         */
        pka_idx = 0;

        /* Get the (already converted) non-VMS permissions. */
        attr = VmsAtt(d)->perms;        /* Use right-sized prot storage. */

        /* Revoke directory Delete permission for all. */
        attr |= (((1<< XAB$V_NODEL)<< XAB$V_SYS)|
                 ((1<< XAB$V_NODEL)<< XAB$V_OWN)|
                 ((1<< XAB$V_NODEL)<< XAB$V_GRP)|
                 ((1<< XAB$V_NODEL)<< XAB$V_WLD));

        pka_atr[pka_idx].atr$w_size = 2;
        pka_atr[pka_idx].atr$w_type = ATR$C_FPRO;
        pka_atr[pka_idx].atr$l_addr = GVTC &attr;
        ++pka_idx;

        /* Restore directory date-time if user requests it (-D). */
        if (uO.D_flag <= 0)
        {
            /* Set the directory date-time from the non-VMS data.
             * Dummy up the DOS-style modification date into global (G)
             * data from the preserved directory attribute data.
             */
            G.lrec.last_mod_dos_datetime = VmsAtt(d)->mod_dos_datetime;

            /* Extract date-time data from the normal attribute data. */
            set_default_datetime_XABs(__G);

            /* Make an attribute descriptor list for the VMS creation
             * and revision dates (which were stored in the XABs by
             * set_default_datetime_XABs()).
             */
            pka_atr[pka_idx].atr$w_size = 8;
            pka_atr[pka_idx].atr$w_type = ATR$C_CREDATE;
            pka_atr[pka_idx].atr$l_addr = GVTC &dattim.xab$q_cdt;
            ++pka_idx;
            pka_atr[pka_idx].atr$w_size = 8;
            pka_atr[pka_idx].atr$w_type = ATR$C_REVDATE;
            pka_atr[pka_idx].atr$l_addr = GVTC &rdt.xab$q_rdt;
            ++pka_idx;
        }

        /* Set the directory protection from the non-VMS data. */

        /* Terminate the attribute descriptor list. */
        pka_atr[pka_idx].atr$w_size = 0;    /* End of list */
        pka_atr[pka_idx].atr$w_type = 0;
        pka_atr[pka_idx].atr$l_addr = 0; /* NULL when DECC VAX gets fixed. */
    }

    nam = CC_RMS_NAM;               /* Initialize NAM[L]. */
    outfab->FAB_NAM = &nam;         /* Point FAB to NAM[L]. */

    /* Point the FAB-NAM[L] to the VMS-format directory file name. */

#ifdef NAML$C_MAXRSS

    outfab->fab$l_dna = (char *) -1;    /* Using NAML for default name. */
    outfab->fab$l_fna = (char *) -1;    /* Using NAML for file name. */

    /* Special ODS5-QIO-compatible name storage. */
    nam.naml$l_filesys_name = sys_nam;
    nam.naml$l_filesys_name_alloc = sizeof(sys_nam);

#endif /* NAML$C_MAXRSS */

    FAB_OR_NAML(*outfab, nam).FAB_OR_NAML_FNA = dir_name;
    FAB_OR_NAML(*outfab, nam).FAB_OR_NAML_FNS = strlen(dir_name);

    /* Expanded and resultant name storage. */
    nam.NAM_ESA = exp_nam;
    nam.NAM_ESS = sizeof(exp_nam);
    nam.NAM_RSA = res_nam;
    nam.NAM_RSS = sizeof(res_nam);

    status = sys$parse(outfab);
    if ( ERR(status) )
    {
        sprintf(warnmsg,
          "warning:  set-dir-attributes failed ($parse) for %s.\n",
          dir_name);
        vms_msg(__G__ warnmsg, status);
        retcode = PK_WARN;
        goto cleanup_exit;
    }

    /* Set the length in the device name descriptor. */
    pka_devdsc.dsc$w_length = (unsigned short) nam.NAM_DVI[0];

    /* Open a channel to the disk device. */
    status = sys$assign(&pka_devdsc, &pka_devchn, 0, 0);
    if ( ERR(status) )
    {
        sprintf(warnmsg,
          "warning:  set-dir-attributes failed ($assign) for %s.\n",
          dir_name);
        vms_msg(__G__ warnmsg, status);
        retcode = PK_WARN;
        goto cleanup_exit;
    }

    /* Move the directory ID from the NAM[L] to the FIB.
       Clear the FID in the FIB, as we're using the name.
    */
    for (i = 0; i < 3; i++)
    {
        pka_fib.FIB$W_DID[i] = nam.NAM_DID[i];
        pka_fib.FIB$W_FID[i] = 0;
    }

#ifdef NAML$C_MAXRSS

    /* Enable fancy name characters.  Note that "fancy" here does
       not include Unicode, for which there's no support elsewhere.
    */
    pka_fib.fib$v_names_8bit = 1;
    pka_fib.fib$b_name_format_in = FIB$C_ISL1;

    /* ODS5 Extended names used as input to QIO have peculiar
       encoding (perhaps to minimize storage?), so the special
       filesys_name result (typically containing fewer carets) must
       be used here.
    */
    pka_fnam.dsc$a_pointer = nam.naml$l_filesys_name;
    pka_fnam.dsc$w_length = nam.naml$l_filesys_name_size;

#else /* !NAML$C_MAXRSS */

    /* ODS2-only: Use the whole name. */
    pka_fnam.dsc$a_pointer = nam.NAM_L_NAME;
    pka_fnam.dsc$w_length = nam.NAM_B_NAME + nam.NAM_B_TYPE + nam.NAM_B_VER;

#endif /* ?NAML$C_MAXRSS */

    /* 2007-07-13 SMS.
     * Our freshly created directory can easily contain fewer files than
     * the original archived directory (for example, if not all the
     * files in the original directory were included in the archive), so
     * its size may differ from that of the archived directory.  Thus,
     * simply restoring the original RECATTR attributes structure, which
     * includes EFBLK (and so on) can cause "SYSTEM-W-BADIRECTORY, bad
     * directory file format" complaints.  Instead, we overwrite
     * selected archived attributes with current attributes, to avoid
     * setting obsolete/inappropriate attributes on the newly created
     * directory file.
     *
     * First, see if there is a RECATTR structure about which we need to
     * worry.
     */
    for (i = 0; pka_atr[i].atr$w_type != 0; i++)
    {
        if (pka_atr[i].atr$w_type == ATR$C_RECATTR)
        {
            /* We found a RECATTR structure which (we must assume) needs
             * adjustment.  Retrieve the RECATTR data for the existing
             * (newly created) directory file.
             */
            status = sys$qiow(0,                    /* event flag */
                              pka_devchn,           /* channel */
                              IO$_ACCESS,           /* function code */
                              &pka_acp_iosb,        /* IOSB */
                              0,                    /* AST address */
                              0,                    /* AST parameter */
                              &pka_fibdsc,          /* P1 = File Info Block */
                              &pka_fnam,            /* P2 = File name */
                              0,                    /* P3 = Rslt nm len */
                              0,                    /* P4 = Rslt nm str */
                              pka_recattr,          /* P5 = Attributes */
                              0);                   /* P6 (not used) */

            /* If initial success, then get the final status from the IOSB. */
            if ( !ERR(status) )
                status = pka_acp_iosb.status;

            if ( ERR(status) )
            {
                sprintf(warnmsg,
                  "warning:  set-dir-attributes failed ($qiow acc) for %s.\n",
                  dir_name);
                vms_msg(__G__ warnmsg, status);
                retcode = PK_WARN;
            }
            else
            {
                /* We should have valid RECATTR data.  Overwrite the
                 * critical bits of the archive RECATTR structure with
                 * the current bits.  The book says that an attempt to
                 * modify HIBLK will be ignored, and FFBYTE should
                 * always be zero, but safety is cheap.
                 */
                struct fatdef *ptr_recattr;

                ptr_recattr = (struct fatdef *) pka_atr[i].atr$l_addr;
                ptr_recattr->fat$l_hiblk =  pka_rattr.fat$l_hiblk;
                ptr_recattr->fat$l_efblk =  pka_rattr.fat$l_efblk;
                ptr_recattr->fat$w_ffbyte = pka_rattr.fat$w_ffbyte;
            }
        /* There should be only one RECATTR structure in the list, so
         * escape from the loop after the first/only one has been
         * processed.
         */
        break;
        }
    }

    /* Modify the file (directory) attributes. */
    status = sys$qiow(0,                            /* event flag */
                      pka_devchn,                   /* channel */
                      IO$_MODIFY,                   /* function code */
                      &pka_acp_iosb,                /* IOSB */
                      0,                            /* AST address */
                      0,                            /* AST parameter */
                      &pka_fibdsc,                  /* P1 = File Info Block */
                      &pka_fnam,                    /* P2 = File name */
                      0,                            /* P3 = Rslt nm len */
                      0,                            /* P4 = Rslt nm str */
                      pka_atr,                      /* P5 = Attributes */
                      0);                           /* P6 (not used) */

    /* If initial success, then get the final status from the IOSB. */
    if ( !ERR(status) )
        status = pka_acp_iosb.status;

    if ( ERR(status) )
    {
        sprintf(warnmsg,
          "warning:  set-dir-attributes failed ($qiow mod) for %s.\n",
          dir_name);
        vms_msg(__G__ warnmsg, status);
        retcode = PK_WARN;
    }
    sys$dassgn(pka_devchn);
cleanup_exit:
    free_up();                          /* Free FAB, XAB storage. */
    free(d);                            /* Free directory attribute storage. */
    G.extra_field = sav_ef_ptr;         /* Restore original pointer. */
    return retcode;
} /* end function set_direc_attribs() */

#endif /* SET_DIR_ATTRIB */



#ifdef TIMESTAMP

/* Nonzero if `y' is a leap year, else zero. */
#define leap(y) (((y) % 4 == 0 && (y) % 100 != 0) || (y) % 400 == 0)

/* Number of leap years from 1970 to `y' (not including `y' itself). */
#define nleap(y) (((y) - 1969) / 4 - ((y) - 1901) / 100 + ((y) - 1601) / 400)

/* Accumulated number of days from 01-Jan up to start of current month. */
static ZCONST short ydays[] = {
    0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365
};

/***********************/
/* Function mkgmtime() */
/***********************/

static time_t mkgmtime(tm)
    struct tm *tm;
{
    time_t m_time;
    int yr, mo, dy, hh, mm, ss;
    unsigned days;

    yr = tm->tm_year - 70;
    mo = tm->tm_mon;
    dy = tm->tm_mday - 1;
    hh = tm->tm_hour;
    mm = tm->tm_min;
    ss = tm->tm_sec;

    /* calculate days from BASE to this year and add expired days this year */
    dy = (unsigned)dy + ((unsigned)yr * 365) + (unsigned)nleap(yr+1970) +
         (unsigned)ydays[mo] + ((mo > 1) && leap(yr+1970));

    /* convert date & time to seconds relative to 00:00:00, 01/01/1970 */
    return (time_t)((unsigned long)(unsigned)dy * 86400L +
                    (unsigned long)hh * 3600L +
                    (unsigned long)(mm * 60 + ss));

} /* end function mkgmtime() */



/*******************************/
/* Function dos_to_unix_time() */  /* only used for timestamping of archives */
/*******************************/

time_t dos_to_unix_time(dosdatetime)
    ulg dosdatetime;
{
    struct tm *ltm;             /* Local time. */
    time_t loctime;             /* The time_t value of local time. */
    time_t then;                /* The time to return. */
    long tzoffset_adj;          /* timezone-adjustment `remainder' */
    int bailout_cnt;            /* counter of tries for tz correction */

    then = time(NULL);
    ltm = localtime(&then);

    /* dissect date */
    ltm->tm_year = ((int)(dosdatetime >> 25) & 0x7f) + 80;
    ltm->tm_mon  = ((int)(dosdatetime >> 21) & 0x0f) - 1;
    ltm->tm_mday = ((int)(dosdatetime >> 16) & 0x1f);

    /* dissect time */
    ltm->tm_hour = (int)(dosdatetime >> 11) & 0x1f;
    ltm->tm_min  = (int)(dosdatetime >> 5) & 0x3f;
    ltm->tm_sec  = (int)(dosdatetime << 1) & 0x3e;

    loctime = mkgmtime(ltm);

    /* Correct for the timezone and any daylight savings time.
       The correction is verified and repeated when not correct, to
       take into account the rare case that a change to or from daylight
       savings time occurs between when it is the time in `tm' locally
       and when it is that time in Greenwich. After the second correction,
       the "timezone & daylight" offset should be correct in all cases. To
       be sure, we allow a third try, but then the loop is stopped. */
    bailout_cnt = 3;
    then = loctime;
    do {
      ltm = localtime(&then);
      tzoffset_adj = (ltm != NULL) ? (loctime - mkgmtime(ltm)) : 0L;
      if (tzoffset_adj == 0L)
        break;
      then += tzoffset_adj;
    } while (--bailout_cnt > 0);

    if ( (dosdatetime >= DOSTIME_2038_01_18) &&
         (then < (time_t)0x70000000L) )
        then = U_TIME_T_MAX;    /* saturate in case of (unsigned) overflow */
    if (then < (time_t)0L)      /* a converted DOS time cannot be negative */
        then = S_TIME_T_MAX;    /*  -> saturate at max signed time_t value */
    return then;

} /* end function dos_to_unix_time() */



/*******************************/
/*  Function uxtime2vmstime()  */
/*******************************/

static void uxtime2vmstime(  /* convert time_t value into 64 bit VMS bintime */
    time_t utimeval,
    long int binval[2] )
{
    time_t m_time = utimeval;
    struct tm *t = localtime(&m_time);

    if (t == (struct tm *)NULL) {
        /* time conversion error; use current time instead, hoping
           that localtime() does not reject it as well! */
        m_time = time(NULL);
        t = localtime(&m_time);
    }
    sprintf(timbuf, "%02d-%3s-%04d %02d:%02d:%02d.00",
            t->tm_mday, month[t->tm_mon], t->tm_year + 1900,
            t->tm_hour, t->tm_min, t->tm_sec);
    sys$bintim(&date_str, binval);
} /* end function uxtime2vmstime() */



/***************************/
/*  Function stamp_file()  */  /* adapted from VMSmunch...it just won't die! */
/***************************/

int stamp_file(fname, modtime)
    ZCONST char *fname;
    time_t modtime;
{
    int status;
    int i;
    static long int Cdate[2], Rdate[2], Edate[2], Bdate[2];
    static short int revisions;
#if defined(__DECC) || defined(__DECCXX)
#pragma __member_alignment __save
#pragma __nomember_alignment
#endif /* __DECC || __DECCXX */
    static union {
      unsigned short int value;
      struct {
        unsigned system : 4;
        unsigned owner : 4;
        unsigned group : 4;
        unsigned world : 4;
      } bits;
    } prot;
#if defined(__DECC) || defined(__DECCXX)
#pragma __member_alignment __restore
#endif /* __DECC || __DECCXX */
    static unsigned long uic;
    static struct fjndef jnl;

    static struct atrdef Atr[] = {
        {sizeof(pka_rattr), ATR$C_RECATTR, GVTC &pka_rattr},
        {sizeof(pka_uchar), ATR$C_UCHAR, GVTC &pka_uchar},
        {sizeof(Cdate), ATR$C_CREDATE, GVTC &Cdate[0]},
        {sizeof(Rdate), ATR$C_REVDATE, GVTC &Rdate[0]},
        {sizeof(Edate), ATR$C_EXPDATE, GVTC &Edate[0]},
        {sizeof(Bdate), ATR$C_BAKDATE, GVTC &Bdate[0]},
        {sizeof(revisions), ATR$C_ASCDATES, GVTC &revisions},
        {sizeof(prot), ATR$C_FPRO, GVTC &prot},
        {sizeof(uic), ATR$C_UIC, GVTC &uic},
        {sizeof(jnl), ATR$C_JOURNAL, GVTC &jnl},
        {0, 0, 0}
    };

    fileblk = cc$rms_fab;               /* Initialize FAB. */
    nam = CC_RMS_NAM;                   /* Initialize NAM[L]. */
    fileblk.FAB_NAM = &nam;             /* Point FAB to NAM[L]. */

#ifdef NAML$C_MAXRSS

    fileblk.fab$l_dna = (char *) -1;    /* Using NAML for default name. */
    fileblk.fab$l_fna = (char *) -1;    /* Using NAML for file name. */

    /* Special ODS5-QIO-compatible name storage. */
    nam.naml$l_filesys_name = sys_nam;
    nam.naml$l_filesys_name_alloc = sizeof(sys_nam);

#endif /* NAML$C_MAXRSS */

    FAB_OR_NAML(fileblk, nam).FAB_OR_NAML_FNA = (char *)fname;
    FAB_OR_NAML(fileblk, nam).FAB_OR_NAML_FNS = strlen(fname);

    nam.NAM_ESA = exp_nam;
    nam.NAM_ESS = sizeof(exp_nam);
    nam.NAM_RSA = res_nam;
    nam.NAM_RSS = sizeof(res_nam);

    if ( ERR(status = sys$parse(&fileblk)) )
    {
        vms_msg(__G__ "stamp_file: sys$parse failed.\n", status);
        return -1;
    }

    pka_devdsc.dsc$w_length = (unsigned short)nam.NAM_DVI[0];

    if ( ERR(status = sys$assign(&pka_devdsc, &pka_devchn, 0, 0)) )
    {
        vms_msg(__G__ "stamp_file: sys$assign failed.\n", status);
        return -1;
    }

    /* Load the descriptor with the appropriate name data: */
#ifdef NAML$C_MAXRSS

    /* Enable fancy name characters.  Note that "fancy" here does
       not include Unicode, for which there's no support elsewhere.
    */
    pka_fib.fib$v_names_8bit = 1;
    pka_fib.fib$b_name_format_in = FIB$C_ISL1;

    /* ODS5 Extended names used as input to QIO have peculiar
       encoding (perhaps to minimize storage?), so the special
       filesys_name result (typically containing fewer carets) must
       be used here.
    */
    pka_fnam.dsc$a_pointer = nam.naml$l_filesys_name;
    pka_fnam.dsc$w_length = nam.naml$l_filesys_name_size;

#else /* !NAML$C_MAXRSS */

    /* Extract only the name.type;version. */
    pka_fnam.dsc$a_pointer = nam.NAM_L_NAME;
    pka_fnam.dsc$w_length = nam.NAM_B_NAME + nam.NAM_B_TYPE + nam.NAM_B_VER;

#endif /* ?NAML$C_MAXRSS */

    /* Move the directory ID from the NAM[L] to the FIB.
       Clear the FID in the FIB, as we're using the name.
    */
    for (i = 0; i < 3; i++)
    {
        pka_fib.FIB$W_DID[i] = nam.NAM_DID[i];
        pka_fib.FIB$W_FID[i] = 0;
    }

    /* Use the IO$_ACCESS function to return info about the file.
       This way, the file is not opened, and the expiration and
       revision dates are not modified.
    */
    status = sys$qiow(0, pka_devchn, IO$_ACCESS,
                      &pka_acp_iosb, 0, 0,
                      &pka_fibdsc, &pka_fnam, 0, 0, Atr, 0);

    if ( !ERR(status) )
        status = pka_acp_iosb.status;

    if ( ERR(status) )
    {
        vms_msg(__G__ "[ Access file QIO failed. ]\n", status);
        sys$dassgn(pka_devchn);
        return -1;
    }

    uxtime2vmstime(modtime, Cdate);
    memcpy(Rdate, Cdate, sizeof(Cdate));

    /* Note: Part of the FIB was cleared by earlier QIOW, so reset it. */
    pka_fib.FIB$L_ACCTL = FIB$M_NORECORD;

    /* Move the directory ID from the NAM[L] to the FIB.
       Clear the FID in the FIB, as we're using the name.
    */
    for (i = 0; i < 3; i++)
    {
        pka_fib.FIB$W_DID[i] = nam.NAM_DID[i];
        pka_fib.FIB$W_FID[i] = 0;
    }

    /* Use the IO$_MODIFY function to change info about the file */
    /* Note, used this way, the file is not opened, however this would */
    /* normally cause the expiration and revision dates to be modified. */
    /* Using FIB$M_NORECORD prohibits this from happening. */
    status = sys$qiow(0, pka_devchn, IO$_MODIFY,
                      &pka_acp_iosb, 0, 0,
                      &pka_fibdsc, &pka_fnam, 0, 0, Atr, 0);

    if ( !ERR(status) )
        status = pka_acp_iosb.status;

    if ( ERR(status) )
    {
        vms_msg(__G__ "[ Modify file QIO failed. ]\n", status);
        sys$dassgn(pka_devchn);
        return -1;
    }

    if ( ERR(status = sys$dassgn(pka_devchn)) )
    {
        vms_msg(__G__ "stamp_file: sys$dassgn failed.\n", status);
        return -1;
    }

    return 0;

} /* end function stamp_file() */

#endif /* TIMESTAMP */



#ifdef DEBUG
#if 0   /* currently not used anywhere ! */
void dump_rms_block(p)
    unsigned char *p;
{
    unsigned char bid, len;
    int err;
    char *type;
    char buf[132];
    int i;

    err = 0;
    bid = p[0];
    len = p[1];
    switch (bid)
    {
        case FAB$C_BID:
            type = "FAB";
            break;
        case XAB$C_ALL:
            type = "xabALL";
            break;
        case XAB$C_KEY:
            type = "xabKEY";
            break;
        case XAB$C_DAT:
            type = "xabDAT";
            break;
        case XAB$C_RDT:
            type = "xabRDT";
            break;
        case XAB$C_FHC:
            type = "xabFHC";
            break;
        case XAB$C_PRO:
            type = "xabPRO";
            break;
        default:
            type = "Unknown";
            err = 1;
            break;
    }
    printf("Block @%08X of type %s (%d).", p, type, bid);
    if (err)
    {
        printf("\n");
        return;
    }
    printf(" Size = %d\n", len);
    printf(" Offset - Hex - Dec\n");
    for (i = 0; i < len; i += 8)
    {
        int j;

        printf("%3d - ", i);
        for (j = 0; j < 8; j++)
            if (i + j < len)
                printf("%02X ", p[i + j]);
            else
                printf("   ");
        printf(" - ");
        for (j = 0; j < 8; j++)
            if (i + j < len)
                printf("%03d ", p[i + j]);
            else
                printf("    ");
        printf("\n");
    }
}

#endif                          /* never */
#endif                          /* DEBUG */



static char vms_msgbuf[256];            /* VMS-specific error message. */
static $DESCRIPTOR(vms_msgbuf_dscr, vms_msgbuf);


char *vms_msg_text(void)
{
    return vms_msgbuf;
}


static int vms_msg_fetch(int status)
{
    int msglen = 0;
    int sts;

    sts = lib$sys_getmsg(&status, &msglen, &vms_msgbuf_dscr, 0, 0);

    vms_msgbuf[msglen] = '\0';
    return sts;
}


static void vms_msg(__GPRO__ ZCONST char *string, int status)
{
    if (ERR(vms_msg_fetch(status)))
        Info(slide, 1, ((char *)slide,
             "%s[ VMS status = %d ]\n", string, status));
    else
        Info(slide, 1, ((char *)slide,
             "%s[ %s ]\n", string, vms_msgbuf));
}



#ifndef SFX

/* 2004-11-23 SMS.
 * Changed to return the resulting file name even when sys$search()
 * fails.  Before, if the user specified "fred.zip;4" and there was
 * none, the error message would complain:
 *    cannot find either fred.zip;4 or fred.zip;4.zip.
 * when it wasn't really looking for "fred.zip;4.zip".
 */
/* 2005-08-11 SPC.
 * The calling interface for the VMS version of do_wild() differs from all
 * other implementations in the way it returns status info.
 * There are three return states:
 * a) pointer to buffer with non-zero-length string
 *    - canonical full filespec of existing file (search succeeded).
 * b) pointer to buffer with zero-length string
 *    - initial file search has failed, extended VMS error info is available
 *      through call to vms_msg_text().
 * c) NULL pointer
 *    - repeated file search has failed, because
 *      i)   the list of matches for the pattern has been exhausted after at
 *           least one successful attempt.
 *      ii)  a second attempt for a failed initial pattern (where do_wild()
 *           has returned a zero-length string) was tried and failed again.
 */
char *do_wild( __G__ wld )
    __GDEF
    ZCONST char *wld;
{
    int status;

    static char filenam[NAM_MAXRSS + 1];
    static char efn[NAM_MAXRSS];
    static char last_wild[NAM_MAXRSS + 1];
    static struct FAB fab;
    static struct NAM_STRUCT nam;
    static int first_call = 1;
    static ZCONST char deflt[] = "[]*.ZIP";

    if ( first_call || strcmp(wld, last_wild) )
    {   /* (Re)Initialize everything */

        strcpy( last_wild, wld );

        fab = cc$rms_fab;               /* Initialize FAB. */
        nam = CC_RMS_NAM;               /* Initialize NAM[L]. */
        fab.FAB_NAM = &nam;             /* Point FAB to NAM[L]. */

#ifdef NAML$C_MAXRSS

        fab.fab$l_dna = (char *) -1;    /* Using NAML for default name. */
        fab.fab$l_fna = (char *) -1;    /* Using NAML for file name. */

#endif /* NAML$C_MAXRSS */

        FAB_OR_NAML(fab, nam).FAB_OR_NAML_DNA = (char *) deflt;
        FAB_OR_NAML(fab, nam).FAB_OR_NAML_DNS = sizeof(deflt) - 1;

        FAB_OR_NAML(fab, nam).FAB_OR_NAML_FNA = last_wild;
        FAB_OR_NAML(fab, nam).FAB_OR_NAML_FNS = strlen(last_wild);

        nam.NAM_ESA = efn;
        nam.NAM_ESS = sizeof(efn)-1;
        nam.NAM_RSA = filenam;
        nam.NAM_RSS = sizeof(filenam)-1;

        first_call = 0;

        /* 2005-08-08 SMS.
         * Parse the file spec.  If sys$parse() fails, save the VMS
         * error message for later use, and return an empty string.
         */
        nam.NAM_NOP = NAM_M_SYNCHK;     /* Syntax-only analysis. */
        if ( !OK(status = sys$parse(&fab)) )
        {
            vms_msg_fetch(status);
            filenam[0] = '\0';          /* Initialization failed */
            return filenam;
        }

        /* 2005-11-16 SMS.
         * If syntax-only parse worked, re-parse normally so that
         * sys$search() will work properly.  Regardless of parse error,
         * leave filenam[] as-was.
         */
        nam.NAM_NOP = 0;                /* Normal analysis. */
        if ( OK(status = sys$parse(&fab)) )
        {
            status = sys$search(&fab);
        }

        if ( !OK(status) )
        {
            /* Save the VMS error message for later use. */
            vms_msg_fetch(status);
        }
    }
    else
    {
        if ( !OK(sys$search(&fab)) )
        {
            first_call = 1;             /* Reinitialize next time */
            return (char *)NULL;
        }
    }
    filenam[nam.NAM_RSL] = '\0';        /* Add the NUL terminator. */
    return filenam;

} /* end function do_wild() */

#endif /* !SFX */



static ulg unix_to_vms[8]={ /* Map from UNIX rwx to VMS rwed */
                            /* Note that unix w bit is mapped to VMS wd bits */
                                                              /* no access */
    XAB$M_NOREAD | XAB$M_NOWRITE | XAB$M_NODEL | XAB$M_NOEXE,    /* --- */
    XAB$M_NOREAD | XAB$M_NOWRITE | XAB$M_NODEL,                  /* --x */
    XAB$M_NOREAD |                               XAB$M_NOEXE,    /* -w- */
    XAB$M_NOREAD,                                                /* -wx */
                   XAB$M_NOWRITE | XAB$M_NODEL | XAB$M_NOEXE,    /* r-- */
                   XAB$M_NOWRITE | XAB$M_NODEL,                  /* r-x */
                                                 XAB$M_NOEXE,    /* rw- */
    0                                                            /* rwx */
                                                              /* full access */
};

#define SETDFPROT   /* We are using undocumented VMS System Service     */
                    /* SYS$SETDFPROT here. If your version of VMS does  */
                    /* not have that service, undef SETDFPROT.          */
                    /* IM: Maybe it's better to put this to Makefile    */
                    /* and DESCRIP.MMS */
#ifdef SETDFPROT
extern int sys$setdfprot();
#endif

int mapattr(__G)
    __GDEF
{
    ulg tmp = G.crec.external_file_attributes;
    ulg theprot;
    static ulg  defprot = (ulg)-1L,
                sysdef, owndef, grpdef, wlddef; /* Default protection fields */

    /* IM: The only field of XABPRO we need to set here is */
    /*     file protection, so we need not to change type */
    /*     of G.pInfo->file_attr. WORD is quite enough. */

    if ( defprot == (ulg)-1L )
    {
        /*
         * First time here -- Get user default settings
         */

#ifdef SETDFPROT    /* Undef this if linker cat't resolve SYS$SETDFPROT */
        defprot = (ulg)0L;
        if ( !ERR(sys$setdfprot(0, &defprot)) )
        {
            sysdef = defprot & ( (1L<<XAB$S_SYS)-1 ) << XAB$V_SYS;
            owndef = defprot & ( (1L<<XAB$S_OWN)-1 ) << XAB$V_OWN;
            grpdef = defprot & ( (1L<<XAB$S_GRP)-1 ) << XAB$V_GRP;
            wlddef = defprot & ( (1L<<XAB$S_WLD)-1 ) << XAB$V_WLD;
        }
        else
#endif /* SETDFPROT */
        {
            umask(defprot = umask(0));
            defprot = ~defprot;
            wlddef = unix_to_vms[defprot & 07] << XAB$V_WLD;
            grpdef = unix_to_vms[(defprot>>3) & 07] << XAB$V_GRP;
            owndef = unix_to_vms[(defprot>>6) & 07] << XAB$V_OWN;
            sysdef = owndef >> (XAB$V_OWN - XAB$V_SYS);
            defprot = sysdef | owndef | grpdef | wlddef;
        }
    }

    switch (G.pInfo->hostnum) {
        case AMIGA_:
            tmp = (unsigned)(tmp>>16 & 0x0f);   /* Amiga RWED bits */
            G.pInfo->file_attr =  (tmp << XAB$V_OWN) |
                                   grpdef | sysdef | wlddef;
            break;

        case THEOS_:
            tmp &= 0xF1FFFFFFL;
            if ((tmp & 0xF0000000L) != 0x40000000L)
                tmp &= 0x01FFFFFFL;     /* not a dir, mask all ftype bits */
            else
                tmp &= 0x41FFFFFFL;     /* leave directory bit as set */
            /* fall through! */

        case UNIX_:
        case VMS_:  /*IM: ??? Does VMS Zip store protection in UNIX format ?*/
                    /* GRR:  Yup.  Bad decision on my part... */
        case ACORN_:
        case ATARI_:
        case ATHEOS_:
        case BEOS_:
        case QDOS_:
        case TANDEM_:
            {
              int r = FALSE;
              unsigned uxattr = (unsigned)(tmp >> 16);  /* drwxrwxrwx */

              if (uxattr == 0 && G.extra_field) {
                /* Some (non-Info-ZIP) implementations of Zip for Unix and
                 * VMS (and probably others ??) leave 0 in the upper 16-bit
                 * part of the external_file_attributes field. Instead, they
                 * store file permission attributes in some e.f. block.
                 * As a work-around, we search for the presence of one of
                 * these extra fields and fall back to the MSDOS compatible
                 * part of external_file_attributes if one of the known
                 * e.f. types has been detected.
                 * Later, we might implement extraction of the permission
                 * bits from the VMS extra field. But for now, the work-around
                 * should be sufficient to provide "readable" extracted files.
                 * (For ASI Unix e.f., an experimental remap of the e.f.
                 * mode value IS already provided!)
                 */
                ush ebID;
                unsigned ebLen;
                uch *ef = G.extra_field;
                unsigned ef_len = G.crec.extra_field_length;

                while (!r && ef_len >= EB_HEADSIZE) {
                    ebID = makeword(ef);
                    ebLen = (unsigned)makeword(ef+EB_LEN);
                    if (ebLen > (ef_len - EB_HEADSIZE))
                        /* discoverd some e.f. inconsistency! */
                        break;
                    switch (ebID) {
                      case EF_ASIUNIX:
                        if (ebLen >= (EB_ASI_MODE+2)) {
                            uxattr =
                              (unsigned)makeword(ef+(EB_HEADSIZE+EB_ASI_MODE));
                            /* force stop of loop: */
                            ef_len = (ebLen + EB_HEADSIZE);
                            break;
                        }
                        /* else: fall through! */
                      case EF_PKVMS:
                        /* "found nondecypherable e.f. with perm. attr" */
                        r = TRUE;
                      default:
                        break;
                    }
                    ef_len -= (ebLen + EB_HEADSIZE);
                    ef += (ebLen + EB_HEADSIZE);
                }
              }
              if (!r) {
#ifdef SYMLINKS
                  /* Check if the file is a (POSIX-compatible) symbolic link.
                   * We restrict symlink support to those "made-by" hosts that
                   * are known to support symbolic links.
                   */
                  G.pInfo->symlink = S_ISLNK(uxattr) &&
                                     SYMLINK_HOST(G.pInfo->hostnum);
#endif
                  theprot  = (unix_to_vms[uxattr & 07] << XAB$V_WLD)
                           | (unix_to_vms[(uxattr>>3) & 07] << XAB$V_GRP)
                           | (unix_to_vms[(uxattr>>6) & 07] << XAB$V_OWN);
                  if ( uxattr & 0x4000 )
                      /* Directory -- set D bits */
                      theprot |= (XAB$M_NODEL << XAB$V_SYS)
                              | (XAB$M_NODEL << XAB$V_OWN)
                              | (XAB$M_NODEL << XAB$V_GRP)
                              | (XAB$M_NODEL << XAB$V_WLD);
                  G.pInfo->file_attr = theprot;
                  break;
              }
            }
            /* fall through! */

        /* all remaining cases:  expand MSDOS read-only bit into write perms */
        case FS_FAT_:
        case FS_HPFS_:
        case FS_NTFS_:
        case MAC_:
        case TOPS20_:
        default:
            theprot = defprot;
            if ( tmp & 1 )   /* Test read-only bit */
            {   /* Bit is set -- set bits in all fields */
                tmp = XAB$M_NOWRITE | XAB$M_NODEL;
                theprot |= (tmp << XAB$V_SYS) | (tmp << XAB$V_OWN) |
                           (tmp << XAB$V_GRP) | (tmp << XAB$V_WLD);
            }
            G.pInfo->file_attr = theprot;
            break;
    } /* end switch (host-OS-created-by) */

    return 0;

} /* end function mapattr() */


#define PATH_DEFAULT "SYS$DISK:[]"

/* dest_struct_level()

      Returns file system structure level for argument, negative on
      error.
*/

int dest_struct_level(char *path)
{
    int acp_code;

#ifdef DVI$C_ACP_F11V5

    /* Should know about ODS5 file system.  Do actual check.
       (This should be non-VAX with __CRTL_VER >= 70200000.)
    */

    int sts;

    struct FAB fab;
    struct NAM_STRUCT nam;
    char e_name[NAM_MAXRSS + 1];

    struct dsc$descriptor_s dev_descr =
     { 0, DSC$K_DTYPE_T, DSC$K_CLASS_S, 0 };

    fab = cc$rms_fab;                   /* Initialize FAB. */
    nam = CC_RMS_NAM;                   /* Initialize NAM[L]. */
    fab.FAB_NAM = &nam;                 /* Point FAB to NAM[L]. */

#ifdef NAML$C_MAXRSS

    fab.fab$l_dna = (char *) -1;        /* Using NAML for default name. */
    fab.fab$l_fna = (char *) -1;        /* Using NAML for file name. */

#endif /* NAML$C_MAXRSS */

    FAB_OR_NAML(fab, nam).FAB_OR_NAML_DNA = PATH_DEFAULT;
    FAB_OR_NAML(fab, nam).FAB_OR_NAML_DNS = strlen(PATH_DEFAULT);

    FAB_OR_NAML(fab, nam).FAB_OR_NAML_FNA = path;
    FAB_OR_NAML(fab, nam).FAB_OR_NAML_FNS = strlen(path);

    nam.NAM_ESA = e_name;
    nam.NAM_ESS = sizeof(e_name) - 1;

    nam.NAM_NOP = NAM_M_SYNCHK;         /* Syntax-only analysis. */
    sts = sys$parse(&fab);

    if ((sts & STS$M_SUCCESS) == STS$K_SUCCESS)
    {
        /* Load resultant device name into device descriptor. */
        dev_descr.dsc$a_pointer = nam.NAM_L_DEV;
        dev_descr.dsc$w_length = nam.NAM_B_DEV;

        /* Get filesystem type code.
           (Text results for this item code have been unreliable.)
        */
        sts = lib$getdvi(&((int) DVI$_ACPTYPE),
                         0,
                         &dev_descr,
                         &acp_code,
                         0,
                         0);

        if ((sts & STS$M_SUCCESS) != STS$K_SUCCESS)
        {
            acp_code = -2;
        }
    }
    else
    {
        acp_code = -1;
    }

#else /* !DVI$C_ACP_F11V5 */

/* Too old for ODS5 file system.  Return level 2. */

    acp_code = DVI$C_ACP_F11V2;

#endif /* ?DVI$C_ACP_F11V5 */

    return acp_code;
}

/* 2005-02-12 SMS.
   Note that these name conversion functions do no length checking.
   Buffer overflows are possible.
*/

static void adj_dir_name_ods2(char *dest, char *src, int src_len)
{
    /* The source string (src) typically extends beyond the directory
       segment of interest, hence the confining src_len argument.
    */
    unsigned char uchr;
    unsigned char prop;
    char * src_last;

    for (src_last = src + src_len; src < src_last; src++)
    {
        prop = char_prop[uchr = *src];  /* Get source char, properties. */
        if ((prop & 2) != 0)            /* Up-case lower case. */
        {
            uchr -= ('a' - 'A');        /* (Simple-fast is adequate.) */
        }
        else if ((prop & 1) == 0)       /* Replace invalid char */
        {
            uchr = '_';                 /* with "_". */
        }
        *dest++ = uchr;                 /* Store good char. */
    }
    *dest = '\0';                       /* Terminate destination. */
}


static void adj_dir_name_ods5(char *dest, char *src, int src_len)
{
    /* The source string (src) typically extends beyond the directory
       segment of interest, hence the confining src_len argument.
    */
    unsigned char uchr;
    unsigned char prop;
    char * src_last;

    for (src_last = src + src_len; src < src_last; src++)
    {
        prop = char_prop[uchr = *src];          /* Get source char, props. */
        prop = char_prop[uchr];                 /* Get source char props. */
        if ((prop & (32+8+4)) != 0)             /* Escape 1-char, including */
        {                                       /* SP and dot. */
            *dest++ = '^';                      /* Insert caret. */
            if ((prop & 8) != 0)                /* Replace SP with "_". */
            {
                uchr = '_';
            }
            else if (uchr == '?')
            {
                uchr = '/';                     /* Replace "?" with "/". */
            }
        }
        else if ((prop & 64) != 0)              /* Escape hex-hex. */
        {
            *dest++ = '^';                      /* Insert caret. */
            *dest++ = hex_digit[uchr >> 4];     /* First hex digit. */
            uchr = hex_digit[uchr & 15];        /* Second hex digit. */
        }
        else if ((prop & 16) == 0)              /* Replace invalid with "_". */
        {
            uchr = '_';
        }
        *dest++ = uchr;                         /* Put good (or last) char. */
    }
    *dest = '\0';                               /* Terminate destination. */
}


static void adj_file_name_ods2(char *dest, char *src)
{
    unsigned char uchr;
    unsigned char prop;
    char *endp;
    char *versionp;
    char *last_dot;

    endp = src + strlen(src);   /* Pointer to the NUL-terminator of src. */
    /* Starting at the end, find the last non-decimal-digit. */
    versionp = endp;
    while ((--versionp >= src) && isdigit(*versionp));

    /* Left-most non-digit of a valid version is ";" (or perhaps "."). */
    if ((*versionp != ';') && ((uO.Y_flag == 0) || (*versionp != '.')))
    {
        /* No valid version.  The last dot is the last dot. */
        versionp = endp;
    }
    else
    {   /* Some kind of valid version. */
        if (!uO.V_flag)                 /* Not -V, so cut off version. */
        {
            *versionp = '\0';
        }
        else if (*versionp == '.')
        {
            *versionp = ';';            /* Replace version dot with ";". */
        }
    }

    /* 2008-11-04 SMS.
     * Simplified the scheme here to escape all non-last dots.  This
     * should work when Zip works correctly (Zip 3.1).
     * Note that if no last dot is found, the non-last-dot test below
     * will always fail, but that's not a problem.
     */

    /* Find the last dot (if any). */
    last_dot = versionp;
    while ((--last_dot >= src) && (*last_dot != '.'));

    /* Critical features having been located, transform the name. */
    while ((uchr = *src++) != '\0')     /* Get source character. */
    {
        /* Note that "src" has been incremented, affecting "src <=". */
        prop = char_prop[uchr];         /* Get source char properties. */
        if ((prop & 2) != 0)            /* Up-case lower case. */
        {
            uchr -= ('a' - 'A');        /* (Simple-fast is adequate.) */
        }
        else if ((prop & 4) != 0)       /* Dot. */
        {
            if (src <= last_dot)        /* Replace non-last dot */
            {
                uchr = '_';             /* with "_". */
            }
        }
        else if ((prop & 1) == 0)       /* Replace SP or invalid char, */
        {
            if (src <= versionp)        /* if not in version, */
            {
                uchr = '_';             /* with "_". */
            }
        }
        *dest++ = uchr;                 /* Store good char. */
    }
    *dest = '\0';                       /* Terminate destination. */
}


static void adj_file_name_ods5(char *dest, char *src)
{
    unsigned char uchr;
    unsigned char prop;
    char *endp;
    char *versionp;
    char *last_dot;

    endp = src + strlen(src);   /* Pointer to the NUL-terminator of src. */
    /* Starting at the end, find the last non-decimal-digit. */
    versionp = endp;
    while ((--versionp >= src) && isdigit(*versionp));

    /* Left-most non-digit of a valid version is ";" (or perhaps "."). */
    if ((*versionp != ';') && ((uO.Y_flag == 0) || (*versionp != '.')))
    {
        /* No valid version.  The last dot is the last dot. */
        versionp = endp;
    }
    else
    {   /* Some kind of valid version. */
        if (!uO.V_flag)                 /* Not -V, so cut off version. */
        {
            *versionp = '\0';
        }
        else if (*versionp == '.')
        {
            *versionp = ';';            /* Replace version dot with ";". */
        }
    }

    /* 2008-11-04 SMS.
     * Simplified the scheme here to escape all non-last dots.  This
     * should work when Zip works correctly (Zip 3.1).
     * Note that if no last dot is found, the non-last-dot test below
     * will always fail, but that's not a problem.
     */

    /* Find the last dot (if any). */
    last_dot = versionp;
    while ((--last_dot >= src) && (*last_dot != '.'));

    /* Critical features having been located, transform the name. */
    while ((uchr = *src++) != '\0')             /* Get source character. */
    {
        /* Note that "src" has been incremented, affecting "src <=". */
        prop = char_prop[uchr];                 /* Get source char props. */
        if ((prop & (32+8)) != 0)               /* Escape 1-char, including */
        {                                       /* SP (but not dot). */
            if (src <= versionp)                /* No escapes for version. */
            {
                *dest++ = '^';                  /* Insert caret. */
                if ((prop & 8) != 0)            /* Replace SP with "_". */
                {
                    uchr = '_';
                }
                else if (uchr == '?')
                {
                    uchr = '/';                 /* Replace "?" with "/". */
                }
            }
        }
        else if ((prop & 4) != 0)               /* Dot. */
        {
            if (src <= last_dot)                /* Escape non-last dot */
            {
                *dest++ = '^';                  /* Insert caret. */
            }
        }
        else if ((prop & 64) != 0)              /* Escape hex-hex. */
        {
            *dest++ = '^';                      /* Insert caret. */
            *dest++ = hex_digit[uchr >> 4];     /* First hex digit. */
            uchr = hex_digit[uchr & 15];        /* Second hex digit. */
        }
        else if ((prop & 16) == 0)              /* Replace invalid with "_". */
        {
            uchr = '_';
        }
        *dest++ = uchr;                         /* Put good (or last) char. */
    }
    *dest = '\0';                               /* Terminate destination. */
}



#   define FN_MASK   7
#   define USE_DEFAULT  (FN_MASK+1)

/*
 * Checkdir function codes:
 *      ROOT        -   set root path from unzip qq d:[dir]
 *      INIT        -   get ready for "filename"
 *      APPEND_DIR  -   append pathcomp
 *      APPEND_NAME -   append filename
 *      APPEND_NAME | USE_DEFAULT   -    expand filename using collected path
 *      GETPATH     -   return resulting filespec
 *      END         -   free dynamically allocated space prior to program exit
 */

static int created_dir;
static int dest_file_sys_level;
static int ods2_names = -1;

int mapname(__G__ renamed)
        /* returns: */
        /* MPN_OK if no error, */
        /* MPN_INF_TRUNC if caution (filename trunc), */
        /* MPN_INF_SKIP if warning (skip file, dir doesn't exist), */
        /* MPN_ERR_SKIP if error (skip file), */
        /* MPN_CREATED_DIR if has created directory, */
        /* MPN_VOL_LABEL if path was volume label (skip it) */
        /* MPN_NOMEM if no memory (skip file) */
    __GDEF
    int renamed;
{
    char pathcomp[FILNAMSIZ];       /* Path-component buffer. */
    char *last_slash;               /* Last slash in path. */
    char *next_slash;               /* Next slash in path. */
    int  dir_len;                   /* Length of a directory segment. */

    char *cp = (char *)NULL;        /* character pointer */
    int killed_ddot = FALSE;        /* Set when skipping "../" pathcomp. */
    int error = MPN_OK;

    if ( renamed )
    {
        if ( !(error = checkdir(__G__ pathcomp, APPEND_NAME | USE_DEFAULT)) )
            strcpy(G.filename, pathcomp);
        return error;
    }

/*---------------------------------------------------------------------------
    Initialize various pointers and counters and stuff.
  ---------------------------------------------------------------------------*/

    if (G.pInfo->vollabel)
        return MPN_VOL_LABEL;   /* can't set disk volume labels on VMS */

    /* can create path as long as not just freshening, or if user told us */
    G.create_dirs = !uO.fflag;

    created_dir = FALSE;        /* not yet */

    /* If not yet known, determine the destination file system type
       (ODS2 or ODS5).  (If the user specified a destination, we should
       already have this, so use the default destination.)
    */
    if (ods2_names < 0)
    {
        /* If user doesn't force ODS2, set flag according to destination. */
        if (uO.ods2_flag == 0)
        {
            ods2_names =
             (dest_struct_level(PATH_DEFAULT) <= DVI$C_ACP_F11V2);
        }
        else
        {
            ods2_names = 1;     /* User demands ODS2 names. */
        }
    }

/* GRR:  for VMS, convert to internal format now or later? or never? */
    if (checkdir(__G__ pathcomp, INIT) == 10)
        return MPN_NOMEM;       /* Initialize path buffer, unless no memory. */

    /* Locate and treat directory segments one at a time.
       When pointer exceeds last_slash, then directory segments are
       done, and only the name (with version?) remains.
    */

    *pathcomp = '\0';           /* Initialize translation buffer. */
    last_slash = strrchr(G.filename, '/');      /* Find last slash. */

    if (uO.jflag)               /* If junking directories, */
        cp = last_slash;        /* start at (will be after) the last slash. */

    if (cp == NULL)             /* If no '/', or keeping directories, */
        cp = G.filename;        /* start at the front of the pathname. */
    else                        /* Else, with directories to junk, */
        ++cp;                   /* start after the last slash. */

    /* Loop through the directory segments. */
    while (cp < last_slash)
    {
        next_slash = strchr(cp, '/');  /* Find the next slash. */
        dir_len = next_slash- cp;

        /* Filter out unacceptable directories. */
        if ((dir_len == 2) && (strncmp(cp, "..", 2) == 0))
        {   /* Double dot. */
            if (!uO.ddotflag)           /* Not allowed.  Skip it. */
            {
                dir_len = 0;
                killed_ddot = TRUE;     /* Record skipping double-dot. */
            }
        }
        else if ((dir_len == 1) && (strncmp(cp, ".", 1) == 0))
        {   /* Single dot.  No-op.  Skip it. */
            dir_len = 0;
        }

        /* If non-null, acceptable directory, then process it. */
        if (dir_len > 0)
        {
            if (ods2_names)     /* Make directory name ODS2-compliant. */
            {
                adj_dir_name_ods2(pathcomp, cp, dir_len);
            }
            else                /* Make directory name ODS5-compliant. */
            {
                adj_dir_name_ods5(pathcomp, cp, dir_len);
            }
            if (((error = checkdir(__G__ pathcomp, APPEND_DIR))
                 & MPN_MASK) > MPN_INF_TRUNC)
                return error;
        }
        cp = next_slash+ 1;     /* Continue at the next name segment. */
    } /* end while loop */

    /* Show warning when stripping insecure "parent dir" path components */
    if (killed_ddot && QCOND2) {
        Info(slide, 0, ((char *)slide,
          "warning:  skipped \"../\" path component(s) in %s\n",
          FnFilter1(G.filename)));
        if (!(error & ~MPN_MASK))
            error = (error & MPN_MASK) | PK_WARN;
    }

    /* If there is one, adjust the name.type;version segment. */
    if (strlen(cp) == 0)
    {
        /* Directory only, no file name.  Create the directory, as needed.
           Report directory creation to user.
        */
        checkdir(__G__ "", APPEND_NAME);   /* create directory, if not found */
        checkdir(__G__ G.filename, GETPATH);
        if (created_dir) {
            if (QCOND2) {
                Info(slide, 0, ((char *)slide, "   creating: %s\n",
                  FnFilter1(G.filename)));
            }
            /* set dir time (note trailing '/') */
            return (error & ~MPN_MASK) | MPN_CREATED_DIR;
        }
        /* dir existed already; don't look for data to extract */
        return (error & ~MPN_MASK) | MPN_INF_SKIP;
    }

    /* Process the file name. */
    if (ods2_names)     /* Make file name ODS2-compliant. */
    {
        adj_file_name_ods2(pathcomp, cp);
    }
    else                /* Make file name ODS5-compliant. */
    {
        adj_file_name_ods5(pathcomp, cp);
    }

    checkdir(__G__ pathcomp, APPEND_NAME);  /* returns 1 if truncated: care? */
    checkdir(__G__ G.filename, GETPATH);

    return error;

} /* end function mapname() */



int checkdir(__G__ pathcomp, fcn)
/*
 * returns:
 *  MPN_OK          - no problem detected
 *  MPN_INF_TRUNC   - (on APPEND_NAME) truncated filename
 *  MPN_INF_SKIP    - path doesn't exist, not allowed to create
 *  MPN_ERR_SKIP    - path doesn't exist, tried to create and failed; or path
 *                    exists and is not a directory, but is supposed to be
 *  MPN_ERR_TOOLONG - path is too long
 *  MPN_NOMEM       - can't allocate memory for filename buffers
 */
    __GDEF
    char *pathcomp;
    int fcn;
{
    int function=fcn & FN_MASK;
    static char pathbuf[FILNAMSIZ];

    /* previously created directory (initialized to impossible dir. spec.) */
    static char lastdir[FILNAMSIZ] = "\t";

    static char *pathptr = pathbuf;     /* For debugger */
    static char *devptr, *dirptr;
    static int  devlen, dirlen;
    static int  root_dirlen;
    static char *end;
    static int  first_comp, root_has_dir;
    static int  rootlen=0;
    static char *rootend;
    static int  mkdir_failed=0;
    int status;
    struct FAB fab;
    struct NAM_STRUCT nam;


/************
 *** ROOT ***
 ************/

#if (!defined(SFX) || defined(SFX_EXDIR))
    if (function == ROOT)
    {   /*  Assume VMS root spec */
        /* 2006-01-20 SMS.
           Changed to use sys$parse() instead of sys$filescan() for analysis
           of the user-specified destination directory.  Previously, various
           values behaved badly, without complaint, e.g. "-d sys$scratch".
        */
        char *root_dest;

        /* If the root path has already been set, return immediately. */
        if (rootlen > 0)
            return MPN_OK;

        /* Initialization. */
        root_dest = PATH_DEFAULT;   /* Default destination for ODSx sensing. */
        root_has_dir = 0;           /* Root includes a directory. */
        fab = cc$rms_fab;           /* Initialize FAB. */
        nam = CC_RMS_NAM;           /* Initialize NAM[L]. */
        fab.FAB_NAM = &nam;         /* Point FAB to NAM[L]. */

#ifdef NAML$C_MAXRSS

        fab.fab$l_dna = (char *) -1;    /* Using NAML for default name. */
        fab.fab$l_fna = (char *) -1;    /* Using NAML for file name. */

#endif /* NAML$C_MAXRSS */

        /* Specified file spec. */
        FAB_OR_NAML(fab, nam).FAB_OR_NAML_FNA = pathcomp;
        FAB_OR_NAML(fab, nam).FAB_OR_NAML_FNS = strlen(pathcomp);

        /* Default file spec. */
        FAB_OR_NAML(fab, nam).FAB_OR_NAML_DNA = PATH_DEFAULT;
        FAB_OR_NAML(fab, nam).FAB_OR_NAML_DNS = strlen(PATH_DEFAULT);

        /* Expanded file spec. */
        nam.NAM_ESA = pathbuf;
        nam.NAM_ESS = NAM_MAXRSS;

        status = sys$parse(&fab);

        /* OK so far, if OK or if directory not found. */
        if (((status & STS$M_SEVERITY) != STS$K_SUCCESS) &&
            (status != RMS$_DNF))
        {
            /* Invalid destination directory specified. */
            Info(slide, 1, ((char *)slide,
              "Invalid destination directory (parse error): %s\n",
              FnFilter1(pathcomp)));
            return MPN_ERR_SKIP;
        }

        /* Should be only a device:[directory], so name+type+version
           should have length 2 (".;").
        */
        if (nam.NAM_B_NAME + nam.NAM_B_TYPE + nam.NAM_B_VER > 2)
        {
            Info(slide, 1, ((char *)slide,
              "Invalid destination directory (includes file name): %s\n",
              FnFilter1(nam.NAM_ESA)));
            return MPN_ERR_SKIP;
        }

        /* Truncate at name, leaving only "dev:[dir]". */
        *nam.NAM_L_NAME = '\0';
        rootlen = nam.NAM_L_NAME - nam.NAM_ESA;

        /* Remove any trailing dots in directory. */
        if ((nam.NAM_ESA[rootlen-1] == ']') &&
            (nam.NAM_ESA[rootlen-2] != '^'))
        {
            root_has_dir = 1;
            rootlen -= 2;
            while ((nam.NAM_ESA[rootlen] == '.') &&
                   (nam.NAM_ESA[rootlen-1] != '^'))
            {
                rootlen--;
            }
            nam.NAM_ESA[++rootlen] = ']';
            nam.NAM_ESA[++rootlen] = '\0';
        }

        devlen = nam.NAM_L_DIR - nam.NAM_ESA;

        /* If directory not found, then create it. */
        if (status == RMS$_DNF)
        {
            if (status = mkdir(nam.NAM_ESA, 0))
            {
                Info(slide, 1, ((char *)slide,
                  "Can not create destination directory: %s\n",
                  FnFilter1(nam.NAM_ESA)));

                /* path didn't exist, tried to create, and failed. */
                return MPN_ERR_SKIP;
            }
        }

        /* Save the (valid) device:[directory] spec. */
        strcpy(pathbuf, nam.NAM_ESA);
        root_dest = pathbuf;

        /* At this point, the true destination is known.  If the user
           supplied an invalid destination directory, the default
           directory will be used.  (This may be pointless, but should
           be safe.)
        */

        /* If not yet known, determine the destination (root_dest) file
           system type (ODS2 or ODS5).
        */
        if (ods2_names < 0)
        {
            /* If user doesn't force ODS2, set flag according to dest. */
            if (uO.ods2_flag == 0)
            {
                ods2_names = (dest_struct_level(root_dest) <= DVI$C_ACP_F11V2);
            }
            else
            {
                ods2_names = 1;     /* User demands ODS2 names. */
            }
        }

        /* Replace trailing "]" with ".", for later appending. */
        if ((pathbuf[rootlen-1] == ']') || (pathbuf[rootlen-1] == '>'))
        {
            pathbuf[rootlen-1] = '.';
        }

        /* Set various pointers and lengths. */
        devptr = pathbuf;
        dirptr = pathbuf + (nam.NAM_L_DIR - nam.NAM_ESA);
        rootend = pathbuf + rootlen;
        *(end = rootend) = '\0';
        root_dirlen = dirlen = rootlen - devlen;
        first_comp = !root_has_dir;
        return MPN_OK;
    }
#endif /* !SFX || SFX_EXDIR */


/************
 *** INIT ***
 ************/

    if ( function == INIT )
    {
        if ( strlen(G.filename) + rootlen + 13 > NAM_MAXRSS )
            return MPN_ERR_TOOLONG;

        if ( rootlen == 0 )     /* No root given, reset everything. */
        {
            devptr = dirptr = rootend = pathbuf;
            devlen = dirlen = 0;
        }
        end = rootend;
        first_comp = !root_has_dir;
        if ( dirlen = root_dirlen )
            end[-1] = '.';
        *end = '\0';
        return MPN_OK;
    }


/******************
 *** APPEND_DIR ***
 ******************/
    if ( function == APPEND_DIR )
    {
        int cmplen;

        cmplen = strlen(pathcomp);

        if ( first_comp )
        {
            *end++ = '[';
            if ( cmplen )
                *end++ = '.';   /*       "dir/..." --> "[.dir...]"    */
            /*                     else  "/dir..." --> "[dir...]"     */
            first_comp = 0;
        }

        if ( cmplen == 1 && *pathcomp == '.' )
            ; /* "..././..." -- ignore */

        else if ( cmplen == 2 && pathcomp[0] == '.' && pathcomp[1] == '.' )
        {   /* ".../../..." -- convert to "...-..." */
            *end++ = '-';
            *end++ = '.';
        }

        else if ( cmplen + (end-pathptr) > NAM_MAXRSS )
            return MPN_ERR_TOOLONG;

        else
        {
            strcpy(end, pathcomp);
            *(end+=cmplen) = '.';
            ++end;
        }
        dirlen = end - dirptr;
        *end = '\0';
        return MPN_OK;
    }


/*******************
 *** APPEND_NAME ***
 *******************/
    if ( function == APPEND_NAME )
    {
        if ( fcn & USE_DEFAULT )
        {   /* Expand renamed filename using collected path, return
             *  at pathcomp */
            fab = cc$rms_fab;           /* Initialize FAB. */
            nam = CC_RMS_NAM;           /* Initialize NAM[L]. */
            fab.FAB_NAM = &nam;         /* Point FAB to NAM[L]. */

#ifdef NAML$C_MAXRSS

            fab.fab$l_dna = (char *) -1;    /* Using NAML for default name. */
            fab.fab$l_fna = (char *) -1;    /* Using NAML for file name. */

#endif /* NAML$C_MAXRSS */

            FAB_OR_NAML(fab, nam).FAB_OR_NAML_DNA = pathptr;
            FAB_OR_NAML(fab, nam).FAB_OR_NAML_DNS = end - pathptr;
            FAB_OR_NAML(fab, nam).FAB_OR_NAML_FNA = G.filename;
            FAB_OR_NAML(fab, nam).FAB_OR_NAML_FNS = strlen(G.filename);

            nam.NAM_ESA = pathcomp;     /* (Great design. ---v.  SMS.) */
            nam.NAM_ESS = NAM_MAXRSS;   /* Assume large enough. */

            if (!OK(status = sys$parse(&fab)) && status == RMS$_DNF )
                                         /* Directory not found: */
            {                            /* ... try to create it */
                char    save;
                char    *dirend;
                int     mkdir_failed;

                dirend = (char*)nam.NAM_L_DIR + nam.NAM_B_DIR;
                save = *dirend;
                *dirend = '\0';
                if ( (mkdir_failed = mkdir(nam.NAM_L_DEV, 0)) &&
                     errno == EEXIST )
                    mkdir_failed = 0;
                *dirend = save;
                if ( mkdir_failed )
                    return 3;
                created_dir = TRUE;
            }                                /* if (sys$parse... */
            pathcomp[nam.NAM_ESL] = '\0';
            return MPN_OK;
        }                                /* if (USE_DEFAULT) */
        else
        {
            *end = '\0';
            if ( dirlen )
            {
                dirptr[dirlen-1] = ']'; /* Close directory */

                /*
                 *  Try to create the target directory.
                 *  Don't waste time creating directory that was created
                 *  last time.
                 */
                if ( STRICMP(lastdir, pathbuf) )
                {
                    mkdir_failed = 0;
                    if ( mkdir(pathbuf, 0) )
                    {
                        if ( errno != EEXIST )
                            mkdir_failed = 1;   /* Mine for GETPATH */
                    }
                    else
                        created_dir = TRUE;
                    strcpy(lastdir, pathbuf);
                }
            }
            else
            {   /*
                 * Target directory unspecified.
                 * Try to create "SYS$DISK:[]"
                 */
                if ( strcmp(lastdir, PATH_DEFAULT) )
                {
                    strcpy(lastdir, PATH_DEFAULT);
                    mkdir_failed = 0;
                    if ( mkdir(lastdir, 0) && errno != EEXIST )
                        mkdir_failed = 1;   /* Mine for GETPATH */
                }
            }
            if ( strlen(pathcomp) + (end-pathbuf) > 255 )
                return MPN_INF_TRUNC;
            strcpy(end, pathcomp);
            end += strlen(pathcomp);
            return MPN_OK;
        }
    }


/***************
 *** GETPATH ***
 ***************/
    if ( function == GETPATH )
    {
        if ( mkdir_failed )
            return MPN_ERR_SKIP;
        *end = '\0';                    /* To be safe */
        strcpy( pathcomp, pathbuf );
        return MPN_OK;
    }


/***********
 *** END ***
 ***********/
    if ( function == END )
    {
        Trace((stderr, "checkdir(): nothing to free...\n"));
        rootlen = 0;
        return MPN_OK;
    }

    return MPN_INVALID; /* should never reach */

}



int check_for_newer(__G__ filenam)   /* return 1 if existing file newer or */
    __GDEF                           /*  equal; 0 if older; -1 if doesn't */
    char *filenam;                   /*  exist yet */
{
#ifdef USE_EF_UT_TIME
    iztimes z_utime;
    struct tm *t;
#endif
    char *filenam_stat;
    unsigned short timbuf[7];
    unsigned dy, mo, yr, hh, mm, ss, dy2, mo2, yr2, hh2, mm2, ss2;
    struct FAB fab;
    struct XABDAT xdat;
#ifdef NAML$C_MAXRSS
    struct NAM_STRUCT nam;
#endif

    /* 2008-07-12 SMS.
     * Special case for "." as a file name, not as the current directory.
     * Substitute ".;" to keep stat() from seeing a plain ".".
    */
    if (strcmp(filenam, ".") == 0)
        filenam_stat = ".;";
    else
        filenam_stat = filenam;

    if (stat(filenam_stat, &G.statbuf))
        return DOES_NOT_EXIST;

    fab  = cc$rms_fab;                  /* Initialize FAB. */
    xdat = cc$rms_xabdat;               /* Initialize XAB. */

#ifdef NAML$C_MAXRSS

    nam  = CC_RMS_NAM;                  /* Initialize NAM[L]. */
    fab.FAB_NAM = &nam;                 /* Point FAB to NAM[L]. */

    fab.fab$l_dna = (char *) -1;    /* Using NAML for default name. */
    fab.fab$l_fna = (char *) -1;    /* Using NAML for file name. */

#endif /* NAML$C_MAXRSS */

    FAB_OR_NAML(fab, nam).FAB_OR_NAML_FNA = filenam;
    FAB_OR_NAML(fab, nam).FAB_OR_NAML_FNS = strlen(filenam);

    fab.fab$l_xab = (char *) &xdat;
    fab.fab$l_fop = FAB$M_GET | FAB$M_UFO;

    if (ERR(sys$open(&fab)))             /* open failure:  report exists and */
        return EXISTS_AND_OLDER;         /*  older so new copy will be made  */
    sys$numtim(&timbuf, &xdat.xab$q_cdt);
    fab.fab$l_xab = NULL;

    sys$dassgn(fab.fab$l_stv);
    sys$close(&fab);   /* be sure file is closed and RMS knows about it */

#ifdef USE_EF_UT_TIME
    if (G.extra_field &&
#ifdef IZ_CHECK_TZ
        G.tz_is_valid &&
#endif
        (ef_scan_for_izux(G.extra_field, G.lrec.extra_field_length, 0,
                          G.lrec.last_mod_dos_datetime, &z_utime, NULL)
         & EB_UT_FL_MTIME))
        t = localtime(&(z_utime.mtime));
    else
        t = (struct tm *)NULL;

    if (t != (struct tm *)NULL)
    {
        yr2 = (unsigned)(t->tm_year) + 1900;
        mo2 = (unsigned)(t->tm_mon) + 1;
        dy2 = (unsigned)(t->tm_mday);
        hh2 = (unsigned)(t->tm_hour);
        mm2 = (unsigned)(t->tm_min);
        ss2 = (unsigned)(t->tm_sec);

        /* round to nearest sec--may become 60,
           but doesn't matter for compare */
        ss = (unsigned)((float)timbuf[5] + (float)timbuf[6]*.01 + 0.5);
        TTrace((stderr, "check_for_newer:  using Unix extra field mtime\n"));
    }
    else
#endif /* USE_EF_UT_TIME */
    {
        yr2 = ((G.lrec.last_mod_dos_datetime >> 25) & 0x7f) + 1980;
        mo2 = (G.lrec.last_mod_dos_datetime >> 21) & 0x0f;
        dy2 = (G.lrec.last_mod_dos_datetime >> 16) & 0x1f;
        hh2 = (G.lrec.last_mod_dos_datetime >> 11) & 0x1f;
        mm2 = (G.lrec.last_mod_dos_datetime >> 5) & 0x3f;
        ss2 = (G.lrec.last_mod_dos_datetime << 1) & 0x1f;

        /* round to nearest 2 secs--may become 60,
           but doesn't matter for compare */
        ss = (unsigned)((float)timbuf[5] + (float)timbuf[6]*.01 + 1.) & (~1);
    }
    yr = timbuf[0];
    mo = timbuf[1];
    dy = timbuf[2];
    hh = timbuf[3];
    mm = timbuf[4];

    if (yr > yr2)
        return EXISTS_AND_NEWER;
    else if (yr < yr2)
        return EXISTS_AND_OLDER;

    if (mo > mo2)
        return EXISTS_AND_NEWER;
    else if (mo < mo2)
        return EXISTS_AND_OLDER;

    if (dy > dy2)
        return EXISTS_AND_NEWER;
    else if (dy < dy2)
        return EXISTS_AND_OLDER;

    if (hh > hh2)
        return EXISTS_AND_NEWER;
    else if (hh < hh2)
        return EXISTS_AND_OLDER;

    if (mm > mm2)
        return EXISTS_AND_NEWER;
    else if (mm < mm2)
        return EXISTS_AND_OLDER;

    if (ss >= ss2)
        return EXISTS_AND_NEWER;

    return EXISTS_AND_OLDER;
}



#ifdef RETURN_CODES
void return_VMS(__G__ err)
    __GDEF
#else
void return_VMS(err)
#endif
    int err;
{
    int severity;

#ifdef RETURN_CODES
/*---------------------------------------------------------------------------
    Do our own, explicit processing of error codes and print message, since
    VMS misinterprets return codes as rather obnoxious system errors ("access
    violation," for example).
  ---------------------------------------------------------------------------*/

    switch (err) {
        case PK_COOL:
            break;   /* life is fine... */
        case PK_WARN:
            Info(slide, 1, ((char *)slide, "\n\
[return-code %d:  warning error \
(e.g., failed CRC or unknown compression method)]\n", err));
            break;
        case PK_ERR:
        case PK_BADERR:
            Info(slide, 1, ((char *)slide, "\n\
[return-code %d:  error in zipfile \
(e.g., cannot find local file header sig)]\n", err));
            break;
        case PK_MEM:
        case PK_MEM2:
        case PK_MEM3:
        case PK_MEM4:
        case PK_MEM5:
            Info(slide, 1, ((char *)slide,
              "\n[return-code %d:  insufficient memory]\n", err));
            break;
        case PK_NOZIP:
            Info(slide, 1, ((char *)slide,
              "\n[return-code %d:  zipfile not found]\n", err));
            break;
        case PK_PARAM:   /* exit(PK_PARAM); gives "access violation" */
            Info(slide, 1, ((char *)slide, "\n\
[return-code %d:  bad or illegal parameters specified on command line]\n",
              err));
            break;
        case PK_FIND:
            Info(slide, 1, ((char *)slide,
              "\n[return-code %d:  no files found to extract/view/etc.]\n",
              err));
            break;
        case PK_DISK:
            Info(slide, 1, ((char *)slide,
              "\n[return-code %d:  disk full or other I/O error]\n", err));
            break;
        case PK_EOF:
            Info(slide, 1, ((char *)slide, "\n\
[return-code %d:  unexpected EOF in zipfile (i.e., truncated)]\n", err));
            break;
        case IZ_CTRLC:
            Info(slide, 1, ((char *)slide,
              "\n[return-code %d:  you hit ctrl-C to terminate]\n", err));
            break;
        case IZ_UNSUP:
            Info(slide, 1, ((char *)slide, "\n\
[return-code %d:  unsupported compression or encryption for all files]\n",
              err));
            break;
        case IZ_BADPWD:
            Info(slide, 1, ((char *)slide,
              "\n[return-code %d:  bad decryption password for all files]\n",
              err));
            break;
#ifdef DO_SAFECHECK_2GB
        case IZ_ERRBF:
            Info(slide, 1, ((char *)slide,
              "\n[return-code %d:  big-file archive, small-file program]\n",
              err));
            break;
#endif /* DO_SAFECHECK_2GB */
        default:
            Info(slide, 1, ((char *)slide,
              "\n[return-code %d:  unknown return-code (screw-up)]\n", err));
            break;
    }
#endif /* RETURN_CODES */

/*---------------------------------------------------------------------------
 *  Return an intelligent status/severity level:
 *
 *  2007-01-29 SMS.
 *
 *  VMS Status Code Summary  (See STSDEF.H for details.)
 *
 *      Bits:   31:28    27:16     15:3     2 1 0
 *      Field:  Control  Facility  Message  Severity
 *                                          -----
 *                                          0 0 0  0    Warning
 *                                          0 0 1  1    Success
 *                                          0 1 0  2    Error
 *                                          0 1 1  3    Information
 *                                          1 0 0  4    Severe (fatal) error
 *
 *  In the Control field, bits 31:29 are reserved.  Bit 28 inhibits
 *  printing the message.  In the Facility field, bit 27 means
 *  customer-defined (not HP-assigned, like us).  In the Message field,
 *  bit 15 means facility-specific (which our messages are).
 *
 *  Note that the C library translates exit(0) to a $STATUS value of 1
 *  (i.e., exit is both silent and has a $SEVERITY of "success").
 *
 *  Previous versions of Info-ZIP programs used a generic ("chosen (by
 *  experimentation)") Control+Facility code of 0x7FFF, which included
 *  some reserved control bits, the inhibit-printing bit, and the
 *  customer-defined bit.
 *
 *  HP has now assigned official Facility names and corresponding
 *  Facility codes for the Info-ZIP products:
 *
 *      Facility Name    Facility Code
 *      IZ_UNZIP         1954 = 0x7A2
 *      IZ_ZIP           1955 = 0x7A3
 *
 *  Now, unless the CTL_FAC_IZ_UZP macro is defined at build-time, we
 *  will use the official Facility code.
 *
  ---------------------------------------------------------------------------*/

/* Official HP-assigned Info-ZIP UnZip Facility code. */
#define FAC_IZ_UZP 1954   /* 0x7A2 */

#ifndef CTL_FAC_IZ_UZP
   /*
    * Default is inhibit-printing with the official Facility code.
    */
#  define CTL_FAC_IZ_UZP ((0x1 << 12) | FAC_IZ_UZP)
#  define MSG_FAC_SPEC 0x8000   /* Facility-specific code. */
#else /* CTL_FAC_IZ_UZP */
   /* Use the user-supplied Control+Facility code for err or warn. */
#  ifndef MSG_FAC_SPEC          /* Old default is not Facility-specific. */
#    define MSG_FAC_SPEC 0x0    /* Facility-specific code.  Or 0x8000. */
#  endif /* !MSG_FAC_SPEC */
#endif /* ?CTL_FAC_IZ_ZIP */
#define VMS_UZ_FAC_BITS       ((CTL_FAC_IZ_UZP << 16) | MSG_FAC_SPEC)

    severity = (err == PK_WARN) ? 0 :                           /* warn  */
               (err == PK_ERR ||                                /* error */
                (err >= PK_NOZIP && err <= PK_FIND) ||          /*  ...  */
                (err >= IZ_CTRLC && err <= IZ_BADPWD)) ? 2 :    /*  ...  */
               4;                                               /* fatal */

    exit(                                           /* $SEVERITY:            */
         (err == PK_COOL) ? SS$_NORMAL :            /* success               */
         (VMS_UZ_FAC_BITS | (err << 4) | severity)  /* warning, error, fatal */
        );

} /* end function return_VMS() */


#ifdef MORE
static int scrnlines = -1;
static int scrncolumns = -1;
static int scrnwrap = -1;


static int getscreeninfo(int *tt_rows, int *tt_cols, int *tt_wrap)
{
    /*
     * For VMS v5.x:
     *   IO$_SENSEMODE/SETMODE info:  Programming, Vol. 7A, System Programming,
     *     I/O User's: Part I, sec. 8.4.1.1, 8.4.3, 8.4.5, 8.6
     *   sys$assign(), sys$qio() info:  Programming, Vol. 4B, System Services,
     *     System Services Reference Manual, pp. sys-23, sys-379
     *   fixed-length descriptor info:  Programming, Vol. 3, System Services,
     *     Intro to System Routines, sec. 2.9.2
     * GRR, 15 Aug 91 / SPC, 07 Aug 1995, 14 Nov 1999
     */

#ifndef OUTDEVICE_NAME
#define OUTDEVICE_NAME  "SYS$OUTPUT"
#endif

    static ZCONST struct dsc$descriptor_s OutDevDesc =
        {(sizeof(OUTDEVICE_NAME) - 1), DSC$K_DTYPE_T, DSC$K_CLASS_S,
         OUTDEVICE_NAME};
     /* {dsc$w_length, dsc$b_dtype, dsc$b_class, dsc$a_pointer}; */

    short  OutDevChan, iosb[4];
    long   status;
    struct tt_characts
    {
        uch class, type;
        ush pagewidth;
        union {
            struct {
                uch ttcharsbits[3];
                uch pagelength;
            } ttdef_bits;
            unsigned ttcharflags;
        } ttdef_area;
    }      ttmode;              /* total length = 8 bytes */


    /* assign a channel to standard output */
    status = sys$assign(&OutDevDesc, &OutDevChan, 0, 0);
    if (OK(status))
    {
        /* use sys$qiow and the IO$_SENSEMODE function to determine
         * the current tty status.
         */
        status = sys$qiow(0, OutDevChan, IO$_SENSEMODE, &iosb, 0, 0,
                          &ttmode, sizeof(ttmode), 0, 0, 0, 0);
        /* deassign the output channel by way of clean-up */
        (void) sys$dassgn(OutDevChan);
    }

    if ( OK(status) && OK(status = iosb[0]) ) {
        if (tt_rows != NULL)
            *tt_rows = ( (ttmode.ttdef_area.ttdef_bits.pagelength >= 5)
                        ? (int) (ttmode.ttdef_area.ttdef_bits.pagelength)
                                                        /* TT device value */
                        : (24) );                       /* VT 100 default  */
        if (tt_cols != NULL)
            *tt_cols = ( (ttmode.pagewidth >= 10)
                        ? (int) (ttmode.pagewidth)      /* TT device value */
                        : (80) );                       /* VT 100 default  */
        if (tt_wrap != NULL)
            *tt_wrap = ((ttmode.ttdef_area.ttcharflags & TT$M_WRAP) != 0);
    } else {
        /* VT 100 defaults */
        if (tt_rows != NULL)
            *tt_rows = 24;
        if (tt_cols != NULL)
            *tt_cols = 80;
        if (tt_wrap != NULL)
            *tt_wrap = FALSE;
    }

    return (OK(status));
}

int screensize(int *tt_rows, int *tt_cols)
{
    if (scrnlines < 0 || scrncolumns < 0)
        getscreeninfo(&scrnlines, &scrncolumns, &scrnwrap);
    if (tt_rows != NULL) *tt_rows = scrnlines;
    if (tt_cols != NULL) *tt_cols = scrncolumns;
    return !(scrnlines > 0 && scrncolumns > 0);
}

int screenlinewrap()
{
    if (scrnwrap == -1)
        getscreeninfo(&scrnlines, &scrncolumns, &scrnwrap);
    return (scrnwrap);
}
#endif /* MORE */


#ifndef SFX

/************************/
/*  Function version()  */
/************************/

/* 2004-11-23 SMS.
 * Changed to include the "-x" part of the VMS version.
 * Added the IA64 system type name.
 * Prepared for VMS versions after 9.  (We should live so long.)
 */

void version(__G)
    __GDEF
{
    int len;
#ifdef VMS_VERSION
    char *chrp1;
    char *chrp2;
    char buf[40];
    char vms_vers[16];
    int ver_maj;
#endif
#ifdef __DECC_VER
    char buf2[40];
    int  vtyp;
#endif

#ifdef VMS_VERSION
    /* Truncate the version string at the first (trailing) space. */
    strncpy(vms_vers, VMS_VERSION, sizeof(vms_vers));
    vms_vers[sizeof(vms_vers)-1] = '\0';
    chrp1 = strchr(vms_vers, ' ');
    if (chrp1 != NULL)
        *chrp1 = '\0';

    /* Determine the major version number. */
    ver_maj = 0;
    chrp1 = strchr(&vms_vers[1], '.');
    for (chrp2 = &vms_vers[1];
         chrp2 < chrp1;
         ver_maj = ver_maj * 10 + *(chrp2++) - '0');
#endif /* VMS_VERSION */

/*  DEC C in ANSI mode does not like "#ifdef MACRO" inside another
    macro when MACRO is equated to a value (by "#define MACRO 1").   */

    len = sprintf((char *)slide, LoadFarString(CompiledWith),

#ifdef __GNUC__
      "gcc ", __VERSION__,
#else
#  if defined(DECC) || defined(__DECC) || defined (__DECC__)
      "DEC C",
#    ifdef __DECC_VER
      (sprintf(buf2, " %c%d.%d-%03d",
               ((vtyp = (__DECC_VER / 10000) % 10) == 6 ? 'T' :
                (vtyp == 8 ? 'S' : 'V')),
               __DECC_VER / 10000000,
               (__DECC_VER % 10000000) / 100000, __DECC_VER % 1000), buf2),
#    else
      "",
#    endif
#  else
#    ifdef VAXC
      "VAX C", "",
#    else
      "unknown compiler", "",
#    endif
#  endif
#endif

#ifdef VMS_VERSION
#  if defined(__alpha)
      "OpenVMS",
      (sprintf(buf, " (%s Alpha)", vms_vers), buf),
#  elif defined(__ia64)
      "OpenVMS",
      (sprintf(buf, " (%s IA64)", vms_vers), buf),
#  else /* VAX */
      (ver_maj >= 6) ? "OpenVMS" : "VMS",
      (sprintf(buf, " (%s VAX)", vms_vers), buf),
#  endif
#else
      "VMS",
      "",
#endif /* ?VMS_VERSION */

#ifdef __DATE__
      " on ", __DATE__
#else
      "", ""
#endif
    );

    (*G.message)((zvoid *)&G, slide, (ulg)len, 0);

} /* end function version() */

#endif /* !SFX */



#ifdef __DECC

/* 2004-11-20 SMS.
 *
 *       acc_cb(), access callback function for DEC C open().
 *
 *    Set some RMS FAB/RAB items, with consideration of user-specified
 * values from (DCL) SET RMS_DEFAULT.  Items of particular interest are:
 *
 *       fab$w_deq         default extension quantity (blocks) (write).
 *       rab$b_mbc         multi-block count.
 *       rab$b_mbf         multi-buffer count (used with rah and wbh).
 *
 *    See also the OPEN* macros in VMSCFG.H.  Currently, no notice is
 * taken of the caller-ID value, but options could be set differently
 * for read versus write access.  (I assume that specifying fab$w_deq,
 * for example, for a read-only file has no ill effects.)
 */

/* Global storage. */

int openr_id = OPENR_ID;        /* Callback id storage, read. */

/* acc_cb() */

int acc_cb(int *id_arg, struct FAB *fab, struct RAB *rab)
{
    int sts;

    /* Get process RMS_DEFAULT values, if not already done. */
    if (rms_defaults_known == 0)
    {
        get_rms_defaults();
    }

    /* If RMS_DEFAULT (and adjusted active) values are available, then set
     * the FAB/RAB parameters.  If RMS_DEFAULT values are not available,
     * suffer with the default parameters.
     */
    if (rms_defaults_known > 0)
    {
        /* Set the FAB/RAB parameters accordingly. */
        fab-> fab$w_deq = rms_ext_active;
        rab-> rab$b_mbc = rms_mbc_active;
        rab-> rab$b_mbf = rms_mbf_active;

        /* Truncate at EOF on close, as we'll probably over-extend. */
        fab-> fab$v_tef = 1;

        /* If using multiple buffers, enable read-ahead and write-behind. */
        if (rms_mbf_active > 1)
        {
            rab-> rab$v_rah = 1;
            rab-> rab$v_wbh = 1;
        }

        if (DIAG_FLAG)
        {
            fprintf(stderr,
              "Open callback.  ID = %d, deq = %6d, mbc = %3d, mbf = %3d.\n",
              *id_arg, fab-> fab$w_deq, rab-> rab$b_mbc, rab-> rab$b_mbf);
        }
    }

    /* Declare success. */
    return 0;
}



/*
 * 2004-09-19 SMS.
 *
 *----------------------------------------------------------------------
 *
 *       decc_init()
 *
 *    On non-VAX systems, uses LIB$INITIALIZE to set a collection of C
 *    RTL features without using the DECC$* logical name method.
 *
 *----------------------------------------------------------------------
 */

#ifdef __CRTL_VER
#if !defined(__VAX) && (__CRTL_VER >= 70301000)

#include <unixlib.h>

/*--------------------------------------------------------------------*/

/* Global storage. */

/*    Flag to sense if decc_init() was called. */

static int decc_init_done = -1;

/*--------------------------------------------------------------------*/

/* decc_init()

      Uses LIB$INITIALIZE to set a collection of C RTL features without
      requiring the user to define the corresponding logical names.
*/

/* Structure to hold a DECC$* feature name and its desired value. */

typedef struct
{
   char *name;
   int value;
} decc_feat_t;

/* Array of DECC$* feature names and their desired values. */

decc_feat_t decc_feat_array[] = {

   /* Preserve command-line case with SET PROCESS/PARSE_STYLE=EXTENDED */
 { "DECC$ARGV_PARSE_STYLE", 1 },

   /* Preserve case for file names on ODS5 disks. */
 { "DECC$EFS_CASE_PRESERVE", 1 },

   /* Enable multiple dots (and most characters) in ODS5 file names,
      while preserving VMS-ness of ";version". */
 { "DECC$EFS_CHARSET", 1 },

   /* List terminator. */
 { (char *)NULL, 0 } };


/* LIB$INITIALIZE initialization function. */

static void decc_init(void)
{
    int feat_index;
    int feat_value;
    int feat_value_max;
    int feat_value_min;
    int i;
    int sts;

    /* Set the global flag to indicate that LIB$INITIALIZE worked. */

    decc_init_done = 1;

    /* Loop through all items in the decc_feat_array[]. */

    for (i = 0; decc_feat_array[i].name != NULL; i++)
    {
        /* Get the feature index. */
        feat_index = decc$feature_get_index(decc_feat_array[i].name);
        if (feat_index >= 0)
        {
            /* Valid item.  Collect its properties. */
            feat_value = decc$feature_get_value(feat_index, 1);
            feat_value_min = decc$feature_get_value(feat_index, 2);
            feat_value_max = decc$feature_get_value(feat_index, 3);

            if ((decc_feat_array[i].value >= feat_value_min) &&
                (decc_feat_array[i].value <= feat_value_max))
            {
                /* Valid value.  Set it if necessary. */
                if (feat_value != decc_feat_array[i].value)
                {
                    sts = decc$feature_set_value(
                              feat_index,
                              1,
                              decc_feat_array[i].value);
                }
            }
            else
            {
                /* Invalid DECC feature value. */
                printf(" INVALID DECC FEATURE VALUE, %d: %d <= %s <= %d.\n",
                  feat_value,
                  feat_value_min, decc_feat_array[i].name, feat_value_max);
            }
        }
        else
        {
            /* Invalid DECC feature name. */
            printf(" UNKNOWN DECC FEATURE: %s.\n", decc_feat_array[i].name);
        }
    }
}

/* Get "decc_init()" into a valid, loaded LIB$INITIALIZE PSECT. */

#pragma nostandard

/* Establish the LIB$INITIALIZE PSECT, with proper alignment and
   attributes.
*/
globaldef {"LIB$INITIALIZ"} readonly _align (LONGWORD)
   int spare[8] = { 0 };
globaldef {"LIB$INITIALIZE"} readonly _align (LONGWORD)
   void (*x_decc_init)() = decc_init;

/* Fake reference to ensure loading the LIB$INITIALIZE PSECT. */

#pragma extern_model save
/* The declaration for LIB$INITIALIZE() is missing in the VMS system header
   files.  Addionally, the lowercase name "lib$initialize" is defined as a
   macro, so that this system routine can be reference in code using the
   traditional C-style lowercase convention of function names for readability.
   (VMS system functions declared in the VMS system headers are defined in a
   similar way to allow using lowercase names within the C code, whereas the
   "externally" visible names in the created object files are uppercase.)
 */
#ifndef lib$initialize
#  define lib$initialize LIB$INITIALIZE
#endif
int lib$initialize(void);
#pragma extern_model strict_refdef
int dmy_lib$initialize = (int)lib$initialize;
#pragma extern_model restore

#pragma standard

#endif /* !defined(__VAX) && (__CRTL_VER >= 70301000) */
#endif /* __CRTL_VER */
#endif /* __DECC */

#endif /* VMS */