diff --git a/doc/pacman.8.asciidoc b/doc/pacman.8.asciidoc index 8580f453..345405d4 100644 --- a/doc/pacman.8.asciidoc +++ b/doc/pacman.8.asciidoc @@ -195,11 +195,10 @@ Options Disable defaults for low speed limit and timeout on downloads. Use this if you have issues downloading files with proxy and/or security gateway. -*\--sysroot* :: - Specify an alternative system root. Pacman will chroot and chdir into the - system root prior to running. This allows mounted guest systems to be - properly operated on. Any other paths given will be interpreted as relative - to the system root. Requires root privileges. +*\--sysroot* :: Specify an alternative system root. This path will be + prepended to all other configuration directories and any repository servers + beginning with `file://`. Any paths or URLs passed as targets will not be + modified. This allows mounted guest systems to be properly operated on. Transaction Options (apply to '-S', '-R' and '-U') diff --git a/src/pacman/conf.c b/src/pacman/conf.c index 14096441..dbce1a4f 100644 --- a/src/pacman/conf.c +++ b/src/pacman/conf.c @@ -108,6 +108,7 @@ config_t *config_new(void) newconfig->op = PM_OP_MAIN; newconfig->logmask = ALPM_LOG_ERROR | ALPM_LOG_WARNING; newconfig->configfile = strdup(CONFFILE); + newconfig->sysroot = strdup("/"); if(alpm_capabilities() & ALPM_CAPABILITY_SIGNATURES) { newconfig->siglevel = ALPM_SIG_PACKAGE | ALPM_SIG_PACKAGE_OPTIONAL | ALPM_SIG_DATABASE | ALPM_SIG_DATABASE_OPTIONAL; @@ -151,6 +152,7 @@ int config_free(config_t *oldconfig) FREELIST(oldconfig->noextract); FREELIST(oldconfig->overwrite_files); free(oldconfig->configfile); + free(oldconfig->sysroot); free(oldconfig->rootdir); free(oldconfig->dbpath); free(oldconfig->logfile); @@ -1047,6 +1049,100 @@ static int _parse_repo(const char *key, char *value, const char *file, static int _parse_directive(const char *file, int linenum, const char *name, char *key, char *value, void *data); +static char *escape_chars(const char *pattern, const char *escape) +{ + size_t escape_len, len, pattern_chars; + const char *c; + char *escaped, *e; + + if(pattern == NULL || escape == NULL) { + return NULL; + } + + len = strlen(pattern); + escape_len = strlen(escape); + pattern_chars = 0; + for(c = pattern; *c; c++) { + if(memchr(escape, *c, escape_len)) { + pattern_chars++; + } + } + + if(pattern_chars == 0) { + return strdup(pattern); + } + + /* allocate new string with overflow check */ + if(SIZE_MAX - len < pattern_chars + || !(escaped = malloc(len + pattern_chars))) { + errno = ENOMEM; + return NULL; + } + + for(c = pattern, e = escaped; *c; c++, e++) { + if(memchr(escape, *c, escape_len)) { + *e = '\\'; + e++; + } + *e = *c; + } + *e = '\0'; + + return escaped; +} + +static char *escape_glob_pattern(const char *pattern) +{ + return escape_chars(pattern, "\\*?["); +} + +static char *prepend_dir(const char *dir, const char *path) +{ + char *newpath; + size_t dlen = strlen(dir); + const char *sep = dlen && dir[dlen - 1] == '/' ? "" : "/"; + while(path[0] == '/') { path++; } + return pm_asprintf(&newpath, "%s%s%s", dir, sep, path) == -1 ? NULL : newpath; +} + +static int globdir(const char *dir, const char *pattern, int flags, + int (*errfunc) (const char *epath, int eerrno), glob_t *globbuf) +{ + int gret; + char *fullpattern = NULL, *escaped_dir = NULL; + + if((escaped_dir = escape_glob_pattern(dir)) == NULL) { + goto nospace; + } + + if(flags & GLOB_NOESCAPE) { + /* "disable" backslash escaping by escaping any backlashes */ + char *escaped_pattern = escape_chars(pattern, "\\"); + if(escaped_pattern == NULL) { + goto nospace; + } + fullpattern = prepend_dir(escaped_dir, escaped_pattern); + free(escaped_pattern); + flags &= ~GLOB_NOESCAPE; + } else { + fullpattern = prepend_dir(escaped_dir, pattern); + } + + if(fullpattern == NULL) { + goto nospace; + } + + gret = glob(fullpattern, flags, errfunc, globbuf); + free(escaped_dir); + free(fullpattern); + return gret; + +nospace: + free(escaped_dir); + free(fullpattern); + return GLOB_NOSPACE; +} + static int process_include(const char *value, void *data, const char *file, int linenum) { @@ -1072,7 +1168,7 @@ static int process_include(const char *value, void *data, section->depth++; /* Ignore include failures... assume non-critical */ - globret = glob(value, GLOB_NOCHECK, NULL, &globbuf); + globret = globdir(config->sysroot, value, GLOB_NOCHECK, NULL, &globbuf); switch(globret) { case GLOB_NOSPACE: pm_printf(ALPM_LOG_DEBUG, @@ -1149,6 +1245,58 @@ static int _parse_directive(const char *file, int linenum, const char *name, } } +static int prepend_sysroot(config_t *c) +{ + alpm_list_t *i; + + if(c->sysroot == NULL || c->sysroot[0] == '\0' + || strcmp(c->sysroot, "/") == 0) { + return 0; + } + +#define SETSYSROOT(opt) \ + if(opt) { \ + char *n = prepend_dir(c->sysroot, opt);\ + if(n == NULL) { return -1; } \ + else { free(opt); opt = n; } \ + } + + SETSYSROOT(c->rootdir); + SETSYSROOT(c->dbpath); + SETSYSROOT(c->logfile); + SETSYSROOT(c->gpgdir); + for(i = c->cachedirs; i; i = i->next) { + SETSYSROOT(i->data); + } + for(i = c->hookdirs; i; i = i->next) { + SETSYSROOT(i->data); + } + + for(i = c->repos; i; i = i->next) { + config_repo_t *r = i->data; + alpm_list_t *j; + for(j = r->servers; j; j = j->next) { + if(strncmp("file://", j->data, 7) == 0) { + char *newdir = NULL, *newurl = NULL, *oldurl = j->data; + const char *olddir = oldurl + 7; + if((newdir = prepend_dir(c->sysroot, olddir)) == NULL + || (pm_asprintf(&newurl, "file://%s", newdir)) == -1) { + free(newdir); + free(newurl); + return -1; + } + free(newdir); + free(j->data); + j->data = newurl; + } + } + } + +#undef SETSYSROOT + + return 0; +} + int setdefaults(config_t *c) { alpm_list_t *i; @@ -1212,14 +1360,22 @@ int setdefaults(config_t *c) #undef SETDEFAULT + prepend_sysroot(c); + return 0; } int parseconfigfile(const char *file) { struct section_t section = {0}; - pm_printf(ALPM_LOG_DEBUG, "config: attempting to read file %s\n", file); - return parse_ini(file, _parse_directive, §ion); + char *realfile; + if((realfile = prepend_dir(config->sysroot, file)) == NULL) { + return -1; + } + pm_printf(ALPM_LOG_DEBUG, "config: attempting to read file %s\n", realfile); + free(config->configfile); + config->configfile = realfile; + return parse_ini(realfile, _parse_directive, §ion); } /** Parse a configuration file. diff --git a/src/pacman/pacman-conf.c b/src/pacman/pacman-conf.c index 9517de3e..1c35e753 100644 --- a/src/pacman/pacman-conf.c +++ b/src/pacman/pacman-conf.c @@ -44,6 +44,7 @@ static void usage(int ret) fputs(_("options:\n"), stream); fputs(_(" -c, --config= set an alternate configuration file\n"), stream); fputs(_(" -R, --rootdir= set an alternate installation root\n"), stream); + fputs(_(" -S, --sysroot= set an alternate system root\n"), stream); fputs(_(" -r, --repo= query options for a specific repo\n"), stream); fputs(_(" -v, --verbose always show directive names\n"), stream); fputs(_(" -l, --repo-list list configured repositories\n"), stream); @@ -58,10 +59,11 @@ static void parse_opts(int argc, char **argv) int c; config_file = CONFFILE; - const char *short_opts = "c:hlR:r:Vv"; + const char *short_opts = "c:hlR:S:r:Vv"; struct option long_opts[] = { { "config" , required_argument , NULL , 'c' }, { "rootdir" , required_argument , NULL , 'R' }, + { "sysroot" , required_argument , NULL , 'S' }, { "repo" , required_argument , NULL , 'r' }, { "repo-list" , no_argument , NULL , 'l' }, { "verbose" , no_argument , NULL , 'v' }, @@ -83,6 +85,14 @@ static void parse_opts(int argc, char **argv) exit(1); } break; + case 'S': + free(config->sysroot); + if((config->sysroot = strdup(optarg)) == NULL) { + fprintf(stderr, _("error setting sysroot '%s': out of memory\n"), optarg); + cleanup(); + exit(1); + } + break; case 'l': repo_list = 1; break; diff --git a/src/pacman/pacman.c b/src/pacman/pacman.c index e5c6e420..8f94590c 100644 --- a/src/pacman/pacman.c +++ b/src/pacman/pacman.c @@ -1187,12 +1187,6 @@ int main(int argc, char *argv[]) } } - if(config->sysroot && (chroot(config->sysroot) != 0 || chdir("/") != 0)) { - pm_printf(ALPM_LOG_ERROR, - _("chroot to '%s' failed: (%s)\n"), config->sysroot, strerror(errno)); - cleanup(EXIT_FAILURE); - } - pm_printf(ALPM_LOG_DEBUG, "pacman v%s - libalpm v%s\n", PACKAGE_VERSION, alpm_version()); /* parse the config file */ diff --git a/src/pacman/util.c b/src/pacman/util.c index ffdb38e8..2b976495 100644 --- a/src/pacman/util.c +++ b/src/pacman/util.c @@ -121,9 +121,6 @@ int trans_release(void) int needs_root(void) { - if(config->sysroot) { - return 1; - } switch(config->op) { case PM_OP_DATABASE: return !config->op_q_check;