manually apply --sysroot to configuration

The previous chroot-based sysroot often broke due to glibc's delayed
loading for much of its functionality when the sysroot did not contain
compatible copies of the necessary libraries.

This approach instead manually prepends the sysroot to all configuration
paths.

BREAKING CHANGE: targets to -U are no longer interpreted relative to
sysroot

Signed-off-by: Andrew Gregory <andrew.gregory.8@gmail.com>
Signed-off-by: Allan McRae <allan@archlinux.org>
This commit is contained in:
Andrew Gregory 2020-10-11 14:01:11 -07:00 committed by Allan McRae
parent 2180e4d127
commit 7016adcb70
5 changed files with 174 additions and 18 deletions

View file

@ -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* <dir>::
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* <dir>:: 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')

View file

@ -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, &section);
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, &section);
}
/** Parse a configuration file.

View file

@ -44,6 +44,7 @@ static void usage(int ret)
fputs(_("options:\n"), stream);
fputs(_(" -c, --config=<path> set an alternate configuration file\n"), stream);
fputs(_(" -R, --rootdir=<path> set an alternate installation root\n"), stream);
fputs(_(" -S, --sysroot=<path> set an alternate system root\n"), stream);
fputs(_(" -r, --repo=<remote> 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;

View file

@ -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 */

View file

@ -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;