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:
parent
6866041ff0
commit
7c7189c469
|
@ -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)) {
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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`
|
||||
|
|
Loading…
Reference in New Issue