8d038450d7
* From android-11, recovery warns the user about the danger of rebooting after a failed update, but if we didn't even start the install procedure it doesn't really make much sense. Change-Id: Ifaf5eae9fe6e1d915a035e2570c22260de59f99b
395 lines
15 KiB
C++
395 lines
15 KiB
C++
/*
|
|
* Copyright (C) 2012 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 "install/adb_install.h"
|
|
|
|
#include <errno.h>
|
|
#include <fcntl.h>
|
|
#include <signal.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <sys/epoll.h>
|
|
#include <sys/socket.h>
|
|
#include <sys/stat.h>
|
|
#include <sys/types.h>
|
|
#include <sys/wait.h>
|
|
#include <unistd.h>
|
|
|
|
#include <atomic>
|
|
#include <functional>
|
|
#include <map>
|
|
#include <utility>
|
|
#include <vector>
|
|
|
|
#include <android-base/file.h>
|
|
#include <android-base/logging.h>
|
|
#include <android-base/memory.h>
|
|
#include <android-base/properties.h>
|
|
#include <android-base/strings.h>
|
|
#include <android-base/unique_fd.h>
|
|
|
|
#include "fuse_sideload.h"
|
|
#include "install/install.h"
|
|
#include "install/wipe_data.h"
|
|
#include "minadbd/types.h"
|
|
#include "otautil/sysutil.h"
|
|
#include "recovery_ui/device.h"
|
|
#include "recovery_ui/ui.h"
|
|
|
|
// A CommandFunction returns a pair of (result, should_continue), which indicates the command
|
|
// execution result and whether it should proceed to the next iteration. The execution result will
|
|
// always be sent to the minadbd side.
|
|
using CommandFunction = std::function<std::pair<bool, bool>()>;
|
|
|
|
static bool SetUsbConfig(const std::string& state) {
|
|
android::base::SetProperty("sys.usb.config", state);
|
|
return android::base::WaitForProperty("sys.usb.state", state);
|
|
}
|
|
|
|
// Parses the minadbd command in |message|; returns MinadbdCommand::kError upon errors.
|
|
static MinadbdCommand ParseMinadbdCommand(const std::string& message) {
|
|
if (!android::base::StartsWith(message, kMinadbdCommandPrefix)) {
|
|
LOG(ERROR) << "Failed to parse command in message " << message;
|
|
return MinadbdCommand::kError;
|
|
}
|
|
|
|
auto cmd_code_string = message.substr(strlen(kMinadbdCommandPrefix));
|
|
auto cmd_code = android::base::get_unaligned<uint32_t>(cmd_code_string.c_str());
|
|
if (cmd_code >= static_cast<uint32_t>(MinadbdCommand::kError)) {
|
|
LOG(ERROR) << "Unsupported command code: " << cmd_code;
|
|
return MinadbdCommand::kError;
|
|
}
|
|
|
|
return static_cast<MinadbdCommand>(cmd_code);
|
|
}
|
|
|
|
static bool WriteStatusToFd(MinadbdCommandStatus status, int fd) {
|
|
char message[kMinadbdMessageSize];
|
|
memcpy(message, kMinadbdStatusPrefix, strlen(kMinadbdStatusPrefix));
|
|
android::base::put_unaligned(message + strlen(kMinadbdStatusPrefix), status);
|
|
|
|
if (!android::base::WriteFully(fd, message, kMinadbdMessageSize)) {
|
|
PLOG(ERROR) << "Failed to write message " << message;
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
// Installs the package from FUSE. Returns the installation result and whether it should continue
|
|
// waiting for new commands.
|
|
static auto AdbInstallPackageHandler(Device* device, InstallResult* result) {
|
|
// How long (in seconds) we wait for the package path to be ready. It doesn't need to be too long
|
|
// because the minadbd service has already issued an install command. FUSE_SIDELOAD_HOST_PATHNAME
|
|
// will start to exist once the host connects and starts serving a package. Poll for its
|
|
// appearance. (Note that inotify doesn't work with FUSE.)
|
|
auto ui = device->GetUI();
|
|
constexpr int ADB_INSTALL_TIMEOUT = 15;
|
|
bool should_continue = true;
|
|
*result = INSTALL_ERROR;
|
|
for (int i = 0; i < ADB_INSTALL_TIMEOUT; ++i) {
|
|
struct stat st;
|
|
if (stat(FUSE_SIDELOAD_HOST_PATHNAME, &st) != 0) {
|
|
if (errno == ENOENT && i < ADB_INSTALL_TIMEOUT - 1) {
|
|
sleep(1);
|
|
continue;
|
|
} else {
|
|
should_continue = false;
|
|
ui->Print("\nTimed out waiting for fuse to be ready.\n\n");
|
|
break;
|
|
}
|
|
}
|
|
|
|
auto package =
|
|
Package::CreateFilePackage(FUSE_SIDELOAD_HOST_PATHNAME,
|
|
std::bind(&RecoveryUI::SetProgress, ui, std::placeholders::_1));
|
|
*result = InstallPackage(package.get(), FUSE_SIDELOAD_HOST_PATHNAME, false, 0, device);
|
|
break;
|
|
}
|
|
|
|
// Calling stat() on this magic filename signals the FUSE to exit.
|
|
struct stat st;
|
|
stat(FUSE_SIDELOAD_HOST_EXIT_PATHNAME, &st);
|
|
return std::make_pair(*result == INSTALL_SUCCESS, should_continue);
|
|
}
|
|
|
|
static auto AdbRebootHandler(MinadbdCommand command, InstallResult* result,
|
|
Device::BuiltinAction* reboot_action) {
|
|
// Use Device::REBOOT_{FASTBOOT,RECOVERY,RESCUE}, instead of the ones with ENTER_. This allows
|
|
// rebooting back into fastboot/recovery/rescue mode through bootloader, which may use a newly
|
|
// installed bootloader/recovery image.
|
|
switch (command) {
|
|
case MinadbdCommand::kRebootBootloader:
|
|
*reboot_action = Device::REBOOT_BOOTLOADER;
|
|
break;
|
|
case MinadbdCommand::kRebootFastboot:
|
|
*reboot_action = Device::REBOOT_FASTBOOT;
|
|
break;
|
|
case MinadbdCommand::kRebootRecovery:
|
|
*reboot_action = Device::REBOOT_RECOVERY;
|
|
break;
|
|
case MinadbdCommand::kRebootRescue:
|
|
*reboot_action = Device::REBOOT_RESCUE;
|
|
break;
|
|
case MinadbdCommand::kRebootAndroid:
|
|
default:
|
|
*reboot_action = Device::REBOOT;
|
|
break;
|
|
}
|
|
*result = INSTALL_REBOOT;
|
|
return std::make_pair(true, false);
|
|
}
|
|
|
|
// Parses and executes the command from minadbd. Returns whether the caller should keep waiting for
|
|
// next command.
|
|
static bool HandleMessageFromMinadbd(int socket_fd,
|
|
const std::map<MinadbdCommand, CommandFunction>& command_map) {
|
|
char buffer[kMinadbdMessageSize];
|
|
if (!android::base::ReadFully(socket_fd, buffer, kMinadbdMessageSize)) {
|
|
PLOG(ERROR) << "Failed to read message from minadbd";
|
|
return false;
|
|
}
|
|
|
|
std::string message(buffer, buffer + kMinadbdMessageSize);
|
|
auto command_type = ParseMinadbdCommand(message);
|
|
if (command_type == MinadbdCommand::kError) {
|
|
return false;
|
|
}
|
|
if (command_map.find(command_type) == command_map.end()) {
|
|
LOG(ERROR) << "Unsupported command: "
|
|
<< android::base::get_unaligned<unsigned int>(
|
|
message.substr(strlen(kMinadbdCommandPrefix)).c_str());
|
|
return false;
|
|
}
|
|
|
|
// We have received a valid command, execute the corresponding function.
|
|
const auto& command_func = command_map.at(command_type);
|
|
const auto [result, should_continue] = command_func();
|
|
LOG(INFO) << "Command " << static_cast<uint32_t>(command_type) << " finished with " << result;
|
|
if (!WriteStatusToFd(result ? MinadbdCommandStatus::kSuccess : MinadbdCommandStatus::kFailure,
|
|
socket_fd)) {
|
|
return false;
|
|
}
|
|
return should_continue;
|
|
}
|
|
|
|
// TODO(xunchang) add a wrapper function and kill the minadbd service there.
|
|
static void ListenAndExecuteMinadbdCommands(
|
|
RecoveryUI* ui, pid_t minadbd_pid, android::base::unique_fd&& socket_fd,
|
|
const std::map<MinadbdCommand, CommandFunction>& command_map) {
|
|
android::base::unique_fd epoll_fd(epoll_create1(O_CLOEXEC));
|
|
if (epoll_fd == -1) {
|
|
PLOG(ERROR) << "Failed to create epoll";
|
|
kill(minadbd_pid, SIGKILL);
|
|
return;
|
|
}
|
|
|
|
constexpr int EPOLL_MAX_EVENTS = 10;
|
|
struct epoll_event ev = {};
|
|
ev.events = EPOLLIN | EPOLLHUP;
|
|
ev.data.fd = socket_fd.get();
|
|
struct epoll_event events[EPOLL_MAX_EVENTS];
|
|
if (epoll_ctl(epoll_fd.get(), EPOLL_CTL_ADD, socket_fd.get(), &ev) == -1) {
|
|
PLOG(ERROR) << "Failed to add socket fd to epoll";
|
|
kill(minadbd_pid, SIGKILL);
|
|
return;
|
|
}
|
|
|
|
// Set the timeout to be 300s when waiting for minadbd commands.
|
|
constexpr int TIMEOUT_MILLIS = 300 * 1000;
|
|
while (true) {
|
|
// Reset the progress bar and the background image before each command.
|
|
ui->SetProgressType(RecoveryUI::EMPTY);
|
|
ui->SetBackground(RecoveryUI::NO_COMMAND);
|
|
|
|
// Poll for the status change of the socket_fd, and handle the message if the fd is ready to
|
|
// read.
|
|
int event_count =
|
|
TEMP_FAILURE_RETRY(epoll_wait(epoll_fd.get(), events, EPOLL_MAX_EVENTS, TIMEOUT_MILLIS));
|
|
if (event_count == -1) {
|
|
PLOG(ERROR) << "Failed to wait for epoll events";
|
|
kill(minadbd_pid, SIGKILL);
|
|
return;
|
|
}
|
|
if (event_count == 0) {
|
|
LOG(ERROR) << "Timeout waiting for messages from minadbd";
|
|
kill(minadbd_pid, SIGKILL);
|
|
return;
|
|
}
|
|
|
|
for (int n = 0; n < event_count; n++) {
|
|
if (events[n].events & EPOLLHUP) {
|
|
LOG(INFO) << "Socket has been closed";
|
|
kill(minadbd_pid, SIGKILL);
|
|
return;
|
|
}
|
|
if (!HandleMessageFromMinadbd(socket_fd.get(), command_map)) {
|
|
kill(minadbd_pid, SIGKILL);
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Recovery starts minadbd service as a child process, and spawns another thread to listen for the
|
|
// message from minadbd through a socket pair. Here is an example to execute one command from adb
|
|
// host.
|
|
// a. recovery b. listener thread c. minadbd service
|
|
//
|
|
// a1. create socket pair
|
|
// a2. fork minadbd service
|
|
// c3. wait for the adb commands
|
|
// from host
|
|
// c4. after receiving host commands:
|
|
// 1) set up pre-condition (i.e.
|
|
// start fuse for adb sideload)
|
|
// 2) issue command through
|
|
// socket.
|
|
// 3) wait for result
|
|
// a5. start listener thread
|
|
// b6. listen for message from
|
|
// minadbd in a loop.
|
|
// b7. After receiving a minadbd
|
|
// command from socket
|
|
// 1) execute the command function
|
|
// 2) send the result back to
|
|
// minadbd
|
|
// ......
|
|
// c8. exit upon receiving the
|
|
// result
|
|
// a9. wait for listener thread
|
|
// to exit.
|
|
//
|
|
// a10. wait for minadbd to
|
|
// exit
|
|
// b11. exit the listening loop
|
|
//
|
|
static void CreateMinadbdServiceAndExecuteCommands(
|
|
RecoveryUI* ui, const std::map<MinadbdCommand, CommandFunction>& command_map,
|
|
bool rescue_mode) {
|
|
signal(SIGPIPE, SIG_IGN);
|
|
|
|
android::base::unique_fd recovery_socket;
|
|
android::base::unique_fd minadbd_socket;
|
|
if (!android::base::Socketpair(AF_UNIX, SOCK_STREAM, 0, &recovery_socket, &minadbd_socket)) {
|
|
PLOG(ERROR) << "Failed to create socket";
|
|
return;
|
|
}
|
|
|
|
pid_t child = fork();
|
|
if (child == -1) {
|
|
PLOG(ERROR) << "Failed to fork child process";
|
|
return;
|
|
}
|
|
if (child == 0) {
|
|
recovery_socket.reset();
|
|
std::vector<std::string> minadbd_commands = {
|
|
"/system/bin/minadbd",
|
|
"--socket_fd",
|
|
std::to_string(minadbd_socket.release()),
|
|
};
|
|
if (rescue_mode) {
|
|
minadbd_commands.push_back("--rescue");
|
|
}
|
|
auto exec_args = StringVectorToNullTerminatedArray(minadbd_commands);
|
|
execv(exec_args[0], exec_args.data());
|
|
_exit(EXIT_FAILURE);
|
|
}
|
|
|
|
minadbd_socket.reset();
|
|
|
|
// We need to call SetUsbConfig() after forking minadbd service. Because the function waits for
|
|
// the usb state to be updated, which depends on sys.usb.ffs.ready=1 set in the adb daemon.
|
|
if (!SetUsbConfig("sideload")) {
|
|
LOG(ERROR) << "Failed to set usb config to sideload";
|
|
return;
|
|
}
|
|
|
|
std::thread listener_thread(ListenAndExecuteMinadbdCommands, ui, child,
|
|
std::move(recovery_socket), std::ref(command_map));
|
|
if (listener_thread.joinable()) {
|
|
listener_thread.join();
|
|
}
|
|
|
|
int status;
|
|
waitpid(child, &status, 0);
|
|
if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) {
|
|
if (WEXITSTATUS(status) == MinadbdErrorCode::kMinadbdAdbVersionError) {
|
|
LOG(ERROR) << "\nYou need adb 1.0.32 or newer to sideload\nto this device.\n";
|
|
} else if (!WIFSIGNALED(status)) {
|
|
LOG(ERROR) << "\n(adbd status " << WEXITSTATUS(status) << ")";
|
|
}
|
|
}
|
|
|
|
signal(SIGPIPE, SIG_DFL);
|
|
}
|
|
|
|
InstallResult ApplyFromAdb(Device* device, bool rescue_mode, Device::BuiltinAction* reboot_action) {
|
|
// Save the usb state to restore after the sideload operation.
|
|
std::string usb_state = android::base::GetProperty("sys.usb.state", "none");
|
|
// Clean up state and stop adbd.
|
|
if (usb_state != "none" && !SetUsbConfig("none")) {
|
|
LOG(ERROR) << "Failed to clear USB config";
|
|
return INSTALL_ERROR;
|
|
}
|
|
|
|
RecoveryUI* ui = device->GetUI();
|
|
|
|
InstallResult install_result = INSTALL_NONE;
|
|
std::map<MinadbdCommand, CommandFunction> command_map{
|
|
{ MinadbdCommand::kInstall, std::bind(&AdbInstallPackageHandler, device, &install_result) },
|
|
{ MinadbdCommand::kRebootAndroid, std::bind(&AdbRebootHandler, MinadbdCommand::kRebootAndroid,
|
|
&install_result, reboot_action) },
|
|
{ MinadbdCommand::kRebootBootloader,
|
|
std::bind(&AdbRebootHandler, MinadbdCommand::kRebootBootloader, &install_result,
|
|
reboot_action) },
|
|
{ MinadbdCommand::kRebootFastboot, std::bind(&AdbRebootHandler, MinadbdCommand::kRebootFastboot,
|
|
&install_result, reboot_action) },
|
|
{ MinadbdCommand::kRebootRecovery, std::bind(&AdbRebootHandler, MinadbdCommand::kRebootRecovery,
|
|
&install_result, reboot_action) },
|
|
{ MinadbdCommand::kRebootRescue,
|
|
std::bind(&AdbRebootHandler, MinadbdCommand::kRebootRescue, &install_result, reboot_action) },
|
|
};
|
|
|
|
if (!rescue_mode) {
|
|
ui->Print(
|
|
"\n\nNow send the package you want to apply\n"
|
|
"to the device with \"adb sideload <filename>\"...\n");
|
|
} else {
|
|
command_map.emplace(MinadbdCommand::kWipeData, [&device]() {
|
|
bool result = WipeData(device);
|
|
return std::make_pair(result, true);
|
|
});
|
|
command_map.emplace(MinadbdCommand::kNoOp, []() { return std::make_pair(true, true); });
|
|
|
|
ui->Print("\n\nWaiting for rescue commands...\n");
|
|
}
|
|
|
|
CreateMinadbdServiceAndExecuteCommands(ui, command_map, rescue_mode);
|
|
|
|
// Clean up before switching to the older state, for example setting the state
|
|
// to none sets sys/class/android_usb/android0/enable to 0.
|
|
if (!SetUsbConfig("none")) {
|
|
LOG(ERROR) << "Failed to clear USB config";
|
|
}
|
|
|
|
if (usb_state != "none") {
|
|
if (!SetUsbConfig(usb_state)) {
|
|
LOG(ERROR) << "Failed to set USB config to " << usb_state;
|
|
}
|
|
}
|
|
|
|
return install_result;
|
|
}
|