From 88aab61e094054baf1e2ae39840930c9ba43f150 Mon Sep 17 00:00:00 2001 From: Norman Feske Date: Wed, 18 Apr 2012 16:56:15 +0200 Subject: [PATCH] Mechanism for using chroot on Linux The new 'chroot' tool at 'os/src/app/chroot' allows for executing subsystems within chroot jails on Linux. For using the tool, please refer to the test case 'os/run/chroot.run'. Fixes #37 --- os/run/chroot.run | 122 +++++++++++++++++++++ os/src/app/chroot/main.cc | 211 ++++++++++++++++++++++++++++++++++++ os/src/app/chroot/target.mk | 19 ++++ 3 files changed, 352 insertions(+) create mode 100644 os/run/chroot.run create mode 100644 os/src/app/chroot/main.cc create mode 100644 os/src/app/chroot/target.mk diff --git a/os/run/chroot.run b/os/run/chroot.run new file mode 100644 index 0000000000..df01363530 --- /dev/null +++ b/os/run/chroot.run @@ -0,0 +1,122 @@ +# +# \brief Test for using chroot on Linux +# \author Norman Feske +# \date 2012-04-18 +# +# +if {![have_spec linux]} { puts "Run script requires Linux"; exit 0 } + +# +# Build +# + +build { core init app/chroot drivers/timer/linux test/timer } + +if {[catch { exec which setcap }]} { + puts stderr "Error: setcap not available, please install the libcap2-bin package" + return 0 +} + + +create_boot_directory + +# +# Generate config +# + +set config { + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +} + +proc chroot_path { } { return "/tmp/chroot-test" } +proc chroot_cwd_path { } { return "[chroot_path][pwd]/[run_dir]" } +proc chroot_genode_tmp_path { } { return "[chroot_path]/tmp/genode-[exec id -u]" } + +proc cleanup_chroot { } { + catch { exec sudo umount -l [chroot_cwd_path] } + catch { exec sudo umount -l [chroot_genode_tmp_path] } + exec rm -rf [chroot_path] +} + +# replace 'chroot_path' marker in config with actual path +regsub "chroot_path" $config [chroot_path] config + +install_config $config + +# +# Copy boot modules into run directory +# +# We cannot use the predefined 'build_boot_image' function here because +# this would create mere symlinks. However, we want to hardlink the +# run directory into the chroot environment. If the directory entries +# were symlinks, those would point to nowhere within the chroot. +# +foreach binary { core init chroot timer test-timer } { + exec cp -H bin/$binary [run_dir] } + +# +# Grant chroot permission to 'chroot' tool +# +# CAP_SYS_ADMIN is needed for bind mounting genode runtime directories +# CAP_SYS_CHROOT is needed to perform the chroot syscall +# +exec sudo setcap cap_sys_admin,cap_sys_chroot=ep [run_dir]/chroot + +# +# Setup chroot environment +# + +# start with fresh directory +cleanup_chroot +exec mkdir -p [chroot_path] + +# +# Execute test case +# +run_genode_until {.*--- timer test finished ---.*} 60 + +# +# Remove artifacts created while running the test +# +cleanup_chroot + +# vi: set ft=tcl : diff --git a/os/src/app/chroot/main.cc b/os/src/app/chroot/main.cc new file mode 100644 index 0000000000..f28a6add49 --- /dev/null +++ b/os/src/app/chroot/main.cc @@ -0,0 +1,211 @@ +/* + * \brief Utility for using the Linux chroot mechanism with Genode + * \author Norman Feske + * \date 2012-04-18 + */ + +/* Genode includes */ +#include +#include + +/* Linux includes */ +#include +#include +#include +#include +#include + + +enum { MAX_PATH_LEN = 256 }; + +static bool verbose = false; + + +/** + * Return true if specified path is an existing directory + */ +static bool is_directory(char const *path) +{ + struct stat s; + if (stat(path, &s) != 0) + return false; + + if (!(s.st_mode & S_IFDIR)) + return false; + + return true; +} + + +static bool is_path_delimiter(char c) { return c == '/'; } + + +static bool has_trailing_path_delimiter(char const *path) +{ + char last_char = 0; + for (; *path; path++) + last_char = *path; + + return is_path_delimiter(last_char); +} + + +/** + * Return number of path elements of given path + */ +static size_t num_path_elements(char const *path) +{ + size_t count = 0; + + /* + * If path starts with non-slash, the first characters belongs to a path + * element. + */ + if (*path && !is_path_delimiter(*path)) + count = 1; + + /* count slashes */ + for (; *path; path++) + if (is_path_delimiter(*path)) + count++; + + return count; +} + + +static bool leading_path_elements(char const *path, unsigned num, + char *dst, size_t dst_len) +{ + /* counter of path delimiters */ + unsigned count = 0; + unsigned i = 0; + + if (is_path_delimiter(path[0])) + num++; + + for (; path[i] && (count < num) && (i < dst_len); i++) + { + if (is_path_delimiter(path[i])) + count++; + + if (count == num) + break; + + dst[i] = path[i]; + } + + if (i + 1 < dst_len) { + dst[i] = 0; + return true; + } + + /* string is cut, append null termination anyway */ + dst[dst_len - 1] = 0; + return false; +} + + +static void mirror_path_to_chroot(char const *chroot_path, char const *path) +{ + char target_path[MAX_PATH_LEN]; + Genode::snprintf(target_path, sizeof(target_path), "%s%s", + chroot_path, path); + + /* + * Create directory hierarchy pointing to the target path except for the + * last element. The last element will be bind-mounted to refer to the + * original 'path'. + */ + for (unsigned i = 1; i <= num_path_elements(target_path); i++) + { + char buf[MAX_PATH_LEN]; + leading_path_elements(target_path, i, buf, sizeof(buf)); + + /* skip existing directories */ + if (is_directory(buf)) + continue; + + /* create new directory */ + mkdir(buf, 0777); + } + + umount(target_path); + + if (verbose) { + PINF("bind mount from: %s", path); + PINF(" to: %s", target_path); + } + + if (mount(path, target_path, 0, MS_BIND, 0)) + PERR("bind mount failed (errno=%d: %s)", errno, strerror(errno)); +} + + +int main(int, char **argv) +{ + using namespace Genode; + + static char chroot_path[MAX_PATH_LEN]; + static char cwd_path[MAX_PATH_LEN]; + static char genode_tmp_path[MAX_PATH_LEN]; + + /* + * Read configuration + */ + try { + Xml_node config = Genode::config()->xml_node(); + + config.sub_node("root").attribute("path") + .value(chroot_path, sizeof(chroot_path)); + + verbose = config.attribute("verbose").has_value("yes"); + + } catch (...) { + PERR("invalid config"); + return 1; + } + + getcwd(cwd_path, sizeof(cwd_path)); + + uid_t const uid = getuid(); + snprintf(genode_tmp_path, sizeof(genode_tmp_path), "/tmp/genode-%d", uid); + + /* + * Print diagnostic information + */ + if (verbose) { + PINF("work directory: %s", cwd_path); + PINF("chroot path: %s", chroot_path); + PINF("genode tmp path: %s", genode_tmp_path); + } + + /* + * Validate chroot path + */ + if (!is_directory(chroot_path)) { + PERR("chroot path does not point to valid directory"); + return 2; + } + + if (has_trailing_path_delimiter(chroot_path)) { + PERR("chroot path has trailing slash"); + return 3; + } + + /* + * Hardlink directories needed for running Genode within the chroot + * environment. + */ + mirror_path_to_chroot(chroot_path, cwd_path); + mirror_path_to_chroot(chroot_path, genode_tmp_path); + + printf("changing root to %s ...\n", chroot_path); + + if (chroot(chroot_path)) { + PERR("chroot failed (errno=%d: %s)", errno, strerror(errno)); + return 4; + } + + execve("init", argv, environ); + return 0; +} diff --git a/os/src/app/chroot/target.mk b/os/src/app/chroot/target.mk new file mode 100644 index 0000000000..34f42cc7f7 --- /dev/null +++ b/os/src/app/chroot/target.mk @@ -0,0 +1,19 @@ +TARGET = chroot +REQUIRES = linux +SRC_CC = main.cc +LIBS = cxx env server lx_hybrid + +# +# XXX find a way to remove superfluous warning: +# +# base/include/util/token.h: In constructor ‘Genode::Config::Config()’: +# base/include/util/token.h:69:67: warning: ‘ret’ may be used uninitialized in +# this function [-Wuninitialized] +# base/include/base/capability.h:196:62: note: ‘ret’ was declared here +# base/include/util/token.h:100:68: warning: ‘prephitmp.1897’ may be used +# uninitialized in this function +# [-Wuninitialized] +# os/include/os/config.h:42:4: note: ‘prephitmp.1897’ was declared here +# +CC_WARN = -Wall -Wno-uninitialized +