third_party: Update popt to 1.16 release
[samba.git] / third_party / popt / poptconfig.c
1 /** \ingroup popt
2  * \file popt/poptconfig.c
3  */
4
5 /* (C) 1998-2002 Red Hat, Inc. -- Licensing details are in the COPYING
6    file accompanying popt source distributions, available from 
7    ftp://ftp.rpm.org/pub/rpm/dist. */
8
9 #include "system.h"
10 #include "poptint.h"
11 #include <sys/stat.h>
12
13 #if defined(HAVE_FNMATCH_H)
14 #include <fnmatch.h>
15
16 #if defined(__LCLINT__)
17 /*@-declundef -exportheader -incondefs -protoparammatch -redecl -type @*/
18 extern int fnmatch (const char *__pattern, const char *__name, int __flags)
19         /*@*/;
20 /*@=declundef =exportheader =incondefs =protoparammatch =redecl =type @*/
21 #endif  /* __LCLINT__ */
22 #endif
23
24 #if defined(HAVE_GLOB_H)
25 #include <glob.h>
26
27 #if defined(__LCLINT__)
28 /*@-declundef -exportheader -incondefs -protoparammatch -redecl -type @*/
29 extern int glob (const char *__pattern, int __flags,
30                 /*@null@*/ int (*__errfunc) (const char *, int),
31                 /*@out@*/ glob_t *__pglob)
32         /*@globals errno, fileSystem @*/
33         /*@modifies *__pglob, errno, fileSystem @*/;
34
35 /* XXX only annotation is a white lie */
36 extern void globfree (/*@only@*/ glob_t *__pglob)
37         /*@modifies *__pglob @*/;
38
39 /* XXX _GNU_SOURCE ifdef and/or retrofit is needed for portability. */
40 extern int glob_pattern_p (const char *__pattern, int __quote)
41         /*@*/;
42 /*@=declundef =exportheader =incondefs =protoparammatch =redecl =type @*/
43 #endif  /* __LCLINT__ */
44
45 #if !defined(__GLIBC__)
46 /* Return nonzero if PATTERN contains any metacharacters.
47    Metacharacters can be quoted with backslashes if QUOTE is nonzero.  */
48 static int
49 glob_pattern_p (const char * pattern, int quote)
50         /*@*/
51 {
52     const char * p;
53     int open = 0;
54
55     for (p = pattern; *p != '\0'; ++p)
56     switch (*p) {
57     case '?':
58     case '*':
59         return 1;
60         /*@notreached@*/ /*@switchbreak@*/ break;
61     case '\\':
62         if (quote && p[1] != '\0')
63           ++p;
64         /*@switchbreak@*/ break;
65     case '[':
66         open = 1;
67         /*@switchbreak@*/ break;
68     case ']':
69         if (open)
70           return 1;
71         /*@switchbreak@*/ break;
72     }
73     return 0;
74 }
75 #endif  /* !defined(__GLIBC__) */
76
77 /*@unchecked@*/
78 static int poptGlobFlags = 0;
79
80 static int poptGlob_error(/*@unused@*/ UNUSED(const char * epath),
81                 /*@unused@*/ UNUSED(int eerrno))
82         /*@*/
83 {
84     return 1;
85 }
86 #endif  /* HAVE_GLOB_H */
87
88 /**
89  * Return path(s) from a glob pattern.
90  * @param con           context
91  * @param pattern       glob pattern
92  * @retval *acp         no. of paths
93  * @retval *avp         array of paths
94  * @return              0 on success
95  */
96 static int poptGlob(/*@unused@*/ UNUSED(poptContext con), const char * pattern,
97                 /*@out@*/ int * acp, /*@out@*/ const char *** avp)
98         /*@modifies *acp, *avp @*/
99 {
100     const char * pat = pattern;
101     int rc = 0;         /* assume success */
102
103     /* XXX skip the attention marker. */
104     if (pat[0] == '@' && pat[1] != '(')
105         pat++;
106
107 #if defined(HAVE_GLOB_H)
108     if (glob_pattern_p(pat, 0)) {
109         glob_t _g, *pglob = &_g;
110
111         if (!glob(pat, poptGlobFlags, poptGlob_error, pglob)) {
112             if (acp) {
113                 *acp = (int) pglob->gl_pathc;
114                 pglob->gl_pathc = 0;
115             }
116             if (avp) {
117 /*@-onlytrans@*/
118                 *avp = (const char **) pglob->gl_pathv;
119 /*@=onlytrans@*/
120                 pglob->gl_pathv = NULL;
121             }
122 /*@-nullstate@*/
123             globfree(pglob);
124 /*@=nullstate@*/
125         } else
126             rc = POPT_ERROR_ERRNO;
127     } else
128 #endif  /* HAVE_GLOB_H */
129     {
130         if (acp)
131             *acp = 1;
132         if (avp && (*avp = calloc((size_t)(1 + 1), sizeof (**avp))) != NULL)
133             (*avp)[0] = xstrdup(pat);
134     }
135
136     return rc;
137 }
138
139 /*@access poptContext @*/
140
141 int poptSaneFile(const char * fn)
142 {
143     struct stat sb;
144     uid_t uid = getuid();
145
146     if (stat(fn, &sb) == -1)
147         return 1;
148     if ((uid_t)sb.st_uid != uid)
149         return 0;
150     if (!S_ISREG(sb.st_mode))
151         return 0;
152 /*@-bitwisesigned@*/
153     if (sb.st_mode & (S_IWGRP|S_IWOTH))
154         return 0;
155 /*@=bitwisesigned@*/
156     return 1;
157 }
158
159 int poptReadFile(const char * fn, char ** bp, size_t * nbp, int flags)
160 {
161     int fdno;
162     char * b = NULL;
163     off_t nb = 0;
164     char * s, * t, * se;
165     int rc = POPT_ERROR_ERRNO;  /* assume failure */
166
167     fdno = open(fn, O_RDONLY);
168     if (fdno < 0)
169         goto exit;
170
171     if ((nb = lseek(fdno, 0, SEEK_END)) == (off_t)-1
172      || lseek(fdno, 0, SEEK_SET) == (off_t)-1
173      || (b = calloc(sizeof(*b), (size_t)nb + 1)) == NULL
174      || read(fdno, (char *)b, (size_t)nb) != (ssize_t)nb)
175     {
176         int oerrno = errno;
177         (void) close(fdno);
178         errno = oerrno;
179         goto exit;
180     }
181     if (close(fdno) == -1)
182         goto exit;
183     if (b == NULL) {
184         rc = POPT_ERROR_MALLOC;
185         goto exit;
186     }
187     rc = 0;
188
189    /* Trim out escaped newlines. */
190 /*@-bitwisesigned@*/
191     if (flags & POPT_READFILE_TRIMNEWLINES)
192 /*@=bitwisesigned@*/
193     {
194         for (t = b, s = b, se = b + nb; *s && s < se; s++) {
195             switch (*s) {
196             case '\\':
197                 if (s[1] == '\n') {
198                     s++;
199                     continue;
200                 }
201                 /*@fallthrough@*/
202             default:
203                 *t++ = *s;
204                 /*@switchbreak@*/ break;
205             }
206         }
207         *t++ = '\0';
208         nb = (off_t)(t - b);
209     }
210
211 exit:
212     if (rc != 0) {
213 /*@-usedef@*/
214         if (b)
215             free(b);
216 /*@=usedef@*/
217         b = NULL;
218         nb = 0;
219     }
220     if (bp)
221         *bp = b;
222 /*@-usereleased@*/
223     else if (b)
224         free(b);
225 /*@=usereleased@*/
226     if (nbp)
227         *nbp = (size_t)nb;
228 /*@-compdef -nullstate @*/      /* XXX cannot annotate char ** correctly */
229     return rc;
230 /*@=compdef =nullstate @*/
231 }
232
233 /**
234  * Check for application match.
235  * @param con           context
236  * @param s             config application name
237  * return               0 if config application matches
238  */
239 static int configAppMatch(poptContext con, const char * s)
240         /*@*/
241 {
242     int rc = 1;
243
244     if (con->appName == NULL)   /* XXX can't happen. */
245         return rc;
246
247 #if defined(HAVE_GLOB_H) && defined(HAVE_FNMATCH_H)
248     if (glob_pattern_p(s, 1)) {
249 /*@-bitwisesigned@*/
250         static int flags = FNM_PATHNAME | FNM_PERIOD;
251 #ifdef FNM_EXTMATCH
252         flags |= FNM_EXTMATCH;
253 #endif
254 /*@=bitwisesigned@*/
255         rc = fnmatch(s, con->appName, flags);
256     } else
257 #endif
258         rc = strcmp(s, con->appName);
259     return rc;
260 }
261
262 /*@-compmempass@*/      /* FIX: item->option.longName kept, not dependent. */
263 static int poptConfigLine(poptContext con, char * line)
264         /*@globals fileSystem, internalState @*/
265         /*@modifies con, fileSystem, internalState @*/
266 {
267     char *b = NULL;
268     size_t nb = 0;
269     char * se = line;
270     const char * appName;
271     const char * entryType;
272     const char * opt;
273     struct poptItem_s item_buf;
274     poptItem item = &item_buf;
275     int i, j;
276     int rc = POPT_ERROR_BADCONFIG;
277
278     if (con->appName == NULL)
279         goto exit;
280     
281     memset(item, 0, sizeof(*item));
282
283     appName = se;
284     while (*se != '\0' && !_isspaceptr(se)) se++;
285     if (*se == '\0')
286         goto exit;
287     else
288         *se++ = '\0';
289
290     if (configAppMatch(con, appName)) goto exit;
291
292     while (*se != '\0' && _isspaceptr(se)) se++;
293     entryType = se;
294     while (*se != '\0' && !_isspaceptr(se)) se++;
295     if (*se != '\0') *se++ = '\0';
296
297     while (*se != '\0' && _isspaceptr(se)) se++;
298     if (*se == '\0') goto exit;
299     opt = se;
300     while (*se != '\0' && !_isspaceptr(se)) se++;
301     if (opt[0] == '-' && *se == '\0') goto exit;
302     if (*se != '\0') *se++ = '\0';
303
304     while (*se != '\0' && _isspaceptr(se)) se++;
305     if (opt[0] == '-' && *se == '\0') goto exit;
306
307 /*@-temptrans@*/ /* FIX: line alias is saved */
308     if (opt[0] == '-' && opt[1] == '-')
309         item->option.longName = opt + 2;
310     else if (opt[0] == '-' && opt[2] == '\0')
311         item->option.shortName = opt[1];
312     else {
313         const char * fn = opt;
314
315         /* XXX handle globs and directories in fn? */
316         if ((rc = poptReadFile(fn, &b, &nb, POPT_READFILE_TRIMNEWLINES)) != 0)
317             goto exit;
318         if (b == NULL || nb == 0)
319             goto exit;
320
321         /* Append remaining text to the interpolated file option text. */
322         if (*se != '\0') {
323             size_t nse = strlen(se) + 1;
324             if ((b = realloc(b, (nb + nse))) == NULL)   /* XXX can't happen */
325                 goto exit;
326             (void) stpcpy( stpcpy(&b[nb-1], " "), se);
327             nb += nse;
328         }
329         se = b;
330
331         /* Use the basename of the path as the long option name. */
332         {   const char * longName = strrchr(fn, '/');
333             if (longName != NULL)
334                 longName++;
335             else
336                 longName = fn;
337             if (longName == NULL)       /* XXX can't happen. */
338                 goto exit;
339             /* Single character basenames are treated as short options. */
340             if (longName[1] != '\0')
341                 item->option.longName = longName;
342             else
343                 item->option.shortName = longName[0];
344         }
345     }
346 /*@=temptrans@*/
347
348     if (poptParseArgvString(se, &item->argc, &item->argv)) goto exit;
349
350 /*@-modobserver@*/
351     item->option.argInfo = POPT_ARGFLAG_DOC_HIDDEN;
352     for (i = 0, j = 0; i < item->argc; i++, j++) {
353         const char * f;
354         if (!strncmp(item->argv[i], "--POPTdesc=", sizeof("--POPTdesc=")-1)) {
355             f = item->argv[i] + sizeof("--POPTdesc=");
356             if (f[0] == '$' && f[1] == '"') f++;
357             item->option.descrip = f;
358             item->option.argInfo &= ~POPT_ARGFLAG_DOC_HIDDEN;
359             j--;
360         } else
361         if (!strncmp(item->argv[i], "--POPTargs=", sizeof("--POPTargs=")-1)) {
362             f = item->argv[i] + sizeof("--POPTargs=");
363             if (f[0] == '$' && f[1] == '"') f++;
364             item->option.argDescrip = f;
365             item->option.argInfo &= ~POPT_ARGFLAG_DOC_HIDDEN;
366             item->option.argInfo |= POPT_ARG_STRING;
367             j--;
368         } else
369         if (j != i)
370             item->argv[j] = item->argv[i];
371     }
372     if (j != i) {
373         item->argv[j] = NULL;
374         item->argc = j;
375     }
376 /*@=modobserver@*/
377
378 /*@-nullstate@*/ /* FIX: item->argv[] may be NULL */
379     if (!strcmp(entryType, "alias"))
380         rc = poptAddItem(con, item, 0);
381     else if (!strcmp(entryType, "exec"))
382         rc = poptAddItem(con, item, 1);
383 /*@=nullstate@*/
384 exit:
385     rc = 0;     /* XXX for now, always return success */
386     if (b)
387         free(b);
388     return rc;
389 }
390 /*@=compmempass@*/
391
392 int poptReadConfigFile(poptContext con, const char * fn)
393 {
394     char * b = NULL, *be;
395     size_t nb = 0;
396     const char *se;
397     char *t, *te;
398     int rc;
399     int xx;
400
401     if ((rc = poptReadFile(fn, &b, &nb, POPT_READFILE_TRIMNEWLINES)) != 0)
402         return (errno == ENOENT ? 0 : rc);
403     if (b == NULL || nb == 0)
404         return POPT_ERROR_BADCONFIG;
405
406     if ((t = malloc(nb + 1)) == NULL)
407         goto exit;
408     te = t;
409
410     be = (b + nb);
411     for (se = b; se < be; se++) {
412         switch (*se) {
413           case '\n':
414             *te = '\0';
415             te = t;
416             while (*te && _isspaceptr(te)) te++;
417             if (*te && *te != '#')
418                 xx = poptConfigLine(con, te);
419             /*@switchbreak@*/ break;
420 /*@-usedef@*/   /* XXX *se may be uninitialized */
421           case '\\':
422             *te = *se++;
423             /* \ at the end of a line does not insert a \n */
424             if (se < be && *se != '\n') {
425                 te++;
426                 *te++ = *se;
427             }
428             /*@switchbreak@*/ break;
429           default:
430             *te++ = *se;
431             /*@switchbreak@*/ break;
432 /*@=usedef@*/
433         }
434     }
435
436     free(t);
437     rc = 0;
438
439 exit:
440     if (b)
441         free(b);
442     return rc;
443 }
444
445 int poptReadConfigFiles(poptContext con, const char * paths)
446 {
447     char * buf = (paths ? xstrdup(paths) : NULL);
448     const char * p;
449     char * pe;
450     int rc = 0;         /* assume success */
451
452     for (p = buf; p != NULL && *p != '\0'; p = pe) {
453         const char ** av = NULL;
454         int ac = 0;
455         int i;
456         int xx;
457
458         /* locate start of next path element */
459         pe = strchr(p, ':');
460         if (pe != NULL && *pe == ':')
461             *pe++ = '\0';
462         else
463             pe = (char *) (p + strlen(p));
464
465         xx = poptGlob(con, p, &ac, &av);
466
467         /* work-off each resulting file from the path element */
468         for (i = 0; i < ac; i++) {
469             const char * fn = av[i];
470             if (av[i] == NULL)  /* XXX can't happen */
471                 /*@innercontinue@*/ continue;
472             /* XXX should '@' attention be pushed into poptReadConfigFile? */
473             if (p[0] == '@' && p[1] != '(') {
474                 if (fn[0] == '@' && fn[1] != '(')
475                     fn++;
476                 xx = poptSaneFile(fn);
477                 if (!xx && rc == 0)
478                     rc = POPT_ERROR_BADCONFIG;
479                 /*@innercontinue@*/ continue;
480             }
481             xx = poptReadConfigFile(con, fn);
482             if (xx && rc == 0)
483                 rc = xx;
484             free((void *)av[i]);
485             av[i] = NULL;
486         }
487         free(av);
488         av = NULL;
489     }
490
491 /*@-usedef@*/
492     if (buf)
493         free(buf);
494 /*@=usedef@*/
495
496     return rc;
497 }
498
499 int poptReadDefaultConfig(poptContext con, /*@unused@*/ UNUSED(int useEnv))
500 {
501     static const char _popt_sysconfdir[] = POPT_SYSCONFDIR "/popt";
502     static const char _popt_etc[] = "/etc/popt";
503     char * home;
504     struct stat sb;
505     int rc = 0;         /* assume success */
506
507     if (con->appName == NULL) goto exit;
508
509     if (strcmp(_popt_sysconfdir, _popt_etc)) {
510         rc = poptReadConfigFile(con, _popt_sysconfdir);
511         if (rc) goto exit;
512     }
513
514     rc = poptReadConfigFile(con, _popt_etc);
515     if (rc) goto exit;
516
517 #if defined(HAVE_GLOB_H)
518     if (!stat("/etc/popt.d", &sb) && S_ISDIR(sb.st_mode)) {
519         const char ** av = NULL;
520         int ac = 0;
521         int i;
522
523         if ((rc = poptGlob(con, "/etc/popt.d/*", &ac, &av)) == 0) {
524             for (i = 0; rc == 0 && i < ac; i++) {
525                 const char * fn = av[i];
526                 if (fn == NULL || strstr(fn, ".rpmnew") || strstr(fn, ".rpmsave"))
527                     continue;
528                 if (!stat(fn, &sb)) {
529                     if (!S_ISREG(sb.st_mode) && !S_ISLNK(sb.st_mode))
530                         continue;
531                 }
532                 rc = poptReadConfigFile(con, fn);
533                 free((void *)av[i]);
534                 av[i] = NULL;
535             }
536             free(av);
537             av = NULL;
538         }
539     }
540     if (rc) goto exit;
541 #endif
542
543     if ((home = getenv("HOME"))) {
544         char * fn = malloc(strlen(home) + 20);
545         if (fn != NULL) {
546             (void) stpcpy(stpcpy(fn, home), "/.popt");
547             rc = poptReadConfigFile(con, fn);
548             free(fn);
549         } else
550             rc = POPT_ERROR_ERRNO;
551         if (rc) goto exit;
552     }
553
554 exit:
555     return rc;
556 }
557
558 poptContext
559 poptFini(poptContext con)
560 {
561     return poptFreeContext(con);
562 }
563
564 poptContext
565 poptInit(int argc, const char ** argv,
566                 const struct poptOption * options, const char * configPaths)
567 {
568     poptContext con = NULL;
569     const char * argv0;
570
571     if (argv == NULL || argv[0] == NULL || options == NULL)
572         return con;
573
574     if ((argv0 = strrchr(argv[0], '/')) != NULL) argv0++;
575     else argv0 = argv[0];
576
577     con = poptGetContext(argv0, argc, (const char **)argv, options, 0);
578     if (con != NULL&& poptReadConfigFiles(con, configPaths))
579         con = poptFini(con);
580
581     return con;
582 }