| // Copyright 2019 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 <errno.h> |
| #include <fcntl.h> |
| |
| #include <fbl/alloc_checker.h> |
| #include <fbl/auto_lock.h> |
| #include <fbl/intrusive_double_list.h> |
| #include <fbl/string.h> |
| #include <fbl/unique_fd.h> |
| #include <fbl/unique_ptr.h> |
| #include <fs-test-utils/blobfs/blobfs.h> |
| #include <fs-test-utils/blobfs/bloblist.h> |
| #include <lib/fdio/io.h> |
| #include <unittest/unittest.h> |
| |
| namespace fs_test_utils { |
| |
| bool BlobList::CreateBlob(unsigned* seed) { |
| return CreateBlob(seed, 1); |
| } |
| |
| // Generate and open a new blob |
| bool BlobList::CreateBlob(unsigned* seed, size_t writes_remaining) { |
| BEGIN_HELPER; |
| ASSERT_EQ(list_state_, BlobListState::kOpen); |
| |
| fbl::unique_ptr<BlobInfo> info; |
| ASSERT_TRUE(GenerateRandomBlob(mount_path_, 1 + (rand_r(seed) % (1 << 16)), &info)); |
| |
| fbl::AllocChecker ac; |
| fbl::unique_ptr<BlobState> state(new (&ac) BlobState(std::move(info), writes_remaining)); |
| ASSERT_EQ(ac.check(), true); |
| |
| { |
| fbl::AutoLock al(&list_lock_); |
| |
| if (blob_count_ >= kMaxBlobs) { |
| return true; |
| } |
| fbl::unique_fd fd(open(state->info->path, O_CREAT | O_RDWR)); |
| ASSERT_TRUE(fd, "Failed to create blob"); |
| state->fd.reset(fd.release()); |
| |
| list_.push_front(std::move(state)); |
| blob_count_++; |
| } |
| END_HELPER; |
| } |
| |
| // Allocate space for an open, empty blob |
| bool BlobList::ConfigBlob() { |
| BEGIN_HELPER; |
| ASSERT_EQ(list_state_, BlobListState::kOpen); |
| |
| fbl::unique_ptr<BlobState> state; |
| { |
| fbl::AutoLock al(&list_lock_); |
| state = list_.pop_back(); |
| } |
| |
| if (state == nullptr) { |
| return true; |
| } else if (state->state == TestState::kEmpty) { |
| // if we are going to run out of space on the underlying blobfs partition, the |
| // ZX_ERR_NO_SPACE is going to come up here. if we run out of space, put the kEmpty blob |
| // back onto the blob list. |
| if (ftruncate(state->fd.get(), state->info->size_data) == 0) { |
| state->state = TestState::kConfigured; |
| } else { |
| ASSERT_EQ(errno, ENOSPC, "ftruncate returned an unrecoverable error"); |
| } |
| } |
| { |
| fbl::AutoLock al(&list_lock_); |
| list_.push_front(std::move(state)); |
| } |
| END_HELPER; |
| } |
| |
| // Write the data for an open, partially written blob |
| bool BlobList::WriteData() { |
| BEGIN_HELPER; |
| ASSERT_EQ(list_state_, BlobListState::kOpen); |
| |
| fbl::unique_ptr<BlobState> state; |
| { |
| fbl::AutoLock al(&list_lock_); |
| state = list_.pop_back(); |
| } |
| if (state == nullptr) { |
| return true; |
| } else if (state->state == TestState::kConfigured) { |
| size_t bytes_write = state->bytes_remaining / state->writes_remaining; |
| size_t bytes_offset = state->info->size_data - state->bytes_remaining; |
| ASSERT_EQ(StreamAll(write, state->fd.get(), state->info->data.get() + bytes_offset, |
| bytes_write), 0, "Failed to write Data"); |
| |
| state->writes_remaining--; |
| state->bytes_remaining -= bytes_write; |
| if (state->writes_remaining == 0 && state->bytes_remaining == 0) { |
| state->state = TestState::kReadable; |
| } |
| } |
| { |
| fbl::AutoLock al(&list_lock_); |
| list_.push_front(std::move(state)); |
| } |
| END_HELPER; |
| } |
| |
| // Read the blob's data |
| bool BlobList::ReadData() { |
| BEGIN_HELPER; |
| ASSERT_EQ(list_state_, BlobListState::kOpen); |
| |
| fbl::unique_ptr<BlobState> state; |
| { |
| fbl::AutoLock al(&list_lock_); |
| state = list_.pop_back(); |
| } |
| if (state == nullptr) { |
| return true; |
| } else if (state->state == TestState::kReadable) { |
| ASSERT_TRUE(VerifyContents(state->fd.get(), state->info->data.get(), |
| state->info->size_data)); |
| } |
| { |
| fbl::AutoLock al(&list_lock_); |
| list_.push_front(std::move(state)); |
| } |
| END_HELPER; |
| } |
| |
| // Unlink the blob |
| bool BlobList::UnlinkBlob() { |
| BEGIN_HELPER; |
| ASSERT_EQ(list_state_, BlobListState::kOpen); |
| |
| fbl::unique_ptr<BlobState> state; |
| { |
| fbl::AutoLock al(&list_lock_); |
| state = list_.pop_back(); |
| } |
| if (state == nullptr) { |
| return true; |
| } |
| ASSERT_EQ(unlink(state->info->path), 0, "Could not unlink blob"); |
| ASSERT_EQ(close(state->fd.release()), 0, "Could not close blob"); |
| { |
| fbl::AutoLock al(&list_lock_); |
| blob_count_--; |
| } |
| END_HELPER; |
| } |
| |
| bool BlobList::ReopenBlob() { |
| BEGIN_HELPER; |
| ASSERT_EQ(list_state_, BlobListState::kOpen); |
| |
| fbl::unique_ptr<BlobState> state; |
| { |
| fbl::AutoLock al(&list_lock_); |
| state = list_.pop_back(); |
| } |
| if (state == nullptr) { |
| return true; |
| } else if (state->state == TestState::kReadable) { |
| ASSERT_EQ(close(state->fd.release()), 0, "Could not close blob"); |
| fbl::unique_fd fd(open(state->info->path, O_RDONLY)); |
| ASSERT_TRUE(fd, "Failed to reopen blob"); |
| state->fd.reset(fd.release()); |
| } |
| { |
| fbl::AutoLock al(&list_lock_); |
| list_.push_front(std::move(state)); |
| } |
| END_HELPER; |
| } |
| |
| bool BlobList::VerifyAll() { |
| BEGIN_HELPER; |
| ASSERT_EQ(list_state_, BlobListState::kOpen); |
| |
| fbl::AutoLock al(&list_lock_); |
| |
| for (auto& state : list_) { |
| if (state.state == TestState::kReadable) { |
| ASSERT_TRUE(VerifyContents(state.fd.get(), state.info->data.get(), |
| state.info->size_data)); |
| } |
| } |
| |
| END_HELPER; |
| } |
| |
| bool BlobList::CloseAll() { |
| BEGIN_HELPER; |
| ASSERT_EQ(list_state_, BlobListState::kOpen); |
| |
| // the functions that act on all the blobs in the list are not really |
| // thread-safe, but we are going to be good citizens anyway. |
| fbl::AutoLock al(&list_lock_); |
| |
| fbl::DoublyLinkedList<fbl::unique_ptr<BlobState>> readable_list; |
| fbl::unique_ptr<BlobState> state; |
| while(!list_.is_empty()) { |
| state = list_.pop_back(); |
| ASSERT_EQ(close(state->fd.release()), 0, "Could not close blob"); |
| // only put the blob back in the blob list if it's fully written. |
| if (state->state == TestState::kReadable) { |
| readable_list.push_front(std::move(state)); |
| } |
| } |
| |
| list_ = std::move(readable_list); |
| list_state_ = BlobListState::kClosed; |
| |
| END_HELPER; |
| } |
| |
| bool BlobList::OpenAll() { |
| BEGIN_HELPER; |
| ASSERT_EQ(list_state_, BlobListState::kClosed); |
| |
| // the functions that act on all the blobs in the list are not really |
| // thread-safe, but we are going to be good citizens anyway. |
| fbl::AutoLock al(&list_lock_); |
| |
| for (auto& state : list_) { |
| if (state.state == TestState::kReadable) { |
| fbl::unique_fd fd(open(state.info->path, O_RDONLY)); |
| ASSERT_TRUE(fd, "Failed to open blob"); |
| state.fd.reset(fd.release()); |
| } else { // kEmpty, kConfig |
| // if a blob was not fully written by the time it was closed, it |
| // should be gone. |
| ASSERT_LT(open(state.info->path, O_RDONLY), 0); |
| } |
| } |
| |
| list_state_ = BlobListState::kOpen; |
| |
| END_HELPER; |
| } |
| |
| } // namespace fs_test_utils |