blob: f2fb54090454406d02dd444001adf2fd0e572456 [file] [log] [blame]
// Copyright 2024 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 <lib/component/incoming/cpp/directory_watcher.h>
#include <lib/fdio/directory.h>
namespace component {
zx_status_t DirectoryWatcher::Begin(fidl::UnownedClientEnd<fuchsia_io::Directory> dir,
DirectoryWatcher::Callback callback,
async_dispatcher_t* dispatcher) {
if (callback_ != nullptr) {
return ZX_ERR_UNAVAILABLE;
}
callback_ = std::move(callback);
auto [client, server] = fidl::Endpoints<fuchsia_io::DirectoryWatcher>::Create();
auto watch_mask = fuchsia_io::wire::WatchMask::kAdded | fuchsia_io::wire::WatchMask::kExisting |
fuchsia_io::wire::WatchMask::kIdle;
const fidl::WireResult<::fuchsia_io::Directory::Watch> result =
fidl::WireCall(dir)->Watch(watch_mask, 0, std::move(server));
if (!result.ok()) {
return result.status();
}
const fidl::WireResponse<::fuchsia_io::Directory::Watch> response = result.value();
if (zx_status_t status = response.s; status != ZX_OK) {
return status;
}
buf_ = std::shared_ptr<std::array<uint8_t, fuchsia_io::wire::kMaxBuf>>(
new std::array<uint8_t, fuchsia_io::wire::kMaxBuf>);
client_end_ = client.TakeChannel();
wait_.set_object(client_end_.get());
wait_.set_trigger(ZX_CHANNEL_READABLE);
return wait_.Begin(dispatcher);
}
zx_status_t DirectoryWatcher::Cancel() { return wait_.Cancel(); }
void DirectoryWatcher::OnWatchedEvent(async_dispatcher_t* dispatcher, async::WaitBase* wait,
zx_status_t status, const zx_packet_signal_t* signal) {
if (status != ZX_OK || !(signal->observed & ZX_CHANNEL_READABLE)) {
return;
}
uint32_t size;
status = client_end_.read(0, buf_->data(), nullptr, fuchsia_io::wire::kMaxBuf, 0, &size, nullptr);
if (status != ZX_OK) {
return;
}
std::weak_ptr<std::array<uint8_t, fuchsia_io::wire::kMaxBuf>> weak_buf = buf_;
uint8_t* msg = buf_->data();
while (size >= 2) {
// Process message structure, as described by fuchsia_io::wire::WatchedEvent.
auto event = static_cast<fuchsia_io::wire::WatchEvent>(*msg++);
uint64_t len = *msg++;
// Restrict the length to the remaining size of the buffer.
if (size < (len + 2u)) {
break;
}
// If the entry is valid, invoke the callback.
if (event == fuchsia_io::wire::WatchEvent::kIdle) { // we don't need to look for a filename
callback_(event, "");
} else {
std::string filename(reinterpret_cast<char*>(msg), len);
// "." is not a device, so ignore it.
if (filename != ".") {
callback_(event, filename);
}
}
// The callback may have destroyed the ServiceWatcher, so make sure we still exist.
if (weak_buf.expired()) {
return;
}
msg += len;
size -= len + 2;
}
wait_.Begin(dispatcher);
}
zx::result<> SyncDirectoryWatcher::Begin() {
auto callback = fit::bind_member<&SyncDirectoryWatcher::OnWatchedEvent>(this);
// Get the actual directory client depending on how this was initialized:
if (path_ && dir_) {
auto result = OpenDirectoryAt(*dir_, *path_);
if (result.is_error()) {
return result.take_error();
}
return zx::make_result(
directory_watcher_.Begin(result.value().borrow(), std::move(callback), loop_.dispatcher()));
}
// full path case:
if (path_) {
auto result = OpenDirectory(*path_);
if (result.is_error()) {
return result.take_error();
}
return zx::make_result(
directory_watcher_.Begin(result.value().borrow(), std::move(callback), loop_.dispatcher()));
}
// just directory case
return zx::make_result(directory_watcher_.Begin(*dir_, std::move(callback), loop_.dispatcher()));
}
zx::result<std::string> SyncDirectoryWatcher::GetNextEntry(bool stop_at_idle, zx::time deadline) {
if (!has_begun_iterating_) {
auto result = Begin();
if (result.is_error()) {
return result.take_error();
}
has_begun_iterating_ = true;
}
// Run the loop to get the file events
// Due to the nature of the fuchsia_io::Watcher protocol, one event for the service watcher may
// correspond to multiple file events. For this reason, we just run the loop until idle and
// then process all the file events that we have received.
zx_status_t run_status;
do {
run_status = loop_.RunUntilIdle();
if (run_status != ZX_OK) { // loop was cancelled or shutdown
return zx::error(run_status);
}
// First get all the entries that were existing/added:
if (!entries_.empty()) {
std::string ret_instance = entries_.front();
entries_.pop_front();
return zx::ok(ret_instance);
}
// Once the queue is emptied, we can let the user know that the idle callback was invoked.
if (stop_at_idle && idle_called_) {
return zx::error(ZX_ERR_STOP);
}
// At this point, we are either in a race for the idle signal, or stop_at_idle == false,
// and we are just waiting for any future file events.
run_status = loop_.Run(deadline, true);
} while (run_status == ZX_OK);
// loop_.Run exited with a timeout or because it was canceled.
return zx::error(run_status);
}
void SyncDirectoryWatcher::OnWatchedEvent(fuchsia_io::wire::WatchEvent event,
const std::string& instance) {
if (event == fuchsia_io::wire::WatchEvent::kIdle) {
idle_called_ = true;
return;
}
if (event == fuchsia_io::wire::WatchEvent::kRemoved || instance.size() < 2) {
return;
}
entries_.push_back(instance);
}
} // namespace component