recovery代码分析(五)

block_image_update

前文提到过,在ota升级中,recovery会新启一个进程来执行update-binary来做实际的升级。而update-binary会执行update-script脚本。update-script脚本会调用block_image_update来进行安装。这里注意一下升级system分区的时候block_image_update调用时传入的参数:

block_image_update("/dev/block/platform/bootdevice/by-name/system", package_extract_file("system.transfer.list"), "system.new.dat", "system.patch.dat")

这里,将升级包里的system.transfer.list,system.new.data,system.patch.data三个文件作为了参数。
从名字可以看出,后面两个肯定是数据文件,那么第一个是什么呢?下面贴出一部分的内容。

4
603251
0
50219
erase 4,713559,720384,721410,753152
move 3dc1d84a782873cb2dc473842f1e849bc8ca1cb5 2,547639,547694 55 2,491341,491396
move a83a6df87107f3cf013a395cc458181fa6255339 2,547779,547785 6 2,491483,491489
move 613183b4997dac1ecd1928b1ad44103eb4f264bd 2,524229,524254 25 2,477729,477754
move e47da16b6e23261cfa538a341c90d11a11c18695 2,511591,513147 1556 2,455296,456852
move 03f9748aa6250d1b9c2741a4eaf07d83d260a686 2,573512,573530 18 2,58393,58411
move e26740c0d800fe01e1a0c9ace5f441fb65202f6a 2,572459,572685 226 2,57339,57565
move 2700d4eac598c1e4067f3b7723dacec9a61c40c6 2,571299,571595 296 2,56179,56475
move 4512393adc458d48e58361398a018c1eb77de5c1 2,571777,571848 71 2,56657,56728
move 42e33ce46890261ee5b54092121eb28d94ce6f72 2,514891,518955 4064 2,459259,463323
move c59caaf4ed998521668a3560359ad2ef98876e13 2,513407,513470 63 2,457234,457297
move 8fca6b29d448cdde56c1c2c67b21ced0a79cc2b5 2,579954,579958 4 2,64810,64814
move 82592720cbd6e9edf1f164b43fd169c35770bab4 2,571885,571927 42 2,56765,56807
move 0fe9abbc8de5ccfa5ba03a46d0751d0513534c12 2,547837,547849 12 2,524795,524807
move 2ab628d501f76ea02b9f6ce9c234a8079d722125 2,524146,524184 38 2,475499,475537
move 8a3f615a3d9841289c1ee8e03c41ded61d163639 2,523938,524136 198 2,475291,475489
move b19222233ffcc248f70f1ea2493ca973f6107dc7 2,572149,572189 40 2,57029,57069
move 1ab1150a4a3cb0ee60d114436c462c9a15e6f7f6 2,522837,523360 523 2,467189,467712
move b90562fd6a4972143f56d21df98f5f02e2dc2d59 2,513346,513382 36 2,457173,457209

这个就是升级具体步骤了。当然,除了move命令以外,后面还有一些其他的命令,读者可以去脚本里面探索一下。

updater.cpp

首先,我们来看一下updater.cpp,也就是update-binary的main函数:

int main(int argc, char **argv) {
	
	...
    
	// Timothy:注册函数
	// Configure edify's functions.
    RegisterBuiltins();
    RegisterInstallFunctions();
    RegisterBlockImageFunctions();
    RegisterDeviceExtensions();

	...
	
	// Timothy:执行脚本
	std::string result;
    bool status = Evaluate(&state, root, &result);
	
	...
	
}

在main函数里面,首先,将binary-script脚本里的函数和c++里的函数进行了映射,然后执行脚本。我们看一下其中的RegisterBlockImageFunctions函数:

void RegisterBlockImageFunctions() {
    RegisterFunction("block_image_verify", BlockImageVerifyFn);
    RegisterFunction("block_image_update", BlockImageUpdateFn);
    RegisterFunction("block_image_recover", BlockImageRecoverFn);
    RegisterFunction("check_first_block", CheckFirstBlockFn);
    RegisterFunction("range_sha1", RangeSha1Fn);
}

从这里,我们看到了前面提到的block_image_update函数。现在看一下调用block_image_update的时候发生了什么:

Value *BlockImageUpdateFn(const char *name, State *state,
                          const std::vector<std::unique_ptr<Expr>> &argv) {
    const Command commands[] = {
        { "bsdiff",     PerformCommandDiff  },
        { "erase",      PerformCommandErase },
        { "free",       PerformCommandFree  },
        { "imgdiff",    PerformCommandDiff  },
        { "move",       PerformCommandMove  },
        { "new",        PerformCommandNew   },
        { "stash",      PerformCommandStash },
        { "zero",       PerformCommandZero  }
    };

    return PerformBlockImageUpdate(name, state, argv, commands,
                                   sizeof(commands) / sizeof(commands[0]), false);
}

这里,首先就是注册各种命令的实现,然后调用PerformBlockImageUpdate函数启动升级:

PerformBlockImageUpdate

static Value *PerformBlockImageUpdate(const char *name, State *state,
                                      const std::vector<std::unique_ptr<Expr>> &argv,
                                      const Command *commands, size_t cmdcount, bool dryrun) {
	
	...

    // First line in transfer list is the version number.
    if (!android::base::ParseInt(lines[0], &params.version, 3, 4)) {
        LOG(ERROR) << "unexpected transfer list version [" << lines[0] << "]";
        return StringValue("");
    }

    LOG(INFO) << "blockimg version is " << params.version;

    // Second line in transfer list is the total number of blocks we expect to write.
    size_t total_blocks;
    if (!android::base::ParseUint(lines[1], &total_blocks)) {
        ErrorAbort(state, kArgsParsingFailure, "unexpected block count [%s]\n", lines[1].c_str());
        return StringValue("");
    }

    if (total_blocks == 0) {
        return StringValue("t");
    }

    size_t start = 2;
    if (lines.size() < 4) {
        ErrorAbort(state, kArgsParsingFailure, "too few lines in the transfer list [%zu]\n",
                   lines.size());
        return StringValue("");
    }

    // Third line is how many stash entries are needed simultaneously.
    LOG(INFO) << "maximum stash entries " << lines[2];

    // Fourth line is the maximum number of blocks that will be stashed simultaneously
    size_t stash_max_blocks;
    if (!android::base::ParseUint(lines[3], &stash_max_blocks)) {
        ErrorAbort(state, kArgsParsingFailure, "unexpected maximum stash blocks [%s]\n",
                   lines[3].c_str());
        return StringValue("");
    }

    ...
	
}

这里解释了list文件的头四行是什么意思:

  1. 版本号;
  2. 待写入的blocks总数;
  3. 预计需要多少stash的实体;
  4. 预计将进行stash操作的最大的blocks数量;

PerformCommandMove

以move命令为例,分析一下list文件中命令的实现。

static int PerformCommandMove(CommandParameters &params) {
    size_t blocks = 0;
    bool overlap = false;
    RangeSet tgt;
    // Timothy:把source和stash的内容组合后,存储到params.buffer中。
    int status = LoadSrcTgtVersion3(params, tgt, &blocks, true, &overlap);

    if (status == -1) {
        LOG(ERROR) << "failed to read blocks for move";
        return -1;
    }

    if (status == 0) {
        params.foundwrites = true;
    } else if (params.foundwrites) {
        LOG(WARNING) << "warning: commands executed out of order [" << params.cmdname << "]";
    }

    // Timothy:因为这个函数在verify和install阶段都会调用,所以用params.canwrite区分两个阶段。verify阶段为false,install阶段为true。
    if (params.canwrite) {
        if (status == 0) {
            LOG(INFO) << "  moving " << blocks << " blocks";
            // Timothy:将存储在params.buffer中的source写入target。
            if (WriteBlocks(tgt, params.buffer, params.fd) == -1) {
                return -1;
            }
        } else { // Timothy:LoadSrcTgtVersion3()函数返回值为1的情况,即本次升级为resume,上次升级的时候已经对这些blocks升级过了。
            LOG(INFO) << "skipping " << blocks << " already moved blocks";
        }
    }

    if (!params.freestash.empty()) {
        FreeStash(params.stashbase, params.freestash);
        params.freestash.clear();
    }

    params.written += tgt.blocks();

    return 0;
}

这个函数不是很长,首先,会载入source和target,如果载入成功,就把source的blocks写入target的blocks中,然后进行一些清理。在这个函数里面,写blocks的步骤比较简单,复杂的是啥呢,是载入的步骤。下面直接看LoadSrcTgtVersion3()函数的代码,注意注释中“Timothy”开头的部分。

// Timothy:载入source和target
// 从注释里看,所有system.transfer.list里的命令有如下三种形式的参数:
// <tgt_range> <src_block_count> <src_range>
//    (loads data from source image only)
// <tgt_range> <src_block_count> - <[stash_id:stash_range] ...>
//    (loads data from stashes only)
// <tgt_range> <src_block_count> <src_range> <src_loc> <[stash_id:stash_range] ...>
//    (loads data from both source image and stashes)
// 这个函数里面的onehas参数为true时,source和target的hash值是一样的,为false时,不一样。
// 函数返回值为-1,表示无法载入必须的block或者内容和hash值不符,命令无法进行。
// 函数返回值为1,表示经验证,该命令要升级的block已经完成了升级。也就是这次升级是retry,而当前block在上次升级的时候已经完成了升级。
// 函数返回值为0,表示block的hash值验证通过,可以升级。
/**
 * Do a source/target load for move/bsdiff/imgdiff in version 3.
 *
 * We expect to parse the remainder of the parameter tokens as one of:
 *
 *    <tgt_range> <src_block_count> <src_range>
 *        (loads data from source image only)
 *
 *    <tgt_range> <src_block_count> - <[stash_id:stash_range] ...>
 *        (loads data from stashes only)
 *
 *    <tgt_range> <src_block_count> <src_range> <src_loc> <[stash_id:stash_range] ...>
 *        (loads data from both source image and stashes)
 *
 * 'onehash' tells whether to expect separate source and targe block hashes, or if they are both the
 * same and only one hash should be expected. params.isunresumable will be set to true if block
 * verification fails in a way that the update cannot be resumed anymore.
 *
 * If the function is unable to load the necessary blocks or their contents don't match the hashes,
 * the return value is -1 and the command should be aborted.
 *
 * If the return value is 1, the command has already been completed according to the contents of the
 * target blocks, and should not be performed again.
 *
 * If the return value is 0, source blocks have expected content and the command can be performed.
 */
static int LoadSrcTgtVersion3(CommandParameters &params, RangeSet &tgt, size_t *src_blocks,
                              bool onehash, bool *overlap) {
    CHECK(src_blocks != nullptr);
    CHECK(overlap != nullptr);

    // Timothy:从参数里面解析出source和target的hash值。
    if (params.cpos >= params.tokens.size()) {
        LOG(ERROR) << "missing source hash";
        return -1;
    }

    std::string srchash = params.tokens[params.cpos++];
    std::string tgthash;

    if (onehash) {
        tgthash = srchash;
    } else {
        if (params.cpos >= params.tokens.size()) {
            LOG(ERROR) << "missing target hash";
            return -1;
        }
        tgthash = params.tokens[params.cpos++];
    }

    // At least it needs to provide three parameters: <tgt_range>, <src_block_count> and
    // "-"/<src_range>.
    if (params.cpos + 2 >= params.tokens.size()) {
        LOG(ERROR) << "invalid parameters";
        return -1;
    }

    // Timothy:解析出tgt_range字段。
    // <tgt_range>
    tgt = RangeSet::Parse(params.tokens[params.cpos++]);

    // Timothy:BLOCKSIZE是4096。如果读不到target的指定block的内容,返回-1,命令无法进行。
    std::vector<uint8_t> tgtbuffer(tgt.blocks() * BLOCKSIZE);
    if (ReadBlocks(tgt, tgtbuffer, params.fd) == -1) {
        return -1;
    }

    // Timothy:如果target的block的hash值和指定的hash值相同,说明该block在上次升级的时候已经完成了升级,返回1。
    // Return now if target blocks already have expected content.
    if (VerifyBlocks(tgthash, tgtbuffer, tgt.blocks(), false) == 0) {
        return 1;
    }

    // Timothy:把source和stash的内容组合后,存储到params.buffer中。
    // Load source blocks.
    if (LoadSourceBlocks(params, tgt, src_blocks, overlap) == -1) {
        return -1;
    }

    // Timothy:计算params.buffer的hash值,并和命令中的source hash比较。
    if (VerifyBlocks(srchash, params.buffer, *src_blocks, true) == 0) {
        // If source and target blocks overlap, stash the source blocks so we can
        // resume from possible write errors. In verify mode, we can skip stashing
        // because the source blocks won't be overwritten.
        // Timothy:如果source和target有重叠,就把source的内容先写到/cache分区。
        if (*overlap && params.canwrite) {
            LOG(INFO) << "stashing " << *src_blocks << " overlapping blocks to " << srchash;

            bool stash_exists = false;
            if (WriteStash(params.stashbase, srchash, *src_blocks, params.buffer, true,
                           &stash_exists) != 0) {
                LOG(ERROR) << "failed to stash overlapping source blocks";
                return -1;
            }

            params.stashed += *src_blocks;
            // Can be deleted when the write has completed.
            if (!stash_exists) {
                params.freestash = srchash;
            }
        }

        // Source blocks have expected content, command can proceed.
        return 0;
    }

    // Timothy:如果source和target有重叠,而且,验证source的hash值失败了,那么就尝试从cache分区里面恢复。
    // 因为有可能上次更新的时候,将source复制了一份到cache。
    if (*overlap && LoadStash(params, srchash, true, nullptr, params.buffer, true) == 0) {
        // Overlapping source blocks were previously stashed, command can proceed. We are recovering
        // from an interrupted command, so we don't know if the stash can safely be deleted after this
        // command.
        return 0;
    }

    // Valid source data not available, update cannot be resumed.
    LOG(ERROR) << "partition has unexpected contents";
    PrintHashForCorruptedSourceBlocks(params, params.buffer);

    params.isunresumable = true;

    return -1;
}
  • 3
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值