三、sbin/recovery/服务流程-recovery.cpp main方法

本文详细分析了recovery.cppmain方法的执行流程,包括加载分区表、获取升级参数、执行升级命令等关键步骤,深入探讨了Android系统recovery模式下的启动和服务流程。

三、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();
}

 

<think>我们被要求解释一个Nginx配置命令,该命令用于在编译前配置Nginx的安装选项。命令如下: ./configure --prefix=/usr/local/nginx-1.26.3 --sbin-path=/usr/sbin/nginx-1.26.3 --pid-path=/usr/local/nginx/nginx.pid --error-log-path=/usr/local/nginx-1.26.3/error.log --http-log-path=/usr/local/nginx-1.26.3/access.log --with-pcre=/usr/local/pcre-8.39 --with-zlib=/usr/local/zlib-1.2.11 --with-http_stub_status_module 我们将逐一解析每个选项的含义和作用,并讨论可能出现的问题及解决方法。 1. `--prefix=/usr/local/nginx-1.26.3` - 含义:指定Nginx的安装根目录。这意味着Nginx将被安装到`/usr/local/nginx-1.26.3`目录下。 - 作用:该目录将包含Nginx运行所需的子目录,如`conf`(配置文件)、`html`(网页文件)、`logs`(日志)等。 - 问题:如果目录不存在或权限不足,安装会失败。 - 解决:确保目录存在并有足够的权限(通常需要root权限),或提前创建目录。 2. `--sbin-path=/usr/sbin/nginx-1.26.3` - 含义:指定Nginx可执行文件的安装路径。这里将可执行文件命名为`nginx-1.26.3`并放在`/usr/sbin`目录下。 - 作用:方便在命令行中直接运行`nginx-1.26.3`命令(通常需要将`/usr/sbin`加入PATH环境变量)。 - 问题:如果系统中已有同名文件,可能会被覆盖;或者路径不在PATH中导致找不到命令。 - 解决:确保该路径在PATH环境变量中,或者使用绝对路径启动;如果之前有安装,注意备份。 3. `--pid-path=/usr/local/nginx/nginx.pid` - 含义:指定Nginx主进程ID(PID)文件的存放路径。 - 作用:当Nginx启动后,会将主进程的PID写入该文件,便于管理脚本控制Nginx(如停止、重载)。 - 问题:如果目录不存在或权限不足,Nginx可能无法启动。 - 解决:确保目录存在并具有写入权限(通常为运行Nginx的用户,如www-data或nobody)。 4. `--error-log-path=/usr/local/nginx-1.26.3/error.log` - 含义:指定错误日志文件的存放路径。 - 作用:记录Nginx运行过程中的错误信息,便于故障排查。 - 问题:如果目录不存在或权限不足,Nginx启动失败。 - 解决:确保目录存在并具有写入权限。 5. `--http-log-path=/usr/local/nginx-1.26.3/access.log` - 含义:指定HTTP访问日志文件的存放路径(默认格式)。 - 作用:记录HTTP访问信息,如客户端IP、请求时间、请求路径等。 - 问题:同上,目录或权限问题。 - 解决:同上。 6. `--with-pcre=/usr/local/pcre-8.39` - 含义:指定PCRE库的源代码路径。PCRE(Perl Compatible Regular Expressions)用于支持Nginx的rewrite模块。 - 作用:Nginx需要PCRE库来解析正则表达式。 - 问题:如果路径不正确或PCRE版本不兼容,配置会失败。 - 解决:确保PCRE库已下载并解压到指定路径,或者使用系统已安装的PCRE(但这里指定了路径,所以必须存在)。 7. `--with-zlib=/usr/local/zlib-1.2.11` - 含义:指定zlib库的源代码路径。zlib用于HTTP响应内容的gzip压缩。 - 作用:提供压缩功能,减少网络传输量。 - 问题:路径错误或zlib版本问题会导致配置失败。 - 解决:确保zlib库已下载并解压到指定路径。 8. `--with-http_stub_status_module` - 含义:启用HTTP Stub Status模块。 - 作用:该模块提供简单的状态信息页面,用于监控Nginx的运行状态(如连接数、请求数等)。 - 问题:无特别问题,但需要配置Nginx才能访问状态页面(默认不暴露)。 - 解决:在Nginx配置文件中添加一个location块来提供状态信息,并注意访问控制。 可能出现的问题及解决办法总结: - 依赖库问题:确保PCRE和zlib已正确安装(或源代码路径正确),否则配置阶段会报错。可以通过预先安装这些库(使用系统包管理器或源码编译)来解决。 - 权限问题:确保Nginx的运行用户(在nginx.conf中指定的user)对日志文件、PID文件所在目录有写入权限。 - 路径冲突:如果之前安装过Nginx,需要注意新安装的路径与旧版本不同,避免覆盖。 配置完成后,通常执行: make make install 启动Nginx:使用指定的sbin路径启动,例如: /usr/sbin/nginx-1.26.3 验证安装:访问配置的状态模块(需要先在配置文件中启用)和默认页面。 相关问题: 1. 如何配置Nginx的HTTP Stub Status模块以监控状态? 2. 在编译Nginx时,如何解决依赖库(如PCRE, zlib)缺失的问题? 3. 如何配置Nginx的日志切割和轮转? 4. 如何将不同版本的Nginx共存于同一系统? </think>### Nginx配置命令详解 以下是对该配置命令各参数的逐项解析,涵盖含义、作用、潜在问题及解决方案: --- #### 1. `--prefix=/usr/local/nginx-1.26.3` - **含义**:指定Nginx的安装根目录。 - **作用**:所有基础文件(如`conf/`、`html/`、`logs/`)将安装在此路径下[^3]。 - **问题**: - 目录权限不足导致安装失败 - 与现有Nginx版本冲突 - **解决**: `sudo mkdir -p /usr/local/nginx-1.26.3 && sudo chown -R $USER:$USER /usr/local/nginx-1.26.3` --- #### 2. `--sbin-path=/usr/sbin/nginx-1.26.3` - **含义**:自定义可执行文件路径。 - **作用**:将`nginx`二进制文件安装到系统命令目录,便于直接调用[^1]。 - **问题**: - 覆盖系统默认命令(如已有旧版`nginx`) - `$PATH`未包含路径导致找不到命令 - **解决**: `sudo ln -s /usr/sbin/nginx-1.26.3 /usr/bin/nginx-1.26` 添加软链接避免覆盖 --- #### 3. `--pid-path=/usr/local/nginx/nginx.pid` - **含义**:设置PID文件路径(主进程ID记录)。 - **作用**:用于控制Nginx进程(启动/停止)[^1]。 - **问题**: - 目录不存在导致启动失败 - 权限不足无法写入PID - **解决**: ```bash sudo mkdir -p /usr/local/nginx sudo touch /usr/local/nginx/nginx.pid sudo chmod 644 /usr/local/nginx/nginx.pid ``` --- #### 4. 日志路径参数 - **`--error-log-path`**:错误日志存放位置 - **`--http-log-path`**:HTTP访问日志存放位置 - **作用**:分离日志便于排查问题[^2][^3]。 - **问题**: - 日志文件路径未创建 - 磁盘空间不足导致日志写入失败 - **解决**: ```bash sudo mkdir -p /usr/local/nginx-1.26.3 sudo touch /usr/local/nginx-1.26.3/{error,access}.log sudo chown -R nginx:nginx /usr/local/nginx-1.26.3/ ``` --- #### 5. 依赖库链接参数 - **`--with-pcre=/usr/local/pcre-8.39`** - 作用:链接PCRE库(正则表达式支持,必需`rewrite`模块) - **`--with-zlib=/usr/local/zlib-1.2.11`** - 作用:链接zlib库(HTTP响应压缩) - **问题**: - 路径错误:`configure: error: PCRE library not found` - 版本不兼容 - **解决**: 1. 验证库是否存在:`ls /usr/local/pcre-8.39/include/pcre.h` 2. 编译安装依赖库: ```bash # PCRE安装示例 wget https://sourceforge.net/projects/pcre/files/pcre/8.39/pcre-8.39.tar.gz tar -zxvf pcre-8.39.tar.gz cd pcre-8.39 ./configure && make && sudo make install ``` --- #### 6. `--with-http_stub_status_module` - **作用**:启用状态监控模块,提供实时性能指标(连接数、请求数等)[^2]。 - **配置示例**(需在nginx.conf中添加): ```nginx location /nginx_status { stub_status on; access_log off; allow 127.0.0.1; # 限制访问IP deny all; } ``` - **问题**: - 未配置访问控制导致安全风险 - 访问`http://server/nginx_status`返回404 - **解决**: 检查模块是否编译成功:`nginx -V 2>&1 | grep -o stub_status_module` --- ### 常见综合问题及解决 1. **编译失败** - **原因**:缺失编译依赖(gcc, make) - **解决**:`sudo apt install build-essential libtool` 2. **启动时报权限错误** - **原因**:以非root用户运行时未授权 - **解决**: ```bash sudo setcap 'cap_net_bind_service=+ep' /usr/sbin/nginx-1.26.3 ``` 3. **端口冲突** - **检测**:`ss -tulnp | grep :80` - **解决**:关闭占用进程或修改Nginx监听端口 --- ### 操作流程建议 1. 安装依赖库:PCRE/zlib → 编译Nginx → 配置日志目录权限 2. 启动验证: ```bash sudo /usr/sbin/nginx-1.26.3 -t # 测试配置 sudo /usr/sbin/nginx-1.26.3 # 启动 curl http://localhost/nginx_status # 检查模块 ```
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值