1 /*********************************************************************
2  *   Copyright 1993, University Corporation for Atmospheric Research
3  *   See netcdf/README file for copying and redistribution conditions.
4  *   $Header: /private-cvsroot/minc/progs/mincdump/mincdump.c,v 1.10 2008-01-11 07:17:08 stever Exp $
5  *********************************************************************/
6 #if HAVE_CONFIG_H
7 #include "config.h"
8 #endif
9 
10 #include <stdio.h>
11 #include <stdlib.h>
12 #include <string.h>
13 #include <ctype.h>
14 #include <stdlib.h>
15 
16 #include <minc.h>
17 #include <ParseArgv.h>
18 #include "mincdump.h"
19 #include "dumplib.h"
20 #include "vardata.h"
21 
22 static void usage(void);
23 static char* name_path(const char* path);
24 static char* type_name(nc_type  type);
25 static void tztrim(char* ss);
26 static void pr_att_string(long len, const char* string);
27 static void pr_att_vals(nc_type  type, long len, const void * vals);
28 static void pr_att(int ncid, int varid, const char *varname, int ia);
29 static void do_ncdump(char* path, struct fspec* specp);
30 int main(int argc, char** argv);
31 
32 #define	STREQ(a, b)	(*(a) == *(b) && strcmp((a), (b)) == 0)
33 
34 char *progname;
35 
36 static void
usage(void)37 usage(void)
38 {
39 #define USAGE   "\
40   [-c]             Coordinate variable data and header information\n\
41   [-h]             Header information only, no data\n\
42   [-v var1[,...]]  Data for variable(s) <var1>,... only\n\
43   [-b [c|f]]       Brief annotations for C or Fortran indices in data\n\
44   [-f [c|f]]       Full annotations for C or Fortran indices in data\n\
45   [-l len]         Line length maximum in data section (default 80)\n\
46   [-n name]        Name for netCDF (default derived from file name)\n\
47   [-p n[,n]]       Display floating-point values with less precision\n\
48   file             File name of input netCDF file\n"
49 
50     (void) fprintf(stderr,
51 		   "%s [-c|-h] [-v ...] [[-b|-f] [c|f]] [-l len] [-n name] [-p n[,n]] file\n%s",
52 		   progname,
53 		   USAGE);
54 
55     (void) fprintf(stderr,
56                  "netcdf library version %s\n",
57                  nc_inq_libvers());
58     exit(EXIT_FAILURE);
59 }
60 
61 
62 /*
63  * convert pathname of netcdf file into name for cdl unit, by taking
64  * last component of path and stripping off any extension.
65  */
66 static char *
name_path(const char * path)67 name_path(const char *path)
68 {
69     const char *cp;
70     char *new;
71     char *sp;
72 
73 #ifdef vms
74 #define FILE_DELIMITER ']'
75 #endif
76 #ifdef MSDOS
77 #define FILE_DELIMITER '\\'
78 #endif
79 #ifndef FILE_DELIMITER /* default to unix */
80 #define FILE_DELIMITER '/'
81 #endif
82     cp = strrchr(path, FILE_DELIMITER);
83     if (cp == 0)		/* no delimiter */
84       cp = path;
85     else			/* skip delimeter */
86       cp++;
87     new = (char *) malloc((unsigned) (strlen(cp)+1));
88     if (new == 0) {
89 	error("out of memory!");
90     }
91     (void) strcpy(new, cp);	/* copy last component of path */
92     if ((sp = strrchr(new, '.')) != NULL)
93       *sp = '\0';		/* strip off any extension */
94     return new;
95 }
96 
97 
98 static char *
type_name(nc_type type)99 type_name(nc_type type)
100 {
101     switch (type) {
102       case NC_BYTE:
103 	return "byte";
104       case NC_CHAR:
105 	return "char";
106       case NC_SHORT:
107 	return "short";
108       case NC_INT:
109 	return "int";
110       case NC_FLOAT:
111 	return "float";
112       case NC_DOUBLE:
113 	return "double";
114       default:
115 	error("type_name: bad type %d", type);
116 	return "bogus";
117     }
118 }
119 
120 
121 /*
122  * Remove trailing zeros (after decimal point) but not trailing decimal
123  * point from ss, a string representation of a floating-point number that
124  * might include an exponent part.
125  */
126 static void
tztrim(char * ss)127 tztrim(char *ss)
128 {
129     char *cp, *ep;
130 
131     cp = ss;
132     if (*cp == '-')
133       cp++;
134     while(isdigit((int)*cp) || *cp == '.')
135       cp++;
136     if (*--cp == '.')
137       return;
138     ep = cp+1;
139     while (*cp == '0')
140       cp--;
141     cp++;
142     if (cp == ep)
143       return;
144     while (*ep)
145       *cp++ = *ep++;
146     *cp = '\0';
147     return;
148 }
149 
150 
151 /*
152  * Print attribute string, for text attributes.
153  */
154 static void
pr_att_string(long len,const char * string)155 pr_att_string(
156      long len,
157      const char *string
158      )
159 {
160     int iel;
161     const char *cp;
162     const char *sp;
163     unsigned char uc;
164 
165     cp = string;
166     Printf ("\"");
167     /* adjust len so trailing nulls don't get printed */
168     sp = cp + len - 1;
169     while (len != 0 && *sp-- == '\0')
170 	len--;
171     for (iel = 0; iel < len; iel++)
172 	switch (uc = *cp++ & 0377) {
173 	case '\b':
174 	    Printf ("\\b");
175 	    break;
176 	case '\f':
177 	    Printf ("\\f");
178 	    break;
179 	case '\n':		/* generate linebreaks after new-lines */
180 	    Printf ("\\n\",\n\t\t\t\"");
181 	    break;
182 	case '\r':
183 	    Printf ("\\r");
184 	    break;
185 	case '\t':
186 	    Printf ("\\t");
187 	    break;
188 	case '\v':
189 	    Printf ("\\v");
190 	    break;
191 	case '\\':
192 	    Printf ("\\\\");
193 	    break;
194 	case '\'':
195 	    Printf ("\\'");
196 	    break;
197 	case '\"':
198 	    Printf ("\\\"");
199 	    break;
200 	default:
201 	    Printf ("%c",uc);
202 	    break;
203 	}
204     Printf ("\"");
205 
206 }
207 
208 
209 /*
210  * Print list of attribute values, for numeric attributes.  Attribute values
211  * must be printed with explicit type tags, because CDL doesn't have explicit
212  * syntax to declare an attribute type.
213  */
214 static void
pr_att_vals(nc_type type,long len,const void * vals)215 pr_att_vals(
216      nc_type type,
217      long len,
218      const void *vals
219      )
220 {
221     int iel;
222     signed char sc;
223     short ss;
224     int ii;
225     char gps[30];
226     float ff;
227     double dd;
228 
229     /* See if we would prefer to handle this as a string.
230      */
231     if (type == NC_BYTE && len > 2) {
232         int isstring = 1;
233         int ch;
234         ch = ((signed char *)vals)[len-1];
235         if (isprint(ch) || ch == '\0' || ch == '\n') {
236             for (iel = 0; iel < len-1; iel++) {
237                 ch = ((signed char *)vals)[iel];
238                 if (!isprint(ch) && ch != '\n') {
239                     isstring = 0;
240                     break;
241                 }
242             }
243             if (isstring) {
244                 pr_att_string(len, vals);
245                 return;
246             }
247         }
248     }
249 
250     if (len == 0)
251 	return;
252     for (iel = 0; iel < len-1; iel++) {
253 	switch (type) {
254 	case NC_BYTE:
255 	    sc = ((signed char *) vals)[iel] & 0377;
256 	    Printf ("%db, ", sc);
257 	    break;
258 	case NC_SHORT:
259 	    ss = ((short *)vals)[iel];
260 	    Printf ("%ds, ", ss);
261 	    break;
262 	case NC_INT:
263 	    ii = ((int *)vals)[iel];
264 	    Printf ("%d, ", ii);
265 	    break;
266 	case NC_FLOAT:
267 	    ff = ((float *)vals)[iel];
268 	    (void) sprintf(gps, float_att_fmt, ff);
269 	    tztrim(gps);	/* trim trailing 0's after '.' */
270 	    Printf ("%s, ", gps);
271 	    break;
272 	case NC_DOUBLE:
273 	    dd = ((double *)vals)[iel];
274 	    (void) sprintf(gps, double_att_fmt, dd);
275 	    tztrim(gps);
276 	    Printf ("%s, ", gps);
277 	    break;
278 	default:
279 	    error("pr_att_vals: bad type");
280 	}
281     }
282     switch (type) {
283     case NC_BYTE:
284 	sc = ((signed char *) vals)[iel] & 0377;
285 	Printf ("%db", sc);
286 	break;
287     case NC_SHORT:
288 	ss = ((short *)vals)[iel];
289 	Printf ("%ds", ss);
290 	break;
291     case NC_INT:
292 	ii = ((int *)vals)[iel];
293 	Printf ("%d", ii);
294 	break;
295     case NC_FLOAT:
296 	ff = ((float *)vals)[iel];
297 	(void) sprintf(gps, float_att_fmt, ff);
298 	tztrim(gps);
299 	Printf ("%s", gps);
300 	break;
301     case NC_DOUBLE:
302 	dd = ((double *)vals)[iel];
303 	(void) sprintf(gps, double_att_fmt, dd);
304 	tztrim(gps);
305 	Printf ("%s", gps);
306 	break;
307     default:
308 	error("pr_att_vals: bad type");
309     }
310 }
311 
312 
313 static void
pr_att(int ncid,int varid,const char * varname,int ia)314 pr_att(
315     int ncid,
316     int varid,
317     const char *varname,
318     int ia
319     )
320 {
321     struct ncatt att;		/* attribute */
322 
323     ncattname(ncid, varid, ia, att.name);
324 
325     Printf ("\t\t%s:%s = ", varname, att.name);
326 
327     ncattinq(ncid, varid, att.name, &att.type, &att.len);
328 
329     if (att.len == 0) {	/* show 0-length attributes as empty strings */
330 	att.type = NC_CHAR;
331         /* att.len = 1; */
332     }
333     switch (att.type) {
334     case NC_CHAR:
335 	att.string = (char *) malloc(att.len + 1); /* + 1 for null byte */
336 	if (!att.string) {
337 	    error("Out of memory!");
338 	    NC_CHECK( ncclose(ncid) );
339 	    return;
340 	}
341         *att.string = '\0';     /* Initialize in case att.len == 0 */
342 	ncattget(ncid, varid, att.name, att.string );
343         pr_att_string(att.len, att.string);
344 	free(att.string);
345 	break;
346     default:
347 	att.vals = (double *) malloc(att.len * sizeof(double));
348 	if (!att.vals) {
349 	    error("Out of memory!");
350 	    NC_CHECK( ncclose(ncid) );
351 	    return;
352 	}
353 	ncattget(ncid, varid, att.name, att.vals );
354 	pr_att_vals(att.type, att.len, att.vals);
355 	free(att.vals);
356 	break;
357     }
358     Printf (" ;\n");
359 }
360 
361 static void
do_ncdump(char * path,struct fspec * specp)362 do_ncdump(char *path, struct fspec* specp)
363 {
364     int ndims;			/* number of dimensions */
365     int nvars;			/* number of variables */
366     int ngatts;			/* number of global attributes */
367     int xdimid;			/* id of unlimited dimension */
368     int dimid;			/* dimension id */
369     int varid;			/* variable id */
370     struct ncdim dims[NC_MAX_DIMS]; /* dimensions */
371     long vdims[NC_MAX_DIMS];	/* dimension sizes for a single variable */
372     struct ncvar var;		/* variable */
373     struct ncatt att;		/* attribute */
374     int id;			/* dimension number per variable */
375     int ia;			/* attribute number */
376     int iv;			/* variable number */
377     int is_coord;		/* true if variable is a coordinate variable */
378     int ncid;			/* netCDF id */
379     vnode* vlist = 0;		/* list for vars specified with -v option */
380     int nc_status;
381     int old_nc_opts;
382 
383     ncid = miopen(path, NC_NOWRITE);
384     if (ncid < 0) {
385 	error("%s: can't open\n", path);
386     }
387     /*
388      * If any vars were specified with -v option, get list of associated
389      * variable ids
390      */
391     if (specp->nlvars > 0) {
392 	vlist = newvlist();	/* list for vars specified with -v option */
393 	for (iv=0; iv < specp->nlvars; iv++) {
394 	    varid = ncvarid(ncid, specp->lvars[iv]);
395 	    varadd(vlist, varid);
396 	}
397     }
398 
399     /* if name not specified, derive it from path */
400     if (specp->name == (char *)0) {
401 	specp->name = name_path (path);
402     }
403 
404     if (MI2_ISH5OBJ(ncid)) {
405         Printf ("hdf5 %s {\n", specp->name);
406     }
407     else {
408         Printf ("netcdf %s {\n", specp->name);
409     }
410     /*
411      * get number of dimensions, number of variables, number of global
412      * atts, and dimension id of unlimited dimension, if any
413      */
414     nc_status = ncinquire(ncid, &ndims, &nvars, &ngatts, &xdimid);
415 
416     /* get dimension info */
417     if (ndims > 0)
418       Printf ("dimensions:\n");
419     for (dimid = 0; dimid < ndims; dimid++) {
420 	NC_CHECK(ncdiminq(ncid, dimid, dims[dimid].name, &dims[dimid].size));
421 	if (dimid == xdimid)
422 	  Printf ("\t%s = %s ; // (%ld currently)\n",dims[dimid].name,
423 		  "UNLIMITED", (long)dims[dimid].size);
424 	else
425 	  Printf ("\t%s = %ld ;\n", dims[dimid].name, (long)dims[dimid].size);
426     }
427 
428     if (nvars > 0)
429 	Printf ("variables:\n");
430     /* get variable info, with variable attributes */
431     for (varid = 0; varid < nvars; varid++) {
432 	NC_CHECK(ncvarinq(ncid, varid, var.name, &var.type, &var.ndims,
433                           var.dims, &var.natts));
434 	Printf ("\t%s %s", type_name(var.type), var.name);
435 	if (var.ndims > 0)
436 	  Printf ("(");
437 	for (id = 0; id < var.ndims; id++) {
438 	    Printf ("%s%s",
439 		    dims[var.dims[id]].name,
440 		    id < var.ndims-1 ? ", " : ")");
441 	}
442 	Printf (" ;\n");
443 
444 	/* get variable attributes */
445 	for (ia = 0; ia < var.natts; ia++)
446 	    pr_att(ncid, varid, var.name, ia); /* print ia-th attribute */
447     }
448 
449 
450     /* get global attributes */
451     if (ngatts > 0)
452       Printf ("\n// global attributes:\n");
453     for (ia = 0; ia < ngatts; ia++)
454 	pr_att(ncid, NC_GLOBAL, "", ia); /* print ia-th global attribute */
455 
456     if (! specp->header_only) {
457 	if (nvars > 0) {
458 	    Printf ("data:\n");
459 	}
460 	/* output variable data */
461 	for (varid = 0; varid < nvars; varid++) {
462 	    /* if var list specified, test for membership */
463 	    if (specp->nlvars > 0 && ! varmember(vlist, varid))
464 	      continue;
465 	    NC_CHECK( ncvarinq(ncid, varid, var.name, &var.type, &var.ndims,
466                                var.dims, &var.natts) );
467 	    if (specp->coord_vals) {
468 		/* Find out if this is a coordinate variable */
469 		is_coord = 0;
470 		for (dimid = 0; dimid < ndims; dimid++) {
471 		    if (strcmp(dims[dimid].name, var.name) == 0 &&
472 			var.ndims == 1) {
473 			is_coord = 1;
474 			break;
475 		    }
476 		}
477 		if (! is_coord)	/* don't get data for non-coordinate vars */
478 		  continue;
479 	    }
480 	    /*
481 	     * Only get data for variable if it is not a record variable,
482 	     * or if it is a record variable and at least one record has
483 	     * been written.
484 	     */
485 	    if (var.ndims == 0
486 		|| var.dims[0] != xdimid
487 		|| dims[xdimid].size != 0) {
488 
489 		/* Collect variable's dim sizes */
490 		for (id = 0; id < var.ndims; id++)
491 		  vdims[id] = dims[var.dims[id]].size;
492 		var.has_fillval = 1; /* by default, but turn off for bytes */
493 
494 		/* get _FillValue attribute */
495                 old_nc_opts = ncopts;
496                 ncopts = 0;
497                 nc_status = ncattinq(ncid,varid,_FillValue,&att.type,&att.len);
498                 ncopts = old_nc_opts;
499 		if(nc_status == NC_NOERR &&
500 		   att.type == var.type && att.len == 1) {
501 		    if(var.type == NC_CHAR) {
502 			char fillc;
503 			ncattget(ncid, varid, _FillValue, &fillc );
504 			var.fillval = fillc;
505 		    }
506                     else {
507 			ncattget(ncid, varid, _FillValue, &var.fillval);
508 		    }
509 		} else {
510 		    switch (var.type) {
511 		    case NC_BYTE:
512 			/* don't do default fill-values for bytes, too risky */
513 			var.has_fillval = 0;
514 			break;
515 		    case NC_CHAR:
516 			var.fillval = NC_FILL_CHAR;
517 			break;
518 		    case NC_SHORT:
519 			var.fillval = NC_FILL_SHORT;
520 			break;
521 		    case NC_INT:
522 			var.fillval = NC_FILL_INT;
523 			break;
524 		    case NC_FLOAT:
525 			var.fillval = NC_FILL_FLOAT;
526 			break;
527 		    case NC_DOUBLE:
528 			var.fillval = NC_FILL_DOUBLE;
529 			break;
530 		    default:
531 			break;
532 		    }
533 		}
534 		if (vardata(&var, vdims, ncid, varid, specp) == -1) {
535 		    error("can't output data for variable %s", var.name);
536 		    NC_CHECK(
537 			miclose(ncid) );
538 		    if (vlist)
539 			free(vlist);
540 		    return;
541 		}
542 	    }
543 	}
544     }
545 
546     Printf ("}\n");
547     NC_CHECK(
548 	miclose(ncid) );
549     if (vlist)
550 	free(vlist);
551 }
552 
553 static void
set_brief(struct fspec * fspecp,char * key,char * optarg)554 set_brief(struct fspec * fspecp, char *key, char *optarg)
555 {
556     fspecp->brief_data_cmnts = my_true;
557     switch (tolower(optarg[0])) {
558     case 'c':
559         fspecp->data_lang = LANG_C;
560         break;
561     case 'f':
562         fspecp->data_lang = LANG_F;
563         break;
564     default:
565         error("invalid value for -b option: %s", optarg);
566     }
567 }
568 
569 static void
set_full(struct fspec * fspecp,char * key,char * optarg)570 set_full(struct fspec * fspecp, char *key, char *optarg)
571 {
572     fspecp->full_data_cmnts = my_true;
573     switch (tolower(optarg[0])) {
574     case 'c':
575         fspecp->data_lang = LANG_C;
576         break;
577     case 'f':
578         fspecp->data_lang = LANG_F;
579         break;
580     default:
581         error("invalid value for -f option: %s", optarg);
582     }
583 }
584 
585 static int
make_lvars(struct fspec * fspecp,char * key,char * optarg)586 make_lvars(struct fspec * fspecp, char *key, char *optarg)
587 {
588     char *cp = optarg;
589     int nvars = 1;
590     char ** cpp;
591 
592     /* compute number of variable names in comma-delimited list */
593     fspecp->nlvars = 1;
594     while (*cp++)
595       if (*cp == ',')
596  	nvars++;
597 
598     fspecp->lvars = (char **) malloc(nvars * sizeof(char*));
599     if (!fspecp->lvars) {
600 	error("out of memory");
601     }
602 
603     cpp = fspecp->lvars;
604     /* copy variable names into list */
605     for (cp = strtok(optarg, ",");
606 	 cp != NULL;
607 	 cp = strtok((char *) NULL, ",")) {
608 
609 	*cpp = (char *) malloc(strlen(cp) + 1);
610 	if (!*cpp) {
611 	    error("out of memory");
612 	}
613 	strcpy(*cpp, cp);
614 	cpp++;
615     }
616     fspecp->nlvars = nvars;
617     return 1;
618 }
619 
620 
621 /*
622  * Extract the significant-digits specifiers from the -d argument on the
623  * command-line and update the default data formats appropriately.
624  */
625 static int
set_sigdigs(char * dest,char * key,const char * optarg)626 set_sigdigs(char *dest, char *key, const char *optarg)
627 {
628     char *ptr1 = 0;
629     char *ptr2 = 0;
630     int flt_digits = FLT_DIGITS; /* default floating-point digits */
631     int dbl_digits = DBL_DIGITS; /* default double-precision digits */
632 
633     if (optarg != 0 && (int) strlen(optarg) > 0 && optarg[0] != ',')
634         flt_digits = (int)strtol(optarg, &ptr1, 10);
635 
636     if (flt_digits < 1 || flt_digits > 20) {
637 	error("unreasonable value for float significant digits: %d",
638 	      flt_digits);
639     }
640     if (*ptr1 == ',')
641       dbl_digits = (int)strtol(ptr1+1, &ptr2, 10);
642     if (ptr2 == ptr1+1 || dbl_digits < 1 || dbl_digits > 20) {
643 	error("unreasonable value for double significant digits: %d",
644 	      dbl_digits);
645     }
646     set_formats(flt_digits, dbl_digits);
647     return 1;
648 }
649 
650 
651 /*
652  * Extract the significant-digits specifiers from the -p argument on the
653  * command-line, set flags so we can override C_format attributes (if any),
654  * and update the default data formats appropriately.
655  */
656 static int
set_precision(char * dest,char * key,const char * optarg)657 set_precision(char *dest, char *key, const char *optarg)
658 {
659     char *ptr1 = 0;
660     char *ptr2 = 0;
661     int flt_digits = FLT_DIGITS;	/* default floating-point digits */
662     int dbl_digits = DBL_DIGITS;	/* default double-precision digits */
663 
664     if (optarg != 0 && (int) strlen(optarg) > 0 && optarg[0] != ',') {
665         flt_digits = (int)strtol(optarg, &ptr1, 10);
666 	float_precision_specified = 1;
667     }
668 
669     if (flt_digits < 1 || flt_digits > 20) {
670 	error("unreasonable value for float significant digits: %d",
671 	      flt_digits);
672     }
673     if (*ptr1 == ',') {
674 	dbl_digits = (int) strtol(ptr1+1, &ptr2, 10);
675 	double_precision_specified = 1;
676     }
677     if (ptr2 == ptr1+1 || dbl_digits < 1 || dbl_digits > 20) {
678 	error("unreasonable value for double significant digits: %d",
679 	      dbl_digits);
680     }
681     set_formats(flt_digits, dbl_digits);
682     return 1;
683 }
684 
685 int
main(int argc,char * argv[])686 main(int argc, char *argv[])
687 {
688     static struct fspec fspec =	/* defaults, overridden on command line */
689       {
690 	  0,			/* construct netcdf name from file name */
691 	  my_false,		/* print header info only, no data? */
692 	  my_false,		/* just print coord vars? */
693 	  my_false,		/* brief  comments in data section? */
694 	  my_false,		/* full annotations in data section?  */
695 	  LANG_C,		/* language conventions for indices */
696 	  0,			/* if -v specified, number of variables */
697 	  0			/* if -v specified, list of variable names */
698 	  };
699     int i;
700     static int max_len = 80;    /* default maximum line length */
701     static ArgvInfo argTable[] = {
702         {"-b", ARGV_FUNC, (char *) set_brief, (char *) &fspec,
703          "Brief annotations for C or Fortran indices in data" },
704         {"-c", ARGV_CONSTANT, (char *) my_true, (char *) &fspec.coord_vals,
705          "Coordinate variable data and header information" },
706         {"-d", ARGV_FUNC, (char *) set_sigdigs, (char *) NULL,
707          "Obsolete option for setting significant digits" },
708         {"-f", ARGV_FUNC, (char *) set_full, (char *) &fspec,
709          "Full annotations for C or Fortran indices in data" },
710         {"-h", ARGV_CONSTANT, (char *) my_true, (char *) &fspec.header_only,
711          "Header information only, no data" },
712         {"-l", ARGV_INT, (char *) 1, (char *) &max_len,
713          "Line length maximum in data section (default 80)" },
714         {"-n", ARGV_STRING, (char *) 1, (char *) &fspec.name,
715          "Name for netCDF (default derived from file name)" },
716         {"-p", ARGV_FUNC, (char *) set_precision, (char *) NULL,
717          "Display floating-point values with less precision" },
718         {"-v", ARGV_FUNC, (char *) make_lvars, (char *) &fspec,
719          "Data for variable(s) <var1>,... only" },
720         {NULL, ARGV_END, NULL, NULL, NULL}
721         };
722 
723     progname = argv[0];
724     set_formats(FLT_DIGITS, DBL_DIGITS); /* default for float, double data */
725 
726     if (ParseArgv(&argc, argv, argTable, 0)) {
727         usage();
728     }
729 
730     set_max_len(max_len);
731 
732     argc--;
733     argv++;
734 
735     i = 0;
736 
737     do {
738 	if (argc > 0)
739 	  do_ncdump(argv[i], &fspec);
740     } while (++i < argc);
741 #ifdef vms
742     exit(EXIT_SUCCESS);
743 #else
744     return EXIT_SUCCESS;
745 #endif
746 }
747