2 * Copyright (C) 2016 Simon Fels <morphis@gravedo.de>
4 * This program is free software: you can redistribute it and/or modify it
5 * under the terms of the GNU General Public License version 3, as published
6 * by the Free Software Foundation.
8 * This program is distributed in the hope that it will be useful, but
9 * WITHOUT ANY WARRANTY; without even the implied warranties of
10 * MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
11 * PURPOSE. See the GNU General Public License for more details.
13 * You should have received a copy of the GNU General Public License along
14 * with this program. If not, see <http://www.gnu.org/licenses/>.
18 #include "anbox/cmds/container_manager.h"
19 #include "anbox/container/service.h"
20 #include "anbox/common/loop_device_allocator.h"
21 #include "anbox/logger.h"
22 #include "anbox/runtime.h"
23 #include "anbox/system_configuration.h"
25 #include "core/posix/signal.h"
26 #include "core/posix/exec.h"
28 #include <sys/mount.h>
29 #include <linux/loop.h>
32 namespace fs = boost::filesystem;
35 constexpr unsigned int unprivileged_user_id{100000};
38 anbox::cmds::ContainerManager::ContainerManager()
39 : CommandWithFlagsAndAction{
40 cli::Name{"container-manager"}, cli::Usage{"container-manager"},
41 cli::Description{"Start the container manager service"}, true} {
43 flag(cli::make_flag(cli::Name{"android-image"},
44 cli::Description{"Path to the Android rootfs image file if not stored in the data path"},
46 flag(cli::make_flag(cli::Name{"data-path"},
47 cli::Description{"Path where the container and its data is stored"},
49 flag(cli::make_flag(cli::Name{"privileged"},
50 cli::Description{"Run Android container in privileged mode"},
52 flag(cli::make_flag(cli::Name{"daemon"},
53 cli::Description{"Mark service as being started as systemd daemon"},
55 flag(cli::make_flag(cli::Name{"use-rootfs-overlay"},
56 cli::Description{"Use an overlay for the Android rootfs"},
57 enable_rootfs_overlay_));
58 flag(cli::make_flag(cli::Name{"force-squashfuse"},
59 cli::Description{"Force using squashfuse for mounting the Android rootfs"},
61 flag(cli::make_flag(cli::Name{"container-network-address"},
62 cli::Description{"Assign the specified network address to the Android container"},
63 container_network_address_));
64 flag(cli::make_flag(cli::Name{"container-network-gateway"},
65 cli::Description{"Assign the specified network gateway to the Android container"},
66 container_network_gateway_));
67 flag(cli::make_flag(cli::Name{"container-network-dns-servers"},
68 cli::Description{"Assign the specified DNS servers to the Android container"},
69 container_network_dns_servers_));
71 action([&](const cli::Command::Context&) {
74 WARNING("You are running the container manager manually which is most likely not");
75 WARNING("what you want. The container manager is normally started by systemd or");
76 WARNING("another init system. If you still want to run the container-manager");
77 WARNING("you can get rid of this warning by starting with the --daemon option.");
82 ERROR("You are not running the container-manager as root. Generally you don't");
83 ERROR("want to run the container-manager manually unless you're a developer");
84 ERROR("as it is started by the init system of your operating system.");
88 auto trap = core::posix::trap_signals_for_process(
89 {core::posix::Signal::sig_term, core::posix::Signal::sig_int});
90 trap->signal_raised().connect([trap](const core::posix::Signal& signal) {
91 INFO("Signal %i received. Good night.", static_cast<int>(signal));
95 if (!data_path_.empty())
96 SystemConfiguration::instance().set_data_path(data_path_);
98 if (!fs::exists(data_path_))
99 fs::create_directories(data_path_);
104 auto rt = Runtime::create();
105 container::Service::Configuration config;
106 config.privileged = privileged_;
107 config.rootfs_overlay = enable_rootfs_overlay_;
108 config.container_network_address = container_network_address_;
109 config.container_network_gateway = container_network_gateway_;
111 if (container_network_dns_servers_.length() > 0)
112 config.container_network_dns_servers = utils::string_split(container_network_dns_servers_, ',');
114 auto service = container::Service::create(rt, config);
121 } catch (std::exception &err) {
122 ERROR("%s", err.what());
128 anbox::cmds::ContainerManager::~ContainerManager() {}
130 bool anbox::cmds::ContainerManager::setup_mounts() {
131 fs::path android_img_path = android_img_path_;
132 if (android_img_path.empty())
133 android_img_path = SystemConfiguration::instance().data_dir() / "android.img";
135 if (!fs::exists(android_img_path)) {
136 ERROR("Android image does not exist at path %s", android_img_path);
140 const auto android_rootfs_dir = SystemConfiguration::instance().rootfs_dir();
141 if (utils::is_mounted(android_rootfs_dir)) {
142 ERROR("Androd rootfs is already mounted!?");
146 if (!fs::exists(android_rootfs_dir))
147 fs::create_directory(android_rootfs_dir);
149 // We prefer using the kernel for mounting the squashfs image but
150 // for some cases (unprivileged containers) where no loop support
151 // is available we do the mount instead via squashfuse which will
152 // work entirely in userspace.
153 if (!fs::exists("/dev/loop-control") && !enable_squashfuse_) {
154 WARNING("/dev/loop-control not found. Falling back to squashfuse.");
155 enable_squashfuse_ = true;
157 if (!enable_squashfuse_) {
158 std::shared_ptr<common::LoopDevice> loop_device;
161 loop_device = common::LoopDeviceAllocator::new_device();
162 } catch (const std::exception& e) {
163 ERROR("Could not create loopback device: %s", e.what());
166 ERROR("Could not create loopback device");
170 if (!loop_device->attach_file(android_img_path)) {
171 ERROR("Failed to attach Android rootfs image to loopback device");
175 auto m = common::MountEntry::create(loop_device, android_rootfs_dir, "squashfs", MS_MGC_VAL | MS_RDONLY | MS_PRIVATE);
177 ERROR("Failed to mount Android rootfs");
180 mounts_.push_back(m);
181 } else if (fs::exists("/dev/fuse") && !utils::find_program_on_path("squashfuse").empty()) {
182 std::vector<std::string> args = {
183 "-t", "fuse.squashfuse",
184 // Allow other users than root to access the rootfs
186 android_img_path.string(),
190 // Easiest is here to go with the standard mount program as that
191 // will handle everything for us which is relevant to get the
192 // squashfs via squashfuse properly mount without having to
193 // reimplement all the details. Once the mount call comes back
194 // without an error we can expect the image to be mounted.
195 auto child = core::posix::exec("/bin/mount", args, {}, core::posix::StandardStream::empty, []() {});
196 const auto result = child.wait_for(core::posix::wait::Flags::untraced);
197 if (result.status != core::posix::wait::Result::Status::exited ||
198 result.detail.if_exited.status != core::posix::exit::Status::success) {
199 ERROR("Failed to mount squashfs Android image");
203 auto m = common::MountEntry::create(android_rootfs_dir);
205 ERROR("Failed to create mount entry for Android rootfs");
208 mounts_.push_back(m);
210 ERROR("No loop device or FUSE support found. Can't setup Android rootfs!");
214 auto final_android_rootfs_dir = android_rootfs_dir;
215 if (enable_rootfs_overlay_) {
216 if (!setup_rootfs_overlay())
219 final_android_rootfs_dir = SystemConfiguration::instance().combined_rootfs_dir();
223 for (const auto &dir_name : std::vector<std::string>{"cache", "data"}) {
224 auto target_dir_path = fs::path(final_android_rootfs_dir) / dir_name;
225 auto src_dir_path = SystemConfiguration::instance().data_dir() / dir_name;
227 if (!fs::exists(src_dir_path)) {
228 if (!fs::create_directory(src_dir_path)) {
229 ERROR("Failed to create Android %s directory", dir_name);
233 if (::chown(src_dir_path.c_str(), unprivileged_user_id, unprivileged_user_id) != 0) {
234 ERROR("Failed to allow access for unprivileged user on %s directory of the rootfs", dir_name);
240 auto m = common::MountEntry::create(src_dir_path, target_dir_path, "", MS_MGC_VAL | MS_BIND | MS_PRIVATE);
242 ERROR("Failed to mount Android %s directory", dir_name);
246 mounts_.push_back(m);
249 // Unmounting needs to happen in reverse order
250 std::reverse(mounts_.begin(), mounts_.end());
255 bool anbox::cmds::ContainerManager::setup_rootfs_overlay() {
256 const auto combined_rootfs_path = SystemConfiguration::instance().combined_rootfs_dir();
257 if (!fs::exists(combined_rootfs_path))
258 fs::create_directories(combined_rootfs_path);
260 const auto overlay_path = SystemConfiguration::instance().overlay_dir();
261 if (!fs::exists(overlay_path))
262 fs::create_directories(overlay_path);
264 const auto rootfs_path = SystemConfiguration::instance().rootfs_dir();
265 const auto overlay_config = utils::string_format("lowerdir=%s:%s", overlay_path, rootfs_path);
266 auto m = common::MountEntry::create("overlay", combined_rootfs_path, "overlay", MS_RDONLY, overlay_config.c_str());
268 ERROR("Failed to setup rootfs overlay");
272 mounts_.push_back(m);
274 DEBUG("Successfully setup rootfs overlay");