blob: 6650248a61a97804b87e1069fba6ba866770ba54 [file] [log] [blame] [edit]
// 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 <fidl/fuchsia.io/cpp/wire.h>
#include <lib/async-loop/cpp/loop.h>
#include <lib/async-loop/default.h>
#include <lib/fit/defer.h>
#include <lib/zxio/ops.h>
#include <lib/zxio/types.h>
#include <string.h>
#include <algorithm>
#include <atomic>
#include <limits>
#include <memory>
#include <zxtest/zxtest.h>
#include "sdk/lib/zxio/private.h"
#include "sdk/lib/zxio/tests/test_directory_server_base.h"
namespace {
namespace fio = fuchsia_io;
class TestServer final : public zxio_tests::TestDirectoryServerBase {
public:
constexpr static size_t kEntryCount = 1000;
// Exercised by |zxio_close|.
void Close(CloseCompleter::Sync& completer) final {
num_close_.fetch_add(1);
completer.ReplySuccess();
}
void ReadDirents(ReadDirentsRequestView request, ReadDirentsCompleter::Sync& completer) override {
size_t actual = 0;
for (; index_ < kEntryCount; ++index_) {
char name[ZXIO_MAX_FILENAME + 1];
const int name_length = snprintf(name, sizeof(name), "%zu", index_);
struct dirent {
uint64_t inode;
uint8_t size;
uint8_t type;
char name[0];
} __PACKED;
auto& entry = *reinterpret_cast<dirent*>(buffer_ + actual);
const size_t entry_size = sizeof(dirent) + name_length;
if (actual + entry_size > request->max_bytes) {
break;
}
ASSERT_GE(name_length, 0);
// No null termination
memcpy(entry.name, name, name_length);
if (name_length > std::numeric_limits<uint8_t>::max()) {
return completer.Close(ZX_ERR_BAD_STATE);
}
entry.size = static_cast<uint8_t>(name_length);
entry.inode = index_;
actual += entry_size;
}
completer.Reply(ZX_OK, fidl::VectorView<uint8_t>::FromExternal(
reinterpret_cast<uint8_t*>(buffer_), actual));
}
void Rewind(RewindCompleter::Sync& completer) final {
memset(buffer_, 0, sizeof(buffer_));
index_ = 0;
completer.Reply(ZX_OK);
}
uint32_t num_close() const { return num_close_.load(); }
private:
std::atomic<uint32_t> num_close_ = 0;
char buffer_[fio::wire::kMaxBuf] = {};
size_t index_ = 0;
};
class DirentTest : public zxtest::Test {
protected:
void SetUp() final {
auto [client_end, server_end] = fidl::Endpoints<fio::Directory>::Create();
ASSERT_OK(zxio_dir_init(&dir_, std::move(client_end)));
server_ = std::make_unique<TestServer>();
loop_ = std::make_unique<async::Loop>(&kAsyncLoopConfigNoAttachToCurrentThread);
ASSERT_OK(loop_->StartThread("fake-filesystem"));
fidl::BindServer(loop_->dispatcher(), std::move(server_end), server_.get());
}
void TearDown() final {
ASSERT_EQ(0, server_->num_close());
ASSERT_OK(zxio_close(&dir_.io, /*should_wait=*/true));
ASSERT_EQ(1, server_->num_close());
}
auto InitIteratorDeferCleanup(zxio_dirent_iterator_t& iterator) {
return zx::make_result(zxio_dirent_iterator_init(&iterator, &dir_.io),
fit::defer([&]() { zxio_dirent_iterator_destroy(&iterator); }));
}
private:
zxio_storage_t dir_;
std::unique_ptr<TestServer> server_;
std::unique_ptr<async::Loop> loop_;
};
TEST_F(DirentTest, StandardBufferSize) {
zxio_dirent_iterator_t iterator;
zx::result cleanup = InitIteratorDeferCleanup(iterator);
ASSERT_OK(cleanup);
char name_buffer[ZXIO_MAX_FILENAME + 1];
zxio_dirent_t entry = {.name = name_buffer};
for (size_t count = 0; count < TestServer::kEntryCount; ++count) {
ASSERT_OK(zxio_dirent_iterator_next(&iterator, &entry));
EXPECT_TRUE(entry.has.id);
EXPECT_EQ(entry.id, count);
char name[ZXIO_MAX_FILENAME + 1];
const int name_length = snprintf(name, sizeof(name), "%zu", count);
EXPECT_EQ(entry.name_length, name_length);
EXPECT_STREQ(std::string_view(name, name_length),
std::string_view(entry.name, entry.name_length));
}
}
TEST_F(DirentTest, Rewind) {
zxio_dirent_iterator_t iterator;
zx::result cleanup = InitIteratorDeferCleanup(iterator);
ASSERT_OK(cleanup);
char name_buffer[ZXIO_MAX_FILENAME + 1];
zxio_dirent_t entry = {.name = name_buffer};
std::vector<std::string> names;
names.reserve(TestServer::kEntryCount);
for (size_t count = 0; count < TestServer::kEntryCount; ++count) {
ASSERT_OK(zxio_dirent_iterator_next(&iterator, &entry));
ASSERT_LE(entry.name_length, ZXIO_MAX_FILENAME);
names.emplace_back(entry.name, entry.name_length);
}
ASSERT_OK(zxio_dirent_iterator_rewind(&iterator));
for (size_t count = 0; count < TestServer::kEntryCount; ++count) {
ASSERT_OK(zxio_dirent_iterator_next(&iterator, &entry));
ASSERT_LE(entry.name_length, ZXIO_MAX_FILENAME);
EXPECT_STREQ(std::string_view(entry.name, entry.name_length), names[count]);
}
}
TEST_F(DirentTest, RewindHalfwayThrough) {
zxio_dirent_iterator_t iterator;
zx::result cleanup = InitIteratorDeferCleanup(iterator);
ASSERT_OK(cleanup);
constexpr size_t kHalfEntryCount = TestServer::kEntryCount / 2;
static_assert(kHalfEntryCount != 0);
char name_buffer[ZXIO_MAX_FILENAME + 1];
zxio_dirent_t entry = {.name = name_buffer};
std::vector<std::string> names;
names.reserve(kHalfEntryCount);
for (size_t count = 0; count < kHalfEntryCount; ++count) {
ASSERT_OK(zxio_dirent_iterator_next(&iterator, &entry));
ASSERT_LE(entry.name_length, ZXIO_MAX_FILENAME);
names.emplace_back(entry.name, entry.name_length);
}
ASSERT_OK(zxio_dirent_iterator_rewind(&iterator));
for (size_t count = 0; count < kHalfEntryCount; ++count) {
ASSERT_OK(zxio_dirent_iterator_next(&iterator, &entry));
ASSERT_LE(entry.name_length, ZXIO_MAX_FILENAME);
EXPECT_STREQ(std::string_view(entry.name, entry.name_length), names[count]);
}
}
} // namespace