| // Copyright 2020 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 "src/lib/loader_service/loader_service.h" |
| |
| #include <fuchsia/io/llcpp/fidl.h> |
| #include <lib/fdio/directory.h> |
| #include <lib/fdio/io.h> |
| #include <lib/fidl-async/cpp/bind.h> |
| #include <lib/syslog/cpp/macros.h> |
| #include <zircon/errors.h> |
| |
| #include "src/lib/files/path.h" |
| #include "src/lib/fxl/strings/string_printf.h" |
| |
| namespace loader { |
| |
| namespace fio = ::llcpp::fuchsia::io; |
| |
| LoaderServiceBase::~LoaderServiceBase() {} |
| |
| const std::string& LoaderServiceBase::log_prefix() { |
| if (log_prefix_.empty()) { |
| log_prefix_ = fxl::StringPrintf("ldsvc (%s): ", name_.data()); |
| } |
| return log_prefix_; |
| } |
| |
| zx::status<zx::channel> LoaderServiceBase::Connect() { |
| zx::channel c1, c2; |
| auto status = zx::make_status(zx::channel::create(0, &c1, &c2)); |
| if (status.is_error()) { |
| return status.take_error(); |
| } |
| status = Bind(std::move(c1)); |
| if (status.is_error()) { |
| return status.take_error(); |
| } |
| return zx::ok(std::move(c2)); |
| } |
| |
| zx::status<> LoaderServiceBase::Bind(zx::channel channel) { |
| // Each connection gets a strong (shared_ptr) reference to the server, which keeps the overall |
| // service alive as long as there is one open connection even if the original reference is |
| // dropped. |
| auto conn = std::make_unique<LoaderConnection>(shared_from_this()); |
| // This returns a ServerBindingRef on success, but we don't need it since we don't currently need |
| // a way to unbind connections from the server side. Dropping it does not automatically unbind. |
| auto status = fidl::BindServer(dispatcher_, std::move(channel), std::move(conn)); |
| if (status.is_error()) { |
| return zx::error(status.take_error()); |
| } |
| return zx::ok(); |
| } |
| |
| void LoaderConnection::Done(DoneCompleter::Sync& completer) { completer.Close(ZX_OK); } |
| |
| void LoaderConnection::LoadObject(fidl::StringView object_name, |
| LoadObjectCompleter::Sync& completer) { |
| std::string name(object_name.data(), object_name.size()); |
| |
| auto reply = [this, &name, &completer](zx::status<zx::vmo> status) { |
| // Generally we wouldn't want to log in a library, but these logs have proven to be useful in |
| // past, and the new loader name in the prefix will make them moreso. |
| if (status.status_value() == ZX_ERR_NOT_FOUND) { |
| FX_LOGS(WARNING) << log_prefix() << "could not find '" << name << "'"; |
| } |
| |
| auto result = completer.Reply(status.status_value(), std::move(status).value_or(zx::vmo())); |
| if (!result.ok()) { |
| FX_LOGS(WARNING) << log_prefix() << "failed to reply to LoadObject(" << name |
| << "): " << result.status_string() << " (" << result.error() << ')'; |
| } |
| }; |
| |
| if (config_.subdir.empty()) { |
| reply(server_->LoadObjectImpl(name)); |
| return; |
| } |
| |
| // If subdir is non-empty, the loader should search this subdirectory for the object first. If |
| // exclusive is also true, only subdir should be searched. |
| std::string prefixed_name = files::JoinPath(config_.subdir, name); |
| auto status = server_->LoadObjectImpl(prefixed_name); |
| if (status.is_error() && !config_.exclusive) { |
| reply(server_->LoadObjectImpl(name)); |
| return; |
| } |
| reply(std::move(status)); |
| } |
| |
| void LoaderConnection::Config(fidl::StringView config, ConfigCompleter::Sync& completer) { |
| // fidl::StringView is not null-terminated so must pass size to std::string constructor. |
| std::string config_str(config.data(), config.size()); |
| |
| auto reply = [this, &config_str, &completer](zx_status_t status) { |
| auto result = completer.Reply(status); |
| if (!result.ok()) { |
| FX_LOGS(WARNING) << log_prefix() << "failed to reply to Config(" << config_str |
| << "): " << result.status_string() << " (" << result.error() << ')'; |
| } |
| }; |
| |
| // Config strings must not contain path separators. |
| if (config_str.find('/') != std::string::npos) { |
| reply(ZX_ERR_INVALID_ARGS); |
| return; |
| } |
| |
| // The config string is a single subdirectory name to be searched for objects first, optionally |
| // followed by a '!' character, which indicates that only the subdirectory should be searched. |
| bool exclusive = false; |
| if (!config_str.empty() && config_str.back() == '!') { |
| exclusive = true; |
| config_str.pop_back(); |
| |
| // Make sure config wasn't "!" (though just "" is ok to reset config) |
| if (config_str.empty()) { |
| reply(ZX_ERR_INVALID_ARGS); |
| return; |
| } |
| } |
| config_ = LoadConfig{.subdir = config_str, .exclusive = exclusive}; |
| reply(ZX_OK); |
| } |
| |
| void LoaderConnection::Clone(zx::channel loader, CloneCompleter::Sync& completer) { |
| completer.Reply(server_->Bind(std::move(loader)).status_value()); |
| } |
| |
| // static |
| std::shared_ptr<LoaderService> LoaderService::Create(async_dispatcher_t* dispatcher, |
| fbl::unique_fd lib_dir, std::string name) { |
| // Can't use make_shared because constructor is protected |
| return std::shared_ptr<LoaderService>( |
| new LoaderService(dispatcher, std::move(lib_dir), std::move(name))); |
| } |
| |
| zx::status<zx::vmo> LoaderService::LoadObjectImpl(std::string path) { |
| const uint32_t kFlags = |
| fio::OPEN_FLAG_NOT_DIRECTORY | fio::OPEN_RIGHT_READABLE | fio::OPEN_RIGHT_EXECUTABLE; |
| |
| fbl::unique_fd fd; |
| zx_status_t status = fdio_open_fd_at(dir_.get(), path.data(), kFlags, fd.reset_and_get_address()); |
| if (status != ZX_OK) { |
| return zx::error(status); |
| } |
| |
| zx::vmo vmo; |
| status = fdio_get_vmo_exec(fd.get(), vmo.reset_and_get_address()); |
| if (status != ZX_OK) { |
| return zx::error(status); |
| } |
| return zx::ok(std::move(vmo)); |
| } |
| |
| } // namespace loader |