Files
android_bootable_recovery/updater/updater.cpp
Tianjie Xu 1536db887f Add UpdaterRuntime class
This class adds a wrapper to the runtime dependent functions. Therefore,
the behavior of update on device stays the same, while simulators can
have their own implementations. Also change the caller side of the
registered updater functions to call these runtime wrappers.

Bug: 131911365
Test: unit tests pass, sideload an update on cuttlefish
Change-Id: Ib3ab67132991d67fc132f27120e4152439d16ac5
2019-05-20 18:03:27 -07:00

186 lines
6.1 KiB
C++

/*
* Copyright (C) 2009 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "updater/updater.h"
#include <string.h>
#include <unistd.h>
#include <string>
#include <android-base/logging.h>
#include <android-base/strings.h>
#include "updater/updater_runtime.h"
Updater::Updater() : Updater(std::make_unique<UpdaterRuntime>(nullptr)) {}
Updater::~Updater() {
if (package_handle_) {
CloseArchive(package_handle_);
}
}
bool Updater::Init(int fd, const std::string_view package_filename, bool is_retry) {
// Set up the pipe for sending commands back to the parent process.
cmd_pipe_.reset(fdopen(fd, "wb"));
if (!cmd_pipe_) {
LOG(ERROR) << "Failed to open the command pipe";
return false;
}
setlinebuf(cmd_pipe_.get());
if (!mapped_package_.MapFile(std::string(package_filename))) {
LOG(ERROR) << "failed to map package " << package_filename;
return false;
}
if (int open_err = OpenArchiveFromMemory(mapped_package_.addr, mapped_package_.length,
std::string(package_filename).c_str(), &package_handle_);
open_err != 0) {
LOG(ERROR) << "failed to open package " << package_filename << ": "
<< ErrorCodeString(open_err);
return false;
}
if (!ReadEntryToString(package_handle_, SCRIPT_NAME, &updater_script_)) {
return false;
}
is_retry_ = is_retry;
return true;
}
bool Updater::RunUpdate() {
CHECK(runtime_);
// Parse the script.
std::unique_ptr<Expr> root;
int error_count = 0;
int error = ParseString(updater_script_, &root, &error_count);
if (error != 0 || error_count > 0) {
LOG(ERROR) << error_count << " parse errors";
return false;
}
// Evaluate the parsed script.
State state(updater_script_, this);
state.is_retry = is_retry_;
bool status = Evaluate(&state, root, &result_);
if (status) {
fprintf(cmd_pipe_.get(), "ui_print script succeeded: result was [%s]\n", result_.c_str());
// Even though the script doesn't abort, still log the cause code if result is empty.
if (result_.empty() && state.cause_code != kNoCause) {
fprintf(cmd_pipe_.get(), "log cause: %d\n", state.cause_code);
}
for (const auto& func : skipped_functions_) {
LOG(WARNING) << "Skipped executing function " << func;
}
return true;
}
ParseAndReportErrorCode(&state);
return false;
}
void Updater::WriteToCommandPipe(const std::string_view message, bool flush) const {
fprintf(cmd_pipe_.get(), "%s\n", std::string(message).c_str());
if (flush) {
fflush(cmd_pipe_.get());
}
}
void Updater::UiPrint(const std::string_view message) const {
// "line1\nline2\n" will be split into 3 tokens: "line1", "line2" and "".
// so skip sending empty strings to ui.
std::vector<std::string> lines = android::base::Split(std::string(message), "\n");
for (const auto& line : lines) {
if (!line.empty()) {
fprintf(cmd_pipe_.get(), "ui_print %s\n", line.c_str());
}
}
// on the updater side, we need to dump the contents to stderr (which has
// been redirected to the log file). because the recovery will only print
// the contents to screen when processing pipe command ui_print.
LOG(INFO) << message;
}
std::string Updater::FindBlockDeviceName(const std::string_view name) const {
return runtime_->FindBlockDeviceName(name);
}
void Updater::ParseAndReportErrorCode(State* state) {
CHECK(state);
if (state->errmsg.empty()) {
LOG(ERROR) << "script aborted (no error message)";
fprintf(cmd_pipe_.get(), "ui_print script aborted (no error message)\n");
} else {
LOG(ERROR) << "script aborted: " << state->errmsg;
const std::vector<std::string> lines = android::base::Split(state->errmsg, "\n");
for (const std::string& line : lines) {
// Parse the error code in abort message.
// Example: "E30: This package is for bullhead devices."
if (!line.empty() && line[0] == 'E') {
if (sscanf(line.c_str(), "E%d: ", &state->error_code) != 1) {
LOG(ERROR) << "Failed to parse error code: [" << line << "]";
}
}
fprintf(cmd_pipe_.get(), "ui_print %s\n", line.c_str());
}
}
// Installation has been aborted. Set the error code to kScriptExecutionFailure unless
// a more specific code has been set in errmsg.
if (state->error_code == kNoError) {
state->error_code = kScriptExecutionFailure;
}
fprintf(cmd_pipe_.get(), "log error: %d\n", state->error_code);
// Cause code should provide additional information about the abort.
if (state->cause_code != kNoCause) {
fprintf(cmd_pipe_.get(), "log cause: %d\n", state->cause_code);
if (state->cause_code == kPatchApplicationFailure) {
LOG(INFO) << "Patch application failed, retry update.";
fprintf(cmd_pipe_.get(), "retry_update\n");
} else if (state->cause_code == kEioFailure) {
LOG(INFO) << "Update failed due to EIO, retry update.";
fprintf(cmd_pipe_.get(), "retry_update\n");
}
}
}
bool Updater::ReadEntryToString(ZipArchiveHandle za, const std::string& entry_name,
std::string* content) {
ZipEntry entry;
int find_err = FindEntry(za, entry_name, &entry);
if (find_err != 0) {
LOG(ERROR) << "failed to find " << entry_name
<< " in the package: " << ErrorCodeString(find_err);
return false;
}
content->resize(entry.uncompressed_length);
int extract_err = ExtractToMemory(za, &entry, reinterpret_cast<uint8_t*>(&content->at(0)),
entry.uncompressed_length);
if (extract_err != 0) {
LOG(ERROR) << "failed to read script from package: " << ErrorCodeString(extract_err);
return false;
}
return true;
}