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;