美文网首页
Android之Recovery业务(一)-升级过程

Android之Recovery业务(一)-升级过程

作者: 锄禾豆 | 来源:发表于2022-01-24 09:14 被阅读0次

简介

系统升级,相当于两个系统之间通信,他们是怎么实现跨“系统”操作升级?

框架

     Bootloader
         |
         |
      misc分区
        /\ 
       /  \
      /    \
    Main  Recovery

代码列表

frameworks/base/core/java/android/os/RecoverySystem.java
frameworks/base/services/core/java/com/android/server/RecoverySystemService.java

bootable/recovery
注:
7.1

案例分析一:apk调系统接口升级
system部分
1.app调用接口:RecoverySystem.installPackage

1)权限
android.permission.REBOOT
android.permission.RECOVERY

2)分析
installPackage
    public static void installPackage(Context context, File packageFile, boolean processed)
            throws IOException {
        synchronized (sRequestLock) {
            ···
            String filename = packageFile.getCanonicalPath();//升级包路径
            Log.w(TAG, "!!! REBOOTING TO INSTALL " + filename + " !!!");
            ···
            // If the package is on the /data partition, the package needs to
            // be processed (i.e. uncrypt'd). The caller specifies if that has
            // been done in 'processed' parameter.
            if (filename.startsWith("/data/")) {//升级包的根目录一定要在/data下面
                if (processed) {
                    ···
                } else {
                    FileWriter uncryptFile = new FileWriter(UNCRYPT_PACKAGE_FILE);
                    try {
                        uncryptFile.write(filename + "\n");//将升级包路径写入文件/cache/recovery/uncrypt_file
                    } finally {
                        uncryptFile.close();
                    }
                    ···
                }

                // If the package is on the /data partition, use the block map
                // file as the package name instead.
                filename = "@/cache/recovery/block.map";//特别注意,这里的路径为block.map
            }

            final String filenameArg = "--update_package=" + filename + "\n";
            final String localeArg = "--locale=" + Locale.getDefault().toString() + "\n";
            final String securityArg = "--security\n";

            String command = filenameArg + localeArg;//拼接升级指令
            
            if (securityUpdate) {
                command += securityArg;
            }

            RecoverySystem rs = (RecoverySystem) context.getSystemService(
                    Context.RECOVERY_SERVICE);
            if (!rs.setupBcb(command)) {//设置跨系统标签
                throw new IOException("Setup BCB failed");
            }

            // Having set up the BCB (bootloader control block), go ahead and reboot
            PowerManager pm = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
            pm.reboot(PowerManager.REBOOT_RECOVERY_UPDATE);//根据UNCRYPT_PACKAGE_FILE初始化block.map,再重启系统

            throw new IOException("Reboot failed (no permissions?)");
        }
    }

2.system_server处理setupBcb业务

RecoverySystemService.BinderService
setupBcb --> setupOrClearBcb

        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) {
                SystemProperties.set("ctl.start", "setup-bcb");//执行setup-bcb,启动uncrypt进程
            } else {
                SystemProperties.set("ctl.start", "clear-bcb");
            }

            // Connect to the uncrypt service socket.
            LocalSocket socket = connectService();//system_server与uncrypt跨进程通信
            if (socket == null) {
                Slog.e(TAG, "Failed to connect to uncrypt socket");
                return false;
            }

            DataInputStream dis = null;
            DataOutputStream dos = null;
            try {
                dis = new DataInputStream(socket.getInputStream());
                dos = new DataOutputStream(socket.getOutputStream());

                // Send the BCB commands if it's to setup BCB.
                if (isSetup) {
                    dos.writeInt(command.length());//socket写指令长度
                    dos.writeBytes(command);//socket写指令值
                    dos.flush();
                }

                // Read the status from the socket.
                int status = dis.readInt();//读取socket状态

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

                if (status == 100) {//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 {
                IoUtils.closeQuietly(dis);
                IoUtils.closeQuietly(dos);
                IoUtils.closeQuietly(socket);
            }

            return true;
        }

3.uncrypt进程作为socket服务端处理指令。由SystemProperties.set("ctl.start", "setup-bcb")启动
(bcb -- Bootloader Control Block(BCB))

代码路径:
bootable/recovery/uncrypt

1)uncrypt.rc
service setup-bcb /system/bin/uncrypt --setup-bcb  //启动uncrypt
    class main
    socket uncrypt stream 600 system system
    disabled
    oneshot
    
2)uncrypt.cpp --> main
int main(int argc, char** argv) {
    enum { UNCRYPT, SETUP_BCB, CLEAR_BCB } action;
    const char* input_path = nullptr;
    const char* map_file = CACHE_BLOCK_MAP.c_str();

    if (argc == 2 && strcmp(argv[1], "--clear-bcb") == 0) {
        action = CLEAR_BCB;
    } else if (argc == 2 && strcmp(argv[1], "--setup-bcb") == 0) {
        action = SETUP_BCB;//执行setup-bcb
    } else if (argc == 1) {
        action = UNCRYPT;
    } else if (argc == 3) {
        input_path = argv[1];
        map_file = argv[2];
        action = UNCRYPT;
    } else {
        usage(argv[0]);
        return 2;
    }

    if ((fstab = read_fstab()) == nullptr) {
        log_uncrypt_error_code(kUncryptFstabReadError);
        return 1;
    }

    // c3. The socket is created by init when starting the service. uncrypt
    // will use the socket to communicate with its caller.
    unique_fd service_socket(android_get_control_socket(UNCRYPT_SOCKET.c_str()));//生成service_socket对象
    if (!service_socket) {
        ALOGE("failed to open socket \"%s\": %s", UNCRYPT_SOCKET.c_str(), strerror(errno));
        log_uncrypt_error_code(kUncryptSocketOpenError);
        return 1;
    }
    fcntl(service_socket.get(), F_SETFD, FD_CLOEXEC);

    if (listen(service_socket.get(), 1) == -1) {//监听socket
        ALOGE("failed to listen on socket %d: %s", service_socket.get(), strerror(errno));
        log_uncrypt_error_code(kUncryptSocketListenError);
        return 1;
    }

    unique_fd socket_fd(accept4(service_socket.get(), nullptr, nullptr, SOCK_CLOEXEC));//接收socket数据
    if (!socket_fd) {
        ALOGE("failed to accept on socket %d: %s", service_socket.get(), strerror(errno));
        log_uncrypt_error_code(kUncryptSocketAcceptError);
        return 1;
    }

    bool success = false;
    switch (action) {
        case UNCRYPT:
            success = uncrypt_wrapper(input_path, map_file, socket_fd.get());
            break;
        case SETUP_BCB:
            success = setup_bcb(socket_fd.get());//将数据传输到函数setup_bcb种
            break;
        case CLEAR_BCB:
            success = clear_bcb(socket_fd.get());
            break;
        default:  // Should never happen.
            ALOGE("Invalid uncrypt action code: %d", action);
            return 1;
    }

    // c13. Read a 4-byte code from the client before uncrypt exits. This is to
    // ensure the client to receive the last status code before the socket gets
    // destroyed.
    int code;
    if (android::base::ReadFully(socket_fd.get(), &code, 4)) {
        ALOGI("  received %d, exiting now", code);
    } else {
        ALOGE("failed to read the code: %s", strerror(errno));
    }
    return success ? 0 : 1;
}

3)setup_bcb对misc分区做数据处理
static bool setup_bcb(const int socket) {
    // c5. receive message length
    int length;
    if (!android::base::ReadFully(socket, &length, 4)) {
        ALOGE("failed to read the length: %s", strerror(errno));
        return false;
    }
    length = ntohl(length);

    // c7. receive message
    std::string content;
    content.resize(length);
    if (!android::base::ReadFully(socket, &content[0], length)) {//读取socket数据,也及时command
        ALOGE("failed to read the length: %s", strerror(errno));
        return false;
    }
    ALOGI("  received command: [%s] (%zu)", content.c_str(), 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)) {
                ALOGE("failed to read %s: %s", path.c_str(), strerror(errno));
                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)) {//将数据写道misc分区
        ALOGE("failed to set bootloader message: %s", err.c_str());
        write_status_to_socket(-1, socket);
        return false;
    }
    ···
    // c10. send "100" status
    write_status_to_socket(100, socket);
    return true;
}

4)bootloader_message.cpp实现函数write_bootloader_message

bool write_bootloader_message(const std::vector<std::string>& options, std::string* err) {
  bootloader_message boot = {};
  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));//分段写入结构体boot.recovery变量种
    if (s.back() != '\n') {
      strlcat(boot.recovery, "\n", sizeof(boot.recovery));
    }
  }
  return write_bootloader_message(boot, err);
}

提醒:
recovery:可被Main System写入,也可被Recovery服务程序写入。该文件的内容格式为:
recovery\n
<recovery command>\n
<recovery command>
该文件存储的就是一个字符串,必须以recovery\n开头,否则这个字段的所有内容域会被忽略。
“recovery\n”之后的部分,是/cache/recovery/command支持的命令。
可以将其理解为Recovery操作过程中对命令操作的备份。
Recovery对其操作的过程为:先读取BCB然后读取/cache/recovery/command,然后将二者重新写回BCB,这样在进入Main system之前,确保操作被执行。
在操作之后进入Main system之前,Recovery又会清空BCB的command域和recovery域,这样确保重启后不再进入Recovery模式。

5)将数据写入misc分区:bootloader_message.cpp : write_bootloader_message --> write_misc_partition

static bool write_misc_partition(const void* p, size_t size, size_t offset, std::string* err) {
  std::string misc_blk_device = get_misc_blk_device(err);//获取misc分区目录
  //例如:/dev/block/platform/ff0f0000.dwmmc/by-name/misc         /misc               emmc      defaults     defaults
  //即:misc_blk_device --> /dev/block/platform/ff0f0000.dwmmc/by-name/misc
  if (misc_blk_device.empty()) {
    return false;
  }
  android::base::unique_fd fd(open(misc_blk_device.c_str(), O_WRONLY | O_SYNC));
  if (fd.get() == -1) {
    *err = android::base::StringPrintf("failed to open %s: %s", misc_blk_device.c_str(),
                                       strerror(errno));
    return false;
  }
  if (lseek(fd.get(), 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.get(), p, size)) {//将数据写入misc分区
    *err = android::base::StringPrintf("failed to write %s: %s", misc_blk_device.c_str(),
                                       strerror(errno));
    return false;
  }

  // TODO: O_SYNC and fsync duplicates each other?
  if (fsync(fd.get()) == -1) {
    *err = android::base::StringPrintf("failed to fsync %s: %s", misc_blk_device.c_str(),
                                       strerror(errno));
    return false;
  }
  return true;
}

补充:
static std::string get_misc_blk_device(std::string* err) {
  struct fstab* fstab = read_fstab(err);//读分区表
  if (fstab == nullptr) {
    return "";
  }
  fstab_rec* record = fs_mgr_get_entry_for_mount_point(fstab, "/misc");//根据misc获取misc分区的数据结构
  if (record == nullptr) {
    *err = "failed to find /misc partition";
    return "";
  }
  return record->blk_device;
}

4.Powermanager.reboot重启系统

Powermanager.reboot(PowerManager.REBOOT_RECOVERY_UPDATE)
--> PowerManagerService.reboot 
--> ShutdownThread.reboot --> shutdownInner --> beginShutdownSequence --> run

    public void run() {
        ···
        if (mRebootHasProgressBar) {
            ···

            // If it's to reboot to install an update and uncrypt hasn't been
            // done yet, trigger it now.
            uncrypt();//执行uncrypt业务
        }
        ···
        rebootOrShutdown(mContext, mReboot, mReason);//重启
    }
    
    private void uncrypt() {
        Log.i(TAG, "Calling uncrypt and monitoring the progress...");

        final RecoverySystem.ProgressListener progressListener =
                new RecoverySystem.ProgressListener() {
            @Override
            public void onProgress(int status) {
                ···
            }
        };

        final boolean[] done = new boolean[1];
        done[0] = false;
        Thread t = new Thread() {
            @Override
            public void run() {
                RecoverySystem rs = (RecoverySystem) mContext.getSystemService(
                        Context.RECOVERY_SERVICE);
                String filename = null;
                try {
                    filename = FileUtils.readTextFile(RecoverySystem.UNCRYPT_PACKAGE_FILE, 0, null);//利用了UNCRYPT_PACKAGE_FILE
                    rs.processPackage(mContext, new File(filename), progressListener);//1)将数据传递到RecoverySystemService 2)RecoverySystemService传递给uncrypt进程
                } catch (IOException e) {
                    Log.e(TAG, "Error uncrypting file", e);
                }
                done[0] = true;
            }
        };
        t.start();

        try {
            t.join(MAX_UNCRYPT_WAIT_TIME);
        } catch (InterruptedException unused) {
        }
        if (!done[0]) {
            ···
        }
    }

5.总结

system_server(RecoverySystemService)通过socket与uncrypt建立了通信,uncrypt做了如下操作:
1.指令保存在misc分区
2.update.zip所在内存地址被保存在/cache/recovery/block.map
另外,update.zip包的路径需要以/data开头

细节如下:
1)在android7.0中,recovery模式中已经不在挂载data分区,而且data分区可以设置加密,这样更促进了在recovery分区中不挂载data分区,加强了用户的安全性。但是这样就会有问题,当升级包较大时cache分区是放不下的,增大cache分区只会浪费资源,最好的办法还是把它放在data分区下。但是因为加密和不挂载的原因导致在recovery模式下是无法使用升级包的。而uncrypt机制就是解决这个问题的。它的基本原理就是,在正常模式下记录升级包所在的磁盘区块号,而且如果加密了就通过读进行解密,再绕过加密写到存储磁盘上。当记录好了磁盘区块号后,启动到recovery模式后就可以不挂载data分区,直接根据区块号读取升级包中的数据进行升级

2)system_server把update.zip路径保存在文件/cache/recovery/uncrypt_file
uncrypt读取/cache/recovery/uncrypt_file生成/cache/recovery/block.map

3)/cache/recovery/block.map 是update.zip存放的数据地址

recovery部分

提出两个问题进行分析:
1)系统怎么知道将进入的是recovery模式呢?
2)recovery怎么知道update.zip放在哪里呢?

代码路径:
boottable/recovery

1.Bootloader通过根据结构体bootloader_message.recovery的值进行判断
注:bootloader_message的初始化来自于misc分区

int main(int argc, char **argv) {
    ···
    get_args(&argc, &argv);//从misc分区中读取数据,并初始化结构体bootloader_message

    dumpCmdArgs(argc, argv);

    ···
    int arg;
    int option_index;
    while ((arg = getopt_long(argc, argv, "", OPTIONS, &option_index)) != -1) {
        switch (arg) {
        ···
        case 'u': update_package = optarg; break;
        ···
        }
    }

    ···

    if (update_package) {
        ···
        strcpy(updatepath, update_package);
    }
    ···
    int status = INSTALL_SUCCESS;

    if (update_package != NULL) {
        ···
        status = install_package(reallyPath, &should_wipe_cache,
                                 TEMPORARY_INSTALL_FILE, true, retry_count);//更新系统
        if (status == INSTALL_SUCCESS && should_wipe_cache) {
            wipe_cache(false, device);
        }
        ···
    }
    ···
    finish_recovery(send_intent);//重启misc分区数据
    ···
    // Should be unreachable.
    return EXIT_SUCCESS;
}

部分日志如下:
[    0.002458] I:Boot command: boot-recovery
[    0.002475] I:Got arguments from boot message
[    0.004836] === start void dumpCmdArgs(int, char **):296 ===
[    0.004853] argv[0] =  recovery.
[    0.004859] argv[1] =  --update_package=@/cache/recovery/block.map.
[    0.004865] argv[2] =  --locale=zh_CN.

其他

RK的高可靠性业务
BOARD_USES_FULL_RECOVERY_IMAGE

https://blog.csdn.net/guyongqiangx/article/details/71334889

参考学习

https://blog.csdn.net/luzhenrong45/article/details/60968458

相关文章

网友评论

      本文标题:Android之Recovery业务(一)-升级过程

      本文链接:https://www.haomeiwen.com/subject/dxpthrtx.html