blob: 7e7f827223dd20026c8d5d3b6f5a12b27d63ad9e [file] [log] [blame]
/*
* 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 <errno.h>
#include <sys/stat.h>
#include <sys/statvfs.h>
#include <sys/types.h>
#include <unistd.h>
#include <chrono>
#include <string>
#include <vector>
#include <android-base/file.h>
#include <android-base/logging.h>
#include <android-base/stringprintf.h>
#include <android/gsi/IGsiService.h>
#include <fs_mgr_dm_linear.h>
#include <libfiemap_writer/fiemap_writer.h>
#include <logwrap/logwrap.h>
#include "file_paths.h"
namespace android {
namespace gsi {
using namespace std::literals;
using namespace android::fs_mgr;
using android::fiemap_writer::FiemapWriter;
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;
static constexpr std::chrono::milliseconds kDmTimeout = 5000ms;
void GsiService::Register() {
auto ret = android::BinderService<GsiService>::publish();
if (ret != android::OK) {
LOG(FATAL) << "Could not register gsi service: " << ret;
}
}
GsiService::~GsiService() {
PostInstallCleanup();
}
binder::Status GsiService::startGsiInstall(int64_t gsiSize, int64_t userdataSize,
bool* _aidl_return) {
std::lock_guard<std::mutex> guard(main_lock_);
// Make sure any interrupted installations are cleaned up.
PostInstallCleanup();
if (!StartInstall(gsiSize, userdataSize)) {
// Perform local cleanup and delete any lingering files.
PostInstallCleanup();
RemoveGsiInstall();
*_aidl_return = false;
} else {
*_aidl_return = true;
}
return binder::Status::ok();
}
binder::Status GsiService::commitGsiChunkFromStream(const android::base::unique_fd& stream,
int64_t bytes, bool* _aidl_return) {
std::lock_guard<std::mutex> guard(main_lock_);
*_aidl_return = CommitGsiChunk(stream, bytes);
return binder::Status::ok();
}
binder::Status GsiService::commitGsiChunkFromMemory(const std::vector<uint8_t>& bytes,
bool* _aidl_return) {
std::lock_guard<std::mutex> guard(main_lock_);
*_aidl_return = CommitGsiChunk(bytes.data(), bytes.size());
return binder::Status::ok();
}
binder::Status GsiService::setGsiBootable(bool* _aidl_return) {
std::lock_guard<std::mutex> guard(main_lock_);
*_aidl_return = SetGsiBootable();
return binder::Status::ok();
}
binder::Status GsiService::removeGsiInstall(bool* _aidl_return) {
std::lock_guard<std::mutex> guard(main_lock_);
// Just in case an install was left hanging.
PostInstallCleanup();
*_aidl_return = RemoveGsiInstall();
return binder::Status::ok();
}
binder::Status GsiService::isGsiRunning(bool* _aidl_return) {
*_aidl_return = IsGsiRunning();
return binder::Status::ok();
}
void GsiService::PostInstallCleanup() {
// This must be closed before unmapping partitions.
system_fd_ = {};
DestroyLogicalPartition("userdata_gsi", kDmTimeout);
DestroyLogicalPartition("system_gsi", kDmTimeout);
installing_ = false;
}
bool GsiService::StartInstall(int64_t gsi_size, int64_t userdata_size) {
gsi_size_ = gsi_size;
userdata_size_ = userdata_size;
if (!PerformSanityChecks() || !PreallocateFiles() || !FormatUserdata()) {
return false;
}
// Map system_gsi so we can write to it.
std::string block_device;
if (!CreateLogicalPartition(kUserdataDevice, *metadata_.get(), "system_gsi", true, kDmTimeout,
&block_device)) {
LOG(ERROR) << "Error creating device-mapper node for system_gsi";
return false;
}
static const int kOpenFlags = O_RDWR | O_NOFOLLOW | O_CLOEXEC;
system_fd_.reset(open(block_device.c_str(), kOpenFlags));
if (system_fd_ < 0) {
LOG(ERROR) << "could not open " << block_device << ": " << strerror(errno);
return false;
}
installing_ = true;
return true;
}
bool GsiService::PerformSanityChecks() {
if (gsi_size_ < 0) {
LOG(ERROR) << "image size " << gsi_size_ << " is negative";
return false;
}
if (!EnsureFolderExists(kGsiDataFolder) || !EnsureFolderExists(kGsiMetadataFolder)) {
return false;
}
struct statvfs sb;
if (statvfs(kGsiDataFolder, &sb)) {
LOG(ERROR) << "failed to read file system stats: " << strerror(errno);
return false;
}
// 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 = 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 false;
}
// 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<uint64_t>(free_space_percent)
<< "% is below the minimum threshold of " << kMinimumFreeSpaceThreshold << "%";
return false;
}
return true;
}
bool GsiService::PreallocateFiles() {
// Create fallocated files.
auto userdata_image = CreateFiemapWriter(kUserdataFile, userdata_size_);
if (!userdata_image) {
return false;
}
auto system_image = CreateFiemapWriter(kSystemFile, gsi_size_);
if (!system_image) {
return false;
}
// Save the extent information in liblp.
auto builder = CreateMetadataBuilder();
if (!builder) {
return false;
}
Partition* userdata = builder->AddPartition("userdata_gsi", LP_PARTITION_ATTR_NONE);
Partition* system = builder->AddPartition("system_gsi", LP_PARTITION_ATTR_READONLY);
if (!userdata || !system) {
LOG(ERROR) << "Error creating partition table";
return false;
}
if (!AddPartitionFiemap(builder.get(), userdata, userdata_image.get()) ||
!AddPartitionFiemap(builder.get(), system, system_image.get())) {
return false;
}
metadata_ = builder->Export();
if (!metadata_) {
LOG(ERROR) << "Error exporting partition table";
return false;
}
system_image->Flush();
userdata_image->Flush();
// We're ready to start streaming data in.
gsi_bytes_written_ = 0;
userdata_block_size_ = userdata_image->block_size();
system_block_size_ = system_image->block_size();
return true;
}
fiemap_writer::FiemapUniquePtr GsiService::CreateFiemapWriter(const std::string& path,
uint64_t size) {
auto file = FiemapWriter::Open(path, size);
if (!file) {
LOG(ERROR) << "failed to create " << path;
return nullptr;
}
uint64_t extents = file->extents().size();
if (extents > kMaximumExtents) {
LOG(ERROR) << "file " << path << " has too many extents: " << extents;
return nullptr;
}
return file;
}
bool GsiService::CommitGsiChunk(const android::base::unique_fd& stream, int64_t bytes) {
if (bytes < 0) {
LOG(ERROR) << "chunk size " << bytes << " is negative";
return false;
}
auto buffer = std::make_unique<char[]>(system_block_size_);
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, buffer.get(), max_to_read));
if (rv < 0) {
LOG(ERROR) << "read: " << strerror(errno);
return false;
}
if (rv == 0) {
LOG(ERROR) << "no bytes left in stream";
return false;
}
if (!CommitGsiChunk(buffer.get(), rv)) {
return false;
}
CHECK(static_cast<uint64_t>(rv) <= remaining);
remaining -= rv;
}
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<uint64_t>(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 (!android::base::WriteFully(system_fd_, data, bytes)) {
LOG(ERROR) << "write failed: " << strerror(errno);
return false;
}
gsi_bytes_written_ += bytes;
return true;
}
bool GsiService::SetGsiBootable() {
if (!installing_) {
LOG(ERROR) << "no gsi installation in progress";
return false;
}
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;
}
if (fsync(system_fd_)) {
LOG(ERROR) << "fsync failed: " << strerror(errno);
return false;
}
if (!CreateMetadataFile() || !CreateBootableFile()) {
return false;
}
PostInstallCleanup();
return true;
}
bool GsiService::RemoveGsiInstall() {
const std::vector<std::string> files{
kUserdataFile,
kSystemFile,
kGsiBootableFile,
kGsiLpMetadataFile,
};
bool ok = true;
for (const auto& file : files) {
std::string message;
if (!android::base::RemoveFileIfExists(file, &message)) {
LOG(ERROR) << message;
ok = false;
}
}
installing_ = false;
return ok;
}
std::unique_ptr<android::fs_mgr::MetadataBuilder> GsiService::CreateMetadataBuilder() {
PartitionOpener opener;
BlockDeviceInfo userdata_device;
if (!opener.GetInfo("userdata", &userdata_device)) {
LOG(ERROR) << "Error reading userdata partition";
return nullptr;
}
std::vector<BlockDeviceInfo> block_devices = {userdata_device};
auto builder = MetadataBuilder::New(block_devices, "userdata", 128 * 1024, 1);
if (!builder) {
LOG(ERROR) << "Error creating metadata builder";
return nullptr;
}
builder->IgnoreSlotSuffixing();
return builder;
}
bool GsiService::CreateMetadataFile() {
if (!WriteToImageFile(kGsiLpMetadataFile, *metadata_.get())) {
LOG(ERROR) << "Error writing GSI partition table image";
return false;
}
return true;
}
bool GsiService::FormatUserdata() {
std::string block_device;
if (!CreateLogicalPartition(kUserdataDevice, *metadata_.get(), "userdata_gsi", true, kDmTimeout,
&block_device)) {
LOG(ERROR) << "Error creating device-mapper node for userdata_gsi";
return false;
}
std::string block_size = std::to_string(userdata_block_size_);
const char* const mke2fs_args[] = {
"/system/bin/mke2fs", "-t", "ext4", "-b", block_size.c_str(),
block_device.c_str(), nullptr,
};
int rc = android_fork_execvp(arraysize(mke2fs_args), const_cast<char**>(mke2fs_args), nullptr,
true, true);
if (rc) {
LOG(ERROR) << "mke2fs returned " << rc;
return false;
}
return true;
}
bool GsiService::AddPartitionFiemap(MetadataBuilder* builder, Partition* partition,
FiemapWriter* writer) {
for (const auto& extent : 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 = extent.fe_length / LP_SECTOR_SIZE;
uint64_t physical_sector = extent.fe_physical / LP_SECTOR_SIZE;
if (!builder->AddLinearExtent(partition, "userdata", num_sectors, physical_sector)) {
LOG(ERROR) << "Could not add extent to lp metadata";
return false;
}
}
return true;
}
bool GsiService::CreateBootableFile() {
android::base::unique_fd fd(
open(kGsiBootableFile, O_WRONLY | O_CLOEXEC | O_NOFOLLOW | O_CREAT, 0755));
if (fd < 0) {
LOG(ERROR) << "open: " << strerror(errno) << ": " << kGsiBootableFile;
return false;
}
return true;
}
bool GsiService::EnsureFolderExists(const std::string& path) {
if (!mkdir(path.c_str(), 0755) || errno == EEXIST) {
return true;
}
LOG(ERROR) << "mkdir: " << strerror(errno) << ": " << path;
return false;
}
} // namespace gsi
} // namespace android