fs_mgr_overlayfs: Support remounting submounts

The move mount motions logic are built on top of heuristics and allow
non-fatal failures.
If the overlay was setup and mounted without reboot (this could happen
if AVB is disabled), the mount state (especially the MS_SHARED and
MS_PRIVATE flags) can still get skewed somehow, due to unforeseen errors
or resource race.
It is always advised to reboot after initial overlay setup, as the
overlay mount logic is less likely to require moving submounts when
executed by `init`, this provides the greatest chance of success.

Below is an example of the expected outcome of remounting submounts:
[precondition]
* AVB is already disabled, so overlay is setup and mounted w/o reboot
* No existing overlay; is initial overlay setup

0. /proc/self/mountinfo would contain something like:
<id> <parent id> <mountpoint> <shared/private> <filesystem>
2    1           /product     shared:2         erofs
3    2           /product/app shared:3         erofs

1. adb remount /product/app
(note how the mount flag of <3> changes to private so <30> can be
MS_MOVE later)
2    1           /product     shared:2         erofs
3    2           /product/app private          erofs
30   3           /product/app shared:30        overlay

2. adb remount /product
(note how the parent of <30> changes to <40> as the result of MS_MOVE)
(note that <3> is _not_ moved)
2    1           /product     private          erofs
3    2           /product/app private          erofs
30   40          /product/app shared:30        overlay
40   2           /product     shared:40        overlay

Bug: 306124139
Test: adb-remount-test
Test: Verified with a remount submount scenario by editing the fstab.
Test: remount /system/bin then /system and verify the submount overlay
  (/system/bin) is moved under the parent mount overlay (/system).
Change-Id: I6cdbe8c52d826a6f03fd363c909ebb0005446b96
This commit is contained in:
Yi-Yo Chiang 2023-10-19 18:16:42 +08:00
parent b28db2a923
commit c78df87fad
2 changed files with 47 additions and 47 deletions

View File

@ -381,32 +381,64 @@ static bool fs_mgr_overlayfs_mount(const FstabEntry& entry) {
auto retval = true;
struct move_entry {
struct MoveEntry {
std::string mount_point;
std::string dir;
bool shared_flag;
};
std::vector<move_entry> move;
std::vector<MoveEntry> moved_mounts;
auto parent_private = false;
auto parent_made_private = false;
auto dev_private = false;
auto dev_made_private = false;
for (auto& entry : ReadMountinfoFromFile("/proc/self/mountinfo")) {
// There could be multiple mount entries with the same mountpoint.
// Group these entries together with stable_sort, and keep only the last entry of a group.
// Only move mount the last entry in an over mount group, because the other entries are
// overshadowed and only the filesystem mounted with the last entry participates in file
// pathname resolution.
auto mountinfo = ReadMountinfoFromFile("/proc/self/mountinfo");
std::stable_sort(mountinfo.begin(), mountinfo.end(), [](const auto& lhs, const auto& rhs) {
return lhs.mount_point < rhs.mount_point;
});
std::reverse(mountinfo.begin(), mountinfo.end());
auto erase_from = std::unique(
mountinfo.begin(), mountinfo.end(),
[](const auto& lhs, const auto& rhs) { return lhs.mount_point == rhs.mount_point; });
mountinfo.erase(erase_from, mountinfo.end());
std::reverse(mountinfo.begin(), mountinfo.end());
// mountinfo is reversed twice, so still is in lexical sorted order.
for (const auto& entry : mountinfo) {
if ((entry.mount_point == mount_point) && !entry.shared_flag) {
parent_private = true;
}
if ((entry.mount_point == "/dev") && !entry.shared_flag) {
dev_private = true;
}
}
// Need to make the original mountpoint MS_PRIVATE, so that the overlayfs can be MS_MOVE.
// This could happen if its parent mount is remounted later.
if (!parent_private) {
parent_made_private = fs_mgr_overlayfs_set_shared_mount(mount_point, false);
}
for (const auto& entry : mountinfo) {
// Find all immediate submounts.
if (!android::base::StartsWith(entry.mount_point, mount_point + "/")) {
continue;
}
if (std::find_if(move.begin(), move.end(), [&entry](const auto& it) {
return android::base::StartsWith(entry.mount_point, it.mount_point + "/");
}) != move.end()) {
// Exclude duplicated or more specific entries.
if (std::find_if(moved_mounts.begin(), moved_mounts.end(), [&entry](const auto& it) {
return it.mount_point == entry.mount_point ||
android::base::StartsWith(entry.mount_point, it.mount_point + "/");
}) != moved_mounts.end()) {
continue;
}
// mountinfo is in lexical order, so no need to worry about |entry| being a parent mount of
// entries of |moved_mounts|.
// use as the bound directory in /dev.
AutoSetFsCreateCon createcon;
@ -414,8 +446,7 @@ static bool fs_mgr_overlayfs_mount(const FstabEntry& entry) {
if (new_context.empty() || !createcon.Set(new_context)) {
continue;
}
move_entry new_entry = {std::move(entry.mount_point), "/dev/TemporaryDir-XXXXXX",
entry.shared_flag};
MoveEntry new_entry{entry.mount_point, "/dev/TemporaryDir-XXXXXX", entry.shared_flag};
const auto target = mkdtemp(new_entry.dir.data());
if (!createcon.Restore()) {
return false;
@ -426,9 +457,6 @@ static bool fs_mgr_overlayfs_mount(const FstabEntry& entry) {
continue;
}
if (!parent_private && !parent_made_private) {
parent_made_private = fs_mgr_overlayfs_set_shared_mount(mount_point, false);
}
if (new_entry.shared_flag) {
new_entry.shared_flag = fs_mgr_overlayfs_set_shared_mount(new_entry.mount_point, false);
}
@ -439,7 +467,7 @@ static bool fs_mgr_overlayfs_mount(const FstabEntry& entry) {
}
continue;
}
move.emplace_back(std::move(new_entry));
moved_mounts.push_back(std::move(new_entry));
}
// hijack __mount() report format to help triage
@ -463,7 +491,7 @@ static bool fs_mgr_overlayfs_mount(const FstabEntry& entry) {
}
// Move submounts back.
for (const auto& entry : move) {
for (const auto& entry : moved_mounts) {
if (!dev_private && !dev_made_private) {
dev_made_private = fs_mgr_overlayfs_set_shared_mount("/dev", false);
}
@ -658,22 +686,13 @@ Fstab fs_mgr_overlayfs_candidate_list(const Fstab& fstab) {
!fs_mgr_wants_overlayfs(&new_entry)) {
continue;
}
auto new_mount_point = fs_mgr_mount_point(entry.mount_point);
auto duplicate_or_more_specific = false;
for (auto it = candidates.begin(); it != candidates.end();) {
auto it_mount_point = fs_mgr_mount_point(it->mount_point);
if ((it_mount_point == new_mount_point) ||
(android::base::StartsWith(new_mount_point, it_mount_point + "/"))) {
duplicate_or_more_specific = true;
break;
const auto new_mount_point = fs_mgr_mount_point(new_entry.mount_point);
if (std::find_if(candidates.begin(), candidates.end(), [&](const auto& it) {
return fs_mgr_mount_point(it.mount_point) == new_mount_point;
}) != candidates.end()) {
continue;
}
if (android::base::StartsWith(it_mount_point, new_mount_point + "/")) {
it = candidates.erase(it);
} else {
++it;
}
}
if (!duplicate_or_more_specific) candidates.emplace_back(std::move(new_entry));
candidates.push_back(std::move(new_entry));
}
return candidates;
}

View File

@ -88,17 +88,6 @@ const std::string system_mount_point(const android::fs_mgr::FstabEntry& entry) {
return entry.mount_point;
}
const FstabEntry* GetWrappedEntry(const Fstab& overlayfs_candidates, const FstabEntry& entry) {
auto mount_point = system_mount_point(entry);
auto it = std::find_if(overlayfs_candidates.begin(), overlayfs_candidates.end(),
[&mount_point](const auto& entry) {
return android::base::StartsWith(mount_point,
system_mount_point(entry) + "/");
});
if (it == overlayfs_candidates.end()) return nullptr;
return &(*it);
}
class MyLogger {
public:
explicit MyLogger(bool verbose) : verbose_(verbose) {}
@ -196,9 +185,6 @@ static bool IsRemountable(Fstab& candidates, const FstabEntry& entry) {
if (auto candidate_entry = GetEntryForMountPoint(&candidates, entry.mount_point)) {
return candidate_entry->fs_type == entry.fs_type;
}
if (GetWrappedEntry(candidates, entry)) {
return false;
}
return true;
}
@ -252,11 +238,6 @@ bool GetRemountList(const Fstab& fstab, const std::vector<std::string>& argv, Fs
}
const FstabEntry* entry = &*it;
if (auto wrap = GetWrappedEntry(candidates, *entry); wrap != nullptr) {
LOG(INFO) << "partition " << arg << " covered by overlayfs for " << wrap->mount_point
<< ", switching";
entry = wrap;
}
// If it's already remounted, include it so it gets gracefully skipped
// later on.