Handle adb sync with Bionic under /bionic

Background:
We now have two sets of Bionic: the bootstrap Bionic which is at
/system/{lib|bin}/bootstrap for early processes and the default Bionic
which is from the runtime APEX for all the others. In order to give the
same path for Bionic to both categories of processes, the init prepares
two mount namespaces and bind-mount appropriate Bionic files onto the
common mount points under /bionic. For example,
/system/bin/bootstrap/linker is bind-mounted to /bionic/bin/linker for
the early processes. Likewise, /apex/com.android.runtime/bin/linker is
bind-mounted to the same path for rest of the processes.

In addition, in order not to propagate mount events in one mount
namespace to the other namespace, /bionic itself is created as a mount
namespace (via self bind-mount) and its propagation type is set to
private.

Changes required:
This however requires some adjustments to adb sync and remount
mechanism.

For remounting, /bionic path should also be re-mounted for RW, because
it is a RO mount in the beginning. This remounting is done only for the
system-as-root devices where entire / can be re-mounted as RW.

For synching, the sync thread creates a temporary mount namespace where
there is no bind-mount. This ensures that a path that the thread handles
is pointing to the correct file that is expected from the client side.

In addition, push operation to /bionic path is done without unlinking.
This is required because the mount points under /bionic are gone in the
current mount namespace but are still active in other mount namespaces.
If unlinked, the existing mounts on the path are all silently removed.
In order to prevent the unwanted situation, the moint points are not
unlinked but truncated to 0. This however is not a significant problem
because the files that serve as mount points do not carry any
useful information (i.e. the content is meaningless).

Bug: 879416
Test: adb sync
adb push <random_file> /bionic/bin/linker64
adb push <random_file> /system/bin/bootstrap/bin/linker64
system/core/fs_mgr/tests/adb-remount-test.sh
Change-Id: Id87dc9ee7ec5c43d06b54969b55e2cb394329317
This commit is contained in:
Jiyong Park 2019-01-21 15:46:21 +09:00
parent 6866041ff0
commit 7c7189c469
3 changed files with 125 additions and 0 deletions

View File

@ -25,6 +25,7 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/mount.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
@ -209,6 +210,22 @@ done:
return WriteFdExactly(s, &msg.dent, sizeof(msg.dent));
}
static bool is_mountpoint(const std::string& path, pid_t tid) {
const std::string mountinfo_path = "/proc/" + std::to_string(tid) + "/mountinfo";
std::string mountinfo;
if (!android::base::ReadFileToString(mountinfo_path, &mountinfo)) {
PLOG(ERROR) << "Failed to open " << mountinfo_path;
return false;
}
std::vector<std::string> lines = android::base::Split(mountinfo, "\n");
return std::find_if(lines.begin(), lines.end(), [&path](const auto& line) {
auto tokens = android::base::Split(line, " ");
// line format is ...
// mountid parentmountid major:minor sourcepath targetpath option ...
return tokens.size() >= 4 && tokens[4] == path;
}) != lines.end();
}
// Make sure that SendFail from adb_io.cpp isn't accidentally used in this file.
#pragma GCC poison SendFail
@ -415,6 +432,18 @@ static bool do_send(int s, const std::string& spec, std::vector<char>& buffer) {
struct stat st;
bool do_unlink = (lstat(path.c_str(), &st) == -1) || S_ISREG(st.st_mode) ||
(S_ISLNK(st.st_mode) && !S_ISLNK(mode));
// If the path is a file that is a mount point, don't unlink it, but instead
// truncate to zero. If unlinked, existing mounts on the path is all
// unmounted
if (S_ISREG(st.st_mode) && is_mountpoint(path, getpid())) {
do_unlink = false;
if (truncate(path.c_str(), 0) == -1) {
SendSyncFail(s, "truncate to zero failed");
return false;
}
}
if (do_unlink) {
adb_unlink(path.c_str());
}
@ -546,7 +575,64 @@ static bool handle_sync_command(int fd, std::vector<char>& buffer) {
return true;
}
#if defined(__ANDROID__)
class FileSyncPreparer {
public:
FileSyncPreparer() : saved_ns_fd_(-1), rooted_(getuid() == 0) {
const std::string namespace_path = "/proc/" + std::to_string(gettid()) + "/ns/mnt";
const int ns_fd = adb_open(namespace_path.c_str(), O_RDONLY | O_CLOEXEC);
if (ns_fd == -1) {
if (rooted_) PLOG(ERROR) << "Failed to save mount namespace";
return;
}
saved_ns_fd_.reset(ns_fd);
// Note: this is for the current thread only
if (unshare(CLONE_NEWNS) != 0) {
if (rooted_) PLOG(ERROR) << "Failed to clone mount namespace";
return;
}
// Set the propagation type of / to private so that unmount below is
// not propagated to other mount namespaces.
if (mount(nullptr, "/", nullptr, MS_PRIVATE | MS_REC, nullptr) == -1) {
if (rooted_) PLOG(ERROR) << "Could not change propagation type of / to MS_PRIVATE";
return;
}
// unmount /bionic which is bind-mount to itself by init. Under /bionic,
// there are other bind mounts for the bionic files. By unmounting this,
// we unmount them all thus revealing the raw file system that is the
// same as the local file system seen by the adb client.
if (umount2("/bionic", MNT_DETACH) == -1 && errno != ENOENT) {
if (rooted_) PLOG(ERROR) << "Could not unmount /bionic to reveal raw filesystem";
return;
}
}
~FileSyncPreparer() {
if (saved_ns_fd_.get() != -1) {
// In fact, this is not strictly required because this thread for file
// sync service will be destroyed after the current transfer is all
// done. However, let's restore the ns in case the same thread is
// reused by multiple transfers in the future refactoring.
if (setns(saved_ns_fd_, CLONE_NEWNS) == -1) {
PLOG(ERROR) << "Failed to restore saved mount namespace";
}
}
}
private:
unique_fd saved_ns_fd_;
bool rooted_;
};
#endif
void file_sync_service(unique_fd fd) {
#if defined(__ANDROID__)
FileSyncPreparer preparer;
#endif
std::vector<char> buffer(SYNC_DATA_MAX);
while (handle_sync_command(fd.get(), buffer)) {

View File

@ -230,6 +230,23 @@ static void reboot_for_remount(int fd, bool need_fsck) {
android::base::SetProperty(ANDROID_RB_PROPERTY, reboot_cmd.c_str());
}
static void try_unmount_bionic(int fd) {
static constexpr const char* kBionic = "/bionic";
struct statfs buf;
if (statfs(kBionic, &buf) == -1) {
WriteFdFmt(fd, "statfs of the %s mount failed: %s.\n", kBionic, strerror(errno));
return;
}
if (buf.f_flags & ST_RDONLY) {
// /bionic is on a read-only partition; can happen for
// non-system-as-root-devices. Don' try to unmount.
return;
}
// Success/Fail of the actual remount will be reported by the function.
remount_partition(fd, kBionic);
return;
}
void remount_service(unique_fd fd, const std::string& cmd) {
bool user_requested_reboot = cmd == "-R";
@ -323,6 +340,8 @@ void remount_service(unique_fd fd, const std::string& cmd) {
return;
}
try_unmount_bionic(fd.get());
if (!success) {
WriteFdExactly(fd.get(), "remount failed\n");
} else {

View File

@ -569,6 +569,18 @@ B="`adb_cat /vendor/hello`" ||
die "vendor hello"
check_eq "${A}" "${B}" /vendor before reboot
# download libc.so, append some gargage, push back, and check if the file is updated.
tempdir="`mktemp -d`"
cleanup() {
rm -rf ${tempdir}
}
adb pull /system/lib/bootstrap/libc.so ${tempdir} || die "pull libc.so from device"
garbage="`hexdump -n 16 -e '4/4 "%08X" 1 "\n"' /dev/random`"
echo ${garbage} >> ${tempdir}/libc.so
adb push ${tempdir}/libc.so /system/lib/bootstrap/libc.so || die "push libc.so to device"
adb pull /system/lib/bootstrap/libc.so ${tempdir}/libc.so.fromdevice || die "pull libc.so from device"
diff ${tempdir}/libc.so ${tempdir}/libc.so.fromdevice > /dev/null || die "libc.so differ"
echo "${GREEN}[ RUN ]${NORMAL} reboot to confirm content persistent" >&2
adb_reboot &&
@ -607,6 +619,14 @@ adb_root &&
check_eq "${A}" "${B}" vendor after reboot
echo "${GREEN}[ OK ]${NORMAL} /vendor content remains after reboot" >&2
# check if the updated libc.so is persistent after reboot
adb_root &&
adb pull /system/lib/bootstrap/libc.so ${tempdir}/libc.so.fromdevice ||
die "pull libc.so from device"
diff ${tempdir}/libc.so ${tempdir}/libc.so.fromdevice > /dev/null || die "libc.so differ"
rm -r ${tempdir}
echo "${GREEN}[ OK ]${NORMAL} /system/lib/bootstrap/libc.so content remains after reboot" >&2
echo "${GREEN}[ RUN ]${NORMAL} flash vendor, confirm its content disappears" >&2
H=`adb_sh echo '${HOSTNAME}' </dev/null 2>/dev/null`