From 6ce026bad4c2baffc39ad5856db3f4abbdfd162b Mon Sep 17 00:00:00 2001 From: Simon Fels Date: Fri, 10 Feb 2017 23:10:03 +0100 Subject: [PATCH] Setup Android rootfs mount on container manager startup --- scripts/container-manager.sh | 16 +-- src/CMakeLists.txt | 3 + src/anbox/cmds/container_manager.cpp | 119 ++++++++++++++++++--- src/anbox/cmds/container_manager.h | 9 ++ src/anbox/common/loop_device.cpp | 62 +++++++++++ src/anbox/common/loop_device.h | 46 ++++++++ src/anbox/common/loop_device_allocator.cpp | 56 ++++++++++ src/anbox/common/loop_device_allocator.h | 33 ++++++ src/anbox/common/mount_entry.cpp | 59 ++++++++++ src/anbox/common/mount_entry.h | 46 ++++++++ src/anbox/config.cpp | 4 + src/anbox/config.h | 1 + src/anbox/container/service.cpp | 2 +- src/anbox/daemon.cpp | 7 +- src/anbox/utils.cpp | 16 +++ src/anbox/utils.h | 2 + 16 files changed, 451 insertions(+), 30 deletions(-) create mode 100644 src/anbox/common/loop_device.cpp create mode 100644 src/anbox/common/loop_device.h create mode 100644 src/anbox/common/loop_device_allocator.cpp create mode 100644 src/anbox/common/loop_device_allocator.h create mode 100644 src/anbox/common/mount_entry.cpp create mode 100644 src/anbox/common/mount_entry.h diff --git a/scripts/container-manager.sh b/scripts/container-manager.sh index 568995a..12dfff6 100755 --- a/scripts/container-manager.sh +++ b/scripts/container-manager.sh @@ -34,17 +34,6 @@ load_kernel_modules() { } start() { - # Setup the read-only rootfs - mkdir -p $ROOTFS_PATH - mount -o loop,ro $ANDROID_IMG $ROOTFS_PATH - - # but certain top-level directories need to be in a writable space - for dir in cache data; do - mkdir -p $DATA_PATH/android-$dir - chown $CONTAINER_BASE_UID:$CONTAINER_BASE_UID $DATA_PATH/android-$dir - mount -o bind $DATA_PATH/android-$dir $ROOTFS_PATH/$dir - done - # Make sure our setup path for the container rootfs # is present as lxc is statically configured for # this path. @@ -62,7 +51,10 @@ start() { # Ensure FUSE support for user namespaces is enabled echo Y | sudo tee /sys/module/fuse/parameters/userns_mounts || echo "WARNING: kernel doesn't support fuse in user namespaces" - exec $SNAP/usr/sbin/aa-exec -p unconfined -- $SNAP/bin/anbox-wrapper.sh container-manager --data-path=$DATA_PATH + exec $SNAP/usr/sbin/aa-exec -p unconfined -- \ + $SNAP/bin/anbox-wrapper.sh container-manager \ + --data-path=$DATA_PATH \ + --android-image=$ANDROID_IMG } stop() { diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 3ce6621..02a38a9 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -71,6 +71,9 @@ set(SOURCES anbox/common/type_traits.h anbox/common/message_channel.cpp anbox/common/scope_ptr.h + anbox/common/loop_device.cpp + anbox/common/loop_device_allocator.cpp + anbox/common/mount_entry.cpp anbox/testing/gtest_utils.h diff --git a/src/anbox/cmds/container_manager.cpp b/src/anbox/cmds/container_manager.cpp index fa8d0d2..a688f1f 100644 --- a/src/anbox/cmds/container_manager.cpp +++ b/src/anbox/cmds/container_manager.cpp @@ -17,39 +17,130 @@ #include "anbox/cmds/container_manager.h" #include "anbox/container/service.h" +#include "anbox/common/loop_device_allocator.h" #include "anbox/logger.h" #include "anbox/runtime.h" #include "anbox/config.h" #include "core/posix/signal.h" +#include +#include +#include + +namespace fs = boost::filesystem; + +namespace { +constexpr unsigned int unprivileged_user_id{100000}; +} + anbox::cmds::ContainerManager::ContainerManager() : CommandWithFlagsAndAction{ cli::Name{"container-manager"}, cli::Usage{"container-manager"}, cli::Description{"Start the container manager service"}} { + flag(cli::make_flag(cli::Name{"android-image"}, + cli::Description{"Path to the Android rootfs image file if not stored in the data path"}, + android_img_path_)); flag(cli::make_flag(cli::Name{"data-path"}, cli::Description{"Path where the container and its data is stored"}, data_path_)); action([&](const cli::Command::Context&) { - auto trap = core::posix::trap_signals_for_process( - {core::posix::Signal::sig_term, core::posix::Signal::sig_int}); - trap->signal_raised().connect([trap](const core::posix::Signal& signal) { - INFO("Signal %i received. Good night.", static_cast(signal)); - trap->stop(); - }); + try { + auto trap = core::posix::trap_signals_for_process( + {core::posix::Signal::sig_term, core::posix::Signal::sig_int}); + trap->signal_raised().connect([trap](const core::posix::Signal& signal) { + INFO("Signal %i received. Good night.", static_cast(signal)); + trap->stop(); + }); - if (!data_path_.empty()) - SystemConfiguration::instance().set_data_path(data_path_); + if (!data_path_.empty()) + SystemConfiguration::instance().set_data_path(data_path_); - auto rt = Runtime::create(); - auto service = container::Service::create(rt); + if (!setup_mounts()) + return EXIT_FAILURE; - rt->start(); - trap->run(); - rt->stop(); + auto rt = Runtime::create(); + auto service = container::Service::create(rt); - return 0; + rt->start(); + trap->run(); + rt->stop(); + + return EXIT_SUCCESS; + } catch (std::exception &err) { + ERROR("%s", err.what()); + return EXIT_FAILURE; + } }); } + +anbox::cmds::ContainerManager::~ContainerManager() {} + +bool anbox::cmds::ContainerManager::setup_mounts() { + fs::path android_img_path = android_img_path_; + if (android_img_path.empty()) + android_img_path = SystemConfiguration::instance().data_dir() / "android.img"; + + if (!fs::exists(android_img_path)) { + ERROR("Android image does not exist at path %s", android_img_path); + return false; + } + + const auto android_rootfs_dir = SystemConfiguration::instance().rootfs_dir(); + if (utils::is_mounted(android_rootfs_dir)) { + ERROR("Androd rootfs is already mounted!?"); + return false; + } + + if (!fs::exists(android_rootfs_dir)) + fs::create_directory(android_rootfs_dir); + + auto loop_device = common::LoopDeviceAllocator::new_device(); + if (!loop_device) + return false; + + if (!loop_device->attach_file(android_img_path)) { + ERROR("Failed to attach Android rootfs image to loopback device"); + return false; + } + + auto m = common::MountEntry::create(loop_device, android_rootfs_dir, "squashfs", MS_MGC_VAL | MS_RDONLY); + if (!m) { + ERROR("Failed to mount Android rootfs"); + return false; + } + mounts_.push_back(m); + + for (const auto &dir_name : std::vector{"cache", "data"}) { + auto target_dir_path = fs::path(android_rootfs_dir) / dir_name; + auto src_dir_path = SystemConfiguration::instance().data_dir() / dir_name; + + if (!fs::exists(src_dir_path)) { + if (!fs::create_directory(src_dir_path)) { + ERROR("Failed to create Android %s directory", dir_name); + mounts_.clear(); + return false; + } + if (::chown(src_dir_path.c_str(), unprivileged_user_id, unprivileged_user_id) != 0) { + ERROR("Failed to allow access for unprivileged user on %s directory of the rootfs", dir_name); + mounts_.clear(); + return false; + } + } + + auto m = common::MountEntry::create(src_dir_path, target_dir_path, "", MS_MGC_VAL | MS_BIND); + if (!m) { + ERROR("Failed to mount Android %s directory", dir_name); + mounts_.clear(); + return false; + } + mounts_.push_back(m); + } + + // Unmounting needs to happen in reverse order + std::reverse(mounts_.begin(), mounts_.end()); + + return true; +} diff --git a/src/anbox/cmds/container_manager.h b/src/anbox/cmds/container_manager.h index 14d10fb..36bd2a7 100644 --- a/src/anbox/cmds/container_manager.h +++ b/src/anbox/cmds/container_manager.h @@ -24,14 +24,23 @@ #include "anbox/cli.h" +#include "anbox/common/loop_device.h" +#include "anbox/common/mount_entry.h" + namespace anbox { namespace cmds { class ContainerManager : public cli::CommandWithFlagsAndAction { public: ContainerManager(); + ~ContainerManager(); private: + bool setup_mounts(); + + std::string android_img_path_; std::string data_path_; + std::shared_ptr android_img_loop_dev_; + std::vector> mounts_; }; } // namespace cmds } // namespace anbox diff --git a/src/anbox/common/loop_device.cpp b/src/anbox/common/loop_device.cpp new file mode 100644 index 0000000..dc43450 --- /dev/null +++ b/src/anbox/common/loop_device.cpp @@ -0,0 +1,62 @@ +/* + * Copyright (C) 2017 Simon Fels + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 3, as published + * by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranties of + * MERCHANTABILITY, SATISFACTORY QUALITY, 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, see . + * + */ + +#include "anbox/common/loop_device.h" +#include "anbox/defer_action.h" + +#include +#include +#include + +namespace anbox { +namespace common { +std::shared_ptr LoopDevice::create(const boost::filesystem::path &path) { + const auto fd = ::open(path.c_str(), O_RDWR); + if (fd < 0) + return nullptr; + + return std::shared_ptr(new LoopDevice(Fd{fd}, path)); +} + +LoopDevice::LoopDevice(Fd fd, const boost::filesystem::path &path) : + fd_{fd}, path_{path} {} + +LoopDevice::~LoopDevice() { + if (fd_ < 0) + return; + + ::ioctl(fd_, LOOP_CLR_FD); + ::close(fd_); +} + +bool LoopDevice::attach_file(const boost::filesystem::path &file_path) { + if (fd_ < 0) + return false; + + int file_fd = ::open(file_path.c_str(), O_RDONLY); + if (file_fd < 0) + return false; + + DeferAction close_file_fd{[&]() { ::close(file_fd); }}; + + if (::ioctl(fd_, LOOP_SET_FD, file_fd) < 0) + return false; + + return true; +} +} // namespace common +} // namespace anbox diff --git a/src/anbox/common/loop_device.h b/src/anbox/common/loop_device.h new file mode 100644 index 0000000..1895ac7 --- /dev/null +++ b/src/anbox/common/loop_device.h @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2017 Simon Fels + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 3, as published + * by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranties of + * MERCHANTABILITY, SATISFACTORY QUALITY, 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, see . + * + */ + +#ifndef ANBOX_COMMON_LOOP_DEVICE_H_ +#define ANBOX_COMMON_LOOP_DEVICE_H_ + +#include "anbox/common/fd.h" + +#include + +namespace anbox { +namespace common { +class LoopDevice { + public: + static std::shared_ptr create(const boost::filesystem::path &path); + + ~LoopDevice(); + + bool attach_file(const boost::filesystem::path &file_path); + + boost::filesystem::path path() const { return path_; } + + private: + LoopDevice(Fd fd, const boost::filesystem::path &path); + + Fd fd_; + boost::filesystem::path path_; +}; +} // namespace common +} // namespace anbox + +#endif diff --git a/src/anbox/common/loop_device_allocator.cpp b/src/anbox/common/loop_device_allocator.cpp new file mode 100644 index 0000000..f7b7c7f --- /dev/null +++ b/src/anbox/common/loop_device_allocator.cpp @@ -0,0 +1,56 @@ +/* + * Copyright (C) 2017 Simon Fels + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 3, as published + * by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranties of + * MERCHANTABILITY, SATISFACTORY QUALITY, 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, see . + * + */ + +#include "anbox/common/loop_device_allocator.h" +#include "anbox/common/loop_device.h" +#include "anbox/defer_action.h" +#include "anbox/utils.h" + +#include + +#include +#include +#include + +namespace fs = boost::filesystem; + +namespace { +const constexpr char *loop_control_path{"/dev/loop-control"}; +const constexpr char *base_loop_path{"/dev/loop"}; +} + +namespace anbox { +namespace common { +std::shared_ptr LoopDeviceAllocator::new_device() { + const auto ctl_fd = ::open(loop_control_path, O_RDWR); + if (ctl_fd < 0) + return nullptr; + + DeferAction close_ctl_fd{[&]() { ::close(ctl_fd); }}; + + const auto device_nr = ::ioctl(ctl_fd, LOOP_CTL_GET_FREE); + if (device_nr < 0) + return nullptr; + + const auto path = utils::string_format("%s%d", base_loop_path, device_nr); + if (!fs::exists(path)) + return nullptr; + + return LoopDevice::create(path); +} +} // namespace common +} // namespace anbox diff --git a/src/anbox/common/loop_device_allocator.h b/src/anbox/common/loop_device_allocator.h new file mode 100644 index 0000000..0ce4bad --- /dev/null +++ b/src/anbox/common/loop_device_allocator.h @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2017 Simon Fels + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 3, as published + * by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranties of + * MERCHANTABILITY, SATISFACTORY QUALITY, 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, see . + * + */ + +#ifndef ANBOX_COMMON_LOOP_DEVICE_ALLOCATOR_H_ +#define ANBOX_COMMON_LOOP_DEVICE_ALLOCATOR_H_ + +#include + +namespace anbox { +namespace common { +class LoopDevice; +class LoopDeviceAllocator { + public: + static std::shared_ptr new_device(); +}; +} // namespace common +} // namespace anbox + +#endif diff --git a/src/anbox/common/mount_entry.cpp b/src/anbox/common/mount_entry.cpp new file mode 100644 index 0000000..fce7831 --- /dev/null +++ b/src/anbox/common/mount_entry.cpp @@ -0,0 +1,59 @@ +/* + * Copyright (C) 2017 Simon Fels + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 3, as published + * by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranties of + * MERCHANTABILITY, SATISFACTORY QUALITY, 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, see . + * + */ + +#include "anbox/common/mount_entry.h" +#include "anbox/common/loop_device.h" + +#include + +namespace anbox { +namespace common { +std::shared_ptr MountEntry::create(const boost::filesystem::path &src, const boost::filesystem::path &target, + const std::string &fs_type, unsigned long flags) { + auto entry = std::shared_ptr(new MountEntry(target)); + if (!entry) + return nullptr; + + if (::mount(src.c_str(), target.c_str(), !fs_type.empty() ? fs_type.c_str() : nullptr, flags, nullptr) != 0) + return nullptr; + + entry->active_ = true; + + return entry; +} + +std::shared_ptr MountEntry::create(const std::shared_ptr &loop, const boost::filesystem::path &target, + const std::string &fs_type, unsigned long flags) { + auto entry = create(loop->path(), target, fs_type, flags); + if (!entry) + return nullptr; + + entry->loop_ = loop; + return entry; +} + +MountEntry::MountEntry(const boost::filesystem::path &target) : + active_{false}, target_{target} {} + +MountEntry::~MountEntry() { + if (!active_) + return; + + ::umount(target_.c_str()); +} +} // namespace common +} // namespace anbox diff --git a/src/anbox/common/mount_entry.h b/src/anbox/common/mount_entry.h new file mode 100644 index 0000000..5b0be38 --- /dev/null +++ b/src/anbox/common/mount_entry.h @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2017 Simon Fels + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 3, as published + * by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranties of + * MERCHANTABILITY, SATISFACTORY QUALITY, 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, see . + * + */ + +#ifndef ANBOX_COMMON_MOUNT_ENTRY_H_ +#define ANBOX_COMMON_MOUNT_ENTRY_H_ + +#include + +namespace anbox { +namespace common { +class LoopDevice; +class MountEntry { + public: + static std::shared_ptr create(const boost::filesystem::path &src, const boost::filesystem::path &target, + const std::string &fs_type = "", unsigned long flags = 0); + + static std::shared_ptr create(const std::shared_ptr &loop, const boost::filesystem::path &target, + const std::string &fs_type = "", unsigned long flags = 0); + + ~MountEntry(); + + private: + MountEntry(const boost::filesystem::path &target); + + bool active_; + std::shared_ptr loop_; + boost::filesystem::path target_; +}; +} // namespace common +} // namespace anbox + +#endif diff --git a/src/anbox/config.cpp b/src/anbox/config.cpp index 25d0b9b..134d10d 100644 --- a/src/anbox/config.cpp +++ b/src/anbox/config.cpp @@ -40,6 +40,10 @@ void anbox::SystemConfiguration::set_data_path(const std::string &path) { data_path = path; } +fs::path anbox::SystemConfiguration::data_dir() const { + return data_path; +} + std::string anbox::SystemConfiguration::rootfs_dir() const { return (data_path / "rootfs").string(); } diff --git a/src/anbox/config.h b/src/anbox/config.h index 51eadd5..6d1b19d 100644 --- a/src/anbox/config.h +++ b/src/anbox/config.h @@ -32,6 +32,7 @@ class SystemConfiguration { void set_data_path(const std::string &path); + boost::filesystem::path data_dir() const; std::string rootfs_dir() const; std::string log_dir() const; std::string socket_dir() const; diff --git a/src/anbox/container/service.cpp b/src/anbox/container/service.cpp index 22e8e15..3b0f6fe 100644 --- a/src/anbox/container/service.cpp +++ b/src/anbox/container/service.cpp @@ -56,7 +56,7 @@ Service::Service(const std::shared_ptr &rt) std::make_shared>()) { } -Service::~Service() { DEBUG(""); } +Service::~Service() {} int Service::next_id() { return next_connection_id_++; } diff --git a/src/anbox/daemon.cpp b/src/anbox/daemon.cpp index 055237c..248a70c 100644 --- a/src/anbox/daemon.cpp +++ b/src/anbox/daemon.cpp @@ -36,9 +36,9 @@ Daemon::Daemon() : cmd{cli::Name{"anbox"}, cli::Usage{"anbox"}, cli::Description{"The Android in a Box runtime"}} { cmd.command(std::make_shared()) - .command(std::make_shared()) - .command(std::make_shared()) - .command(std::make_shared()); + .command(std::make_shared()) + .command(std::make_shared()) + .command(std::make_shared()); } int Daemon::Run(const std::vector &arguments) try { @@ -47,6 +47,7 @@ int Daemon::Run(const std::vector &arguments) try { return cmd.run({std::cin, std::cout, argv}); } catch (std::exception &err) { ERROR("%s", err.what()); + return EXIT_FAILURE; } } // namespace anbox diff --git a/src/anbox/utils.cpp b/src/anbox/utils.cpp index ae31a71..83cefc2 100644 --- a/src/anbox/utils.cpp +++ b/src/anbox/utils.cpp @@ -24,6 +24,7 @@ #include #include +#include #include "anbox/utils.h" @@ -163,5 +164,20 @@ std::string process_get_exe_path(const pid_t &pid) { return boost::filesystem::read_symlink(exe_path).string(); } +bool is_mounted(const std::string &path) { + FILE *mtab = nullptr; + struct mntent *part = nullptr; + bool is_mounted = false; + if ((mtab = setmntent("/etc/mtab", "r")) != nullptr) { + while ((part = getmntent(mtab)) != nullptr) { + if ((part->mnt_fsname != nullptr) && (strcmp(part->mnt_fsname, path.c_str())) == 0) + is_mounted = true; + } + endmntent(mtab); + } + return is_mounted; + +} + } // namespace utils } // namespace anbox diff --git a/src/anbox/utils.h b/src/anbox/utils.h index ab2f2a5..d06d9da 100644 --- a/src/anbox/utils.h +++ b/src/anbox/utils.h @@ -50,6 +50,8 @@ std::string prefix_dir_from_env(const std::string &path, std::string process_get_exe_path(const pid_t &pid); +bool is_mounted(const std::string &path); + template static std::string string_format(const std::string &fmt_str, Types &&... args); } // namespace utils