third_party: Update popt to 1.16 release
[samba.git] / third_party / popt / poptconfig.c
index 837828ccf92341019272bfd14718f0c6e4eb26eb..f0a92e01bd5a4994d98fca88b6f83ed6d935d3f0 100644 (file)
 
 #include "system.h"
 #include "poptint.h"
+#include <sys/stat.h>
+
+#if defined(HAVE_FNMATCH_H)
+#include <fnmatch.h>
+
+#if defined(__LCLINT__)
+/*@-declundef -exportheader -incondefs -protoparammatch -redecl -type @*/
+extern int fnmatch (const char *__pattern, const char *__name, int __flags)
+       /*@*/;
+/*@=declundef =exportheader =incondefs =protoparammatch =redecl =type @*/
+#endif /* __LCLINT__ */
+#endif
+
+#if defined(HAVE_GLOB_H)
+#include <glob.h>
+
+#if defined(__LCLINT__)
+/*@-declundef -exportheader -incondefs -protoparammatch -redecl -type @*/
+extern int glob (const char *__pattern, int __flags,
+               /*@null@*/ int (*__errfunc) (const char *, int),
+               /*@out@*/ glob_t *__pglob)
+       /*@globals errno, fileSystem @*/
+       /*@modifies *__pglob, errno, fileSystem @*/;
+
+/* XXX only annotation is a white lie */
+extern void globfree (/*@only@*/ glob_t *__pglob)
+       /*@modifies *__pglob @*/;
+
+/* XXX _GNU_SOURCE ifdef and/or retrofit is needed for portability. */
+extern int glob_pattern_p (const char *__pattern, int __quote)
+        /*@*/;
+/*@=declundef =exportheader =incondefs =protoparammatch =redecl =type @*/
+#endif /* __LCLINT__ */
+
+#if !defined(__GLIBC__)
+/* Return nonzero if PATTERN contains any metacharacters.
+   Metacharacters can be quoted with backslashes if QUOTE is nonzero.  */
+static int
+glob_pattern_p (const char * pattern, int quote)
+       /*@*/
+{
+    const char * p;
+    int open = 0;
+
+    for (p = pattern; *p != '\0'; ++p)
+    switch (*p) {
+    case '?':
+    case '*':
+       return 1;
+       /*@notreached@*/ /*@switchbreak@*/ break;
+    case '\\':
+       if (quote && p[1] != '\0')
+         ++p;
+       /*@switchbreak@*/ break;
+    case '[':
+       open = 1;
+       /*@switchbreak@*/ break;
+    case ']':
+       if (open)
+         return 1;
+       /*@switchbreak@*/ break;
+    }
+    return 0;
+}
+#endif /* !defined(__GLIBC__) */
+
+/*@unchecked@*/
+static int poptGlobFlags = 0;
+
+static int poptGlob_error(/*@unused@*/ UNUSED(const char * epath),
+               /*@unused@*/ UNUSED(int eerrno))
+       /*@*/
+{
+    return 1;
+}
+#endif /* HAVE_GLOB_H */
+
+/**
+ * Return path(s) from a glob pattern.
+ * @param con          context
+ * @param pattern      glob pattern
+ * @retval *acp                no. of paths
+ * @retval *avp                array of paths
+ * @return             0 on success
+ */
+static int poptGlob(/*@unused@*/ UNUSED(poptContext con), const char * pattern,
+               /*@out@*/ int * acp, /*@out@*/ const char *** avp)
+       /*@modifies *acp, *avp @*/
+{
+    const char * pat = pattern;
+    int rc = 0;                /* assume success */
+
+    /* XXX skip the attention marker. */
+    if (pat[0] == '@' && pat[1] != '(')
+       pat++;
+
+#if defined(HAVE_GLOB_H)
+    if (glob_pattern_p(pat, 0)) {
+       glob_t _g, *pglob = &_g;
+
+       if (!glob(pat, poptGlobFlags, poptGlob_error, pglob)) {
+           if (acp) {
+               *acp = (int) pglob->gl_pathc;
+               pglob->gl_pathc = 0;
+           }
+           if (avp) {
+/*@-onlytrans@*/
+               *avp = (const char **) pglob->gl_pathv;
+/*@=onlytrans@*/
+               pglob->gl_pathv = NULL;
+           }
+/*@-nullstate@*/
+           globfree(pglob);
+/*@=nullstate@*/
+       } else
+           rc = POPT_ERROR_ERRNO;
+    } else
+#endif /* HAVE_GLOB_H */
+    {
+       if (acp)
+           *acp = 1;
+       if (avp && (*avp = calloc((size_t)(1 + 1), sizeof (**avp))) != NULL)
+           (*avp)[0] = xstrdup(pat);
+    }
+
+    return rc;
+}
+
+/*@access poptContext @*/
+
+int poptSaneFile(const char * fn)
+{
+    struct stat sb;
+    uid_t uid = getuid();
+
+    if (stat(fn, &sb) == -1)
+       return 1;
+    if ((uid_t)sb.st_uid != uid)
+       return 0;
+    if (!S_ISREG(sb.st_mode))
+       return 0;
+/*@-bitwisesigned@*/
+    if (sb.st_mode & (S_IWGRP|S_IWOTH))
+       return 0;
+/*@=bitwisesigned@*/
+    return 1;
+}
+
+int poptReadFile(const char * fn, char ** bp, size_t * nbp, int flags)
+{
+    int fdno;
+    char * b = NULL;
+    off_t nb = 0;
+    char * s, * t, * se;
+    int rc = POPT_ERROR_ERRNO; /* assume failure */
+
+    fdno = open(fn, O_RDONLY);
+    if (fdno < 0)
+       goto exit;
+
+    if ((nb = lseek(fdno, 0, SEEK_END)) == (off_t)-1
+     || lseek(fdno, 0, SEEK_SET) == (off_t)-1
+     || (b = calloc(sizeof(*b), (size_t)nb + 1)) == NULL
+     || read(fdno, (char *)b, (size_t)nb) != (ssize_t)nb)
+    {
+       int oerrno = errno;
+       (void) close(fdno);
+       errno = oerrno;
+       goto exit;
+    }
+    if (close(fdno) == -1)
+       goto exit;
+    if (b == NULL) {
+       rc = POPT_ERROR_MALLOC;
+       goto exit;
+    }
+    rc = 0;
+
+   /* Trim out escaped newlines. */
+/*@-bitwisesigned@*/
+    if (flags & POPT_READFILE_TRIMNEWLINES)
+/*@=bitwisesigned@*/
+    {
+       for (t = b, s = b, se = b + nb; *s && s < se; s++) {
+           switch (*s) {
+           case '\\':
+               if (s[1] == '\n') {
+                   s++;
+                   continue;
+               }
+               /*@fallthrough@*/
+           default:
+               *t++ = *s;
+               /*@switchbreak@*/ break;
+           }
+       }
+       *t++ = '\0';
+       nb = (off_t)(t - b);
+    }
+
+exit:
+    if (rc != 0) {
+/*@-usedef@*/
+       if (b)
+           free(b);
+/*@=usedef@*/
+       b = NULL;
+       nb = 0;
+    }
+    if (bp)
+       *bp = b;
+/*@-usereleased@*/
+    else if (b)
+       free(b);
+/*@=usereleased@*/
+    if (nbp)
+       *nbp = (size_t)nb;
+/*@-compdef -nullstate @*/     /* XXX cannot annotate char ** correctly */
+    return rc;
+/*@=compdef =nullstate @*/
+}
+
+/**
+ * Check for application match.
+ * @param con          context
+ * @param s            config application name
+ * return              0 if config application matches
+ */
+static int configAppMatch(poptContext con, const char * s)
+       /*@*/
+{
+    int rc = 1;
+
+    if (con->appName == NULL)  /* XXX can't happen. */
+       return rc;
+
+#if defined(HAVE_GLOB_H) && defined(HAVE_FNMATCH_H)
+    if (glob_pattern_p(s, 1)) {
+/*@-bitwisesigned@*/
+       static int flags = FNM_PATHNAME | FNM_PERIOD;
+#ifdef FNM_EXTMATCH
+       flags |= FNM_EXTMATCH;
+#endif
+/*@=bitwisesigned@*/
+       rc = fnmatch(s, con->appName, flags);
+    } else
+#endif
+       rc = strcmp(s, con->appName);
+    return rc;
+}
 
 /*@-compmempass@*/     /* FIX: item->option.longName kept, not dependent. */
-static void configLine(poptContext con, char * line)
-       /*@modifies con @*/
+static int poptConfigLine(poptContext con, char * line)
+       /*@globals fileSystem, internalState @*/
+       /*@modifies con, fileSystem, internalState @*/
 {
-    /*@-type@*/
-    int nameLength = strlen(con->appName);
-    /*@=type@*/
+    char *b = NULL;
+    size_t nb = 0;
+    char * se = line;
+    const char * appName;
     const char * entryType;
     const char * opt;
-    poptItem item = (poptItem)alloca(sizeof(*item));
+    struct poptItem_s item_buf;
+    poptItem item = &item_buf;
     int i, j;
+    int rc = POPT_ERROR_BADCONFIG;
+
+    if (con->appName == NULL)
+       goto exit;
     
-/*@-boundswrite@*/
     memset(item, 0, sizeof(*item));
 
-    /*@-type@*/
-    if (strncmp(line, con->appName, nameLength)) return;
-    /*@=type@*/
+    appName = se;
+    while (*se != '\0' && !_isspaceptr(se)) se++;
+    if (*se == '\0')
+       goto exit;
+    else
+       *se++ = '\0';
 
-    line += nameLength;
-    if (*line == '\0' || !isspace(*line)) return;
+    if (configAppMatch(con, appName)) goto exit;
 
-    while (*line != '\0' && isspace(*line)) line++;
-    entryType = line;
-    while (*line == '\0' || !isspace(*line)) line++;
-    *line++ = '\0';
+    while (*se != '\0' && _isspaceptr(se)) se++;
+    entryType = se;
+    while (*se != '\0' && !_isspaceptr(se)) se++;
+    if (*se != '\0') *se++ = '\0';
 
-    while (*line != '\0' && isspace(*line)) line++;
-    if (*line == '\0') return;
-    opt = line;
-    while (*line == '\0' || !isspace(*line)) line++;
-    *line++ = '\0';
+    while (*se != '\0' && _isspaceptr(se)) se++;
+    if (*se == '\0') goto exit;
+    opt = se;
+    while (*se != '\0' && !_isspaceptr(se)) se++;
+    if (opt[0] == '-' && *se == '\0') goto exit;
+    if (*se != '\0') *se++ = '\0';
 
-    while (*line != '\0' && isspace(*line)) line++;
-    if (*line == '\0') return;
+    while (*se != '\0' && _isspaceptr(se)) se++;
+    if (opt[0] == '-' && *se == '\0') goto exit;
 
-    /*@-temptrans@*/ /* FIX: line alias is saved */
+/*@-temptrans@*/ /* FIX: line alias is saved */
     if (opt[0] == '-' && opt[1] == '-')
        item->option.longName = opt + 2;
     else if (opt[0] == '-' && opt[2] == '\0')
        item->option.shortName = opt[1];
-    /*@=temptrans@*/
+    else {
+       const char * fn = opt;
 
-    if (poptParseArgvString(line, &item->argc, &item->argv)) return;
+       /* XXX handle globs and directories in fn? */
+       if ((rc = poptReadFile(fn, &b, &nb, POPT_READFILE_TRIMNEWLINES)) != 0)
+           goto exit;
+       if (b == NULL || nb == 0)
+           goto exit;
 
-    /*@-modobserver@*/
+       /* Append remaining text to the interpolated file option text. */
+       if (*se != '\0') {
+           size_t nse = strlen(se) + 1;
+           if ((b = realloc(b, (nb + nse))) == NULL)   /* XXX can't happen */
+               goto exit;
+           (void) stpcpy( stpcpy(&b[nb-1], " "), se);
+           nb += nse;
+       }
+       se = b;
+
+       /* Use the basename of the path as the long option name. */
+       {   const char * longName = strrchr(fn, '/');
+           if (longName != NULL)
+               longName++;
+           else
+               longName = fn;
+           if (longName == NULL)       /* XXX can't happen. */
+               goto exit;
+           /* Single character basenames are treated as short options. */
+           if (longName[1] != '\0')
+               item->option.longName = longName;
+           else
+               item->option.shortName = longName[0];
+       }
+    }
+/*@=temptrans@*/
+
+    if (poptParseArgvString(se, &item->argc, &item->argv)) goto exit;
+
+/*@-modobserver@*/
     item->option.argInfo = POPT_ARGFLAG_DOC_HIDDEN;
     for (i = 0, j = 0; i < item->argc; i++, j++) {
        const char * f;
@@ -80,111 +373,210 @@ static void configLine(poptContext con, char * line)
        item->argv[j] = NULL;
        item->argc = j;
     }
-    /*@=modobserver@*/
-/*@=boundswrite@*/
+/*@=modobserver@*/
 
-    /*@-nullstate@*/ /* FIX: item->argv[] may be NULL */
+/*@-nullstate@*/ /* FIX: item->argv[] may be NULL */
     if (!strcmp(entryType, "alias"))
-       (void) poptAddItem(con, item, 0);
+       rc = poptAddItem(con, item, 0);
     else if (!strcmp(entryType, "exec"))
-       (void) poptAddItem(con, item, 1);
-    /*@=nullstate@*/
+       rc = poptAddItem(con, item, 1);
+/*@=nullstate@*/
+exit:
+    rc = 0;    /* XXX for now, always return success */
+    if (b)
+       free(b);
+    return rc;
 }
 /*@=compmempass@*/
 
 int poptReadConfigFile(poptContext con, const char * fn)
 {
-    const char * file, * chptr, * end;
-    char * buf;
-/*@dependent@*/ char * dst;
-    int fd, rc;
-    off_t fileLength;
-
-    fd = open(fn, O_RDONLY);
-    if (fd < 0)
-       return (errno == ENOENT ? 0 : POPT_ERROR_ERRNO);
-
-    fileLength = lseek(fd, 0, SEEK_END);
-    if (fileLength == -1 || lseek(fd, 0, 0) == -1) {
-       rc = errno;
-       (void) close(fd);
-       /*@-mods@*/
-       errno = rc;
-       /*@=mods@*/
-       return POPT_ERROR_ERRNO;
-    }
+    char * b = NULL, *be;
+    size_t nb = 0;
+    const char *se;
+    char *t, *te;
+    int rc;
+    int xx;
 
-    file = (const char *)alloca(fileLength + 1);
-    if (read(fd, (char *)file, fileLength) != fileLength) {
-       rc = errno;
-       (void) close(fd);
-       /*@-mods@*/
-       errno = rc;
-       /*@=mods@*/
-       return POPT_ERROR_ERRNO;
-    }
-    if (close(fd) == -1)
-       return POPT_ERROR_ERRNO;
+    if ((rc = poptReadFile(fn, &b, &nb, POPT_READFILE_TRIMNEWLINES)) != 0)
+       return (errno == ENOENT ? 0 : rc);
+    if (b == NULL || nb == 0)
+       return POPT_ERROR_BADCONFIG;
 
-/*@-boundswrite@*/
-    dst = buf = (char *)alloca(fileLength + 1);
+    if ((t = malloc(nb + 1)) == NULL)
+       goto exit;
+    te = t;
 
-    chptr = file;
-    end = (file + fileLength);
-    /*@-infloops@*/    /* LCL: can't detect chptr++ */
-    while (chptr < end) {
-       switch (*chptr) {
+    be = (b + nb);
+    for (se = b; se < be; se++) {
+       switch (*se) {
          case '\n':
-           *dst = '\0';
-           dst = buf;
-           while (*dst && isspace(*dst)) dst++;
-           if (*dst && *dst != '#')
-               configLine(con, dst);
-           chptr++;
+           *te = '\0';
+           te = t;
+           while (*te && _isspaceptr(te)) te++;
+           if (*te && *te != '#')
+               xx = poptConfigLine(con, te);
            /*@switchbreak@*/ break;
+/*@-usedef@*/  /* XXX *se may be uninitialized */
          case '\\':
-           *dst++ = *chptr++;
-           if (chptr < end) {
-               if (*chptr == '\n') 
-                   dst--, chptr++;     
-                   /* \ at the end of a line does not insert a \n */
-               else
-                   *dst++ = *chptr++;
+           *te = *se++;
+           /* \ at the end of a line does not insert a \n */
+           if (se < be && *se != '\n') {
+               te++;
+               *te++ = *se;
            }
            /*@switchbreak@*/ break;
          default:
-           *dst++ = *chptr++;
+           *te++ = *se;
            /*@switchbreak@*/ break;
+/*@=usedef@*/
        }
     }
-    /*@=infloops@*/
-/*@=boundswrite@*/
 
-    return 0;
+    free(t);
+    rc = 0;
+
+exit:
+    if (b)
+       free(b);
+    return rc;
 }
 
-int poptReadDefaultConfig(poptContext con, /*@unused@*/ int useEnv)
+int poptReadConfigFiles(poptContext con, const char * paths)
 {
-    char * fn, * home;
-    int rc;
+    char * buf = (paths ? xstrdup(paths) : NULL);
+    const char * p;
+    char * pe;
+    int rc = 0;                /* assume success */
+
+    for (p = buf; p != NULL && *p != '\0'; p = pe) {
+       const char ** av = NULL;
+       int ac = 0;
+       int i;
+       int xx;
+
+       /* locate start of next path element */
+       pe = strchr(p, ':');
+       if (pe != NULL && *pe == ':')
+           *pe++ = '\0';
+       else
+           pe = (char *) (p + strlen(p));
+
+       xx = poptGlob(con, p, &ac, &av);
+
+       /* work-off each resulting file from the path element */
+       for (i = 0; i < ac; i++) {
+           const char * fn = av[i];
+           if (av[i] == NULL)  /* XXX can't happen */
+               /*@innercontinue@*/ continue;
+           /* XXX should '@' attention be pushed into poptReadConfigFile? */
+           if (p[0] == '@' && p[1] != '(') {
+               if (fn[0] == '@' && fn[1] != '(')
+                   fn++;
+               xx = poptSaneFile(fn);
+               if (!xx && rc == 0)
+                   rc = POPT_ERROR_BADCONFIG;
+               /*@innercontinue@*/ continue;
+           }
+           xx = poptReadConfigFile(con, fn);
+           if (xx && rc == 0)
+               rc = xx;
+           free((void *)av[i]);
+           av[i] = NULL;
+       }
+       free(av);
+       av = NULL;
+    }
 
-    /*@-type@*/
-    if (!con->appName) return 0;
-    /*@=type@*/
+/*@-usedef@*/
+    if (buf)
+       free(buf);
+/*@=usedef@*/
 
-    rc = poptReadConfigFile(con, "/etc/popt");
-    if (rc) return rc;
-#if defined(HAVE_GETUID) && defined(HAVE_GETEUID)
-    if (getuid() != geteuid()) return 0;
+    return rc;
+}
+
+int poptReadDefaultConfig(poptContext con, /*@unused@*/ UNUSED(int useEnv))
+{
+    static const char _popt_sysconfdir[] = POPT_SYSCONFDIR "/popt";
+    static const char _popt_etc[] = "/etc/popt";
+    char * home;
+    struct stat sb;
+    int rc = 0;                /* assume success */
+
+    if (con->appName == NULL) goto exit;
+
+    if (strcmp(_popt_sysconfdir, _popt_etc)) {
+       rc = poptReadConfigFile(con, _popt_sysconfdir);
+       if (rc) goto exit;
+    }
+
+    rc = poptReadConfigFile(con, _popt_etc);
+    if (rc) goto exit;
+
+#if defined(HAVE_GLOB_H)
+    if (!stat("/etc/popt.d", &sb) && S_ISDIR(sb.st_mode)) {
+       const char ** av = NULL;
+       int ac = 0;
+       int i;
+
+       if ((rc = poptGlob(con, "/etc/popt.d/*", &ac, &av)) == 0) {
+           for (i = 0; rc == 0 && i < ac; i++) {
+               const char * fn = av[i];
+               if (fn == NULL || strstr(fn, ".rpmnew") || strstr(fn, ".rpmsave"))
+                   continue;
+               if (!stat(fn, &sb)) {
+                   if (!S_ISREG(sb.st_mode) && !S_ISLNK(sb.st_mode))
+                       continue;
+               }
+               rc = poptReadConfigFile(con, fn);
+               free((void *)av[i]);
+               av[i] = NULL;
+           }
+           free(av);
+           av = NULL;
+       }
+    }
+    if (rc) goto exit;
 #endif
 
     if ((home = getenv("HOME"))) {
-       fn = (char *)alloca(strlen(home) + 20);
-       strcpy(fn, home);
-       strcat(fn, "/.popt");
-       rc = poptReadConfigFile(con, fn);
-       if (rc) return rc;
+       char * fn = malloc(strlen(home) + 20);
+       if (fn != NULL) {
+           (void) stpcpy(stpcpy(fn, home), "/.popt");
+           rc = poptReadConfigFile(con, fn);
+           free(fn);
+       } else
+           rc = POPT_ERROR_ERRNO;
+       if (rc) goto exit;
     }
 
-    return 0;
+exit:
+    return rc;
+}
+
+poptContext
+poptFini(poptContext con)
+{
+    return poptFreeContext(con);
+}
+
+poptContext
+poptInit(int argc, const char ** argv,
+               const struct poptOption * options, const char * configPaths)
+{
+    poptContext con = NULL;
+    const char * argv0;
+
+    if (argv == NULL || argv[0] == NULL || options == NULL)
+       return con;
+
+    if ((argv0 = strrchr(argv[0], '/')) != NULL) argv0++;
+    else argv0 = argv[0];
+
+    con = poptGetContext(argv0, argc, (const char **)argv, options, 0);
+    if (con != NULL&& poptReadConfigFiles(con, configPaths))
+       con = poptFini(con);
+
+    return con;
 }