/*
  Copyright (c) 1990-2002 Info-ZIP.  All rights reserved.

  See the accompanying file LICENSE, version 2000-Apr-09 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()
             dos_to_unix_time()         (TIMESTAMP only)
             stamp_file()               (TIMESTAMP only)
             do_wild()
             mapattr()
             mapname()
             checkdir()
             check_for_newer()
             return_VMS
             screensize()
             screenlinewrap()
             version()

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

#ifdef VMS                      /* VMS only! */

#define UNZIP_INTERNAL
#include "unzip.h"
#include "vms.h"
#include "vmsdefs.h"
#ifdef MORE
#  include <ttdef.h>
#endif
#include <lib$routines.h>
#include <unixlib.h>

#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 */

#define OK(s)   ((s)&1)         /* VMS success or warning status */
#define STRICMP(s1,s2)  STRNICMP(s1,s2,2147483647)

/*
 *   Local static storage
 */
static struct FAB       fileblk;
static struct XABDAT    dattim;
static struct XABRDT    rdt;
static struct RAB       rab;
static struct NAM       nam;

static struct FAB *outfab = NULL;
static struct RAB *outrab = NULL;
static struct XABFHC *xabfhc = NULL;
static struct XABDAT *xabdat = NULL;
static struct XABRDT *xabrdt = NULL;
static struct XABPRO *xabpro = NULL;
static struct XABKEY *xabkey = NULL;
static struct XABALL *xaball = NULL;
static struct XAB *first_xab = NULL, *last_xab = NULL;

static char query = '\0';

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),
            _flush_stream(__GPRO__ uch *rawbuf, unsigned size, int final_flag),
            _flush_varlen(__GPRO__ uch *rawbuf, unsigned size, int final_flag),
            _flush_qio(__GPRO__ uch *rawbuf, unsigned size, int final_flag),
            _close_rms(__GPRO),
            _close_qio(__GPRO),
#ifdef ASYNCH_QIO
            WriteQIO(__GPRO__ uch *buf, unsigned len),
#endif
            WriteBuffer(__GPRO__ uch *buf, unsigned len),
            WriteRecord(__GPRO__ uch *rec, unsigned len);

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

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


int check_format(__G)
    __GDEF
{
    int rtype;
    struct FAB fab;

    fab = cc$rms_fab;
    fab.fab$l_fna = G.zipfn;
    fab.fab$b_fns = strlen(G.zipfn);

    if ((sys$open(&fab) & 1) == 0)
    {
        Info(slide, 1, ((char *)slide, "\n\
     error:  cannot open zipfile [ %s ] (access denied?).\n\n",
          FnFilter1(G.zipfn)));
        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)
 */

int open_outfile(__G)           /* return 1 (PK_WARN) if fail */
    __GDEF
{
    switch (find_vms_attrs(__G))
    {
        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));
}


static int create_default_output(__GPRO)      /* return 1 (PK_WARN) if fail */
{
    int ierr;
    int text_output, bin_fixed;

    /* extract the file in text (variable-length) format, when
     * a) explicitely requested by the user (through the -a option)
     *  or
     * b) piping to SYS$OUTPUT, unless "binary" piping was requested
     *    by the user (through the -b option)
     */
    text_output = G.pInfo->textmode ||
                  (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) entry is not extracted in text mode
     */
    bin_fixed = !text_output &&
                (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;       /* fill RAB with default values */
        fileblk = cc$rms_fab;   /* fill FAB with default values */

        outfab = &fileblk;
        outfab->fab$l_xab = NULL;

        if (text_output)
        {   /* Default format for output `real' text file */

            outfab->fab$b_rfm = FAB$C_VAR;      /* variable length records */
            outfab->fab$b_rat = FAB$M_CR;       /* implied (CR) carriage ctrl */
        }
        else if (bin_fixed)
        {   /* Default format for output `real' binary file */

            outfab->fab$b_rfm = FAB$C_FIX;      /* fixed length record format */
            outfab->fab$w_mrs = 512;            /* record size 512 bytes */
            outfab->fab$b_rat = 0;              /* no carriage ctrl */
        }
        else
        {   /* Default format for output misc (bin or text) file */

            outfab->fab$b_rfm = FAB$C_STMLF;    /* stream-LF record format */
            outfab->fab$b_rat = FAB$M_CR;       /* implied (CR) carriage ctrl */
        }

        outfab->fab$l_fna = G.filename;
        outfab->fab$b_fns = strlen(outfab->fab$l_fna);

        {
            set_default_datetime_XABs(__G);

            dattim.xab$l_nxt = outfab->fab$l_xab;
            outfab->fab$l_xab = (void *) &dattim;
        }

        outfab->fab$w_ifi = 0;  /* Clear IFI. It may be nonzero after ZIP */
        outfab->fab$b_fac = FAB$M_BRO | FAB$M_PUT;  /* {block|record} output */

        ierr = sys$create(outfab);
        if (ierr == RMS$_FEX)
            ierr = replace(__G);

        if (ierr == 0)          /* Canceled */
            return (free_up(), PK_WARN);

        if (ERR(ierr))
        {
            char buf[256];

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

        outrab = &rab;
        rab.rab$l_fab = outfab;
        if (!text_output)
        {
            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_default_output: sys$connect failed.\n", ierr);
            vms_msg(__G__ "", outfab->fab$l_stv);
#endif
            Info(slide, 1, ((char *)slide,
                 "Can't create output file:  %s\n", FnFilter1(G.filename)));
            free_up();
            return PK_WARN;
        }
    }                   /* end if (!uO.cflag) */

    init_buf_ring();

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



static int create_rms_output(__GPRO)          /* return 1 (PK_WARN) if fail */
{
    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)
    {
        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 PK_WARN;
        }
    }
    else                        /* Redirect output */
    {
        rab = cc$rms_rab;       /* fill RAB with default values */

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

        outfab->fab$l_fna = G.filename;
        outfab->fab$b_fns = strlen(outfab->fab$l_fna);

        if (!(xabdat && xabrdt))        /* Use date/time info
                                         *  from zipfile if
                                         *  no attributes given
                                         */
        {
            set_default_datetime_XABs(__G);

            if (xabdat == NULL)
            {
                dattim.xab$l_nxt = outfab->fab$l_xab;
                outfab->fab$l_xab = (void *) &dattim;
            }
        }

        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 */

        ierr = sys$create(outfab);
        if (ierr == RMS$_FEX)
            ierr = replace(__G);

        if (ierr == 0)          /* Canceled */
            return (free_up(), PK_WARN);

        if (ERR(ierr))
        {
            char buf[256];

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

        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[256];

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

        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);
            vms_msg(__G__ "", outfab->fab$l_stv);
#endif
            Info(slide, 1, ((char *)slide,
                 "Can't create output file:  %s\n", FnFilter1(G.filename)));
            free_up();
            return PK_WARN;
        }
    }                   /* 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 PK_COOL;
}



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

#if defined(__DECC) || defined(__DECCXX)
#pragma __member_alignment __save
#pragma __nomember_alignment
#endif /* __DECC || __DECCXX */
static struct
{
    short   status;
    long    count;
    short   dummy;
} pka_io_sb;
#if defined(__DECC) || defined(__DECCXX)
#pragma __member_alignment __restore
#endif /* __DECC || __DECCXX */

static struct
{
    short   status;
    short   dummy;
    void    *addr;
} pka_acp_sb;

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;

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$t_dvi[1]  };

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

static char exp_nam[NAM$C_MAXRSS];
static char res_nam[NAM$C_MAXRSS];

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


static int create_qio_output(__GPRO)          /* return 1 (PK_WARN) if fail */
{
    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 PK_WARN;
            }
        }
        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;
        fileblk.fab$l_fna = G.filename;
        fileblk.fab$b_fns = strlen(G.filename);

        nam = cc$rms_nam;
        fileblk.fab$l_nam = &nam;
        nam.nam$l_esa = exp_nam;
        nam.nam$b_ess = sizeof(exp_nam);
        nam.nam$l_rsa = res_nam;
        nam.nam$b_rss = sizeof(res_nam);

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

        pka_devdsc.dsc$w_length = (unsigned short)nam.nam$t_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 PK_WARN;
        }

        pka_fnam.dsc$a_pointer = nam.nam$l_name;
        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;

        for (i=0;i<3;i++)
        {
            pka_fib.FIB$W_DID[i]=nam.nam$w_did[i];
            pka_fib.FIB$W_FID[i]=0;
        }

        pka_fib.FIB$L_ACCTL = FIB$M_WRITE;
        /* 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, pka_devchn, IO$_CREATE|IO$M_CREATE|IO$M_ACCESS,
                          &pka_acp_sb, 0, 0,
                          &pka_fibdsc, &pka_fnam, 0, 0, &pka_atr, 0);

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

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

#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 PK_COOL;
}



static int replace(__GPRO)
{                       /*
                         *      File exists. Inquire user about further action.
                         */
    char answ[10];
    struct NAM nam;
    int ierr;

    if (query == '\0')
    {
        do
        {
            Info(slide, 0x81, ((char *)slide,
                 "%s exists:  [o]verwrite, new [v]ersion or [n]o extract?\n\
  (uppercase response [O,V,N] = do same for all files): ",
                 FnFilter1(G.filename)));
            fflush(stderr);
        } while (fgets(answ, 9, stderr) == NULL && !isalpha(answ[0])
                 && tolower(answ[0]) != 'o'
                 && tolower(answ[0]) != 'v'
                 && tolower(answ[0]) != 'n');

        if (isupper(answ[0]))
            query = answ[0] = tolower(answ[0]);
    }
    else
        answ[0] = query;

    switch (answ[0])
    {
        case 'n':
            ierr = 0;
            break;
        case 'v':
            nam = cc$rms_nam;
            nam.nam$l_rsa = G.filename;
            nam.nam$b_rss = FILNAMSIZ - 1;

            outfab->fab$l_fop |= FAB$M_MXV;
            outfab->fab$l_nam = &nam;

            ierr = sys$create(outfab);
            if (!ERR(ierr))
            {
                outfab->fab$l_nam = NULL;
                G.filename[outfab->fab$b_fns = nam.nam$b_rsl] = '\0';
            }
            break;
        case 'o':
            outfab->fab$l_fop |= FAB$M_SUP;
            ierr = sys$create(outfab);
            break;
    }
    return ierr;
}



#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 ZIP entry extra field if any   *
 * and looks for VMS attribute records. Returns 0 if either no  *
 * attributes found or no fab given.                            *
 ****************************************************************/
static int find_vms_attrs(__G)
    __GDEF
{
    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)) {
                xabdat = (struct XABDAT *) extract_izvms_block(__G__ blk, siz,
                                        NULL, (uch *)&cc$rms_xabdat, XDATL);
            } else if (EQL_L(block_id, XRDTSIG)) {
                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;
                }

                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 = &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,XABRDT now. Leave them for sys$close() */

            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;
    }
    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_sb);
        if (!ERR(status))
            status = pka_io_sb.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_sb, 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;
}

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;
        }
    }

    return (final_flag & (curbuf->bufcnt > 0)) ?
        WriteQIO(curbuf->buf, (curbuf->bufcnt+1)&(~1)) : /* even byte count! */
        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 )
        {
            status = sys$qiow(0, pka_devchn, IO$_WRITEVBLK,
                              &pka_io_sb, 0, 0,
                              locbuf,
                              (loccnt+1)&(~1), /* Round up to even byte count */
                              pka_vbn,
                              0, 0, 0);
            if (!ERR(status))
                status = pka_io_sb.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_sb, 0, 0,
                              locbuf, loccnt, pka_vbn,
                              0, 0, 0);
            if (!ERR(status))
                status = pka_io_sb.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_sb, 0, 0,
                          out_ptr, put_cnt, pka_vbn,
                          0, 0, 0);
        if (!ERR(status))
            status = pka_io_sb.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.
 */
    uch *p;
    unsigned n;
    unsigned *l;
{
    unsigned off = n;
    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);
            vms_msg(__G__ "", outrab->rab$l_stv);
        }
        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);
            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);
            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);
            vms_msg(__G__ "", outrab->rab$l_stv);
            return PK_DISK;
        }
    }
    return PK_COOL;
}



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;

    /* 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);
        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);
        vms_msg(__G__ "", outfab->fab$l_stv);
    }
#endif
    free_up();
    return PK_COOL;
}



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_sb);
        if (!ERR(status))
            status = pka_io_sb.status;
        if (ERR(status))
        {
            vms_msg(__G__ "[ _close_qio: sys$synch found I/O failure ]\n",
                    status);
        }
        pka_io_pending = FALSE;
    }
#endif /* ASYNCH_QIO */

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

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



#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, &pka_rattr},
        {sizeof(pka_uchar), ATR$C_UCHAR, &pka_uchar},
        {sizeof(Cdate), ATR$C_CREDATE, &Cdate[0]},
        {sizeof(Rdate), ATR$C_REVDATE, &Rdate[0]},
        {sizeof(Edate), ATR$C_EXPDATE, &Edate[0]},
        {sizeof(Bdate), ATR$C_BAKDATE, &Bdate[0]},
        {sizeof(revisions), ATR$C_ASCDATES, &revisions},
        {sizeof(prot), ATR$C_FPRO, &prot},
        {sizeof(uic), ATR$C_UIC, &uic},
        {sizeof(jnl), ATR$C_JOURNAL, &jnl},
        {0, 0, 0}
    };

    fileblk = cc$rms_fab;
    fileblk.fab$l_fna = (char *)fname;
    fileblk.fab$b_fns = strlen(fname);

    nam = cc$rms_nam;
    fileblk.fab$l_nam = &nam;
    nam.nam$l_esa = exp_nam;
    nam.nam$b_ess = sizeof(exp_nam);
    nam.nam$l_rsa = res_nam;
    nam.nam$b_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$t_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;
    }

    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;

    for (i=0;i<3;i++)
    {
        pka_fib.FIB$W_DID[i]=nam.nam$w_did[i];
        pka_fib.FIB$W_FID[i]=nam.nam$w_fid[i];
    }

    /* Use the IO$_ACCESS function to return info about the file */
    /* Note, used 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_sb, 0, 0,
                      &pka_fibdsc, &pka_fnam, 0, 0, &Atr, 0);

    if ( !ERR(status) )
        status = pka_acp_sb.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;
    for (i=0;i<3;i++)
    {
        pka_fib.FIB$W_DID[i]=nam.nam$w_did[i];
        pka_fib.FIB$W_FID[i]=nam.nam$w_fid[i];
    }

    /* 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_sb, 0, 0,
                      &pka_fibdsc, &pka_fnam, 0, 0, &Atr, 0);

    if ( !ERR(status) )
        status = pka_acp_sb.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 void vms_msg(__GPRO__ char *string, int status)
{
    static char msgbuf[256];

    $DESCRIPTOR(msgd, msgbuf);
    int msglen = 0;

    if (ERR(lib$sys_getmsg(&status, &msglen, &msgd, 0, 0)))
        Info(slide, 1, ((char *)slide,
             "%s[ VMS status = %d ]\n", string, status));
    else
    {
        msgbuf[msglen] = '\0';
        Info(slide, 1, ((char *)slide, "%s[ %s ]\n", string, msgbuf));
    }
}



#ifndef SFX

char *do_wild( __G__ wld )
    __GDEF
    ZCONST char *wld;
{
    int status;

    static char filenam[256];
    static char efn[256];
    static char last_wild[256];
    static struct FAB fab;
    static struct NAM 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 );
        first_call = 1;            /* New wild spec */

        fab = cc$rms_fab;
        fab.fab$l_fna = last_wild;
        fab.fab$b_fns = strlen(last_wild);
        fab.fab$l_dna = (char *) deflt;
        fab.fab$b_dns = sizeof(deflt)-1;
        fab.fab$l_nam = &nam;
        nam = cc$rms_nam;
        nam.nam$l_esa = efn;
        nam.nam$b_ess = sizeof(efn)-1;
        nam.nam$l_rsa = filenam;
        nam.nam$b_rss = sizeof(filenam)-1;

        if ( !OK(sys$parse(&fab)) )
            return (char *)NULL;     /* Initialization failed */
        first_call = 0;
        if ( !OK(sys$search(&fab)) )
        {
            strcpy( filenam, wld );
            return filenam;
        }
    }
    else
    {
        if ( !OK(sys$search(&fab)) )
        {
            first_call = 1;        /* Reinitialize next time */
            return (char *)NULL;
        }
    }
    filenam[nam.nam$b_rsl] = 0;
    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 BEOS_:
        case QDOS_:
        case TANDEM_:
            {
              unsigned uxattr = (unsigned)(tmp >> 16);  /* drwxrwxrwx */
              int r = FALSE;

              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 an 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) {
                  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() */



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

#include <fscndef.h> /* for filescan */

#   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;

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 *pp, *cp=(char *)NULL;    /* character pointers */
    char *lastsemi = NULL;         /* pointer to last semi-colon in pathcomp */
    char *last_dot = NULL;         /* last dot not converted to underscore */
    int quote = FALSE;             /* flag:  next char is literal */
    int dotname = FALSE;           /* flag:  path component begins with dot */
    int killed_ddot = FALSE;       /* is set when skipping "../" pathcomp */
    int error = MPN_OK;
    register unsigned workch;      /* hold the character being tested */

    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 */

/* 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 */

    *pathcomp = '\0';           /* initialize translation buffer */
    pp = pathcomp;              /* point to translation buffer */
    if (uO.jflag)               /* junking directories */
/* GRR:  watch out for VMS version... */
        cp = (char *)strrchr(G.filename, '/');
    if (cp == NULL)             /* no '/' or not junking dirs */
        cp = G.filename;        /* point to internal zipfile-member pathname */
    else
        ++cp;                   /* point to start of last component of path */

/*---------------------------------------------------------------------------
    Begin main loop through characters in filename.
  ---------------------------------------------------------------------------*/

    while ((workch = (uch)*cp++) != 0) {

        if (quote) {              /* if character quoted, */
            *pp++ = (char)workch; /*  include it literally */
            quote = FALSE;
        } else
            switch (workch) {
            case '/':             /* can assume -j flag not given */
                *pp = '\0';
                if (((error = checkdir(__G__ pathcomp, APPEND_DIR)) & MPN_MASK)
                     > MPN_INF_TRUNC)
                    return error;
                pp = pathcomp;    /* reset conversion buffer for next piece */
                last_dot = NULL;  /* directory names must not contain dots */
                lastsemi = NULL;  /* leave directory semi-colons alone */
                break;

            case '.':
                if (pp == pathcomp) {     /* nothing appended yet... */
                    if (*cp == '/') {     /* don't bother appending a "./" */
                        ++cp;             /*  component to the path:  skip */
                                          /*  to next char after the '/' */
                        break;
                    } else if (*cp == '.' && cp[1] == '/') {   /* "../" */
                        if (!uO.ddotflag) { /* "../" dir traversal detected */
                            cp += 2;        /*  skip over behind the '/' */
                            killed_ddot = TRUE;
                        } else {
                            *pp++ = '.';    /* add first dot, unchanged... */
                            *pp++ = '.';    /* add second dot, unchanged... */
                            ++cp;           /* skip second dot */
                        }                   /* next char is  the '/' */
                        break;
                    }
                }
                last_dot = pp;    /* point at last dot so far... */
                *pp++ = '_';      /* convert dot to underscore for now */
                break;

            case ';':             /* start of VMS version? */
                if (lastsemi)
                    *lastsemi = '_';   /* convert previous one to underscore */
                lastsemi = pp;
                *pp++ = ';';      /* keep for now; remove VMS vers. later */
                break;

            case ':':   /* drive names illegal in zipfile, so no ':' allowed */
            case ' ':
                *pp++ = '_';
                break;

            default:
                if ( isalpha(workch) || isdigit(workch) ||
                    workch=='$' || workch=='-' )
                    *pp++ = (char)workch;
                else
                    *pp++ = '_';  /* convert everything else to underscore */
                break;
            } /* end switch */

    } /* 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;
    }

/*---------------------------------------------------------------------------
    Report if directory was created (and no file to create:  filename ended
    in '/'), check name to be sure it exists, and combine path and name be-
    fore exiting.
  ---------------------------------------------------------------------------*/

    if (G.filename[strlen(G.filename) - 1] == '/') {
        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;
    }

    *pp = '\0';                   /* done with pathcomp:  terminate it */

    /* if not saving them, remove VMS version numbers (appended ";###") */
    if (lastsemi) {
        pp = lastsemi + 1;        /* expect all digits after semi-colon */
        while (isdigit((uch)(*pp)))
            ++pp;
        if (*pp)                  /* not version number:  convert ';' to '_' */
            *lastsemi = '_';
        else if (!uO.V_flag)      /* only digits between ';' and end:  nuke */
            *lastsemi = '\0';
        /* else only digits and we're saving version number:  do nothing */
    }

    if (last_dot != NULL)         /* one dot is OK:  put it back in */
        *last_dot = '.';

    if (*pathcomp == '\0') {
        Info(slide, 1, ((char *)slide, "mapname:  conversion of %s failed\n",
          FnFilter1(G.filename)));
        return (error & ~MPN_MASK) | MPN_ERR_SKIP;
    }

    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];
    static char lastdir[FILNAMSIZ]="\t"; /* directory created last time */
                                         /* initially - impossible dir. spec. */
    static char *pathptr=pathbuf;        /* For debugger */
    static char *devptr, *dirptr, *namptr;
    static int  devlen, dirlen, namlen;
    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;

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

#if (!defined(SFX) || defined(SFX_EXDIR))
    if (function==ROOT)
    {        /*  Assume VMS root spec */
        char  *p = pathcomp;
        char  *q;

        struct
        {
            short  len;
            short  code;
            char   *addr;
        } itl [4] =
        {
            {  0,  FSCN$_DEVICE,    NULL  },
            {  0,  FSCN$_ROOT,      NULL  },
            {  0,  FSCN$_DIRECTORY, NULL  },
            {  0,  0,               NULL  }   /* End of itemlist */
        };
        int fields = 0;
        struct dsc$descriptor  pthcmp;

        if (rootlen > 0)        /* rootpath was already set, nothing to do */
            return MPN_OK;

        /*
         *  Initialize everything
         */
        end = devptr = dirptr = rootend = pathbuf;
        devlen = dirlen = rootlen = 0;

        pthcmp.dsc$a_pointer = pathcomp;
        if ( (pthcmp.dsc$w_length = strlen(pathcomp)) > 255 )
            return MPN_ERR_TOOLONG;

        status = sys$filescan(&pthcmp, itl, &fields);
        if ( !OK(status) )
            return MPN_ERR_SKIP;

        if ( fields & FSCN$M_DEVICE )
        {
            strncpy(devptr = end, itl[0].addr, itl[0].len);
            dirptr = (end += (devlen = itl[0].len));
        }

        root_has_dir = 0;

        if ( fields & FSCN$M_ROOT )
        {
            int   len;

            strncpy(dirptr = end, itl[1].addr,
                len = itl[1].len - 1);        /* Cut out trailing ']' */
            end += len;
            root_has_dir = 1;
        }

        if ( fields & FSCN$M_DIRECTORY )
        {
            char  *ptr;
            int   len;

            len = itl[2].len-1;
            ptr = itl[2].addr;

            if ( root_has_dir /* i.e. root specified */ )
            {
                --len;                            /* Cut out leading dot */
                ++ptr;                            /* ??? [a.b.c.][.d.e] */
            }

            strncpy(dirptr=end, ptr, len);  /* Replace trailing ']' */
            *(end+=len) = '.';                    /* ... with dot */
            ++end;
            root_has_dir = 1;
        }

        /* When user specified "[a.b.c.]" or "[qq...]", we have too many
        *  trailing dots. Let's cut them out. Now we surely have at least
        *  one trailing dot and "end" points just behind it. */

        dirlen = end - dirptr;
        while ( dirlen > 1 && end[-2] == '.' )
            --dirlen,--end;

        first_comp = !root_has_dir;
        root_dirlen = end - dirptr;
        *(rootend = end) = '\0';
        rootlen = rootend - devptr;
        return MPN_OK;
    }
#endif /* !SFX || SFX_EXDIR */


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

    if ( function == INIT )
    {
        if ( strlen(G.filename) + rootlen + 13 > 255 )
            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) > 255 )
            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 */
            struct        FAB fab;
            struct        NAM nam;

            fab = cc$rms_fab;
            fab.fab$l_fna = G.filename;
            fab.fab$b_fns = strlen(G.filename);
            fab.fab$l_dna = pathptr;
            fab.fab$b_dns = end-pathptr;

            fab.fab$l_nam = &nam;
            nam = cc$rms_nam;
            nam.nam$l_esa = pathcomp;
            nam.nam$b_ess = 255;            /* Assume large enaugh */

            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$b_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,"sys$disk:[]") )
                {
                    strcpy(lastdir,"sys$disk:[]");
                    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
    unsigned short timbuf[7];
    unsigned dy, mo, yr, hh, mm, ss, dy2, mo2, yr2, hh2, mm2, ss2;
    struct FAB fab;
    struct XABDAT xdat;


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

    fab  = cc$rms_fab;
    xdat = cc$rms_xabdat;

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

    if ((sys$open(&fab) & 1) == 0)       /* 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;
        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:

        $STATUS          $SEVERITY = $STATUS & 7
        31 .. 16 15 .. 3   2 1 0
                           -----
        VMS                0 0 0  0    Warning
        FACILITY           0 0 1  1    Success
        Number             0 1 0  2    Error
                 MESSAGE   0 1 1  3    Information
                 Number    1 0 0  4    Severe (fatal) error

    0x7FFF0000 was chosen (by experimentation) to be outside the range of
    VMS FACILITYs that have dedicated message numbers.  Hopefully this will
    always result in silent exits--it does on VMS 5.4.  Note that the C li-
    brary translates exit arguments of zero to a $STATUS value of 1 (i.e.,
    exit is both silent and has a $SEVERITY of "success").
  ---------------------------------------------------------------------------*/

    severity = (err == PK_WARN) ? 1 :           /* warn  */
               (err == 2 ||                     /* error */
                (err >= 9 && err <= 11) ||      /*  ...  */
                (err >= 80 && err <= 82)) ? 2 : /*  ...  */
               4;                               /* fatal */

    exit(                                       /* $SEVERITY:              */
         (err == PK_COOL) ? 1 :                 /*   success               */
         (0x7FFF0000 | (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 (status & 1)
    {
        /* 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 ( (status & 1) && ((status = iosb[0]) & 1) ) {
        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 ((status & 1) != 0);
}

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()  */
/************************/

void version(__G)
    __GDEF
{
    int len;
#ifdef VMS_VERSION
    char buf[40];
#endif
#ifdef __DECC_VER
    char buf2[40];
    int  vtyp;
#endif

/*  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",   /* version has trailing spaces ("V6.1   "), so truncate: */
      (sprintf(buf, " (%.4s for Alpha)", VMS_VERSION), buf),
#  else /* VAX */
      (VMS_VERSION[1] >= '6') ? "OpenVMS" : "VMS",
      (sprintf(buf, " (%.4s for VAX)", VMS_VERSION), 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 */
#endif /* VMS */
