Magisk/native/jni/init/init.cpp
Andrew Gunnerson 1f8c063dc6 Fix booting into recovery with Android 13 GKI kernels
With Android 13 GKI kernels, the boot partition has no ramdisk, so
Magisk constructs one from scratch. In this scenario, there's no backup
init binary at /.backup/init. For normal boot, magiskinit will symlink
/init -> /system/bin/init if needed. This commit implements the same
for booting into recovery. Before, magiskinit would just exec itself
over and over again because it couldn't restore the backup init.

Signed-off-by: Andrew Gunnerson <chillermillerlong@hotmail.com>
2022-07-18 13:33:50 -07:00

136 lines
3.3 KiB
C++

#include <sys/stat.h>
#include <sys/types.h>
#include <libgen.h>
#include <vector>
#include <xz.h>
#include <base.hpp>
#include <binaries.h>
#if defined(__arm__)
#include <armeabi-v7a_binaries.h>
#elif defined(__aarch64__)
#include <arm64-v8a_binaries.h>
#elif defined(__i386__)
#include <x86_binaries.h>
#elif defined(__x86_64__)
#include <x86_64_binaries.h>
#else
#error Unsupported ABI
#endif
#include "init.hpp"
#include <init-rs.cpp>
using namespace std;
bool unxz(int fd, const uint8_t *buf, size_t size) {
uint8_t out[8192];
xz_crc32_init();
struct xz_dec *dec = xz_dec_init(XZ_DYNALLOC, 1 << 26);
struct xz_buf b = {
.in = buf,
.in_pos = 0,
.in_size = size,
.out = out,
.out_pos = 0,
.out_size = sizeof(out)
};
enum xz_ret ret;
do {
ret = xz_dec_run(dec, &b);
if (ret != XZ_OK && ret != XZ_STREAM_END)
return false;
write(fd, out, b.out_pos);
b.out_pos = 0;
} while (b.in_pos != size);
return true;
}
static int dump_bin(const uint8_t *buf, size_t sz, const char *path, mode_t mode) {
int fd = xopen(path, O_WRONLY | O_CREAT | O_TRUNC | O_CLOEXEC, mode);
if (fd < 0)
return 1;
if (!unxz(fd, buf, sz))
return 1;
close(fd);
return 0;
}
void restore_ramdisk_init() {
unlink("/init");
const char *orig_init = backup_init();
if (access(orig_init, F_OK) == 0) {
xrename(orig_init, "/init");
} else {
// If the backup init is missing, this means that the boot ramdisk
// was created from scratch, and the real init is in a separate CPIO,
// which is guaranteed to be placed at /system/bin/init.
xsymlink(INIT_PATH, "/init");
}
}
int dump_manager(const char *path, mode_t mode) {
return dump_bin(manager_xz, sizeof(manager_xz), path, mode);
}
int dump_preload(const char *path, mode_t mode) {
return dump_bin(preload_xz, sizeof(preload_xz), path, mode);
}
class RecoveryInit : public BaseInit {
public:
using BaseInit::BaseInit;
void start() override {
LOGD("Ramdisk is recovery, abort\n");
restore_ramdisk_init();
rm_rf("/.backup");
exec_init();
}
};
int main(int argc, char *argv[]) {
umask(0);
auto name = basename(argv[0]);
if (name == "magisk"sv)
return magisk_proxy_main(argc, argv);
if (argc > 1 && argv[1] == "-x"sv) {
if (argc > 2 && argv[2] == "manager"sv)
return dump_manager(argv[3], 0644);
return 1;
}
if (getpid() != 1)
return 1;
BaseInit *init;
BootConfig config{};
if (argc > 1 && argv[1] == "selinux_setup"sv) {
init = new SecondStageInit(argv);
} else {
// This will also mount /sys and /proc
load_kernel_info(&config);
if (config.skip_initramfs)
init = new LegacySARInit(argv, &config);
else if (config.force_normal_boot)
init = new FirstStageInit(argv, &config);
else if (access("/sbin/recovery", F_OK) == 0 || access("/system/bin/recovery", F_OK) == 0)
init = new RecoveryInit(argv, &config);
else if (check_two_stage())
init = new FirstStageInit(argv, &config);
else
init = new RootFSInit(argv, &config);
}
// Run the main routine
init->start();
exit(1);
}