blob: 7840d0c891ebd75a1e3cd2b6175d849f6281c02e [file] [log] [blame]
/** \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 <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 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;
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;
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 */
if (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;
int xx;
if ((rc = poptReadFile(fn, &b, &nb, POPT_READFILE_TRIMNEWLINES)) != 0)
return (errno == ENOENT ? 0 : rc);
if (b == NULL || nb == 0)
return POPT_ERROR_BADCONFIG;
if ((t = 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 != '#')
xx = poptConfigLine(con, te);
/*@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@*/
}
}
free(t);
rc = 0;
exit:
if (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;
free((void *)av[i]);
av[i] = NULL;
}
free(av);
av = NULL;
}
/*@-usedef@*/
if (buf)
free(buf);
/*@=usedef@*/
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"))) {
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;
}
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;
}