xref: /freebsd/contrib/ntp/sntp/libopts/save.c (revision 4c8945a06b01a5c8122cdeb402af36bb46a06acc)
1 
2 /*
3  *  save.c  $Id: save.c,v 4.18 2007/04/15 19:01:18 bkorb Exp $
4  * Time-stamp:      "2007-04-15 11:11:10 bkorb"
5  *
6  *  This module's routines will take the currently set options and
7  *  store them into an ".rc" file for re-interpretation the next
8  *  time the invoking program is run.
9  */
10 
11 /*
12  *  Automated Options copyright 1992-2007 Bruce Korb
13  *
14  *  Automated Options is free software.
15  *  You may redistribute it and/or modify it under the terms of the
16  *  GNU General Public License, as published by the Free Software
17  *  Foundation; either version 2, or (at your option) any later version.
18  *
19  *  Automated Options is distributed in the hope that it will be useful,
20  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
21  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
22  *  GNU General Public License for more details.
23  *
24  *  You should have received a copy of the GNU General Public License
25  *  along with Automated Options.  See the file "COPYING".  If not,
26  *  write to:  The Free Software Foundation, Inc.,
27  *             51 Franklin Street, Fifth Floor,
28  *             Boston, MA  02110-1301, USA.
29  *
30  * As a special exception, Bruce Korb gives permission for additional
31  * uses of the text contained in his release of AutoOpts.
32  *
33  * The exception is that, if you link the AutoOpts library with other
34  * files to produce an executable, this does not by itself cause the
35  * resulting executable to be covered by the GNU General Public License.
36  * Your use of that executable is in no way restricted on account of
37  * linking the AutoOpts library code into it.
38  *
39  * This exception does not however invalidate any other reasons why
40  * the executable file might be covered by the GNU General Public License.
41  *
42  * This exception applies only to the code released by Bruce Korb under
43  * the name AutoOpts.  If you copy code from other sources under the
44  * General Public License into a copy of AutoOpts, as the General Public
45  * License permits, the exception does not apply to the code that you add
46  * in this way.  To avoid misleading anyone as to the status of such
47  * modified files, you must delete this exception notice from them.
48  *
49  * If you write modifications of your own for AutoOpts, it is your choice
50  * whether to permit this exception to apply to your modifications.
51  * If you do not wish that, delete this exception notice.
52  */
53 
54 tSCC  zWarn[] = "%s WARNING:  cannot save options - ";
55 
56 /* = = = START-STATIC-FORWARD = = = */
57 /* static forward declarations maintained by :mkfwd */
58 static tCC*
59 findDirName( tOptions* pOpts, int* p_free );
60 
61 static tCC*
62 findFileName( tOptions* pOpts, int* p_free_name );
63 
64 static void
65 printEntry(
66     FILE *     fp,
67     tOptDesc * p,
68     tCC*       pzLA );
69 /* = = = END-STATIC-FORWARD = = = */
70 
71 static tCC*
72 findDirName( tOptions* pOpts, int* p_free )
73 {
74     tCC*  pzDir;
75 
76     if (pOpts->specOptIdx.save_opts == 0)
77         return NULL;
78 
79     pzDir = pOpts->pOptDesc[ pOpts->specOptIdx.save_opts ].optArg.argString;
80     if ((pzDir != NULL) && (*pzDir != NUL))
81         return pzDir;
82 
83     /*
84      *  This function only works if there is a directory where
85      *  we can stash the RC (INI) file.
86      */
87     {
88         tCC* const* papz = pOpts->papzHomeList;
89         if (papz == NULL)
90             return NULL;
91 
92         while (papz[1] != NULL) papz++;
93         pzDir = *papz;
94     }
95 
96     /*
97      *  IF it does not require deciphering an env value, then just copy it
98      */
99     if (*pzDir != '$')
100         return pzDir;
101 
102     {
103         tCC*  pzEndDir = strchr( ++pzDir, DIRCH );
104         char* pzFileName;
105         char* pzEnv;
106 
107         if (pzEndDir != NULL) {
108             char z[ AO_NAME_SIZE ];
109             if ((pzEndDir - pzDir) > AO_NAME_LIMIT )
110                 return NULL;
111             strncpy( z, pzDir, (size_t)(pzEndDir - pzDir) );
112             z[ (pzEndDir - pzDir) ] = NUL;
113             pzEnv = getenv( z );
114         } else {
115 
116             /*
117              *  Make sure we can get the env value (after stripping off
118              *  any trailing directory or file names)
119              */
120             pzEnv = getenv( pzDir );
121         }
122 
123         if (pzEnv == NULL) {
124             fprintf( stderr, zWarn, pOpts->pzProgName );
125             fprintf( stderr, zNotDef, pzDir );
126             return NULL;
127         }
128 
129         if (pzEndDir == NULL)
130             return pzEnv;
131 
132         {
133             size_t sz = strlen( pzEnv ) + strlen( pzEndDir ) + 2;
134             pzFileName = (char*)AGALOC( sz, "dir name" );
135         }
136 
137         if (pzFileName == NULL)
138             return NULL;
139 
140         *p_free = 1;
141         /*
142          *  Glue together the full name into the allocated memory.
143          *  FIXME: We lose track of this memory.
144          */
145         sprintf( pzFileName, "%s/%s", pzEnv, pzEndDir );
146         return pzFileName;
147     }
148 }
149 
150 
151 static tCC*
152 findFileName( tOptions* pOpts, int* p_free_name )
153 {
154     tCC*   pzDir;
155     struct stat stBuf;
156     int    free_dir_name = 0;
157 
158     pzDir = findDirName( pOpts, &free_dir_name );
159     if (pzDir == NULL)
160         return NULL;
161 
162     /*
163      *  See if we can find the specified directory.  We use a once-only loop
164      *  structure so we can bail out early.
165      */
166     if (stat( pzDir, &stBuf ) != 0) do {
167 
168         /*
169          *  IF we could not, check to see if we got a full
170          *  path to a file name that has not been created yet.
171          */
172         if (errno == ENOENT) {
173             char z[AG_PATH_MAX];
174 
175             /*
176              *  Strip off the last component, stat the remaining string and
177              *  that string must name a directory
178              */
179             char* pzDirCh = strrchr( pzDir, DIRCH );
180             if (pzDirCh == NULL) {
181                 stBuf.st_mode = S_IFREG;
182                 continue;  /* bail out of error condition */
183             }
184 
185             strncpy( z, pzDir, (size_t)(pzDirCh - pzDir));
186             z[ pzDirCh - pzDir ] = NUL;
187 
188             if (  (stat( z, &stBuf ) == 0)
189                && S_ISDIR( stBuf.st_mode )) {
190 
191                 /*
192                  *  We found the directory.  Restore the file name and
193                  *  mark the full name as a regular file
194                  */
195                 stBuf.st_mode = S_IFREG;
196                 continue;  /* bail out of error condition */
197             }
198         }
199 
200         /*
201          *  We got a bogus name.
202          */
203         fprintf( stderr, zWarn, pOpts->pzProgName );
204         fprintf( stderr, zNoStat, errno, strerror( errno ), pzDir );
205         if (free_dir_name)
206             AGFREE( (void*)pzDir );
207         return NULL;
208     } while (0);
209 
210     /*
211      *  IF what we found was a directory,
212      *  THEN tack on the config file name
213      */
214     if (S_ISDIR( stBuf.st_mode )) {
215         size_t sz = strlen( pzDir ) + strlen( pOpts->pzRcName ) + 2;
216 
217         {
218             char*  pzPath = (char*)AGALOC( sz, "file name" );
219 #ifdef HAVE_SNPRINTF
220             snprintf( pzPath, sz, "%s/%s", pzDir, pOpts->pzRcName );
221 #else
222             sprintf( pzPath, "%s/%s", pzDir, pOpts->pzRcName );
223 #endif
224             if (free_dir_name)
225                 AGFREE( (void*)pzDir );
226             pzDir = pzPath;
227             free_dir_name = 1;
228         }
229 
230         /*
231          *  IF we cannot stat the object for any reason other than
232          *     it does not exist, then we bail out
233          */
234         if (stat( pzDir, &stBuf ) != 0) {
235             if (errno != ENOENT) {
236                 fprintf( stderr, zWarn, pOpts->pzProgName );
237                 fprintf( stderr, zNoStat, errno, strerror( errno ),
238                          pzDir );
239                 AGFREE( (void*)pzDir );
240                 return NULL;
241             }
242 
243             /*
244              *  It does not exist yet, but it will be a regular file
245              */
246             stBuf.st_mode = S_IFREG;
247         }
248     }
249 
250     /*
251      *  Make sure that whatever we ultimately found, that it either is
252      *  or will soon be a file.
253      */
254     if (! S_ISREG( stBuf.st_mode )) {
255         fprintf( stderr, zWarn, pOpts->pzProgName );
256         fprintf( stderr, zNotFile, pzDir );
257         if (free_dir_name)
258             AGFREE( (void*)pzDir );
259         return NULL;
260     }
261 
262     /*
263      *  Get rid of the old file
264      */
265     unlink( pzDir );
266     *p_free_name = free_dir_name;
267     return pzDir;
268 }
269 
270 
271 static void
272 printEntry(
273     FILE *     fp,
274     tOptDesc * p,
275     tCC*       pzLA )
276 {
277     /*
278      *  There is an argument.  Pad the name so values line up.
279      *  Not disabled *OR* this got equivalenced to another opt,
280      *  then use current option name.
281      *  Otherwise, there must be a disablement name.
282      */
283     {
284         char const * pz;
285         if (! DISABLED_OPT(p) || (p->optEquivIndex != NO_EQUIVALENT))
286             pz = p->pz_Name;
287         else
288             pz = p->pz_DisableName;
289 
290         fprintf(fp, "%-18s", pz);
291     }
292     /*
293      *  IF the option is numeric only,
294      *  THEN the char pointer is really the number
295      */
296     if (OPTST_GET_ARGTYPE(p->fOptState) == OPARG_TYPE_NUMERIC)
297         fprintf( fp, "  %d\n", (int)(t_word)pzLA );
298 
299     /*
300      *  OTHERWISE, FOR each line of the value text, ...
301      */
302     else if (pzLA == NULL)
303         fputc( '\n', fp );
304 
305     else {
306         fputc( ' ', fp ); fputc( ' ', fp );
307         for (;;) {
308             tCC* pzNl = strchr( pzLA, '\n' );
309 
310             /*
311              *  IF this is the last line
312              *  THEN bail and print it
313              */
314             if (pzNl == NULL)
315                 break;
316 
317             /*
318              *  Print the continuation and the text from the current line
319              */
320             (void)fwrite( pzLA, (size_t)(pzNl - pzLA), (size_t)1, fp );
321             pzLA = pzNl+1; /* advance the Last Arg pointer */
322             fputs( "\\\n", fp );
323         }
324 
325         /*
326          *  Terminate the entry
327          */
328         fputs( pzLA, fp );
329         fputc( '\n', fp );
330     }
331 }
332 
333 
334 /*=export_func  optionSaveFile
335  *
336  * what:  saves the option state to a file
337  *
338  * arg:   tOptions*,   pOpts,  program options descriptor
339  *
340  * doc:
341  *
342  * This routine will save the state of option processing to a file.  The name
343  * of that file can be specified with the argument to the @code{--save-opts}
344  * option, or by appending the @code{rcfile} attribute to the last
345  * @code{homerc} attribute.  If no @code{rcfile} attribute was specified, it
346  * will default to @code{.@i{programname}rc}.  If you wish to specify another
347  * file, you should invoke the @code{SET_OPT_SAVE_OPTS( @i{filename} )} macro.
348  *
349  * err:
350  *
351  * If no @code{homerc} file was specified, this routine will silently return
352  * and do nothing.  If the output file cannot be created or updated, a message
353  * will be printed to @code{stderr} and the routine will return.
354 =*/
355 void
356 optionSaveFile( tOptions* pOpts )
357 {
358     tOptDesc* pOD;
359     int       ct;
360     FILE*     fp;
361 
362     {
363         int   free_name = 0;
364         tCC*  pzFName = findFileName( pOpts, &free_name );
365         if (pzFName == NULL)
366             return;
367 
368         fp = fopen( pzFName, "w" FOPEN_BINARY_FLAG );
369         if (fp == NULL) {
370             fprintf( stderr, zWarn, pOpts->pzProgName );
371             fprintf( stderr, zNoCreat, errno, strerror( errno ), pzFName );
372             if (free_name)
373                 AGFREE((void*) pzFName );
374             return;
375         }
376 
377         if (free_name)
378             AGFREE( (void*)pzFName );
379     }
380 
381     {
382         char const*  pz = pOpts->pzUsageTitle;
383         fputs( "#  ", fp );
384         do { fputc( *pz, fp ); } while (*(pz++) != '\n');
385     }
386 
387     {
388         time_t  timeVal = time( NULL );
389         char*   pzTime  = ctime( &timeVal );
390 
391         fprintf( fp, zPresetFile, pzTime );
392 #ifdef HAVE_ALLOCATED_CTIME
393         /*
394          *  The return values for ctime(), localtime(), and gmtime()
395          *  normally point to static data that is overwritten by each call.
396          *  The test to detect allocated ctime, so we leak the memory.
397          */
398         AGFREE( (void*)pzTime );
399 #endif
400     }
401 
402     /*
403      *  FOR each of the defined options, ...
404      */
405     ct  = pOpts->presetOptCt;
406     pOD = pOpts->pOptDesc;
407     do  {
408         int arg_state;
409         tOptDesc*  p;
410 
411         /*
412          *  IF    the option has not been defined
413          *     OR it does not take an initialization value
414          *     OR it is equivalenced to another option
415          *  THEN continue (ignore it)
416          */
417         if (UNUSED_OPT( pOD ))
418             continue;
419 
420         if ((pOD->fOptState & (OPTST_NO_INIT|OPTST_DOCUMENT|OPTST_OMITTED))
421             != 0)
422             continue;
423 
424         if (  (pOD->optEquivIndex != NO_EQUIVALENT)
425               && (pOD->optEquivIndex != pOD->optIndex))
426             continue;
427 
428         /*
429          *  Set a temporary pointer to the real option description
430          *  (i.e. account for equivalencing)
431          */
432         p = ((pOD->fOptState & OPTST_EQUIVALENCE) != 0)
433             ? (pOpts->pOptDesc + pOD->optActualIndex) : pOD;
434 
435         /*
436          *  IF    no arguments are allowed
437          *  THEN just print the name and continue
438          */
439         if (OPTST_GET_ARGTYPE(pOD->fOptState) == OPARG_TYPE_NONE) {
440             char const * pznm =
441                 (DISABLED_OPT( p )) ? p->pz_DisableName : p->pz_Name;
442             /*
443              *  If the option was disabled and the disablement name is NULL,
444              *  then the disablement was caused by aliasing.
445              *  Use the name as the string to emit.
446              */
447             if (pznm == NULL)
448                 pznm = p->pz_Name;
449 
450             fprintf(fp, "%s\n", pznm);
451             continue;
452         }
453 
454         arg_state = OPTST_GET_ARGTYPE(p->fOptState);
455         switch (arg_state) {
456         case 0:
457         case OPARG_TYPE_NUMERIC:
458             printEntry( fp, p, (void*)(p->optArg.argInt));
459             break;
460 
461         case OPARG_TYPE_STRING:
462             if (p->fOptState & OPTST_STACKED) {
463                 tArgList*  pAL = (tArgList*)p->optCookie;
464                 int        uct = pAL->useCt;
465                 tCC**      ppz = pAL->apzArgs;
466 
467                 /*
468                  *  Disallow multiple copies of disabled options.
469                  */
470                 if (uct > 1)
471                     p->fOptState &= ~OPTST_DISABLED;
472 
473                 while (uct-- > 0)
474                     printEntry( fp, p, *(ppz++) );
475             } else {
476                 printEntry( fp, p, p->optArg.argString );
477             }
478             break;
479 
480         case OPARG_TYPE_ENUMERATION:
481         case OPARG_TYPE_MEMBERSHIP:
482         {
483             uintptr_t val = p->optArg.argEnum;
484             /*
485              *  This is a magic incantation that will convert the
486              *  bit flag values back into a string suitable for printing.
487              */
488             (*(p->pOptProc))( (tOptions*)2UL, p );
489             printEntry( fp, p, (void*)(p->optArg.argString));
490 
491             if (  (p->optArg.argString != NULL)
492                && (arg_state != OPARG_TYPE_ENUMERATION)) {
493                 /*
494                  *  set membership strings get allocated
495                  */
496                 AGFREE( (void*)p->optArg.argString );
497                 p->fOptState &= ~OPTST_ALLOC_ARG;
498             }
499 
500             p->optArg.argEnum = val;
501             break;
502         }
503 
504         case OPARG_TYPE_BOOLEAN:
505             printEntry( fp, p, p->optArg.argBool ? "true" : "false" );
506             break;
507 
508         default:
509             break; /* cannot handle - skip it */
510         }
511     } while ( (pOD++), (--ct > 0));
512 
513     fclose( fp );
514 }
515 /*
516  * Local Variables:
517  * mode: C
518  * c-file-style: "stroustrup"
519  * indent-tabs-mode: nil
520  * End:
521  * end of autoopts/save.c */
522