blob: a21c548a568a59c58d8da9bfbb543866e578820d [file] [edit]
/* -*- 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/types.h> /* mkdir(2), lstat(2), */
#include <sys/stat.h> /* mkdir(2), lstat(2), */
#include <fcntl.h> /* mknod(2), */
#include <unistd.h> /* mknod(2), lstat(2), unlink(2), rmdir(2), */
#include <string.h> /* string(3), */
#include <assert.h> /* assert(3), */
#include <limits.h> /* PATH_MAX, */
#include <errno.h> /* errno, E* */
#include <talloc.h> /* talloc_*, */
#include "path/binding.h"
#include "path/path.h"
#include "path/temp.h"
#include "cli/note.h"
#include "compat.h"
/**
* Remove @path if it is empty only.
*
* Note: this is a Talloc destructor.
*/
static int remove_placeholder(char *path)
{
struct stat statl;
int status;
status = lstat(path, &statl);
if (status)
return 0; /* Not fatal. */
if (!S_ISDIR(statl.st_mode)) {
if (statl.st_size != 0)
return 0; /* Not fatal. */
status = unlink(path);
}
else
status = rmdir(path);
if (status)
return 0; /* Not fatal. */
return 0;
}
/**
* Attach a copy of @path to the autofree context, and set its
* destructor to remove_placeholder().
*/
static void set_placeholder_destructor(const char *path)
{
TALLOC_CTX *autofreed;
char *placeholder;
autofreed = talloc_autofree_context();
if (autofreed == NULL)
return;
placeholder = talloc_strdup(autofreed, path);
if (placeholder == NULL)
return;
talloc_set_destructor(placeholder, remove_placeholder);
}
/**
* Build in a temporary filesystem the glue between the guest part and
* the host part of the @binding_path. This function returns the type
* of the bound path, otherwise 0 if an error occured.
*
* For example, assuming the host path "/opt" is mounted/bound to the
* guest path "/black/holes/and/revelations", and assuming this path
* can't be created in the guest rootfs (eg. permission denied), then
* it is created in a temporary rootfs and all these paths are glued
* that way:
*
* $GUEST/black/ --> $GLUE/black/
* ./holes
* ./holes/and
* ./holes/and/revelations --> $HOST/opt/
*
* This glue allows operations on paths that do not exist in the guest
* rootfs but that were specified as the guest part of a binding.
*/
mode_t build_glue(Tracee *tracee, const char *guest_path, char host_path[PATH_MAX],
Finality finality)
{
bool belongs_to_gluefs;
Comparison comparison;
Binding *binding;
mode_t type;
mode_t mode;
int status;
assert(tracee->glue_type != 0);
/* Create the temporary directory where the "glue" rootfs will
* lie. */
if (tracee->glue == NULL) {
tracee->glue = create_temp_directory(NULL, tracee->tool_name);
if (tracee->glue == NULL) {
note(tracee, ERROR, INTERNAL, "can't create glue rootfs");
return 0;
}
talloc_set_name_const(tracee->glue, "$glue");
}
comparison = compare_paths(tracee->glue, host_path);
belongs_to_gluefs = (comparison == PATHS_ARE_EQUAL || comparison == PATH1_IS_PREFIX);
/* If it's not a final component then it is a directory. I definitely
* hate how the potential type of the final component is propagated
* from initialize_binding() down to here, sadly there's no elegant way
* to know its type at this stage. */
if (IS_FINAL(finality)) {
type = tracee->glue_type;
mode = (belongs_to_gluefs ? 0777 : 0);
}
else {
type = S_IFDIR;
mode = 0777;
}
if (getenv("PROOT_DONT_POLLUTE_ROOTFS") != NULL && !belongs_to_gluefs)
goto create_binding;
/* Try to create this component into the "guest" or "glue"
* rootfs (depending if there were a glue previously). */
if (S_ISDIR(type))
status = mkdir(host_path, mode);
else /* S_IFREG, S_IFCHR, S_IFBLK, S_IFIFO or S_IFSOCK. */
status = mknod(host_path, mode | type, 0);
/* Remove placeholders from the guest rootfs once PRoot is
* terminated. */
if (status >= 0 && !belongs_to_gluefs)
set_placeholder_destructor(host_path);
/* Nothing else to do if the path already exists or if it is
* the final component since it will be pointed to by the
* binding being initialized (from the example,
* "$GUEST/black/holes/and/revelations" -> "$HOST/opt"). */
if (status >= 0 || errno == EEXIST || IS_FINAL(finality))
return type;
/* mkdir/mknod are supposed to always succeed in
* tracee->glue. */
if (belongs_to_gluefs) {
note(tracee, WARNING, SYSTEM, "mkdir/mknod");
return 0;
}
create_binding:
/* Sanity checks. */
if ( strnlen(tracee->glue, PATH_MAX) >= PATH_MAX
|| strnlen(guest_path, PATH_MAX) >= PATH_MAX) {
note(tracee, WARNING, INTERNAL, "installing the binding: guest path too long");
return 0;
}
/* From the example, create the binding "/black" ->
* "$GLUE/black". */
binding = insort_binding3(tracee, tracee->glue, tracee->glue, guest_path);
if (binding == NULL)
return 0;
/* TODO: emulation of getdents(parent(guest_path)) to finalize
* the glue, "black" in getdents("/") from the example. */
return type;
}