1/*
2  Copyright (c) 1990-2008 Info-ZIP.  All rights reserved.
3
4  See the accompanying file LICENSE, version 2007-Mar-04 or later
5  (the contents of which are also included in unzip.h) for terms of use.
6  If, for some reason, all these files are missing, the Info-ZIP license
7  also may be found at:  ftp://ftp.info-zip.org/pub/infozip/license.html
8*/
9
10/* 2004-12-13 SMS.
11 * Disabled the module name macro to accommodate old GNU C which didn't
12 * obey the directive, and thus confused MMS/MMK where the object
13 * library dependencies need to have the correct module name.
14 */
15#if 0
16#define module_name VMS_UNZIP_CMDLINE
17#define module_ident "02-013"
18#endif /* 0 */
19
20/*
21**
22**  Facility:   UNZIP
23**
24**  Module:     VMS_UNZIP_CMDLINE
25**
26**  Author:     Hunter Goatley <goathunter@MadGoat.com>
27**
28**  Date:       25 Apr 97 (orig. Zip version, 30 Jul 93)
29**
30**  Abstract:   Routines to handle a VMS CLI interface for UnZip.  The CLI
31**              command line is parsed and a new argc/argv are built and
32**              returned to UnZip.
33**
34**  Modified by:
35**
36**      02-013          S. Schweda, C. Spieler  29-Dec-2007 03:34
37**              Extended /RESTORE qualifier to support timestamp restoration
38**              options.
39**      02-012          Steven Schweda          07-Jul-2006 19:04
40**              Added /TEXT=STMLF qualifier option.
41**      02-011          Christian Spieler       21-Apr-2005 01:23
42**              Added /FULL=DIAGNOSTICS option modifier.
43**      02-010          Steven Schweda          14-FEB-2005 20:04
44**              Added /DOT_VERSION (-Y) and /ODS2 (-2) qualifiers.
45**      02-009          Steven Schweda          28-JAN-2005 16:16
46**              Added /TIMESTAMP (-T) qualifier.
47**      02-008          Christian Spieler       08-DEC-2001 23:44
48**              Added support for /TRAVERSE_DIRS argument
49**      02-007          Christian Spieler       24-SEP-2001 21:12
50**              Escape verbatim '%' chars in format strings; version unchanged.
51**      02-007          Onno van der Linden     02-Jul-1998 19:07
52**              Modified to support GNU CC 2.8 on Alpha; version unchanged.
53**      02-007          Johnny Lee              25-Jun-1998 07:38
54**              Fixed typo (superfluous ';'); no version num change.
55**      02-007          Hunter Goatley          11-NOV-1997 10:38
56**              Fixed "zip" vs. "unzip" typo; no version num change.
57**      02-007          Christian Spieler       14-SEP-1997 22:43
58**              Cosmetic mods to stay in sync with Zip; no version num change.
59**      02-007          Christian Spieler       12-JUL-1997 02:05
60**              Revised argv vector construction for better handling of quoted
61**              arguments (e.g.: embedded white space); no version num change.
62**      02-007          Christian Spieler       04-MAR-1997 22:25
63**              Made /CASE_INSENSITIVE common to UnZip and ZipInfo mode;
64**              added support for /PASSWORD="decryption_key" argument.
65**      02-006          Christian Spieler       11-MAY-1996 22:40
66**              Added SFX version of VMSCLI_usage().
67**      02-005          Patrick Ellis           09-MAY-1996 22:25
68**              Show UNIX style usage screen when UNIX style options are used.
69**      02-004          Christian Spieler       06-FEB-1996 02:20
70**              Added /HELP qualifier.
71**      02-003          Christian Spieler       23-DEC-1995 17:20
72**              Adapted to UnZip 5.2.
73**      02-002          Hunter Goatley          16-JUL-1994 10:20
74**              Fixed some typos.
75**      02-001          Cave Newt               14-JUL-1994 15:18
76**              Removed obsolete /EXTRACT option; fixed /*TEXT options;
77**              wrote VMSCLI usage() function
78**      02-000          Hunter Goatley          12-JUL-1994 00:00
79**              Original UnZip version (v5.11).
80**      01-000          Hunter Goatley          30-JUL-1993 07:54
81**              Original version (for Zip v1.9p1).
82**
83*/
84
85/*    Stand-alone test procedure:
86 *
87 * cc /define = TEST=1 [.vms]cmdline.c /include = [] /object = [.vms]
88 * set command /object = [.vms]unz_cli.obj [.vms]unz_cli.cld
89 * link /executable = [] [.vms]cmdline.obj, [.vms]unz_cli.obj
90 * EXEC*UTE == "$SYS$DISK:[]'"
91 * exec cmdline [ /qualifiers ...] [parameters ...]
92 */
93
94
95
96/* 2004-12-13 SMS.
97 * Disabled the module name macro to accommodate old GNU C which didn't
98 * obey the directive, and thus confused MMS/MMK where the object
99 * library dependencies need to have the correct module name.
100 */
101#if 0
102#if defined(__DECC) || defined(__GNUC__)
103#pragma module module_name module_ident
104#else
105#module module_name module_ident
106#endif
107#endif /* 0 */
108
109#define UNZIP_INTERNAL
110#include "unzip.h"
111#ifndef TEST
112#  include "unzvers.h"  /* for VMSCLI_usage() */
113#endif /* !TEST */
114
115/* Workaround for broken header files of older DECC distributions
116 * that are incompatible with the /NAMES=AS_IS qualifier. */
117/* - lib$routines.h definitions: */
118#define lib$establish LIB$ESTABLISH
119#define lib$get_foreign LIB$GET_FOREIGN
120#define lib$get_input LIB$GET_INPUT
121#define lib$sig_to_ret LIB$SIG_TO_RET
122/* - str$routines.h definitions: */
123#define str$concat STR$CONCAT
124#define str$find_first_substring STR$FIND_FIRST_SUBSTRING
125
126#include <ssdef.h>
127#include <descrip.h>
128#include <climsgdef.h>
129#include <clidef.h>
130#include <lib$routines.h>
131#include <str$routines.h>
132
133#ifndef CLI$_COMMA
134globalvalue CLI$_COMMA;
135#endif
136
137/*
138**  "Macro" to initialize a dynamic string descriptor.
139*/
140#define init_dyndesc(dsc) {\
141        dsc.dsc$w_length = 0;\
142        dsc.dsc$b_dtype = DSC$K_DTYPE_T;\
143        dsc.dsc$b_class = DSC$K_CLASS_D;\
144        dsc.dsc$a_pointer = NULL;}
145
146/*
147**  Memory allocation step for argv string buffer.
148*/
149#define ARGBSIZE_UNIT 256
150
151/*
152**  Memory reallocation macro for argv string buffer.
153*/
154#define CHECK_BUFFER_ALLOCATION(buf, reserved, requested) { \
155    if ((requested) > (reserved)) { \
156        char *save_buf = (buf); \
157        (reserved) += ARGBSIZE_UNIT; \
158        if (((buf) = (char *) realloc((buf), (reserved))) == NULL) { \
159            if (save_buf != NULL) free(save_buf); \
160            return (SS$_INSFMEM); \
161        } \
162    } \
163}
164
165/*
166**  Define descriptors for all of the CLI parameters and qualifiers.
167*/
168#if 0
169$DESCRIPTOR(cli_extract,        "EXTRACT");             /* obsolete */
170#endif
171$DESCRIPTOR(cli_text,           "TEXT");                /* -a[a] */
172$DESCRIPTOR(cli_text_auto,      "TEXT.AUTO");           /* -a */
173$DESCRIPTOR(cli_text_all,       "TEXT.ALL");            /* -aa */
174$DESCRIPTOR(cli_text_none,      "TEXT.NONE");           /* ---a */
175$DESCRIPTOR(cli_text_stmlf,     "TEXT.STMLF");          /* -S */
176$DESCRIPTOR(cli_binary,         "BINARY");              /* -b[b] */
177$DESCRIPTOR(cli_binary_auto,    "BINARY.AUTO");         /* -b */
178$DESCRIPTOR(cli_binary_all,     "BINARY.ALL");          /* -bb */
179$DESCRIPTOR(cli_binary_none,    "BINARY.NONE");         /* ---b */
180$DESCRIPTOR(cli_case_insensitive,"CASE_INSENSITIVE");   /* -C */
181$DESCRIPTOR(cli_screen,         "SCREEN");              /* -c */
182$DESCRIPTOR(cli_directory,      "DIRECTORY");           /* -d */
183$DESCRIPTOR(cli_freshen,        "FRESHEN");             /* -f */
184$DESCRIPTOR(cli_help,           "HELP");                /* -h */
185$DESCRIPTOR(cli_junk,           "JUNK");                /* -j */
186$DESCRIPTOR(cli_lowercase,      "LOWERCASE");           /* -L */
187$DESCRIPTOR(cli_list,           "LIST");                /* -l */
188$DESCRIPTOR(cli_brief,          "BRIEF");               /* -l */
189$DESCRIPTOR(cli_full,           "FULL");                /* -v */
190$DESCRIPTOR(cli_full_diags,     "FULL.DIAGNOSTICS");    /* -vv */
191$DESCRIPTOR(cli_existing,       "EXISTING");            /* -o, -oo, -n */
192$DESCRIPTOR(cli_exist_newver,   "EXISTING.NEW_VERSION"); /* -o */
193$DESCRIPTOR(cli_exist_over,     "EXISTING.OVERWRITE");  /* -oo */
194$DESCRIPTOR(cli_exist_noext,    "EXISTING.NOEXTRACT");  /* -n */
195$DESCRIPTOR(cli_overwrite,      "OVERWRITE");           /* -o, -n */
196$DESCRIPTOR(cli_quiet,          "QUIET");               /* -q */
197$DESCRIPTOR(cli_super_quiet,    "QUIET.SUPER");         /* -qq */
198$DESCRIPTOR(cli_test,           "TEST");                /* -t */
199$DESCRIPTOR(cli_pipe,           "PIPE");                /* -p */
200$DESCRIPTOR(cli_password,       "PASSWORD");            /* -P */
201$DESCRIPTOR(cli_timestamp,      "TIMESTAMP");           /* -T */
202$DESCRIPTOR(cli_uppercase,      "UPPERCASE");           /* -U */
203$DESCRIPTOR(cli_update,         "UPDATE");              /* -u */
204$DESCRIPTOR(cli_version,        "VERSION");             /* -V */
205$DESCRIPTOR(cli_restore,        "RESTORE");             /* -X */
206$DESCRIPTOR(cli_restore_own,    "RESTORE.OWNER_PROT");  /* -X */
207$DESCRIPTOR(cli_restore_date,   "RESTORE.DATE");        /* -DD */
208$DESCRIPTOR(cli_restore_date_all, "RESTORE.DATE.ALL");  /* --D */
209$DESCRIPTOR(cli_restore_date_files, "RESTORE.DATE.FILES"); /* -D */
210$DESCRIPTOR(cli_dot_version,    "DOT_VERSION");         /* -Y */
211$DESCRIPTOR(cli_comment,        "COMMENT");             /* -z */
212$DESCRIPTOR(cli_exclude,        "EXCLUDE");             /* -x */
213$DESCRIPTOR(cli_ods2,           "ODS2");                /* -2 */
214$DESCRIPTOR(cli_traverse,       "TRAVERSE_DIRS");       /* -: */
215
216$DESCRIPTOR(cli_information,    "ZIPINFO");             /* -Z */
217$DESCRIPTOR(cli_short,          "SHORT");               /* -Zs */
218$DESCRIPTOR(cli_medium,         "MEDIUM");              /* -Zm */
219$DESCRIPTOR(cli_long,           "LONG");                /* -Zl */
220$DESCRIPTOR(cli_verbose,        "VERBOSE");             /* -Zv */
221$DESCRIPTOR(cli_header,         "HEADER");              /* -Zh */
222$DESCRIPTOR(cli_totals,         "TOTALS");              /* -Zt */
223$DESCRIPTOR(cli_times,          "TIMES");               /* -ZT */
224$DESCRIPTOR(cli_one_line,       "ONE_LINE");            /* -Z2 */
225
226$DESCRIPTOR(cli_page,           "PAGE");                /* -M , -ZM */
227
228$DESCRIPTOR(cli_yyz,            "YYZ_UNZIP");
229
230$DESCRIPTOR(cli_zipfile,        "ZIPFILE");
231$DESCRIPTOR(cli_infile,         "INFILE");
232$DESCRIPTOR(unzip_command,      "unzip ");
233
234static int show_VMSCLI_usage;
235
236#ifndef vms_unzip_cld
237#  define vms_unzip_cld VMS_UNZIP_CLD
238#endif
239#if defined(__DECC) || defined(__GNUC__)
240extern void *vms_unzip_cld;
241#else
242globalref void *vms_unzip_cld;
243#endif
244
245/* extern unsigned long LIB$GET_INPUT(void), LIB$SIG_TO_RET(void); */
246
247#ifndef cli$dcl_parse
248#  define cli$dcl_parse CLI$DCL_PARSE
249#endif
250#ifndef cli$present
251#  define cli$present CLI$PRESENT
252#endif
253#ifndef cli$get_value
254#  define cli$get_value CLI$GET_VALUE
255#endif
256extern unsigned long cli$dcl_parse ();
257extern unsigned long cli$present ();
258extern unsigned long cli$get_value ();
259
260unsigned long vms_unzip_cmdline (int *, char ***);
261static unsigned long get_list (struct dsc$descriptor_s *,
262                               struct dsc$descriptor_d *, int,
263                               char **, unsigned long *, unsigned long *);
264static unsigned long check_cli (struct dsc$descriptor_s *);
265
266
267#ifdef TEST
268int
269main(int argc, char **argv)
270{
271    return (vms_unzip_cmdline(&argc, &argv));
272}
273#endif /* TEST */
274
275
276unsigned long
277vms_unzip_cmdline (int *argc_p, char ***argv_p)
278{
279/*
280**  Routine:    vms_unzip_cmdline
281**
282**  Function:
283**
284**      Parse the DCL command line and create a fake argv array to be
285**      handed off to Zip.
286**
287**      NOTE: the argv[] is built as we go, so all the parameters are
288**      checked in the appropriate order!!
289**
290**  Formal parameters:
291**
292**      argc_p          - Address of int to receive the new argc
293**      argv_p          - Address of char ** to receive the argv address
294**
295**  Calling sequence:
296**
297**      status = vms_unzip_cmdline (&argc, &argv);
298**
299**  Returns:
300**
301**      SS$_NORMAL      - Success.
302**      SS$_INSFMEM     - A malloc() or realloc() failed
303**      SS$_ABORT       - Bad time value
304**
305*/
306    register unsigned long status;
307    char options[256];
308    char *the_cmd_line;                 /* buffer for argv strings */
309    unsigned long cmdl_size;            /* allocated size of buffer */
310    unsigned long cmdl_len;             /* used size of buffer */
311    char *ptr;
312    int  x, len, zipinfo, exclude_list;
313    int restore_date;
314
315    int new_argc;
316    char **new_argv;
317
318    struct dsc$descriptor_d work_str;
319    struct dsc$descriptor_d foreign_cmdline;
320    struct dsc$descriptor_d output_directory;
321    struct dsc$descriptor_d password_arg;
322
323    init_dyndesc(work_str);
324    init_dyndesc(foreign_cmdline);
325    init_dyndesc(output_directory);
326    init_dyndesc(password_arg);
327
328    /*
329    **  See if the program was invoked by the CLI (SET COMMAND) or by
330    **  a foreign command definition.  Check for /YYZ_UNZIP, which is a
331    **  valid default qualifier solely for this test.
332    */
333    show_VMSCLI_usage = TRUE;
334    status = check_cli(&cli_yyz);
335    if (!(status & 1)) {
336        lib$get_foreign(&foreign_cmdline);
337        /*
338        **  If nothing was returned or the first character is a "-", then
339        **  assume it's a UNIX-style command and return.
340        */
341        if (foreign_cmdline.dsc$w_length == 0)
342            return (SS$_NORMAL);
343        if ((*(foreign_cmdline.dsc$a_pointer) == '-') ||
344            ((foreign_cmdline.dsc$w_length > 1) &&
345             (*(foreign_cmdline.dsc$a_pointer) == '"') &&
346             (*(foreign_cmdline.dsc$a_pointer + 1) == '-'))) {
347            show_VMSCLI_usage = FALSE;
348            return (SS$_NORMAL);
349        }
350
351        str$concat(&work_str, &unzip_command, &foreign_cmdline);
352        status = cli$dcl_parse(&work_str, &vms_unzip_cld, lib$get_input,
353                        lib$get_input, 0);
354        if (!(status & 1)) return (status);
355    }
356
357    /*
358    **  There's always going to be a new_argv[] because of the image name.
359    */
360    if ((the_cmd_line = (char *) malloc(cmdl_size = ARGBSIZE_UNIT)) == NULL)
361        return (SS$_INSFMEM);
362
363    strcpy(the_cmd_line, "unzip");
364    cmdl_len = sizeof("unzip");
365
366    /*
367    **  First, check to see if any of the regular options were specified.
368    */
369
370    options[0] = '-';
371    ptr = &options[1];          /* Point to temporary buffer */
372
373    /*
374    **  Is it ZipInfo??
375    */
376    zipinfo = 0;
377    status = cli$present(&cli_information);
378    if (status & 1) {
379
380        zipinfo = 1;
381
382        *ptr++ = 'Z';
383
384        if (cli$present(&cli_one_line) & 1)
385            *ptr++ = '2';
386        if (cli$present(&cli_short) & 1)
387            *ptr++ = 's';
388        if (cli$present(&cli_medium) & 1)
389            *ptr++ = 'm';
390        if (cli$present(&cli_long) & 1)
391            *ptr++ = 'l';
392        if (cli$present(&cli_verbose) & 1)
393            *ptr++ = 'v';
394        if (cli$present(&cli_header) & 1)
395            *ptr++ = 'h';
396        if (cli$present(&cli_comment) & 1)
397            *ptr++ = 'c';
398        if (cli$present(&cli_totals) & 1)
399            *ptr++ = 't';
400        if (cli$present(&cli_times) & 1)
401            *ptr++ = 'T';
402
403    }
404    else {
405
406#if 0
407        /*
408        **  Extract files?
409        */
410        status = cli$present(&cli_extract);
411        if (status == CLI$_NEGATED)
412            *ptr++ = '-';
413        if (status != CLI$_ABSENT)
414            *ptr++ = 'x';
415#endif
416
417        /*
418        **  Write binary files in VMS binary (fixed-length, 512-byte records,
419        **  record attributes: none) format
420        **  (auto-convert, or force to convert all files)
421        */
422        status = cli$present(&cli_binary);
423        if (status != CLI$_ABSENT) {
424            *ptr++ = '-';
425            *ptr++ = '-';
426            *ptr++ = 'b';
427            if ((status & 1) &&
428                !((status = cli$present(&cli_binary_none)) & 1)) {
429                *ptr++ = 'b';
430                if ((status = cli$present(&cli_binary_all)) & 1)
431                    *ptr++ = 'b';
432            }
433        }
434
435        /*
436        **  Convert files as text (CR LF -> LF, etc.)
437        **  (auto-convert, or force to convert all files)
438        */
439        status = cli$present(&cli_text);
440        if (status != CLI$_ABSENT) {
441            *ptr++ = '-';
442            *ptr++ = '-';
443            *ptr++ = 'a';
444            if ((status & 1) &&
445                !((status = cli$present(&cli_text_none)) & 1)) {
446                *ptr++ = 'a';
447                if ((status = cli$present(&cli_text_all)) & 1)
448                    *ptr++ = 'a';
449                if ((status = cli$present(&cli_text_stmlf)) & 1)
450                    *ptr++ = 'S';
451            }
452        }
453
454        /*
455        **  Extract files to screen?
456        */
457        status = cli$present(&cli_screen);
458        if (status == CLI$_NEGATED)
459            *ptr++ = '-';
460        if (status != CLI$_ABSENT)
461            *ptr++ = 'c';
462
463        /*
464        **  Re-create directory structure?  (default)
465        */
466        status = cli$present(&cli_directory);
467        if (status == CLI$_PRESENT) {
468            status = cli$get_value(&cli_directory, &output_directory);
469        }
470
471        /*
472        **  Restore directory date-times.
473        */
474        restore_date = 0;
475        status = cli$present(&cli_restore_date);
476        if (status != CLI$_ABSENT) {
477            /* Emit "----D" to reset the timestamp restore state "D_flag"
478            ** consistently to 0 (independent of optional environment
479            ** option settings).
480            */
481            *ptr++ = '-';
482            *ptr++ = '-';
483            *ptr++ = '-';
484            *ptr++ = 'D';
485            if (status == CLI$_NEGATED) {
486                /* /RESTORE=NODATE */
487                restore_date = 2;
488            } else {
489                status = cli$present(&cli_restore_date_all);
490                if (status == CLI$_PRESENT) {
491                    /* /RESTORE=(DATE=ALL) */
492                    restore_date = 0;
493                } else {
494                    /* /RESTORE=(DATE=FILES) (default) */
495                    restore_date = 1;
496                }
497            }
498            /* Emit the required number of (positive) "D" characters. */
499            while (restore_date > 0) {
500                *ptr++ = 'D';
501                restore_date--;
502            }
503        }
504
505        /*
506        **  Freshen existing files, create none
507        */
508        status = cli$present(&cli_freshen);
509        if (status == CLI$_NEGATED)
510            *ptr++ = '-';
511        if (status != CLI$_ABSENT)
512            *ptr++ = 'f';
513
514        /*
515        **  Show the help.
516        */
517        status = cli$present(&cli_help);
518        if (status & 1)
519            *ptr++ = 'h';
520
521        /*
522        **  Junk stored directory names on unzip
523        */
524        status = cli$present(&cli_junk);
525        if (status == CLI$_NEGATED)
526            *ptr++ = '-';
527        if (status != CLI$_ABSENT)
528            *ptr++ = 'j';
529
530        /*
531        **  List contents (/BRIEF (default) or /FULL)
532        */
533        status = cli$present(&cli_list);
534        if (status & 1) {
535            if (cli$present(&cli_full) & 1) {
536               *ptr++ = 'v';
537               if (cli$present(&cli_full_diags) & 1)
538                   *ptr++ = 'v';
539            } else
540               *ptr++ = 'l';
541        }
542
543        /*
544        **  Existing files: new version, overwrite, no extract?
545        */
546        status = cli$present(&cli_exist_newver);
547        if (status == CLI$_PRESENT) {
548            *ptr++ = 'o';
549        }
550        status = cli$present(&cli_exist_over);
551        if (status == CLI$_PRESENT) {
552            *ptr++ = 'o';
553            *ptr++ = 'o';
554        }
555        status = cli$present(&cli_exist_noext);
556        if (status == CLI$_PRESENT) {
557            *ptr++ = 'n';
558        }
559
560        /*
561        **  Overwrite files (deprecated) ?
562        */
563        status = cli$present(&cli_overwrite);
564        if (status == CLI$_NEGATED)
565            *ptr++ = 'n';
566        else if (status != CLI$_ABSENT)
567            *ptr++ = 'o';
568
569        /*
570        **  Decryption password from command line?
571        */
572        status = cli$present(&cli_password);
573        if (status == CLI$_PRESENT) {
574            status = cli$get_value(&cli_password, &password_arg);
575        }
576
577        /*
578        **  Pipe files to SYS$OUTPUT with no informationals?
579        */
580        status = cli$present(&cli_pipe);
581        if (status != CLI$_ABSENT)
582            *ptr++ = 'p';
583
584        /*
585        **  Quiet
586        */
587        status = cli$present(&cli_quiet);
588        if (status & 1) {
589            *ptr++ = 'q';
590            if ((status = cli$present(&cli_super_quiet)) & 1)
591                *ptr++ = 'q';
592        }
593
594        /*
595        **  Test archive integrity
596        */
597        status = cli$present(&cli_test);
598        if (status == CLI$_NEGATED)
599            *ptr++ = '-';
600        if (status != CLI$_ABSENT)
601            *ptr++ = 't';
602
603        /*
604        **  Set archive timestamp according to its newest file.
605        */
606        status = cli$present(&cli_timestamp);
607        if (status & 1)
608            *ptr++ = 'T';
609
610        /*
611        **  Extract "foo.ext.###" as "foo.ext;###" (treat .### as version number)
612        */
613        status = cli$present(&cli_dot_version);
614        if (status == CLI$_NEGATED)
615            *ptr++ = '-';
616        if (status != CLI$_ABSENT)
617            *ptr++ = 'Y';
618
619        /*
620        **  Force conversion of extracted file names to old ODS2 conventions
621        */
622        status = cli$present(&cli_ods2);
623        if (status == CLI$_NEGATED)
624            *ptr++ = '-';
625        if (status != CLI$_ABSENT)
626            *ptr++ = '2';
627
628        /*
629        **  Traverse directories (don't skip "../" path components)
630        */
631        status = cli$present(&cli_traverse);
632        if (status == CLI$_NEGATED)
633            *ptr++ = '-';
634        if (status != CLI$_ABSENT)
635            *ptr++ = ':';
636
637        /*
638        **  Make (some) names lowercase
639        */
640        status = cli$present(&cli_lowercase);
641        if (status == CLI$_NEGATED)
642            *ptr++ = '-';
643        if (status != CLI$_ABSENT)
644            *ptr++ = 'L';
645
646        /*
647        **  Uppercase (don't convert to lower)
648        */
649        status = cli$present(&cli_uppercase);
650        if (status == CLI$_NEGATED)
651            *ptr++ = '-';
652        if (status != CLI$_ABSENT)
653            *ptr++ = 'U';
654
655        /*
656        **  Update (extract only new and newer files)
657        */
658        status = cli$present(&cli_update);
659        if (status == CLI$_NEGATED)
660            *ptr++ = '-';
661        if (status != CLI$_ABSENT)
662            *ptr++ = 'u';
663
664        /*
665        **  Version (retain VMS/DEC-20 file versions)
666        */
667        status = cli$present(&cli_version);
668        if (status == CLI$_NEGATED)
669            *ptr++ = '-';
670        if (status != CLI$_ABSENT)
671            *ptr++ = 'V';
672
673        /*
674        **  Restore owner/protection info
675        */
676        status = cli$present(&cli_restore_own);
677        if (status != CLI$_ABSENT) {
678            if (status == CLI$_NEGATED) {
679                *ptr++ = '-';
680            } else if ((status = cli$present(&cli_restore))
681                       == CLI$_NEGATED) {
682                *ptr++ = '-';
683            }
684            *ptr++ = 'X';
685        }
686
687        /*
688        **  Display only the archive comment
689        */
690        status = cli$present(&cli_comment);
691        if (status == CLI$_NEGATED)
692            *ptr++ = '-';
693        if (status != CLI$_ABSENT)
694            *ptr++ = 'z';
695
696    }   /* ZipInfo check way up there.... */
697
698    /* The following options are common to both UnZip and ZipInfo mode. */
699
700    /*
701    **  Match filenames case-insensitively (-C)
702    */
703    status = cli$present(&cli_case_insensitive);
704    if (status == CLI$_NEGATED)
705        *ptr++ = '-';
706    if (status != CLI$_ABSENT)
707        *ptr++ = 'C';
708
709    /*
710    **  Use builtin pager for all screen output
711    */
712    status = cli$present(&cli_page);
713    if (status == CLI$_NEGATED)
714        *ptr++ = '-';
715    if (status != CLI$_ABSENT)
716        *ptr++ = 'M';
717
718    /*
719    **  Check existence of a list of files to exclude, fetch is done later.
720    */
721    status = cli$present(&cli_exclude);
722    exclude_list = ((status & 1) != 0);
723
724    /*
725    **  If the user didn't give any DCL qualifier, assume he wants the
726    **  Un*x interface.
727    if ( (ptr == &options[1]) &&
728         (output_directory.dsc$w_length == 0) &&
729         (password_arg.dsc$w_length == 0) &&
730         (!exclude_list)  ) {
731        free(the_cmd_line);
732        return (SS$_NORMAL);
733    }
734    */
735
736    /*
737    **  Now copy the final options string to the_cmd_line.
738    */
739    len = ptr - &options[0];
740    if (len > 1) {
741        options[len] = '\0';
742        x = cmdl_len;
743        cmdl_len += len + 1;
744        CHECK_BUFFER_ALLOCATION(the_cmd_line, cmdl_size, cmdl_len)
745        strcpy(&the_cmd_line[x], options);
746    }
747
748    /*
749    **  If specified, add the decryption password argument.
750    **/
751    if (password_arg.dsc$w_length != 0) {
752        x = cmdl_len;
753        cmdl_len += password_arg.dsc$w_length + 4;
754        CHECK_BUFFER_ALLOCATION(the_cmd_line, cmdl_size, cmdl_len)
755        strcpy(&the_cmd_line[x], "-P");
756        strncpy(&the_cmd_line[x+3], password_arg.dsc$a_pointer,
757                password_arg.dsc$w_length);
758        the_cmd_line[cmdl_len-1] = '\0';
759    }
760
761    /*
762    **  Now get the specified zip file name.
763    */
764    status = cli$present(&cli_zipfile);
765    if (status & 1) {
766        status = cli$get_value(&cli_zipfile, &work_str);
767
768        x = cmdl_len;
769        cmdl_len += work_str.dsc$w_length + 1;
770        CHECK_BUFFER_ALLOCATION(the_cmd_line, cmdl_size, cmdl_len)
771        strncpy(&the_cmd_line[x], work_str.dsc$a_pointer,
772                work_str.dsc$w_length);
773        the_cmd_line[cmdl_len-1] = '\0';
774
775    }
776
777    /*
778    **  Get the output directory, for UnZip.
779    **/
780    if (output_directory.dsc$w_length != 0) {
781        x = cmdl_len;
782        cmdl_len += output_directory.dsc$w_length + 4;
783        CHECK_BUFFER_ALLOCATION(the_cmd_line, cmdl_size, cmdl_len)
784        strcpy(&the_cmd_line[x], "-d");
785        strncpy(&the_cmd_line[x+3], output_directory.dsc$a_pointer,
786                output_directory.dsc$w_length);
787        the_cmd_line[cmdl_len-1] = '\0';
788    }
789
790    /*
791    **  Run through the list of files to unzip.
792    */
793    status = cli$present(&cli_infile);
794    if (status & 1) {
795        status = get_list(&cli_infile, &foreign_cmdline, '\0',
796                          &the_cmd_line, &cmdl_size, &cmdl_len);
797        if (!(status & 1)) return (status);
798    }
799
800    /*
801    **  Get the list of files to exclude, if there are any.
802    */
803    if (exclude_list) {
804        x = cmdl_len;
805        cmdl_len += 3;
806        CHECK_BUFFER_ALLOCATION(the_cmd_line, cmdl_size, cmdl_len)
807        strcpy(&the_cmd_line[x], "-x");
808
809        status = get_list(&cli_exclude, &foreign_cmdline, '\0',
810                          &the_cmd_line, &cmdl_size, &cmdl_len);
811        if (!(status & 1)) return (status);
812    }
813
814    /*
815    **  We have finished collecting the strings for the argv vector,
816    **  release unused space.
817    */
818    if ((the_cmd_line = (char *) realloc(the_cmd_line, cmdl_len)) == NULL)
819        return (SS$_INSFMEM);
820
821    /*
822    **  Now that we've built our new UNIX-like command line, count the
823    **  number of args and build an argv array.
824    */
825    for (new_argc = 0, x = 0; x < cmdl_len; x++)
826        if (the_cmd_line[x] == '\0')
827            new_argc++;
828
829    /*
830    **  Allocate memory for the new argv[].  The last element of argv[]
831    **  is supposed to be NULL, so allocate enough for new_argc+1.
832    */
833    if ((new_argv = (char **) calloc(new_argc+1, sizeof(char *))) == NULL)
834        return (SS$_INSFMEM);
835
836    /*
837    **  For each option, store the address in new_argv[] and convert the
838    **  separating blanks to nulls so each argv[] string is terminated.
839    */
840    for (ptr = the_cmd_line, x = 0; x < new_argc; x++) {
841        new_argv[x] = ptr;
842        ptr += strlen(ptr) + 1;
843    }
844    new_argv[new_argc] = NULL;
845
846#if defined(TEST) || defined(DEBUG)
847    printf("new_argc    = %d\n", new_argc);
848    for (x = 0; x < new_argc; x++)
849        printf("new_argv[%d] = %s\n", x, new_argv[x]);
850#endif /* TEST || DEBUG */
851
852    /*
853    **  All finished.  Return the new argc and argv[] addresses to Zip.
854    */
855    *argc_p = new_argc;
856    *argv_p = new_argv;
857
858    return (SS$_NORMAL);
859}
860
861
862
863static unsigned long
864get_list (struct dsc$descriptor_s *qual, struct dsc$descriptor_d *rawtail,
865          int delim, char **p_str, unsigned long *p_size, unsigned long *p_end)
866{
867/*
868**  Routine:    get_list
869**
870**  Function:   This routine runs through a comma-separated CLI list
871**              and copies the strings to the argv buffer.  The
872**              specified separation character is used to separate
873**              the strings in the argv buffer.
874**
875**              All unquoted strings are converted to lower-case.
876**
877**  Formal parameters:
878**
879**      qual    - Address of descriptor for the qualifier name
880**      rawtail - Address of descriptor for the full command line tail
881**      delim   - Character to use to separate the list items
882**      p_str   - Address of pointer pointing to output buffer (argv strings)
883**      p_size  - Address of number containing allocated size for output string
884**      p_end   - Address of number containing used length in output buf
885**
886*/
887
888    register unsigned long status;
889    struct dsc$descriptor_d work_str;
890
891    init_dyndesc(work_str);
892
893    status = cli$present(qual);
894    if (status & 1) {
895
896        unsigned long len, old_len;
897        long ind, sind;
898        int keep_case;
899        char *src, *dst; int x;
900
901        /*
902        **  Just in case the string doesn't exist yet, though it does.
903        */
904        if (*p_str == NULL) {
905            *p_size = ARGBSIZE_UNIT;
906            if ((*p_str = (char *) malloc(*p_size)) == NULL)
907                return (SS$_INSFMEM);
908            len = 0;
909        } else {
910            len = *p_end;
911        }
912
913        while ((status = cli$get_value(qual, &work_str)) & 1) {
914            old_len = len;
915            len += work_str.dsc$w_length + 1;
916            CHECK_BUFFER_ALLOCATION(*p_str, *p_size, len)
917
918            /*
919            **  Look for the filename in the original foreign command
920            **  line to see if it was originally quoted.  If so, then
921            **  don't convert it to lowercase.
922            */
923            keep_case = FALSE;
924            str$find_first_substring(rawtail, &ind, &sind, &work_str);
925            if ((ind > 1 && *(rawtail->dsc$a_pointer + ind - 2) == '"') ||
926                (ind == 0))
927                keep_case = TRUE;
928
929            /*
930            **  Copy the string to the buffer, converting to lowercase.
931            */
932            src = work_str.dsc$a_pointer;
933            dst = *p_str+old_len;
934            for (x = 0; x < work_str.dsc$w_length; x++) {
935                if (!keep_case && ((*src >= 'A') && (*src <= 'Z')))
936                    *dst++ = *src++ + 32;
937                else
938                    *dst++ = *src++;
939            }
940            if (status == CLI$_COMMA)
941                (*p_str)[len-1] = (char)delim;
942            else
943                (*p_str)[len-1] = '\0';
944        }
945        *p_end = len;
946    }
947
948    return (SS$_NORMAL);
949
950}
951
952
953static unsigned long
954check_cli (struct dsc$descriptor_s *qual)
955{
956/*
957**  Routine:    check_cli
958**
959**  Function:   Check to see if a CLD was used to invoke the program.
960**
961**  Formal parameters:
962**
963**      qual    - Address of descriptor for qualifier name to check.
964**
965*/
966    lib$establish(lib$sig_to_ret);      /* Establish condition handler */
967    return (cli$present(qual));         /* Just see if something was given */
968}
969
970
971#ifndef TEST
972#ifdef SFX
973
974#ifdef SFX_EXDIR
975#  define SFXOPT_EXDIR "\n                   and /DIRECTORY=exdir-spec"
976#else
977#  define SFXOPT_EXDIR ""
978#endif
979
980#ifdef MORE
981#  define SFXOPT1 "/PAGE, "
982#else
983#  define SFXOPT1 ""
984#endif
985
986int VMSCLI_usage(__GPRO__ int error)    /* returns PK-type error code */
987{
988    extern ZCONST char UnzipSFXBanner[];
989#ifdef BETA
990    extern ZCONST char BetaVersion[];
991#endif
992    int flag;
993
994    if (!show_VMSCLI_usage)
995       return usage(__G__ error);
996
997    flag = (error? 1 : 0);
998
999    Info(slide, flag, ((char *)slide, UnzipSFXBanner,
1000      UZ_MAJORVER, UZ_MINORVER, UZ_PATCHLEVEL, UZ_BETALEVEL, UZ_VERSION_DATE));
1001    Info(slide, flag, ((char *)slide, "\
1002Valid main options are /TEST, /FRESHEN, /UPDATE, /PIPE, /SCREEN, /COMMENT%s.\n",
1003      SFXOPT_EXDIR));
1004    Info(slide, flag, ((char *)slide, "\
1005Modifying options are /TEXT, /BINARY, /JUNK, /EXISTING, /QUIET,\n\
1006                      /CASE_INSENSITIVE, /LOWERCASE, %s/VERSION, /RESTORE.\n",
1007      SFXOPT1));
1008#ifdef BETA
1009    Info(slide, flag, ((char *)slide, BetaVersion, "\n", "SFX"));
1010#endif
1011
1012    if (error)
1013        return PK_PARAM;
1014    else
1015        return PK_COOL;     /* just wanted usage screen: no error */
1016
1017} /* end function VMSCLI_usage() */
1018
1019
1020#else /* !SFX */
1021
1022int VMSCLI_usage(__GPRO__ int error)    /* returns PK-type error code */
1023{
1024    extern ZCONST char UnzipUsageLine1[];
1025#ifdef BETA
1026    extern ZCONST char BetaVersion[];
1027#endif
1028    int flag;
1029
1030    if (!show_VMSCLI_usage)
1031       return usage(__G__ error);
1032
1033/*---------------------------------------------------------------------------
1034    If user requested usage, send it to stdout; else send to stderr.
1035  ---------------------------------------------------------------------------*/
1036
1037    flag = (error? 1 : 0);
1038
1039
1040/*---------------------------------------------------------------------------
1041    Print either ZipInfo usage or UnZip usage, depending on incantation.
1042  ---------------------------------------------------------------------------*/
1043
1044    if (uO.zipinfo_mode) {
1045
1046#ifndef NO_ZIPINFO
1047
1048        Info(slide, flag, ((char *)slide, "\
1049ZipInfo %d.%d%d%s %s, by Newtware and the fine folks at Info-ZIP.\n\n\
1050List name, date/time, attribute, size, compression method, etc., about files\n\
1051in list (excluding those in xlist) contained in the specified .zip archive(s).\
1052\n\"file[.zip]\" may be a wildcard name containing * or %% (e.g., \"*font-%%\
1053.zip\").\n", ZI_MAJORVER, ZI_MINORVER, UZ_PATCHLEVEL, UZ_BETALEVEL,
1054          UZ_VERSION_DATE));
1055
1056        Info(slide, flag, ((char *)slide, "\
1057   usage:  zipinfo file[.zip] [list] [/EXCL=(xlist)] [/DIR=exdir] /options\n\
1058   or:  unzip /ZIPINFO file[.zip] [list] [/EXCL=(xlist)] [/DIR=exdir] /options\
1059\n\nmain\
1060 listing-format options:              /SHORT   short \"ls -l\" format (def.)\n\
1061  /ONE_LINE  just filenames, one/line     /MEDIUM  medium Unix \"ls -l\" format\n\
1062  /VERBOSE   verbose, multi-page format   /LONG    long Unix \"ls -l\" format\n\
1063"));
1064
1065        Info(slide, flag, ((char *)slide, "\
1066miscellaneous options:\n  \
1067/HEADER   print header line       /TOTALS  totals for listed files or for all\n\
1068  /COMMENT  print zipfile comment   /TIMES   times in sortable decimal format\n\
1069  /[NO]CASE_INSENSITIVE  match filenames case-insensitively\n\
1070  /[NO]PAGE page output through built-in \"more\"\n\
1071  /EXCLUDE=(file-spec1,etc.)  exclude file-specs from listing\n"));
1072
1073        Info(slide, flag, ((char *)slide, "\n\
1074Type unzip \"-Z\" for Unix style flags\n\
1075Remember that non-lowercase filespecs must be\
1076 quoted in VMS (e.g., \"Makefile\").\n"));
1077
1078#endif /* !NO_ZIPINFO */
1079
1080    } else {   /* UnZip mode */
1081
1082        Info(slide, flag, ((char *)slide, UnzipUsageLine1,
1083          UZ_MAJORVER, UZ_MINORVER, UZ_PATCHLEVEL, UZ_BETALEVEL,
1084          UZ_VERSION_DATE));
1085
1086#ifdef BETA
1087        Info(slide, flag, ((char *)slide, BetaVersion, "", ""));
1088#endif
1089
1090        Info(slide, flag, ((char *)slide, "\
1091Usage: unzip file[.zip] [list] [/EXCL=(xlist)] [/DIR=exdir] /options /modifiers\
1092\n  Default action is to extract files in list, except those in xlist, to exdir\
1093;\n  file[.zip] may be a wildcard.  %s\n\n",
1094#ifdef NO_ZIPINFO
1095          "(ZipInfo mode is disabled in this version.)"
1096#else
1097          "Type \"unzip /ZIPINFO\" for ZipInfo-mode usage."
1098#endif
1099          ));
1100
1101        Info(slide, flag, ((char *)slide, "\
1102Major options include (type unzip -h for Unix style flags):\n\
1103   /[NO]TEST, /LIST, /[NO]SCREEN, /PIPE, /[NO]FRESHEN, /[NO]UPDATE,\n\
1104   /[NO]COMMENT, /DIRECTORY=directory-spec, /EXCLUDE=(file-spec1,etc.)\n\n\
1105Modifiers include:\n\
1106   /BRIEF, /FULL, /[NO]TEXT[=NONE|AUTO|ALL], /[NO]BINARY[=NONE|AUTO|ALL],\n\
1107   /EXISTING={NEW_VERSION|OVERWRITE|NOEXTRACT}, /[NO]JUNK, /QUIET,\n\
1108   /QUIET[=SUPER], /[NO]PAGE, /[NO]CASE_INSENSITIVE, /[NO]LOWERCASE,\n\
1109   /[NO]VERSION, /RESTORE[=([NO]OWNER_PROT[,NODATE|DATE={ALL|FILES}])]\n\n"));
1110
1111        Info(slide, flag, ((char *)slide, "\
1112Examples (see unzip.txt or \"HELP UNZIP\" for more info):\n\
1113   unzip edit1 /EXCL=joe.jou /CASE_INSENSITIVE    => Extract all files except\
1114\n\
1115      joe.jou (or JOE.JOU, or any combination of case) from zipfile edit1.zip.\
1116\n  \
1117 unzip zip201 \"Makefile.VMS\" vms/*.[ch]         => extract VMS Makefile and\
1118\n\
1119      *.c and *.h files; must quote uppercase names if /CASE_INSENS not used.\
1120\n\
1121   unzip foo /DIR=tmp:[.test] /JUNK /TEXT /EXIS=NEW  => extract all files to\
1122\n\
1123      tmp. dir., flatten hierarchy, auto-conv. text files, create new versions.\
1124\n"));
1125
1126    } /* end if (zipinfo_mode) */
1127
1128    if (error)
1129        return PK_PARAM;
1130    else
1131        return PK_COOL;     /* just wanted usage screen: no error */
1132
1133} /* end function VMSCLI_usage() */
1134
1135#endif /* ?SFX */
1136#endif /* !TEST */
1137