blob: f53c18cdb77ff8538d7407814f88189ec3d2f09a [file]
/* -*- c-set-style: "K&R"; c-basic-offset: 8 -*-
*
* This file is part of PRoot.
*
* Copyright (C) 2014 STMicroelectronics
*
* 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, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
* 02110-1301 USA.
*/
#include <sys/stat.h> /* lstat(2), */
#include <unistd.h> /* getcwd(2), lstat(2), */
#include <string.h> /* string(3), */
#include <strings.h> /* bzero(3), */
#include <assert.h> /* assert(3), */
#include <limits.h> /* PATH_MAX, */
#include <errno.h> /* E* */
#include <sys/queue.h> /* CIRCLEQ_*, */
#include <talloc.h> /* talloc_*, */
#include "path/binding.h"
#include "path/path.h"
#include "path/canon.h"
#include "cli/note.h"
#include "compat.h"
#define HEAD(tracee, side) \
(side == GUEST \
? (tracee)->fs->bindings.guest \
: (side == HOST \
? (tracee)->fs->bindings.host \
: (tracee)->fs->bindings.pending))
#define NEXT(binding, side) \
(side == GUEST \
? CIRCLEQ_NEXT(binding, link.guest) \
: (side == HOST \
? CIRCLEQ_NEXT(binding, link.host) \
: CIRCLEQ_NEXT(binding, link.pending)))
#define CIRCLEQ_FOREACH_(tracee, binding, side) \
for (binding = CIRCLEQ_FIRST(HEAD(tracee, side)); \
binding != (void *) HEAD(tracee, side); \
binding = NEXT(binding, side))
#define CIRCLEQ_INSERT_AFTER_(tracee, previous, binding, side) do { \
switch (side) { \
case GUEST: CIRCLEQ_INSERT_AFTER(HEAD(tracee, side), previous, binding, link.guest); break; \
case HOST: CIRCLEQ_INSERT_AFTER(HEAD(tracee, side), previous, binding, link.host); break; \
default: CIRCLEQ_INSERT_AFTER(HEAD(tracee, side), previous, binding, link.pending); break; \
} \
(void) talloc_reference(HEAD(tracee, side), binding); \
} while (0)
#define CIRCLEQ_INSERT_BEFORE_(tracee, next, binding, side) do { \
switch (side) { \
case GUEST: CIRCLEQ_INSERT_BEFORE(HEAD(tracee, side), next, binding, link.guest); break; \
case HOST: CIRCLEQ_INSERT_BEFORE(HEAD(tracee, side), next, binding, link.host); break; \
default: CIRCLEQ_INSERT_BEFORE(HEAD(tracee, side), next, binding, link.pending); break; \
} \
(void) talloc_reference(HEAD(tracee, side), binding); \
} while (0)
#define CIRCLEQ_INSERT_HEAD_(tracee, binding, side) do { \
switch (side) { \
case GUEST: CIRCLEQ_INSERT_HEAD(HEAD(tracee, side), binding, link.guest); break; \
case HOST: CIRCLEQ_INSERT_HEAD(HEAD(tracee, side), binding, link.host); break; \
default: CIRCLEQ_INSERT_HEAD(HEAD(tracee, side), binding, link.pending); break; \
} \
(void) talloc_reference(HEAD(tracee, side), binding); \
} while (0)
#define IS_LINKED(binding, link) \
((binding)->link.cqe_next != NULL && (binding)->link.cqe_prev != NULL)
#define CIRCLEQ_REMOVE_(tracee, binding, name) do { \
CIRCLEQ_REMOVE((tracee)->fs->bindings.name, binding, link.name);\
(binding)->link.name.cqe_next = NULL; \
(binding)->link.name.cqe_prev = NULL; \
talloc_unlink((tracee)->fs->bindings.name, binding); \
} while (0)
/**
* Print all bindings (verbose purpose).
*/
static void print_bindings(const Tracee *tracee)
{
const Binding *binding;
if (tracee->fs->bindings.guest == NULL)
return;
CIRCLEQ_FOREACH_(tracee, binding, GUEST) {
if (compare_paths(binding->host.path, binding->guest.path) == PATHS_ARE_EQUAL)
note(tracee, INFO, USER, "binding = %s", binding->host.path);
else
note(tracee, INFO, USER, "binding = %s:%s",
binding->host.path, binding->guest.path);
}
}
/**
* Get the binding for the given @path (relatively to the given
* binding @side).
*/
Binding *get_binding(const Tracee *tracee, Side side, const char path[PATH_MAX])
{
Binding *binding;
size_t path_length = strlen(path);
/* Sanity checks. */
assert(path != NULL && path[0] == '/');
CIRCLEQ_FOREACH_(tracee, binding, side) {
Comparison comparison;
const Path *ref;
switch (side) {
case GUEST:
ref = &binding->guest;
break;
case HOST:
ref = &binding->host;
break;
default:
assert(0);
return NULL;
}
comparison = compare_paths2(ref->path, ref->length, path, path_length);
if ( comparison != PATHS_ARE_EQUAL
&& comparison != PATH1_IS_PREFIX)
continue;
/* Avoid false positive when a prefix of the rootfs is
* used as an asymmetric binding, ex.:
*
* proot -m /usr:/location /usr/local/slackware
*/
if ( side == HOST
&& compare_paths(get_root(tracee), "/") != PATHS_ARE_EQUAL
&& belongs_to_guestfs(tracee, path))
continue;
return binding;
}
return NULL;
}
/**
* Get the binding path for the given @path (relatively to the given
* binding @side).
*/
const char *get_path_binding(const Tracee *tracee, Side side, const char path[PATH_MAX])
{
const Binding *binding;
binding = get_binding(tracee, side, path);
if (!binding)
return NULL;
switch (side) {
case GUEST:
return binding->guest.path;
case HOST:
return binding->host.path;
default:
assert(0);
return NULL;
}
}
/**
* Return the path to the guest rootfs for the given @tracee, from the
* host point-of-view obviously. Depending on whether
* initialize_bindings() was called or not, the path is retrieved from
* the "bindings.guest" list or from the "bindings.pending" list,
* respectively.
*/
const char *get_root(const Tracee* tracee)
{
const Binding *binding;
if (tracee == NULL || tracee->fs == NULL)
return NULL;
if (tracee->fs->bindings.guest == NULL) {
if (tracee->fs->bindings.pending == NULL
|| CIRCLEQ_EMPTY(tracee->fs->bindings.pending))
return NULL;
binding = CIRCLEQ_LAST(tracee->fs->bindings.pending);
if (compare_paths(binding->guest.path, "/") != PATHS_ARE_EQUAL)
return NULL;
return binding->host.path;
}
assert(!CIRCLEQ_EMPTY(tracee->fs->bindings.guest));
binding = CIRCLEQ_LAST(tracee->fs->bindings.guest);
assert(strcmp(binding->guest.path, "/") == 0);
return binding->host.path;
}
/**
* Substitute the guest path (if any) with the host path in @path.
* This function returns:
*
* * -errno if an error occured
*
* * 0 if it is a binding location but no substitution is needed
* ("symetric" binding)
*
* * 1 if it is a binding location and a substitution was performed
* ("asymmetric" binding)
*/
int substitute_binding(const Tracee *tracee, Side side, char path[PATH_MAX])
{
const Path *reverse_ref;
const Path *ref;
const Binding *binding;
binding = get_binding(tracee, side, path);
if (!binding)
return -ENOENT;
/* Is it a "symetric" binding? */
if (!binding->need_substitution)
return 0;
switch (side) {
case GUEST:
ref = &binding->guest;
reverse_ref = &binding->host;
break;
case HOST:
ref = &binding->host;
reverse_ref = &binding->guest;
break;
default:
assert(0);
return -EACCES;
}
substitute_path_prefix(path, ref->length, reverse_ref->path, reverse_ref->length);
return 1;
}
/**
* Remove @binding from all the @tracee's lists of bindings it belongs to.
*/
void remove_binding_from_all_lists(const Tracee *tracee, Binding *binding)
{
if (IS_LINKED(binding, link.pending))
CIRCLEQ_REMOVE_(tracee, binding, pending);
if (IS_LINKED(binding, link.guest))
CIRCLEQ_REMOVE_(tracee, binding, guest);
if (IS_LINKED(binding, link.host))
CIRCLEQ_REMOVE_(tracee, binding, host);
}
/**
* Insert @binding into the list of @bindings, in a sorted manner so
* as to make the substitution of nested bindings determistic, ex.:
*
* -b /bin:/foo/bin -b /usr/bin/more:/foo/bin/more
*
* Note: "nested" from the @side point-of-view.
*/
static void insort_binding(const Tracee *tracee, Side side, Binding *binding)
{
Binding *iterator;
Binding *previous = NULL;
Binding *next = CIRCLEQ_FIRST(HEAD(tracee, side));
/* Find where it should be added in the list. */
CIRCLEQ_FOREACH_(tracee, iterator, side) {
Comparison comparison;
const Path *binding_path;
const Path *iterator_path;
switch (side) {
case PENDING:
case GUEST:
binding_path = &binding->guest;
iterator_path = &iterator->guest;
break;
case HOST:
binding_path = &binding->host;
iterator_path = &iterator->host;
break;
default:
assert(0);
return;
}
comparison = compare_paths2(binding_path->path, binding_path->length,
iterator_path->path, iterator_path->length);
switch (comparison) {
case PATHS_ARE_EQUAL:
if (side == HOST) {
previous = iterator;
break;
}
if (tracee->verbose > 0 && getenv("PROOT_IGNORE_MISSING_BINDINGS") == NULL) {
note(tracee, WARNING, USER,
"both '%s' and '%s' are bound to '%s', "
"only the last binding is active.",
iterator->host.path, binding->host.path,
binding->guest.path);
}
/* Replace this iterator with the new binding. */
CIRCLEQ_INSERT_AFTER_(tracee, iterator, binding, side);
remove_binding_from_all_lists(tracee, iterator);
return;
case PATH1_IS_PREFIX:
/* The new binding contains the iterator. */
previous = iterator;
break;
case PATH2_IS_PREFIX:
/* The iterator contains the new binding.
* Use the deepest container. */
if (next == (void *) HEAD(tracee, side))
next = iterator;
break;
case PATHS_ARE_NOT_COMPARABLE:
break;
default:
assert(0);
return;
}
}
/* Insert this binding in the list. */
if (previous != NULL)
CIRCLEQ_INSERT_AFTER_(tracee, previous, binding, side);
else if (next != (void *) HEAD(tracee, side))
CIRCLEQ_INSERT_BEFORE_(tracee, next, binding, side);
else
CIRCLEQ_INSERT_HEAD_(tracee, binding, side);
}
/**
* c.f. function above.
*/
static void insort_binding2(const Tracee *tracee, Binding *binding)
{
binding->need_substitution =
compare_paths(binding->host.path, binding->guest.path) != PATHS_ARE_EQUAL;
insort_binding(tracee, GUEST, binding);
insort_binding(tracee, HOST, binding);
}
/**
* Create and insert a new binding (@host_path:@guest_path) into the
* list of @tracee's bindings. The Talloc parent of this new binding
* is @context. This function returns NULL if an error occurred,
* otherwise a pointer to the newly created binding.
*/
Binding *insort_binding3(const Tracee *tracee, const TALLOC_CTX *context,
const char host_path[PATH_MAX],
const char guest_path[PATH_MAX])
{
Binding *binding;
binding = talloc_zero(context, Binding);
if (binding == NULL)
return NULL;
strcpy(binding->host.path, host_path);
strcpy(binding->guest.path, guest_path);
binding->host.length = strlen(binding->host.path);
binding->guest.length = strlen(binding->guest.path);
insort_binding2(tracee, binding);
return binding;
}
/**
* Free all bindings from @bindings.
*
* Note: this is a Talloc destructor.
*/
static int remove_bindings(Bindings *bindings)
{
Binding *binding;
Tracee *tracee;
/* Unlink all bindings from the @link list. */
#define CIRCLEQ_REMOVE_ALL(name) do { \
binding = CIRCLEQ_FIRST(bindings); \
while (binding != (void *) bindings) { \
Binding *next = CIRCLEQ_NEXT(binding, link.name);\
CIRCLEQ_REMOVE_(tracee, binding, name); \
binding = next; \
} \
} while (0)
/* Search which link is used by this list. */
tracee = TRACEE(bindings);
if (bindings == tracee->fs->bindings.pending)
CIRCLEQ_REMOVE_ALL(pending);
else if (bindings == tracee->fs->bindings.guest)
CIRCLEQ_REMOVE_ALL(guest);
else if (bindings == tracee->fs->bindings.host)
CIRCLEQ_REMOVE_ALL(host);
bzero(bindings, sizeof(Bindings));
return 0;
}
/**
* Allocate a new binding "@host:@guest" and attach it to
* @tracee->fs->bindings.pending. This function complains about
* missing @host path only if @must_exist is true. This function
* returns the allocated binding on success, NULL on error.
*/
Binding *new_binding(Tracee *tracee, const char *host, const char *guest, bool must_exist)
{
Binding *binding;
char base[PATH_MAX];
int status;
/* Lasy allocation of the list of bindings specified by the
* user. This list will be used by initialize_bindings(). */
if (tracee->fs->bindings.pending == NULL) {
tracee->fs->bindings.pending = talloc_zero(tracee->fs, Bindings);
if (tracee->fs->bindings.pending == NULL)
return NULL;
CIRCLEQ_INIT(tracee->fs->bindings.pending);
talloc_set_destructor(tracee->fs->bindings.pending, remove_bindings);
}
/* Allocate an empty binding. */
binding = talloc_zero(tracee->ctx, Binding);
if (binding == NULL)
return NULL;
/* Canonicalize the host part of the binding, as expected by
* get_binding(). */
status = realpath2(tracee->reconf.tracee, binding->host.path, host, true);
if (status < 0) {
if (must_exist && getenv("PROOT_IGNORE_MISSING_BINDINGS") == NULL)
note(tracee, WARNING, INTERNAL, "can't sanitize binding \"%s\": %s",
host, strerror(-status));
goto error;
}
binding->host.length = strlen(binding->host.path);
/* Symetric binding? */
guest = guest ?: host;
/* When not absolute, assume the guest path is relative to the
* current working directory, as with ``-b .`` for instance. */
if (guest[0] != '/') {
status = getcwd2(tracee->reconf.tracee, base);
if (status < 0) {
note(tracee, WARNING, INTERNAL, "can't sanitize binding \"%s\": %s",
binding->guest.path, strerror(-status));
goto error;
}
}
else
strcpy(base, "/");
status = join_paths(2, binding->guest.path, base, guest);
if (status < 0) {
note(tracee, WARNING, SYSTEM, "can't sanitize binding \"%s\"",
binding->guest.path);
goto error;
}
binding->guest.length = strlen(binding->guest.path);
/* Keep the list of bindings specified by the user ordered,
* for the sake of consistency. For instance binding to "/"
* has to be the last in the list. */
insort_binding(tracee, PENDING, binding);
return binding;
error:
TALLOC_FREE(binding);
return NULL;
}
/**
* Canonicalize the guest part of the given @binding, insert it into
* @tracee->fs->bindings.guest and @tracee->fs->bindings.host. This
* function returns -1 if an error occured, 0 otherwise.
*/
static void initialize_binding(Tracee *tracee, Binding *binding)
{
char path[PATH_MAX];
struct stat statl;
int status;
/* All bindings but "/" must be canonicalized. The exception
* for "/" is required to bootstrap the canonicalization. */
if (compare_paths(binding->guest.path, "/") != PATHS_ARE_EQUAL) {
bool dereference;
size_t length;
strcpy(path, binding->guest.path);
length = strlen(path);
assert(length > 0);
/* Does the user explicitly tell not to dereference
* guest path? */
dereference = (path[length - 1] != '!');
if (!dereference)
path[length - 1] = '\0';
/* Initial state before canonicalization. */
strcpy(binding->guest.path, "/");
/* Remember the type of the final component, it will
* be used in build_glue() later. */
status = lstat(binding->host.path, &statl);
tracee->glue_type = (status < 0 || S_ISBLK(statl.st_mode) || S_ISCHR(statl.st_mode)
? S_IFREG : statl.st_mode & S_IFMT);
/* Sanitize the guest path of the binding within the
alternate rootfs since it is assumed by
substitute_binding(). */
status = canonicalize(tracee, path, dereference, binding->guest.path, 0);
if (status < 0) {
note(tracee, WARNING, INTERNAL,
"sanitizing the guest path (binding) \"%s\": %s",
path, strerror(-status));
return;
}
/* Remove the trailing "/" or "/." as expected by
* substitute_binding(). */
chop_finality(binding->guest.path);
/* Disable definitively the creation of the glue for
* this binding. */
tracee->glue_type = 0;
}
binding->guest.length = strlen(binding->guest.path);
insort_binding2(tracee, binding);
}
/**
* Add bindings induced by @new_binding when @tracee is being sub-reconfigured.
* For example, if the previous configuration ("-r /rootfs1") contains this
* binding:
*
* -b /home/ced:/usr/local/ced
*
* and if the current configuration ("-r /rootfs2") introduces such a new
* binding:
*
* -b /usr:/media
*
* then the following binding is induced:
*
* -b /home/ced:/media/local/ced
*/
static void add_induced_bindings(Tracee *tracee, const Binding *new_binding)
{
Binding *old_binding;
char path[PATH_MAX];
int status;
/* Only for reconfiguration. */
if (tracee->reconf.tracee == NULL)
return;
/* From the example, PRoot has already converted "-b /usr:/media" into
* "-b /rootfs1/usr:/media" in order to ensure the host part is really a
* host path. Here, the host part is converted back to "/usr" since the
* comparison can't be made on "/rootfs1/usr".
*/
strcpy(path, new_binding->host.path);
status = detranslate_path(tracee->reconf.tracee, path, NULL);
if (status < 0)
return;
CIRCLEQ_FOREACH_(tracee->reconf.tracee, old_binding, GUEST) {
Binding *induced_binding;
Comparison comparison;
char path2[PATH_MAX];
size_t prefix_length;
/* Check if there's an induced binding by searching a common
* path prefix in between new/old bindings:
*
* -b /home/ced:[/usr]/local/ced
* -b [/usr]:/media
*/
comparison = compare_paths(path, old_binding->guest.path);
if (comparison != PATH1_IS_PREFIX)
continue;
/* Convert the path of this induced binding to the new
* filesystem namespace. From the example, "/usr/local/ced" is
* converted into "/media/local/ced". Note: substitute_binding
* can't be used in this case since it would expect
* "/rootfs1/usr/local/ced instead".
*/
prefix_length = strlen(path);
if (prefix_length == 1)
prefix_length = 0;
status = join_paths(2, path2, new_binding->guest.path, old_binding->guest.path + prefix_length);
if (status < 0)
continue;
/* Install the induced binding. From the example:
*
* -b /home/ced:/media/local/ced
*/
induced_binding = talloc_zero(tracee->ctx, Binding);
if (induced_binding == NULL)
continue;
strcpy(induced_binding->host.path, old_binding->host.path);
strcpy(induced_binding->guest.path, path2);
induced_binding->host.length = strlen(induced_binding->host.path);
induced_binding->guest.length = strlen(induced_binding->guest.path);
VERBOSE(tracee, 2, "induced binding: %s:%s (old) & %s:%s (new) -> %s:%s (induced)",
old_binding->host.path, old_binding->guest.path, path, new_binding->guest.path,
induced_binding->host.path, induced_binding->guest.path);
insort_binding2(tracee, induced_binding);
}
}
/**
* Allocate @tracee->fs->bindings.guest and
* @tracee->fs->bindings.host, then call initialize_binding() on each
* binding listed in @tracee->fs->bindings.pending.
*/
int initialize_bindings(Tracee *tracee)
{
Binding *binding;
/* Sanity checks. */
assert(get_root(tracee) != NULL);
assert(tracee->fs->bindings.pending != NULL);
assert(tracee->fs->bindings.guest == NULL);
assert(tracee->fs->bindings.host == NULL);
/* Allocate @tracee->fs->bindings.guest and
* @tracee->fs->bindings.host. */
tracee->fs->bindings.guest = talloc_zero(tracee->fs, Bindings);
tracee->fs->bindings.host = talloc_zero(tracee->fs, Bindings);
if (tracee->fs->bindings.guest == NULL || tracee->fs->bindings.host == NULL) {
note(tracee, ERROR, INTERNAL, "can't allocate enough memory");
TALLOC_FREE(tracee->fs->bindings.guest);
TALLOC_FREE(tracee->fs->bindings.host);
return -1;
}
CIRCLEQ_INIT(tracee->fs->bindings.guest);
CIRCLEQ_INIT(tracee->fs->bindings.host);
talloc_set_destructor(tracee->fs->bindings.guest, remove_bindings);
talloc_set_destructor(tracee->fs->bindings.host, remove_bindings);
/* The binding to "/" has to be installed before other
* bindings since this former is required to canonicalize
* these latters. */
binding = CIRCLEQ_LAST(tracee->fs->bindings.pending);
assert(compare_paths(binding->guest.path, "/") == PATHS_ARE_EQUAL);
/* Call initialize_binding() on each pending binding in
* reverse order: the last binding "/" is used to bootstrap
* the canonicalization. */
while (binding != (void *) tracee->fs->bindings.pending) {
Binding *previous;
previous = CIRCLEQ_PREV(binding, link.pending);
/* Canonicalize then insert this binding into
* tracee->fs->bindings.guest/host. */
initialize_binding(tracee, binding);
/* Add induced bindings on sub-reconfiguration. */
add_induced_bindings(tracee, binding);
binding = previous;
}
TALLOC_FREE(tracee->fs->bindings.pending);
if (tracee->verbose > 0)
print_bindings(tracee);
return 0;
}