#include #include #include #include #include #include #include "init.hpp" using namespace std; struct devinfo { int major; int minor; char devname[32]; char partname[32]; char dmname[32]; }; static vector dev_list; static void parse_device(devinfo *dev, const char *uevent) { dev->partname[0] = '\0'; parse_prop_file(uevent, [=](string_view key, string_view value) -> bool { if (key == "MAJOR") dev->major = parse_int(value.data()); else if (key == "MINOR") dev->minor = parse_int(value.data()); else if (key == "DEVNAME") strcpy(dev->devname, value.data()); else if (key == "PARTNAME") strcpy(dev->partname, value.data()); return true; }); } static void collect_devices() { char path[128]; devinfo dev{}; if (auto dir = xopen_dir("/sys/dev/block"); dir) { for (dirent *entry; (entry = readdir(dir.get()));) { if (entry->d_name == "."sv || entry->d_name == ".."sv) continue; sprintf(path, "/sys/dev/block/%s/uevent", entry->d_name); parse_device(&dev, path); sprintf(path, "/sys/dev/block/%s/dm/name", entry->d_name); if (access(path, F_OK) == 0) { auto name = rtrim(full_read(path)); strcpy(dev.dmname, name.data()); } dev_list.push_back(dev); } } } static struct { char partname[32]; char block_dev[64]; } blk_info; static int64_t setup_block(bool write_block) { if (dev_list.empty()) collect_devices(); xmkdir("/dev", 0755); xmkdir("/dev/block", 0755); for (int tries = 0; tries < 3; ++tries) { for (auto &dev : dev_list) { if (strcasecmp(dev.partname, blk_info.partname) == 0) LOGD("Setup %s: [%s] (%d, %d)\n", dev.partname, dev.devname, dev.major, dev.minor); else if (strcasecmp(dev.dmname, blk_info.partname) == 0) LOGD("Setup %s: [%s] (%d, %d)\n", dev.dmname, dev.devname, dev.major, dev.minor); else continue; if (write_block) { sprintf(blk_info.block_dev, "/dev/block/%s", dev.devname); } dev_t rdev = makedev(dev.major, dev.minor); xmknod(blk_info.block_dev, S_IFBLK | 0600, rdev); return rdev; } // Wait 10ms and try again usleep(10000); dev_list.clear(); collect_devices(); } // The requested partname does not exist return -1; } static bool is_lnk(const char *name) { struct stat st{}; if (lstat(name, &st)) return false; return S_ISLNK(st.st_mode); } #define read_info(val) \ if (access(#val, F_OK) == 0) {\ entry.val = rtrim(full_read(#val)); \ } void BaseInit::read_dt_fstab(vector &fstab) { if (access(config->dt_dir, F_OK) != 0) return; char cwd[128]; getcwd(cwd, sizeof(cwd)); chdir(config->dt_dir); run_finally cd([&]{ chdir(cwd); }); if (access("fstab", F_OK) != 0) return; chdir("fstab"); // Make sure dt fstab is enabled if (access("status", F_OK) == 0) { auto status = rtrim(full_read("status")); if (status != "okay" && status != "ok") return; } auto dir = xopen_dir("."); for (dirent *dp; (dp = xreaddir(dir.get()));) { if (dp->d_type != DT_DIR) continue; chdir(dp->d_name); run_finally f([]{ chdir(".."); }); if (access("status", F_OK) == 0) { auto status = rtrim(full_read("status")); if (status != "okay" && status != "ok") continue; } fstab_entry entry; read_info(dev); read_info(mnt_point) else { entry.mnt_point = "/"; entry.mnt_point += dp->d_name; } read_info(type); read_info(mnt_flags); read_info(fsmgr_flags); fstab.emplace_back(std::move(entry)); } } void MagiskInit::mount_with_dt() { vector fstab; read_dt_fstab(fstab); for (const auto &entry : fstab) { if (is_lnk(entry.mnt_point.data())) continue; // When we force AVD to disable SystemAsRoot, it will always add system // to dt fstab, which we actually have already mounted as root if (avd_hack && entry.mnt_point == "/system") continue; // Derive partname from dev sprintf(blk_info.partname, "%s%s", basename(entry.dev.data()), config->slot); setup_block(true); xmkdir(entry.mnt_point.data(), 0755); xmount(blk_info.block_dev, entry.mnt_point.data(), entry.type.data(), MS_RDONLY, nullptr); // When avd_hack is true, do not add any early mount partitions to mount_list // as we will actually forcefully disable original init's early mount if (!avd_hack) mount_list.push_back(entry.mnt_point); } } static void switch_root(const string &path) { LOGD("Switch root to %s\n", path.data()); int root = xopen("/", O_RDONLY); vector mounts; parse_mnt("/proc/mounts", [&](mntent *me) { // Skip root and self if (me->mnt_dir == "/"sv || me->mnt_dir == path) return true; // Do not include subtrees for (const auto &m : mounts) { if (strncmp(me->mnt_dir, m.data(), m.length()) == 0 && me->mnt_dir[m.length()] == '/') return true; } mounts.emplace_back(me->mnt_dir); return true; }); for (auto &dir : mounts) { auto new_path = path + dir; xmkdir(new_path.data(), 0755); xmount(dir.data(), new_path.data(), nullptr, MS_MOVE, nullptr); } chdir(path.data()); xmount(path.data(), "/", nullptr, MS_MOVE, nullptr); chroot("."); LOGD("Cleaning rootfs\n"); frm_rf(root); } bool is_dsu() { strcpy(blk_info.partname, "metadata"); xmkdir("/metadata", 0755); if (setup_block(true) < 0 || xmount(blk_info.block_dev, "/metadata", "ext4", MS_RDONLY, nullptr)) { PLOGE("Failed to mount /metadata"); return false; } else { run_finally f([]{ xumount2("/metadata", MNT_DETACH); }); constexpr auto dsu_status = "/metadata/gsi/dsu/install_status"; if (xaccess(dsu_status, F_OK) == 0) { char status[PATH_MAX] = {0}; auto fp = xopen_file(dsu_status, "r"); fgets(status, sizeof(status), fp.get()); if (status == "ok"sv || status == "0"sv) return true; } } return false; } void MagiskInit::mount_rules_dir(const char *dev_base, const char *mnt_base) { char path[128]; xrealpath(dev_base, blk_info.block_dev); xrealpath(mnt_base, path); char *b = blk_info.block_dev + strlen(blk_info.block_dev); char *p = path + strlen(path); auto do_mount = [&](const char *type) -> bool { xmkdir(path, 0755); bool success = xmount(blk_info.block_dev, path, type, 0, nullptr) == 0; if (success) mount_list.emplace_back(path); return success; }; // First try userdata strcpy(blk_info.partname, "userdata"); strcpy(b, "/data"); strcpy(p, "/data"); if (setup_block(false) < 0) { // Try NVIDIA naming scheme strcpy(blk_info.partname, "UDA"); if (setup_block(false) < 0) goto cache; } // WARNING: DO NOT ATTEMPT TO MOUNT F2FS AS IT MAY CRASH THE KERNEL // Failure means either f2fs, FDE, or metadata encryption if (!do_mount("ext4")) goto cache; strcpy(p, "/data/unencrypted"); if (xaccess(path, F_OK) == 0) { // FBE, need to use an unencrypted path custom_rules_dir = path + "/magisk"s; } else { // Skip if /data/adb does not exist strcpy(p, SECURE_DIR); if (xaccess(path, F_OK) != 0) return; strcpy(p, MODULEROOT); if (xaccess(path, F_OK) != 0) { goto cache; } // Unencrypted, directly use module paths custom_rules_dir = string(path); } goto success; cache: // Fallback to cache strcpy(blk_info.partname, "cache"); strcpy(b, "/cache"); strcpy(p, "/cache"); if (setup_block(false) < 0) { // Try NVIDIA naming scheme strcpy(blk_info.partname, "CAC"); if (setup_block(false) < 0) goto metadata; } if (!do_mount("ext4")) goto metadata; custom_rules_dir = path + "/magisk"s; goto success; metadata: // Fallback to metadata strcpy(blk_info.partname, "metadata"); strcpy(b, "/metadata"); strcpy(p, "/metadata"); if (setup_block(false) < 0 || !do_mount("ext4")) goto persist; custom_rules_dir = path + "/magisk"s; goto success; persist: // Fallback to persist strcpy(blk_info.partname, "persist"); strcpy(b, "/persist"); strcpy(p, "/persist"); if (setup_block(false) < 0 || !do_mount("ext4")) return; custom_rules_dir = path + "/magisk"s; success: // Create symlinks so we don't need to go through this logic again strcpy(p, "/sepolicy.rules"); xsymlink(custom_rules_dir.data(), path); } void RootFSInit::early_mount() { self = mmap_data("/init"); LOGD("Restoring /init\n"); rename(backup_init(), "/init"); mount_with_dt(); } void SARBase::backup_files() { if (access("/overlay.d", F_OK) == 0) backup_folder("/overlay.d", overlays); self = mmap_data("/proc/self/exe"); if (access("/.backup/.magisk", R_OK) == 0) magisk_config = mmap_data("/.backup/.magisk"); } void SARBase::mount_system_root() { LOGD("Early mount system_root\n"); strcpy(blk_info.block_dev, "/dev/root"); do { // Try legacy SAR dm-verity strcpy(blk_info.partname, "vroot"); auto dev = setup_block(false); if (dev >= 0) goto mount_root; // Try NVIDIA naming scheme strcpy(blk_info.partname, "APP"); dev = setup_block(false); if (dev >= 0) goto mount_root; sprintf(blk_info.partname, "system%s", config->slot); dev = setup_block(false); if (dev >= 0) goto mount_root; // Poll forever if rootwait was given in cmdline } while (config->rootwait); // We don't really know what to do at this point... LOGE("Cannot find root partition, abort\n"); exit(1); mount_root: xmkdir("/system_root", 0755); if (xmount("/dev/root", "/system_root", "ext4", MS_RDONLY, nullptr)) xmount("/dev/root", "/system_root", "erofs", MS_RDONLY, nullptr); } void SARInit::early_mount() { backup_files(); mount_system_root(); switch_root("/system_root"); // Use the apex folder to determine whether 2SI (Android 10+) is_two_stage = access("/apex", F_OK) == 0; LOGD("is_two_stage: [%d]\n", is_two_stage); if (!is_two_stage) { // Make dev writable xmkdir("/dev", 0755); xmount("tmpfs", "/dev", "tmpfs", 0, "mode=755"); mount_list.emplace_back("/dev"); avd_hack = config->emulator; mount_with_dt(); } } bool SecondStageInit::prepare() { backup_files(); umount2("/init", MNT_DETACH); umount2("/proc/self/exe", MNT_DETACH); // some weird devices, like meizu, embrace two stage init but still have legacy rootfs behaviour bool legacy = false; if (access("/system_root", F_OK) == 0) { if (access("/system_root/proc", F_OK) == 0) { switch_root("/system_root"); } else { xmount("/system_root", "/system", nullptr, MS_MOVE, nullptr); rmdir("/system_root"); legacy = true; } } return legacy; } void BaseInit::exec_init() { // Unmount in reverse order for (auto &p : reversed(mount_list)) { if (xumount2(p.data(), MNT_DETACH) == 0) LOGD("Unmount [%s]\n", p.data()); } execv("/init", argv); exit(1); } void MagiskInit::setup_tmp(const char *path) { LOGD("Setup Magisk tmp at %s\n", path); xmount("tmpfs", path, "tmpfs", 0, "mode=755"); chdir(path); xmkdir(INTLROOT, 0755); xmkdir(MIRRDIR, 0); xmkdir(BLOCKDIR, 0); int fd = xopen(INTLROOT "/config", O_WRONLY | O_CREAT, 0); xwrite(fd, magisk_config.buf, magisk_config.sz); close(fd); fd = xopen("magiskinit", O_WRONLY | O_CREAT, 0755); xwrite(fd, self.buf, self.sz); close(fd); // The magisk binary will be handled later // Create applet symlinks for (int i = 0; applet_names[i]; ++i) xsymlink("./magisk", applet_names[i]); xsymlink("./magiskinit", "magiskpolicy"); xsymlink("./magiskinit", "supolicy"); chdir("/"); }