You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
738 lines
24 KiB
738 lines
24 KiB
//
|
|
// Copyright (C) 2019 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 <getopt.h>
|
|
#include <stdio.h>
|
|
#include <sysexits.h>
|
|
#include <unistd.h>
|
|
|
|
#include <chrono>
|
|
#include <condition_variable>
|
|
#include <functional>
|
|
#include <iostream>
|
|
#include <map>
|
|
#include <mutex>
|
|
#include <string>
|
|
#include <thread>
|
|
|
|
#include <android-base/logging.h>
|
|
#include <android-base/parseint.h>
|
|
#include <android-base/properties.h>
|
|
#include <android-base/stringprintf.h>
|
|
#include <android-base/strings.h>
|
|
#include <android-base/unique_fd.h>
|
|
#include <android/gsi/IGsiService.h>
|
|
#include <cutils/android_reboot.h>
|
|
#include <libgsi/libgsi.h>
|
|
#include <libgsi/libgsid.h>
|
|
|
|
using namespace android::gsi;
|
|
using namespace std::chrono_literals;
|
|
|
|
using android::sp;
|
|
using android::base::Split;
|
|
using android::base::StringPrintf;
|
|
using CommandCallback = std::function<int(sp<IGsiService>, int, char**)>;
|
|
|
|
static int Disable(sp<IGsiService> gsid, int argc, char** argv);
|
|
static int Enable(sp<IGsiService> gsid, int argc, char** argv);
|
|
static int Install(sp<IGsiService> gsid, int argc, char** argv);
|
|
static int CreatePartition(sp<IGsiService> gsid, int argc, char** argv);
|
|
static int Wipe(sp<IGsiService> gsid, int argc, char** argv);
|
|
static int WipeData(sp<IGsiService> gsid, int argc, char** argv);
|
|
static int Status(sp<IGsiService> gsid, int argc, char** argv);
|
|
static int Cancel(sp<IGsiService> gsid, int argc, char** argv);
|
|
|
|
static const std::map<std::string, CommandCallback> kCommandMap = {
|
|
// clang-format off
|
|
{"disable", Disable},
|
|
{"enable", Enable},
|
|
{"install", Install},
|
|
{"create-partition", CreatePartition},
|
|
{"wipe", Wipe},
|
|
{"wipe-data", WipeData},
|
|
{"status", Status},
|
|
{"cancel", Cancel},
|
|
// clang-format on
|
|
};
|
|
|
|
static std::string ErrorMessage(const android::binder::Status& status,
|
|
int error_code = IGsiService::INSTALL_ERROR_GENERIC) {
|
|
if (!status.isOk()) {
|
|
return status.exceptionMessage().string();
|
|
}
|
|
return "error code " + std::to_string(error_code);
|
|
}
|
|
|
|
class ProgressBar {
|
|
public:
|
|
explicit ProgressBar(sp<IGsiService> gsid) : gsid_(gsid) {}
|
|
|
|
~ProgressBar() { Stop(); }
|
|
|
|
void Display() {
|
|
Finish();
|
|
done_ = false;
|
|
last_update_ = {};
|
|
worker_ = std::make_unique<std::thread>([this]() { Worker(); });
|
|
}
|
|
|
|
void Stop() {
|
|
if (!worker_) {
|
|
return;
|
|
}
|
|
SignalDone();
|
|
worker_->join();
|
|
worker_ = nullptr;
|
|
}
|
|
|
|
void Finish() {
|
|
if (!worker_) {
|
|
return;
|
|
}
|
|
Stop();
|
|
FinishLastBar();
|
|
}
|
|
|
|
private:
|
|
void Worker() {
|
|
std::unique_lock<std::mutex> lock(mutex_);
|
|
while (!done_) {
|
|
if (!UpdateProgress()) {
|
|
return;
|
|
}
|
|
cv_.wait_for(lock, 500ms, [this] { return done_; });
|
|
}
|
|
}
|
|
|
|
bool UpdateProgress() {
|
|
GsiProgress latest;
|
|
auto status = gsid_->getInstallProgress(&latest);
|
|
if (!status.isOk()) {
|
|
std::cout << std::endl;
|
|
return false;
|
|
}
|
|
if (latest.status == IGsiService::STATUS_NO_OPERATION) {
|
|
return true;
|
|
}
|
|
if (last_update_.step != latest.step) {
|
|
FinishLastBar();
|
|
}
|
|
Display(latest);
|
|
return true;
|
|
}
|
|
|
|
void FinishLastBar() {
|
|
// If no bar was in progress, don't do anything.
|
|
if (last_update_.total_bytes == 0) {
|
|
return;
|
|
}
|
|
// Ensure we finish the display at 100%.
|
|
last_update_.bytes_processed = last_update_.total_bytes;
|
|
Display(last_update_);
|
|
std::cout << std::endl;
|
|
}
|
|
|
|
void Display(const GsiProgress& progress) {
|
|
if (progress.total_bytes == 0) {
|
|
return;
|
|
}
|
|
|
|
static constexpr int kColumns = 80;
|
|
static constexpr char kRedColor[] = "\x1b[31m";
|
|
static constexpr char kGreenColor[] = "\x1b[32m";
|
|
static constexpr char kResetColor[] = "\x1b[0m";
|
|
|
|
int percentage = (progress.bytes_processed * 100) / progress.total_bytes;
|
|
int64_t bytes_per_col = progress.total_bytes / kColumns;
|
|
uint32_t fill_count = progress.bytes_processed / bytes_per_col;
|
|
uint32_t dash_count = kColumns - fill_count;
|
|
std::string fills = std::string(fill_count, '=');
|
|
std::string dashes = std::string(dash_count, '-');
|
|
|
|
// Give the end of the bar some flare.
|
|
if (!fills.empty() && !dashes.empty()) {
|
|
fills[fills.size() - 1] = '>';
|
|
}
|
|
|
|
fprintf(stdout, "\r%-15s%6d%% ", progress.step.c_str(), percentage);
|
|
fprintf(stdout, "%s[%s%s%s", kGreenColor, fills.c_str(), kRedColor, dashes.c_str());
|
|
fprintf(stdout, "%s]%s", kGreenColor, kResetColor);
|
|
fflush(stdout);
|
|
|
|
last_update_ = progress;
|
|
}
|
|
|
|
void SignalDone() {
|
|
std::lock_guard<std::mutex> guard(mutex_);
|
|
done_ = true;
|
|
cv_.notify_all();
|
|
}
|
|
|
|
private:
|
|
sp<IGsiService> gsid_;
|
|
std::unique_ptr<std::thread> worker_;
|
|
std::condition_variable cv_;
|
|
std::mutex mutex_;
|
|
GsiProgress last_update_;
|
|
bool done_ = false;
|
|
};
|
|
|
|
static int Install(sp<IGsiService> gsid, int argc, char** argv) {
|
|
constexpr const char* kDefaultPartition = "system";
|
|
struct option options[] = {
|
|
{"install-dir", required_argument, nullptr, 'i'},
|
|
{"gsi-size", required_argument, nullptr, 's'},
|
|
{"no-reboot", no_argument, nullptr, 'n'},
|
|
{"userdata-size", required_argument, nullptr, 'u'},
|
|
{"partition-name", required_argument, nullptr, 'p'},
|
|
{"wipe", no_argument, nullptr, 'w'},
|
|
{nullptr, 0, nullptr, 0},
|
|
};
|
|
|
|
int64_t gsiSize = 0;
|
|
int64_t userdataSize = 0;
|
|
bool wipeUserdata = false;
|
|
bool reboot = true;
|
|
std::string installDir = "";
|
|
std::string partition = kDefaultPartition;
|
|
if (getuid() != 0) {
|
|
std::cerr << "must be root to install a GSI" << std::endl;
|
|
return EX_NOPERM;
|
|
}
|
|
|
|
int rv, index;
|
|
while ((rv = getopt_long_only(argc, argv, "", options, &index)) != -1) {
|
|
switch (rv) {
|
|
case 'p':
|
|
partition = optarg;
|
|
break;
|
|
case 's':
|
|
if (!android::base::ParseInt(optarg, &gsiSize) || gsiSize <= 0) {
|
|
std::cerr << "Could not parse image size: " << optarg << std::endl;
|
|
return EX_USAGE;
|
|
}
|
|
break;
|
|
case 'u':
|
|
if (!android::base::ParseInt(optarg, &userdataSize) || userdataSize < 0) {
|
|
std::cerr << "Could not parse image size: " << optarg << std::endl;
|
|
return EX_USAGE;
|
|
}
|
|
break;
|
|
case 'i':
|
|
installDir = optarg;
|
|
break;
|
|
case 'w':
|
|
wipeUserdata = true;
|
|
break;
|
|
case 'n':
|
|
reboot = false;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (gsiSize <= 0) {
|
|
std::cerr << "Must specify --gsi-size." << std::endl;
|
|
return EX_USAGE;
|
|
}
|
|
|
|
bool running_gsi = false;
|
|
gsid->isGsiRunning(&running_gsi);
|
|
if (running_gsi) {
|
|
std::cerr << "Cannot install a GSI within a live GSI." << std::endl;
|
|
std::cerr << "Use gsi_tool disable or wipe and reboot first." << std::endl;
|
|
return EX_SOFTWARE;
|
|
}
|
|
|
|
android::base::unique_fd input(dup(STDIN_FILENO));
|
|
if (input < 0) {
|
|
std::cerr << "Error duplicating descriptor: " << strerror(errno) << std::endl;
|
|
return EX_SOFTWARE;
|
|
}
|
|
// Note: the progress bar needs to be re-started in between each call.
|
|
ProgressBar progress(gsid);
|
|
progress.Display();
|
|
int error;
|
|
auto status = gsid->openInstall(installDir, &error);
|
|
if (!status.isOk() || error != IGsiService::INSTALL_OK) {
|
|
std::cerr << "Could not open DSU installation: " << ErrorMessage(status, error) << "\n";
|
|
return EX_SOFTWARE;
|
|
}
|
|
if (partition == kDefaultPartition) {
|
|
auto status = gsid->createPartition("userdata", userdataSize, false, &error);
|
|
if (!status.isOk() || error != IGsiService::INSTALL_OK) {
|
|
std::cerr << "Could not start live image install: " << ErrorMessage(status, error)
|
|
<< "\n";
|
|
return EX_SOFTWARE;
|
|
}
|
|
status = gsid->closePartition(&error);
|
|
if (!status.isOk() || error != IGsiService::INSTALL_OK) {
|
|
std::cerr << "Could not closePartition(userdata): " << ErrorMessage(status, error)
|
|
<< std::endl;
|
|
return EX_SOFTWARE;
|
|
}
|
|
}
|
|
|
|
status = gsid->createPartition(partition, gsiSize, true, &error);
|
|
if (!status.isOk() || error != IGsiService::INSTALL_OK) {
|
|
std::cerr << "Could not start live image install: " << ErrorMessage(status, error) << "\n";
|
|
return EX_SOFTWARE;
|
|
}
|
|
android::os::ParcelFileDescriptor stream(std::move(input));
|
|
|
|
bool ok = false;
|
|
progress.Display();
|
|
status = gsid->commitGsiChunkFromStream(stream, gsiSize, &ok);
|
|
if (!ok) {
|
|
std::cerr << "Could not commit live image data: " << ErrorMessage(status) << "\n";
|
|
return EX_SOFTWARE;
|
|
}
|
|
|
|
status = gsid->closePartition(&error);
|
|
if (!status.isOk() || error != IGsiService::INSTALL_OK) {
|
|
std::cerr << "Could not closePartition(" << partition
|
|
<< "): " << ErrorMessage(status, error) << std::endl;
|
|
return EX_SOFTWARE;
|
|
}
|
|
|
|
status = gsid->closeInstall(&error);
|
|
if (!status.isOk() || error != IGsiService::INSTALL_OK) {
|
|
std::cerr << "Could not close DSU installation: " << ErrorMessage(status, error) << "\n";
|
|
return EX_SOFTWARE;
|
|
}
|
|
progress.Finish();
|
|
std::string dsuSlot;
|
|
status = gsid->getActiveDsuSlot(&dsuSlot);
|
|
if (!status.isOk()) {
|
|
std::cerr << "Could not get the active DSU slot: " << ErrorMessage(status) << "\n";
|
|
return EX_SOFTWARE;
|
|
}
|
|
status = gsid->enableGsi(true, dsuSlot, &error);
|
|
if (!status.isOk() || error != IGsiService::INSTALL_OK) {
|
|
std::cerr << "Could not make live image bootable: " << ErrorMessage(status, error) << "\n";
|
|
return EX_SOFTWARE;
|
|
}
|
|
|
|
if (reboot) {
|
|
if (!android::base::SetProperty(ANDROID_RB_PROPERTY, "reboot,adb")) {
|
|
std::cerr << "Failed to reboot automatically" << std::endl;
|
|
return EX_SOFTWARE;
|
|
}
|
|
} else {
|
|
std::cout << "Please reboot to use the GSI." << std::endl;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
// Experimental API
|
|
static int CreatePartition(sp<IGsiService> gsid, int argc, char** argv) {
|
|
std::string installDir;
|
|
std::string partitionName;
|
|
bool readOnly = true;
|
|
int64_t partitionSize = 0;
|
|
|
|
struct option options[] = {
|
|
{"install-dir", required_argument, nullptr, 'i'},
|
|
{"partition-name", required_argument, nullptr, 'p'},
|
|
{"readwrite", no_argument, nullptr, 'r'},
|
|
{"size", required_argument, nullptr, 's'},
|
|
{nullptr, 0, nullptr, 0},
|
|
};
|
|
|
|
int rv = 0;
|
|
while ((rv = getopt_long_only(argc, argv, "", options, nullptr)) != -1) {
|
|
switch (rv) {
|
|
case 'i':
|
|
installDir = optarg;
|
|
break;
|
|
case 'p':
|
|
partitionName = optarg;
|
|
break;
|
|
case 'r':
|
|
readOnly = false;
|
|
break;
|
|
case 's':
|
|
if (!android::base::ParseInt(optarg, &partitionSize)) {
|
|
std::cerr << "Could not parse partition size: " << optarg << std::endl;
|
|
return EX_USAGE;
|
|
}
|
|
break;
|
|
default:
|
|
return EX_USAGE;
|
|
}
|
|
}
|
|
|
|
if (getuid() != 0) {
|
|
std::cerr << "must be root to install a DSU" << std::endl;
|
|
return EX_NOPERM;
|
|
}
|
|
|
|
bool gsiRunning = false;
|
|
auto status = gsid->isGsiRunning(&gsiRunning);
|
|
if (!status.isOk()) {
|
|
std::cerr << "Could not get DSU running status: " << ErrorMessage(status) << std::endl;
|
|
return EX_SOFTWARE;
|
|
}
|
|
if (gsiRunning) {
|
|
std::cerr << "Could not install DSU within an active DSU." << std::endl;
|
|
return EX_SOFTWARE;
|
|
}
|
|
|
|
if (partitionSize <= 0) {
|
|
std::cerr << "Partition size must be greater than zero: " << partitionSize << std::endl;
|
|
return EX_USAGE;
|
|
}
|
|
|
|
// Note: the progress bar needs to be re-started in between each call.
|
|
ProgressBar progress(gsid);
|
|
progress.Display();
|
|
|
|
int error;
|
|
status = gsid->openInstall(installDir, &error);
|
|
if (!status.isOk() || error != IGsiService::INSTALL_OK) {
|
|
std::cerr << "Could not open DSU installation: " << ErrorMessage(status, error)
|
|
<< std::endl;
|
|
return EX_SOFTWARE;
|
|
}
|
|
|
|
status = gsid->createPartition(partitionName, partitionSize, readOnly, &error);
|
|
if (!status.isOk() || error != IGsiService::INSTALL_OK) {
|
|
std::cerr << "Could not create DSU partition: " << ErrorMessage(status, error) << std::endl;
|
|
return EX_SOFTWARE;
|
|
}
|
|
|
|
if (readOnly) {
|
|
android::base::unique_fd input(dup(STDIN_FILENO));
|
|
if (input < 0) {
|
|
std::cerr << "Error duplicating descriptor: " << strerror(errno) << std::endl;
|
|
return EX_SOFTWARE;
|
|
}
|
|
android::os::ParcelFileDescriptor stream(std::move(input));
|
|
|
|
bool ok = false;
|
|
status = gsid->commitGsiChunkFromStream(stream, partitionSize, &ok);
|
|
if (!ok) {
|
|
std::cerr << "Could not commit data from stdin: " << ErrorMessage(status) << std::endl;
|
|
return EX_SOFTWARE;
|
|
}
|
|
}
|
|
|
|
status = gsid->closePartition(&error);
|
|
if (!status.isOk() || error != IGsiService::INSTALL_OK) {
|
|
std::cerr << "Could not close DSU partition:" << ErrorMessage(status, error) << std::endl;
|
|
return EX_SOFTWARE;
|
|
}
|
|
|
|
status = gsid->closeInstall(&error);
|
|
if (!status.isOk() || error != IGsiService::INSTALL_OK) {
|
|
std::cerr << "Could not close DSU installation: " << ErrorMessage(status, error)
|
|
<< std::endl;
|
|
return EX_SOFTWARE;
|
|
}
|
|
|
|
progress.Finish();
|
|
|
|
std::string dsuSlot;
|
|
status = gsid->getActiveDsuSlot(&dsuSlot);
|
|
if (!status.isOk()) {
|
|
std::cerr << "Could not get the active DSU slot: " << ErrorMessage(status) << std::endl;
|
|
return EX_SOFTWARE;
|
|
}
|
|
|
|
// Immediately enable DSU after a partition is installed to ensure the installation status file
|
|
// is created.
|
|
status = gsid->enableGsi(/* one_shot = */ true, dsuSlot, &error);
|
|
if (!status.isOk() || error != IGsiService::INSTALL_OK) {
|
|
std::cerr << "Could not make DSU bootable: " << ErrorMessage(status, error) << std::endl;
|
|
return EX_SOFTWARE;
|
|
}
|
|
|
|
std::cout << "Enabled DSU slot: " << dsuSlot << std::endl;
|
|
std::cout << "Please reboot to use the DSU." << std::endl;
|
|
return 0;
|
|
}
|
|
|
|
static int Wipe(sp<IGsiService> gsid, int argc, char** /* argv */) {
|
|
if (argc > 1) {
|
|
std::cerr << "Unrecognized arguments to wipe." << std::endl;
|
|
return EX_USAGE;
|
|
}
|
|
bool ok;
|
|
auto status = gsid->removeGsi(&ok);
|
|
if (!status.isOk() || !ok) {
|
|
std::cerr << "Could not remove GSI install: " << ErrorMessage(status) << "\n";
|
|
return EX_SOFTWARE;
|
|
}
|
|
|
|
bool running = false;
|
|
if (gsid->isGsiRunning(&running).isOk() && running) {
|
|
std::cout << "Live image install will be removed next reboot." << std::endl;
|
|
} else {
|
|
std::cout << "Live image install successfully removed." << std::endl;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int WipeData(sp<IGsiService> gsid, int argc, char** /* argv */) {
|
|
if (argc > 1) {
|
|
std::cerr << "Unrecognized arguments to wipe-data.\n";
|
|
return EX_USAGE;
|
|
}
|
|
|
|
bool running;
|
|
auto status = gsid->isGsiRunning(&running);
|
|
if (!status.isOk()) {
|
|
std::cerr << "error: " << status.exceptionMessage().string() << std::endl;
|
|
return EX_SOFTWARE;
|
|
}
|
|
if (running) {
|
|
std::cerr << "Cannot wipe GSI userdata while running a GSI.\n";
|
|
return EX_USAGE;
|
|
}
|
|
|
|
bool installed;
|
|
status = gsid->isGsiInstalled(&installed);
|
|
if (!status.isOk()) {
|
|
std::cerr << "error: " << status.exceptionMessage().string() << std::endl;
|
|
return EX_SOFTWARE;
|
|
}
|
|
if (!installed) {
|
|
std::cerr << "No GSI is installed.\n";
|
|
return EX_USAGE;
|
|
}
|
|
|
|
int error;
|
|
status = gsid->zeroPartition("userdata" + std::string(kDsuPostfix), &error);
|
|
if (!status.isOk() || error) {
|
|
std::cerr << "Could not wipe GSI userdata: " << ErrorMessage(status, error) << "\n";
|
|
return EX_SOFTWARE;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int Status(sp<IGsiService> gsid, int argc, char** /* argv */) {
|
|
if (argc > 1) {
|
|
std::cerr << "Unrecognized arguments to status." << std::endl;
|
|
return EX_USAGE;
|
|
}
|
|
bool running;
|
|
auto status = gsid->isGsiRunning(&running);
|
|
if (!status.isOk()) {
|
|
std::cerr << "error: " << status.exceptionMessage().string() << std::endl;
|
|
return EX_SOFTWARE;
|
|
} else if (running) {
|
|
std::cout << "running" << std::endl;
|
|
}
|
|
bool installed;
|
|
status = gsid->isGsiInstalled(&installed);
|
|
if (!status.isOk()) {
|
|
std::cerr << "error: " << status.exceptionMessage().string() << std::endl;
|
|
return EX_SOFTWARE;
|
|
} else if (installed) {
|
|
std::cout << "installed" << std::endl;
|
|
}
|
|
bool enabled;
|
|
status = gsid->isGsiEnabled(&enabled);
|
|
if (!status.isOk()) {
|
|
std::cerr << status.exceptionMessage().string() << std::endl;
|
|
return EX_SOFTWARE;
|
|
} else if (running || installed) {
|
|
std::cout << (enabled ? "enabled" : "disabled") << std::endl;
|
|
} else {
|
|
std::cout << "normal" << std::endl;
|
|
}
|
|
if (getuid() != 0) {
|
|
return 0;
|
|
}
|
|
|
|
std::vector<std::string> dsu_slots;
|
|
status = gsid->getInstalledDsuSlots(&dsu_slots);
|
|
if (!status.isOk()) {
|
|
std::cerr << status.exceptionMessage().string() << std::endl;
|
|
return EX_SOFTWARE;
|
|
}
|
|
int n = 0;
|
|
for (auto&& dsu_slot : dsu_slots) {
|
|
std::cout << "[" << n++ << "] " << dsu_slot << std::endl;
|
|
sp<IImageService> image_service = nullptr;
|
|
status = gsid->openImageService("dsu/" + dsu_slot + "/", &image_service);
|
|
if (!status.isOk()) {
|
|
std::cerr << "error: " << status.exceptionMessage().string() << std::endl;
|
|
return EX_SOFTWARE;
|
|
}
|
|
std::vector<std::string> images;
|
|
status = image_service->getAllBackingImages(&images);
|
|
if (!status.isOk()) {
|
|
std::cerr << "error: " << status.exceptionMessage().string() << std::endl;
|
|
return EX_SOFTWARE;
|
|
}
|
|
for (auto&& image : images) {
|
|
std::cout << "installed: " << image << std::endl;
|
|
AvbPublicKey public_key;
|
|
int err = 0;
|
|
status = image_service->getAvbPublicKey(image, &public_key, &err);
|
|
std::cout << "AVB public key (sha1): ";
|
|
if (!public_key.bytes.empty()) {
|
|
for (auto b : public_key.sha1) {
|
|
std::cout << StringPrintf("%02x", b & 255);
|
|
}
|
|
std::cout << std::endl;
|
|
} else {
|
|
std::cout << "[NONE]" << std::endl;
|
|
}
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int Cancel(sp<IGsiService> gsid, int /* argc */, char** /* argv */) {
|
|
bool cancelled = false;
|
|
auto status = gsid->cancelGsiInstall(&cancelled);
|
|
if (!status.isOk()) {
|
|
std::cerr << status.exceptionMessage().string() << std::endl;
|
|
return EX_SOFTWARE;
|
|
}
|
|
if (!cancelled) {
|
|
std::cout << "Fail to cancel the installation." << std::endl;
|
|
return EX_SOFTWARE;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int Enable(sp<IGsiService> gsid, int argc, char** argv) {
|
|
bool one_shot = false;
|
|
std::string dsuSlot = {};
|
|
struct option options[] = {
|
|
{"single-boot", no_argument, nullptr, 's'},
|
|
{"dsuslot", required_argument, nullptr, 'd'},
|
|
{nullptr, 0, nullptr, 0},
|
|
};
|
|
int rv, index;
|
|
while ((rv = getopt_long_only(argc, argv, "", options, &index)) != -1) {
|
|
switch (rv) {
|
|
case 's':
|
|
one_shot = true;
|
|
break;
|
|
case 'd':
|
|
dsuSlot = optarg;
|
|
break;
|
|
default:
|
|
std::cerr << "Unrecognized argument to enable\n";
|
|
return EX_USAGE;
|
|
}
|
|
}
|
|
|
|
bool installed = false;
|
|
gsid->isGsiInstalled(&installed);
|
|
if (!installed) {
|
|
std::cerr << "Could not find GSI install to re-enable" << std::endl;
|
|
return EX_SOFTWARE;
|
|
}
|
|
|
|
bool installing = false;
|
|
gsid->isGsiInstallInProgress(&installing);
|
|
if (installing) {
|
|
std::cerr << "Cannot enable or disable while an installation is in progress." << std::endl;
|
|
return EX_SOFTWARE;
|
|
}
|
|
if (dsuSlot.empty()) {
|
|
auto status = gsid->getActiveDsuSlot(&dsuSlot);
|
|
if (!status.isOk()) {
|
|
std::cerr << "Could not get the active DSU slot: " << ErrorMessage(status) << "\n";
|
|
return EX_SOFTWARE;
|
|
}
|
|
}
|
|
int error;
|
|
auto status = gsid->enableGsi(one_shot, dsuSlot, &error);
|
|
if (!status.isOk() || error != IGsiService::INSTALL_OK) {
|
|
std::cerr << "Error re-enabling GSI: " << ErrorMessage(status, error) << "\n";
|
|
return EX_SOFTWARE;
|
|
}
|
|
std::cout << "Live image install successfully enabled." << std::endl;
|
|
return 0;
|
|
}
|
|
|
|
static int Disable(sp<IGsiService> gsid, int argc, char** /* argv */) {
|
|
if (argc > 1) {
|
|
std::cerr << "Unrecognized arguments to disable." << std::endl;
|
|
return EX_USAGE;
|
|
}
|
|
|
|
bool installing = false;
|
|
gsid->isGsiInstallInProgress(&installing);
|
|
if (installing) {
|
|
std::cerr << "Cannot enable or disable while an installation is in progress." << std::endl;
|
|
return EX_SOFTWARE;
|
|
}
|
|
|
|
bool ok = false;
|
|
gsid->disableGsi(&ok);
|
|
if (!ok) {
|
|
std::cerr << "Error disabling GSI" << std::endl;
|
|
return EX_SOFTWARE;
|
|
}
|
|
std::cout << "Live image install successfully disabled." << std::endl;
|
|
return 0;
|
|
}
|
|
|
|
static int usage(int /* argc */, char* argv[]) {
|
|
fprintf(stderr,
|
|
"%s - command-line tool for installing GSI images.\n"
|
|
"\n"
|
|
"Usage:\n"
|
|
" %s <disable|install|wipe|status> [options]\n"
|
|
"\n"
|
|
" disable Disable the currently installed GSI.\n"
|
|
" enable [-s, --single-boot]\n"
|
|
" [-d, --dsuslot slotname]\n"
|
|
" Enable a previously disabled GSI.\n"
|
|
" install Install a new GSI. Specify the image size with\n"
|
|
" --gsi-size and the desired userdata size with\n"
|
|
" --userdata-size (the latter defaults to 8GiB)\n"
|
|
" --wipe (remove old gsi userdata first)\n"
|
|
" wipe Completely remove a GSI and its associated data\n"
|
|
" wipe-data Ensure the GSI's userdata will be formatted\n"
|
|
" cancel Cancel the installation\n"
|
|
" status Show status\n",
|
|
argv[0], argv[0]);
|
|
return EX_USAGE;
|
|
}
|
|
|
|
int main(int argc, char** argv) {
|
|
android::base::InitLogging(argv, android::base::StderrLogger, android::base::DefaultAborter);
|
|
|
|
android::sp<IGsiService> service = GetGsiService();
|
|
if (!service) {
|
|
return EX_SOFTWARE;
|
|
}
|
|
|
|
if (1 >= argc) {
|
|
std::cerr << "Expected command." << std::endl;
|
|
return EX_USAGE;
|
|
}
|
|
|
|
std::string command = argv[1];
|
|
|
|
auto iter = kCommandMap.find(command);
|
|
if (iter == kCommandMap.end()) {
|
|
std::cerr << "Unrecognized command: " << command << std::endl;
|
|
return usage(argc, argv);
|
|
}
|
|
|
|
int rc = iter->second(service, argc - 1, argv + 1);
|
|
return rc;
|
|
}
|