diff options
-rw-r--r-- | aidl/android/gsi/IGsiService.aidl | 24 | ||||
-rw-r--r-- | file_paths.h | 1 | ||||
-rw-r--r-- | gsi_service.cpp | 87 | ||||
-rw-r--r-- | gsi_service.h | 8 | ||||
-rw-r--r-- | gsi_tool.cpp | 28 | ||||
-rw-r--r-- | libgsi.cpp | 10 |
6 files changed, 126 insertions, 32 deletions
diff --git a/aidl/android/gsi/IGsiService.aidl b/aidl/android/gsi/IGsiService.aidl index 57028fb..7cd8408 100644 --- a/aidl/android/gsi/IGsiService.aidl +++ b/aidl/android/gsi/IGsiService.aidl @@ -83,9 +83,11 @@ interface IGsiService { * Complete a GSI installation and mark it as bootable. The caller is * responsible for rebooting the device as soon as possible. * + * @param oneShot If true, the GSI will boot once and then disable itself. + * It can still be re-enabled again later with setGsiBootable. * @return INSTALL_* error code. */ - int setGsiBootable(); + int setGsiBootable(boolean oneShot); /** * Cancel an in-progress GSI install. @@ -127,4 +129,24 @@ interface IGsiService { * Returns true if a gsi is installed. */ boolean isGsiInstalled(); + + /* No GSI is installed. */ + const int BOOT_STATUS_NOT_INSTALLED = 0; + /* GSI is installed, but booting is disabled. */ + const int BOOT_STATUS_DISABLED = 1; + /* GSI is installed, but will only boot once. */ + const int BOOT_STATUS_SINGLE_BOOT = 2; + /* GSI is installed and bootable. */ + const int BOOT_STATUS_ENABLED = 3; + /* GSI will be wiped next boot. */ + const int BOOT_STATUS_WILL_WIPE = 4; + + /** + * Returns the boot status of a GSI. See the BOOT_STATUS constants in IGsiService. + * + * GSI_STATE_NOT_INSTALLED will be returned if no GSI installation has been + * fully completed. Any other value indicates a GSI is installed. If a GSI + * currently running, DISABLED or SINGLE_BOOT can still be returned. + */ + int getGsiBootStatus(); } diff --git a/file_paths.h b/file_paths.h index 0005b19..0fc255e 100644 --- a/file_paths.h +++ b/file_paths.h @@ -26,6 +26,7 @@ static constexpr char kSystemFile[] = "/data/gsi/system_gsi.img"; static constexpr char kGsiMetadataFolder[] = "/metadata/gsi"; static constexpr char kGsiLpMetadataFile[] = "/metadata/gsi/lp_metadata"; +static constexpr char kGsiOneShotBootFile[] = "/metadata/gsi/one_shot_boot"; // This file can contain the following values: // [int] - boot attempt counter, starting from 0 diff --git a/gsi_service.cpp b/gsi_service.cpp index 5b975ac..bf5410e 100644 --- a/gsi_service.cpp +++ b/gsi_service.cpp @@ -163,15 +163,15 @@ binder::Status GsiService::commitGsiChunkFromMemory(const std::vector<uint8_t>& return binder::Status::ok(); } -binder::Status GsiService::setGsiBootable(int* _aidl_return) { +binder::Status GsiService::setGsiBootable(bool one_shot, int* _aidl_return) { std::lock_guard<std::mutex> guard(main_lock_); if (installing_) { ENFORCE_SYSTEM; - *_aidl_return = SetGsiBootable() ? INSTALL_OK : INSTALL_ERROR_GENERIC; + *_aidl_return = SetGsiBootable(one_shot); } else { ENFORCE_SYSTEM_OR_SHELL; - *_aidl_return = ReenableGsi(); + *_aidl_return = ReenableGsi(one_shot); } return binder::Status::ok(); } @@ -241,6 +241,45 @@ binder::Status GsiService::cancelGsiInstall(bool* _aidl_return) { return binder::Status::ok(); } +binder::Status GsiService::getGsiBootStatus(int* _aidl_return) { + ENFORCE_SYSTEM_OR_SHELL; + std::lock_guard<std::mutex> guard(main_lock_); + + if (!IsGsiInstalled()) { + *_aidl_return = BOOT_STATUS_NOT_INSTALLED; + return binder::Status::ok(); + } + + std::string boot_key; + if (!GetInstallStatus(&boot_key)) { + PLOG(ERROR) << "read " << kGsiInstallStatusFile; + *_aidl_return = BOOT_STATUS_NOT_INSTALLED; + return binder::Status::ok(); + } + + bool single_boot = !access(kGsiOneShotBootFile, F_OK); + + if (boot_key == kInstallStatusWipe) { + // This overrides all other statuses. + *_aidl_return = BOOT_STATUS_WILL_WIPE; + } else if (boot_key == kInstallStatusDisabled) { + // A single-boot GSI will have a "disabled" status, because it's + // disabled immediately upon reading the one_shot_boot file. However, + // we still want to return SINGLE_BOOT, because it makes the + // transition clearer to the user. + if (IsGsiRunning() && single_boot) { + *_aidl_return = BOOT_STATUS_SINGLE_BOOT; + } else { + *_aidl_return = BOOT_STATUS_DISABLED; + } + } else if (single_boot) { + *_aidl_return = BOOT_STATUS_SINGLE_BOOT; + } else { + *_aidl_return = BOOT_STATUS_ENABLED; + } + return binder::Status::ok(); +} + binder::Status GsiService::getUserdataImageSize(int64_t* _aidl_return) { ENFORCE_SYSTEM; *_aidl_return = -1; @@ -605,33 +644,33 @@ bool GsiService::CommitGsiChunk(const void* data, size_t bytes) { return true; } -bool GsiService::SetGsiBootable() { +int GsiService::SetGsiBootable(bool one_shot) { if (gsi_bytes_written_ != gsi_size_) { // We cannot boot if the image is incomplete. LOG(ERROR) << "image incomplete; expected " << gsi_size_ << " bytes, waiting for " << (gsi_size_ - gsi_bytes_written_) << " bytes"; - return false; + return INSTALL_ERROR_GENERIC; } if (fsync(system_fd_)) { PLOG(ERROR) << "fsync failed"; - return false; + return INSTALL_ERROR_GENERIC; } // If these files moved, the metadata file will be invalid. - if (!CheckPinning(kUserdataFile) || !CheckPinning(kSystemFile)) { - return false; + if (!CheckPinning(kUserdataFile) || !CheckPinning(kSystemFile) || !SetBootMode(one_shot)) { + return INSTALL_ERROR_GENERIC; } if (!CreateMetadataFile(*metadata_.get()) || !CreateInstallStatusFile()) { - return false; + return INSTALL_ERROR_GENERIC; } PostInstallCleanup(); - return true; + return INSTALL_OK; } -int GsiService::ReenableGsi() { +int GsiService::ReenableGsi(bool one_shot) { if (!android::gsi::IsGsiInstalled()) { LOG(ERROR) << "no gsi installed - cannot re-enable"; return INSTALL_ERROR_GENERIC; @@ -671,7 +710,8 @@ int GsiService::ReenableGsi() { if (!metadata) { return INSTALL_ERROR_GENERIC; } - if (!CreateMetadataFile(*metadata.get()) || !CreateInstallStatusFile()) { + if (!CreateMetadataFile(*metadata.get()) || !SetBootMode(one_shot) || + !CreateInstallStatusFile()) { return INSTALL_ERROR_GENERIC; } return INSTALL_OK; @@ -725,6 +765,7 @@ bool GsiService::RemoveGsiFiles(bool wipeUserdata) { kSystemFile, kGsiInstallStatusFile, kGsiLpMetadataFile, + kGsiOneShotBootFile, }; if (wipeUserdata) { files.emplace_back(kUserdataFile); @@ -853,6 +894,22 @@ bool GsiService::AddPartitionFiemap(MetadataBuilder* builder, Partition* partiti return true; } +bool GsiService::SetBootMode(bool one_shot) { + if (one_shot) { + if (!android::base::WriteStringToFile("1", kGsiOneShotBootFile)) { + PLOG(ERROR) << "write " << kGsiOneShotBootFile; + return false; + } + } else if (!access(kGsiOneShotBootFile, F_OK)) { + std::string error; + if (!android::base::RemoveFileIfExists(kGsiOneShotBootFile, &error)) { + LOG(ERROR) << error; + return false; + } + } + return true; +} + bool GsiService::CreateInstallStatusFile() { if (!android::base::WriteStringToFile("0", kGsiInstallStatusFile)) { PLOG(ERROR) << "write " << kGsiInstallStatusFile; @@ -878,9 +935,9 @@ void GsiService::RunStartupTasks() { RemoveGsiFiles(true /* wipeUserdata */); } } else { - // NB: When kOnlyAllowSingleBoot is true, init will write "disabled" - // into the install_status file, which will cause GetBootAttempts to - // return false. Thus, we won't write "ok" here. + // NB: When single-boot is enabled, init will write "disabled" into the + // install_status file, which will cause GetBootAttempts to return + // false. Thus, we won't write "ok" here. int ignore; if (GetBootAttempts(boot_key, &ignore)) { // Mark the GSI as having successfully booted. diff --git a/gsi_service.h b/gsi_service.h index d2a87bd..1250b45 100644 --- a/gsi_service.h +++ b/gsi_service.h @@ -47,13 +47,14 @@ class GsiService : public BinderService<GsiService>, public BnGsiService { binder::Status commitGsiChunkFromMemory(const ::std::vector<uint8_t>& bytes, bool* _aidl_return) override; binder::Status cancelGsiInstall(bool* _aidl_return) override; - binder::Status setGsiBootable(int* _aidl_return) override; + binder::Status setGsiBootable(bool oneShot, int* _aidl_return) override; binder::Status removeGsiInstall(bool* _aidl_return) override; binder::Status disableGsiInstall(bool* _aidl_return) override; binder::Status isGsiRunning(bool* _aidl_return) override; binder::Status isGsiInstalled(bool* _aidl_return) override; binder::Status isGsiInstallInProgress(bool* _aidl_return) override; binder::Status getUserdataImageSize(int64_t* _aidl_return) override; + binder::Status getGsiBootStatus(int* _aidl_return) override; static char const* getServiceName() { return kGsiServiceName; } @@ -77,8 +78,8 @@ class GsiService : public BinderService<GsiService>, public BnGsiService { bool FormatUserdata(); bool CommitGsiChunk(int stream_fd, int64_t bytes); bool CommitGsiChunk(const void* data, size_t bytes); - bool SetGsiBootable(); - int ReenableGsi(); + int SetGsiBootable(bool one_shot); + int ReenableGsi(bool one_shot); bool DisableGsiInstall(); bool AddPartitionFiemap(android::fs_mgr::MetadataBuilder* builder, android::fs_mgr::Partition* partition, const Image& image); @@ -87,6 +88,7 @@ class GsiService : public BinderService<GsiService>, public BnGsiService { int* error); bool CreateInstallStatusFile(); bool CreateMetadataFile(const android::fs_mgr::LpMetadata& metadata); + bool SetBootMode(bool one_shot); void PostInstallCleanup(); void StartAsyncOperation(const std::string& step, int64_t total_bytes); diff --git a/gsi_tool.cpp b/gsi_tool.cpp index 94cca16..3403cd1 100644 --- a/gsi_tool.cpp +++ b/gsi_tool.cpp @@ -284,7 +284,7 @@ static int Install(sp<IGsiService> gsid, int argc, char** argv) { progress.Finish(); - status = gsid->setGsiBootable(&error); + status = gsid->setGsiBootable(true, &error); if (!status.isOk() || error != IGsiService::INSTALL_OK) { std::cerr << "Could not make live image bootable: " << ErrorMessage(status, error) << "\n"; return EX_SOFTWARE; @@ -343,10 +343,23 @@ static int Status(sp<IGsiService> gsid, int argc, char** /* argv */) { return 0; } -static int Enable(sp<IGsiService> gsid, int argc, char** /* argv */) { - if (argc > 1) { - std::cerr << "Unrecognized arguments to enable." << std::endl; - return EX_USAGE; +static int Enable(sp<IGsiService> gsid, int argc, char** argv) { + bool one_shot = false; + + struct option options[] = { + {"single-boot", no_argument, nullptr, 's'}, + {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; + default: + std::cerr << "Unrecognized argument to enable\n"; + return EX_USAGE; + } } bool installed = false; @@ -364,7 +377,7 @@ static int Enable(sp<IGsiService> gsid, int argc, char** /* argv */) { } int error; - auto status = gsid->setGsiBootable(&error); + auto status = gsid->setGsiBootable(one_shot, &error); if (!status.isOk() || error != IGsiService::INSTALL_OK) { std::cerr << "Error re-enabling GSI: " << ErrorMessage(status, error) << "\n"; return EX_SOFTWARE; @@ -404,7 +417,8 @@ static int usage(int /* argc */, char* argv[]) { " %s <disable|install|wipe|status> [options]\n" "\n" " disable Disable the currently installed GSI.\n" - " enable Enable a previously disabled GSI.\n" + " enable [-s, --single-boot]\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" @@ -42,10 +42,6 @@ bool IsGsiInstalled() { return !access(kGsiInstallStatusFile, F_OK); } -// If true, we only ever allow a single boot into GSI. Rebooting the device -// will bring the device back to its normal system image. -static constexpr bool kOnlyAllowSingleBoot = true; - static bool WriteAndSyncFile(const std::string& data, const std::string& file) { unique_fd fd(open(file.c_str(), O_WRONLY | O_NOFOLLOW | O_CLOEXEC)); if (fd < 0) { @@ -78,9 +74,11 @@ static bool CanBootIntoGsi(std::string* error) { } std::string new_key; - if (kOnlyAllowSingleBoot) { + if (!access(kGsiOneShotBootFile, F_OK)) { // Mark the GSI as disabled. This only affects the next boot, not - // the current boot. + // the current boot. Note that we leave the one_shot status behind. + // This is so IGsiService can still return GSI_STATE_SINGLE_BOOT + // while the GSI is running. new_key = kInstallStatusDisabled; } else { new_key = std::to_string(attempts + 1); |