blob: 495d8c1771c5c82eff7ed187045a6f4aaf9620ef [file]
// Copyright 2021 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <fcntl.h>
#include <lib/fdio/spawn.h>
#include <lib/zx/process.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
#include <zircon/device/block.h>
#include <algorithm>
#include <climits>
#include <cstdint>
#include <cstdio>
#include <cstdlib>
#include <iterator>
#include <memory>
#include <string>
#include <fbl/unique_fd.h>
#include "src/storage/blobfs/common.h"
#include "src/storage/blobfs/format.h"
#include "src/storage/blobfs/test/blob_utils.h"
#include "src/storage/blobfs/test/integration/blobfs_fixtures.h"
#include "src/storage/extractor/c/extractor.h"
#include "src/storage/extractor/cpp/extractor.h"
#include "src/storage/fs_test/blobfs_test.h"
#include "src/storage/fs_test/fs_test.h"
#include "src/storage/fs_test/fs_test_fixture.h"
namespace extractor {
namespace {
using BlobfsExtractionTest = fs_test::FilesystemTest;
void VerifyInputSuperblock(blobfs::Superblock* info, int input_fd) {
ASSERT_EQ(blobfs::kBlobfsBlockSize,
pread(input_fd, info, blobfs::kBlobfsBlockSize, blobfs::kSuperblockOffset));
ASSERT_EQ(info->magic0, blobfs::kBlobfsMagic0);
ASSERT_EQ(info->magic1, blobfs::kBlobfsMagic1);
}
void VerifyOutputSuperblock(blobfs::Superblock* info, uint64_t kExtractedImageBlockCount,
int output_fd) {
ssize_t expected = (kExtractedImageBlockCount)*blobfs::kBlobfsBlockSize;
char read_buffer[blobfs::kBlobfsBlockSize];
ASSERT_EQ(pread(output_fd, read_buffer, sizeof(read_buffer), expected),
static_cast<ssize_t>(sizeof(read_buffer)));
ASSERT_EQ(memcmp(info, read_buffer, sizeof(read_buffer)), 0);
}
TEST_P(BlobfsExtractionTest, TestSuperblock) {
ASSERT_EQ(fs().Unmount().status_value(), ZX_OK);
fbl::unique_fd input_fd(open(fs().DevicePath().value().c_str(), O_RDONLY));
ASSERT_TRUE(input_fd);
char out_path[] = "/tmp/blobfs-extraction.XXXXXX";
fbl::unique_fd output_fd(mkostemp(out_path, O_RDWR | O_CREAT | O_EXCL));
ASSERT_TRUE(output_fd);
ExtractorOptions options = ExtractorOptions{.force_dump_pii = false,
.add_checksum = false,
.alignment = blobfs::kBlobfsBlockSize,
.compress = false};
auto extractor_status = Extractor::Create(input_fd.duplicate(), options, output_fd.duplicate());
ASSERT_EQ(extractor_status.status_value(), ZX_OK);
auto extractor = std::move(extractor_status.value());
auto status = BlobfsExtract(input_fd.duplicate(), *extractor);
ASSERT_TRUE(status.is_ok());
ASSERT_TRUE(extractor->Write().is_ok());
blobfs::Superblock info;
VerifyInputSuperblock(&info, input_fd.get());
struct stat stats;
ASSERT_EQ(fstat(output_fd.get(), &stats), 0);
// One block for header and one for extent cluster
constexpr uint64_t kExtractedImageBlockCount = 2;
VerifyOutputSuperblock(&info, kExtractedImageBlockCount, output_fd.get());
}
TEST_P(BlobfsExtractionTest, TestNodeMap) {
std::unique_ptr<blobfs::BlobInfo> blob_info =
blobfs::GenerateRandomBlob(fs().mount_path(), 1 << 10);
fbl::unique_fd fd;
ASSERT_NO_FATAL_FAILURE(MakeBlob(*blob_info, &fd));
ASSERT_EQ(close(fd.release()), 0);
ASSERT_EQ(fs().Unmount().status_value(), ZX_OK);
fbl::unique_fd input_fd(open(fs().DevicePath().value().c_str(), O_RDONLY));
ASSERT_TRUE(input_fd);
char out_path[] = "/tmp/blobfs-extraction.XXXXXX";
fbl::unique_fd output_fd(mkostemp(out_path, O_RDWR | O_CREAT | O_EXCL));
ASSERT_TRUE(output_fd);
ExtractorOptions options = ExtractorOptions{.force_dump_pii = false,
.add_checksum = false,
.alignment = blobfs::kBlobfsBlockSize,
.compress = false};
auto extractor_status = Extractor::Create(input_fd.duplicate(), options, output_fd.duplicate());
ASSERT_EQ(extractor_status.status_value(), ZX_OK);
auto extractor = std::move(extractor_status.value());
auto status = BlobfsExtract(input_fd.duplicate(), *extractor);
ASSERT_TRUE(status.is_ok());
ASSERT_TRUE(extractor->Write().is_ok());
blobfs::Superblock info;
VerifyInputSuperblock(&info, input_fd.get());
ASSERT_EQ(int(info.alloc_inode_count), 1);
// One block for header and one for extent cluster
constexpr uint64_t kExtractedImageBlockCount = 2;
VerifyOutputSuperblock(&info, kExtractedImageBlockCount, output_fd.get());
std::unique_ptr<blobfs::Inode[]> inode_table;
inode_table =
std::make_unique<blobfs::Inode[]>(NodeMapBlocks(info) * blobfs::kBlobfsInodesPerBlock);
ssize_t size = blobfs::NodeMapBlocks(info) * blobfs::kBlobfsBlockSize;
ASSERT_EQ(size, pread(input_fd.get(), inode_table.get(), size,
blobfs::kBlobfsBlockSize * blobfs::NodeMapStartBlock(info)));
ssize_t node_map_offset =
(kExtractedImageBlockCount + blobfs::kBlobfsSuperblockBlocks + blobfs::BlockMapBlocks(info)) *
blobfs::kBlobfsBlockSize;
char read_buffer_nodemap[size];
ASSERT_EQ(pread(output_fd.get(), read_buffer_nodemap, size, node_map_offset), size);
ASSERT_EQ(memcmp(inode_table.get(), read_buffer_nodemap, size), 0);
}
TEST_P(BlobfsExtractionTest, TestBlockMap) {
ssize_t size_of_blob = 1 << 17;
std::unique_ptr<blobfs::BlobInfo> blob_info =
blobfs::GenerateRandomBlob(fs().mount_path(), size_of_blob);
fbl::unique_fd fd;
ASSERT_NO_FATAL_FAILURE(MakeBlob(*blob_info, &fd));
ASSERT_EQ(close(fd.release()), 0);
ASSERT_EQ(fs().Unmount().status_value(), ZX_OK);
fbl::unique_fd input_fd(open(fs().DevicePath().value().c_str(), O_RDONLY));
ASSERT_TRUE(input_fd);
char out_path[] = "/tmp/blobfs-extraction.XXXXXX";
fbl::unique_fd output_fd(mkostemp(out_path, O_RDWR | O_CREAT | O_EXCL));
ASSERT_TRUE(output_fd);
ExtractorOptions options = ExtractorOptions{.force_dump_pii = false,
.add_checksum = false,
.alignment = blobfs::kBlobfsBlockSize,
.compress = false};
auto extractor_status = Extractor::Create(input_fd.duplicate(), options, output_fd.duplicate());
ASSERT_EQ(extractor_status.status_value(), ZX_OK);
auto extractor = std::move(extractor_status.value());
auto status = BlobfsExtract(input_fd.duplicate(), *extractor);
ASSERT_TRUE(status.is_ok());
ASSERT_TRUE(extractor->Write().is_ok());
blobfs::Superblock info;
VerifyInputSuperblock(&info, input_fd.get());
ASSERT_EQ(int(info.alloc_inode_count), 1);
// One block for header and one for extent cluster
constexpr uint64_t kExtractedImageBlockCount = 2;
VerifyOutputSuperblock(&info, kExtractedImageBlockCount, output_fd.get());
ssize_t size = blobfs::BlockMapBlocks(info) * blobfs::kBlobfsBlockSize;
char block_bitmap[size];
ASSERT_EQ(size, pread(input_fd.get(), block_bitmap, size,
blobfs::kBlobfsBlockSize * blobfs::BlockMapStartBlock(info)));
ssize_t block_map_offset =
(kExtractedImageBlockCount + blobfs::kBlobfsSuperblockBlocks) * blobfs::kBlobfsBlockSize;
char read_buffer_blockmap[size];
ASSERT_EQ(pread(output_fd.get(), read_buffer_blockmap, size, block_map_offset), size);
ASSERT_EQ(memcmp(block_bitmap, read_buffer_blockmap, size), 0);
}
INSTANTIATE_TEST_SUITE_P(/*no prefix*/, BlobfsExtractionTest,
testing::Values(blobfs::BlobfsDefaultTestParam()),
testing::PrintToStringParamName());
} // namespace
} // namespace extractor