Support FiemapWriters that extend across multiple files.

This introduces a new SplitFiemap class that will divide an allocation
request across multiple FiemapWriters. This is primarily useful on
filesystems that have onerous restrictions on maximum file sizes. Vfat,
for example, supports a maximum of 4GiB, which is too small to satisfy
larger userdata size requests.

Bug: 126230649
Test: fiemap_writer_test gtest
Change-Id: I3c95d341e4e94e0c44bbf0e8553c34ccfdcd155b
This commit is contained in:
David Anderson 2019-03-02 00:28:35 -08:00
parent 4d4db8c09e
commit f344d63222
7 changed files with 464 additions and 3 deletions

View File

@ -25,6 +25,12 @@ cc_library_static {
srcs: [
"fiemap_writer.cpp",
"split_fiemap_writer.cpp",
"utility.cpp",
],
static_libs: [
"libext4_utils",
],
header_libs: [

View File

@ -624,8 +624,8 @@ FiemapUniquePtr FiemapWriter::Open(const std::string& file_path, uint64_t file_s
fmap->fs_type_ = fs_type;
fmap->block_size_ = blocksz;
LOG(INFO) << "Successfully created FiemapWriter for file " << abs_path << " on block device "
<< bdev_path;
LOG(VERBOSE) << "Successfully created FiemapWriter for file " << abs_path << " on block device "
<< bdev_path;
return fmap;
}

View File

@ -33,8 +33,10 @@
#include <android-base/unique_fd.h>
#include <gtest/gtest.h>
#include <libdm/loop_control.h>
#include <libfiemap_writer/fiemap_writer.h>
#include <libfiemap_writer/split_fiemap_writer.h>
#include "utility.h"
using namespace std;
using namespace std::string_literals;
@ -59,6 +61,24 @@ class FiemapWriterTest : public ::testing::Test {
std::string testfile;
};
class SplitFiemapTest : public ::testing::Test {
protected:
void SetUp() override {
const ::testing::TestInfo* tinfo = ::testing::UnitTest::GetInstance()->current_test_info();
testfile = gTestDir + "/"s + tinfo->name();
}
void TearDown() override {
std::string message;
if (!SplitFiemap::RemoveSplitFiles(testfile, &message)) {
cerr << "Could not remove all split files: " << message;
}
}
// name of the file we use for testing
std::string testfile;
};
TEST_F(FiemapWriterTest, CreateImpossiblyLargeFile) {
// Try creating a file of size ~100TB but aligned to
// 512 byte to make sure block alignment tests don't
@ -168,6 +188,52 @@ TEST_F(FiemapWriterTest, FileDeletedOnError) {
EXPECT_EQ(errno, ENOENT);
}
TEST_F(FiemapWriterTest, MaxBlockSize) {
ASSERT_GT(DetermineMaximumFileSize(testfile), 0);
}
TEST_F(SplitFiemapTest, Create) {
auto ptr = SplitFiemap::Create(testfile, 1024 * 768, 1024 * 32);
ASSERT_NE(ptr, nullptr);
auto extents = ptr->extents();
// Destroy the fiemap, closing file handles. This should not delete them.
ptr = nullptr;
std::vector<std::string> files;
ASSERT_TRUE(SplitFiemap::GetSplitFileList(testfile, &files));
for (const auto& path : files) {
EXPECT_EQ(access(path.c_str(), F_OK), 0);
}
ASSERT_GE(extents.size(), files.size());
}
TEST_F(SplitFiemapTest, Open) {
{
auto ptr = SplitFiemap::Create(testfile, 1024 * 768, 1024 * 32);
ASSERT_NE(ptr, nullptr);
}
auto ptr = SplitFiemap::Open(testfile);
ASSERT_NE(ptr, nullptr);
auto extents = ptr->extents();
ASSERT_GE(extents.size(), 24);
}
TEST_F(SplitFiemapTest, DeleteOnFail) {
auto ptr = SplitFiemap::Create(testfile, 1024 * 1024 * 10, 1);
ASSERT_EQ(ptr, nullptr);
std::string first_file = testfile + ".0001";
ASSERT_NE(access(first_file.c_str(), F_OK), 0);
ASSERT_EQ(errno, ENOENT);
ASSERT_NE(access(testfile.c_str(), F_OK), 0);
ASSERT_EQ(errno, ENOENT);
}
class VerifyBlockWritesExt4 : public ::testing::Test {
// 2GB Filesystem and 4k block size by default
static constexpr uint64_t block_size = 4096;

View File

@ -0,0 +1,79 @@
/*
* 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.
*/
#pragma once
#include <stdint.h>
#include <functional>
#include <memory>
#include <string>
#include <vector>
#include "fiemap_writer.h"
namespace android {
namespace fiemap_writer {
// Wrapper around FiemapWriter that is able to split images across files if
// necessary.
class SplitFiemap final {
public:
using ProgressCallback = std::function<bool(uint64_t, uint64_t)>;
// Create a new split fiemap file. If |max_piece_size| is 0, the number of
// pieces will be determined automatically by detecting the filesystem.
// Otherwise, the file will be split evenly (with the remainder in the
// final file).
static std::unique_ptr<SplitFiemap> Create(const std::string& file_path, uint64_t file_size,
uint64_t max_piece_size,
ProgressCallback progress = {});
// Open an existing split fiemap file.
static std::unique_ptr<SplitFiemap> Open(const std::string& file_path);
~SplitFiemap();
// Return a list of all files created for a split file.
static bool GetSplitFileList(const std::string& file_path, std::vector<std::string>* list);
// Destroy all components of a split file. If the root file does not exist,
// this returns true and does not report an error.
static bool RemoveSplitFiles(const std::string& file_path, std::string* message = nullptr);
const std::vector<struct fiemap_extent>& extents();
uint32_t block_size() const;
uint64_t size() const { return total_size_; }
// Non-copyable & Non-movable
SplitFiemap(const SplitFiemap&) = delete;
SplitFiemap& operator=(const SplitFiemap&) = delete;
SplitFiemap& operator=(SplitFiemap&&) = delete;
SplitFiemap(SplitFiemap&&) = delete;
private:
SplitFiemap() = default;
void AddFile(FiemapUniquePtr&& file);
bool creating_ = false;
std::string list_file_;
std::vector<FiemapUniquePtr> files_;
std::vector<struct fiemap_extent> extents_;
uint64_t total_size_ = 0;
};
} // namespace fiemap_writer
} // namespace android

View File

@ -0,0 +1,214 @@
/*
* 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 <libfiemap_writer/split_fiemap_writer.h>
#include <fcntl.h>
#include <stdint.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
#include <memory>
#include <string>
#include <vector>
#include <android-base/file.h>
#include <android-base/logging.h>
#include <android-base/stringprintf.h>
#include <android-base/strings.h>
#include <android-base/unique_fd.h>
#include "utility.h"
namespace android {
namespace fiemap_writer {
using android::base::unique_fd;
// We use a four-digit suffix at the end of filenames.
static const size_t kMaxFilePieces = 500;
std::unique_ptr<SplitFiemap> SplitFiemap::Create(const std::string& file_path, uint64_t file_size,
uint64_t max_piece_size,
ProgressCallback progress) {
if (!file_size) {
LOG(ERROR) << "Cannot create a fiemap for a 0-length file: " << file_path;
return nullptr;
}
if (!max_piece_size) {
max_piece_size = DetermineMaximumFileSize(file_path);
if (!max_piece_size) {
LOG(ERROR) << "Could not determine maximum file size for " << file_path;
return nullptr;
}
}
// Call |progress| only when the total percentage would significantly change.
int permille = -1;
uint64_t total_bytes_written = 0;
auto on_progress = [&](uint64_t written, uint64_t) -> bool {
uint64_t actual_written = total_bytes_written + written;
int new_permille = (actual_written * 1000) / file_size;
if (new_permille != permille && actual_written < file_size) {
if (progress && !progress(actual_written, file_size)) {
return false;
}
permille = new_permille;
}
return true;
};
std::unique_ptr<SplitFiemap> out(new SplitFiemap());
out->creating_ = true;
out->list_file_ = file_path;
// Create the split files.
uint64_t remaining_bytes = file_size;
while (remaining_bytes) {
if (out->files_.size() >= kMaxFilePieces) {
LOG(ERROR) << "Requested size " << file_size << " created too many split files";
return nullptr;
}
std::string chunk_path =
android::base::StringPrintf("%s.%04d", file_path.c_str(), (int)out->files_.size());
uint64_t chunk_size = std::min(max_piece_size, remaining_bytes);
auto writer = FiemapWriter::Open(chunk_path, chunk_size, true, on_progress);
if (!writer) {
return nullptr;
}
// To make sure the alignment doesn't create too much inconsistency, we
// account the *actual* size, not the requested size.
total_bytes_written += writer->size();
remaining_bytes -= writer->size();
out->AddFile(std::move(writer));
}
// Create the split file list.
unique_fd fd(open(out->list_file_.c_str(), O_CREAT | O_WRONLY | O_CLOEXEC, 0660));
if (fd < 0) {
PLOG(ERROR) << "Failed to open " << file_path;
return nullptr;
}
for (const auto& writer : out->files_) {
std::string line = android::base::Basename(writer->file_path()) + "\n";
if (!android::base::WriteFully(fd, line.data(), line.size())) {
PLOG(ERROR) << "Write failed " << file_path;
return nullptr;
}
}
// Unset this bit, so we don't unlink on destruction.
out->creating_ = false;
return out;
}
std::unique_ptr<SplitFiemap> SplitFiemap::Open(const std::string& file_path) {
std::vector<std::string> files;
if (!GetSplitFileList(file_path, &files)) {
return nullptr;
}
std::unique_ptr<SplitFiemap> out(new SplitFiemap());
out->list_file_ = file_path;
for (const auto& file : files) {
auto writer = FiemapWriter::Open(file, 0, false);
if (!writer) {
// Error was logged in Open().
return nullptr;
}
out->AddFile(std::move(writer));
}
return out;
}
bool SplitFiemap::GetSplitFileList(const std::string& file_path, std::vector<std::string>* list) {
// This is not the most efficient thing, but it is simple and recovering
// the fiemap/fibmap is much more expensive.
std::string contents;
if (!android::base::ReadFileToString(file_path, &contents, true)) {
PLOG(ERROR) << "Error reading file: " << file_path;
return false;
}
std::vector<std::string> names = android::base::Split(contents, "\n");
std::string dir = android::base::Dirname(file_path);
for (const auto& name : names) {
if (!name.empty()) {
list->emplace_back(dir + "/" + name);
}
}
return true;
}
bool SplitFiemap::RemoveSplitFiles(const std::string& file_path, std::string* message) {
// Early exit if this does not exist, and do not report an error.
if (access(file_path.c_str(), F_OK) && errno == ENOENT) {
return true;
}
bool ok = true;
std::vector<std::string> files;
if (GetSplitFileList(file_path, &files)) {
for (const auto& file : files) {
ok &= android::base::RemoveFileIfExists(file, message);
}
}
ok &= android::base::RemoveFileIfExists(file_path, message);
return ok;
}
const std::vector<struct fiemap_extent>& SplitFiemap::extents() {
if (extents_.empty()) {
for (const auto& file : files_) {
const auto& extents = file->extents();
extents_.insert(extents_.end(), extents.begin(), extents.end());
}
}
return extents_;
}
SplitFiemap::~SplitFiemap() {
if (!creating_) {
return;
}
// We failed to finish creating, so unlink everything.
unlink(list_file_.c_str());
for (auto&& file : files_) {
std::string path = file->file_path();
file = nullptr;
unlink(path.c_str());
}
}
void SplitFiemap::AddFile(FiemapUniquePtr&& file) {
total_size_ += file->size();
files_.emplace_back(std::move(file));
}
uint32_t SplitFiemap::block_size() const {
return files_[0]->block_size();
}
} // namespace fiemap_writer
} // namespace android

View File

@ -0,0 +1,66 @@
/*
* 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 "utility.h"
#include <stdint.h>
#include <sys/vfs.h>
#include <unistd.h>
#include <android-base/logging.h>
#include <libfiemap_writer/fiemap_writer.h>
namespace android {
namespace fiemap_writer {
uint64_t DetermineMaximumFileSize(const std::string& file_path) {
// Create the smallest file possible (one block).
auto writer = FiemapWriter::Open(file_path, 1);
if (!writer) {
return 0;
}
uint64_t result = 0;
switch (writer->fs_type()) {
case EXT4_SUPER_MAGIC:
// The minimum is 16GiB, so just report that. If we wanted we could parse the
// superblock and figure out if 64-bit support is enabled.
result = 17179869184ULL;
break;
case F2FS_SUPER_MAGIC:
// Formula is from https://www.kernel.org/doc/Documentation/filesystems/f2fs.txt
// 4KB * (923 + 2 * 1018 + 2 * 1018 * 1018 + 1018 * 1018 * 1018) := 3.94TB.
result = 4329690886144ULL;
break;
case MSDOS_SUPER_MAGIC:
// 4GB-1, which we want aligned to the block size.
result = 4294967295;
result -= (result % writer->block_size());
break;
default:
LOG(ERROR) << "Unknown file system type: " << writer->fs_type();
break;
}
// Close and delete the temporary file.
writer = nullptr;
unlink(file_path.c_str());
return result;
}
} // namespace fiemap_writer
} // namespace android

View File

@ -0,0 +1,30 @@
/*
* 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.
*/
#pragma once
#include <string>
namespace android {
namespace fiemap_writer {
// Given a file that will be created, determine the maximum size its containing
// filesystem allows. Note this is a theoretical maximum size; free space is
// ignored entirely.
uint64_t DetermineMaximumFileSize(const std::string& file_path);
} // namespace fiemap_writer
} // namespace android