summaryrefslogtreecommitdiff
path: root/vms/vmszip.c
diff options
context:
space:
mode:
Diffstat (limited to 'vms/vmszip.c')
-rw-r--r--vms/vmszip.c1444
1 files changed, 1444 insertions, 0 deletions
diff --git a/vms/vmszip.c b/vms/vmszip.c
new file mode 100644
index 0000000..2dae718
--- /dev/null
+++ b/vms/vmszip.c
@@ -0,0 +1,1444 @@
+/*
+ Copyright (c) 1990-2007 Info-ZIP. All rights reserved.
+
+ See the accompanying file LICENSE, version 2007-Mar-4 or later
+ (the contents of which are also included in zip.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
+*/
+
+/* 2005-02-14 SMS.
+ Added some ODS5 support.
+ Use longer name structures in NAML, where available.
+ Locate special characters mindful of "^" escapes.
+ Replaced compile-time case preservation (VMS_PRESERVE_CASE macro)
+ with command-line-specified case preservation (vms_case_x
+ variables).
+ Prototyped all functions.
+ Removed "#ifndef UTIL", as no one should be compiling it that way.
+*/
+
+#include "zip.h"
+#include "vmsmunch.h"
+#include "vms.h"
+
+#include <ctype.h>
+#include <time.h>
+#include <unixlib.h>
+
+/* Judge availability of str[n]casecmp() in C RTL.
+ (Note: This must follow a "#include <decc$types.h>" in something to
+ ensure that __CRTL_VER is as defined as it will ever be. DEC C on
+ VAX may not define it itself.)
+*/
+#ifdef __CRTL_VER
+#if __CRTL_VER >= 70000000
+#define HAVE_STRCASECMP
+#endif /* __CRTL_VER >= 70000000 */
+#endif /* def __CRTL_VER */
+
+#ifdef HAVE_STRCASECMP
+#include <strings.h> /* str[n]casecmp() */
+#endif /* def HAVE_STRCASECMP */
+
+#include <dvidef.h>
+#include <lib$routines.h>
+#include <ssdef.h>
+#include <stsdef.h>
+#include <starlet.h>
+
+/* Directory file type with version, and its strlen(). */
+#define DIR_TYPE_VER ".DIR;1"
+#define DIR_TYPE_VER_LEN (sizeof( DIR_TYPE_VER)- 1)
+
+/* Extra malloc() space in names for cutpath(). (May have to change
+ ".FOO]" to "]FOO.DIR;1".)
+*/
+#define DIR_PAD (DIR_TYPE_VER_LEN- 1)
+
+/* Hex digit table. */
+
+char hex_digit[ 16] = {
+ '0', '1', '2', '3', '4', '5', '6', '7',
+ '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'
+};
+
+/* Character property table for (re-)escaping ODS5 extended file names.
+ Note that this table ignore Unicode, and does not identify invalid
+ characters.
+
+ ODS2 valid characters: 0-9 A-Z a-z $ - _
+
+ ODS5 Invalid characters:
+ C0 control codes (0x00 to 0x1F inclusive)
+ Asterisk (*)
+ Question mark (?)
+
+ ODS5 Invalid characters only in VMS V7.2 (which no one runs, right?):
+ Double quotation marks (")
+ Backslash (\)
+ Colon (:)
+ Left angle bracket (<)
+ Right angle bracket (>)
+ Slash (/)
+ Vertical bar (|)
+
+ Characters escaped by "^":
+ SP ! # % & ' ( ) + , . ; = @ [ ] ^ ` { } ~
+
+ Either "^_" or "^ " is accepted as a space. Period (.) is a special
+ case. Note that un-escaped < and > can also confuse a directory
+ spec.
+
+ Characters put out as ^xx:
+ 7F (DEL)
+ 80-9F (C1 control characters)
+ A0 (nonbreaking space)
+ FF (Latin small letter y diaeresis)
+
+ Other cases:
+ Unicode: "^Uxxxx", where "xxxx" is four hex digits.
+
+ Property table values:
+ Normal escape: 1
+ Space: 2
+ Dot: 4
+ Hex-hex escape: 8
+ -------------------
+ Hex digit: 64
+*/
+
+unsigned char char_prop[ 256] = {
+
+/* NUL SOH STX ETX EOT ENQ ACK BEL BS HT LF VT FF CR SO SI */
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+
+/* DLE DC1 DC2 DC3 DC4 NAK SYN ETB CAN EM SUB ESC FS GS RS US */
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+
+/* SP ! " # $ % & ' ( ) * + , - . / */
+ 2, 1, 0, 1, 0, 1, 1, 1, 1, 1, 0, 1, 1, 0, 4, 0,
+
+/* 0 1 2 3 4 5 6 7 8 9 : ; < = > ? */
+ 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 0, 1, 1, 1, 1, 1,
+
+/* @ A B C D E F G H I J K L M N O */
+ 1, 64, 64, 64, 64, 64, 64, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+
+/* P Q R S T U V W X Y Z [ \ ] ^ _ */
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 1, 0,
+
+/* ` a b c d e f g h i j k l m n o */
+ 1, 64, 64, 64, 64, 64, 64, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+
+/* p q r s t u v w x y z { | } ~ DEL */
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 1, 8,
+
+ 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8,
+ 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8,
+ 8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8
+};
+
+/* The C RTL from OpenVMS 7.0 and newer supplies POSIX compatible versions of
+ * opendir() et al. Thus, we have to use other names in our private code for
+ * directory scanning to prevent symbol name conflicts at link time.
+ * For now, we do not use the library supplied "dirent.h" functions, since
+ * our private implementation provides some functionality which may not be
+ * present in the library versions. For example:
+ * ==> zopendir("DISK:[DIR.SUB1]SUB2.DIR") scans "DISK:[DIR.SUB1.SUB2]".
+ */
+
+typedef struct zdirent {
+ int d_wild; /* flag for wildcard vs. non-wild */
+ struct FAB fab;
+ struct NAM_STRUCT nam;
+ char d_qualwildname[ NAM_MAXRSS+ 1];
+ char d_name[ NAM_MAXRSS+ 1];
+} zDIR;
+
+extern char *label;
+local ulg label_time = 0;
+local ulg label_mode = 0;
+local time_t label_utim = 0;
+
+local int relative_dir_s = 0; /* Relative directory spec. */
+
+/* Local functions */
+local void vms_wild OF((char *, zDIR *));
+local zDIR *zopendir OF((ZCONST char *));
+local char *readd OF((zDIR *));
+local char *strlower OF((char *));
+local char *strupper OF((char *));
+
+/* 2004-09-25 SMS.
+ str[n]casecmp() replacement for old C RTL.
+ Assumes a prehistorically incompetent toupper().
+*/
+#ifndef HAVE_STRCASECMP
+
+int strncasecmp( char *s1, char *s2, size_t n)
+{
+ /* Initialization prepares for n == 0. */
+ char c1 = '\0';
+ char c2 = '\0';
+
+ while (n-- > 0)
+ {
+ /* Set c1 and c2. Convert lower-case characters to upper-case. */
+ if (islower( c1 = *s1))
+ c1 = toupper( c1);
+
+ if (islower( c2 = *s2))
+ c2 = toupper( c2);
+
+ /* Quit at inequality or NUL. */
+ if ((c1 != c2) || (c1 == '\0'))
+ break;
+
+ s1++;
+ s2++;
+ }
+return ((unsigned int) c1- (unsigned int) c2);
+}
+
+#ifndef UINT_MAX
+#define UINT_MAX 4294967295U
+#endif
+
+#define strcasecmp( s1, s2) strncasecmp( s1, s2, UINT_MAX)
+
+#endif /* ndef HAVE_STRCASECMP */
+
+
+/* 2004-09-27 SMS.
+ eat_carets().
+
+ Delete ODS5 extended file name escape characters ("^") in the
+ original buffer.
+ Note that the current scheme does not handle all EFN cases, but it
+ could be made more complicated.
+*/
+
+local void eat_carets( char *str)
+/* char *str; Source pointer. */
+{
+ char *strd; /* Destination pointer. */
+ char hdgt;
+ unsigned char uchr;
+ unsigned char prop;
+
+ /* Skip ahead to the first "^", if any. */
+ while ((*str != '\0') && (*str != '^'))
+ str++;
+
+ /* If no caret was found, quit early. */
+ if (*str != '\0')
+ {
+ /* Shift characters leftward as carets are found. */
+ strd = str;
+ while (*str != '\0')
+ {
+ uchr = *str;
+ if (uchr == '^')
+ {
+ /* Found a caret. Skip it, and check the next character. */
+ uchr = *(++str);
+ prop = char_prop[ uchr];
+ if (prop& 64)
+ {
+ /* Hex digit. Get char code from this and next hex digit. */
+ if (uchr <= '9')
+ {
+ hdgt = uchr- '0'; /* '0' - '9' -> 0 - 9. */
+ }
+ else
+ {
+ hdgt = ((uchr- 'A')& 7)+ 10; /* [Aa] - [Ff] -> 10 - 15. */
+ }
+ hdgt <<= 4; /* X16. */
+ uchr = *(++str); /* Next char must be hex digit. */
+ if (uchr <= '9')
+ {
+ uchr = hdgt+ uchr- '0';
+ }
+ else
+ {
+ uchr = hdgt+ ((uchr- 'A')& 15)+ 10;
+ }
+ }
+ else if (uchr == '_')
+ {
+ /* Convert escaped "_" to " ". */
+ uchr = ' ';
+ }
+ else if (uchr == '/')
+ {
+ /* Convert escaped "/" (invalid Zip) to "?" (invalid VMS). */
+ uchr = '?';
+ }
+ /* Else, not a hex digit. Must be a simple escaped character
+ (or Unicode, which is not yet handled here).
+ */
+ }
+ /* Else, not a caret. Use as-is. */
+ *strd = uchr;
+
+ /* Advance destination and source pointers. */
+ strd++;
+ str++;
+ }
+ /* Terminate the destination string. */
+ *strd = '\0';
+ }
+}
+
+
+/* 2007-05-22 SMS.
+ * explicit_dev().
+ *
+ * Determine if an explicit device name is present in a (VMS) file
+ * specification.
+ */
+local int explicit_dev( char *file_spec)
+{
+ int sts;
+ struct FAB fab; /* FAB. */
+ struct NAM_STRUCT nam; /* NAM[L]. */
+
+ /* Initialize the FAB and NAM[L], and link the NAM[L] to the FAB. */
+ nam = CC_RMS_NAM;
+ fab = cc$rms_fab;
+ fab.FAB_NAM = &nam;
+
+ /* Point the FAB/NAM[L] fields to the actual name and default name. */
+
+#ifdef NAML$C_MAXRSS
+
+ fab.fab$l_dna = (char *) -1; /* Using NAML for default name. */
+ fab.fab$l_fna = (char *) -1; /* Using NAML for file name. */
+
+#endif /* def NAML$C_MAXRSS */
+
+ /* File name. */
+ FAB_OR_NAML( fab, nam).FAB_OR_NAML_FNA = file_spec;
+ FAB_OR_NAML( fab, nam).FAB_OR_NAML_FNS = strlen( file_spec);
+
+ nam.NAM_NOP = NAM_M_SYNCHK; /* Syntax-only analysis. */
+ sts = sys$parse( &fab, 0, 0); /* Parse the file spec. */
+
+ /* Device found = $PARSE success and "device was explicit" flag. */
+ return (((sts& STS$M_SEVERITY) == STS$M_SUCCESS) &&
+ ((nam.NAM_FNB& NAM_M_EXP_DEV) != 0));
+}
+
+
+/* 2005-02-04 SMS.
+ find_dir().
+
+ Find directory boundaries in an ODS2 or ODS5 file spec.
+ Returns length (zero if no directory, negative if error),
+ and sets "start" argument to first character (typically "[") location.
+
+ No one will care about the details, but the return values are:
+
+ 0 No dir.
+ -2 [, no end. -3 <, no end.
+ -4 [, multiple start. -5 <, multiple start.
+ -8 ], no start. -9 >, no start.
+ -16 ], wrong end. -17 >, wrong end.
+ -32 ], multiple end. -33 >, multiple end.
+
+ Note that the current scheme handles only simple EFN cases, but it
+ could be made more complicated.
+*/
+int find_dir( char *file_spec, char **start)
+{
+ char *cp;
+ char chr;
+
+ char *end_tmp = NULL;
+ char *start_tmp = NULL;
+ int lenth = 0;
+
+ for (cp = file_spec; cp < file_spec+ strlen( file_spec); cp++)
+ {
+ chr = *cp;
+ if (chr == '^')
+ {
+ /* Skip ODS5 extended name escaped characters. */
+ cp++;
+ /* If escaped char is a hex digit, skip the second hex digit, too. */
+ if (char_prop[ (unsigned char) *cp]& 64)
+ cp++;
+ }
+ else if (chr == '[')
+ {
+ /* Found start. */
+ if (start_tmp == NULL)
+ {
+ /* First time. Record start location. */
+ start_tmp = cp;
+ /* Error if no end. */
+ lenth = -2;
+ }
+ else
+ {
+ /* Multiple start characters. */
+ lenth = -4;
+ break;
+ }
+ }
+ else if (chr == '<')
+ {
+ /* Found start. */
+ if (start_tmp == NULL)
+ {
+ /* First time. Record start location. */
+ start_tmp = cp;
+ /* Error if no end. */
+ lenth = -3;
+ }
+ else
+ {
+ /* Multiple start characters. */
+ lenth = -5;
+ break;
+ }
+ }
+ else if (chr == ']')
+ {
+ /* Found end. */
+ if (end_tmp == NULL)
+ {
+ /* First time. */
+ if (lenth == 0)
+ {
+ /* End without start. */
+ lenth = -8;
+ break;
+ }
+ else if (lenth != -2)
+ {
+ /* Wrong kind of end. */
+ lenth = -16;
+ break;
+ }
+ /* End ok. Record end location. */
+ end_tmp = cp;
+ lenth = end_tmp+ 1- start_tmp;
+ /* Could break here, ignoring excessive end characters. */
+ }
+ else
+ {
+ /* Multiple end characters. */
+ lenth = -32;
+ break;
+ }
+ }
+ else if (chr == '>')
+ {
+ /* Found end. */
+ if (end_tmp == NULL)
+ {
+ /* First time. */
+ if (lenth == 0)
+ {
+ /* End without start. */
+ lenth = -9;
+ break;
+ }
+ else if (lenth != -3)
+ {
+ /* Wrong kind of end. */
+ lenth = -17;
+ break;
+ }
+ /* End ok. Record end location. */
+ end_tmp = cp;
+ lenth = end_tmp+ 1- start_tmp;
+ /* Could break here, ignoring excessive end characters. */
+ }
+ else
+ {
+ /* Multiple end characters. */
+ lenth = -33;
+ break;
+ }
+ }
+ }
+
+ /* If both start and end were found,
+ then set result pointer where safe.
+ */
+ if (lenth > 0)
+ {
+ if (start != NULL)
+ {
+ *start = start_tmp;
+ }
+ }
+ return lenth;
+}
+
+
+/* 2005-02-08 SMS.
+ file_sys_type().
+
+ Determine the file system type for the (VMS) path name argument.
+*/
+local int file_sys_type( char *path)
+{
+ int acp_code;
+
+#ifdef DVI$C_ACP_F11V5
+
+/* Should know about ODS5 file system. Do actual check.
+ (This should be non-VAX with __CRTL_VER >= 70200000.)
+*/
+
+ int sts;
+
+ struct dsc$descriptor_s dev_descr =
+ { 0, DSC$K_DTYPE_T, DSC$K_CLASS_S, 0 };
+
+ /* Load path argument into device descriptor. */
+ dev_descr.dsc$a_pointer = path;
+ dev_descr.dsc$w_length = strlen( dev_descr.dsc$a_pointer);
+
+ /* Get filesystem type code.
+ (Text results for this item code have been unreliable.)
+ */
+ sts = lib$getdvi( &((int) DVI$_ACPTYPE), 0, &dev_descr, &acp_code, 0, 0);
+
+ if ((sts & STS$M_SUCCESS) != STS$K_SUCCESS)
+ {
+ acp_code = -1;
+ }
+
+#else /* def DVI$C_ACP_F11V5 */
+
+/* Too old for ODS5 file system. Must be ODS2. */
+
+ acp_code = DVI$C_ACP_F11V2;
+
+#endif /* def DVI$C_ACP_F11V5 */
+
+ return acp_code;
+}
+
+/*---------------------------------------------------------------------------
+
+ _vms_findfirst() and _vms_findnext(), based on public-domain DECUS C
+ fwild() and fnext() routines (originally written by Martin Minow, poss-
+ ibly modified by Jerry Leichter for bintnxvms.c), were written by Greg
+ Roelofs and are still in the public domain. Routines approximate the
+ behavior of MS-DOS (MSC and Turbo C) findfirst and findnext functions.
+
+ 2005-01-04 SMS.
+ Changed to use NAML instead of NAM, where available.
+
+ ---------------------------------------------------------------------------*/
+
+static char wild_version_part[10]="\0";
+
+local void vms_wild( char *p, zDIR *d)
+{
+ /*
+ * Do wildcard setup.
+ */
+ /* Set up the FAB and NAM[L] blocks. */
+ d->fab = cc$rms_fab; /* Initialize FAB. */
+ d->nam = CC_RMS_NAM; /* Initialize NAM[L]. */
+
+ d->fab.FAB_NAM = &d->nam; /* FAB -> NAM[L] */
+
+#ifdef NAML$C_MAXRSS
+
+ d->fab.fab$l_dna =(char *) -1; /* Using NAML for default name. */
+ d->fab.fab$l_fna = (char *) -1; /* Using NAML for file name. */
+
+#endif /* def NAML$C_MAXRSS */
+
+ /* Argument file name and length. */
+ d->FAB_OR_NAML( fab, nam).FAB_OR_NAML_FNA = p;
+ d->FAB_OR_NAML( fab, nam).FAB_OR_NAML_FNS = strlen(p);
+
+#define DEF_DEVDIR "SYS$DISK:[]"
+
+ /* Default file spec and length. */
+ d->FAB_OR_NAML( fab, nam).FAB_OR_NAML_DNA = DEF_DEVDIR;
+ d->FAB_OR_NAML( fab, nam).FAB_OR_NAML_DNS = sizeof( DEF_DEVDIR)- 1;
+
+ d->nam.NAM_ESA = d->d_qualwildname; /* qualified wild name */
+ d->nam.NAM_ESS = NAM_MAXRSS; /* max length */
+ d->nam.NAM_RSA = d->d_name; /* matching file name */
+ d->nam.NAM_RSS = NAM_MAXRSS; /* max length */
+
+ /* parse the file name */
+ if (sys$parse(&d->fab) != RMS$_NORMAL)
+ return;
+ /* Does this replace d->fab.fab$l_fna with a new string in its own space?
+ I sure hope so, since p is free'ed before this routine returns. */
+
+ /* have qualified wild name (i.e., disk:[dir.subdir]*.*); null-terminate
+ * and set wild-flag */
+ d->d_qualwildname[d->nam.NAM_ESL] = '\0';
+ d->d_wild = (d->nam.NAM_FNB & NAM$M_WILDCARD)? 1 : 0; /* not used... */
+#ifdef DEBUG
+ fprintf(mesg, " incoming wildname: %s\n", p);
+ fprintf(mesg, " qualified wildname: %s\n", d->d_qualwildname);
+#endif /* DEBUG */
+}
+
+local zDIR *zopendir( ZCONST char *n)
+/* ZCONST char *n; directory to open */
+/* Start searching for files in the VMS directory n */
+{
+ char *c; /* scans VMS path */
+ zDIR *d; /* malloc'd return value */
+ int m; /* length of name */
+ char *p; /* malloc'd temporary string */
+
+ if ((d = (zDIR *)malloc(sizeof(zDIR))) == NULL ||
+ (p = malloc((m = strlen(n)) + 4)) == NULL) {
+ if (d != NULL) free((zvoid *)d);
+ return NULL;
+ }
+ /* Directory may be in form "[DIR.SUB1.SUB2]" or "[DIR.SUB1]SUB2.DIR;1".
+ If latter, convert to former.
+ 2005-01-31 SMS. Changed to require ";1", as VMS does, which
+ simplified the code slightly, too. Note that ODS5 allows ".DIR" in
+ any case (upper, lower, mixed).
+ */
+ if ((m > 0) && (*(c = strcpy(p,n)+m-1) != ']'))
+ {
+ if ((c- p < DIR_TYPE_VER_LEN) ||
+ strcasecmp((c+ 1- DIR_TYPE_VER_LEN), DIR_TYPE_VER))
+ {
+ free((zvoid *)d); free((zvoid *)p);
+ return NULL;
+ }
+ c -= 4; /* The "D". */
+ *c-- = '\0'; /* terminate at "DIR;1" */
+ *c = ']'; /* "." --> "]" */
+
+ /* Replace the formerly last "]" with ".".
+ For ODS5, ignore "^]".
+ */
+ while ((c > p) && ((*--c != ']') || (*(c- 1) == '^')))
+ ;
+ *c = '.'; /* "]" --> "." */
+ }
+ strcat(p, "*.*");
+ strcat(p, wild_version_part);
+ vms_wild(p, d); /* set up wildcard */
+ free((zvoid *)p);
+ return d;
+}
+
+local char *readd( zDIR *d)
+/* zDIR *d; directory stream to read from */
+/* Return a pointer to the next name in the directory stream d, or NULL if
+ no more entries or an error occurs. */
+{
+ int r; /* return code */
+
+ do {
+ d->fab.fab$w_ifi = 0; /* internal file index: what does this do? */
+/*
+ 2005-02-04 SMS.
+
+ From the docs:
+
+ Note that you must close the file before invoking the Search
+ service (FAB$W_IFI must be 0).
+
+ The same is true for PARSE. Most likely, it's cleared by setting
+ "fab = cc$rms_fab", and left that way, so clearing it here may very
+ well be pointless. (I think it is, and I've never seen it explicitly
+ cleared elsewhere, but I haven't tested it everywhere either.)
+*/
+ /* get next match to possible wildcard */
+ if ((r = sys$search(&d->fab)) == RMS$_NORMAL)
+ {
+ d->d_name[d->nam.NAM_RSL] = '\0'; /* null terminate */
+ return (char *)d->d_name; /* OK */
+ }
+ } while (r == RMS$_PRV);
+ return NULL;
+}
+
+
+int wild( char *p)
+/* char *p; path/pattern to match */
+/* Expand the pattern based on the contents of the file system.
+ Return an error code in the ZE_ class.
+ Note that any command-line file argument may need wildcard expansion,
+ so all user-specified constituent file names pass through here.
+*/
+{
+ zDIR *d; /* stream for reading directory */
+ char *e; /* name found in directory */
+ int f; /* true if there was a match */
+
+ int dir_len; /* Length of the directory part of the name. */
+ char *dir_start; /* First character of the directory part. */
+
+ /* special handling of stdin request */
+ if (strcmp(p, "-") == 0) /* if compressing stdin */
+ return newname(p, 0, 0);
+
+ /* Determine whether this name has an absolute or relative directory
+ spec. It's relative if there is no directory, or if the directory
+ has a leading dot ("[.").
+ */
+ dir_len = find_dir( p, &dir_start);
+ relative_dir_s = ((dir_len <= 0)? 1 : (dir_start[ 1] == '.'));
+
+ /* Search given pattern for matching names */
+ if ((d = (zDIR *)malloc(sizeof(zDIR))) == NULL)
+ return ZE_MEM;
+ vms_wild(p, d); /* pattern may be more than just directory name */
+
+ /*
+ * Save version specified by user to use in recursive drops into
+ * subdirectories.
+ */
+ strncpy(wild_version_part, d->nam.NAM_L_VER, d->nam.NAM_B_VER);
+ wild_version_part[d->nam.NAM_B_VER] = '\0';
+
+ f = 0;
+ while ((e = readd(d)) != NULL) /* "dosmatch" is already built in */
+ if (procname(e, 0) == ZE_OK)
+ f = 1;
+ free(d);
+
+ /* Done */
+ return f ? ZE_OK : ZE_MISS;
+}
+
+int procname( char *n, int caseflag)
+/* char *n; name to process */
+/* int caseflag; true to force case-sensitive match */
+/* Process a name or sh expression to operate on (or exclude). Return
+ an error code in the ZE_ class. */
+{
+ zDIR *d; /* directory stream from zopendir() */
+ char *e; /* pointer to name from readd() */
+ int m; /* matched flag */
+ char *p; /* path for recursion */
+ struct stat s; /* result of stat() */
+ struct zlist far *z; /* steps through zfiles list */
+
+ if (strcmp(n, "-") == 0) /* if compressing stdin */
+ return newname(n, 0, caseflag);
+ else if (LSSTAT(n, &s)
+#if defined(__TURBOC__) || defined(VMS) || defined(__WATCOMC__)
+ /* For these 3 compilers, stat() succeeds on wild card names! */
+ || isshexp(n)
+#endif
+ )
+ {
+ /* Not a file or directory--search for shell expression in zip file */
+ if (caseflag) {
+ p = malloc(strlen(n) + 1);
+ if (p != NULL)
+ strcpy(p, n);
+ } else
+ p = ex2in(n, 0, (int *)NULL); /* shouldn't affect matching chars */
+ m = 1;
+ for (z = zfiles; z != NULL; z = z->nxt) {
+ if (MATCH(p, z->iname, caseflag))
+ {
+ z->mark = pcount ? filter(z->zname, caseflag) : 1;
+ if (verbose)
+ fprintf(mesg, "zip diagnostic: %scluding %s\n",
+ z->mark ? "in" : "ex", z->name);
+ m = 0;
+ }
+ }
+ free((zvoid *)p);
+ return m ? ZE_MISS : ZE_OK;
+ }
+
+ /* Live name--use if file, recurse if directory */
+ if ((s.st_mode & S_IFDIR) == 0)
+ {
+ /* add or remove name of file */
+ if ((m = newname(n, 0, caseflag)) != ZE_OK)
+ return m;
+ } else {
+ if (dirnames && (m = newname(n, 1, caseflag)) != ZE_OK) {
+ return m;
+ }
+ /* recurse into directory */
+ if (recurse && (d = zopendir(n)) != NULL)
+ {
+ while ((e = readd(d)) != NULL) {
+ if ((m = procname(e, caseflag)) != ZE_OK) /* recurse on name */
+ {
+ free(d);
+ return m;
+ }
+ }
+ free(d);
+ }
+ } /* (s.st_mode & S_IFDIR) == 0) */
+ return ZE_OK;
+}
+
+/* 2004-09-24 SMS.
+ Cuter strlower() and strupper() functions.
+*/
+
+local char *strlower( char *s)
+/* Convert all uppercase letters to lowercase in string s */
+{
+ for ( ; *s != '\0'; s++)
+ if (isupper( *s))
+ *s = tolower( *s);
+
+ return s;
+}
+
+local char *strupper( char *s)
+/* Convert all lowercase letters to uppercase in string s */
+{
+ for ( ; *s != '\0'; s++)
+ if (islower( *s))
+ *s = toupper( *s);
+
+ return s;
+}
+
+char *ex2in( char *x, int isdir, int *pdosflag)
+/* char *x; external file name */
+/* int isdir; input: x is a directory */
+/* int *pdosflag; output: force MSDOS file attributes? */
+
+/* Convert the external file name to a zip file name, returning the
+ malloc'ed string or NULL if not enough memory.
+
+ 2005-02-09 SMS.
+ Added some ODS5 support.
+
+ Note that if we were really clever, we'd save the truncated original
+ file name for later use as "iname", instead of running the de-escaped
+ product back through in2ex() to recover it later.
+
+ 2005-11-13 SMS.
+ Changed to translate "[..." into enough "/" characters to cause
+ in2ex() to reconstruct it. This should not be needed, however, as
+ pattern matching really should avoid ex2in() and in2ex().
+*/
+{
+ char *n; /* Internal file name (malloc'ed). */
+ char *nn; /* Temporary "n"-like pointer. */
+ char *ext_dir_and_name; /* External dir]name (less "dev:["). */
+ char chr; /* Temporary character storage. */
+ int dosflag;
+ int down_case; /* Resultant down-case flag. */
+ int dir_len; /* Directory spec length. */
+ int ods_level; /* File system type. */
+
+ dosflag = dosify; /* default for non-DOS and non-OS/2 */
+
+ /* Locate the directory part of the external name. */
+ dir_len = find_dir( x, &ext_dir_and_name);
+ if (dir_len <= 0)
+ {
+ /* Directory not found. Use whole external name. */
+ ext_dir_and_name = x;
+ }
+ else if (pathput)
+ {
+ /* Include directory. */
+ if (ext_dir_and_name[ 1] == '.')
+ {
+ /* Relative path. If not a directory-depth wildcard, then drop
+ first "[." (or "<."). If "[..." (or "<..."), then preserve all
+ characters, including the first "[" (or "<") for special
+ handling below.
+ */
+ if ((ext_dir_and_name[ 2] != '.') || (ext_dir_and_name[ 3] != '.'))
+ {
+ /* Normal relative path. Drop first "[." (or "<."). */
+ dir_len -= 2;
+ ext_dir_and_name += 2;
+ }
+ }
+ else
+ {
+ /* Absolute path. Skip first "[" (or "<"). */
+ dir_len -= 1;
+ ext_dir_and_name += 1;
+
+ /* 2007-04-26 SMS.
+ Skip past "000000." or "000000]" (or "000000>"), which should
+ not be stored in the archive. This arises, for example, with
+ "zip -r archive [000000]foo.dir"
+ */
+#define MFD "000000"
+
+ if ((strncmp( ext_dir_and_name, MFD, strlen( MFD)) == 0) &&
+ ((ext_dir_and_name[ 6] == '.') ||
+ (ext_dir_and_name[ 6] == ']') ||
+ (ext_dir_and_name[ 6] == '>')))
+ {
+ dir_len -= 7;
+ ext_dir_and_name += 7;
+ }
+ }
+ }
+ else
+ {
+ /* Junking paths. Skip the whole directory spec. */
+ ext_dir_and_name += dir_len;
+ dir_len = 0;
+ }
+
+ /* Malloc space for internal name and copy it. */
+ if ((n = malloc(strlen( ext_dir_and_name)+ 1)) == NULL)
+ return NULL;
+ strcpy( n, ext_dir_and_name);
+
+ /* Convert VMS directory separators (".") to "/". */
+ if (dir_len > 0)
+ {
+ for (nn = n; nn < n+ dir_len; nn++)
+ {
+ chr = *nn;
+ if (chr == '^')
+ {
+ /* Skip ODS5 extended name escaped characters. */
+ nn++;
+ /* If escaped char is a hex digit, skip the second hex digit, too. */
+ if (char_prop[ (unsigned char) *nn]& 64)
+ nn++;
+ }
+ else if ((chr == '.') || ((nn == n) && ((chr == '[') || (chr == '<'))))
+ {
+ /* Convert VMS directory separator (".", or initial "[" or "<"
+ of "[..." or "<...") to "/".
+ */
+ *nn = '/';
+ }
+ }
+ /* Replace directory end character (typically "]") with "/". */
+ n[ dir_len- 1] = '/';
+ }
+
+ /* If relative path, then strip off the current directory. */
+ if (relative_dir_s)
+ {
+ char cwd[ NAM_MAXRSS+ 1];
+ char *cwd_dir_only;
+ char *q;
+ int cwd_dir_only_len;
+
+ q = getcwd( cwd, (sizeof( cwd)- 1));
+
+ /* 2004-09-24 SMS.
+ With SET PROCESSS /PARSE = EXTENDED, getcwd() can return a
+ mixed-case result, confounding the comparisons below with an
+ all-uppercase name in "n". Always use a case-insensitive
+ comparison around here.
+ */
+
+ /* Locate the directory part of the external name. */
+ dir_len = find_dir( q, &cwd_dir_only);
+ if (dir_len > 0)
+ {
+ /* Skip first "[" (or "<"). */
+ cwd_dir_only++;
+ /* Convert VMS directory separators (".") to "/". */
+ for (q = cwd_dir_only; q < cwd_dir_only+ dir_len; q++)
+ {
+ chr = *q;
+ if (chr == '^')
+ {
+ /* Skip ODS5 extended name escaped characters. */
+ q++;
+ /* If escaped char is a hex digit, skip the second hex digit, too. */
+ if (char_prop[ (unsigned char) *q]& 64)
+ q++;
+ }
+ else if (chr == '.')
+ {
+ /* Convert VMS directory separator (".") to "/". */
+ *q = '/';
+ }
+ }
+ /* Replace directory end character (typically "]") with "/". */
+ cwd_dir_only[ dir_len- 2] = '/';
+ }
+
+ /* If the slash-converted cwd matches the front of the internal
+ name, then shuffle the remainder of the internal name to the
+ beginning of the internal name storage.
+
+ Because we already know that the path is relative, this test may
+ always succeed.
+ */
+ cwd_dir_only_len = strlen( cwd_dir_only);
+ if (strncasecmp( n, cwd_dir_only, cwd_dir_only_len) == 0)
+ {
+ nn = n+ cwd_dir_only_len;
+ q = n;
+ while (*q++ = *nn++);
+ }
+ } /* (relative_dir_s) */
+
+ /* 2007-05-22 SMS.
+ * If a device name is present, assume that it's a real (VMS) file
+ * specification, and do down-casing according to the ODS2 or ODS5
+ * down-casing policy. If no device name is present, assume that it's
+ * a pattern ("-i", ...), and do no down-casing here. (Case
+ * sensitivity in patterns is handled elsewhere.)
+ */
+ if (explicit_dev( x))
+ {
+ /* If ODS5 is possible, do complicated down-case check.
+
+ Note that the test for ODS2/ODS5 is misleading and over-broad.
+ Here, "ODS2" includes anything from DVI$C_ACP_F11V1 (=1, ODS1) up
+ to (but not including) DVI$C_ACP_F11V5 (= 11, DVI$C_ACP_F11V5),
+ while "ODS5" includes anything from DVI$C_ACP_F11V5 on up. See
+ DVIDEF.H.
+ */
+
+#if defined( DVI$C_ACP_F11V5) && defined( NAML$C_MAXRSS)
+
+ /* Check options and/or ODS level for down-case or preserve case. */
+ down_case = 0; /* Assume preserve case. */
+ if ((vms_case_2 <= 0) && (vms_case_5 < 0))
+ {
+ /* Always down-case. */
+ down_case = 1;
+ }
+ else if ((vms_case_2 <= 0) || (vms_case_5 < 0))
+ {
+ /* Down-case depending on ODS level. (Use (full) external name.) */
+ ods_level = file_sys_type( x);
+
+ if (ods_level > 0)
+ {
+ /* Valid ODS level. (Name (full) contains device.)
+ * Down-case accordingly.
+ */
+ if (((ods_level < DVI$C_ACP_F11V5) && (vms_case_2 <= 0)) ||
+ ((ods_level >= DVI$C_ACP_F11V5) && (vms_case_5 < 0)))
+ {
+ /* Down-case for this ODS level. */
+ down_case = 1;
+ }
+ }
+ }
+
+#else /* defined( DVI$C_ACP_F11V5) && defined( NAML$C_MAXRSS) */
+
+/* No case-preserved names are possible (VAX). Do simple down-case check. */
+
+ down_case = (vms_case_2 <= 0);
+
+#endif /* defined( DVI$C_ACP_F11V5) && defined( NAML$C_MAXRSS) [else] */
+
+ /* If down-casing, convert to lower case. */
+ if (down_case != 0)
+ {
+ strlower( n);
+ }
+ }
+
+ /* Remove simple ODS5 extended file name escape characters. */
+ eat_carets( n);
+
+ if (isdir)
+ {
+ if (strcasecmp( (nn = n+ strlen( n)- DIR_TYPE_VER_LEN), DIR_TYPE_VER))
+ error("directory not version 1");
+ else
+ if (pathput)
+ strcpy( nn, "/");
+ else
+ *n = '\0'; /* directories are discarded with zip -rj */
+ }
+ else if (vmsver == 0)
+ {
+ /* If not keeping version numbers, truncate the name at the ";".
+ (No escaped characters are expected in the version.)
+ */
+ if ((ext_dir_and_name = strrchr( n, ';')) != NULL)
+ *ext_dir_and_name = '\0';
+ }
+ else if (vmsver > 1)
+ {
+ /* Keeping version numbers, but as ".nnn", not ";nnn". */
+ if ((ext_dir_and_name = strrchr( n, ';')) != NULL)
+ *ext_dir_and_name = '.';
+ }
+
+ /* Remove a type-less dot. */
+ /* (Note that currently "name..ver" is not altered.) */
+ if ((ext_dir_and_name = strrchr( n, '.')) != NULL)
+ {
+ if (ext_dir_and_name[ 1] == '\0') /* "name." -> "name" */
+ *ext_dir_and_name = '\0';
+ else if (ext_dir_and_name[ 1] == ';') /* "name.;ver" -> "name;ver" */
+ {
+ char *f = ext_dir_and_name+ 1;
+ while (*ext_dir_and_name++ = *f++);
+ }
+ }
+
+ if (dosify)
+ msname(n);
+
+ /* Returned malloc'ed name */
+ if (pdosflag)
+ *pdosflag = dosflag;
+
+ return n;
+}
+
+
+char *in2ex( char *n)
+/* char *n; internal file name */
+/* Convert the zip file name to an external file name, returning the malloc'ed
+ string or NULL if not enough memory. */
+{
+ char *x; /* external file name */
+ char *t; /* scans name */
+ int i;
+ char chr;
+ char *endp;
+ char *last_slash;
+ char *versionp;
+
+#ifdef NAML$C_MAXRSS
+
+ char buf[ NAML$C_MAXRSS+ 1];
+ unsigned char prop;
+ unsigned char uchr;
+ char *last_dot;
+
+#endif /* def NAML$C_MAXRSS */
+
+ /* Locate the last slash. */
+ last_slash = strrchr( n, '/');
+
+/* If ODS5 is possible, replace escape carets in name. */
+
+#ifdef NAML$C_MAXRSS
+
+ endp = n+ strlen( n);
+
+ /* Locate the version delimiter, if one is expected. */
+ if (vmsver == 0)
+ { /* No version expected. */
+ versionp = endp;
+ }
+ else
+ {
+ if (vmsver > 1)
+ { /* Expect a dot-version, ".nnn". Locate the version ".".
+ Temporarily terminate at this dot to allow the last-dot search
+ below to find the last non-version dot.
+ */
+ versionp = strrchr( n, '.');
+ if (versionp != NULL) /* Can't miss. */
+ {
+ *versionp = '\0';
+ }
+ }
+ else
+ { /* Expect a semi-colon-version, ";nnn". Locate the ";". */
+ versionp = strrchr( n, ';');
+ }
+ if ((versionp == NULL) || (versionp < last_slash))
+ { /* If confused, and the version delimiter was not in the name,
+ then ignore it.
+ */
+ versionp = endp;
+ }
+ }
+
+ /* No escape needed for the last dot, if it's part of the file name.
+ All dots in a directory must be escaped.
+ */
+ last_dot = strrchr( n, '.');
+
+ if ((last_dot != NULL) && (last_slash != NULL) && (last_dot < last_slash))
+ {
+ last_dot = last_slash;
+ }
+
+ /* Replace the version dot if necessary. */
+ if ((vmsver > 1) && (versionp != NULL) && (versionp < endp))
+ {
+ *versionp = '.';
+ }
+
+ /* Add ODS5 escape sequences. Leave "/" and "?" for later.
+ The name here looks (roughly) like: dir1/dir2/a.b
+ */
+ t = n;
+ x = buf;
+ while (uchr = *t++)
+ {
+ /* Characters in the version do not need escaping. */
+ if (t <= versionp)
+ {
+ prop = char_prop[ uchr]& 31;
+ if (prop)
+ {
+ if (prop& 4)
+ { /* Dot. */
+ if (t < last_dot)
+ {
+ /* Dot which must be escaped. */
+ *x++ = '^';
+ }
+ }
+ else if (prop& 8)
+ {
+ /* Character needing hex-hex escape. */
+ *x++ = '^';
+ *x++ = hex_digit[ uchr>> 4];
+ uchr = hex_digit[ uchr& 15];
+ }
+ else
+ {
+ /* Non-dot character which must be escaped (and simple works).
+ "?" gains the caret but remains "?" until later.
+ ("/" remains (unescaped) "/".)
+ */
+ *x++ = '^';
+ if (prop& 2)
+ {
+ /* Escaped space (represented as "^_"). */
+ uchr = '_';
+ }
+ }
+ }
+ }
+ *x++ = uchr;
+ }
+ *x = '\0';
+
+ /* Point "n" to altered name buffer, and re-find the last slash. */
+ n = buf;
+ last_slash = strrchr( n, '/');
+
+#endif /* def NAML$C_MAXRSS */
+
+ if ((t = last_slash) == NULL)
+ {
+ if ((x = malloc(strlen(n) + 1 + DIR_PAD)) == NULL)
+ return NULL;
+ strcpy(x, n);
+ }
+ else
+ {
+ if ((x = malloc(strlen(n) + 3 + DIR_PAD)) == NULL)
+ return NULL;
+
+ /* Begin with "[". */
+ x[ 0] = '[';
+ i = 1;
+ if (*n != '/')
+ {
+ /* Relative path. Add ".". */
+ x[ i++] = '.';
+ }
+ else
+ {
+ /* Absolute path. Skip leading "/". */
+ n++;
+ }
+ strcpy( (x+ i), n);
+
+ /* Place the final ']'. Remember where the name starts. */
+ *(t = x + i + (t - n)) = ']';
+ last_slash = t;
+
+ /* Replace "/" with ".", and "?" with (now escaped) "/", in the
+ directory part of the name.
+ */
+ while (--t > x)
+ {
+ chr = *t;
+ if (chr == '/')
+ {
+ *t = '.';
+ }
+ else if (chr == '?')
+ {
+ *t = '/';
+ }
+ }
+
+ /* Replace "?" with (now escaped) "/", in the non-directory part of
+ the name.
+ */
+ while ((chr = *(++last_slash)) != '\0')
+ {
+ if (chr == '?')
+ {
+ *last_slash = '/';
+ }
+ }
+ }
+
+/* If case preservation is impossible (VAX, say), and down-casing, then
+ up-case. If case preservation is possible and wasn't done, then
+ there's no way to ensure proper restoration of original case, so
+ don't try. This may differ from pre-3.0 behavior.
+*/
+#ifndef NAML$C_MAXRSS
+
+ if (vms_case_2 <= 0)
+ {
+ strupper( x);
+ }
+
+#endif /* ndef NAML$C_MAXRSS */
+
+ return x;
+}
+
+void stamp( char *f, ulg d)
+/* char *f; name of file to change */
+/* ulg d; dos-style time to change it to */
+/* Set last updated and accessed time of file f to the DOS time d. */
+{
+ int tm_sec, tm_min, tm_hour, tm_mday, tm_mon, tm_year;
+ char timbuf[24];
+ static ZCONST char *month[] = {"JAN", "FEB", "MAR", "APR", "MAY", "JUN",
+ "JUL", "AUG", "SEP", "OCT", "NOV", "DEC"};
+ struct VMStimbuf {
+ char *actime; /* VMS revision date, ASCII format */
+ char *modtime; /* VMS creation date, ASCII format */
+ } ascii_times;
+
+ ascii_times.actime = ascii_times.modtime = timbuf;
+
+ /* Convert DOS time to ASCII format for VMSmunch */
+ tm_sec = (int)(d << 1) & 0x3e;
+ tm_min = (int)(d >> 5) & 0x3f;
+ tm_hour = (int)(d >> 11) & 0x1f;
+ tm_mday = (int)(d >> 16) & 0x1f;
+ tm_mon = ((int)(d >> 21) & 0xf) - 1;
+ tm_year = ((int)(d >> 25) & 0x7f) + 1980;
+ sprintf(timbuf, "%02d-%3s-%04d %02d:%02d:%02d.00", tm_mday, month[tm_mon],
+ tm_year, tm_hour, tm_min, tm_sec);
+
+ /* Set updated and accessed times of f */
+ if (VMSmunch(f, SET_TIMES, (char *)&ascii_times) != RMS$_NMF)
+ zipwarn("can't set zipfile time: ", f);
+}
+
+ulg filetime( char *f, ulg *a, zoff_t *n, iztimes *t)
+/* char *f; name of file to get info on */
+/* ulg *a; return value: file attributes */
+/* zoff_t *n; return value: file size */
+/* iztimes *t; return value: access, modific. and creation times */
+/* If file *f does not exist, return 0. Else, return the file's last
+ modified date and time as an MSDOS date and time. The date and
+ time is returned in a long with the date most significant to allow
+ unsigned integer comparison of absolute times. Also, if a is not
+ a NULL pointer, store the file attributes there, with the high two
+ bytes being the Unix attributes, and the low byte being a mapping
+ of that to DOS attributes. If n is not NULL, store the file size
+ there. If t is not NULL, the file's access, modification and creation
+ times are stored there as UNIX time_t values.
+ If f is "-", use standard input as the file. If f is a device, return
+ a file size of -1 */
+{
+ struct stat s; /* results of stat() */
+ /* convert to a malloc string dump FNMAX - 11/8/04 EG */
+ char *name;
+ int len = strlen(f);
+
+ if (f == label) {
+ if (a != NULL)
+ *a = label_mode;
+ if (n != NULL)
+ *n = -2; /* convention for a label name */
+ if (t != NULL)
+ t->atime = t->mtime = t->ctime = label_utim;
+ return label_time;
+ }
+ if ((name = malloc(len + 1)) == NULL) {
+ ZIPERR(ZE_MEM, "filetime");
+ }
+ strcpy(name, f);
+ if (name[len - 1] == '/')
+ name[len - 1] = '\0';
+ /* not all systems allow stat'ing a file with / appended */
+
+ if (strcmp(f, "-") == 0) {
+ if (fstat(fileno(stdin), &s) != 0) {
+ free(name);
+ error("fstat(stdin)");
+ }
+ } else if (LSSTAT(name, &s) != 0) {
+ /* Accept about any file kind including directories
+ * (stored with trailing / with -r option)
+ */
+ free(name);
+ return 0;
+ }
+ free(name);
+
+ if (a != NULL) {
+ *a = ((ulg)s.st_mode << 16) | !(s.st_mode & S_IWRITE);
+ if ((s.st_mode & S_IFDIR) != 0) {
+ *a |= MSDOS_DIR_ATTR;
+ }
+ }
+ if (n != NULL)
+ *n = (s.st_mode & S_IFMT) == S_IFREG ? s.st_size : -1;
+ if (t != NULL) {
+ t->atime = s.st_mtime;
+#ifdef USE_MTIME
+ t->mtime = s.st_mtime; /* Use modification time in VMS */
+#else
+ t->mtime = s.st_ctime; /* Use creation time in VMS */
+#endif
+ t->ctime = s.st_ctime;
+ }
+
+#ifdef USE_MTIME
+ return unix2dostime((time_t *)&s.st_mtime); /* Use modification time in VMS */
+#else
+ return unix2dostime((time_t *)&s.st_ctime); /* Use creation time in VMS */
+#endif
+}
+
+int deletedir( char *d)
+/* char *d; directory to delete */
+
+/* Delete the directory *d if it is empty, do nothing otherwise.
+ Return the result of rmdir(), delete(), or system().
+ For VMS, d must be in format [x.y]z.dir;1 (not [x.y.z]).
+ */
+{
+ /* code from Greg Roelofs, who horked it from Mark Edwards (unzip) */
+ int r, len;
+ char *s; /* malloc'd string for system command */
+
+ len = strlen(d);
+ if ((s = malloc(len + 34)) == NULL)
+ return 127;
+
+ system(strcat(strcpy(s, "set prot=(o:rwed) "), d));
+ r = delete(d);
+ free(s);
+ return r;
+}