platform_bootable_recovery/recovery_utils/roots.cpp
Jaegeuk Kim 80a1d8e341 Avoid to format raw disk if metadata encryption is enabled
The current flow is

1. factory reset formatted raw disk.
2. next boot tries to convert it to metadata encryption
 2.a mount sda27
 2.b umount sda27
 2.c encrypt_inplace()
 2.d fsck on dm-x
 2.e mount dm-x

The #2 flow is unnecessary, if we know the disk will be encrypted. And, that
gives a change of the failure when unmounting the raw disk by starting some
file operations on /data. That can cause the entire encryption failure.

Bug: 208161227
Signed-off-by: Jaegeuk Kim <jaegeuk@google.com>
Change-Id: Iaedfeb74bb6abb667efee6dcadf8d66272466ee0
2021-12-02 10:54:25 -08:00

329 lines
9.9 KiB
C++

/*
* Copyright (C) 2007 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 "recovery_utils/roots.h"
#include <fcntl.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include <iostream>
#include <string>
#include <vector>
#include <android-base/logging.h>
#include <android-base/properties.h>
#include <android-base/stringprintf.h>
#include <android-base/unique_fd.h>
#include <ext4_utils/ext4_utils.h>
#include <ext4_utils/wipe.h>
#include <fs_mgr.h>
#include <fs_mgr/roots.h>
#include "otautil/sysutil.h"
using android::fs_mgr::Fstab;
using android::fs_mgr::FstabEntry;
using android::fs_mgr::ReadDefaultFstab;
static Fstab fstab;
constexpr const char* CACHE_ROOT = "/cache";
void load_volume_table() {
if (!ReadDefaultFstab(&fstab)) {
LOG(ERROR) << "Failed to read default fstab";
return;
}
fstab.emplace_back(FstabEntry{
.blk_device = "ramdisk",
.mount_point = "/tmp",
.fs_type = "ramdisk",
.length = 0,
});
std::cout << "recovery filesystem table" << std::endl << "=========================" << std::endl;
for (size_t i = 0; i < fstab.size(); ++i) {
const auto& entry = fstab[i];
std::cout << " " << i << " " << entry.mount_point << " "
<< " " << entry.fs_type << " " << entry.blk_device << " " << entry.length
<< std::endl;
}
std::cout << std::endl;
}
Volume* volume_for_mount_point(const std::string& mount_point) {
return android::fs_mgr::GetEntryForMountPoint(&fstab, mount_point);
}
// Mount the volume specified by path at the given mount_point.
int ensure_path_mounted_at(const std::string& path, const std::string& mount_point) {
return android::fs_mgr::EnsurePathMounted(&fstab, path, mount_point) ? 0 : -1;
}
int ensure_path_mounted(const std::string& path) {
// Mount at the default mount point.
return android::fs_mgr::EnsurePathMounted(&fstab, path) ? 0 : -1;
}
int ensure_path_unmounted(const std::string& path) {
return android::fs_mgr::EnsurePathUnmounted(&fstab, path) ? 0 : -1;
}
static int exec_cmd(const std::vector<std::string>& args) {
CHECK(!args.empty());
auto argv = StringVectorToNullTerminatedArray(args);
pid_t child;
if ((child = fork()) == 0) {
execv(argv[0], argv.data());
_exit(EXIT_FAILURE);
}
int status;
waitpid(child, &status, 0);
if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) {
LOG(ERROR) << args[0] << " failed with status " << WEXITSTATUS(status);
}
return WEXITSTATUS(status);
}
static int64_t get_file_size(int fd, uint64_t reserve_len) {
struct stat buf;
int ret = fstat(fd, &buf);
if (ret) return 0;
int64_t computed_size;
if (S_ISREG(buf.st_mode)) {
computed_size = buf.st_size - reserve_len;
} else if (S_ISBLK(buf.st_mode)) {
uint64_t block_device_size = get_block_device_size(fd);
if (block_device_size < reserve_len ||
block_device_size > std::numeric_limits<int64_t>::max()) {
computed_size = 0;
} else {
computed_size = block_device_size - reserve_len;
}
} else {
computed_size = 0;
}
return computed_size;
}
int format_volume(const std::string& volume, const std::string& directory) {
const FstabEntry* v = android::fs_mgr::GetEntryForPath(&fstab, volume);
if (v == nullptr) {
LOG(ERROR) << "unknown volume \"" << volume << "\"";
return -1;
}
if (v->fs_type == "ramdisk") {
LOG(ERROR) << "can't format_volume \"" << volume << "\"";
return -1;
}
if (v->mount_point != volume) {
LOG(ERROR) << "can't give path \"" << volume << "\" to format_volume";
return -1;
}
if (ensure_path_unmounted(volume) != 0) {
LOG(ERROR) << "format_volume: Failed to unmount \"" << v->mount_point << "\"";
return -1;
}
if (v->fs_type != "ext4" && v->fs_type != "f2fs") {
LOG(ERROR) << "format_volume: fs_type \"" << v->fs_type << "\" unsupported";
return -1;
}
bool needs_casefold = false;
bool needs_projid = false;
if (volume == "/data") {
needs_casefold = android::base::GetBoolProperty("external_storage.casefold.enabled", false);
needs_projid = android::base::GetBoolProperty("external_storage.projid.enabled", false);
}
int64_t length = 0;
if (v->length > 0) {
length = v->length;
} else if (v->length < 0) {
android::base::unique_fd fd(open(v->blk_device.c_str(), O_RDONLY));
if (fd == -1) {
PLOG(ERROR) << "format_volume: failed to open " << v->blk_device;
return -1;
}
length = get_file_size(fd.get(), -v->length);
if (length <= 0) {
LOG(ERROR) << "get_file_size: invalid size " << length << " for " << v->blk_device;
return -1;
}
}
// If the raw disk will be used as a metadata encrypted device mapper target,
// next boot will do encrypt_in_place the raw disk which gives a subtle duration
// to get any failure in the process. In order to avoid it, let's simply wipe
// the raw disk if we don't reserve any space, which behaves exactly same as booting
// after "fastboot -w".
if (!v->metadata_encryption.empty() && length == 0) {
android::base::unique_fd fd(open(v->blk_device.c_str(), O_RDWR));
if (fd == -1) {
PLOG(ERROR) << "format_volume: failed to open " << v->blk_device;
return -1;
}
int64_t device_size = get_file_size(fd.get(), 0);
if (device_size > 0 && !wipe_block_device(fd.get(), device_size)) {
LOG(INFO) << "format_volume: wipe metadata encrypted " << v->blk_device << " with size "
<< device_size;
return 0;
}
}
if (v->fs_type == "ext4") {
static constexpr int kBlockSize = 4096;
std::vector<std::string> mke2fs_args = {
"/system/bin/mke2fs", "-F", "-t", "ext4", "-b", std::to_string(kBlockSize),
};
// Project ID's require wider inodes. The Quotas themselves are enabled by tune2fs on boot.
if (needs_projid) {
mke2fs_args.push_back("-I");
mke2fs_args.push_back("512");
}
if (v->fs_mgr_flags.ext_meta_csum) {
mke2fs_args.push_back("-O");
mke2fs_args.push_back("metadata_csum");
mke2fs_args.push_back("-O");
mke2fs_args.push_back("64bit");
mke2fs_args.push_back("-O");
mke2fs_args.push_back("extent");
}
int raid_stride = v->logical_blk_size / kBlockSize;
int raid_stripe_width = v->erase_blk_size / kBlockSize;
// stride should be the max of 8KB and logical block size
if (v->logical_blk_size != 0 && v->logical_blk_size < 8192) {
raid_stride = 8192 / kBlockSize;
}
if (v->erase_blk_size != 0 && v->logical_blk_size != 0) {
mke2fs_args.push_back("-E");
mke2fs_args.push_back(
android::base::StringPrintf("stride=%d,stripe-width=%d", raid_stride, raid_stripe_width));
}
mke2fs_args.push_back(v->blk_device);
if (length != 0) {
mke2fs_args.push_back(std::to_string(length / kBlockSize));
}
int result = exec_cmd(mke2fs_args);
if (result == 0 && !directory.empty()) {
std::vector<std::string> e2fsdroid_args = {
"/system/bin/e2fsdroid", "-e", "-f", directory, "-a", volume, v->blk_device,
};
result = exec_cmd(e2fsdroid_args);
}
if (result != 0) {
PLOG(ERROR) << "format_volume: Failed to make ext4 on " << v->blk_device;
return -1;
}
return 0;
}
// Has to be f2fs because we checked earlier.
static constexpr int kSectorSize = 4096;
std::vector<std::string> make_f2fs_cmd = {
"/system/bin/make_f2fs",
"-g",
"android",
};
if (needs_projid) {
make_f2fs_cmd.push_back("-O");
make_f2fs_cmd.push_back("project_quota,extra_attr");
}
if (needs_casefold) {
make_f2fs_cmd.push_back("-O");
make_f2fs_cmd.push_back("casefold");
make_f2fs_cmd.push_back("-C");
make_f2fs_cmd.push_back("utf8");
}
if (v->fs_mgr_flags.fs_compress) {
make_f2fs_cmd.push_back("-O");
make_f2fs_cmd.push_back("compression");
make_f2fs_cmd.push_back("-O");
make_f2fs_cmd.push_back("extra_attr");
}
make_f2fs_cmd.push_back(v->blk_device);
if (length >= kSectorSize) {
make_f2fs_cmd.push_back(std::to_string(length / kSectorSize));
}
if (exec_cmd(make_f2fs_cmd) != 0) {
PLOG(ERROR) << "format_volume: Failed to make_f2fs on " << v->blk_device;
return -1;
}
if (!directory.empty()) {
std::vector<std::string> sload_f2fs_cmd = {
"/system/bin/sload_f2fs", "-f", directory, "-t", volume, v->blk_device,
};
if (exec_cmd(sload_f2fs_cmd) != 0) {
PLOG(ERROR) << "format_volume: Failed to sload_f2fs on " << v->blk_device;
return -1;
}
}
return 0;
}
int format_volume(const std::string& volume) {
return format_volume(volume, "");
}
int setup_install_mounts() {
if (fstab.empty()) {
LOG(ERROR) << "can't set up install mounts: no fstab loaded";
return -1;
}
for (const FstabEntry& entry : fstab) {
// We don't want to do anything with "/".
if (entry.mount_point == "/") {
continue;
}
if (entry.mount_point == "/tmp" || entry.mount_point == "/cache") {
if (ensure_path_mounted(entry.mount_point) != 0) {
LOG(ERROR) << "Failed to mount " << entry.mount_point;
return -1;
}
} else {
if (ensure_path_unmounted(entry.mount_point) != 0) {
LOG(ERROR) << "Failed to unmount " << entry.mount_point;
return -1;
}
}
}
return 0;
}
bool HasCache() {
CHECK(!fstab.empty());
static bool has_cache = volume_for_mount_point(CACHE_ROOT) != nullptr;
return has_cache;
}