blob: fc21e50bb0b4a4c8aa7fc58e5af2aefd60f151de [file] [log] [blame]
// 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 <fuchsia/io/llcpp/fidl.h>
#include <lib/async-loop/cpp/loop.h>
#include <lib/async-loop/default.h>
#include <lib/fidl-async/cpp/bind.h>
#include <lib/zxio/inception.h>
#include <lib/zxio/ops.h>
#include <string.h>
#include <algorithm>
#include <atomic>
#include <memory>
#include <zxtest/zxtest.h>
namespace {
namespace fio = fuchsia_io;
namespace fio2 = fuchsia_io2;
class TestServer final : public fidl::WireRawChannelInterface<fio::Directory> {
public:
TestServer() = default;
constexpr static int kEntryCount = 1000;
// Exercised by |zxio_close|.
void Close(CloseCompleter::Sync& completer) override {
num_close_.fetch_add(1);
completer.Reply(ZX_OK);
}
void Clone(uint32_t flags, zx::channel object, CloneCompleter::Sync& completer) override {
completer.Close(ZX_ERR_NOT_SUPPORTED);
}
void Describe(DescribeCompleter::Sync& completer) override {
completer.Close(ZX_ERR_NOT_SUPPORTED);
}
void Sync(SyncCompleter::Sync& completer) override { completer.Close(ZX_ERR_NOT_SUPPORTED); }
void GetAttr(GetAttrCompleter::Sync& completer) override {
completer.Close(ZX_ERR_NOT_SUPPORTED);
}
void SetAttr(uint32_t flags, fio::wire::NodeAttributes attribute,
SetAttrCompleter::Sync& completer) override {
completer.Close(ZX_ERR_NOT_SUPPORTED);
}
void Open(uint32_t flags, uint32_t mode, ::fidl::StringView path, ::zx::channel object,
OpenCompleter::Sync& completer) override {
completer.Close(ZX_ERR_NOT_SUPPORTED);
}
void AddInotifyFilter(::fidl::StringView path, fio2::wire::InotifyWatchMask filters,
uint32_t watch_descriptor, ::zx::socket socket,
AddInotifyFilterCompleter::Sync& completer) override {
completer.Close(ZX_ERR_NOT_SUPPORTED);
}
void Unlink(::fidl::StringView path, UnlinkCompleter::Sync& completer) override {
completer.Close(ZX_ERR_NOT_SUPPORTED);
}
void Unlink2(::fidl::StringView path, Unlink2Completer::Sync& completer) override {
completer.Close(ZX_ERR_NOT_SUPPORTED);
}
void ReadDirents(uint64_t max_bytes, ReadDirentsCompleter::Sync& completer) override {
auto buffer_start = reinterpret_cast<uint8_t*>(buffer_);
size_t actual = 0;
for (; index_ < kEntryCount; index_++) {
const size_t name_length = std::min(static_cast<size_t>(index_) + 1, fio::wire::kMaxFilename);
auto buffer_position = buffer_start + actual;
struct dirent {
uint64_t inode;
uint8_t size;
uint8_t type;
char name[0];
} __PACKED;
auto entry = reinterpret_cast<dirent*>(buffer_position);
size_t entry_size = sizeof(dirent) + name_length;
if (actual + entry_size > max_bytes) {
completer.Reply(ZX_OK, fidl::VectorView<uint8_t>::FromExternal(buffer_start, actual));
return;
}
auto name = new char[name_length + 1];
snprintf(name, name_length + 1, "%0*d", static_cast<int>(name_length), index_);
// No null termination
memcpy(entry->name, name, name_length);
delete[] name;
if (name_length > UINT8_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(buffer_start, actual));
}
void Rewind(RewindCompleter::Sync& completer) override {
memset(buffer_, 0, sizeof(buffer_));
index_ = 0;
completer.Reply(ZX_OK);
}
void GetToken(GetTokenCompleter::Sync& completer) override {
completer.Close(ZX_ERR_NOT_SUPPORTED);
}
void Rename(::fidl::StringView src, ::zx::handle dst_parent_token, ::fidl::StringView dst,
RenameCompleter::Sync& completer) override {
completer.Close(ZX_ERR_NOT_SUPPORTED);
}
void Link(::fidl::StringView src, ::zx::handle dst_parent_token, ::fidl::StringView dst,
LinkCompleter::Sync& completer) override {
completer.Close(ZX_ERR_NOT_SUPPORTED);
}
void Watch(uint32_t mask, uint32_t options, ::zx::channel watcher,
WatchCompleter::Sync& completer) override {
completer.Close(ZX_ERR_NOT_SUPPORTED);
}
uint32_t num_close() const { return num_close_.load(); }
private:
std::atomic<uint32_t> num_close_ = 0;
char buffer_[fio::wire::kMaxBuf] = {};
int index_ = 0;
};
class DirentTest : public zxtest::Test {
public:
void SetUp() final {
ASSERT_OK(zx::channel::create(0, &control_client_end_, &control_server_end_));
ASSERT_OK(zxio_dir_init(&dir_, control_client_end_.release()));
server_ = std::make_unique<TestServer>();
loop_ = std::make_unique<async::Loop>(&kAsyncLoopConfigNoAttachToCurrentThread);
ASSERT_OK(loop_->StartThread("fake-filesystem"));
ASSERT_OK(fidl::BindSingleInFlightOnly(loop_->dispatcher(), std::move(control_server_end_),
server_.get()));
}
void TearDown() final {
ASSERT_EQ(0, server_->num_close());
ASSERT_OK(zxio_close(&dir_.io));
ASSERT_EQ(1, server_->num_close());
}
protected:
zxio_storage_t dir_;
zx::channel control_client_end_;
zx::channel control_server_end_;
std::unique_ptr<TestServer> server_;
std::unique_ptr<async::Loop> loop_;
};
TEST_F(DirentTest, StandardBufferSize) {
zxio_dirent_iterator_t iterator;
ASSERT_OK(zxio_dirent_iterator_init(&iterator, &dir_.io));
for (int count = 0; count < TestServer::kEntryCount; count++) {
zxio_dirent_t* entry;
EXPECT_OK(zxio_dirent_iterator_next(&iterator, &entry));
EXPECT_TRUE(entry->has.id);
EXPECT_EQ(entry->id, count);
const size_t name_length = std::min(static_cast<size_t>(count) + 1, fio::wire::kMaxFilename);
EXPECT_EQ(entry->name_length, name_length);
}
zxio_dirent_iterator_destroy(&iterator);
}
} // namespace