/** \ingroup popt * \file popt/poptconfig.c */ /* (C) 1998-2002 Red Hat, Inc. -- Licensing details are in the COPYING file accompanying popt source distributions, available from ftp://ftp.rpm.org/pub/rpm/dist. */ #include "system.h" #include "poptint.h" #include #if defined(HAVE_FNMATCH_H) #include #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 #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__ */ /* Return nonzero if PATTERN contains any metacharacters. Metacharacters can be quoted with backslashes if QUOTE is nonzero. */ static int poptGlob_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; } #if defined(HAVE_ASSERT_H) #include #else #define assert(_x) #endif /*@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 (poptGlob_pattern_p(pat, 0)) { glob_t _g, *pglob = &_g; if (!(rc = 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 if (rc == GLOB_NOMATCH) { *avp = NULL; *acp = 0; rc = 0; } else rc = POPT_ERROR_ERRNO; } else #endif /* HAVE_GLOB_H */ { if (acp) *acp = 1; if (avp && (*avp = (const char**) 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; 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 = (char*) 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@*/ { char * s, * t, * se; 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@*/ b=_free(b); /*@=usedef@*/ 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 int poptConfigLine(poptContext con, char * line) /*@globals fileSystem, internalState @*/ /*@modifies con, fileSystem, internalState @*/ { char *b = NULL; size_t nb = 0; char * se = line; const char * appName; const char * entryType; const char * opt; struct poptItem_s item_buf; poptItem item = &item_buf; int i, j; int rc = POPT_ERROR_BADCONFIG; if (con->appName == NULL) goto exit; memset(item, 0, sizeof(*item)); appName = se; while (*se != '\0' && !_isspaceptr(se)) se++; if (*se == '\0') goto exit; else *se++ = '\0'; if (configAppMatch(con, appName)) goto exit; while (*se != '\0' && _isspaceptr(se)) se++; entryType = se; while (*se != '\0' && !_isspaceptr(se)) se++; if (*se != '\0') *se++ = '\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 (*se != '\0' && _isspaceptr(se)) se++; if (opt[0] == '-' && *se == '\0') goto exit; /*@-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]; else { const char * fn = opt; /* 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; /* Append remaining text to the interpolated file option text. */ if (*se != '\0') { size_t nse = strlen(se) + 1; /* cppcheck-suppress memleakOnRealloc */ if ((b = (char*) 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; assert(longName != NULL); /* XXX can't happen. */ /* 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; if (!strncmp(item->argv[i], "--POPTdesc=", sizeof("--POPTdesc=")-1)) { f = item->argv[i] + sizeof("--POPTdesc="); if (f[0] == '$' && f[1] == '"') f++; item->option.descrip = f; item->option.argInfo &= ~POPT_ARGFLAG_DOC_HIDDEN; j--; } else if (!strncmp(item->argv[i], "--POPTargs=", sizeof("--POPTargs=")-1)) { f = item->argv[i] + sizeof("--POPTargs="); if (f[0] == '$' && f[1] == '"') f++; item->option.argDescrip = f; item->option.argInfo &= ~POPT_ARGFLAG_DOC_HIDDEN; item->option.argInfo |= POPT_ARG_STRING; j--; } else if (j != i) item->argv[j] = item->argv[i]; } if (j != i) { item->argv[j] = NULL; item->argc = j; } /*@=modobserver@*/ /*@-nullstate@*/ /* FIX: item->argv[] may be NULL */ if (!strcmp(entryType, "alias")) rc = poptAddItem(con, item, 0); else if (!strcmp(entryType, "exec")) rc = poptAddItem(con, item, 1); /*@=nullstate@*/ exit: rc = 0; /* XXX for now, always return success */ b = _free(b); return rc; } /*@=compmempass@*/ int poptReadConfigFile(poptContext con, const char * fn) { char * b = NULL, *be; size_t nb = 0; const char *se; char *t, *te; int rc; if ((rc = poptReadFile(fn, &b, &nb, POPT_READFILE_TRIMNEWLINES)) != 0) return (errno == ENOENT ? 0 : rc); if (b == NULL || nb == 0) { rc = POPT_ERROR_BADCONFIG; goto exit; } if ((t = (char*) malloc(nb + 1)) == NULL) goto exit; te = t; be = (b + nb); for (se = b; se < be; se++) { switch (*se) { case '\n': *te = '\0'; te = t; while (*te && _isspaceptr(te)) te++; if (*te && *te != '#') { poptConfigLine(con, te); /* XXX: unchecked */ } /*@switchbreak@*/ break; /*@-usedef@*/ /* XXX *se may be uninitialized */ case '\\': *te = *se++; /* \ at the end of a line does not insert a \n */ if (se < be && *se != '\n') { te++; *te++ = *se; } /*@switchbreak@*/ break; default: *te++ = *se; /*@switchbreak@*/ break; /*@=usedef@*/ } } t=_free(t); rc = 0; exit: b=_free(b); return rc; } int poptReadConfigFiles(poptContext con, const char * paths) { 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; av[i]=_free((void *)av[i]); } av=_free(av); } /*@-usedef@*/ buf=_free(buf); /*@=usedef@*/ return rc; } int poptReadDefaultConfig(poptContext con, /*@unused@*/ UNUSED(int useEnv)) { static const char _popt_alias[] = POPT_ALIAS; char * home; int rc = 0; /* assume success */ if (con->appName == NULL) goto exit; rc = poptReadConfigFile(con, _popt_alias); if (rc) goto exit; #if defined(HAVE_GLOB_H) { struct stat sb; if (!stat(SYSCONFDIR"/popt.d", &sb) && S_ISDIR(sb.st_mode)) { const char ** av = NULL; int ac = 0; if ((rc = poptGlob(con, SYSCONFDIR"/popt.d/*", &ac, &av)) == 0) { int i; 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); av[i]=_free((void *)av[i]); } av=_free(av); } } } if (rc) goto exit; #endif if ((home = getenv("HOME"))) { char * fn = (char*) malloc(strlen(home) + 20); if (fn != NULL) { (void) stpcpy(stpcpy(fn, home), "/.popt"); rc = poptReadConfigFile(con, fn); fn=_free(fn); } else rc = POPT_ERROR_ERRNO; if (rc) goto exit; } 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; }