美文网首页
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