libalpm
Arch Linux Package Manager Library
conf.c
Go to the documentation of this file.
00001 /*
00002  *  conf.c
00003  *
00004  *  Copyright (c) 2006-2011 Pacman Development Team <pacman-dev@archlinux.org>
00005  *  Copyright (c) 2002-2006 by Judd Vinet <jvinet@zeroflux.org>
00006  *
00007  *  This program is free software; you can redistribute it and/or modify
00008  *  it under the terms of the GNU General Public License as published by
00009  *  the Free Software Foundation; either version 2 of the License, or
00010  *  (at your option) any later version.
00011  *
00012  *  This program is distributed in the hope that it will be useful,
00013  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
00014  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
00015  *  GNU General Public License for more details.
00016  *
00017  *  You should have received a copy of the GNU General Public License
00018  *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
00019  */
00020 
00021 #include <errno.h>
00022 #include <glob.h>
00023 #include <limits.h>
00024 #include <fcntl.h> /* open */
00025 #include <stdlib.h>
00026 #include <stdio.h>
00027 #include <string.h> /* strdup */
00028 #include <sys/stat.h>
00029 #include <sys/types.h>
00030 #include <sys/utsname.h> /* uname */
00031 #include <unistd.h>
00032 
00033 /* pacman */
00034 #include "conf.h"
00035 #include "util.h"
00036 #include "pacman.h"
00037 #include "callback.h"
00038 
00039 /* global config variable */
00040 config_t *config = NULL;
00041 
00042 config_t *config_new(void)
00043 {
00044     config_t *newconfig = calloc(1, sizeof(config_t));
00045     if(!newconfig) {
00046             pm_printf(ALPM_LOG_ERROR,
00047                     _("malloc failure: could not allocate %zd bytes\n"),
00048                     sizeof(config_t));
00049             return NULL;
00050     }
00051     /* defaults which may get overridden later */
00052     newconfig->op = PM_OP_MAIN;
00053     newconfig->logmask = ALPM_LOG_ERROR | ALPM_LOG_WARNING;
00054     newconfig->configfile = strdup(CONFFILE);
00055     if(alpm_capabilities() & ALPM_CAPABILITY_SIGNATURES) {
00056         newconfig->siglevel = ALPM_SIG_PACKAGE | ALPM_SIG_PACKAGE_OPTIONAL |
00057             ALPM_SIG_DATABASE | ALPM_SIG_DATABASE_OPTIONAL;
00058     }
00059 
00060     return newconfig;
00061 }
00062 
00063 int config_free(config_t *oldconfig)
00064 {
00065     if(oldconfig == NULL) {
00066         return -1;
00067     }
00068 
00069     alpm_list_free(oldconfig->explicit_adds);
00070     alpm_list_free(oldconfig->explicit_removes);
00071 
00072     FREELIST(oldconfig->holdpkg);
00073     FREELIST(oldconfig->syncfirst);
00074     FREELIST(oldconfig->ignorepkg);
00075     FREELIST(oldconfig->ignoregrp);
00076     FREELIST(oldconfig->noupgrade);
00077     FREELIST(oldconfig->noextract);
00078     free(oldconfig->configfile);
00079     free(oldconfig->rootdir);
00080     free(oldconfig->dbpath);
00081     free(oldconfig->logfile);
00082     free(oldconfig->gpgdir);
00083     FREELIST(oldconfig->cachedirs);
00084     free(oldconfig->xfercommand);
00085     free(oldconfig->print_format);
00086     free(oldconfig->arch);
00087     free(oldconfig);
00088 
00089     return 0;
00090 }
00091 
00092 /** Helper function for download_with_xfercommand() */
00093 static char *get_filename(const char *url) {
00094     char *filename = strrchr(url, '/');
00095     if(filename != NULL) {
00096         filename++;
00097     }
00098     return filename;
00099 }
00100 
00101 /** Helper function for download_with_xfercommand() */
00102 static char *get_destfile(const char *path, const char *filename) {
00103     char *destfile;
00104     /* len = localpath len + filename len + null */
00105     size_t len = strlen(path) + strlen(filename) + 1;
00106     destfile = calloc(len, sizeof(char));
00107     snprintf(destfile, len, "%s%s", path, filename);
00108 
00109     return destfile;
00110 }
00111 
00112 /** Helper function for download_with_xfercommand() */
00113 static char *get_tempfile(const char *path, const char *filename) {
00114     char *tempfile;
00115     /* len = localpath len + filename len + '.part' len + null */
00116     size_t len = strlen(path) + strlen(filename) + 6;
00117     tempfile = calloc(len, sizeof(char));
00118     snprintf(tempfile, len, "%s%s.part", path, filename);
00119 
00120     return tempfile;
00121 }
00122 
00123 /** External fetch callback */
00124 static int download_with_xfercommand(const char *url, const char *localpath,
00125         int force) {
00126     int ret = 0, retval;
00127     int usepart = 0;
00128     int cwdfd;
00129     struct stat st;
00130     char *parsedcmd,*tempcmd;
00131     char *destfile, *tempfile, *filename;
00132 
00133     if(!config->xfercommand) {
00134         return -1;
00135     }
00136 
00137     filename = get_filename(url);
00138     if(!filename) {
00139         return -1;
00140     }
00141     destfile = get_destfile(localpath, filename);
00142     tempfile = get_tempfile(localpath, filename);
00143 
00144     if(force && stat(tempfile, &st) == 0) {
00145         unlink(tempfile);
00146     }
00147     if(force && stat(destfile, &st) == 0) {
00148         unlink(destfile);
00149     }
00150 
00151     tempcmd = strdup(config->xfercommand);
00152     /* replace all occurrences of %o with fn.part */
00153     if(strstr(tempcmd, "%o")) {
00154         usepart = 1;
00155         parsedcmd = strreplace(tempcmd, "%o", tempfile);
00156         free(tempcmd);
00157         tempcmd = parsedcmd;
00158     }
00159     /* replace all occurrences of %u with the download URL */
00160     parsedcmd = strreplace(tempcmd, "%u", url);
00161     free(tempcmd);
00162 
00163     /* save the cwd so we can restore it later */
00164     do {
00165         cwdfd = open(".", O_RDONLY);
00166     } while(cwdfd == -1 && errno == EINTR);
00167     if(cwdfd < 0) {
00168         pm_printf(ALPM_LOG_ERROR, _("could not get current working directory\n"));
00169     }
00170 
00171     /* cwd to the download directory */
00172     if(chdir(localpath)) {
00173         pm_printf(ALPM_LOG_WARNING, _("could not chdir to download directory %s\n"), localpath);
00174         ret = -1;
00175         goto cleanup;
00176     }
00177     /* execute the parsed command via /bin/sh -c */
00178     pm_printf(ALPM_LOG_DEBUG, "running command: %s\n", parsedcmd);
00179     retval = system(parsedcmd);
00180 
00181     if(retval == -1) {
00182         pm_printf(ALPM_LOG_WARNING, _("running XferCommand: fork failed!\n"));
00183         ret = -1;
00184     } else if(retval != 0) {
00185         /* download failed */
00186         pm_printf(ALPM_LOG_DEBUG, "XferCommand command returned non-zero status "
00187                 "code (%d)\n", retval);
00188         ret = -1;
00189     } else {
00190         /* download was successful */
00191         ret = 0;
00192         if(usepart) {
00193             if(rename(tempfile, destfile)) {
00194                 pm_printf(ALPM_LOG_ERROR, _("could not rename %s to %s (%s)\n"),
00195                         tempfile, destfile, strerror(errno));
00196                 ret = -1;
00197             }
00198         }
00199     }
00200 
00201 cleanup:
00202     /* restore the old cwd if we have it */
00203     if(cwdfd >= 0) {
00204         int ret;
00205         if(fchdir(cwdfd) != 0) {
00206             pm_printf(ALPM_LOG_ERROR, _("could not restore working directory (%s)\n"),
00207                     strerror(errno));
00208         }
00209         do {
00210             ret = close(cwdfd);
00211         } while(ret == -1 && errno == EINTR);
00212     }
00213 
00214     if(ret == -1) {
00215         /* hack to let an user the time to cancel a download */
00216         sleep(2);
00217     }
00218     free(destfile);
00219     free(tempfile);
00220     free(parsedcmd);
00221 
00222     return ret;
00223 }
00224 
00225 
00226 int config_set_arch(const char *arch)
00227 {
00228     if(strcmp(arch, "auto") == 0) {
00229         struct utsname un;
00230         uname(&un);
00231         config->arch = strdup(un.machine);
00232     } else {
00233         config->arch = strdup(arch);
00234     }
00235     pm_printf(ALPM_LOG_DEBUG, "config: arch: %s\n", config->arch);
00236     return 0;
00237 }
00238 
00239 /**
00240  * Parse a signature verification level line.
00241  * @param values the list of parsed option values
00242  * @param storage location to store the derived signature level; any existing
00243  * value here is used as a starting point
00244  * @param file path to the config file
00245  * @param linenum current line number in file
00246  * @return 0 on success, 1 on any parsing error
00247  */
00248 static int process_siglevel(alpm_list_t *values, alpm_siglevel_t *storage,
00249         const char *file, int linenum)
00250 {
00251     alpm_siglevel_t level = *storage;
00252     alpm_list_t *i;
00253     int ret = 0;
00254 
00255     /* Collapse the option names into a single bitmasked value */
00256     for(i = values; i; i = alpm_list_next(i)) {
00257         const char *original = i->data, *value;
00258         int package = 0, database = 0;
00259 
00260         if(strncmp(original, "Package", strlen("Package")) == 0) {
00261             /* only packages are affected, don't flip flags for databases */
00262             value = original + strlen("Package");
00263             package = 1;
00264         } else if(strncmp(original, "Database", strlen("Database")) == 0) {
00265             /* only databases are affected, don't flip flags for packages */
00266             value = original + strlen("Database");
00267             database = 1;
00268         } else {
00269             /* no prefix, so anything found will affect both packages and dbs */
00270             value = original;
00271             package = database = 1;
00272         }
00273 
00274         /* now parse out and store actual flag if it is valid */
00275         if(strcmp(value, "Never") == 0) {
00276             if(package) {
00277                 level &= ~ALPM_SIG_PACKAGE;
00278             }
00279             if(database) {
00280                 level &= ~ALPM_SIG_DATABASE;
00281             }
00282         } else if(strcmp(value, "Optional") == 0) {
00283             if(package) {
00284                 level |= ALPM_SIG_PACKAGE;
00285                 level |= ALPM_SIG_PACKAGE_OPTIONAL;
00286             }
00287             if(database) {
00288                 level |= ALPM_SIG_DATABASE;
00289                 level |= ALPM_SIG_DATABASE_OPTIONAL;
00290             }
00291         } else if(strcmp(value, "Required") == 0) {
00292             if(package) {
00293                 level |= ALPM_SIG_PACKAGE;
00294                 level &= ~ALPM_SIG_PACKAGE_OPTIONAL;
00295             }
00296             if(database) {
00297                 level |= ALPM_SIG_DATABASE;
00298                 level &= ~ALPM_SIG_DATABASE_OPTIONAL;
00299             }
00300         } else if(strcmp(value, "TrustedOnly") == 0) {
00301             if(package) {
00302                 level &= ~ALPM_SIG_PACKAGE_MARGINAL_OK;
00303                 level &= ~ALPM_SIG_PACKAGE_UNKNOWN_OK;
00304             }
00305             if(database) {
00306                 level &= ~ALPM_SIG_DATABASE_MARGINAL_OK;
00307                 level &= ~ALPM_SIG_DATABASE_UNKNOWN_OK;
00308             }
00309         } else if(strcmp(value, "TrustAll") == 0) {
00310             if(package) {
00311                 level |= ALPM_SIG_PACKAGE_MARGINAL_OK;
00312                 level |= ALPM_SIG_PACKAGE_UNKNOWN_OK;
00313             }
00314             if(database) {
00315                 level |= ALPM_SIG_DATABASE_MARGINAL_OK;
00316                 level |= ALPM_SIG_DATABASE_UNKNOWN_OK;
00317             }
00318         } else {
00319             pm_printf(ALPM_LOG_ERROR,
00320                     _("config file %s, line %d: invalid value for '%s' : '%s'\n"),
00321                     file, linenum, "SigLevel", original);
00322             ret = 1;
00323         }
00324         level &= ~ALPM_SIG_USE_DEFAULT;
00325     }
00326 
00327     /* ensure we have sig checking ability and are actually turning it on */
00328     if(!(alpm_capabilities() & ALPM_CAPABILITY_SIGNATURES) &&
00329             level & (ALPM_SIG_PACKAGE | ALPM_SIG_DATABASE)) {
00330         pm_printf(ALPM_LOG_ERROR,
00331                 _("config file %s, line %d: '%s' option invalid, no signature support\n"),
00332                 file, linenum, "SigLevel");
00333         ret = 1;
00334     }
00335 
00336     if(!ret) {
00337         *storage = level;
00338     }
00339     return ret;
00340 }
00341 
00342 static int process_cleanmethods(alpm_list_t *values,
00343         const char *file, int linenum)
00344 {
00345     alpm_list_t *i;
00346     for(i = values; i; i = alpm_list_next(i)) {
00347         const char *value = i->data;
00348         if(strcmp(value, "KeepInstalled") == 0) {
00349             config->cleanmethod |= PM_CLEAN_KEEPINST;
00350         } else if(strcmp(value, "KeepCurrent") == 0) {
00351             config->cleanmethod |= PM_CLEAN_KEEPCUR;
00352         } else {
00353             pm_printf(ALPM_LOG_ERROR,
00354                     _("config file %s, line %d: invalid value for '%s' : '%s'\n"),
00355                     file, linenum, "CleanMethod", value);
00356             return 1;
00357         }
00358     }
00359     return 0;
00360 }
00361 
00362 /** Add repeating options such as NoExtract, NoUpgrade, etc to libalpm
00363  * settings. Refactored out of the parseconfig code since all of them did
00364  * the exact same thing and duplicated code.
00365  * @param ptr a pointer to the start of the multiple options
00366  * @param option the string (friendly) name of the option, used for messages
00367  * @param list the list to add the option to
00368  */
00369 static void setrepeatingoption(char *ptr, const char *option,
00370         alpm_list_t **list)
00371 {
00372     char *q;
00373 
00374     while((q = strchr(ptr, ' '))) {
00375         *q = '\0';
00376         *list = alpm_list_add(*list, strdup(ptr));
00377         pm_printf(ALPM_LOG_DEBUG, "config: %s: %s\n", option, ptr);
00378         ptr = q;
00379         ptr++;
00380     }
00381     *list = alpm_list_add(*list, strdup(ptr));
00382     pm_printf(ALPM_LOG_DEBUG, "config: %s: %s\n", option, ptr);
00383 }
00384 
00385 static int _parse_options(const char *key, char *value,
00386         const char *file, int linenum)
00387 {
00388     if(value == NULL) {
00389         /* options without settings */
00390         if(strcmp(key, "UseSyslog") == 0) {
00391             config->usesyslog = 1;
00392             pm_printf(ALPM_LOG_DEBUG, "config: usesyslog\n");
00393         } else if(strcmp(key, "ILoveCandy") == 0) {
00394             config->chomp = 1;
00395             pm_printf(ALPM_LOG_DEBUG, "config: chomp\n");
00396         } else if(strcmp(key, "VerbosePkgLists") == 0) {
00397             config->verbosepkglists = 1;
00398             pm_printf(ALPM_LOG_DEBUG, "config: verbosepkglists\n");
00399         } else if(strcmp(key, "UseDelta") == 0) {
00400             config->usedelta = 1;
00401             pm_printf(ALPM_LOG_DEBUG, "config: usedelta\n");
00402         } else if(strcmp(key, "TotalDownload") == 0) {
00403             config->totaldownload = 1;
00404             pm_printf(ALPM_LOG_DEBUG, "config: totaldownload\n");
00405         } else if(strcmp(key, "CheckSpace") == 0) {
00406             config->checkspace = 1;
00407         } else {
00408             pm_printf(ALPM_LOG_WARNING,
00409                     _("config file %s, line %d: directive '%s' in section '%s' not recognized.\n"),
00410                     file, linenum, key, "options");
00411         }
00412     } else {
00413         /* options with settings */
00414         if(strcmp(key, "NoUpgrade") == 0) {
00415             setrepeatingoption(value, "NoUpgrade", &(config->noupgrade));
00416         } else if(strcmp(key, "NoExtract") == 0) {
00417             setrepeatingoption(value, "NoExtract", &(config->noextract));
00418         } else if(strcmp(key, "IgnorePkg") == 0) {
00419             setrepeatingoption(value, "IgnorePkg", &(config->ignorepkg));
00420         } else if(strcmp(key, "IgnoreGroup") == 0) {
00421             setrepeatingoption(value, "IgnoreGroup", &(config->ignoregrp));
00422         } else if(strcmp(key, "HoldPkg") == 0) {
00423             setrepeatingoption(value, "HoldPkg", &(config->holdpkg));
00424         } else if(strcmp(key, "SyncFirst") == 0) {
00425             setrepeatingoption(value, "SyncFirst", &(config->syncfirst));
00426         } else if(strcmp(key, "CacheDir") == 0) {
00427             setrepeatingoption(value, "CacheDir", &(config->cachedirs));
00428         } else if(strcmp(key, "Architecture") == 0) {
00429             if(!config->arch) {
00430                 config_set_arch(value);
00431             }
00432         } else if(strcmp(key, "DBPath") == 0) {
00433             /* don't overwrite a path specified on the command line */
00434             if(!config->dbpath) {
00435                 config->dbpath = strdup(value);
00436                 pm_printf(ALPM_LOG_DEBUG, "config: dbpath: %s\n", value);
00437             }
00438         } else if(strcmp(key, "RootDir") == 0) {
00439             /* don't overwrite a path specified on the command line */
00440             if(!config->rootdir) {
00441                 config->rootdir = strdup(value);
00442                 pm_printf(ALPM_LOG_DEBUG, "config: rootdir: %s\n", value);
00443             }
00444         } else if(strcmp(key, "GPGDir") == 0) {
00445             if(!config->gpgdir) {
00446                 config->gpgdir = strdup(value);
00447                 pm_printf(ALPM_LOG_DEBUG, "config: gpgdir: %s\n", value);
00448             }
00449         } else if(strcmp(key, "LogFile") == 0) {
00450             if(!config->logfile) {
00451                 config->logfile = strdup(value);
00452                 pm_printf(ALPM_LOG_DEBUG, "config: logfile: %s\n", value);
00453             }
00454         } else if(strcmp(key, "XferCommand") == 0) {
00455             config->xfercommand = strdup(value);
00456             pm_printf(ALPM_LOG_DEBUG, "config: xfercommand: %s\n", value);
00457         } else if(strcmp(key, "CleanMethod") == 0) {
00458             alpm_list_t *methods = NULL;
00459             setrepeatingoption(value, "CleanMethod", &methods);
00460             if(process_cleanmethods(methods, file, linenum)) {
00461                 FREELIST(methods);
00462                 return 1;
00463             }
00464             FREELIST(methods);
00465         } else if(strcmp(key, "SigLevel") == 0) {
00466             alpm_list_t *values = NULL;
00467             setrepeatingoption(value, "SigLevel", &values);
00468             if(process_siglevel(values, &config->siglevel, file, linenum)) {
00469                 FREELIST(values);
00470                 return 1;
00471             }
00472             FREELIST(values);
00473         } else {
00474             pm_printf(ALPM_LOG_WARNING,
00475                     _("config file %s, line %d: directive '%s' in section '%s' not recognized.\n"),
00476                     file, linenum, key, "options");
00477         }
00478 
00479     }
00480     return 0;
00481 }
00482 
00483 static int _add_mirror(alpm_db_t *db, char *value)
00484 {
00485     const char *dbname = alpm_db_get_name(db);
00486     /* let's attempt a replacement for the current repo */
00487     char *temp = strreplace(value, "$repo", dbname);
00488     /* let's attempt a replacement for the arch */
00489     const char *arch = config->arch;
00490     char *server;
00491     if(arch) {
00492         server = strreplace(temp, "$arch", arch);
00493         free(temp);
00494     } else {
00495         if(strstr(temp, "$arch")) {
00496             free(temp);
00497             pm_printf(ALPM_LOG_ERROR,
00498                     _("mirror '%s' contains the '%s' variable, but no '%s' is defined.\n"),
00499                     value, "$arch", "Architecture");
00500             return 1;
00501         }
00502         server = temp;
00503     }
00504 
00505     if(alpm_db_add_server(db, server) != 0) {
00506         /* pm_errno is set by alpm_db_setserver */
00507         pm_printf(ALPM_LOG_ERROR, _("could not add server URL to database '%s': %s (%s)\n"),
00508                 dbname, server, alpm_strerror(alpm_errno(config->handle)));
00509         free(server);
00510         return 1;
00511     }
00512 
00513     free(server);
00514     return 0;
00515 }
00516 
00517 /** Sets up libalpm global stuff in one go. Called after the command line
00518  * and inital config file parsing. Once this is complete, we can see if any
00519  * paths were defined. If a rootdir was defined and nothing else, we want all
00520  * of our paths to live under the rootdir that was specified. Safe to call
00521  * multiple times (will only do anything the first time).
00522  */
00523 static int setup_libalpm(void)
00524 {
00525     int ret = 0;
00526     alpm_errno_t err;
00527     alpm_handle_t *handle;
00528 
00529     pm_printf(ALPM_LOG_DEBUG, "setup_libalpm called\n");
00530 
00531     /* Configure root path first. If it is set and dbpath/logfile were not
00532      * set, then set those as well to reside under the root. */
00533     if(config->rootdir) {
00534         char path[PATH_MAX];
00535         if(!config->dbpath) {
00536             snprintf(path, PATH_MAX, "%s/%s", config->rootdir, DBPATH + 1);
00537             config->dbpath = strdup(path);
00538         }
00539         if(!config->logfile) {
00540             snprintf(path, PATH_MAX, "%s/%s", config->rootdir, LOGFILE + 1);
00541             config->logfile = strdup(path);
00542         }
00543     } else {
00544         config->rootdir = strdup(ROOTDIR);
00545         if(!config->dbpath) {
00546             config->dbpath = strdup(DBPATH);
00547         }
00548     }
00549 
00550     /* initialize library */
00551     handle = alpm_initialize(config->rootdir, config->dbpath, &err);
00552     if(!handle) {
00553         pm_printf(ALPM_LOG_ERROR, _("failed to initialize alpm library (%s)\n"),
00554                 alpm_strerror(err));
00555         if(err == ALPM_ERR_DB_VERSION) {
00556             pm_printf(ALPM_LOG_ERROR, _("  try running pacman-db-upgrade\n"));
00557         }
00558         return -1;
00559     }
00560     config->handle = handle;
00561 
00562     alpm_option_set_logcb(handle, cb_log);
00563     alpm_option_set_dlcb(handle, cb_dl_progress);
00564     alpm_option_set_eventcb(handle, cb_event);
00565     alpm_option_set_questioncb(handle, cb_question);
00566     alpm_option_set_progresscb(handle, cb_progress);
00567 
00568     config->logfile = config->logfile ? config->logfile : strdup(LOGFILE);
00569     ret = alpm_option_set_logfile(handle, config->logfile);
00570     if(ret != 0) {
00571         pm_printf(ALPM_LOG_ERROR, _("problem setting logfile '%s' (%s)\n"),
00572                 config->logfile, alpm_strerror(alpm_errno(handle)));
00573         return ret;
00574     }
00575 
00576     /* Set GnuPG's home directory.  This is not relative to rootdir, even if
00577      * rootdir is defined. Reasoning: gpgdir contains configuration data. */
00578     config->gpgdir = config->gpgdir ? config->gpgdir : strdup(GPGDIR);
00579     ret = alpm_option_set_gpgdir(handle, config->gpgdir);
00580     if(ret != 0) {
00581         pm_printf(ALPM_LOG_ERROR, _("problem setting gpgdir '%s' (%s)\n"),
00582                 config->gpgdir, alpm_strerror(alpm_errno(handle)));
00583         return ret;
00584     }
00585 
00586     /* add a default cachedir if one wasn't specified */
00587     if(config->cachedirs == NULL) {
00588         alpm_option_add_cachedir(handle, CACHEDIR);
00589     } else {
00590         alpm_option_set_cachedirs(handle, config->cachedirs);
00591     }
00592 
00593     alpm_option_set_default_siglevel(handle, config->siglevel);
00594 
00595     if(config->xfercommand) {
00596         alpm_option_set_fetchcb(handle, download_with_xfercommand);
00597     } else if(!(alpm_capabilities() & ALPM_CAPABILITY_DOWNLOADER)) {
00598         pm_printf(ALPM_LOG_WARNING, _("no '%s' configured"), "XferCommand");
00599     }
00600 
00601     if(config->totaldownload) {
00602         alpm_option_set_totaldlcb(handle, cb_dl_total);
00603     }
00604 
00605     alpm_option_set_arch(handle, config->arch);
00606     alpm_option_set_checkspace(handle, config->checkspace);
00607     alpm_option_set_usesyslog(handle, config->usesyslog);
00608     alpm_option_set_usedelta(handle, config->usedelta);
00609 
00610     alpm_option_set_ignorepkgs(handle, config->ignorepkg);
00611     alpm_option_set_ignoregroups(handle, config->ignoregrp);
00612     alpm_option_set_noupgrades(handle, config->noupgrade);
00613     alpm_option_set_noextracts(handle, config->noextract);
00614 
00615     return 0;
00616 }
00617 
00618 /**
00619  * Allows parsing in advance of an entire config section before we start
00620  * calling library methods.
00621  */
00622 struct section_t {
00623     /* useful for all sections */
00624     char *name;
00625     int is_options;
00626     /* db section option gathering */
00627     alpm_siglevel_t siglevel;
00628     alpm_list_t *servers;
00629 };
00630 
00631 /**
00632  * Wrap up a section once we have reached the end of it. This should be called
00633  * when a subsequent section is encountered, or when we have reached the end of
00634  * the root config file. Once called, all existing saved config pieces on the
00635  * section struct are freed.
00636  * @param section the current parsed and saved section data
00637  * @param parse_options whether we are parsing options or repo data
00638  * @return 0 on success, 1 on failure
00639  */
00640 static int finish_section(struct section_t *section, int parse_options)
00641 {
00642     int ret = 0;
00643     alpm_list_t *i;
00644     alpm_db_t *db;
00645 
00646     pm_printf(ALPM_LOG_DEBUG, "config: finish section '%s'\n", section->name);
00647 
00648     /* parsing options (or nothing)- nothing to do except free the pieces */
00649     if(!section->name || parse_options || section->is_options) {
00650         goto cleanup;
00651     }
00652 
00653     /* if we are not looking at options sections only, register a db */
00654     db = alpm_db_register_sync(config->handle, section->name, section->siglevel);
00655     if(db == NULL) {
00656         pm_printf(ALPM_LOG_ERROR, _("could not register '%s' database (%s)\n"),
00657                 section->name, alpm_strerror(alpm_errno(config->handle)));
00658         ret = 1;
00659         goto cleanup;
00660     }
00661 
00662     for(i = section->servers; i; i = alpm_list_next(i)) {
00663         char *value = i->data;
00664         if(_add_mirror(db, value) != 0) {
00665             pm_printf(ALPM_LOG_ERROR,
00666                     _("could not add mirror '%s' to database '%s' (%s)\n"),
00667                     value, section->name, alpm_strerror(alpm_errno(config->handle)));
00668             ret = 1;
00669             goto cleanup;
00670         }
00671         free(value);
00672     }
00673 
00674 cleanup:
00675     alpm_list_free(section->servers);
00676     section->servers = NULL;
00677     section->siglevel = ALPM_SIG_USE_DEFAULT;
00678     free(section->name);
00679     section->name = NULL;
00680     return ret;
00681 }
00682 
00683 /** The "real" parseconfig. Each "Include" directive will recall this method so
00684  * recursion and stack depth are limited to 10 levels. The publicly visible
00685  * parseconfig calls this with a NULL section argument so we can recall from
00686  * within ourself on an include.
00687  * @param file path to the config file
00688  * @param section the current active section
00689  * @param parse_options whether to parse and call methods for the options
00690  * section; if 0, parse and call methods for the repos sections
00691  * @param depth the current recursion depth
00692  * @return 0 on success, 1 on failure
00693  */
00694 static int _parseconfig(const char *file, struct section_t *section,
00695         int parse_options, int depth)
00696 {
00697     FILE *fp = NULL;
00698     char line[PATH_MAX];
00699     int linenum = 0;
00700     int ret = 0;
00701     const int max_depth = 10;
00702 
00703     if(depth >= max_depth) {
00704         pm_printf(ALPM_LOG_ERROR,
00705                 _("config parsing exceeded max recursion depth of %d.\n"), max_depth);
00706         ret = 1;
00707         goto cleanup;
00708     }
00709 
00710     pm_printf(ALPM_LOG_DEBUG, "config: attempting to read file %s\n", file);
00711     fp = fopen(file, "r");
00712     if(fp == NULL) {
00713         pm_printf(ALPM_LOG_ERROR, _("config file %s could not be read.\n"), file);
00714         ret = 1;
00715         goto cleanup;
00716     }
00717 
00718     while(fgets(line, PATH_MAX, fp)) {
00719         char *key, *value, *ptr;
00720         size_t line_len;
00721 
00722         linenum++;
00723 
00724         /* ignore whole line and end of line comments */
00725         if((ptr = strchr(line, '#'))) {
00726             *ptr = '\0';
00727         }
00728 
00729         line_len = strtrim(line);
00730 
00731         if(line_len == 0) {
00732             continue;
00733         }
00734 
00735         if(line[0] == '[' && line[line_len - 1] == ']') {
00736             char *name;
00737             /* only possibility here is a line == '[]' */
00738             if(line_len <= 2) {
00739                 pm_printf(ALPM_LOG_ERROR, _("config file %s, line %d: bad section name.\n"),
00740                         file, linenum);
00741                 ret = 1;
00742                 goto cleanup;
00743             }
00744             /* new config section, skip the '[' */
00745             name = strdup(line + 1);
00746             name[line_len - 2] = '\0';
00747             /* we're at a new section; perform any post-actions for the prior */
00748             if(finish_section(section, parse_options)) {
00749                 ret = 1;
00750                 goto cleanup;
00751             }
00752             pm_printf(ALPM_LOG_DEBUG, "config: new section '%s'\n", name);
00753             section->name = name;
00754             section->is_options = (strcmp(name, "options") == 0);
00755             continue;
00756         }
00757 
00758         /* directive */
00759         /* strsep modifies the 'line' string: 'key \0 value' */
00760         key = line;
00761         value = line;
00762         strsep(&value, "=");
00763         strtrim(key);
00764         strtrim(value);
00765 
00766         if(key == NULL) {
00767             pm_printf(ALPM_LOG_ERROR, _("config file %s, line %d: syntax error in config file- missing key.\n"),
00768                     file, linenum);
00769             ret = 1;
00770             goto cleanup;
00771         }
00772         /* For each directive, compare to the camelcase string. */
00773         if(section->name == NULL) {
00774             pm_printf(ALPM_LOG_ERROR, _("config file %s, line %d: All directives must belong to a section.\n"),
00775                     file, linenum);
00776             ret = 1;
00777             goto cleanup;
00778         }
00779         /* Include is allowed in both options and repo sections */
00780         if(strcmp(key, "Include") == 0) {
00781             glob_t globbuf;
00782             int globret;
00783             size_t gindex;
00784 
00785             if(value == NULL) {
00786                 pm_printf(ALPM_LOG_ERROR, _("config file %s, line %d: directive '%s' needs a value\n"),
00787                         file, linenum, key);
00788                 ret = 1;
00789                 goto cleanup;
00790             }
00791             /* Ignore include failures... assume non-critical */
00792             globret = glob(value, GLOB_NOCHECK, NULL, &globbuf);
00793             switch(globret) {
00794                 case GLOB_NOSPACE:
00795                     pm_printf(ALPM_LOG_DEBUG,
00796                             "config file %s, line %d: include globbing out of space\n",
00797                             file, linenum);
00798                 break;
00799                 case GLOB_ABORTED:
00800                     pm_printf(ALPM_LOG_DEBUG,
00801                             "config file %s, line %d: include globbing read error for %s\n",
00802                             file, linenum, value);
00803                 break;
00804                 case GLOB_NOMATCH:
00805                     pm_printf(ALPM_LOG_DEBUG,
00806                             "config file %s, line %d: no include found for %s\n",
00807                             file, linenum, value);
00808                 break;
00809                 default:
00810                     for(gindex = 0; gindex < globbuf.gl_pathc; gindex++) {
00811                         pm_printf(ALPM_LOG_DEBUG, "config file %s, line %d: including %s\n",
00812                                 file, linenum, globbuf.gl_pathv[gindex]);
00813                         _parseconfig(globbuf.gl_pathv[gindex], section, parse_options, depth + 1);
00814                     }
00815                 break;
00816             }
00817             globfree(&globbuf);
00818             continue;
00819         }
00820         if(parse_options && section->is_options) {
00821             /* we are either in options ... */
00822             if((ret = _parse_options(key, value, file, linenum)) != 0) {
00823                 goto cleanup;
00824             }
00825         } else if(!parse_options && !section->is_options) {
00826             /* ... or in a repo section */
00827             if(strcmp(key, "Server") == 0) {
00828                 if(value == NULL) {
00829                     pm_printf(ALPM_LOG_ERROR, _("config file %s, line %d: directive '%s' needs a value\n"),
00830                             file, linenum, key);
00831                     ret = 1;
00832                     goto cleanup;
00833                 }
00834                 section->servers = alpm_list_add(section->servers, strdup(value));
00835             } else if(strcmp(key, "SigLevel") == 0) {
00836                 alpm_list_t *values = NULL;
00837                 setrepeatingoption(value, "SigLevel", &values);
00838                 if(values) {
00839                     if(section->siglevel == ALPM_SIG_USE_DEFAULT) {
00840                         section->siglevel = config->siglevel;
00841                     }
00842                     if(process_siglevel(values, &section->siglevel, file, linenum)) {
00843                         FREELIST(values);
00844                         ret = 1;
00845                         goto cleanup;
00846                     }
00847                     FREELIST(values);
00848                 }
00849             } else {
00850                 pm_printf(ALPM_LOG_WARNING,
00851                         _("config file %s, line %d: directive '%s' in section '%s' not recognized.\n"),
00852                         file, linenum, key, section->name);
00853             }
00854         }
00855     }
00856 
00857     if(depth == 0) {
00858         ret = finish_section(section, parse_options);
00859     }
00860 
00861 cleanup:
00862     if(fp) {
00863         fclose(fp);
00864     }
00865     pm_printf(ALPM_LOG_DEBUG, "config: finished parsing %s\n", file);
00866     return ret;
00867 }
00868 
00869 /** Parse a configuration file.
00870  * @param file path to the config file
00871  * @return 0 on success, non-zero on error
00872  */
00873 int parseconfig(const char *file)
00874 {
00875     int ret;
00876     struct section_t section;
00877     memset(&section, 0, sizeof(struct section_t));
00878     section.siglevel = ALPM_SIG_USE_DEFAULT;
00879     /* the config parse is a two-pass affair. We first parse the entire thing for
00880      * the [options] section so we can get all default and path options set.
00881      * Next, we go back and parse everything but [options]. */
00882 
00883     /* call the real parseconfig function with a null section & db argument */
00884     pm_printf(ALPM_LOG_DEBUG, "parseconfig: options pass\n");
00885     if((ret = _parseconfig(file, &section, 1, 0))) {
00886         return ret;
00887     }
00888     if((ret = setup_libalpm())) {
00889         return ret;
00890     }
00891     /* second pass, repo section parsing */
00892     pm_printf(ALPM_LOG_DEBUG, "parseconfig: repo pass\n");
00893     return _parseconfig(file, &section, 0, 0);
00894 }
00895 
00896 /* vim: set ts=2 sw=2 noet: */