| // Copyright 2017 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 <fbl/string.h> |
| #include <fbl/unique_fd.h> |
| #include <fvm/container.h> |
| #include <minfs/host.h> |
| #include <unittest/unittest.h> |
| |
| #define PARTITION_SIZE (1lu << 30) // 1 gb |
| #define CONTAINER_SIZE (4lu << 30) // 4 gb |
| #define SLICE_SIZE (64lu * (1 << 20)) // 64 mb |
| #define FILE_SIZE (5lu * (1 << 20)) // 5 mb |
| |
| static char test_dir[PATH_MAX]; |
| static char data_path[PATH_MAX]; |
| static char system_path[PATH_MAX]; |
| static char blobfs_path[PATH_MAX]; |
| static char sparse_path[PATH_MAX]; |
| static char fvm_path[PATH_MAX]; |
| |
| constexpr uint32_t kData = 1; |
| constexpr uint32_t kSystem = 2; |
| constexpr uint32_t kBlobfs = 4; |
| constexpr uint32_t kSparse = 8; |
| constexpr uint32_t kFvm = 16; |
| |
| // gFileFlags indicates which of the above files has been successfully created. |
| // Keeping track of these across each individual test allows us to unlink only files that actually |
| // exist at the end of the test. It also gives the test information about which partitions should |
| // be added to the container, so this doesn't have to be specified separately. |
| // Keeping track of these globally allows us to know with reasonable certainty whether files from |
| // any tests have been left over, for example in the case of a test failure. In this case we can |
| // optionally recover from the previous state by removing these files and continue with the next |
| // test. |
| static uint32_t gFileFlags = 0; |
| |
| typedef enum { |
| SPARSE, |
| FVM, |
| FVM_NEW, |
| FVM_OFFSET, |
| } container_t; |
| |
| bool CreateFile(const char* path, size_t size, uint32_t type) { |
| BEGIN_HELPER; |
| int r = open(path, O_RDWR | O_CREAT | O_EXCL, 0755); |
| ASSERT_GE(r, 0, "Unable to create path"); |
| gFileFlags |= type; |
| ASSERT_EQ(ftruncate(r, size), 0, "Unable to truncate disk"); |
| ASSERT_EQ(close(r), 0, "Unable to close disk"); |
| END_HELPER; |
| } |
| |
| bool CreateMinfs(const char* path, uint32_t type) { |
| BEGIN_HELPER; |
| printf("Creating Minfs partition: %s\n", path); |
| ASSERT_TRUE(CreateFile(path, PARTITION_SIZE, type)); |
| ASSERT_EQ(emu_mkfs(path), 0, "Unable to run mkfs"); |
| END_HELPER; |
| } |
| |
| bool CreateData() { |
| BEGIN_HELPER; |
| ASSERT_TRUE(CreateMinfs(data_path, kData)); |
| END_HELPER; |
| } |
| |
| bool CreateSystem() { |
| BEGIN_HELPER; |
| ASSERT_TRUE(CreateMinfs(system_path, kSystem)); |
| END_HELPER; |
| } |
| |
| bool CreateBlobstore() { |
| BEGIN_HELPER; |
| printf("Creating Blobstore partition: %s\n", blobfs_path); |
| int r = open(blobfs_path, O_RDWR | O_CREAT | O_EXCL, 0755); |
| ASSERT_GE(r, 0, "Unable to create path"); |
| gFileFlags |= kBlobfs; |
| ASSERT_EQ(ftruncate(r, PARTITION_SIZE), 0, "Unable to truncate disk"); |
| uint64_t block_count; |
| ASSERT_EQ(blobstore::blobstore_get_blockcount(r, &block_count), ZX_OK, |
| "Cannot find end of underlying device"); |
| ASSERT_EQ(blobstore::blobstore_mkfs(r, block_count), ZX_OK, |
| "Failed to make blobstore partition"); |
| ASSERT_EQ(close(r), 0, "Unable to close disk\n"); |
| END_HELPER; |
| } |
| |
| bool AddPartitions(Container* container) { |
| BEGIN_HELPER; |
| if (gFileFlags & kData) { |
| printf("Adding data partition to container\n"); |
| ASSERT_EQ(container->AddPartition(data_path, "data"), ZX_OK, |
| "Failed to add data partition"); |
| } |
| |
| if (gFileFlags & kSystem) { |
| printf("Adding system partition to container\n"); |
| ASSERT_EQ(container->AddPartition(system_path, "system"), ZX_OK, |
| "Failed to add system partition"); |
| } |
| |
| if (gFileFlags & kBlobfs) { |
| printf("Adding blobstore partition to container\n"); |
| ASSERT_EQ(container->AddPartition(blobfs_path, "blobstore"), ZX_OK, |
| "Failed to add blobstore partition"); |
| |
| } |
| END_HELPER; |
| } |
| |
| |
| bool CreateSparse() { |
| BEGIN_HELPER; |
| printf("Creating sparse container: %s\n", sparse_path); |
| fbl::unique_ptr<SparseContainer> sparseContainer; |
| ASSERT_EQ(SparseContainer::Create(sparse_path, SLICE_SIZE, &sparseContainer), ZX_OK, |
| "Failed to initialize sparse container"); |
| gFileFlags |= kSparse; |
| ASSERT_TRUE(AddPartitions(sparseContainer.get())); |
| ASSERT_EQ(sparseContainer->Commit(), ZX_OK, "Failed to write to sparse file"); |
| END_HELPER; |
| } |
| |
| bool StatFile(const char* path, off_t* length) { |
| BEGIN_HELPER; |
| fbl::unique_fd fd(open(path, O_RDWR, 0755)); |
| ASSERT_TRUE(fd, "Unable to open file"); |
| struct stat s; |
| ASSERT_EQ(fstat(fd.get(), &s), 0, "Unable to stat file"); |
| *length = s.st_size; |
| END_HELPER; |
| } |
| |
| bool ReportContainer(const char* path, off_t offset) { |
| fbl::unique_ptr<Container> container; |
| off_t length; |
| ASSERT_TRUE(StatFile(path, &length)); |
| ASSERT_EQ(Container::Create(path, offset, length - offset, &container), ZX_OK, |
| "Failed to initialize container"); |
| ASSERT_EQ(container->Verify(), ZX_OK, "File check failed\n"); |
| return true; |
| } |
| |
| bool ReportSparse() { |
| return ReportContainer(sparse_path, 0); |
| } |
| |
| bool CreateFvm(bool create_before, off_t offset) { |
| BEGIN_HELPER; |
| printf("Creating fvm container: %s\n", fvm_path); |
| |
| off_t length = 0; |
| if (create_before) { |
| ASSERT_TRUE(CreateFile(fvm_path, CONTAINER_SIZE, kFvm)); |
| ASSERT_TRUE(StatFile(fvm_path, &length)); |
| } |
| |
| fbl::unique_ptr<FvmContainer> fvmContainer; |
| ASSERT_EQ(FvmContainer::Create(fvm_path, SLICE_SIZE, offset, length - offset, &fvmContainer), |
| ZX_OK, "Failed to initialize fvm container"); |
| if (!create_before) { |
| gFileFlags |= kFvm; |
| } |
| ASSERT_TRUE(AddPartitions(fvmContainer.get())); |
| ASSERT_EQ(fvmContainer->Commit(), ZX_OK, "Failed to write to fvm file"); |
| |
| END_HELPER; |
| } |
| |
| bool ReportFvm(off_t offset) { |
| return ReportContainer(fvm_path, offset); |
| } |
| |
| void GenerateFilename(const char* dir, size_t len, char* out) { |
| char filename[len+1]; |
| |
| for (unsigned i = 0; i < len; i++) { |
| filename[i] = 'a' + rand() % 26; |
| } |
| |
| filename[len] = 0; |
| strcpy(out, dir); |
| strcat(out, filename); |
| } |
| |
| void GenerateDirectory(const char* dir, size_t len, char* out) { |
| GenerateFilename(dir, len, out); |
| strcat(out, "/"); |
| } |
| |
| bool GenerateData(size_t len, fbl::unique_ptr<uint8_t[]>* out) { |
| BEGIN_HELPER; |
| // Fill a test buffer with data |
| fbl::AllocChecker ac; |
| fbl::unique_ptr<uint8_t[]> data(new (&ac) uint8_t[len]); |
| ASSERT_TRUE(ac.check()); |
| |
| for (unsigned n = 0; n < len; n++) { |
| data[n] = static_cast<uint8_t>(rand()); |
| } |
| |
| *out = fbl::move(data); |
| END_HELPER; |
| } |
| |
| bool AddDirectoryMinfs(char* path) { |
| BEGIN_HELPER; |
| ASSERT_EQ(emu_mkdir(path, 0755), 0); |
| END_HELPER; |
| } |
| |
| bool AddFileMinfs(char* path, size_t size) { |
| BEGIN_HELPER; |
| int fd = emu_open(path, O_RDWR | O_CREAT, 0644); |
| ASSERT_GT(fd, 0); |
| fbl::unique_ptr<uint8_t[]> data; |
| ASSERT_TRUE(GenerateData(size, &data)); |
| ASSERT_EQ(emu_write(fd, data.get(), size), size, "Failed to write data to file"); |
| ASSERT_EQ(emu_close(fd), 0); |
| END_HELPER; |
| } |
| |
| bool PopulateMinfs(const char* path, size_t ndirs, size_t nfiles, size_t max_size) { |
| BEGIN_HELPER; |
| ASSERT_EQ(emu_mount(path), 0, "Unable to run mount"); |
| fbl::Vector<fbl::String> paths; |
| paths.push_back(fbl::String("::")); |
| |
| for (unsigned i = 0; i < ndirs; i++) { |
| const char* base_dir = paths[rand() % paths.size()].data(); |
| char new_dir[PATH_MAX]; |
| GenerateDirectory(base_dir, 10, new_dir); |
| AddDirectoryMinfs(new_dir); |
| paths.push_back(fbl::String(new_dir)); |
| } |
| |
| for (unsigned i = 0; i < nfiles; i++) { |
| const char* base_dir = paths[rand() % paths.size()].data(); |
| size_t size = 1 + (rand() % max_size); |
| char new_file[PATH_MAX]; |
| GenerateFilename(base_dir, 10, new_file); |
| AddFileMinfs(new_file, size); |
| } |
| END_HELPER; |
| } |
| |
| bool PopulateData(size_t ndirs, size_t nfiles, size_t max_size) { |
| printf("Populating data partition\n"); |
| return PopulateMinfs(data_path, ndirs, nfiles, max_size); |
| } |
| |
| bool PopulateSystem(size_t ndirs, size_t nfiles, size_t max_size) { |
| printf("Populating system partition\n"); |
| return PopulateMinfs(system_path, ndirs, nfiles, max_size); |
| } |
| |
| bool AddFileBlobstore(blobstore::Blobstore* bs, size_t size) { |
| BEGIN_HELPER; |
| char new_file[PATH_MAX]; |
| GenerateFilename(test_dir, 10, new_file);; |
| fbl::unique_fd datafd(open(new_file, O_RDWR | O_CREAT | O_EXCL, 0755)); |
| ASSERT_TRUE(datafd, "Unable to create new file"); |
| fbl::unique_ptr<uint8_t[]> data; |
| ASSERT_TRUE(GenerateData(size, &data)); |
| ASSERT_EQ(write(datafd.get(), data.get(), size), size, "Failed to write data to file"); |
| ASSERT_EQ(blobstore::blobstore_add_blob(bs, datafd.get()), ZX_OK, "Failed to add blob"); |
| ASSERT_EQ(unlink(new_file), 0); |
| END_HELPER; |
| } |
| |
| bool PopulateBlobstore(size_t nfiles, size_t max_size) { |
| BEGIN_HELPER; |
| printf("Populating blobstore partition\n"); |
| fbl::unique_fd blobfd(open(blobfs_path, O_RDWR, 0755)); |
| ASSERT_TRUE(blobfd, "Unable to open blobstore path"); |
| fbl::RefPtr<blobstore::Blobstore> bs; |
| ASSERT_EQ(blobstore::blobstore_create(&bs, fbl::move(blobfd)), ZX_OK, |
| "Failed to create blobstore"); |
| for (unsigned i = 0; i < nfiles; i++) { |
| size_t size = 1 + (rand() % max_size); |
| ASSERT_TRUE(AddFileBlobstore(bs.get(), size)); |
| } |
| END_HELPER; |
| } |
| |
| bool PopulatePartitions(size_t ndirs, size_t nfiles, size_t max_size) { |
| BEGIN_HELPER; |
| printf("Populating blobstore partition\n"); |
| ASSERT_TRUE(PopulateData(ndirs, nfiles, max_size)); |
| ASSERT_TRUE(PopulateSystem(ndirs, nfiles, max_size)); |
| ASSERT_TRUE(PopulateBlobstore(nfiles, max_size)); |
| END_HELPER; |
| } |
| |
| bool Destroy(const char* path, uint32_t type) { |
| BEGIN_HELPER; |
| printf("Destroying partition: %s\n", path); |
| ASSERT_EQ(unlink(path), 0, "Failed to unlink path"); |
| gFileFlags &= ~type; |
| END_HELPER; |
| } |
| |
| bool DestroyAll() { |
| BEGIN_HELPER; |
| if (gFileFlags & kData) { |
| ASSERT_TRUE(Destroy(data_path, kData)); |
| } |
| |
| if (gFileFlags & kSystem) { |
| ASSERT_TRUE(Destroy(system_path, kSystem)); |
| } |
| |
| if (gFileFlags & kBlobfs) { |
| ASSERT_TRUE(Destroy(blobfs_path, kBlobfs)); |
| } |
| |
| if (gFileFlags & kSparse) { |
| ASSERT_TRUE(Destroy(sparse_path, kSparse)); |
| } |
| |
| if (gFileFlags & kFvm) { |
| ASSERT_TRUE(Destroy(fvm_path, kFvm)); |
| } |
| |
| ASSERT_FALSE(gFileFlags, "Failed to delete all partition files"); |
| END_HELPER; |
| } |
| |
| bool CreatePartitions() { |
| BEGIN_HELPER; |
| ASSERT_TRUE(CreateData()); |
| ASSERT_TRUE(CreateSystem()); |
| ASSERT_TRUE(CreateBlobstore()); |
| END_HELPER; |
| } |
| |
| bool CreateAndReport(container_t type) { |
| BEGIN_HELPER; |
| switch (type) { |
| case SPARSE: { |
| ASSERT_TRUE(CreateSparse()); |
| ASSERT_TRUE(ReportSparse()); |
| break; |
| } |
| case FVM: { |
| ASSERT_TRUE(CreateFvm(true, 0)); |
| ASSERT_TRUE(ReportFvm(0)); |
| break; |
| } |
| case FVM_NEW: { |
| ASSERT_TRUE(CreateFvm(false, 0)); |
| ASSERT_TRUE(ReportFvm(0)); |
| break; |
| } |
| case FVM_OFFSET: { |
| ASSERT_TRUE(CreateFvm(true, SLICE_SIZE)); |
| ASSERT_TRUE(ReportFvm(SLICE_SIZE)); |
| break; |
| } |
| default: { |
| ASSERT_TRUE(false); |
| } |
| } |
| END_HELPER; |
| } |
| |
| template <container_t ContainerType> |
| bool TestEmptyPartitions() { |
| BEGIN_TEST; |
| ASSERT_TRUE(CreatePartitions()); |
| ASSERT_TRUE(CreateAndReport(ContainerType)); |
| ASSERT_TRUE(DestroyAll()); |
| END_TEST; |
| } |
| |
| template <container_t ContainerType, size_t NumDirs, size_t NumFiles, size_t MaxSize> |
| bool TestPartitions() { |
| BEGIN_TEST; |
| ASSERT_TRUE(CreatePartitions()); |
| ASSERT_TRUE(PopulatePartitions(NumDirs, NumFiles, MaxSize)); |
| ASSERT_TRUE(CreateAndReport(ContainerType)); |
| ASSERT_TRUE(DestroyAll()); |
| END_TEST; |
| } |
| |
| bool Setup() { |
| BEGIN_HELPER; |
| srand(time(0)); |
| GenerateDirectory("/tmp/", 20, test_dir); |
| ASSERT_EQ(mkdir(test_dir, 0755), 0, "Failed to create test path"); |
| printf("Created test path %s\n", test_dir); |
| sprintf(data_path, "%sdata.bin", test_dir); |
| sprintf(system_path, "%ssystem.bin", test_dir); |
| sprintf(blobfs_path, "%sblobfs.bin", test_dir); |
| sprintf(sparse_path, "%ssparse.bin", test_dir); |
| sprintf(fvm_path, "%sfvm.bin", test_dir); |
| END_HELPER; |
| } |
| |
| bool Cleanup() { |
| BEGIN_HELPER; |
| DIR* dir = opendir(test_dir); |
| if (!dir) { |
| fprintf(stderr, "Couldn't open test directory\n"); |
| return -1; |
| } |
| |
| struct dirent* de; |
| while ((de = readdir(dir)) != NULL) { |
| if (!strcmp(de->d_name, ".") || !strcmp(de->d_name, "..")) { |
| continue; |
| } |
| |
| printf("Destroying leftover file %s\n", de->d_name); |
| ASSERT_EQ(unlinkat(dirfd(dir), de->d_name, 0), 0); |
| } |
| |
| closedir(dir); |
| printf("Destroying test path: %s\n", test_dir); |
| ASSERT_EQ(rmdir(test_dir), 0, "Failed to remove test path"); |
| END_HELPER; |
| } |
| |
| //TODO(planders): add tests for FVM on GPT (with offset) |
| BEGIN_TEST_CASE(fvm_host_tests) |
| RUN_TEST_MEDIUM(TestEmptyPartitions<SPARSE>) |
| RUN_TEST_MEDIUM(TestEmptyPartitions<FVM>) |
| RUN_TEST_MEDIUM(TestEmptyPartitions<FVM_NEW>) |
| RUN_TEST_MEDIUM(TestEmptyPartitions<FVM_OFFSET>) |
| RUN_TEST_MEDIUM((TestPartitions<SPARSE, 10, 100, (1 << 20)>)) |
| RUN_TEST_MEDIUM((TestPartitions<FVM, 10, 100, (1 << 20)>)) |
| RUN_TEST_MEDIUM((TestPartitions<FVM_NEW, 10, 100, (1 << 20)>)) |
| RUN_TEST_MEDIUM((TestPartitions<FVM_OFFSET, 10, 100, (1 << 20)>)) |
| END_TEST_CASE(fvm_host_tests) |
| |
| int main(int argc, char** argv) { |
| if (!Setup()) { |
| return -1; |
| } |
| int result = unittest_run_all_tests(argc, argv) ? 0 : -1; |
| if (!Cleanup()) { |
| return -1; |
| } |
| return result; |
| } |