blob: e20fac48377c6bcd4fa64bbc2f5c07701a2e7110 [file] [log] [blame]
// Copyright 2016 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/fsl/io/device_watcher.h"
#include <lib/async/default.h>
#include <lib/component/incoming/cpp/protocol.h>
#include <lib/syslog/cpp/macros.h>
#include <utility>
namespace fio = fuchsia_io;
namespace fsl {
DeviceWatcher::DeviceWatcher(async_dispatcher_t* dispatcher,
fidl::ClientEnd<fuchsia_io::Directory> dir,
fidl::Endpoints<fuchsia_io::DirectoryWatcher> dir_watcher,
ExistsCallback exists_callback, IdleCallback idle_callback)
: dir_watcher_(std::move(dir_watcher.client)),
exists_callback_(std::move(exists_callback)),
idle_callback_(std::move(idle_callback)),
wait_(this, dir_watcher_.channel().get(), ZX_CHANNEL_READABLE | ZX_CHANNEL_PEER_CLOSED),
weak_ptr_factory_(this) {
if (dispatcher == nullptr) {
dispatcher = async_get_default_dispatcher();
}
fidl::WireClient<fuchsia_io::Directory> client(std::move(dir), dispatcher);
client
->Watch(fio::wire::WatchMask::kAdded | fio::wire::WatchMask::kExisting |
fio::wire::WatchMask::kIdle,
0, std::move(dir_watcher.server))
.Then([client = std::move(client), weak = weak_ptr_factory_.GetWeakPtr(), dispatcher](
const fidl::WireUnownedResult<fuchsia_io::Directory::Watch>& result) mutable {
if (!result.ok()) {
if (!result.is_dispatcher_shutdown()) {
FX_PLOGS(ERROR, result.status()) << "Failed to create device watcher";
}
return;
}
const fidl::WireResponse response = result.value();
if (response.s != ZX_OK) {
FX_PLOGS(ERROR, response.s) << "Failed to create device watcher";
return;
}
fit::result dir = client.UnbindMaybeGetEndpoint();
if (dir.is_error()) {
FX_LOGS(ERROR) << "Failed to unbind directory client "
<< dir.error_value().FormatDescription();
return;
}
DeviceWatcher* self = weak.get();
if (self == nullptr) {
return;
}
self->dir_ = std::move(dir.value());
zx_status_t status = self->wait_.Begin(dispatcher);
FX_DCHECK(status == ZX_OK) << zx_status_get_string(status);
});
}
std::unique_ptr<DeviceWatcher> DeviceWatcher::Create(const std::string& directory_path,
ExistsCallback exists_callback,
async_dispatcher_t* dispatcher) {
return CreateWithIdleCallback(
directory_path, std::move(exists_callback), [] {}, dispatcher);
}
std::unique_ptr<DeviceWatcher> DeviceWatcher::CreateWithIdleCallback(
const std::string& directory_path, ExistsCallback exists_callback, IdleCallback idle_callback,
async_dispatcher_t* dispatcher) {
zx::result dir = component::OpenServiceRoot(directory_path);
if (dir.is_error()) {
FX_PLOGS(ERROR, dir.error_value()) << "Failed to open " << directory_path;
return nullptr;
}
return CreateWithIdleCallback(std::move(dir.value()), std::move(exists_callback),
std::move(idle_callback), dispatcher);
}
std::unique_ptr<DeviceWatcher> DeviceWatcher::CreateWithIdleCallback(
fidl::ClientEnd<fuchsia_io::Directory> dir, ExistsCallback exists_callback,
IdleCallback idle_callback, async_dispatcher_t* dispatcher) {
zx::result endpoints = fidl::CreateEndpoints<fio::DirectoryWatcher>();
if (endpoints.is_error()) {
return nullptr;
}
return std::unique_ptr<DeviceWatcher>(
new DeviceWatcher(dispatcher, std::move(dir), std::move(endpoints.value()),
std::move(exists_callback), std::move(idle_callback)));
}
void DeviceWatcher::Handler(async_dispatcher_t* dispatcher, async::WaitBase* wait,
zx_status_t status, const zx_packet_signal* signal) {
if (status != ZX_OK)
return;
if (signal->observed & ZX_CHANNEL_READABLE) {
uint32_t size;
uint8_t buf[fio::wire::kMaxBuf];
zx_status_t status =
dir_watcher_.channel().read(0, buf, nullptr, sizeof(buf), 0, &size, nullptr);
FX_CHECK(status == ZX_OK) << zx_status_get_string(status);
auto weak = weak_ptr_factory_.GetWeakPtr();
uint8_t* msg = buf;
while (size >= 2) {
fio::wire::WatchEvent event = static_cast<fio::wire::WatchEvent>(*msg++);
unsigned namelen = *msg++;
if (size < (namelen + 2u)) {
break;
}
if ((event == fio::wire::WatchEvent::kAdded) || (event == fio::wire::WatchEvent::kExisting)) {
std::string filename(reinterpret_cast<char*>(msg), namelen);
// "." is not a device, so ignore it.
if (filename != ".") {
exists_callback_(dir_, filename);
}
} else if (event == fio::wire::WatchEvent::kIdle) {
idle_callback_();
// Only call the idle callback once. In case there is some captured
// context, remove the function, or rather set it to an empty function,
// in case we try to call it again.
idle_callback_ = [] {};
}
// Note: Callback may have destroyed the DeviceWatcher before returning.
if (!weak) {
return;
}
msg += namelen;
size -= namelen + 2;
}
wait->Begin(dispatcher); // ignore errors
return;
}
if (signal->observed & ZX_CHANNEL_PEER_CLOSED) {
// TODO(jeffbrown): Should we tell someone about this?
dir_watcher_.reset();
return;
}
FX_CHECK(false);
}
} // namespace fsl