xref: /netbsd-src/external/bsd/ntp/dist/sntp/libopts/save.c (revision 7788a0781fe6ff2cce37368b4578a7ade0850cb1)
1 /*	$NetBSD: save.c,v 1.4 2012/02/03 21:36:40 christos Exp $	*/
2 
3 
4 /*
5  * \file save.c
6  *
7  * Time-stamp:      "2011-04-06 09:21:44 bkorb"
8  *
9  *  This module's routines will take the currently set options and
10  *  store them into an ".rc" file for re-interpretation the next
11  *  time the invoking program is run.
12  *
13  *  This file is part of AutoOpts, a companion to AutoGen.
14  *  AutoOpts is free software.
15  *  AutoOpts is Copyright (c) 1992-2011 by Bruce Korb - all rights reserved
16  *
17  *  AutoOpts is available under any one of two licenses.  The license
18  *  in use must be one of these two and the choice is under the control
19  *  of the user of the license.
20  *
21  *   The GNU Lesser General Public License, version 3 or later
22  *      See the files "COPYING.lgplv3" and "COPYING.gplv3"
23  *
24  *   The Modified Berkeley Software Distribution License
25  *      See the file "COPYING.mbsd"
26  *
27  *  These files have the following md5sums:
28  *
29  *  43b91e8ca915626ed3818ffb1b71248b pkg/libopts/COPYING.gplv3
30  *  06a1a2e4760c90ea5e1dad8dfaac4d39 pkg/libopts/COPYING.lgplv3
31  *  66a5cedaf62c4b2637025f049f9b826f pkg/libopts/COPYING.mbsd
32  */
33 
34 static char const  zWarn[] = "%s WARNING:  cannot save options - ";
35 static char const close_xml[] = "</%s>\n";
36 
37 /* = = = START-STATIC-FORWARD = = = */
38 static tCC*
39 findDirName(tOptions* pOpts, int* p_free);
40 
41 static char const *
42 findFileName(tOptions * pOpts, int * p_free_name);
43 
44 static void
45 printEntry(
46     FILE *     fp,
47     tOptDesc * p,
48     tCC*       pzLA );
49 
50 static void
51 print_a_value(FILE * fp, int depth, tOptDesc * pOD, tOptionValue const * ovp);
52 
53 static void
54 print_a_string(FILE * fp, char const * name, char const * pz);
55 
56 static void
57 printValueList(FILE * fp, char const * name, tArgList * al);
58 
59 static void
60 printHierarchy(FILE * fp, tOptDesc * p);
61 
62 static FILE *
63 openSaveFile(tOptions* pOpts);
64 
65 static void
66 printNoArgOpt(FILE * fp, tOptDesc * p, tOptDesc * pOD);
67 
68 static void
69 printStringArg(FILE * fp, tOptDesc * pOD);
70 
71 static void
72 printEnumArg(FILE * fp, tOptDesc * pOD);
73 
74 static void
75 printSetMemberArg(FILE * fp, tOptDesc * pOD);
76 
77 static void
78 printFileArg(FILE * fp, tOptDesc * pOD, tOptions* pOpts);
79 /* = = = END-STATIC-FORWARD = = = */
80 
81 static tCC*
82 findDirName(tOptions* pOpts, int* p_free)
83 {
84     tCC*  pzDir;
85 
86     if (  (pOpts->specOptIdx.save_opts == NO_EQUIVALENT)
87        || (pOpts->specOptIdx.save_opts == 0))
88         return NULL;
89 
90     pzDir = pOpts->pOptDesc[ pOpts->specOptIdx.save_opts ].optArg.argString;
91     if ((pzDir != NULL) && (*pzDir != NUL))
92         return pzDir;
93 
94     /*
95      *  This function only works if there is a directory where
96      *  we can stash the RC (INI) file.
97      */
98     {
99         tCC* const* papz = pOpts->papzHomeList;
100         if (papz == NULL)
101             return NULL;
102 
103         while (papz[1] != NULL) papz++;
104         pzDir = *papz;
105     }
106 
107     /*
108      *  IF it does not require deciphering an env value, then just copy it
109      */
110     if (*pzDir != '$')
111         return pzDir;
112 
113     {
114         tCC*  pzEndDir = strchr(++pzDir, DIRCH);
115         char* pzFileName;
116         char* pzEnv;
117 
118         if (pzEndDir != NULL) {
119             char z[ AO_NAME_SIZE ];
120             if ((pzEndDir - pzDir) > AO_NAME_LIMIT )
121                 return NULL;
122             memcpy(z, pzDir, (size_t)(pzEndDir - pzDir));
123             z[pzEndDir - pzDir] = NUL;
124             pzEnv = getenv(z);
125         } else {
126 
127             /*
128              *  Make sure we can get the env value (after stripping off
129              *  any trailing directory or file names)
130              */
131             pzEnv = getenv(pzDir);
132         }
133 
134         if (pzEnv == NULL) {
135             fprintf(stderr, zWarn, pOpts->pzProgName);
136             fprintf(stderr, zNotDef, pzDir);
137             return NULL;
138         }
139 
140         if (pzEndDir == NULL)
141             return pzEnv;
142 
143         {
144             size_t sz = strlen(pzEnv) + strlen(pzEndDir) + 2;
145             pzFileName = (char*)AGALOC(sz, "dir name");
146         }
147 
148         if (pzFileName == NULL)
149             return NULL;
150 
151         *p_free = 1;
152         /*
153          *  Glue together the full name into the allocated memory.
154          *  FIXME: We lose track of this memory.
155          */
156         sprintf(pzFileName, "%s/%s", pzEnv, pzEndDir);
157         return pzFileName;
158     }
159 }
160 
161 
162 static char const *
163 findFileName(tOptions * pOpts, int * p_free_name)
164 {
165     struct stat stBuf;
166     int    free_dir_name = 0;
167 
168     char const * pzDir = findDirName(pOpts, &free_dir_name);
169     if (pzDir == NULL)
170         return NULL;
171 
172     /*
173      *  See if we can find the specified directory.  We use a once-only loop
174      *  structure so we can bail out early.
175      */
176     if (stat(pzDir, &stBuf) != 0) do {
177         char z[AG_PATH_MAX];
178         char * dirchp;
179 
180         /*
181          *  IF we could not, check to see if we got a full
182          *  path to a file name that has not been created yet.
183          */
184         if (errno != ENOENT) {
185         bogus_name:
186             fprintf(stderr, zWarn, pOpts->pzProgName);
187             fprintf(stderr, zNoStat, errno, strerror(errno), pzDir);
188             if (free_dir_name)
189                 AGFREE(pzDir);
190             return NULL;
191         }
192 
193         /*
194          *  Strip off the last component, stat the remaining string and
195          *  that string must name a directory
196          */
197         dirchp = strrchr(pzDir, DIRCH);
198         if (dirchp == NULL) {
199             stBuf.st_mode = S_IFREG;
200             break; /* found directory -- viz.,  "." */
201         }
202 
203         if ((size_t)(dirchp - pzDir) >= sizeof(z))
204             goto bogus_name;
205 
206         memcpy(z, pzDir, (size_t)(dirchp - pzDir));
207         z[dirchp - pzDir] = NUL;
208 
209         if ((stat(z, &stBuf) != 0) || ! S_ISDIR(stBuf.st_mode))
210             goto bogus_name;
211         stBuf.st_mode = S_IFREG; /* file within this directory */
212     } while (0);
213 
214     /*
215      *  IF what we found was a directory,
216      *  THEN tack on the config file name
217      */
218     if (S_ISDIR(stBuf.st_mode)) {
219         size_t sz = strlen(pzDir) + strlen(pOpts->pzRcName) + 2;
220 
221         {
222             char*  pzPath = (char*)AGALOC(sz, "file name");
223 #ifdef HAVE_SNPRINTF
224             snprintf(pzPath, sz, "%s/%s", pzDir, pOpts->pzRcName);
225 #else
226             sprintf(pzPath, "%s/%s", pzDir, pOpts->pzRcName);
227 #endif
228             if (free_dir_name)
229                 AGFREE(pzDir);
230             pzDir = pzPath;
231             free_dir_name = 1;
232         }
233 
234         /*
235          *  IF we cannot stat the object for any reason other than
236          *     it does not exist, then we bail out
237          */
238         if (stat(pzDir, &stBuf) != 0) {
239             if (errno != ENOENT) {
240                 fprintf(stderr, zWarn, pOpts->pzProgName);
241                 fprintf(stderr, zNoStat, errno, strerror(errno),
242                         pzDir);
243                 AGFREE(pzDir);
244                 return NULL;
245             }
246 
247             /*
248              *  It does not exist yet, but it will be a regular file
249              */
250             stBuf.st_mode = S_IFREG;
251         }
252     }
253 
254     /*
255      *  Make sure that whatever we ultimately found, that it either is
256      *  or will soon be a file.
257      */
258     if (! S_ISREG(stBuf.st_mode)) {
259         fprintf(stderr, zWarn, pOpts->pzProgName);
260         fprintf(stderr, zNotFile, pzDir);
261         if (free_dir_name)
262             AGFREE(pzDir);
263         return NULL;
264     }
265 
266     /*
267      *  Get rid of the old file
268      */
269     unlink(pzDir);
270     *p_free_name = free_dir_name;
271     return pzDir;
272 }
273 
274 
275 static void
276 printEntry(
277     FILE *     fp,
278     tOptDesc * p,
279     tCC*       pzLA )
280 {
281     /*
282      *  There is an argument.  Pad the name so values line up.
283      *  Not disabled *OR* this got equivalenced to another opt,
284      *  then use current option name.
285      *  Otherwise, there must be a disablement name.
286      */
287     {
288         char const * pz;
289         if (! DISABLED_OPT(p) || (p->optEquivIndex != NO_EQUIVALENT))
290             pz = p->pz_Name;
291         else
292             pz = p->pz_DisableName;
293 
294         fprintf(fp, "%-18s", pz);
295     }
296     /*
297      *  IF the option is numeric only,
298      *  THEN the char pointer is really the number
299      */
300     if (OPTST_GET_ARGTYPE(p->fOptState) == OPARG_TYPE_NUMERIC)
301         fprintf(fp, "  %d\n", (int)(t_word)pzLA);
302 
303     /*
304      *  OTHERWISE, FOR each line of the value text, ...
305      */
306     else if (pzLA == NULL)
307         fputc('\n', fp);
308 
309     else {
310         fputc(' ', fp); fputc(' ', fp);
311         for (;;) {
312             tCC* pzNl = strchr(pzLA, '\n');
313 
314             /*
315              *  IF this is the last line
316              *  THEN bail and print it
317              */
318             if (pzNl == NULL)
319                 break;
320 
321             /*
322              *  Print the continuation and the text from the current line
323              */
324             (void)fwrite(pzLA, (size_t)(pzNl - pzLA), (size_t)1, fp);
325             pzLA = pzNl+1; /* advance the Last Arg pointer */
326             fputs("\\\n", fp);
327         }
328 
329         /*
330          *  Terminate the entry
331          */
332         fputs(pzLA, fp);
333         fputc('\n', fp);
334     }
335 }
336 
337 
338 static void
339 print_a_value(FILE * fp, int depth, tOptDesc * pOD, tOptionValue const * ovp)
340 {
341     static char const bool_atr[]  = "<%1$s type=boolean>%2$s</%1$s>\n";
342     static char const numb_atr[]  = "<%1$s type=integer>0x%2$lX</%1$s>\n";
343     static char const type_atr[]  = "<%s type=%s>";
344     static char const null_atr[]  = "<%s/>\n";
345 
346     while (--depth >= 0)
347         putc(' ', fp), putc(' ', fp);
348 
349     switch (ovp->valType) {
350     default:
351     case OPARG_TYPE_NONE:
352         fprintf(fp, null_atr, ovp->pzName);
353         break;
354 
355     case OPARG_TYPE_STRING:
356         print_a_string(fp, ovp->pzName, ovp->v.strVal);
357         break;
358 
359     case OPARG_TYPE_ENUMERATION:
360     case OPARG_TYPE_MEMBERSHIP:
361         if (pOD != NULL) {
362             tAoUI     opt_state = pOD->fOptState;
363             uintptr_t val = pOD->optArg.argEnum;
364             char const * typ = (ovp->valType == OPARG_TYPE_ENUMERATION)
365                 ? "keyword" : "set-membership";
366 
367             fprintf(fp, type_atr, ovp->pzName, typ);
368 
369             /*
370              *  This is a magic incantation that will convert the
371              *  bit flag values back into a string suitable for printing.
372              */
373             (*(pOD->pOptProc))(OPTPROC_RETURN_VALNAME, pOD );
374             if (pOD->optArg.argString != NULL) {
375                 fputs(pOD->optArg.argString, fp);
376 
377                 if (ovp->valType != OPARG_TYPE_ENUMERATION) {
378                     /*
379                      *  set membership strings get allocated
380                      */
381                     AGFREE(pOD->optArg.argString);
382                 }
383             }
384 
385             pOD->optArg.argEnum = val;
386             pOD->fOptState = opt_state;
387             fprintf(fp, close_xml, ovp->pzName);
388             break;
389         }
390         /* FALLTHROUGH */
391 
392     case OPARG_TYPE_NUMERIC:
393         fprintf(fp, numb_atr, ovp->pzName, ovp->v.longVal);
394         break;
395 
396     case OPARG_TYPE_BOOLEAN:
397         fprintf(fp, bool_atr, ovp->pzName,
398                 ovp->v.boolVal ? "true" : "false");
399         break;
400 
401     case OPARG_TYPE_HIERARCHY:
402         printValueList(fp, ovp->pzName, ovp->v.nestVal);
403         break;
404     }
405 }
406 
407 
408 static void
409 print_a_string(FILE * fp, char const * name, char const * pz)
410 {
411     static char const open_atr[]  = "<%s>";
412 
413     fprintf(fp, open_atr, name);
414     for (;;) {
415         int ch = ((int)*(pz++)) & 0xFF;
416 
417         switch (ch) {
418         case NUL: goto string_done;
419 
420         case '&':
421         case '<':
422         case '>':
423 #if __GNUC__ >= 4
424         case 1 ... (' ' - 1):
425         case ('~' + 1) ... 0xFF:
426 #endif
427             emit_special_char(fp, ch);
428             break;
429 
430         default:
431 #if __GNUC__ < 4
432             if (  ((ch >= 1) && (ch <= (' ' - 1)))
433                || ((ch >= ('~' + 1)) && (ch <= 0xFF)) ) {
434                 emit_special_char(fp, ch);
435                 break;
436             }
437 #endif
438             putc(ch, fp);
439         }
440     } string_done:;
441     fprintf(fp, close_xml, name);
442 }
443 
444 
445 static void
446 printValueList(FILE * fp, char const * name, tArgList * al)
447 {
448     static int depth = 1;
449 
450     int sp_ct;
451     int opt_ct;
452     void ** opt_list;
453 
454     if (al == NULL)
455         return;
456     opt_ct   = al->useCt;
457     opt_list = (void **)(intptr_t)al->apzArgs;
458 
459     if (opt_ct <= 0) {
460         fprintf(fp, "<%s/>\n", name);
461         return;
462     }
463 
464     fprintf(fp, "<%s type=nested>\n", name);
465 
466     depth++;
467     while (--opt_ct >= 0) {
468         tOptionValue const * ovp = *(opt_list++);
469 
470         print_a_value(fp, depth, NULL, ovp);
471     }
472     depth--;
473 
474     for (sp_ct = depth; --sp_ct >= 0;)
475         putc(' ', fp), putc(' ', fp);
476     fprintf(fp, "</%s>\n", name);
477 }
478 
479 
480 static void
481 printHierarchy(FILE * fp, tOptDesc * p)
482 {
483     int opt_ct;
484     tArgList * al = p->optCookie;
485     void ** opt_list;
486 
487     if (al == NULL)
488         return;
489 
490     opt_ct   = al->useCt;
491     opt_list = (void **)(intptr_t)al->apzArgs;
492 
493     if (opt_ct <= 0)
494         return;
495 
496     do  {
497         tOptionValue const * base = *(opt_list++);
498         tOptionValue const * ovp = optionGetValue(base, NULL);
499 
500         if (ovp == NULL)
501             continue;
502 
503         fprintf(fp, "<%s type=nested>\n", p->pz_Name);
504 
505         do  {
506             print_a_value(fp, 1, p, ovp);
507 
508         } while (ovp = optionNextValue(base, ovp),
509                  ovp != NULL);
510 
511         fprintf(fp, "</%s>\n", p->pz_Name);
512     } while (--opt_ct > 0);
513 }
514 
515 
516 static FILE *
517 openSaveFile(tOptions* pOpts)
518 {
519     FILE*     fp;
520 
521     {
522         int   free_name = 0;
523         tCC*  pzFName = findFileName(pOpts, &free_name);
524         if (pzFName == NULL)
525             return NULL;
526 
527         fp = fopen(pzFName, "w" FOPEN_BINARY_FLAG);
528         if (fp == NULL) {
529             fprintf(stderr, zWarn, pOpts->pzProgName);
530             fprintf(stderr, zNoCreat, errno, strerror(errno), pzFName);
531             if (free_name)
532                 AGFREE(pzFName);
533             return fp;
534         }
535 
536         if (free_name)
537             AGFREE(pzFName);
538     }
539 
540     {
541         char const*  pz = pOpts->pzUsageTitle;
542         fputs("#  ", fp);
543         do { fputc(*pz, fp); } while (*(pz++) != '\n');
544     }
545 
546     {
547         time_t  timeVal = time(NULL);
548         char*   pzTime  = ctime(&timeVal);
549 
550         fprintf(fp, zPresetFile, pzTime);
551 #ifdef HAVE_ALLOCATED_CTIME
552         /*
553          *  The return values for ctime(), localtime(), and gmtime()
554          *  normally point to static data that is overwritten by each call.
555          *  The test to detect allocated ctime, so we leak the memory.
556          */
557         AGFREE((void*)pzTime);
558 #endif
559     }
560 
561     return fp;
562 }
563 
564 static void
565 printNoArgOpt(FILE * fp, tOptDesc * p, tOptDesc * pOD)
566 {
567     /*
568      * The aliased to argument indicates whether or not the option
569      * is "disabled".  However, the original option has the name
570      * string, so we get that there, not with "p".
571      */
572     char const * pznm =
573         (DISABLED_OPT(p)) ? pOD->pz_DisableName : pOD->pz_Name;
574     /*
575      *  If the option was disabled and the disablement name is NULL,
576      *  then the disablement was caused by aliasing.
577      *  Use the name as the string to emit.
578      */
579     if (pznm == NULL)
580         pznm = pOD->pz_Name;
581 
582     fprintf(fp, "%s\n", pznm);
583 }
584 
585 static void
586 printStringArg(FILE * fp, tOptDesc * pOD)
587 {
588     if (pOD->fOptState & OPTST_STACKED) {
589         tArgList*  pAL = (tArgList*)pOD->optCookie;
590         int        uct = pAL->useCt;
591         tCC**      ppz = pAL->apzArgs;
592 
593         /*
594          *  un-disable multiple copies of disabled options.
595          */
596         if (uct > 1)
597             pOD->fOptState &= ~OPTST_DISABLED;
598 
599         while (uct-- > 0)
600             printEntry(fp, pOD, *(ppz++));
601     } else {
602         printEntry(fp, pOD, pOD->optArg.argString);
603     }
604 }
605 
606 static void
607 printEnumArg(FILE * fp, tOptDesc * pOD)
608 {
609     uintptr_t val = pOD->optArg.argEnum;
610 
611     /*
612      *  This is a magic incantation that will convert the
613      *  bit flag values back into a string suitable for printing.
614      */
615     (*(pOD->pOptProc))(OPTPROC_RETURN_VALNAME, pOD);
616     printEntry(fp, pOD, (void*)(intptr_t)(pOD->optArg.argString));
617 
618     pOD->optArg.argEnum = val;
619 }
620 
621 static void
622 printSetMemberArg(FILE * fp, tOptDesc * pOD)
623 {
624     uintptr_t val = pOD->optArg.argEnum;
625 
626     /*
627      *  This is a magic incantation that will convert the
628      *  bit flag values back into a string suitable for printing.
629      */
630     (*(pOD->pOptProc))(OPTPROC_RETURN_VALNAME, pOD);
631     printEntry(fp, pOD, (void*)(intptr_t)(pOD->optArg.argString));
632 
633     if (pOD->optArg.argString != NULL) {
634         /*
635          *  set membership strings get allocated
636          */
637         AGFREE(pOD->optArg.argString);
638         pOD->fOptState &= ~OPTST_ALLOC_ARG;
639     }
640 
641     pOD->optArg.argEnum = val;
642 }
643 
644 static void
645 printFileArg(FILE * fp, tOptDesc * pOD, tOptions* pOpts)
646 {
647     /*
648      *  If the cookie is not NULL, then it has the file name, period.
649      *  Otherwise, if we have a non-NULL string argument, then....
650      */
651     if (pOD->optCookie != NULL)
652         printEntry(fp, pOD, pOD->optCookie);
653 
654     else if (HAS_originalOptArgArray(pOpts)) {
655         char const * orig =
656             pOpts->originalOptArgArray[pOD->optIndex].argString;
657 
658         if (pOD->optArg.argString == orig)
659             return;
660 
661         printEntry(fp, pOD, pOD->optArg.argString);
662     }
663 }
664 
665 
666 /*=export_func  optionSaveFile
667  *
668  * what:  saves the option state to a file
669  *
670  * arg:   tOptions*,   pOpts,  program options descriptor
671  *
672  * doc:
673  *
674  * This routine will save the state of option processing to a file.  The name
675  * of that file can be specified with the argument to the @code{--save-opts}
676  * option, or by appending the @code{rcfile} attribute to the last
677  * @code{homerc} attribute.  If no @code{rcfile} attribute was specified, it
678  * will default to @code{.@i{programname}rc}.  If you wish to specify another
679  * file, you should invoke the @code{SET_OPT_SAVE_OPTS(@i{filename})} macro.
680  *
681  * The recommend usage is as follows:
682  * @example
683  *    optionProcess(&progOptions, argc, argv);
684  *    if (i_want_a_non_standard_place_for_this)
685  *        SET_OPT_SAVE_OPTS("myfilename");
686  *    optionSaveFile(&progOptions);
687  * @end example
688  *
689  * err:
690  *
691  * If no @code{homerc} file was specified, this routine will silently return
692  * and do nothing.  If the output file cannot be created or updated, a message
693  * will be printed to @code{stderr} and the routine will return.
694 =*/
695 void
696 optionSaveFile(tOptions* pOpts)
697 {
698     tOptDesc* pOD;
699     int       ct;
700     FILE*     fp = openSaveFile(pOpts);
701 
702     if (fp == NULL)
703         return;
704 
705     /*
706      *  FOR each of the defined options, ...
707      */
708     ct  = pOpts->presetOptCt;
709     pOD = pOpts->pOptDesc;
710     do  {
711         tOptDesc*  p;
712 
713         /*
714          *  IF    the option has not been defined
715          *     OR it does not take an initialization value
716          *     OR it is equivalenced to another option
717          *  THEN continue (ignore it)
718          *
719          *  Equivalenced options get picked up when the equivalenced-to
720          *  option is processed.
721          */
722         if (UNUSED_OPT(pOD))
723             continue;
724 
725         if ((pOD->fOptState & OPTST_DO_NOT_SAVE_MASK) != 0)
726             continue;
727 
728         if (  (pOD->optEquivIndex != NO_EQUIVALENT)
729            && (pOD->optEquivIndex != pOD->optIndex))
730             continue;
731 
732         /*
733          *  The option argument data are found at the equivalenced-to option,
734          *  but the actual option argument type comes from the original
735          *  option descriptor.  Be careful!
736          */
737         p = ((pOD->fOptState & OPTST_EQUIVALENCE) != 0)
738             ? (pOpts->pOptDesc + pOD->optActualIndex) : pOD;
739 
740         switch (OPTST_GET_ARGTYPE(pOD->fOptState)) {
741         case OPARG_TYPE_NONE:
742             printNoArgOpt(fp, p, pOD);
743             break;
744 
745         case OPARG_TYPE_NUMERIC:
746             printEntry(fp, p, (void*)(p->optArg.argInt));
747             break;
748 
749         case OPARG_TYPE_STRING:
750             printStringArg(fp, p);
751             break;
752 
753         case OPARG_TYPE_ENUMERATION:
754             printEnumArg(fp, p);
755             break;
756 
757         case OPARG_TYPE_MEMBERSHIP:
758             printSetMemberArg(fp, p);
759             break;
760 
761         case OPARG_TYPE_BOOLEAN:
762             printEntry(fp, p, p->optArg.argBool ? "true" : "false");
763             break;
764 
765         case OPARG_TYPE_HIERARCHY:
766             printHierarchy(fp, p);
767             break;
768 
769         case OPARG_TYPE_FILE:
770             printFileArg(fp, p, pOpts);
771             break;
772 
773         default:
774             break; /* cannot handle - skip it */
775         }
776     } while (pOD++, (--ct > 0));
777 
778     fclose(fp);
779 }
780 /*
781  * Local Variables:
782  * mode: C
783  * c-file-style: "stroustrup"
784  * indent-tabs-mode: nil
785  * End:
786  * end of autoopts/save.c */
787