安卓恢复出厂设置过程详解

本文基于Android R。高通平台。

设置中点击“恢复出厂设置”即可清楚用户数据。查看代码发现其只是发送了一个广播而已。

                Intent intent = new Intent(Intent.ACTION_FACTORY_RESET);
                intent.setPackage("android");
                intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
                intent.putExtra(Intent.EXTRA_REASON,
                        "CryptKeeper.showFactoryReset() corrupt=" + corrupt);
                sendBroadcast(intent);
public static final String ACTION_FACTORY_RESET = "android.intent.action.FACTORY_RESET";

/framework/base/core/res/AndroidManifest.xml中静态注册了这个广播的接收者MasterClearReceiver。

        <receiver android:name="com.android.server.MasterClearReceiver"
            android:permission="android.permission.MASTER_CLEAR">
            <intent-filter
                    android:priority="100" >
                <!-- For Checkin, Settings, etc.: action=FACTORY_RESET -->
                <action android:name="android.intent.action.FACTORY_RESET" />
                <!-- As above until all the references to the deprecated MASTER_CLEAR get updated to
                     FACTORY_RESET. -->
                <action android:name="android.intent.action.MASTER_CLEAR" />

                <!-- MCS always uses REMOTE_INTENT: category=MASTER_CLEAR -->
                <action android:name="com.google.android.c2dm.intent.RECEIVE" />
                <category android:name="android.intent.category.MASTER_CLEAR" />
            </intent-filter>
        </receiver>

查看MasterClearReceiver代码,onReceive接收到广播时,判断如果广播是Intent.ACTION_FACTORY_RESET,且factoryResetPackage不为空,就重新将广播发出去,return退出。从此可以看到MasterClearReceiver支持供应商重载“恢复出厂设置”操作,只需要将com.android.internal.R.string.config_factoryResetPackage值overlay就可以了。否则继续执行默认的恢厂操作。

        final String factoryResetPackage = context
                .getString(com.android.internal.R.string.config_factoryResetPackage);
        if (Intent.ACTION_FACTORY_RESET.equals(intent.getAction())
                && !TextUtils.isEmpty(factoryResetPackage)) {
    
    
            intent.setPackage(factoryResetPackage).setComponent(null);
            context.sendBroadcastAsUser(intent, UserHandle.SYSTEM);
            return;
        }

新启线程执行操作:

RecoverySystem.rebootWipeUserData(context, shutdown, reason, forceWipe, mWipeEsims);

RecoverySystem的rebootWipeUserData方法中调用bootCommand方法,传入参数"–wipe_data"和当前local参数(当前地区语言)、reasonArg、shutdownArg。

String shutdownArg = null;
if (shutdown) {
    
    
    shutdownArg = "--shutdown_after";
}

String reasonArg = null;
if (!TextUtils.isEmpty(reason)) {
    
    
    String timeStamp = DateFormat.format("yyyy-MM-ddTHH:mm:ssZ", System.currentTimeMillis()).toString();
    reasonArg = "--reason=" + sanitizeArg(reason + "," + timeStamp);
}

final String localeArg = "--locale=" + Locale.getDefault().toLanguageTag() ;
bootCommand(context, shutdownArg, "--wipe_data", reasonArg, localeArg);

最终会调用到RecoverySystemService的rebootRecoveryWithCommand方法。

    @Override // Binder call
    public void rebootRecoveryWithCommand(String command) {
    
    
        if (DEBUG) Slog.d(TAG, "rebootRecoveryWithCommand: [" + command + "]");
        synchronized (sRequestLock) {
    
    
            if (!setupOrClearBcb(true, command)) {
    
    
                return;
            }

            // Having set up the BCB, go ahead and reboot.
            PowerManager pm = mInjector.getPowerManager();
            pm.reboot(PowerManager.REBOOT_RECOVERY);
        }
    }

可以看到rebootRecoveryWithCommand方法只执行了两个操作:

  1. setupOrClearBcb(true, command)
  2. pm.reboot(PowerManager.REBOOT_RECOVERY);

setupOrClearBcb将之前传递过来的参数写入BCB中,然后重启进入recovery模式中。

setupBCB

    private boolean setupOrClearBcb(boolean isSetup, String command) {
    
    
        mContext.enforceCallingOrSelfPermission(android.Manifest.permission.RECOVERY, null);

        final boolean available = checkAndWaitForUncryptService();
        if (!available) {
    
    
            Slog.e(TAG, "uncrypt service is unavailable.");
            return false;
        }

        if (isSetup) {
    
    
            mInjector.systemPropertiesSet("ctl.start", "setup-bcb");
        } else {
    
    
            mInjector.systemPropertiesSet("ctl.start", "clear-bcb");
        }

        // Connect to the uncrypt service socket.
        UncryptSocket socket = mInjector.connectService();
        if (socket == null) {
    
    
            Slog.e(TAG, "Failed to connect to uncrypt socket");
            return false;
        }

        try {
    
    
            // Send the BCB commands if it's to setup BCB.
            if (isSetup) {
    
    
                socket.sendCommand(command);
            }

            // Read the status from the socket.
            int status = socket.getPercentageUncrypted();

            // Ack receipt of the status code. uncrypt waits for the ack so
            // the socket won't be destroyed before we receive the code.
            socket.sendAck();

            if (status == 100) {
    
    
                Slog.i(TAG, "uncrypt " + (isSetup ? "setup" : "clear")
                        + " bcb successfully finished.");
            } else {
    
    
                // Error in /system/bin/uncrypt.
                Slog.e(TAG, "uncrypt failed with status: " + status);
                return false;
            }
        } catch (IOException e) {
    
    
            Slog.e(TAG, "IOException when communicating with uncrypt:", e);
            return false;
        } finally {
    
    
            socket.close();
        }

        return true;
    }
uncrypt

通过设置属性启动 setup-bcb 程序,通过socket与 uncrypt通信,将之前的参数发送给 uncrypt。(关于“ctl.start”属性启动程序的细节可以参考https://blog.csdn.net/u010223349/article/details/8876232)

setup-bcb,clear-bcb,uncrypt都是指同一程序,代码位置位于 /bootable/recovery/uncrypt/。

Android.bp:

cc_binary {
    name: "uncrypt",

    srcs: [
        "uncrypt.cpp",
    ],

    cflags: [
        "-Wall",
        "-Werror",
    ],

    shared_libs: [
        "libbase",
        "libbootloader_message",
        "libcutils",
        "libfs_mgr",
    ],

    static_libs: [
        "libotautil",
    ],

    init_rc: [
        "uncrypt.rc",
    ],
}

uncrypt.rc:

service uncrypt /system/bin/uncrypt
    class main
    socket uncrypt stream 600 system system
    disabled
    oneshot

service setup-bcb /system/bin/uncrypt --setup-bcb
    class main
    socket uncrypt stream 600 system system
    disabled
    oneshot

service clear-bcb /system/bin/uncrypt --clear-bcb
    class main
    socket uncrypt stream 600 system system
    disabled
    oneshot

uncrypt.cpp对程序启动的参数分别处理,当参数为“setup-bcb”时调用setup_bcb()方法。

static bool setup_bcb(const int socket) {
    
    
    // c5. receive message length
    int length;
    if (!android::base::ReadFully(socket, &length, 4)) {
    
    
        PLOG(ERROR) << "failed to read the length";
        return false;
    }
    length = ntohl(length);

    // c7. receive message
    std::string content;
    content.resize(length);
    if (!android::base::ReadFully(socket, &content[0], length)) {
    
    
        PLOG(ERROR) << "failed to read the message";
        return false;
    }
    LOG(INFO) << "  received command: [" << content << "] (" << content.size() << ")";
    std::vector<std::string> options = android::base::Split(content, "\n");
    std::string wipe_package;
    for (auto& option : options) {
    
    
        if (android::base::StartsWith(option, "--wipe_package=")) {
    
    
            std::string path = option.substr(strlen("--wipe_package="));
            if (!android::base::ReadFileToString(path, &wipe_package)) {
    
    
                PLOG(ERROR) << "failed to read " << path;
                return false;
            }
            option = android::base::StringPrintf("--wipe_package_size=%zu", wipe_package.size());
        }
    }

    // c8. setup the bcb command
    std::string err;
    if (!write_bootloader_message(options, &err)) {
    
    
        LOG(ERROR) << "failed to set bootloader message: " << err;
        write_status_to_socket(-1, socket);
        return false;
    }
    if (!wipe_package.empty() && !write_wipe_package(wipe_package, &err)) {
    
    
        PLOG(ERROR) << "failed to set wipe package: " << err;
        write_status_to_socket(-1, socket);
        return false;
    }
    // c10. send "100" status
    write_status_to_socket(100, socket);
    return true;
}

write_bootloader_message()方法将之前传递过来的参数写入BCB。由bootloader_message.cpp中write_misc_partition()方法实现。(关于BCB的更多介绍参考https://blog.csdn.net/Android_2016/article/details/98959849)

bool write_misc_partition(const void* p, size_t size, const std::string& misc_blk_device,
                          size_t offset, std::string* err) {
    
    
  android::base::unique_fd fd(open(misc_blk_device.c_str(), O_WRONLY));
  if (fd == -1) {
    
    
    *err = android::base::StringPrintf("failed to open %s: %s", misc_blk_device.c_str(),
                                       strerror(errno));
    return false;
  }
  if (lseek(fd, static_cast<off_t>(offset), SEEK_SET) != static_cast<off_t>(offset)) {
    
    
    *err = android::base::StringPrintf("failed to lseek %s: %s", misc_blk_device.c_str(),
                                       strerror(errno));
    return false;
  }
  if (!android::base::WriteFully(fd, p, size)) {
    
    
    *err = android::base::StringPrintf("failed to write %s: %s", misc_blk_device.c_str(),
                                       strerror(errno));
    return false;
  }
  if (fsync(fd) == -1) {
    
    
    *err = android::base::StringPrintf("failed to fsync %s: %s", misc_blk_device.c_str(),
                                       strerror(errno));
    return false;
  }
  return true;
}

setup_bcb()方法在简单处理从socket接收的消息后,返回状态值“100”就结束了。

关于write_misc_partition()方法中变量misc_blk_device,misc_blk_device由方法get_misc_blk_device()初始化。查找/misc分区。

static std::string get_misc_blk_device(std::string* err) {
    
    
  Fstab fstab;
  if (!ReadDefaultFstab(&fstab)) {
    
    
    *err = "failed to read default fstab";
    return "";
  }
  auto record = GetEntryForMountPoint(&fstab, "/misc");
  if (record == nullptr) {
    
    
    *err = "failed to find /misc partition";
    return "";
  }
  return record->blk_device;
}

ReadDefaultFstab()方法读取设备分区信息,返回挂载点。

// Loads the fstab file and combines with fstab entries passed in from device tree.
bool ReadDefaultFstab(Fstab* fstab) {
    
    
    Fstab dt_fstab;
    ReadFstabFromDt(&dt_fstab, false);

    *fstab = std::move(dt_fstab);

    std::string default_fstab_path;
    // Use different fstab paths for normal boot and recovery boot, respectively
    if (access("/system/bin/recovery", F_OK) == 0) {
    
    
        default_fstab_path = "/etc/recovery.fstab";
    } else {
    
      // normal boot
        default_fstab_path = GetFstabPath();
    }

    Fstab default_fstab;
    if (!default_fstab_path.empty()) {
    
    
        ReadFstabFromFile(default_fstab_path, &default_fstab);
    } else {
    
    
        LINFO << __FUNCTION__ << "(): failed to find device default fstab";
    }

    for (auto&& entry : default_fstab) {
    
    
        fstab->emplace_back(std::move(entry));
    }

    return !fstab->empty();
}

查看recovery.fstab文件,可以看到关于/misc分区的一些信息:

/dev/block/bootdevice/by-name/misc /misc emmc defaults defaults

recovery模式

pm.reboot(PowerManager.REBOOT_RECOVERY)重启进入recovery模式。recovery相关代码位于/bootable/recovery/目录。

启动recovery程序,首先进入recovery_main.cpp中的main()方法。其中"…"表示对代码进行了删减,这里只关注“恢复出厂设置”的处理逻辑。

int main(int argc, char** argv) {
    
    
  ...
  load_volume_table();

  std::string stage;
  std::vector<std::string> args = get_args(argc, argv, &stage);
  auto args_to_parse = StringVectorToNullTerminatedArray(args);

  static constexpr struct option OPTIONS[] = {
    
    
    {
    
     "fastboot", no_argument, nullptr, 0 },
    {
    
     "locale", required_argument, nullptr, 0 },
    {
    
     "reason", required_argument, nullptr, 0 },
    {
    
     "show_text", no_argument, nullptr, 't' },
    {
    
     nullptr, 0, nullptr, 0 },
  };

  bool show_text = false;
  bool fastboot = false;
  std::string locale;
  std::string reason;

  // The code here is only interested in the options that signal the intent to start fastbootd or
  // recovery. Unrecognized options are likely meant for recovery, which will be processed later in
  // start_recovery(). Suppress the warnings for such -- even if some flags were indeed invalid, the
  // code in start_recovery() will capture and report them.
  opterr = 0;

  int arg;
  int option_index;
  while ((arg = getopt_long(args_to_parse.size() - 1, args_to_parse.data(), "", OPTIONS,
                            &option_index)) != -1) {
    
    
    switch (arg) {
    
    
      case 't':
        show_text = true;
        break;
      case 0: {
    
    
        std::string option = OPTIONS[option_index].name;
        if (option == "locale") {
    
    
          locale = optarg;
        } else if (option == "reason") {
    
    
          reason = optarg;
          LOG(INFO) << "reason is [" << reason << "]";
        } else if (option == "fastboot" &&
                   android::base::GetBoolProperty("ro.boot.dynamic_partitions", false)) {
    
    
          fastboot = true;
        }
        break;
      }
    }
  }
  optind = 1;
  opterr = 1;


  static constexpr const char* kDefaultLibRecoveryUIExt = "librecovery_ui_ext.so";
  // Intentionally not calling dlclose(3) to avoid potential gotchas (e.g. `make_device` may have
  // handed out pointers to code or static [or thread-local] data and doesn't collect them all back
  // in on dlclose).
  void* librecovery_ui_ext = dlopen(kDefaultLibRecoveryUIExt, RTLD_NOW);

  using MakeDeviceType = decltype(&make_device);
  MakeDeviceType make_device_func = nullptr;
  if (librecovery_ui_ext == nullptr) {
    
    
    printf("Failed to dlopen %s: %s\n", kDefaultLibRecoveryUIExt, dlerror());
  } else {
    
    
    reinterpret_cast<void*&>(make_device_func) = dlsym(librecovery_ui_ext, "make_device");
    if (make_device_func == nullptr) {
    
    
      printf("Failed to dlsym make_device: %s\n", dlerror());
    }
  }

  Device* device;
  ...

  BootState boot_state(reason, stage);  // recovery_main owns the state of boot.
  device->SetBootState(&boot_state);
  ...
  while (true) {
    
    
    ...
    auto ret = fastboot ? StartFastboot(device, args) : start_recovery(device, args);

    if (ret == Device::KEY_INTERRUPTED) {
    
    
      ret = action.exchange(ret);
      if (ret == Device::NO_ACTION) {
    
    
        continue;
      }
    }
    switch (ret) {
    
    
      case Device::SHUTDOWN:
        ui->Print("Shutting down...\n");
        Shutdown("userrequested,recovery");
        break;

      case Device::REBOOT_BOOTLOADER:
        ui->Print("Rebooting to bootloader...\n");
        Reboot("bootloader");
        break;

      case Device::REBOOT_FASTBOOT:
        ui->Print("Rebooting to recovery/fastboot...\n");
        Reboot("fastboot");
        break;

      case Device::REBOOT_RECOVERY:
        ui->Print("Rebooting to recovery...\n");
        Reboot("recovery");
        break;

      case Device::ENTER_FASTBOOT:
        if (android::fs_mgr::LogicalPartitionsMapped()) {
    
    
          ui->Print("Partitions may be mounted - rebooting to enter fastboot.");
          Reboot("fastboot");
        } else {
    
    
          LOG(INFO) << "Entering fastboot";
          fastboot = true;
        }
        break;

      case Device::ENTER_RECOVERY:
        LOG(INFO) << "Entering recovery";
        fastboot = false;
        break;

      case Device::REBOOT:
        ui->Print("Rebooting...\n");
        Reboot("userrequested,recovery");
        break;

      default:
        ui->Print("Rebooting...\n");
        Reboot("unknown" + std::to_string(ret));
        break;
        ...
    }
  }

  // Should be unreachable.
  return EXIT_SUCCESS;
}

load_volume_table()加载挂载分区,从打印的log中查看到挂载的分区信息:

0 /system  ext4 system_a 0
1 /system_ext  ext4 system_ext_a 0
2 /product  ext4 product_a 0
3 /vendor  ext4 vendor_a 0
4 /odm  ext4 odm_a 0
5 /metadata  ext4 /dev/block/bootdevice/by-name/metadata 0
6 /data  f2fs /dev/block/bootdevice/by-name/userdata 0
7 /sdcard  vfat /dev/block/mmcblk1p1 0
8 /boot  emmc /dev/block/bootdevice/by-name/boot 0
9 /misc  emmc /dev/block/bootdevice/by-name/misc 0
10 /tmp  ramdisk ramdisk 0

get_args(argc, argv, &stage)方法从/misc分区读取之前写入到BCB中的信息,赋值给变量args,在log中打印args:

/system/bin/recovery
--wipe_data
--reason=MasterClearConfirm,2021-03-29T09:02:07Z
--locale=zh

可以看到就是之前RecoverySystemService的rebootRecoveryWithCommand()方法通过socket发送给uncrypt的消息内容。

对args信息进行一些处理后,判断是否是StartFastboot()或则start_recovery(),这里选择的是start_recovery(),while(true)循环体中判断start_recovery()返回参数,以此执行下一步操作。

recovery.cpp–>start_recovery(),同样“…”处对代码进行了删减。

Device::BuiltinAction start_recovery(Device* device, const std::vector<std::string>& args) {
    
    
  static constexpr struct option OPTIONS[] = {
    
    
    {
    
     "fastboot", no_argument, nullptr, 0 },
    {
    
     "install_with_fuse", no_argument, nullptr, 0 },
    {
    
     "just_exit", no_argument, nullptr, 'x' },
    {
    
     "locale", required_argument, nullptr, 0 },
    {
    
     "prompt_and_wipe_data", no_argument, nullptr, 0 },
    {
    
     "reason", required_argument, nullptr, 0 },
    {
    
     "rescue", no_argument, nullptr, 0 },
    {
    
     "retry_count", required_argument, nullptr, 0 },
    {
    
     "security", no_argument, nullptr, 0 },
    {
    
     "show_text", no_argument, nullptr, 't' },
    {
    
     "shutdown_after", no_argument, nullptr, 0 },
    {
    
     "sideload", no_argument, nullptr, 0 },
    {
    
     "sideload_auto_reboot", no_argument, nullptr, 0 },
    {
    
     "update_package", required_argument, nullptr, 0 },
    {
    
     "wipe_ab", no_argument, nullptr, 0 },
    {
    
     "wipe_cache", no_argument, nullptr, 0 },
    {
    
     "wipe_data", no_argument, nullptr, 0 },
    {
    
     "wipe_package_size", required_argument, nullptr, 0 },
    {
    
     nullptr, 0, nullptr, 0 },
  };
  ...
  auto args_to_parse = StringVectorToNullTerminatedArray(args);
  int arg;
  int option_index;
  // Parse everything before the last element (which must be a nullptr). getopt_long(3) expects a
  // null-terminated char* array, but without counting null as an arg (i.e. argv[argc] should be
  // nullptr).
  while ((arg = getopt_long(args_to_parse.size() - 1, args_to_parse.data(), "", OPTIONS,
                            &option_index)) != -1) {
    
    
    switch (arg) {
    
    
      case 't':
        // Handled in recovery_main.cpp
        break;
      case 'x':
        just_exit = true;
        break;
      case 0: {
    
    
        std::string option = OPTIONS[option_index].name;
        if (option == "install_with_fuse") {
    
    
          install_with_fuse = true;
        } else if (option == "locale" || option == "fastboot" || option == "reason") {
    
    
          // Handled in recovery_main.cpp
        } else if (option == "prompt_and_wipe_data") {
    
    
          should_prompt_and_wipe_data = true;
        } else if (option == "rescue") {
    
    
          rescue = true;
        } else if (option == "retry_count") {
    
    
          android::base::ParseInt(optarg, &retry_count, 0);
        } else if (option == "security") {
    
    
          security_update = true;
        } else if (option == "sideload") {
    
    
          sideload = true;
        } else if (option == "sideload_auto_reboot") {
    
    
          sideload = true;
          sideload_auto_reboot = true;
        } else if (option == "shutdown_after") {
    
    
          shutdown_after = true;
        } else if (option == "update_package") {
    
    
          update_package = optarg;
        } else if (option == "wipe_ab") {
    
    
          should_wipe_ab = true;
        } else if (option == "wipe_cache") {
    
    
          should_wipe_cache = true;
        } else if (option == "wipe_data") {
    
    
          should_wipe_data = true;
        } else if (option == "wipe_package_size") {
    
    
          android::base::ParseUint(optarg, &wipe_package_size);
        }
        break;
      }
      case '?':
        LOG(ERROR) << "Invalid command argument";
        continue;
    }
  }
  optind = 1;

  // next_action indicates the next target to reboot into upon finishing the install. It could be
  // overridden to a different reboot target per user request.
  Device::BuiltinAction next_action = shutdown_after ? Device::SHUTDOWN : Device::REBOOT;
  ...

  if (update_package != nullptr) {
    
    
    ...
  } else if (should_wipe_data) {
    
    
    save_current_log = true;
    CHECK(device->GetReason().has_value());
    bool convert_fbe = device->GetReason().value() == "convert_fbe";
    if (!WipeData(device, convert_fbe)) {
    
    
      status = INSTALL_ERROR;
    }
  } 
  ...

error:
  if (status == INSTALL_ERROR || status == INSTALL_CORRUPT) {
    
    
    ui->SetBackground(RecoveryUI::ERROR);
    if (!ui->IsTextVisible()) {
    
    
      sleep(5);
    }
  }

  // Determine the next action.
  //  - If the state is INSTALL_REBOOT, device will reboot into the target as specified in
  //    `next_action`.
  //  - If the recovery menu is visible, prompt and wait for commands.
  //  - If the state is INSTALL_NONE, wait for commands (e.g. in user build, one manually boots
  //    into recovery to sideload a package or to wipe the device).
  //  - In all other cases, reboot the device. Therefore, normal users will observe the device
  //    rebooting a) immediately upon successful finish (INSTALL_SUCCESS); or b) an "error" screen
  //    for 5s followed by an automatic reboot.
  if (status != INSTALL_REBOOT) {
    
    
    if (status == INSTALL_NONE || ui->IsTextVisible()) {
    
    
      auto temp = PromptAndWait(device, status);
      if (temp != Device::NO_ACTION) {
    
    
        next_action = temp;
      }
    }
  }

  // Save logs and clean up before rebooting or shutting down.
  FinishRecovery(ui);

  return next_action;
}

和上面的main()方法一样,args参数被处理赋值给args_to_parse变量,通过getopt_long()对参数进行判断选择对应的操作,option == "wipe_data"时,should_wipe_data = true,之后通过判断变量should_wipe_data调用WipeData(device, convert_fbe)方法清楚用户数据:

constexpr const char* CACHE_ROOT = "/cache";
constexpr const char* DATA_ROOT = "/data";
constexpr const char* METADATA_ROOT = "/metadata";

bool WipeData(Device* device, bool convert_fbe) {
    
    
  RecoveryUI* ui = device->GetUI();
  ui->Print("\n-- Wiping data...\n");

  if (!FinishPendingSnapshotMerges(device)) {
    
    
    ui->Print("Unable to check update status or complete merge, cannot wipe partitions.\n");
    return false;
  }

  bool success = device->PreWipeData();
  if (success) {
    
    
    success &= EraseVolume(DATA_ROOT, ui, convert_fbe);
    bool has_cache = volume_for_mount_point("/cache") != nullptr;
    if (has_cache) {
    
    
      success &= EraseVolume(CACHE_ROOT, ui, false);
    }
    if (volume_for_mount_point(METADATA_ROOT) != nullptr) {
    
    
      success &= EraseVolume(METADATA_ROOT, ui, false);
    }
  }
  if (success) {
    
    
    success &= device->PostWipeData();
  }
  ui->Print("Data wipe %s.\n", success ? "complete" : "failed");
  return success;
}

WipeData()方法对/data,/cache,/metadata分区进行格式化操作。之后start_recovery()返回Device::REBOOT,设备重启。

猜你喜欢

转载自blog.csdn.net/qq_36063677/article/details/115729587