Parsing pacman's configuration file is non-trivial and extremely difficult to do correctly from scripts; even our own do it incorrectly. pacman-conf is a dedicated tool specifically to allow scripts to parse config files, getting the same value that pacman itself would use. Signed-off-by: Andrew Gregory <andrew.gregory.8@gmail.com> --- src/util/.gitignore | 2 + src/util/Makefile.am | 21 ++- src/util/pacman-conf.c | 437 +++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 458 insertions(+), 2 deletions(-) create mode 100644 src/util/pacman-conf.c diff --git a/src/util/.gitignore b/src/util/.gitignore index 4cb3103e..3c557cac 100644 --- a/src/util/.gitignore +++ b/src/util/.gitignore @@ -2,6 +2,8 @@ .libs cleanupdelta cleanupdelta.exe +pacman-conf +pacman-conf.exe testpkg testpkg.exe vercmp diff --git a/src/util/Makefile.am b/src/util/Makefile.am index 562151bc..aa812b99 100644 --- a/src/util/Makefile.am +++ b/src/util/Makefile.am @@ -3,8 +3,10 @@ conffile = ${sysconfdir}/pacman.conf dbpath = ${localstatedir}/lib/pacman/ gpgdir = ${sysconfdir}/pacman.d/gnupg/ cachedir = ${localstatedir}/cache/pacman/pkg/ +logfile = ${localstatedir}/log/pacman.log +hookdir = ${sysconfdir}/pacman.d/hooks/ -bin_PROGRAMS = vercmp testpkg cleanupdelta +bin_PROGRAMS = vercmp testpkg cleanupdelta pacman-conf AM_CPPFLAGS = \ -imacros $(top_builddir)/config.h \ @@ -13,7 +15,9 @@ AM_CPPFLAGS = \ -DCONFFILE=\"$(conffile)\" \ -DDBPATH=\"$(dbpath)\" \ -DGPGDIR=\"$(gpgdir)\" \ - -DCACHEDIR=\"$(cachedir)\" + -DCACHEDIR=\"$(cachedir)\" \ + -DHOOKDIR=\"$(hookdir)\" \ + -DLOGFILE=\"$(logfile)\" AM_CFLAGS = -pedantic -D_GNU_SOURCE $(WARNING_CFLAGS) \ $(LIBARCHIVE_CFLAGS) @@ -21,6 +25,19 @@ AM_CFLAGS = -pedantic -D_GNU_SOURCE $(WARNING_CFLAGS) \ cleanupdelta_SOURCES = cleanupdelta.c cleanupdelta_LDADD = $(top_builddir)/lib/libalpm/.libs/libalpm.la +pacman_conf_SOURCES = pacman-conf.c \ + $(top_srcdir)/src/pacman/util.h \ + $(top_srcdir)/src/pacman/util.c \ + $(top_srcdir)/src/pacman/ini.h \ + $(top_srcdir)/src/pacman/ini.c \ + $(top_srcdir)/src/pacman/util-common.h \ + $(top_srcdir)/src/pacman/util-common.c \ + $(top_srcdir)/src/pacman/callback.h \ + $(top_srcdir)/src/pacman/callback.c \ + $(top_srcdir)/src/pacman/conf.h \ + $(top_srcdir)/src/pacman/conf.c +pacman_conf_LDADD = $(top_builddir)/lib/libalpm/.libs/libalpm.la + testpkg_SOURCES = testpkg.c testpkg_LDADD = $(top_builddir)/lib/libalpm/.libs/libalpm.la diff --git a/src/util/pacman-conf.c b/src/util/pacman-conf.c new file mode 100644 index 00000000..b2be3e13 --- /dev/null +++ b/src/util/pacman-conf.c @@ -0,0 +1,437 @@ +/* + * pacman-conf.c - parse pacman configuration files + * + * Copyright (c) 2013-2018 Pacman Development Team <pacman-dev@archlinux.org> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <getopt.h> +#include <string.h> +#include "../pacman/conf.h" + +const char *myname = "pacman-conf", *myver = "1.0.0"; + +alpm_list_t *directives = NULL; +char sep = '\n', *repo_name = NULL; +const char *config_file = NULL; +int repo_list = 0, verbose = 0; + +static void cleanup(void) +{ + alpm_list_free(directives); + config_free(config); +} + +static void usage(int ret) +{ + FILE *stream = (ret ? stderr : stdout); +#define hputs(x) fputs(x"\n", stream) + hputs("pacman-conf - query pacman's configuration file"); + hputs("usage: pacman-conf [options] [<directive>...]"); + hputs(" pacman-conf (--repo-list|--help|--version)"); + hputs("options:"); + hputs(" --config=<path> set an alternate configuration file"); + hputs(" --rootdir=<path> set an alternate installation root"); + hputs(" --repo=<remote> query options for a specific repo"); + hputs(" --verbose always show directive names"); + hputs(" --repo-list list configured repositories"); + hputs(" --help display this help information"); + hputs(" --version display version information"); +#undef hputs + cleanup(); + exit(ret); +} + +static void parse_opts(int argc, char **argv) +{ + int c; + config_file = CONFFILE; + + const char *short_opts = ""; + struct option long_opts[] = { + { "config" , required_argument , NULL , 'c' }, + { "rootdir" , required_argument , NULL , 'R' }, + { "repo" , required_argument , NULL , 'r' }, + { "repo-list" , no_argument , NULL , 'l' }, + { "verbose" , no_argument , NULL , 'v' }, + { "help" , no_argument , NULL , 'h' }, + { "version" , no_argument , NULL , 'V' }, + { 0, 0, 0, 0 }, + }; + + while((c = getopt_long(argc, argv, short_opts, long_opts, NULL)) != -1) { + switch(c) { + case 'c': + config_file = optarg; + break; + case 'R': + config->rootdir = strdup(optarg); + break; + case 'l': + repo_list = 1; + break; + case 'r': + repo_name = optarg; + break; + case 'v': + verbose = 1; + break; + case 'h': + usage(0); + break; + case 'V': + printf("%s v%s\n", myname, myver); + cleanup(); + exit(0); + break; + case '?': + default: + usage(1); + break; + } + } + + if(parseconfigfile(config_file) != 0 || setdefaults(config) != 0) { + fprintf(stderr, "error parsing '%s'\n", config_file); + } +} + +static void list_repos(void) +{ + alpm_list_t *r; + for(r = config->repos; r; r = r->next) { + config_repo_t *repo = r->data; + if(!repo_name || strcmp(repo->name, repo_name) == 0) { + printf("%s%c", repo->name, sep); + } + } +} + +static void show_float(const char *directive, float val) +{ + if(verbose) { + printf("%s = ", directive); + } + printf("%f%c", val, sep); +} + +static void show_bool(const char *directive, short unsigned int val) +{ + if(val) { + printf("%s%c", directive, sep); + } +} + +static void show_str(const char *directive, const char *val) +{ + if(!val) { + return; + } + if(verbose) { + printf("%s = ", directive); + } + printf("%s%c", val, sep); +} + +static void show_list_str(const char *directive, alpm_list_t *list) +{ + alpm_list_t *i; + for(i = list; i; i = i->next) { + show_str(directive, i->data); + } +} + +static void show_cleanmethod(const char *directive, unsigned int method) +{ + if(method & PM_CLEAN_KEEPINST) { + show_str(directive, "KeepInstalled"); + } + if(method & PM_CLEAN_KEEPCUR) { + show_str(directive, "KeepCurrent"); + } +} + +static void show_siglevel(const char *directive, alpm_siglevel_t level, int pkgonly) +{ + if(level == ALPM_SIG_USE_DEFAULT) { + return; + } + + if(level & ALPM_SIG_PACKAGE) { + if(level & ALPM_SIG_PACKAGE_OPTIONAL) { + show_str(directive, "PackageOptional"); + } else { + show_str(directive, "PackageRequired"); + } + + if(level & ALPM_SIG_PACKAGE_UNKNOWN_OK) { + show_str(directive, "PackageTrustAll"); + } else { + show_str(directive, "PackageTrustedOnly"); + } + } else { + show_str(directive, "PackageNever"); + } + + if(pkgonly) { + return; + } + + if(level & ALPM_SIG_DATABASE) { + if(level & ALPM_SIG_DATABASE_OPTIONAL) { + show_str(directive, "DatabaseOptional"); + } else { + show_str(directive, "DatabaseRequired"); + } + + if(level & ALPM_SIG_DATABASE_UNKNOWN_OK) { + show_str(directive, "DatabaseTrustAll"); + } else { + show_str(directive, "DatabaseTrustedOnly"); + } + } else { + show_str(directive, "DatabaseNever"); + } +} + +static void show_usage(const char *directive, alpm_db_usage_t usage) +{ + if(usage & ALPM_DB_USAGE_ALL) { + show_str(directive, "All"); + } else { + if(usage & ALPM_DB_USAGE_SYNC) { + show_str(directive, "Sync"); + } + if(usage & ALPM_DB_USAGE_SEARCH) { + show_str(directive, "Search"); + } + if(usage & ALPM_DB_USAGE_INSTALL) { + show_str(directive, "Install"); + } + if(usage & ALPM_DB_USAGE_UPGRADE) { + show_str(directive, "Upgrade"); + } + } +} + +static void dump_repo(config_repo_t *repo) +{ + show_usage("Usage", repo->usage); + show_siglevel("SigLevel", repo->siglevel, 0); + show_list_str("Server", repo->servers); +} + +static void dump_config(void) +{ + alpm_list_t *i; + + printf("[options]%c", sep); + + show_str("RootDir", config->rootdir); + show_str("DBPath", config->dbpath); + show_list_str("CacheDir", config->cachedirs); + show_list_str("HookDir", config->hookdirs); + show_str("GPGDir", config->gpgdir); + show_str("LogFile", config->logfile); + + show_list_str("HoldPkg", config->holdpkg); + show_list_str("IgnorePkg", config->ignorepkg); + show_list_str("IgnoreGroup", config->ignoregrp); + show_list_str("NoUpgrade", config->noupgrade); + show_list_str("NoExtract", config->noextract); + + show_str("Architecture", config->arch); + show_str("XferCommand", config->xfercommand); + + show_bool("UseSyslog", config->usesyslog); + show_bool("Color", config->color); + show_bool("TotalDownload", config->totaldownload); + show_bool("CheckSpace", config->checkspace); + show_bool("VerbosePkgLists", config->verbosepkglists); + show_bool("ILoveCandy", config->chomp); + + show_float("UseDelta", config->deltaratio); + + show_cleanmethod("CleanMethod", config->cleanmethod); + + show_siglevel("SigLevel", config->siglevel, 0); + show_siglevel("LocalFileSigLevel", config->localfilesiglevel, 1); + show_siglevel("RemoteFileSigLevel", config->remotefilesiglevel, 1); + + for(i = config->repos; i; i = i->next) { + config_repo_t *repo = i->data; + printf("[%s]%c", repo->name, sep); + dump_repo(repo); + } +} + +static int list_repo_directives(void) +{ + int ret = 0; + alpm_list_t *i; + config_repo_t *repo = NULL; + + for(i = config->repos; i; i = i->next) { + if(strcmp(repo_name, ((config_repo_t*) i->data)->name) == 0) { + repo = i->data; + break; + } + } + + if(!repo) { + fprintf(stderr, "error: repo '%s' not configured\n", repo_name); + return 1; + } + + if(!directives) { + dump_repo(repo); + return 0; + } + + for(i = directives; i; i = i->next) { + if(strcasecmp(i->data, "Server") == 0) { + show_list_str("Server", repo->servers); + } else if(strcasecmp(i->data, "SigLevel") == 0) { + show_siglevel("SigLevel", repo->siglevel, 0); + } else if(strcasecmp(i->data, "Usage") == 0) { + show_usage("Usage", repo->usage); + } else if(strcasecmp(i->data, "Include") == 0) { + fputs("warning: 'Include' directives cannot be queried\n", stderr); + ret = 1; + } else { + fprintf(stderr, "warning: unknown directive '%s'\n", (char*) i->data); + ret = 1; + } + } + + return ret; +} + +static int list_directives(void) +{ + int ret = 0; + alpm_list_t *i; + + if(!directives) { + dump_config(); + return 0; + } + + for(i = directives; i; i = i->next) { + if(strcasecmp(i->data, "RootDir") == 0) { + show_str("RootDir", config->rootdir); + } else if(strcasecmp(i->data, "DBPath") == 0) { + show_str("DBPath", config->dbpath); + } else if(strcasecmp(i->data, "CacheDir") == 0) { + show_list_str("CacheDir", config->cachedirs); + } else if(strcasecmp(i->data, "HookDir") == 0) { + show_list_str("HookDir", config->hookdirs); + } else if(strcasecmp(i->data, "GPGDir") == 0) { + show_str("GPGDir", config->gpgdir); + } else if(strcasecmp(i->data, "LogFile") == 0) { + show_str("LogFile", config->logfile); + + } else if(strcasecmp(i->data, "HoldPkg") == 0) { + show_list_str("HoldPkg", config->holdpkg); + } else if(strcasecmp(i->data, "IgnorePkg") == 0) { + show_list_str("IgnorePkg", config->ignorepkg); + } else if(strcasecmp(i->data, "IgnoreGroup") == 0) { + show_list_str("IgnoreGroup", config->ignoregrp); + } else if(strcasecmp(i->data, "NoUpgrade") == 0) { + show_list_str("NoUpgrade", config->noupgrade); + } else if(strcasecmp(i->data, "NoExtract") == 0) { + show_list_str("NoExtract", config->noupgrade); + + + } else if(strcasecmp(i->data, "Architecture") == 0) { + show_str("Architecture", config->arch); + } else if(strcasecmp(i->data, "XferCommand") == 0) { + show_str("XferCommand", config->xfercommand); + + } else if(strcasecmp(i->data, "UseSyslog") == 0) { + show_bool("UseSyslog", config->usesyslog); + } else if(strcasecmp(i->data, "Color") == 0) { + show_bool("Color", config->color); + } else if(strcasecmp(i->data, "TotalDownload") == 0) { + show_bool("TotalDownload", config->totaldownload); + } else if(strcasecmp(i->data, "CheckSpace") == 0) { + show_bool("CheckSpace", config->checkspace); + } else if(strcasecmp(i->data, "VerbosePkgLists") == 0) { + show_bool("VerbosePkgLists", config->verbosepkglists); + + } else if(strcasecmp(i->data, "UseDelta") == 0) { + show_float("UseDelta", config->deltaratio); + + } else if(strcasecmp(i->data, "CleanMethod") == 0) { + show_cleanmethod("CleanMethod", config->cleanmethod); + + } else if(strcasecmp(i->data, "SigLevel") == 0) { + show_siglevel("SigLevel", config->siglevel, 0); + } else if(strcasecmp(i->data, "LocalFileSigLevel") == 0) { + show_siglevel("LocalFileSigLevel", config->localfilesiglevel, 1); + } else if(strcasecmp(i->data, "RemoteFileSigLevel") == 0) { + show_siglevel("RemoteFileSigLevel", config->remotefilesiglevel, 1); + + } else if(strcasecmp(i->data, "Include") == 0) { + fputs("warning: 'Include' directives cannot be queried\n", stderr); + ret = 1; + } else { + fprintf(stderr, "warning: unknown directive '%s'\n", (char*) i->data); + ret = 1; + } + } + + return ret; +} + +int main(int argc, char **argv) +{ + int ret = 0; + + config = config_new(); + parse_opts(argc, argv); + if(!config) { + ret = 1; + goto cleanup; + } + + for(; optind < argc; optind++) { + directives = alpm_list_add(directives, argv[optind]); + } + + if(alpm_list_count(directives) != 1) { + verbose = 1; + } + + if(repo_list) { + if(directives) { + fputs("error: directives may not be specified with --repo-list\n", stderr); + ret = 1; + goto cleanup; + } + list_repos(); + } else if(repo_name) { + ret = list_repo_directives(); + } else { + ret = list_directives(); + } + +cleanup: + cleanup(); + + return ret; +} + +/* vim: set ts=2 sw=2 noet: */ -- 2.15.1