blob: af0e5e8708e2983d7ce6e476245e543ba3e7b26a [file] [log] [blame]
// Copyright 2018 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 <fs/service.h>
#include <fs/synchronous_vfs.h>
#include <gtest/gtest.h>
#include <test/placeholders/cpp/fidl.h>
#include "lib/gtest/real_loop_fixture.h"
#include "src/lib/fxl/strings/substitute.h"
#include "src/sys/appmgr/service_provider_dir_impl.h"
#include "src/sys/appmgr/util.h"
namespace component {
namespace {
class FakeEcho : public test::placeholders::Echo {
public:
FakeEcho(fidl::InterfaceRequest<test::placeholders::Echo> request) : binding_(this) {
binding_.Bind(std::move(request));
};
~FakeEcho() override {}
void EchoString(fidl::StringPtr value, EchoStringCallback callback) override {
callback(answer_);
}
void SetAnswer(fidl::StringPtr answer) { answer_ = answer; }
private:
fidl::StringPtr answer_;
::fidl::Binding<test::placeholders::Echo> binding_;
FXL_DISALLOW_COPY_AND_ASSIGN(FakeEcho);
};
class ServiceProviderTest : public ::gtest::RealLoopFixture {
protected:
ServiceProviderTest() : vfs_(dispatcher()) {}
fbl::RefPtr<fs::Service> CreateService(int set_value) {
return fbl::AdoptRef(new fs::Service([this, set_value](zx::channel channel) {
value_ = set_value;
return ZX_OK;
}));
}
fbl::RefPtr<fs::Service> CreateEchoService(fidl::StringPtr answer) {
return fbl::AdoptRef(new fs::Service([this, answer](zx::channel channel) {
auto echo = std::make_unique<FakeEcho>(
fidl::InterfaceRequest<test::placeholders::Echo>(std::move(channel)));
echo->SetAnswer(answer);
echo_services_.push_back(std::move(echo));
return ZX_OK;
}));
}
void AddService(ServiceProviderDirImpl* service_provider, const std::string& prefix, int val) {
AddService(service_provider, prefix, val, CreateService(val));
}
void AddService(ServiceProviderDirImpl* service_provider, const std::string& prefix, int i,
fbl::RefPtr<fs::Service> svc) {
const std::string service_name = fxl::Substitute("$0_service$1", prefix, std::to_string(i));
service_provider->AddService(service_name, svc);
}
void GetService(ServiceProviderDirImpl* service_provider, const fbl::String& service_name,
fbl::RefPtr<fs::Service>* out) {
fbl::RefPtr<fs::Vnode> child;
ASSERT_EQ(ZX_OK, service_provider->Lookup(service_name, &child)) << service_name.c_str();
*out = fbl::RefPtr<fs::Service>(static_cast<fs::Service*>(child.get()));
}
void TestService(ServiceProviderDirImpl* service_provider, const fbl::String& service_name,
int expected_value) {
// Using Lookup.
value_ = -1;
fbl::RefPtr<fs::Vnode> child;
ASSERT_EQ(ZX_OK, service_provider->Lookup(service_name, &child)) << service_name.c_str();
fbl::RefPtr<fs::Service> child_node;
GetService(service_provider, service_name, &child_node);
ASSERT_TRUE(child_node);
ASSERT_EQ(ZX_OK, vfs_.Serve(child_node, zx::channel(), fs::VnodeConnectionOptions()));
EXPECT_EQ(expected_value, value_) << service_name.c_str();
// Using ConnectToService.
value_ = -1;
zx::channel h1, h2;
ASSERT_EQ(ZX_OK, zx::channel::create(0, &h1, &h2));
service_provider->ConnectToService(std::string(service_name.data(), service_name.size()),
std::move(h2));
EXPECT_EQ(expected_value, value_) << service_name.c_str();
}
void TestMissingService(ServiceProviderDirImpl* service_provider,
const fbl::String& service_name) {
// Using Lookup.
value_ = -1;
fbl::RefPtr<fs::Vnode> child;
ASSERT_EQ(ZX_ERR_NOT_FOUND, service_provider->Lookup(service_name, &child))
<< service_name.c_str();
// Never connected to service.
EXPECT_EQ(value_, -1) << service_name.c_str();
// Using ConnectToService.
value_ = -1;
zx::channel h1, h2;
ASSERT_EQ(ZX_OK, zx::channel::create(0, &h1, &h2));
service_provider->ConnectToService(std::string(service_name.data(), service_name.size()),
std::move(h2));
// Never connected to service.
EXPECT_EQ(value_, -1) << service_name.c_str();
}
zx::channel OpenAsDirectory(fbl::RefPtr<ServiceProviderDirImpl> service) {
return Util::OpenAsDirectory(&vfs_, service);
}
fs::SynchronousVfs vfs_;
int value_ = 0;
std::vector<std::unique_ptr<FakeEcho>> echo_services_;
};
TEST_F(ServiceProviderTest, SimpleService) {
ServiceProviderDirImpl service_provider(fbl::AdoptRef(new LogConnectorImpl("test")));
AddService(&service_provider, "my", 1);
TestService(&service_provider, "my_service1", 1);
TestMissingService(&service_provider, "nonexistent_service");
}
TEST_F(ServiceProviderTest, Parent) {
auto service3 = CreateService(3);
auto service4 = CreateService(4);
// 'fake_service1' overlaps in parent and child; the child's service should
// take priority.
ServiceProviderDirImpl service_provider(fbl::AdoptRef(new LogConnectorImpl("test")));
AddService(&service_provider, "fake", 1);
auto parent_service_provider =
fbl::AdoptRef(new ServiceProviderDirImpl(fbl::AdoptRef(new LogConnectorImpl("parent"))));
AddService(parent_service_provider.get(), "fake", 1, service3);
AddService(parent_service_provider.get(), "fake", 2);
service_provider.set_parent(parent_service_provider);
AddService(&service_provider, "fake", 2, service4);
// should call child service
TestService(&service_provider, "fake_service1", 1);
// check that we can get parent service, because service4 was added after
// parent was set
TestService(&service_provider, "fake_service2", 2);
// check that parent is able to access its service
TestService(parent_service_provider.get(), "fake_service1", 3);
// check nonexistence of bogus service
TestMissingService(&service_provider, "nonexistent_service");
}
TEST_F(ServiceProviderTest, RestrictedServices) {
static const std::vector<std::string> kAllowlist{"parent_service1", "my_service1"};
auto parent_service_provider =
fbl::AdoptRef(new ServiceProviderDirImpl(AdoptRef(new LogConnectorImpl("parent"))));
AddService(parent_service_provider.get(), "parent", 1);
AddService(parent_service_provider.get(), "parent", 2);
ServiceProviderDirImpl service_provider(AdoptRef(new LogConnectorImpl("main")), &kAllowlist);
AddService(&service_provider, "my", 1);
AddService(&service_provider, "my", 2);
service_provider.set_parent(parent_service_provider);
// should be able to call "my_service1" and "parent_service1"
TestService(&service_provider, "my_service1", 1);
TestService(&service_provider, "parent_service1", 1);
// "my_service2" and "parent_service2" are not accessible
TestMissingService(&service_provider, "my_service2");
TestMissingService(&service_provider, "parent_service2");
}
class DirentChecker {
public:
DirentChecker(const void* buffer, size_t length)
: current_(reinterpret_cast<const uint8_t*>(buffer)), remaining_(length) {}
void ExpectEnd() { EXPECT_EQ(0u, remaining_); }
void ExpectEntry(const char* name, uint32_t vtype) {
ASSERT_NE(0u, remaining_);
const auto entry = reinterpret_cast<const vdirent_t*>(current_);
const size_t entry_size = entry->size + sizeof(vdirent_t);
ASSERT_GE(remaining_, entry_size);
current_ += entry_size;
remaining_ -= entry_size;
const std::string entry_name(entry->name, strlen(name));
EXPECT_STREQ(entry_name.c_str(), name);
EXPECT_EQ(VTYPE_TO_DTYPE(vtype), entry->type) << "Unexpected DTYPE for '" << name << "'.";
}
private:
const uint8_t* current_;
size_t remaining_;
};
constexpr size_t kBufSz = 4096;
TEST_F(ServiceProviderTest, Readdir_Simple) {
ServiceProviderDirImpl service_provider(AdoptRef(new LogConnectorImpl("test")));
AddService(&service_provider, "my", 1);
AddService(&service_provider, "my", 2);
AddService(&service_provider, "my", 3);
fs::VdirCookie cookie;
uint8_t buffer[kBufSz];
size_t len;
{
EXPECT_EQ(ZX_OK, service_provider.Readdir(&cookie, buffer, sizeof(buffer), &len));
DirentChecker dc(buffer, len);
dc.ExpectEntry(".", V_TYPE_DIR);
dc.ExpectEntry("my_service1", V_TYPE_FILE);
dc.ExpectEntry("my_service2", V_TYPE_FILE);
dc.ExpectEntry("my_service3", V_TYPE_FILE);
dc.ExpectEnd();
}
{
EXPECT_EQ(ZX_OK, service_provider.Readdir(&cookie, buffer, sizeof(buffer), &len));
EXPECT_EQ(len, 0u);
}
}
TEST_F(ServiceProviderTest, Readdir_WithParent) {
ServiceProviderDirImpl service_provider(AdoptRef(new LogConnectorImpl("root")));
AddService(&service_provider, "my", 1);
AddService(&service_provider, "my", 2);
auto parent_service_provider =
fbl::AdoptRef(new ServiceProviderDirImpl(AdoptRef(new LogConnectorImpl("parent"))));
AddService(parent_service_provider.get(), "parent", 1);
AddService(parent_service_provider.get(), "parent", 2);
service_provider.set_parent(parent_service_provider);
fs::VdirCookie cookie;
uint8_t buffer[kBufSz];
size_t len;
{
EXPECT_EQ(ZX_OK, service_provider.Readdir(&cookie, buffer, sizeof(buffer), &len));
DirentChecker dc(buffer, len);
dc.ExpectEntry(".", V_TYPE_DIR);
dc.ExpectEntry("my_service1", V_TYPE_FILE);
dc.ExpectEntry("my_service2", V_TYPE_FILE);
dc.ExpectEntry("parent_service1", V_TYPE_FILE);
dc.ExpectEntry("parent_service2", V_TYPE_FILE);
dc.ExpectEnd();
}
{
EXPECT_EQ(ZX_OK, service_provider.Readdir(&cookie, buffer, sizeof(buffer), &len));
EXPECT_EQ(len, 0u);
}
}
// Test that a parent's custom LogSink is inherited by child, and not provided by appmgr.
TEST_F(ServiceProviderTest, CustomLogSinkInheritedFromParent) {
static const std::vector<std::string> kAllowlist{fuchsia::logger::LogSink::Name_};
// parent has a custom LogSink.
auto parent =
AdoptRef(new ServiceProviderDirImpl(AdoptRef(new LogConnectorImpl("root")), &kAllowlist));
int parent_logger_requests = 0;
parent->AddService(fuchsia::logger::LogSink::Name_,
fbl::AdoptRef(new fs::Service([&](zx::channel channel) {
parent_logger_requests++;
return ZX_OK;
})));
parent->InitLogging();
// child requests a LogSink.
auto child =
AdoptRef(new ServiceProviderDirImpl(AdoptRef(new LogConnectorImpl("child")), &kAllowlist));
child->set_parent(parent);
child->InitLogging();
{
zx::channel client;
zx::channel server;
ZX_ASSERT(zx::channel::create(0, &client, &server) == ZX_OK);
parent->ConnectToService(fuchsia::logger::LogSink::Name_, std::move(server));
RunLoopUntil([&] { return parent_logger_requests == 1; });
}
// test that child connects to parent's custom logsink.
{
zx::channel client;
zx::channel server;
ZX_ASSERT(zx::channel::create(0, &client, &server) == ZX_OK);
child->ConnectToService(fuchsia::logger::LogSink::Name_, std::move(server));
RunLoopUntil([&] { return parent_logger_requests == 2; });
}
}
// NOTE: We don't test readdir with backing_dir because we know it is broken.
// This will be fixed by removing backing_dir.
} // namespace
} // namespace component