blob: 0e59b576bfddce391fb09a157dac781c5cc21f91 [file] [log] [blame]
Tom Cherry4590a2a2018-08-07 10:22:01 -07001/*
2 * Copyright (C) 2018 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17#include "switch_root.h"
18
Tom Cherrye087a6a2018-11-09 18:47:26 +000019#include <dirent.h>
Tom Cherry4590a2a2018-08-07 10:22:01 -070020#include <fcntl.h>
21#include <mntent.h>
22#include <sys/mount.h>
23#include <sys/stat.h>
24#include <unistd.h>
25
26#include <android-base/logging.h>
27#include <android-base/strings.h>
28
29using android::base::StartsWith;
30
31using namespace std::literals;
32
33namespace android {
34namespace init {
35
36namespace {
37
Tom Cherrye087a6a2018-11-09 18:47:26 +000038void FreeRamdisk(DIR* dir, dev_t dev) {
39 int dfd = dirfd(dir);
40
41 dirent* de;
42 while ((de = readdir(dir)) != nullptr) {
43 if (de->d_name == "."s || de->d_name == ".."s) {
44 continue;
45 }
46
47 bool is_dir = false;
48
49 if (de->d_type == DT_DIR || de->d_type == DT_UNKNOWN) {
50 struct stat info;
51 if (fstatat(dfd, de->d_name, &info, AT_SYMLINK_NOFOLLOW) != 0) {
52 continue;
53 }
54
55 if (info.st_dev != dev) {
56 continue;
57 }
58
59 if (S_ISDIR(info.st_mode)) {
60 is_dir = true;
61 auto fd = openat(dfd, de->d_name, O_RDONLY | O_DIRECTORY);
62 if (fd >= 0) {
63 auto subdir =
64 std::unique_ptr<DIR, decltype(&closedir)>{fdopendir(fd), closedir};
65 if (subdir) {
66 FreeRamdisk(subdir.get(), dev);
67 } else {
68 close(fd);
69 }
70 }
71 }
72 }
73 unlinkat(dfd, de->d_name, is_dir ? AT_REMOVEDIR : 0);
74 }
75}
76
Tom Cherry4590a2a2018-08-07 10:22:01 -070077std::vector<std::string> GetMounts(const std::string& new_root) {
78 auto fp = std::unique_ptr<std::FILE, decltype(&endmntent)>{setmntent("/proc/mounts", "re"),
79 endmntent};
80 if (fp == nullptr) {
81 PLOG(FATAL) << "Failed to open /proc/mounts";
82 }
83
84 std::vector<std::string> result;
85 mntent* mentry;
86 while ((mentry = getmntent(fp.get())) != nullptr) {
87 // We won't try to move rootfs.
88 if (mentry->mnt_dir == "/"s) {
89 continue;
90 }
91
92 // The new root mount is handled separately.
93 if (mentry->mnt_dir == new_root) {
94 continue;
95 }
96
97 // Move operates on subtrees, so do not try to move children of other mounts.
98 if (std::find_if(result.begin(), result.end(), [&mentry](const auto& older_mount) {
99 return StartsWith(mentry->mnt_dir, older_mount);
100 }) != result.end()) {
101 continue;
102 }
103
104 result.emplace_back(mentry->mnt_dir);
105 }
106
107 return result;
108}
109
110} // namespace
111
Tom Cherrye087a6a2018-11-09 18:47:26 +0000112void SwitchRoot(const std::string& new_root) {
Tom Cherry4590a2a2018-08-07 10:22:01 -0700113 auto mounts = GetMounts(new_root);
114
115 for (const auto& mount_path : mounts) {
116 auto new_mount_path = new_root + mount_path;
117 if (mount(mount_path.c_str(), new_mount_path.c_str(), nullptr, MS_MOVE, nullptr) != 0) {
118 PLOG(FATAL) << "Unable to move mount at '" << mount_path << "'";
119 }
120 }
121
Tom Cherrye087a6a2018-11-09 18:47:26 +0000122 auto old_root_dir = std::unique_ptr<DIR, decltype(&closedir)>{opendir("/"), closedir};
123 if (!old_root_dir) {
124 PLOG(ERROR) << "Could not opendir(\"/\"), not freeing ramdisk";
125 }
126
127 struct stat old_root_info;
128 if (stat("/", &old_root_info) != 0) {
129 PLOG(ERROR) << "Could not stat(\"/\"), not freeing ramdisk";
130 old_root_dir.reset();
131 }
132
Tom Cherry4590a2a2018-08-07 10:22:01 -0700133 if (chdir(new_root.c_str()) != 0) {
134 PLOG(FATAL) << "Could not chdir to new_root, '" << new_root << "'";
135 }
136
Tom Cherrye087a6a2018-11-09 18:47:26 +0000137 if (mount(new_root.c_str(), "/", nullptr, MS_MOVE, nullptr) != 0) {
138 PLOG(FATAL) << "Unable to move root mount to new_root, '" << new_root << "'";
Tom Cherry4590a2a2018-08-07 10:22:01 -0700139 }
140
141 if (chroot(".") != 0) {
142 PLOG(FATAL) << "Unable to chroot to new root";
143 }
Tom Cherrye087a6a2018-11-09 18:47:26 +0000144
145 if (old_root_dir) {
146 FreeRamdisk(old_root_dir.get(), old_root_info.st_dev);
147 }
Tom Cherry4590a2a2018-08-07 10:22:01 -0700148}
149
150} // namespace init
151} // namespace android