三、sbin/recovery/服务流程-recovery.cpp main方法
本次将分析recovery.cpp main方法,其中会介绍一部分C++ 基础,话不多说,直接开始,我们都知道从bootloder引导分区后会判断misc的参数,如果是boot-recovery ,会驱动进入recovery模式,这部分后面会进行补充,暂时对主要流程进行二次分析
1、加载分区表
2、get_args
3、加载selinux
4、关于路径转换解析
5、执行传入参数命令
6、finish_recovery
那么首先其实跟system的启动相同,会执行内核的init.rc
etc/init.rc:
service recovery /sbin/recovery
seclabel u:r:recovery:s0
直接去启动recovery服务,这个文件是编译在recovey/sbin下的可执行文件,我们开启main方法之旅
//argc和argv参数在用命令行编译程序时有用。main( int argc, char* argv[], char **env ) 中
//第一个参数,int型的argc,为整型,用来统计程序运行时发送给main函数的命令行参数的个数
//第二个参数,char*型的argv[],为字符串数组,用来存放指向的字符串参数的指针数组
//首先我们看到mian,需要知道这两个参数传进来的是什么
int main(int argc, char **argv) {
//所以一开始在这里加了打印出参数的内容,但是试了几次都没有看到last_log的输出,没有参数时不可能的,
//所以应该这里应该是我们的print提前了,这里还没有定义log和log定向输出
//int i;
//for (i = 0; i < argc; i++){
//printf("argc && argv we need know how much the argc argv\n");
//printf("argv %d is %s\n", i, argv[i]);
//}
//从这里开始,定义log部分,从注释中也能够看出来
// We don't have logcat yet under recovery; so we'll print error on screen and
// log to stdout (which is redirected to recovery.log) as we used to do.
android::base::InitLogging(argv, &UiLogger);
// Take last pmsg contents and rewrite it to the current pmsg session.
static const char filter[] = "recovery/";
// Do we need to rotate?
bool doRotate = false;
__android_log_pmsg_file_read(LOG_ID_SYSTEM, ANDROID_LOG_INFO, filter, logbasename, &doRotate);
// Take action to refresh pmsg contents
__android_log_pmsg_file_read(LOG_ID_SYSTEM, ANDROID_LOG_INFO, filter, logrotate, &doRotate);
//启动一个adbd,为了使用adb sideload命令,也就是如果我们用这个命令启动recovery sdcard升级,
//那么其实是启动了sbin/adbd服务,如果我们直接在线升级的话 ,是用不到adbd服务的,所以参数里没有adbd
if (argc == 2 && strcmp(argv[1], "--adbd") == 0) {
minadbd_main();
return 0;
}
// redirect_stdio should be called only in non-sideload mode. Otherwise
// we may have two logger instances with different timestamps.
// log的重定向
redirect_stdio(TEMPORARY_LOG_FILE);
int i;
//打印结果 argv 0 is /sbin/recovery, 目前暂时猜想传进来的只有recovery服务/sbin/recovery
for (i = 0; i < argc; i++){
printf("argc && argv we need know how much the argc argv\n");
printf("argv %d is %s\n", i, argv[i]);
}
//打印出当前升级的时间
printf("Starting recovery (pid %d) on %s", getpid(), ctime(&start));
//加载分区表 TODO 暂时知道输出了什么,具体的流程比较复杂,需要慢慢分析
load_volume_table();
//判断是不是可以加载cache 和 nvdata
has_cache = volume_for_mount_point(CACHE_ROOT) != nullptr;
has_nvdata = volume_for_mount_point(NVDATA_ROOT) != nullptr;
//初始化分区类型,不过这个方法在之前load_volume_table()方法中已经执行过了,所以这里只是再次确认
//如果已经初始化,则跳过
mt_init_partition_type();
1、加载分区表 load_volume_table();
补充说明:fstab类型 system/core/fs_mgr/include_fstab/fstab/fstab.h
//定义了一个链表,包含了三个元素,包含个数,每条信息所包含的内容recs,fstab表的名称
struct fstab {
int num_entries;
struct fstab_rec* recs;
char* fstab_filename;
};
struct fstab_rec {
char* blk_device;
char* mount_point;
char* fs_type;
unsigned long flags;
char* fs_options;
int fs_mgr_flags;
char* key_loc;
char* key_dir;
char* verity_loc;
long long length;
char* label;
int partnum;
int swap_prio;
int max_comp_streams;
unsigned int zram_size;
uint64_t reserved_size;
unsigned int file_contents_mode;
unsigned int file_names_mode;
unsigned int erase_blk_size;
unsigned int logical_blk_size;
char* sysfs_path;
};
bootable/recovery/roots/cpp load_volume_table方法
//注意下这个时全局静态的fstab,因为别的方法也会调用到
static struct fstab* fstab = nullptr;
//在变量定义前加extern关键字表示声明一个变量但不定义它 。它表示"这只是一个声明,它的定义在别的地方",关键字extern,它表示变量是在文件以外定义的,或在文件后面部分才定义.
extern struct selabel_handle* sehandle;
void load_volume_table() {
//根据基础的etc/recovery.fstab生成fstab
fstab = fs_mgr_read_fstab_default();
if (!fstab) {
LOG(ERROR) << "Failed to read default fstab";
return;
}
//追加条目/tmp ramdisk ramdisk 0
int ret = fs_mgr_add_entry(fstab, "/tmp", "ramdisk", "ramdisk");
if (ret == -1) {
LOG(ERROR) << "Failed to add /tmp entry to fstab";
fs_mgr_free_fstab(fstab);
fstab = nullptr;
return;
}
//追加条目sdcard
///sdcard vfat /dev/block/mmcblk1p1 0
///sdcard_dev2 vfat /dev/block/platform/bootdevice/by-name/intsd 0
ret = mt_load_volume_table(fstab);
if (ret < 0 ) {
LOG(ERROR) << "mt_load_volume_table fail to add entry to fstab";
fs_mgr_free_fstab(fstab);
fstab = NULL;
return;
}
mt_ensure_dev_ready("/misc");
mt_ensure_dev_ready("/cache");
mt_fstab_translation_NAND(fstab);
printf("recovery filesystem table\n");
printf("=========================\n");
//遍历出fstab并打印,看log结果中 length为0 为什么都为0?
//这里会遍历fstab,打印出 编号 挂载点 文件系统类型 块设备 长度
for (int i = 0; i < fstab->num_entries; ++i) {
const Volume* v = &fstab->recs[i];
printf(" %d %s %s %s %lld\n", i, v->mount_point, v->fs_type, v->blk_device, v->length);
}
printf("\n");
}
system/core/fs_mgr/fs_mgr_fstab.cpp fs_mgr_read_fstab_default()方法
/*
* loads the fstab file and combines with fstab entries passed in from device tree.
*/
struct fstab *fs_mgr_read_fstab_default()
{
std::string default_fstab;
// Use different fstab paths for normal boot and recovery boot, respectively
//access()会检查是否可以读/写某一已存在的文件 F_OK则是用来判断该文件是否存在,返回值 若所有欲查核的权限都通过了检查则返回0值,表示成功,只要有一权限被禁止则返回-1
//如果有recovery这个可执行文件,说明启动到了recovery,使用etc/recovery.fstab
if (access("/sbin/recovery", F_OK) == 0) {
default_fstab = "/etc/recovery.fstab";
} else { // normal boot
default_fstab = get_fstab_path();
}
//struct fstab {
//int num_entries;
//struct fstab_rec* recs;
//char* fstab_filename;
//};
struct fstab* fstab = nullptr;
if (!default_fstab.empty()) {
//读取/etc/recovery.fstab,返回fstab结构体
fstab = fs_mgr_read_fstab(default_fstab.c_str());
} else {
LINFO << __FUNCTION__ << "(): failed to find device default fstab";
}
//读取/proc/device-tree/firmware/android/fstb文件 返回fstab结构体
struct fstab* fstab_dt = fs_mgr_read_fstab_dt();
// combines fstab entries passed in from device tree with
// the ones found from default_fstab file
//合并fsta 和 fstab_dt
return in_place_merge(fstab_dt, fstab);
}
继续分析后续流程
2、get_args(argc, argv)
recovery.cpp
int main(int argc, char **argv) {
//std::是个名称空间标示符,C++标准库中的函数或者对象都是在命名空间std中定义的,所以我们要使用标准函数库中的函数或对象都要使用std来限定。
//对象count是标准函数库所提供的对象,而标准库在名字空间中被指定为std,所以在使用cout的时候要加上std::。这样编译器就会明白我们调用的cout是名字空间std中的cout。
//get_args获取misc分区中的command信息
//C++ vector类为内置数组提供了一种替代表示
std::vector<std::string> args = get_args(argc, argv);
std::vector<char*> args_to_parse(args.size());
//不知道干了啥就加打印把,trnasform有两种用法,一种时修改大小写,一种时数据转移
//在这里我个人理解是数据转移的意思,因为获得的args容器是string类型的,后续的方法中我们需要的时char类型
std::transform(args.cbegin(), args.cend(), args_to_parse.begin(),
[](const std::string& arg) { return const_cast<char*>(arg.c_str()); });
printf("=======================\n");
printf("std::transform\n");
for (const auto& arg : args_to_parse) {
printf(" \"%s\"", arg); //"/sbin/recovery" "--update_package=@/cache/recovery/block.map" "--locale=en-US"
}
补充说明 bootable/recovery/bootloader_message/include/bootloader_message/bootloader_message.h
//bootloader_message是一个链表,包含了command,status,recovery,stage,reserved元素,为char类型数组
struct bootloader_message {
char command[32];
char status[32];
char recovery[768];
// The 'recovery' field used to be 1024 bytes. It has only ever
// been used to store the recovery command line, so 768 bytes
// should be plenty. We carve off the last 256 bytes to store the
// stage string (for multistage packages) and possible future
// expansion.
char stage[32];
// The 'reserved' field used to be 224 bytes when it was initially
// carved off from the 1024-byte recovery field. Bump it up to
// 1184-byte so that the entire bootloader_message struct rounds up
// to 2048-byte.
char reserved[1184];
};
回到recovery.cpp中的get_args具体方法 从misc或者cache/recovery/command中读取出具体的升级指令
static std::vector<std::string> get_args(const int argc, char** const argv) {
CHECK_GT(argc, 0);
//声明bootloader_message结构体
bootloader_message boot = {};
std::string err;
//读取read_bootloader_message中的信息,如果没有读到,输出error的值,设置bootloder_message为空
if (!read_bootloader_message(&boot, &err)) {
LOG(ERROR) << err;
// If fails, leave a zeroed bootloader_message.
boot = {};
}
stage = std::string(boot.stage);
//打印出获取的bootloader_message结构体中的参数内容
printf("\nget_args==================\n");
//Boot command: boot-recovery
std::string boot_command = std::string(boot.command, sizeof(boot.command));
LOG(INFO) << "Boot command: " << boot_command;
//Boot status:
std::string boot_status = std::string(boot.status, sizeof(boot.status));
LOG(INFO) << "Boot status: " << boot_status;
//boot_stage:
std::string boot_stage = std::string(boot.stage, sizeof(boot.stage));
LOG(INFO) << "boot_stage: " << boot_status;
//boot_recovery: recovery --update_package=@/cache/recovery/block.map --locale=en-US
std::string boot_recovery = std::string(boot.recovery, sizeof(boot.recovery));
LOG(INFO) << "boot_recovery: " << boot_recovery;
//boot_reserved:
std::string boot_reserved = std::string(boot.reserved, sizeof(boot.reserved));
LOG(INFO) << "boot_reserved: " << boot_reserved;
printf("get_args==================\n");
if (boot.command[0] != 0) {
std::string boot_command = std::string(boot.command, sizeof(boot.command));
LOG(INFO) << "Boot command: " << boot_command;
}
if (boot.status[0] != 0) {
std::string boot_status = std::string(boot.status, sizeof(boot.status));
LOG(INFO) << "Boot status: " << boot_status;
}
//输出时什么?size为1 这个容器中其实存储的还是字符串格式的"/sbin/recovery"
std::vector<std::string> args(argv, argv + argc);
printf("vector getargs args the farst data\n");
for (const auto& arg : args) {
printf(" \"%s\"", arg.c_str()); //"/sbin/recovery"
}
printf("\n");
// --- if arguments weren't supplied, look in the bootloader control block
//如果args的size为1,结合我们的打印,其实就是1
if (args.size() == 1) {
//保证数字最后的位置上为null ,"\0"是c++上的null的边上方式
boot.recovery[sizeof(boot.recovery) - 1] = '\0'; // Ensure termination
//转换char为string字符串
std::string boot_recovery(boot.recovery);
//声明一个新的容器,把boot_recovery中的信息按空格进行分割,分割后为三个字符串,
//recovery --update_package=@/cache/recovery/block.map --locale=en-US
std::vector<std::string> tokens = android::base::Split(boot_recovery, "\n");
if (!tokens.empty() && tokens[0] == "recovery") {
//auto可以在声明变量的时候根据变量初始值的类型自动为此变量选择匹配的类型
//for (auto i = vs.begin(); i != vs.end(); i++)代替for (std::vector<std::string>::iterator i = vs.begin(); i != vs.end(); i++)
for (auto it = tokens.begin() + 1; it != tokens.end(); it++) {
// Skip empty and '\0'-filled tokens.
//std::move是将对象的状态或者所有权从一个对象转移到另一个对象,只是转移,没有内存的搬迁或者内存拷贝。在这里其实就是迭代出的内容放到args容器中
if (!it->empty() && (*it)[0] != '\0') args.push_back(std::move(*it));
}
//根据我们的打印,last_log中的Got 3 arguments from boot message是从misc分区读取到的
printf("==============if (args.size() == 1) {==================\n");
LOG(INFO) << "Got " << args.size() << " arguments from boot message";
} else if (boot.recovery[0] != 0) {
LOG(ERROR) << "Bad boot message: \"" << boot_recovery << "\"";
}
}
//跟上面的方法相同其实,只不过取数据的位置不同,如果从misc没取到内容,那么从command中取出
// --- if that doesn't work, try the command file (if we have /cache).
if (args.size() == 1 && has_cache) {
......
......
LOG(INFO) << "Got " << args.size() << " arguments from " << COMMAND_FILE;
}
}
//将得到的参数回写到misc分区中,这里就是为什么如果没有升级完,还是进入recovery的原因,也是断电后继续升级的关键步骤
//TODO 这个问题暂时不看,我们已经知道了后续取出来的参数是什么,后续再继续研究分线流程
std::vector<std::string> options(args.cbegin() + 1, args.cend());
if (!update_bootloader_message(options, &err)) {
LOG(ERROR) << "Failed to set BCB message: " << err;
}
return args;
}
以下main方法中的代码暂时做简单说明,主要为处理UI部分
recovery.cpp
int main(int argc, char **argv) {
.....
.....
int arg;
int option_index;
//getopt_long获取参数,固定用法,将--update_package=@/cache/recovery/block.map
//得到参数-u --locale=en-US 得到参数l
while ((arg = getopt_long(args_to_parse.size(), args_to_parse.data(), "", OPTIONS,
&option_index)) != -1) {
switch (arg) {
case 'n':
android::base::ParseInt(optarg, &retry_count, 0);
break;
case 'u':
//传入升级包路径的参数
update_package = optarg; //@/cache/recovery/block.map
perform_fota = 1;
break;
.....
.....
case 'l':
//得到locale
locale = optarg; //en-US
break;
case '?':
LOG(ERROR) << "Invalid command argument";
continue;
}
}
//如果locale为空,则判断有没有cache分区,如果在cache中没有读到,则使用默认值
if (locale.empty()) {
if (has_cache) {
locale = load_locale_from_cache();
}
if (locale.empty()) {
locale = DEFAULT_LOCALE;
}
}
//初始化UI界面
Device* device = make_device();
if (android::base::GetBoolProperty("ro.boot.quiescent", false)) {
printf("Quiescent recovery mode.\n");
ui = new StubRecoveryUI();
} else {
ui = device->GetUI();
if (!ui->Init(locale)) {
printf("Failed to initialize UI, use stub UI instead.\n");
ui = new StubRecoveryUI();
}
}
// Set background string to "installing security update" for security update,
// otherwise set it to "installing system update".
//界面上显示的内容installing security update 或者 installing system update
ui->SetSystemUpdateText(security_update);
int st_cur, st_max;
if (!stage.empty() && sscanf(stage.c_str(), "%d/%d", &st_cur, &st_max) == 2) {
ui->SetStage(st_cur, st_max);
}
//显示recovery背景
ui->SetBackground(RecoveryUI::NONE);
if (show_text) ui->ShowText(true);
//加载selinux TODO 这部分没有查到太多的资料,后续搞清楚了再往上添加
sehandle = selinux_android_file_context_handle();
selinux_android_set_sehandle(sehandle);
if (!sehandle) {
ui->Print("Warning: No file_contexts\n");
}
//打印出了command信息,这个我们根据之前的代码已经知道了里面的内容
//Command: "/sbin/recovery"
//"--update_package=/storage/032B-13F5/Android/data/com.adups.fota/files/adupsfota/update.zip"
//"--locale=en-US"
printf("Command:");
for (const auto& arg : args) {
printf(" \"%s\"", arg.c_str());
}
printf("\n\n");
// 路径转换
//下面的代码中拼接和拷贝我都加了注释,大致意思,分配内存空间,拷贝字符串放入sdcard/
//拼接字符串将sdcard/.../update.zip拼接
//modified_path这个指针处理好后,会将内存空间给到update_package这个指针做后续处理
#ifdef ADUPS_FOTA_SUPPORT
if (perform_fota == 1) {
printf("xxxxxxxxxxxxxxxxx sleep 6 seconds\n");
sleep(6);
find_update_package_new(update_package);
//strncmp是一个比较字符串的方法,表示update_package 的前9位是否相等,如果相等,返回值为0
if (strncmp(update_package, "/storage/", 9) == 0) {
//strlen(char*)函数求的是字符串的实际长度,它求得方法是从开始到遇到第一个'\0',如果你只定义没有给它赋初值,这个结果是不定的,它会从aa首地址一直找下去,直到遇到'\0'停止
int len = strlen(update_package) + 12;
//分配内存供操作路径使用 在C语言中“char*”是声明一个字符类型的指针
char* modified_path = (char*)malloc(len);
printf("\n====char* modified_path====\n");
printf(" \"%s\"", modified_path);// ""
//这里面用到了两个方法,一个是strlcpy 拷贝字符串 一个是strlcat 拼接字符串,具体的没有查到太多信息,只知道是更新版解决了内存溢出,还是继续打log把
strlcpy(modified_path, SDCARD_ROOT, len);
printf("\n====strlcpy(modified_path, SDCARD_ROOT, len)====\n");
printf(" \"%s\"", modified_path);//"/sdcard"
//strstr(str1,str2) 函数用于判断字符串str2是否是str1的子串。如果是,则该函数返回str2在str1中首次出现的地址
if(strstr(update_package, "/Android/data/com.adups.fota/files/adupsfota/update.zip") != NULL){
char const *SDCARD_PATH = "/Android/data/com.adups.fota/files/adupsfota/update.zip";
strlcat(modified_path, SDCARD_PATH, len);
printf("\n====strlcat(modified_path, SDCARD_PATH, len)====\n");
printf(" \"%s\"\n", modified_path);//"/sdcard/Android/data/com.adups.fota/files/adupsfota/update.zip"
printf("\n\n");
}
else if(strstr(update_package, "/Android/data/com.adups.fota/files/LocalSdUpdate.zip") != NULL){
char const *SDCARD_PATH = "/Android/data/com.adups.fota/files/LocalSdUpdate.zip";
strlcat(modified_path, SDCARD_PATH, len);
}
printf("(replacing path \"%s\" with \"%s\")\n",
update_package, modified_path);
update_package = modified_path;
}
}
#endif
//打印出当前版本的build.prop
property_list(print_property, nullptr);
printf("\n");
继续填坑,分析剩余的进入install_package之前的判断条件和finish_recovery方法,我们先对其中主要流程做下说明,再分析其中的一些细节方法
recovery.cpp
init main
......
......
//读取的 RECOVERY_API_VERSION 在Android.mk中有定义数值
ui->Print("Supported API: %d\n", kRecoveryApiVersion);
//初始化status
int status = INSTALL_SUCCESS;
//判断update_package 是否有值
if (update_package != nullptr) {
modified_flash = true;
//如果判断电量不满足条件,打印log,并跳出升级,目前的最低升级电量为20%,TODO后续有时间再研究时lib里面的方法
if (!is_battery_ok()) {
ui->Print("battery capacity is not enough for installing package, needed is %d%%\n",
BATTERY_OK_PERCENTAGE);
//otautil/include/otautil/error_code.h:22: kLowBattery = 20
//把错误的信息输入到last_install中
log_failure_code(kLowBattery, update_package);
status = INSTALL_SKIPPED;
} else if (bootreason_in_blacklist()) {
//如果bootreason 是kernel_panic 或者similar 则跳出流程,输入到last_install中
ui->Print("bootreason is in the blacklist; skip OTA installation\n");
log_failure_code(kBootreasonInBlacklist, update_package);
status = INSTALL_SKIPPED;
} else {
//首次升级args参数中时没有retry_count,初始定义也为0
if (retry_count == 0) {
set_retry_bootloader_message(retry_count + 1, args);
}
//真正的升级的流程install_package
status = install_package(update_package, &should_wipe_cache, TEMPORARY_INSTALL_FILE, true,
retry_count);
if (status == INSTALL_SUCCESS && should_wipe_cache) {
wipe_cache(false, device);
}
if (status != INSTALL_SUCCESS) {
ui->Print("Installation aborted.\n");
//这里暂时不明白时什么场景下的,但是这里相当于出现异常,又重新走一遍升级的意思
if (status == INSTALL_RETRY && retry_count < RETRY_LIMIT) {
copy_logs();
retry_count += 1;
set_retry_bootloader_message(retry_count, args);
// Print retry count on screen.
ui->Print("Retry attempt %d\n", retry_count);
// Reboot and retry the update
if (!reboot("reboot,recovery")) {
ui->Print("Reboot failed\n");
} else {
while (true) {
pause();
}
}
}
//如果是userdbug或者eng版本,直接显示报错,如果时user版本,需要按键组合显示
if (is_ro_debuggable()) {
ui->ShowText(true);
}
}
}
log_failure_code方法 其实就是把状态信息写入到/tmp/recovery.log 和 /tmp/last_install中
static void log_failure_code(ErrorCode code, const char *update_package) {
//code 为 20,创建一个字符串容器
std::vector<std::string> log_buffer = {
update_package,
"0", // install result
"error: " + std::to_string(code),
};
//在每个元素中间加上换行拼接为字符串,放入到tmp/last_install文件中
std::string log_content = android::base::Join(log_buffer, "\n");
if (!android::base::WriteStringToFile(log_content, TEMPORARY_INSTALL_FILE)) {
PLOG(ERROR) << "failed to write " << TEMPORARY_INSTALL_FILE;
}
// Also write the info into last_log.
LOG(INFO) << log_content;
}
set_retry_bootloader_message(retry_count + 1, args) 方法
recovery.cpp
static void set_retry_bootloader_message(int retry_count, const std::vector<std::string>& args) {
std::vector<std::string> options;
//遍历出args中的每个元素
for (const auto& arg : args) {
if (!android::base::StartsWith(arg, "--retry_count")) {
//把取出的元素添加到option容器中,
options.push_back(arg);
}
}
//把--retry_count放入option的尾部 push_back(elem)// 在尾部加入一个数据。
options.push_back(android::base::StringPrintf("--retry_count=%d", retry_count));
std::string err;
//更新bcb的信息,这个方法其实在get_args中已经出现过,回写BCB,所以我们现在需要填之前的坑
if (!update_bootloader_message(options, &err)) {
LOG(ERROR) << err;
}
}
bootable/recovery/bootloader_message/bootloader_message.cpp
bool update_bootloader_message(const std::vector<std::string>& options, std::string* err) {
bootloader_message boot;
if (!read_bootloader_message(&boot, err)) {
return false;
}
//更新retry_count参数到链表boot中
update_bootloader_message_in_struct(&boot, options);
//把新的boot写入到misc分区
return write_bootloader_message(boot, err);
}
bool update_bootloader_message_in_struct(bootloader_message* boot,
const std::vector<std::string>& options) {
if (!boot) return false;
// Replace the command & recovery fields.
//memset是一个赋值函数,在这里是用来清空了boot.command和boot.recovery的内容
memset(boot->command, 0, sizeof(boot->command));
memset(boot->recovery, 0, sizeof(boot->recovery));
//这个我们在看路径转换的时候已经知道了这两个方法,strlcpy拷贝strlcat拼接
strlcpy(boot->command, "boot-recovery", sizeof(boot->command));
strlcpy(boot->recovery, "recovery\n", sizeof(boot->recovery));
for (const auto& s : options) {
strlcat(boot->recovery, s.c_str(), sizeof(boot->recovery));
//s.back() 传回最后一个数据,不检查这个数据是否存在。
if (s.back() != '\n') {
strlcat(boot->recovery, "\n", sizeof(boot->recovery));
}
}
//输出内容[ 6.905132] update_bootloader_message_in_struct==================
[ 6.905167] boot_command boot-recovery
[ 6.905226] boot_recovery recovery
[ 6.905235] /sbin/recovery
[ 6.905242] --update_package=/storage/C520-1F1D/Android/data/com.adups.fota/files/adupsfota/update.zip
[ 6.905248] --locale=zh-Hans-CN
[ 6.905254] --retry_count=1
[ 6.905260]
[ 6.905289]
[ 6.905317] update_bootloader_message_in_struct==================
printf("\nupdate_bootloader_message_in_struct==================\n");
std::string boot_command = std::string(boot->command, sizeof(boot->command));
printf("boot_command %s\n", boot_command.c_str());
std::string boot_recovery = std::string(boot->recovery, sizeof(boot->recovery));
printf("boot_recovery %s\n", boot_recovery.c_str());
printf("\nupdate_bootloader_message_in_struct==================\n");
return true;
}
这里要插入一个前面提到过的内容
//getopt_long获取参数,固定用法,将--update_package=@/cache/recovery/block.map 得到参数-u --locale=en-US 得到参数l
while ((arg = getopt_long(args_to_parse.size(), args_to_parse.data(), "", OPTIONS,
&option_index)) != -1) {
switch (arg) {
//这里的n就是 读取到的“retry_count”,如果升级中断,会将optarg = 1 赋值给retry_count
//相当于一个标志位,判断是不是首次升级
case 'n':
android::base::ParseInt(optarg, &retry_count, 0);
break;
finish_recovery详情
recovery.cpp
static void finish_recovery() {
//如果locale不为空且cache可以挂载到cache分区
if (!locale.empty() && has_cache) {
//在recovery.log上打印出信息
LOG(INFO) << "Saving locale \"" << locale << "\"";
//如果不能挂载到/cache/recovery/last_locale路径,则打印错误
if (ensure_path_mounted(LOCALE_FILE) != 0) {
LOG(ERROR) << "Failed to mount " << LOCALE_FILE;
//如果可以挂载,将locale信息写入到last_locate中
} else if (!android::base::WriteStringToFile(locale, LOCALE_FILE)) {
PLOG(ERROR) << "Failed to save locale to " << LOCALE_FILE;
}
}
//拷贝log从tmp到cache/recovery/last_*
copy_logs();
//清除了bcb中的信息,保证下次开机直接进入正常启动
std::string err;
if (!clear_bootloader_message(&err)) {
LOG(ERROR) << "Failed to clear BCB message: " << err;
}
//清除了cache分区中command中的信息,保证下次开机直接进入正常启动
//unlink()会删除参数pathname指定的文件,文件夹处理不了。成功返回0,否则返回1
//ENOENT (C++11) //无此文件或目录
if (has_cache) {
if (ensure_path_mounted(COMMAND_FILE) != 0 || (unlink(COMMAND_FILE) && errno != ENOENT)) {
LOG(WARNING) << "Can't unlink " << COMMAND_FILE;
}
ensure_path_unmounted(CACHE_ROOT);
}
sync(); // For good measure.
}
copy_logs方法
recovery.cpp
static void copy_logs() {
//modified初始设定为false,进入update_package=nullptr后置为ture,
if (!modified_flash) {
return;
}
//拷贝log到pmsg,TODO 暂时不知道是作什么的
copy_log_file_to_pmsg(TEMPORARY_LOG_FILE, LAST_LOG_FILE);
copy_log_file_to_pmsg(TEMPORARY_INSTALL_FILE, LAST_INSTALL_FILE);
//判断cache分区是否可以挂载
if (!has_cache) {
return;
}
//确保log路径可以挂载成功,
ensure_path_mounted(LAST_LOG_FILE);
ensure_path_mounted(LAST_KMSG_FILE);
//修改log名称,让last_log,last_kmsg为最新的一次升级,
//的方法在bootable/recovery/rotate_logs中,其实就是修改log名称,把log文件名last_log> last_log.1
rotate_logs(LAST_LOG_FILE, LAST_KMSG_FILE);
//拷贝log,并给到文件相应的权限和文件组
copy_log_file(TEMPORARY_LOG_FILE, LOG_FILE, true);
//拷贝recovery.log到last.log
copy_log_file(TEMPORARY_LOG_FILE, LAST_LOG_FILE, false);
///拷贝tmp/last_install到cache/recovery/last_install
copy_log_file(TEMPORARY_INSTALL_FILE, LAST_INSTALL_FILE, false);
//保存kernel的log到last_kmsg
save_kernel_log(LAST_KMSG_FILE);
//给文件设定权限
chmod(LOG_FILE, 0600);
//给文件设定所在组
chown(LOG_FILE, AID_SYSTEM, AID_SYSTEM);
chmod(LAST_KMSG_FILE, 0600);
chown(LAST_KMSG_FILE, AID_SYSTEM, AID_SYSTEM);
chmod(LAST_LOG_FILE, 0640);
chmod(LAST_INSTALL_FILE, 0644);
sync();
}
本文详细分析了recovery.cppmain方法的执行流程,包括加载分区表、获取升级参数、执行升级命令等关键步骤,深入探讨了Android系统recovery模式下的启动和服务流程。
4931

被折叠的 条评论
为什么被折叠?



