/* * 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 "gsi_service.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "file_paths.h" #include "libgsi_private.h" namespace android { namespace gsi { using namespace std::literals; using namespace android::dm; using namespace android::fs_mgr; using namespace android::fiemap_writer; using android::base::StringPrintf; using android::base::unique_fd; static constexpr char kUserdataDevice[] = "/dev/block/by-name/userdata"; // The default size of userdata.img for GSI. // We are looking for /data to have atleast 40% free space static constexpr uint32_t kMinimumFreeSpaceThreshold = 40; // We determine the fragmentation by making sure the files // we create don't have more than 16 extents. static constexpr uint32_t kMaximumExtents = 512; // Default userdata image size. static constexpr int64_t kDefaultUserdataSize = int64_t(8) * 1024 * 1024 * 1024; static constexpr std::chrono::milliseconds kDmTimeout = 5000ms; void GsiService::Register() { auto ret = android::BinderService::publish(); if (ret != android::OK) { LOG(FATAL) << "Could not register gsi service: " << ret; } } GsiService::GsiService() { progress_ = {}; } GsiService::~GsiService() { PostInstallCleanup(); } #define ENFORCE_SYSTEM \ do { \ binder::Status status = CheckUid(); \ if (!status.isOk()) return status; \ } while (0) #define ENFORCE_SYSTEM_OR_SHELL \ do { \ binder::Status status = CheckUid(AccessLevel::SystemOrShell); \ if (!status.isOk()) return status; \ } while (0) binder::Status GsiService::startGsiInstall(int64_t gsiSize, int64_t userdataSize, bool wipeUserdata, int* _aidl_return) { GsiInstallParams params; params.gsiSize = gsiSize; params.userdataSize = userdataSize; params.wipeUserdata = wipeUserdata; return beginGsiInstall(params, _aidl_return); } binder::Status GsiService::beginGsiInstall(const GsiInstallParams& given_params, int* _aidl_return) { ENFORCE_SYSTEM; std::lock_guard guard(main_lock_); // Make sure any interrupted installations are cleaned up. PostInstallCleanup(); // Do some precursor validation on the arguments before diving into the // install process. GsiInstallParams params = given_params; if (int status = ValidateInstallParams(¶ms)) { *_aidl_return = status; return binder::Status::ok(); } int status = StartInstall(params); if (status != INSTALL_OK) { // Perform local cleanup and delete any lingering files. PostInstallCleanup(); RemoveGsiFiles(params.installDir, wipe_userdata_on_failure_); } *_aidl_return = status; // Clear the progress indicator. UpdateProgress(STATUS_NO_OPERATION, 0); return binder::Status::ok(); } binder::Status GsiService::commitGsiChunkFromStream(const android::os::ParcelFileDescriptor& stream, int64_t bytes, bool* _aidl_return) { ENFORCE_SYSTEM; std::lock_guard guard(main_lock_); *_aidl_return = CommitGsiChunk(stream.get(), bytes); // Clear the progress indicator. UpdateProgress(STATUS_NO_OPERATION, 0); return binder::Status::ok(); } void GsiService::StartAsyncOperation(const std::string& step, int64_t total_bytes) { std::lock_guard guard(progress_lock_); progress_.step = step; progress_.status = STATUS_WORKING; progress_.bytes_processed = 0; progress_.total_bytes = total_bytes; } void GsiService::UpdateProgress(int status, int64_t bytes_processed) { std::lock_guard guard(progress_lock_); progress_.status = status; if (status == STATUS_COMPLETE) { progress_.bytes_processed = progress_.total_bytes; } else { progress_.bytes_processed = bytes_processed; } } binder::Status GsiService::getInstallProgress(::android::gsi::GsiProgress* _aidl_return) { ENFORCE_SYSTEM; std::lock_guard guard(progress_lock_); *_aidl_return = progress_; return binder::Status::ok(); } binder::Status GsiService::commitGsiChunkFromMemory(const std::vector& bytes, bool* _aidl_return) { ENFORCE_SYSTEM; std::lock_guard guard(main_lock_); *_aidl_return = CommitGsiChunk(bytes.data(), bytes.size()); return binder::Status::ok(); } binder::Status GsiService::setGsiBootable(bool one_shot, int* _aidl_return) { std::lock_guard guard(main_lock_); if (installing_) { ENFORCE_SYSTEM; int error = SetGsiBootable(one_shot); PostInstallCleanup(); if (error) { RemoveGsiFiles(install_dir_, wipe_userdata_on_failure_); *_aidl_return = error; } else { *_aidl_return = INSTALL_OK; } } else { ENFORCE_SYSTEM_OR_SHELL; *_aidl_return = ReenableGsi(one_shot); PostInstallCleanup(); } return binder::Status::ok(); } binder::Status GsiService::isGsiEnabled(bool* _aidl_return) { ENFORCE_SYSTEM_OR_SHELL; std::lock_guard guard(main_lock_); std::string boot_key; if (!GetInstallStatus(&boot_key)) { *_aidl_return = false; } else { *_aidl_return = (boot_key == kInstallStatusOk); } return binder::Status::ok(); } binder::Status GsiService::removeGsiInstall(bool* _aidl_return) { ENFORCE_SYSTEM_OR_SHELL; std::lock_guard guard(main_lock_); // Just in case an install was left hanging. std::string install_dir; if (installing_) { install_dir = install_dir_; PostInstallCleanup(); } else { install_dir = GetInstalledImageDir(); } if (IsGsiRunning()) { // Can't remove gsi files while running. *_aidl_return = UninstallGsi(); } else { *_aidl_return = RemoveGsiFiles(install_dir, true /* wipeUserdata */); } return binder::Status::ok(); } binder::Status GsiService::disableGsiInstall(bool* _aidl_return) { ENFORCE_SYSTEM_OR_SHELL; std::lock_guard guard(main_lock_); *_aidl_return = DisableGsiInstall(); return binder::Status::ok(); } binder::Status GsiService::isGsiRunning(bool* _aidl_return) { ENFORCE_SYSTEM_OR_SHELL; std::lock_guard guard(main_lock_); *_aidl_return = IsGsiRunning(); return binder::Status::ok(); } binder::Status GsiService::isGsiInstalled(bool* _aidl_return) { ENFORCE_SYSTEM_OR_SHELL; std::lock_guard guard(main_lock_); *_aidl_return = IsGsiInstalled(); return binder::Status::ok(); } binder::Status GsiService::isGsiInstallInProgress(bool* _aidl_return) { ENFORCE_SYSTEM_OR_SHELL; std::lock_guard guard(main_lock_); *_aidl_return = installing_; return binder::Status::ok(); } binder::Status GsiService::cancelGsiInstall(bool* _aidl_return) { ENFORCE_SYSTEM; should_abort_ = true; std::lock_guard guard(main_lock_); should_abort_ = false; if (installing_) { PostInstallCleanup(); RemoveGsiFiles(install_dir_, wipe_userdata_on_failure_); } *_aidl_return = true; return binder::Status::ok(); } binder::Status GsiService::getGsiBootStatus(int* _aidl_return) { ENFORCE_SYSTEM_OR_SHELL; std::lock_guard 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 (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; std::lock_guard guard(main_lock_); *_aidl_return = -1; if (installing_) { // Size has already been computed. *_aidl_return = userdata_size_; } else if (IsGsiRunning()) { // :TODO: libdm unique_fd fd(open(kUserdataDevice, O_RDONLY | O_NOFOLLOW | O_CLOEXEC)); if (fd < 0) { PLOG(ERROR) << "open " << kUserdataDevice; return binder::Status::ok(); } int64_t size; if (ioctl(fd, BLKGETSIZE64, &size)) { PLOG(ERROR) << "BLKGETSIZE64 " << kUserdataDevice; return binder::Status::ok(); } *_aidl_return = size; } else { // Stat the size of the userdata file. auto userdata_gsi = GetInstalledImagePath("userdata_gsi"); struct stat s; if (stat(userdata_gsi.c_str(), &s)) { if (errno != ENOENT) { PLOG(ERROR) << "open " << userdata_gsi; return binder::Status::ok(); } *_aidl_return = 0; } else { *_aidl_return = s.st_size; } } return binder::Status::ok(); } binder::Status GsiService::getInstalledGsiImageDir(std::string* _aidl_return) { ENFORCE_SYSTEM; std::lock_guard guard(main_lock_); if (IsGsiInstalled()) { *_aidl_return = GetInstalledImageDir(); } return binder::Status::ok(); } binder::Status GsiService::wipeGsiUserdata(int* _aidl_return) { ENFORCE_SYSTEM_OR_SHELL; std::lock_guard guard(main_lock_); if (IsGsiRunning() || !IsGsiInstalled()) { *_aidl_return = IGsiService::INSTALL_ERROR_GENERIC; return binder::Status::ok(); } *_aidl_return = WipeUserdata(); return binder::Status::ok(); } binder::Status GsiService::CheckUid(AccessLevel level) { std::vector allowed_uids{AID_ROOT, AID_SYSTEM}; if (level == AccessLevel::SystemOrShell) { allowed_uids.push_back(AID_SHELL); } uid_t uid = IPCThreadState::self()->getCallingUid(); for (const auto& allowed_uid : allowed_uids) { if (allowed_uid == uid) { return binder::Status::ok(); } } auto message = StringPrintf("UID %d is not allowed", uid); return binder::Status::fromExceptionCode(binder::Status::EX_SECURITY, String8(message.c_str())); } void GsiService::PostInstallCleanup() { // This must be closed before unmapping partitions. system_writer_ = nullptr; const auto& dm = DeviceMapper::Instance(); if (dm.GetState("userdata_gsi") != DmDeviceState::INVALID) { DestroyLogicalPartition("userdata_gsi", kDmTimeout); } if (dm.GetState("system_gsi") != DmDeviceState::INVALID) { DestroyLogicalPartition("system_gsi", kDmTimeout); } installing_ = false; partitions_ .clear(); } static bool IsExternalStoragePath(const std::string& path) { if (!android::base::StartsWith(path, "/mnt/media_rw/")) { return false; } unique_fd fd(open(path.c_str(), O_RDONLY | O_CLOEXEC | O_NOFOLLOW)); if (fd < 0) { PLOG(ERROR) << "open failed: " << path; return false; } struct statfs info; if (fstatfs(fd, &info)) { PLOG(ERROR) << "statfs failed: " << path; return false; } LOG(ERROR) << "fs type: " << info.f_type; return info.f_type == MSDOS_SUPER_MAGIC; } int GsiService::ValidateInstallParams(GsiInstallParams* params) { // If no install path was specified, use the default path. We also allow // specifying the top-level folder, and then we choose the correct location // underneath. if (params->installDir.empty() || params->installDir == "/data/gsi") { params->installDir = kDefaultGsiImageFolder; } // Normalize the path and add a trailing slash. std::string origInstallDir = params->installDir; if (!android::base::Realpath(origInstallDir, ¶ms->installDir)) { PLOG(ERROR) << "realpath failed: " << origInstallDir; return INSTALL_ERROR_GENERIC; } // Ensure the path ends in / for consistency. Even though GetImagePath() // does this already, we want it to appear this way in install_dir. if (!android::base::EndsWith(params->installDir, "/")) { params->installDir += "/"; } // Currently, we can only install to /data/gsi/ or external storage. if (IsExternalStoragePath(params->installDir)) { Fstab fstab; if (!ReadDefaultFstab(&fstab)) { LOG(ERROR) << "cannot read default fstab"; return INSTALL_ERROR_GENERIC; } FstabEntry* system = GetEntryForMountPoint(&fstab, "/system"); if (!system) { LOG(ERROR) << "cannot find /system fstab entry"; return INSTALL_ERROR_GENERIC; } if (fs_mgr_verity_is_check_at_most_once(*system)) { LOG(ERROR) << "cannot install GSIs to external media if verity uses check_at_most_once"; return INSTALL_ERROR_GENERIC; } } else if (params->installDir != kDefaultGsiImageFolder) { LOG(ERROR) << "cannot install GSI to " << params->installDir; return INSTALL_ERROR_GENERIC; } if (params->gsiSize % LP_SECTOR_SIZE) { LOG(ERROR) << "GSI size " << params->gsiSize << " is not a multiple of " << LP_SECTOR_SIZE; return INSTALL_ERROR_GENERIC; } if (params->userdataSize % LP_SECTOR_SIZE) { LOG(ERROR) << "userdata size " << params->userdataSize << " is not a multiple of " << LP_SECTOR_SIZE; return INSTALL_ERROR_GENERIC; } return INSTALL_OK; } int GsiService::StartInstall(const GsiInstallParams& params) { installing_ = true; userdata_block_size_ = 0; system_block_size_ = 0; gsi_size_ = params.gsiSize; userdata_size_ = (params.userdataSize) ? params.userdataSize : kDefaultUserdataSize; wipe_userdata_ = params.wipeUserdata; can_use_devicemapper_ = false; gsi_bytes_written_ = 0; install_dir_ = params.installDir; userdata_gsi_path_ = GetImagePath(install_dir_, "userdata_gsi"); system_gsi_path_ = GetImagePath(install_dir_, "system_gsi"); // Only rm userdata_gsi if one didn't already exist. wipe_userdata_on_failure_ = wipe_userdata_ || access(userdata_gsi_path_.c_str(), F_OK); if (int status = PerformSanityChecks()) { return status; } if (int status = PreallocateFiles()) { return status; } if (int status = DetermineReadWriteMethod()) { return status; } if (!FormatUserdata()) { return INSTALL_ERROR_GENERIC; } // Map system_gsi so we can write to it. system_writer_ = OpenPartition("system_gsi"); if (!system_writer_) { return INSTALL_ERROR_GENERIC; } return INSTALL_OK; } int GsiService::DetermineReadWriteMethod() { // If there is a device-mapper node wrapping the block device, then we're // able to create another node around it; the dm layer does not carry the // exclusion lock down the stack when a mount occurs. // // If there is no intermediate device-mapper node, then partitions cannot be // opened writable due to sepolicy and exclusivity of having a mounted // filesystem. This should only happen on devices with no encryption, or // devices with FBE and no metadata encryption. For these cases it suffices // to perform normal file writes to /data/gsi (which is unencrypted). std::string block_device; if (!FiemapWriter::GetBlockDeviceForFile(system_gsi_path_.c_str(), &block_device, &can_use_devicemapper_)) { return INSTALL_ERROR_GENERIC; } if (install_dir_ != kDefaultGsiImageFolder && can_use_devicemapper_) { // Never use device-mapper on external media. We don't support adopted // storage yet, and accidentally using device-mapper could be dangerous // as we hardcode the userdata device as backing storage. LOG(ERROR) << "unexpected device-mapper node used to mount external media"; return INSTALL_ERROR_GENERIC; } return INSTALL_OK; } std::string GsiService::GetImagePath(const std::string& image_dir, const std::string& name) { std::string dir = image_dir; if (!android::base::EndsWith(dir, "/")) { dir += "/"; } return dir + name + ".img"; } std::string GsiService::GetInstalledImageDir() { // If there's no install left, just return /data/gsi since that's where // installs go by default. std::string dir; if (android::base::ReadFileToString(kGsiInstallDirFile, &dir)) { return dir; } return kDefaultGsiImageFolder; } std::string GsiService::GetInstalledImagePath(const std::string& name) { return GetImagePath(GetInstalledImageDir(), name); } int GsiService::PerformSanityChecks() { if (gsi_size_ < 0) { LOG(ERROR) << "image size " << gsi_size_ << " is negative"; return INSTALL_ERROR_GENERIC; } if (android::gsi::IsGsiRunning()) { LOG(ERROR) << "cannot install gsi inside a live gsi"; return INSTALL_ERROR_GENERIC; } struct statvfs sb; if (statvfs(install_dir_.c_str(), &sb)) { PLOG(ERROR) << "failed to read file system stats"; return INSTALL_ERROR_GENERIC; } // This is the same as android::vold::GetFreebytes() but we also // need the total file system size so we open code it here. uint64_t free_space = 1ULL * sb.f_bavail * sb.f_frsize; uint64_t fs_size = sb.f_blocks * sb.f_frsize; if (free_space <= (gsi_size_ + userdata_size_)) { LOG(ERROR) << "not enough free space (only " << free_space << " bytes available)"; return INSTALL_ERROR_NO_SPACE; } // We are asking for 40% of the /data to be empty. // TODO: may be not hard code it like this double free_space_percent = ((1.0 * free_space) / fs_size) * 100; if (free_space_percent < kMinimumFreeSpaceThreshold) { LOG(ERROR) << "free space " << static_cast(free_space_percent) << "% is below the minimum threshold of " << kMinimumFreeSpaceThreshold << "%"; return INSTALL_ERROR_FILE_SYSTEM_CLUTTERED; } return INSTALL_OK; } int GsiService::PreallocateFiles() { if (wipe_userdata_) { SplitFiemap::RemoveSplitFiles(userdata_gsi_path_); } SplitFiemap::RemoveSplitFiles(system_gsi_path_); // TODO: trigger GC from fiemap writer. // Create fallocated files. if (int status = PreallocateUserdata()) { return status; } if (int status = PreallocateSystem()) { return status; } // Save the extent information in liblp. metadata_ = CreateMetadata(); if (!metadata_) { return INSTALL_ERROR_GENERIC; } UpdateProgress(STATUS_COMPLETE, 0); return INSTALL_OK; } int GsiService::PreallocateUserdata() { int error; std::unique_ptr userdata_image; if (wipe_userdata_ || access(userdata_gsi_path_.c_str(), F_OK)) { StartAsyncOperation("create userdata", userdata_size_); userdata_image = CreateFiemapWriter(userdata_gsi_path_, userdata_size_, &error); if (!userdata_image) { LOG(ERROR) << "Could not create userdata image: " << userdata_gsi_path_; return error; } // Signal that we need to reformat userdata. wipe_userdata_ = true; } else { userdata_image = CreateFiemapWriter(userdata_gsi_path_, 0, &error); if (!userdata_image) { LOG(ERROR) << "Could not open userdata image: " << userdata_gsi_path_; return error; } if (userdata_size_ && userdata_image->size() < userdata_size_) { // :TODO: need to fallocate more blocks and resizefs. } userdata_size_ = userdata_image->size(); } userdata_block_size_ = userdata_image->block_size(); Image image = { .writer = std::move(userdata_image), .actual_size = userdata_size_, }; partitions_.emplace(std::make_pair("userdata_gsi", std::move(image))); return INSTALL_OK; } int GsiService::PreallocateSystem() { StartAsyncOperation("create system", gsi_size_); int error; auto system_image = CreateFiemapWriter(system_gsi_path_, gsi_size_, &error); if (!system_image) { return error; } system_block_size_ = system_image->block_size(); Image image = { .writer = std::move(system_image), .actual_size = gsi_size_, }; partitions_.emplace(std::make_pair("system_gsi", std::move(image))); return INSTALL_OK; } std::unique_ptr GsiService::CreateFiemapWriter(const std::string& path, uint64_t size, int* error) { bool create = (size != 0); std::function progress; if (create) { // TODO: allow cancelling inside cancelGsiInstall. progress = [this](uint64_t bytes, uint64_t /* total */) -> bool { UpdateProgress(STATUS_WORKING, bytes); if (should_abort_) return false; return true; }; } std::unique_ptr file; if (!size) { file = SplitFiemap::Open(path); } else { file = SplitFiemap::Create(path, size, 0, std::move(progress)); } if (!file) { LOG(ERROR) << "failed to create or open " << path; *error = INSTALL_ERROR_GENERIC; return nullptr; } uint64_t extents = file->extents().size(); if (extents > kMaximumExtents) { LOG(ERROR) << "file " << path << " has too many extents: " << extents; *error = INSTALL_ERROR_FILE_SYSTEM_CLUTTERED; return nullptr; } return file; } // Write data through an fd. class FdWriter final : public GsiService::WriteHelper { public: FdWriter(const std::string& path, unique_fd&& fd) : path_(path), fd_(std::move(fd)) {} bool Write(const void* data, uint64_t bytes) override { return android::base::WriteFully(fd_, data, bytes); } bool Flush() override { if (fsync(fd_)) { PLOG(ERROR) << "fsync failed: " << path_; return false; } return true; } uint64_t Size() override { return get_block_device_size(fd_); } private: std::string path_; unique_fd fd_; }; // Write data through a SplitFiemap. class SplitFiemapWriter final : public GsiService::WriteHelper { public: explicit SplitFiemapWriter(SplitFiemap* writer) : writer_(writer) {} bool Write(const void* data, uint64_t bytes) override { return writer_->Write(data, bytes); } bool Flush() override { return writer_->Flush(); } uint64_t Size() override { return writer_->size(); } private: SplitFiemap* writer_; }; std::unique_ptr GsiService::OpenPartition(const std::string& name) { if (can_use_devicemapper_) { std::string path; if (!CreateLogicalPartition(kUserdataDevice, *metadata_.get(), name, true, kDmTimeout, &path)) { LOG(ERROR) << "Error creating device-mapper node for " << name; return {}; } static const int kOpenFlags = O_RDWR | O_NOFOLLOW | O_CLOEXEC; unique_fd fd(open(path.c_str(), kOpenFlags)); if (fd < 0) { PLOG(ERROR) << "could not open " << path; } return std::make_unique(GetImagePath(install_dir_, name), std::move(fd)); } auto iter = partitions_.find(name); if (iter == partitions_.end()) { LOG(ERROR) << "could not find partition " << name; return {}; } return std::make_unique(iter->second.writer.get()); } bool GsiService::CommitGsiChunk(int stream_fd, int64_t bytes) { StartAsyncOperation("write gsi", gsi_size_); if (bytes < 0) { LOG(ERROR) << "chunk size " << bytes << " is negative"; return false; } auto buffer = std::make_unique(system_block_size_); int progress = -1; uint64_t remaining = bytes; while (remaining) { // :TODO: check file pin status! size_t max_to_read = std::min(system_block_size_, remaining); ssize_t rv = TEMP_FAILURE_RETRY(read(stream_fd, buffer.get(), max_to_read)); if (rv < 0) { PLOG(ERROR) << "read gsi chunk"; return false; } if (rv == 0) { LOG(ERROR) << "no bytes left in stream"; return false; } if (!CommitGsiChunk(buffer.get(), rv)) { return false; } CHECK(static_cast(rv) <= remaining); remaining -= rv; // Only update the progress when the % (or permille, in this case) // significantly changes. int new_progress = ((gsi_size_ - remaining) * 1000) / gsi_size_; if (new_progress != progress) { UpdateProgress(STATUS_WORKING, gsi_size_ - remaining); } } UpdateProgress(STATUS_COMPLETE, gsi_size_); return true; } bool GsiService::CommitGsiChunk(const void* data, size_t bytes) { if (!installing_) { LOG(ERROR) << "no gsi installation in progress"; return false; } if (static_cast(bytes) > gsi_size_ - gsi_bytes_written_) { // We cannot write past the end of the image file. LOG(ERROR) << "chunk size " << bytes << " exceeds remaining image size (" << gsi_size_ << " expected, " << gsi_bytes_written_ << " written)"; return false; } if (!system_writer_->Write(data, bytes)) { PLOG(ERROR) << "write failed"; return false; } gsi_bytes_written_ += bytes; return true; } 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 INSTALL_ERROR_GENERIC; } if (!system_writer_->Flush()) { return INSTALL_ERROR_GENERIC; } // If files moved (are no longer pinned), the metadata file will be invalid. for (const auto& [name, image] : partitions_) { if (!image.writer->HasPinnedExtents()) { LOG(ERROR) << name << " no longer has pinned extents"; return INSTALL_ERROR_GENERIC; } } // Remember the installation directory. if (!android::base::WriteStringToFile(install_dir_, kGsiInstallDirFile)) { PLOG(ERROR) << "write failed: " << kGsiInstallDirFile; return INSTALL_ERROR_GENERIC; } // Note: create the install status file last, since this is the actual boot // indicator. if (!CreateMetadataFile() || !SetBootMode(one_shot) || !CreateInstallStatusFile()) { return INSTALL_ERROR_GENERIC; } return INSTALL_OK; } int GsiService::ReenableGsi(bool one_shot) { if (!android::gsi::IsGsiInstalled()) { LOG(ERROR) << "no gsi installed - cannot re-enable"; return INSTALL_ERROR_GENERIC; } std::string boot_key; if (!GetInstallStatus(&boot_key)) { PLOG(ERROR) << "read " << kGsiInstallStatusFile; return INSTALL_ERROR_GENERIC; } if (boot_key != kInstallStatusDisabled) { LOG(ERROR) << "GSI is not currently disabled"; return INSTALL_ERROR_GENERIC; } if (IsGsiRunning()) { if (!SetBootMode(one_shot) || !CreateInstallStatusFile()) { return INSTALL_ERROR_GENERIC; } return INSTALL_OK; } // Note: this metadata is only used to recover the original partition sizes. // We do not trust the extent information, which will get rebuilt later. auto old_metadata = ReadFromImageFile(kGsiLpMetadataFile); if (!old_metadata) { LOG(ERROR) << "GSI install is incomplete"; return INSTALL_ERROR_GENERIC; } // Set up enough installer state so that we can use various helper // methods. // // TODO(dvander) Extract all of the installer state into a separate // class so this is more manageable. install_dir_ = GetInstalledImageDir(); system_gsi_path_ = GetImagePath(install_dir_, "system_gsi"); if (int error = DetermineReadWriteMethod()) { return error; } // Recover parition information. Image userdata_image; if (int error = GetExistingImage(*old_metadata.get(), "userdata_gsi", &userdata_image)) { return error; } partitions_.emplace(std::make_pair("userdata_gsi", std::move(userdata_image))); Image system_image; if (int error = GetExistingImage(*old_metadata.get(), "system_gsi", &system_image)) { return error; } partitions_.emplace(std::make_pair("system_gsi", std::move(system_image))); metadata_ = CreateMetadata(); if (!metadata_) { return INSTALL_ERROR_GENERIC; } if (!CreateMetadataFile() || !SetBootMode(one_shot) || !CreateInstallStatusFile()) { return INSTALL_ERROR_GENERIC; } return INSTALL_OK; } int GsiService::WipeUserdata() { // Note: this metadata is only used to recover the original partition sizes. // We do not trust the extent information, which will get rebuilt later. auto old_metadata = ReadFromImageFile(kGsiLpMetadataFile); if (!old_metadata) { LOG(ERROR) << "GSI install is incomplete"; return INSTALL_ERROR_GENERIC; } install_dir_ = GetInstalledImageDir(); system_gsi_path_ = GetImagePath(install_dir_, "system_gsi"); if (int error = DetermineReadWriteMethod()) { return error; } // Recover parition information. Image userdata_image; if (int error = GetExistingImage(*old_metadata.get(), "userdata_gsi", &userdata_image)) { return error; } partitions_.emplace(std::make_pair("userdata_gsi", std::move(userdata_image))); metadata_ = CreateMetadata(); if (!metadata_) { return INSTALL_ERROR_GENERIC; } auto writer = OpenPartition("userdata_gsi"); if (!writer) { return IGsiService::INSTALL_ERROR_GENERIC; } // Wipe the first 1MiB of the device, ensuring both the first block and // the superblock are destroyed. static constexpr uint64_t kEraseSize = 1024 * 1024; std::string zeroes(4096, 0); uint64_t erase_size = std::min(kEraseSize, writer->Size()); for (uint64_t i = 0; i < erase_size; i += zeroes.size()) { if (!writer->Write(zeroes.data(), zeroes.size())) { PLOG(ERROR) << "write userdata_gsi"; return IGsiService::INSTALL_ERROR_GENERIC; } } return INSTALL_OK; } static uint64_t GetPartitionSize(const LpMetadata& metadata, const LpMetadataPartition& partition) { uint64_t total = 0; for (size_t i = 0; i < partition.num_extents; i++) { const auto& extent = metadata.extents[partition.first_extent_index + i]; if (extent.target_type != LP_TARGET_TYPE_LINEAR) { LOG(ERROR) << "non-linear extent detected"; return 0; } total += extent.num_sectors * LP_SECTOR_SIZE; } return total; } static uint64_t GetPartitionSize(const LpMetadata& metadata, const std::string& name) { for (const auto& partition : metadata.partitions) { if (GetPartitionName(partition) == name) { return GetPartitionSize(metadata, partition); } } return 0; } int GsiService::GetExistingImage(const LpMetadata& metadata, const std::string& name, Image* image) { int error; std::string path = GetInstalledImagePath(name); auto writer = CreateFiemapWriter(path.c_str(), 0, &error); if (!writer) { return error; } // Even after recovering the FIEMAP, we also need to know the exact intended // size of the image, since FiemapWriter may have extended the final block. uint64_t actual_size = GetPartitionSize(metadata, name); if (!actual_size) { LOG(ERROR) << "Could not determine the pre-existing size of " << name; return INSTALL_ERROR_GENERIC; } image->writer = std::move(writer); image->actual_size = actual_size; return INSTALL_OK; } bool GsiService::RemoveGsiFiles(const std::string& install_dir, bool wipeUserdata) { bool ok = true; std::string message; if (!SplitFiemap::RemoveSplitFiles(GetImagePath(install_dir, "system_gsi"), &message)) { LOG(ERROR) << message; ok = false; } if (wipeUserdata && !SplitFiemap::RemoveSplitFiles(GetImagePath(install_dir, "userdata_gsi"), &message)) { LOG(ERROR) << message; ok = false; } std::vector files{ kGsiInstallStatusFile, kGsiLpMetadataFile, kGsiOneShotBootFile, kGsiInstallDirFile, }; for (const auto& file : files) { if (!android::base::RemoveFileIfExists(file, &message)) { LOG(ERROR) << message; ok = false; } } return ok; } bool GsiService::DisableGsiInstall() { if (!android::gsi::IsGsiInstalled()) { LOG(ERROR) << "cannot disable gsi install - no install detected"; return false; } if (installing_) { LOG(ERROR) << "cannot disable gsi during GSI installation"; return false; } if (!DisableGsi()) { PLOG(ERROR) << "could not write gsi status"; return false; } return true; } std::unique_ptr GsiService::CreateMetadata() { std::string data_device_path; if (install_dir_ == kDefaultGsiImageFolder && !access(kUserdataDevice, F_OK)) { data_device_path = kUserdataDevice; } else { auto writer = partitions_["system_gsi"].writer.get(); data_device_path = writer->bdev_path(); } auto data_device_name = android::base::Basename(data_device_path); PartitionOpener opener; BlockDeviceInfo data_device_info; if (!opener.GetInfo(data_device_path, &data_device_info)) { LOG(ERROR) << "Error reading userdata partition"; return nullptr; } std::vector block_devices = {data_device_info}; auto builder = MetadataBuilder::New(block_devices, data_device_name, 128 * 1024, 1); if (!builder) { LOG(ERROR) << "Error creating metadata builder"; return nullptr; } builder->IgnoreSlotSuffixing(); for (const auto& [name, image] : partitions_) { uint32_t flags = LP_PARTITION_ATTR_NONE; if (name == "system_gsi") { flags |= LP_PARTITION_ATTR_READONLY; } Partition* partition = builder->AddPartition(name, flags); if (!partition) { LOG(ERROR) << "Error adding " << name << " to partition table"; return nullptr; } if (!AddPartitionFiemap(builder.get(), partition, image, data_device_name)) { return nullptr; } } auto metadata = builder->Export(); if (!metadata) { LOG(ERROR) << "Error exporting partition table"; return nullptr; } return metadata; } bool GsiService::CreateMetadataFile() { if (!WriteToImageFile(kGsiLpMetadataFile, *metadata_.get())) { LOG(ERROR) << "Error writing GSI partition table image"; return false; } return true; } bool GsiService::FormatUserdata() { auto writer = OpenPartition("userdata_gsi"); if (!writer) { return false; } // libcutils checks the first 4K, no matter the block size. std::string zeroes(4096, 0); if (!writer->Write(zeroes.data(), zeroes.size())) { PLOG(ERROR) << "write userdata_gsi"; return false; } return true; } bool GsiService::AddPartitionFiemap(MetadataBuilder* builder, Partition* partition, const Image& image, const std::string& block_device) { uint64_t sectors_needed = image.actual_size / LP_SECTOR_SIZE; for (const auto& extent : image.writer->extents()) { // :TODO: block size check for length, not sector size if (extent.fe_length % LP_SECTOR_SIZE != 0) { LOG(ERROR) << "Extent is not sector-aligned: " << extent.fe_length; return false; } if (extent.fe_physical % LP_SECTOR_SIZE != 0) { LOG(ERROR) << "Extent physical sector is not sector-aligned: " << extent.fe_physical; return false; } uint64_t num_sectors = std::min(static_cast(extent.fe_length / LP_SECTOR_SIZE), sectors_needed); if (!num_sectors || !sectors_needed) { // This should never happen, but we include it just in case. It would // indicate that the last filesystem block had multiple extents. LOG(WARNING) << "FiemapWriter allocated extra blocks"; break; } uint64_t physical_sector = extent.fe_physical / LP_SECTOR_SIZE; if (!builder->AddLinearExtent(partition, block_device, num_sectors, physical_sector)) { LOG(ERROR) << "Could not add extent to lp metadata"; return false; } sectors_needed -= num_sectors; } 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; return false; } return true; } void GsiService::RunStartupTasks() { if (!IsGsiInstalled()) { return; } std::string boot_key; if (!GetInstallStatus(&boot_key)) { PLOG(ERROR) << "read " << kGsiInstallStatusFile; return; } if (!IsGsiRunning()) { // Check if a wipe was requested from fastboot or adb-in-gsi. if (boot_key == kInstallStatusWipe) { RemoveGsiFiles(GetInstalledImageDir(), true /* wipeUserdata */); } } else { // 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. if (!android::base::WriteStringToFile(kInstallStatusOk, kGsiInstallStatusFile)) { PLOG(ERROR) << "write " << kGsiInstallStatusFile; } } } } } // namespace gsi } // namespace android