Android OTA 升级之四:进入根文件系统

作者: 宋立新

Emailzjujoe@yahoo.com

前言

bootloader 进入Recovery 模式后,首先也是运行Linux内核,该内核跟普通模式没有区别(减轻了BSP开发者的任务)。区别从执行文件系统开始。 Recovery 模式的细节就隐藏在其根文件系统中。

下面,我们就看看进入Recovery 根文件系统都干些啥。

init.rc

和正常启动一样,内核进入文件系统会执行/init, init 的配置文件就是 /init.rc, 前面文章讲过,这个文件来自:bootable/recovery/etc/init.rc,下面,我们看看它的内容。

 1 
2 on init
 3 export PATH /sbin
 4 export ANDROID_ROOT /system
 5 export ANDROID_DATA /data
 6 export EXTERNAL_STORAGE /sdcard
 7 
8 symlink /system/etc /etc
 9 
10 mkdir /sdcard
 11 mkdir /system
 12 mkdir /data
 13 mkdir /cache
 14 mount /tmp /tmp tmpfs
 15 
16 on boot
 17 
18 ifup lo
 19 hostname localhost
 20 domainname localdomain
 21 
22 class_start default
 23 
24 
25 service recovery /sbin/recovery
 26 
27 service adbd /sbin/adbd recovery
 28 disabled
 29 
30 on property:persist.service.adb.enable=1
 31 start adbd
 32 
33 on property:persist.service.adb.enable=0
 34 stop adbd

可以看到,它很非常简单:

1) 设置几个环境变量。备用。

2) 建立 etc 链接。

3) 造几个目录。备用。

4) Mount /tmp 目录为内存文件系统 tmpfs,后面会用到。

5) Trival 设置,不必关心。

6) 启动 recovery主程序。

7) 如果是eng模式(此时persist.service.adb.enable),启动adb

当然,init主程序还会装载属性配置文件 /default.prop, 它包含了很多系统属性设置,比如,ro.build.* 等等。

很明显,这里最重要的就是recovery主程序,下面,我们分析它。

先看一段注释

Recovery.c 中,作者写了一段注释,对我们理解recovery的实现很有帮助,下面看一下:(我就不翻译了)

89 /*
 90 * The recovery tool communicates with the main system through /cache files.
 91 * /cache/recovery/command - INPUT - command line for tool, one arg per line
 92 * /cache/recovery/log - OUTPUT - combined log file from recovery run(s)
 93 * /cache/recovery/intent - OUTPUT - intent that was passed in
 94 *
 95 * The arguments which may be supplied in the recovery.command file:
 96 * --send_intent=anystring - write the text out to recovery.intent
 97 * --update_package=root:path - verify install an OTA package file
 98 * --wipe_data - erase user data (and cache), then reboot
 99 * --wipe_cache - wipe cache (but not user data), then reboot
100 * --set_encrypted_filesystem=on|off - enables / diasables encrypted fs
101 *
102 * After completing, we remove /cache/recovery/command and reboot.
103 * Arguments may also be supplied in the bootloader control block (BCB).
104 * These important scenarios must be safely restartable at any point:
105 *
106 * FACTORY RESET
107 * 1. user selects "factory reset"
108 * 2. main system writes "--wipe_data" to /cache/recovery/command
109 * 3. main system reboots into recovery
110 * 4. get_args() writes BCB with "boot-recovery" and "--wipe_data"
111 * -- after this, rebooting will restart the erase --
112 * 5. erase_root() reformats /data
113 * 6. erase_root() reformats /cache
114 * 7. finish_recovery() erases BCB
115 * -- after this, rebooting will restart the main system --
116 * 8. main() calls reboot() to boot main system
117 *
118 * OTA INSTALL
119 * 1. main system downloads OTA package to /cache/some-filename.zip
120 * 2. main system writes "--update_package=CACHE:some-filename.zip"
121 * 3. main system reboots into recovery
122 * 4. get_args() writes BCB with "boot-recovery" and "--update_package=..."
123 * -- after this, rebooting will attempt to reinstall the update --
124 * 5. install_package() attempts to install the update
125 * NOTE: the package install must itself be restartable from any point
126 * 6. finish_recovery() erases BCB
127 * -- after this, rebooting will (try to) restart the main system --
128 * 7. ** if install failed **
129 * 7a. prompt_and_wait() shows an error icon and waits for the user
130 * 7b; the user reboots (pulling the battery, etc) into the main system
131 * 8. main() calls maybe_install_firmware_update()
132 * ** if the update contained radio/hboot firmware **:
133 * 8a. m_i_f_u() writes BCB with "boot-recovery" and "--wipe_cache"
134 * -- after this, rebooting will reformat cache & restart main system --
135 * 8b. m_i_f_u() writes firmware image into raw cache partition
136 * 8c. m_i_f_u() writes BCB with "update-radio/hboot" and "--wipe_cache"
137 * -- after this, rebooting will attempt to reinstall firmware --
138 * 8d. bootloader tries to flash firmware
139 * 8e. bootloader writes BCB with "boot-recovery" (keeping "--wipe_cache")
140 * -- after this, rebooting will reformat cache & restart main system --
141 * 8f. erase_root() reformats /cache
142 * 8g. finish_recovery() erases BCB
143 * -- after this, rebooting will (try to) restart the main system --
144 * 9. main() calls reboot() to boot main system
145 *
146 * ENCRYPTED FILE SYSTEMS ENABLE/DISABLE
147 * 1. user selects "enable encrypted file systems"
148 * 2. main system writes "--set_encrypted_filesystem=on|off" to
149 * /cache/recovery/command
150 * 3. main system reboots into recovery
151 * 4. get_args() writes BCB with "boot-recovery" and
152 * "--set_encrypted_filesystems=on|off"
153 * -- after this, rebooting will restart the transition --
154 * 5. read_encrypted_fs_info() retrieves encrypted file systems settings from /data
155 * Settings include: property to specify the Encrypted FS istatus and
156 * FS encryption key if enabled (not yet implemented)
157 * 6. erase_root() reformats /data
158 * 7. erase_root() reformats /cache
159 * 8. restore_encrypted_fs_info() writes required encrypted file systems settings to /data
160 * Settings include: property to specify the Encrypted FS status and
161 * FS encryption key if enabled (not yet implemented)
162 * 9. finish_recovery() erases BCB
163 * -- after this, rebooting will restart the main system --
164 * 10. main() calls reboot() to boot main system
165 */

recovery 主程序

559 int
560 main(int argc, char **argv)
561 {
562 time_t start = time(NULL);
563 
564 // If these fail, there's not really anywhere to complain...
565 freopen(TEMPORARY_LOG_FILE, "a", stdout); setbuf(stdout, NULL);
566 freopen(TEMPORARY_LOG_FILE, "a", stderr); setbuf(stderr, NULL);
567 fprintf(stderr, "Starting recovery on %s", ctime(&start));
568 

将标准输出和标准错误输出重定位到"/tmp/recovery.log",如果是eng模式,就可以通过adb pull /tmp/recovery.log, 看到当前的log信息,这为我们提供了有效的调试手段。后面还会看到,recovery模式运行完毕后,会将其拷贝到cache分区,以便后续分析。

569 ui_init();
 Recovery 使用了一个简单的基于framebufferui系统,叫miniui,这里,进行了简单的初始化(主要是图形部分以及事件部分),并启动了一个 event 线程用于响应用户按键。
570 get_args(&argc, &argv);

misc 分区以及 CACHE:recovery/command 文件中读入参数,写入到argc, argv ,并且,如果有必要,回写入misc分区。这样,如果recovery没有操作成功(比如,升级还没有结束,就拔电池),系统会一直进入recovery模式。提醒用户继续升级,直到成功。

572 int previous_runs = 0;
573 const char *send_intent = NULL;
574 const char *update_package = NULL;
575 int wipe_data = 0, wipe_cache = 0;
576 
577 int arg;
578 while ((arg = getopt_long(argc, argv, "", OPTIONS, NULL)) != -1) {
579 switch (arg) {
580 case 'p': previous_runs = atoi(optarg); break;
581 case 's': send_intent = optarg; break;
582 case 'u': update_package = optarg; break;
583 case 'w': wipe_data = wipe_cache = 1; break;
584 case 'c': wipe_cache = 1; break;
585 case '?':
586 LOGE("Invalid command argument/n");
587 continue;
588 }
589 }
590 
解析参数,p: previous_runs没有用到,其它含义见前面注释。
591 device_recovery_start();
这个函数没干什么。看名字,它給设备制造商提供了一个调用机会,可写入设备相关初始化代码。
592 
593 fprintf(stderr, "Command:");
594 for (arg = 0; arg < argc; arg++) {
595 fprintf(stderr, " /"%s/"", argv[arg]);
596 }
597 fprintf(stderr, "/n/n");
598 
打印出命令,比如,正常启动进入recovery模式,会打印:Command: "/sbin/recovery"
599 property_list(print_property, NULL);
600 fprintf(stderr, "/n");
601 
打印出所有的系统属性(from default.prop)到log文件。
602 int status = INSTALL_SUCCESS;
603 
604 if (update_package != NULL) {
605 status = install_package(update_package);
606 if (status != INSTALL_SUCCESS) ui_print("Installation aborted./n");
607 } else if (wipe_data) {
608 if (device_wipe_data()) status = INSTALL_ERROR;
609 if (erase_root("DATA:")) status = INSTALL_ERROR;
610 if (wipe_cache && erase_root("CACHE:")) status = INSTALL_ERROR;
611 if (status != INSTALL_SUCCESS) ui_print("Data wipe failed./n");
612 } else if (wipe_cache) {
613 if (wipe_cache && erase_root("CACHE:")) status = INSTALL_ERROR;
614 if (status != INSTALL_SUCCESS) ui_print("Cache wipe failed./n");
615 } else {
616 status = INSTALL_ERROR; // No command specified
617 }
根据用户提供参数,调用各项功能,比如,安装一个升级包,擦除cache分区, 擦除user data分区,install_package比较复杂,后面专门分析,其它都很简单。忽略。
618 
619 if (status != INSTALL_SUCCESS) ui_set_background(BACKGROUND_ICON_ERROR);
622 if (status != INSTALL_SUCCESS) prompt_and_wait();
如果前面已经做了某项操作并且成功,则进入重启流程。否则,等待用户选择具体操作。
而用户可选操作为: reboot, 安装update.zip,除cache分区, 擦除user data分区,如前所述,只有安装package 比较复杂,其它简单。
623 
624 // Otherwise, get ready to boot the main system...
625 finish_recovery(send_intent);
它的功能如下:
1)将前面定义的intent字符串写入(如果有的话):CACHE:recovery/command
2)将 /tmp/recovery.log 复制到 "CACHE:recovery/log";
3)清空 misc 分区,这样重启就不会进入recovery模式
4)删除command 文件:CACHE:recovery/command;
626 ui_print("Rebooting.../n");
627 sync();
628 reboot(RB_AUTOBOOT);
629 return EXIT_SUCCESS;
630 }

重启。

下面我们分析核心函数 install_package

install_package

289 int
290 install_package(const char *root_path)
291 {
292 ui_set_background(BACKGROUND_ICON_INSTALLING);
294 ui_print("Finding update package.../n");
295 LOGI("Finding update package.../n");
296 ui_show_indeterminate_progress();
297 LOGI("Update location: %s/n", root_path);
298 
更新 UI 显示
299 if (ensure_root_path_mounted(root_path) != 0) {
300 LOGE("Can't mount %s/n", root_path);
301 reset_mark_block();
302 return INSTALL_CORRUPT;
303 }
304 
确保升级包所在分区已经mount,通常为 cache 分区或者 SD 分区
305 char path[PATH_MAX] = "";
306 if (translate_root_path(root_path, path, sizeof(path)) == NULL) {
307 LOGE("Bad path %s/n", root_path);
308 reset_mark_block();
309 return INSTALL_CORRUPT;
310 }
将根分区转化为具体分区信息.这些信息来自:全局数组:g_roots
313 ui_print("Opening update package.../n");
314 LOGI("Opening update package.../n");
315 LOGI("Update file path: %s/n", path);
316 
317 int numKeys;
318 RSAPublicKey* loadedKeys = load_keys(PUBLIC_KEYS_FILE, &numKeys);
319 if (loadedKeys == NULL) {
320 LOGE("Failed to load keys/n");
321 reset_mark_block();
322 return INSTALL_CORRUPT;
323 }
324 LOGI("%d key(s) loaded from %s/n", numKeys, PUBLIC_KEYS_FILE);
/res/keys中装载公钥。
326 // Give verification half the progress bar...
328 ui_print("Verifying update package.../n");
329 LOGI("Verifying update package.../n");
330 ui_show_progress(
331 VERIFICATION_PROGRESS_FRACTION,
332 VERIFICATION_PROGRESS_TIME);
333 
334 int err;
335 err = verify_file(path, loadedKeys, numKeys);
336 free(loadedKeys);
337 LOGI("verify_file returned %d/n", err);
338 if (err != VERIFY_SUCCESS) {
339 LOGE("signature verification failed/n");
340 reset_mark_block();
341 return INSTALL_CORRUPT;
342 }
根据公钥验证升级包verify_file的注释说的很明白:
 // Look for an RSA signature embedded in the .ZIP file comment given
 // the path to the zip. Verify it matches one of the given public
 // keys.
344 /* Try to open the package.
345 */
346 ZipArchive zip;
347 err = mzOpenZipArchive(path, &zip);
348 if (err != 0) {
349 LOGE("Can't open %s/n(%s)/n", path, err != -1 ? strerror(err) : "bad");
350 reset_mark_block();
351 return INSTALL_CORRUPT;
352 }
打开升级包,将相关信息存到ZuoArchive数据机构中,便于后面处理。
354 /* Verify and install the contents of the package.
355 */
356 int status = handle_update_package(path, &zip);
处理函数,我们后面继续分析。
357 mzCloseZipArchive(&zip);
358 return status;
359 }
关闭zip包,结束处理。

handle_update_package

204 static int
205 handle_update_package(const char *path, ZipArchive *zip)
206 {
207 // Update should take the rest of the progress bar.
208 ui_print("Installing update.../n");
209 
210 int result = try_update_binary(path, zip);
211 register_package_root(NULL, NULL); // Unregister package root
212 return result;
213 }

它主要调用函数try_update_binary完成功能。

try_update_binary

84 // If the package contains an update binary, extract it and run it.

85 static int

86 try_update_binary(const char *path, ZipArchive *zip) {

87 const ZipEntry* binary_entry =

88 mzFindZipEntry(zip, ASSUMED_UPDATE_BINARY_NAME);

89 if (binary_entry == NULL) {

90 return INSTALL_CORRUPT;

91 }

92

93 char* binary = "/tmp/update_binary";

94 unlink(binary);

95 int fd = creat(binary, 0755);

96 if (fd < 0) {

97 LOGE("Can't make %s/n", binary);

98 return 1;

99 }

100 bool ok = mzExtractZipEntryToFile(zip, binary_entry, fd);

101 close(fd);

102

103 if (!ok) {

104 LOGE("Can't copy %s/n", ASSUMED_UPDATE_BINARY_NAME);

105 return 1;

106 }

将升级包内文件META-INF/com/google/android/update-binary 复制为/tmp/update_binary

108 int pipefd[2];

109 pipe(pipefd);

110

111 // When executing the update binary contained in the package, the

112 // arguments passed are:

113 //

114 // - the version number for this interface

115 //

116 // - an fd to which the program can write in order to update the

117 // progress bar. The program can write single-line commands:

118 //

119 // progress <frac> <secs>

120 // fill up the next <frac> part of of the progress bar

121 // over <secs> seconds. If <secs> is zero, use

122 // set_progress commands to manually control the

123 // progress of this segment of the bar

124 //

125 // set_progress <frac>

126 // <frac> should be between 0.0 and 1.0; sets the

127 // progress bar within the segment defined by the most

128 // recent progress command.

129 //

130 // firmware <"hboot"|"radio"> <filename>

131 // arrange to install the contents of <filename> in the

132 // given partition on reboot.

133 //

134 // (API v2: <filename> may start with "PACKAGE:" to

135 // indicate taking a file from the OTA package.)

136 //

137 // (API v3: this command no longer exists.)

138 //

139 // ui_print <string>

140 // display <string> on the screen.

141 //

142 // - the name of the package zip file.

143 //

144

注意看这段注释,它解释了以下代码的行为。结合代码,可知:

1) 将会创建新的进程,执行:/tmp/update_binary

2) 同时,会给该进程传入一些参数,其中最重要的就是一个管道fd,供新进程与原进程通信。

3) 新进程诞生后,原进程就变成了一个服务进程,它提供若干UI更新服务:

a) progress

b) set_progress

c) ui_print

这样,新进程就可以通过老进程的UI系统完成显示任务。而其他功能就靠它自己了。

145 char** args = malloc(sizeof(char*) * 5);

146 args[0] = binary;

147 args[1] = EXPAND(RECOVERY_API_VERSION); // defined in Android.mk

148 args[2] = malloc(10);

149 sprintf(args[2], "%d", pipefd[1]);

150 args[3] = (char*)path;

151 args[4] = NULL;

152

153 pid_t pid = fork();

154 if (pid == 0) {

155 close(pipefd[0]);

156 execv(binary, args);

157 fprintf(stderr, "E:Can't run %s (%s)/n", binary, strerror(errno));

158 _exit(-1);

159 }

160 close(pipefd[1]);

161

162 char buffer[1024];

163 FILE* from_child = fdopen(pipefd[0], "r");

164 while (fgets(buffer, sizeof(buffer), from_child) != NULL) {

165 char* command = strtok(buffer, " /n");

166 if (command == NULL) {

167 continue;

168 } else if (strcmp(command, "progress") == 0) {

169 char* fraction_s = strtok(NULL, " /n");

170 char* seconds_s = strtok(NULL, " /n");

171

172 float fraction = strtof(fraction_s, NULL);

173 int seconds = strtol(seconds_s, NULL, 10);

174

175 ui_show_progress(fraction * (1-VERIFICATION_PROGRESS_FRACTION),

176 seconds);

177 } else if (strcmp(command, "set_progress") == 0) {

178 char* fraction_s = strtok(NULL, " /n");

179 float fraction = strtof(fraction_s, NULL);

180 ui_set_progress(fraction);

181 } else if (strcmp(command, "ui_print") == 0) {

182 char* str = strtok(NULL, "/n");

183 if (str) {

184 ui_print(str);

185 } else {

186 ui_print("/n");

187 }

188 } else {

189 LOGE("unknown command [%s]/n", command);

190 }

191 }

192 fclose(from_child);

193

194 int status;

195 waitpid(pid, &status, 0);

196 if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) {

197 LOGE("Error in %s/n(Status %d)/n", path, WEXITSTATUS(status));

198 return INSTALL_ERROR;

199 }

200

201 return INSTALL_SUCCESS;

202 }

这样,我们又回到了升级包中的文件:META-INF/com/google/android/update-binary,下回继续研究。

更多相关文章

  1. Android高手应该精通哪些内容?(转)
  2. Android——使用AIDL实现进程间传递对象案例
  3. 使用观察者模式来实现webview的设置
  4. Android(安卓)Process 详解
  5. [Android]《Android艺术开发探索》第一章读书笔记
  6. 活动的四种启动模式
  7. Android(安卓)8.1 关机充电动画(二)Uboot模式
  8. Android(安卓)系统学习路线
  9. Android(安卓)进程和线程(二)

随机推荐

  1. Android NDK r4 windows 环境中的安装
  2. Android 系统架构了解学习
  3. android recovery mode
  4. IDEA搭建Android wear开发环境,Android we
  5. Android 创建与解析XML(一)—— 概述
  6. Android最简单播放GIF动画方法
  7. android内存泄露分析
  8. Android(Java):adb与linux命令
  9. Android路线图
  10. 如何从Android so中提取URL