/* vms.c -- target dependent functions for VMS
 * This is free software; you can redistribute it and/or modify it under the
 * terms of the GNU General Public License, see the file COPYING.
 *
 * This file was written by Karl-Jose Filler <pla_jfi@pki-nbg.philips.de>
 * and updated by Jean-loup Gailly.
 */

#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stat.h>
#include <unixio.h>

#ifdef __DECC

#include <lib$routines.h>
#include <jpidef.h>

/* Modern code would use fabdef.h, namdef.h, and rabdef.h, but some
 * old environments can't deal with both XXX.h and XXXdef.h and or only
 * XXXdef.h.
 */
#include <fab.h>
#include <nam.h>
#include <rab.h>
#include <rmsdef.h>

#include <starlet.h>
#include <stsdef.h>

#endif /* def __DECC */

#include "config.h"
#include "gzip.h"
#include "xalloc.h"
#include "vms.h"

static char **vms_argv = NULL;

static int max_files = 10000;

struct	Str_desc {
    int     length;
    char    *addr;
};


int find_file_c(in,out,out_len,context)
    char *in;
    char *out;
    int   out_len;
    int  *context;
{
    struct	Str_desc in_desc,out_desc;
    int		status;
    char	*p;
  
    in_desc.addr = in;
    in_desc.length = strlen(in);
  
    out_desc.addr = out;
    out_desc.length = out_len;
  
    status = lib$find_file(&in_desc,&out_desc,context);

    p   = out_desc.addr;
    while(*p != ' ') {
	p++;
    }
    *p = 0;
  
    return status;
}


vms_expand_args(old_argc, argv)
    int *old_argc;
    char **argv[];
{
    int	    i;
    int	    new_argc = 0;
    int	    context, status;
    char    buf[255], *p;
    
    vms_argv = (char**)xmalloc((max_files+1)*sizeof(char*));

    vms_argv[new_argc++] = **argv;

    for (i=1; i < *old_argc; i++) {
	if (*argv[0][i] == '-') {   /* switches */
	    if (new_argc < max_files) {
		vms_argv[new_argc++] = argv[0][i];
	    }
	} else {		    /* Files */
	    context = 0;
	    if (find_file_c(argv[0][i], buf, sizeof(buf), &context) & 1 != 1) {
		/* 
	         * Wrong file ?
		 * forward it to gzip
		 */
		if (new_argc < max_files) {
		    vms_argv[new_argc++] = argv[0][i];
		}
	    } else {
		p = (char*)xmalloc(strlen(buf)+1);
		strcpy(p, buf);
		if (new_argc < max_files) {
		    vms_argv[new_argc++] = p;
		}
		while (find_file_c(argv[0][i], buf, 
		       sizeof(buf), &context) & 1 == 1) {
		    p = (char*)xmalloc(strlen(buf)+1);
		    strcpy(p, buf);
		    if (new_argc < max_files) {
			vms_argv[new_argc++] = p;
		    }
		}
	    }
	}
    }
    if (new_argc <= max_files) {
	*old_argc = new_argc;
	vms_argv[new_argc] = NULL;
	*argv = vms_argv;
    } else {
	free(vms_argv); /* the expanded file names should also be freed ... */
	vms_argv = NULL;
	max_files = new_argc + 1;
	vms_expand_args(old_argc, argv);
    }
}


#ifdef __DECC

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


#  define DIAG_FLAG (verbose > 1)

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

/* Durable storage */

static int rms_defaults_known = 0;

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

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

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

/* GETJPI item descriptor set. */

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

int get_rms_defaults()
{
int sts;

/* Get process RMS_DEFAULT values. */

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

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

if (rms_defaults_known > 0)
    {
    /* Set the default values. */

    rms_ext_active = RMS_DEQ_DEFAULT;
    rms_mbc_active = RMS_MBC_DEFAULT;
    rms_mbf_active = RMS_MBF_DEFAULT;

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

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

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

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

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


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

/* Global storage. */

int fopi_id = FOPI_ID;          /* Callback id storage, input. */
int fopo_id = FOPO_ID;          /* Callback id storage, output. */

/* acc_cb() */

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

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

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

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

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

    /* Set the "sequential access only" flag to avoid excessive lock
       time when writing on a file system with highwater marking
       enabled.
    */
    fab-> fab$v_sqo = 1;

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

/* Declare success. */
return 0;
}

/* 2007-12-14 SMS.
 * VMS-specific file open (DECC).
 */

/* Extra open() arguments for VMS with DEC/Compaq/HP C.
 *
 * Use stream access for a binary (compressed) input file, to reduce the
 * influence of different record formats.
 *
 * Be sure to clear the (fake) O_BINARY flag before passing the flags
 * argument to anyone else.
 */
#  define OPEN_ARGS_I_A , "acc", acc_cb, &fopi_id
#  define OPEN_ARGS_I_B , "ctx=stm", "acc", acc_cb, &fopi_id
#  define OPEN_ARGS_O , "acc", acc_cb, &fopo_id

int open_vms( const char *file_spec, int flags, mode_t mode)
{
    if (flags& O_WRONLY)
    {
        /* Output file. */
        return open( file_spec, (flags& (~O_BINARY)), mode OPEN_ARGS_O);
    }
    else
    {
        /* Input file. */
        if (flags& O_BINARY)
        {
            return open( file_spec, (flags& (~O_BINARY)), mode OPEN_ARGS_I_B);
        }
        else
        {
            return open( file_spec, flags, mode OPEN_ARGS_I_A);
        }
    }
}

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

#  ifdef __CRTL_VER

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

#      include <unixlib.h>

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

/* Global storage. */

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

int decc_init_done = -1;

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

/* decc_init()

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

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

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

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

decc_feat_t decc_feat_array[] = {

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

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

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

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

/* LIB$INITIALIZE initialization function. */

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

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

decc_init_done = 1;

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

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

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

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

#      pragma nostandard

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

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

#      pragma extern_model save
int LIB$INITIALIZE( void);
#      pragma extern_model strict_refdef
int dmy_lib$initialize = (int) LIB$INITIALIZE;
#      pragma extern_model restore

#      pragma standard

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

#  endif /* def __CRTL_VER */

#else /* def __DECC */

/* VMS-specific file open (non-DECC). */

int open_vms( const char *file_spec, int flags, mode_t mode)
{
    return open( file_spec, flags, mode);
}

#endif /* def __DECC [else] */

#if __CRTL_VER < 70300000

/* Private utime(). */

#include <errno.h>
#include <time.h>
#include <utime.h>

#include <atrdef.h>
#include <descrip.h>
#include <fibdef.h>
#include <iodef.h>
#include <rms.h>
#include <stsdef.h>
#include <starlet.h>

#ifdef VAXC
#  define FIB_L_ACCTL fib$r_acctl_overlay.fib$l_acctl
#  define FIB_W_DID fib$r_did_overlay.fib$w_did
#  define FIB_W_FID fib$r_fid_overlay.fib$w_fid
#else /* def VAXC */
#  define FIB_L_ACCTL fib$l_acctl
#  define FIB_W_DID fib$w_did
#  define FIB_W_FID fib$w_fid
#endif /* def VAXC [else] */

/* Use <iosbdef.h> if available.  Otherwise declare IOSB here. */

#if !defined( __VAX) && (__CRTL_VER >= 70000000)
#include <iosbdef.h>
#else /* __CRTL_VER >= 70000000 */
typedef struct _iosb {
        unsigned short int iosb$w_status; /* Final I/O status   */
        unsigned short int iosb$w_bcnt; /* 16-bit byte count    */
        unsigned int iosb$l_dev_depend; /* 32-bit dev dependent */
    } IOSB;
#endif /* !defined( __VAX) && (__CRTL_VER >= 70000000) */

/* Ugly work-around for bad type in VAX <atrdef.h>. */

#ifdef __VAX
#define UWA (unsigned int)
#else /* def __VAX */
#define UWA
#endif /* def __VAX */


/* Use long name (NAML) structure only where available.
   (This should be non-VAX with __CRTL_VER >= 70200000.)
*/

#ifdef NAML$C_BID

/* Use long name (NAML) structure. */

#define FAB$L_NAMX fab$l_naml
#define NAMX NAML
#define NAMX$C_MAXRSS NAML$C_MAXRSS
#define NAMX$B_DEV naml$l_long_dev_size
#define NAMX$L_DEV naml$l_long_dev
#define NAMX$L_ESA naml$l_long_expand
#define NAMX$B_ESL naml$l_long_expand_size
#define NAMX$B_ESS naml$l_long_expand_alloc
#define NAMX$W_FID naml$w_fid
#define NAMX$L_RSA naml$l_long_result
#define NAMX$B_RSL naml$l_long_result_size
#define NAMX$B_RSS naml$l_long_result_alloc
#define CC$RMS_NAMX cc$rms_naml

#else /* def NAML$C_BID */

/* Use short name (NAM) structure. */

#define FAB$L_NAMX fab$l_nam
#define NAMX NAM
#define NAMX$C_MAXRSS NAM$C_MAXRSS
#define NAMX$B_DEV nam$b_dev
#define NAMX$L_DEV nam$l_dev
#define NAMX$L_ESA nam$l_esa
#define NAMX$B_ESL nam$b_esl
#define NAMX$B_ESS nam$b_ess
#define NAMX$W_FID nam$w_fid
#define NAMX$L_RSA nam$l_rsa
#define NAMX$B_RSL nam$b_rsl
#define NAMX$B_RSS nam$b_rss
#define CC$RMS_NAMX cc$rms_nam

#endif /* def NAML$C_BID */

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

/* Private utime() helper function. */

/* Action routine for decc$to_vms(), in utime(). */

#if !defined( VAX) && defined( __DECC)

char vms_path[ NAMX$C_MAXRSS+ 1];

int set_vms_name( char *name, int type)
{
   strncpy( vms_path, name, NAMX$C_MAXRSS);
   vms_path[ NAMX$C_MAXRSS] = '\0';
   return 1;
}

#endif /* !defined( VAX) && defined( __DECC) */

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

/* utime() replacement. */

int utime( const char *path, const struct utimbuf *times)
{
time_t utc_unsigned;

int chan, i;
int sts, sts2;

unsigned short int vms_num_vec_time[ 7];
static unsigned int vms_abs_time[ 2];
struct tm *tms;
struct _iosb iosb_q;

/* QIOW item list used to set creation and revision dates. */

struct atrdef ut_atr[ 3] = {
 {sizeof( vms_abs_time), ATR$C_CREDATE, UWA vms_abs_time},
 {sizeof( vms_abs_time), ATR$C_REVDATE, UWA vms_abs_time},
 {0,0,0}};

/* Various RMS structures used for file access. */

struct FAB ut_fab = cc$rms_fab;
struct RAB ut_rab = cc$rms_rab;
struct NAMX ut_namx = CC$RMS_NAMX;
static struct fibdef ut_fib;

/* Device and file name buffers and their descriptors. */

static char dev_namx[ NAMX$C_MAXRSS+ 1];
char esa_namx[ NAMX$C_MAXRSS+ 1];
char rsa_namx[ NAMX$C_MAXRSS+ 1];

struct dsc$descriptor dev_dsc =
 {0, DSC$K_DTYPE_T, DSC$K_CLASS_S, dev_namx};

struct dsc$descriptor fib_dsc =
 {sizeof( ut_fib), DSC$K_DTYPE_T, DSC$K_CLASS_S, (char *) &ut_fib};

/* Accomodate a UNIX-like or VMS-like path name.  If a slash is found in
 * the name, assume that it's UNIX-like, and convert it to VMS form. 
 * Otherwise, use it as-is.
 */

#if !defined( VAX) && defined( __DECC)

if (strchr( path, '/') != NULL)
   {
   sts = decc$to_vms( path, set_vms_name, 0, 0);
   path = vms_path;
   }

#endif /* !defined( VAX) && defined( __DECC) */

/* Install the VMS file specification into the FAB. */

ut_fab.fab$l_fna = (char *) path;
ut_fab.fab$b_fns = (unsigned char) strlen( path);

ut_fab.fab$l_dna = "";
ut_fab.fab$b_dns = 0;

/* Point the FAB to the NAMX. */

ut_fab.FAB$L_NAMX = &ut_namx;

/* Install the name buffers into the NAM. */

ut_namx.NAMX$L_ESA = esa_namx;
ut_namx.NAMX$B_ESL = 0;
ut_namx.NAMX$B_ESS = sizeof( esa_namx)- 1;

ut_namx.NAMX$L_RSA = rsa_namx;
ut_namx.NAMX$B_RSL = 0;
ut_namx.NAMX$B_RSS = sizeof( rsa_namx)- 1;

/* Convert the modification time (UTC time_t) to local "tm" time. */

tms = localtime( &(times-> modtime));

/* Move (translate) "tm" structure local time to VMS vector time. */

if (tms != NULL)
   {
   vms_num_vec_time[ 0] = tms-> tm_year+ 1900;
   vms_num_vec_time[ 1] = tms-> tm_mon+ 1;
   vms_num_vec_time[ 2] = tms-> tm_mday;
   vms_num_vec_time[ 3] = tms-> tm_hour;
   vms_num_vec_time[ 4] = tms-> tm_min;
   vms_num_vec_time[ 5] = tms-> tm_sec;
   vms_num_vec_time[ 6] = 0;  /* centiseconds */

/* Convert VMS vector time to VMS absolute time (quadword). */

   sts = lib$cvt_vectim( vms_num_vec_time, vms_abs_time);

   if ((sts& STS$M_SEVERITY) == STS$K_SUCCESS)
      {
/* Parse the file specification. */

      sts = sys$parse( &ut_fab, 0, 0);

      if ((sts& STS$M_SEVERITY) == STS$K_SUCCESS)
         {
/* Locate the file. (Gets the FID.) */

         sts = sys$search( &ut_fab, 0, 0);

         if ((sts& STS$M_SEVERITY) == STS$K_SUCCESS)
            {
/* Form the device name descriptor. */

            dev_dsc.dsc$w_length = ut_namx.NAMX$B_DEV;
            dev_dsc.dsc$a_pointer = (char *) ut_namx.NAMX$L_DEV;

/* Assign a channel to the disk device. */

            sts = sys$assign( &dev_dsc, &chan, 0, 0);

            if ((sts& STS$M_SEVERITY) == STS$K_SUCCESS)
               {
/* Move the FID (and not the DID) into the FIB. */

               memset( (void *) &ut_fib, 0, sizeof( ut_fib));

               for (i = 0; i < 3; i++)
                  {
                  ut_fib.FIB_W_FID[ i] = ut_namx.NAMX$W_FID[ i];
                  ut_fib.FIB_W_DID[ i] = 0;
                  }

/* Prevent this QIOW from setting the revision time to now. */

               ut_fib.FIB_L_ACCTL = FIB$M_NORECORD;

/* Set the file dates. */

               sts = sys$qiow( 0,
                               chan,
                               IO$_MODIFY,
                               &iosb_q,
                               0,
                               0,
                               &fib_dsc,
                               0,
                               0,
                               0,
                               ut_atr,
                               0);

               if ((sts& STS$M_SEVERITY) == STS$K_SUCCESS)
                  {
                   sts = iosb_q.iosb$w_status;
                  }
               sts2 = sys$dassgn( chan);

               if ((sts& STS$M_SEVERITY) == STS$K_SUCCESS)
                  {
                  sts = sts2;
                  }
               }
            }
         }
      }
   }

/* Convert successful VMS status to zero = success status.
   If failure, set errno and vaxc$errno, and return -1 = failure status.
*/

if ((sts& STS$M_SEVERITY) == STS$K_SUCCESS)
   {
   sts = 0;
   }
else
   {
   errno = EVMSERR;
   vaxc$errno = sts;
   sts = -1;
   }

return sts;
}

#endif /* __CRTL_VER < 70300000 */
