blob: 5937aff71392f04f1c770fa572fb8528f2ab72a7 [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 "lib/fsl/io/device_watcher.h"
#include <dirent.h>
#include <fcntl.h>
#include <sys/types.h>
#include <utility>
#include <fbl/unique_fd.h>
#include <fuchsia/io/c/fidl.h>
#include <lib/async/default.h>
#include <lib/fdio/io.h>
#include <lib/fzl/fdio.h>
#include <zircon/device/vfs.h>
#include "lib/fxl/logging.h"
namespace fsl {
DeviceWatcher::DeviceWatcher(fxl::UniqueFD dir_fd, zx::channel dir_watch,
ExistsCallback exists_callback,
IdleCallback idle_callback)
: dir_fd_(std::move(dir_fd)),
dir_watch_(std::move(dir_watch)),
exists_callback_(std::move(exists_callback)),
idle_callback_(std::move(idle_callback)),
wait_(this, dir_watch_.get(),
ZX_CHANNEL_READABLE | ZX_CHANNEL_PEER_CLOSED),
weak_ptr_factory_(this) {
auto status = wait_.Begin(async_get_default_dispatcher());
FXL_DCHECK(status == ZX_OK);
}
std::unique_ptr<DeviceWatcher> DeviceWatcher::Create(
std::string directory_path, ExistsCallback exists_callback) {
return CreateWithIdleCallback(directory_path, std::move(exists_callback),
[] {});
}
std::unique_ptr<DeviceWatcher> DeviceWatcher::CreateWithIdleCallback(
std::string directory_path, ExistsCallback exists_callback,
IdleCallback idle_callback) {
// Open the directory.
int open_result = open(directory_path.c_str(), O_DIRECTORY | O_RDONLY);
if (open_result < 0) {
FXL_LOG(ERROR) << "Failed to open " << directory_path
<< ", errno=" << errno;
return nullptr;
}
fbl::unique_fd dir_fd(open_result);
zx::channel client, server;
if (zx::channel::create(0, &client, &server) != ZX_OK) {
return nullptr;
}
fzl::FdioCaller caller{std::move(dir_fd)};
uint32_t mask =
fuchsia_io_WATCH_MASK_ADDED | fuchsia_io_WATCH_MASK_EXISTING | fuchsia_io_WATCH_MASK_IDLE;
zx_status_t status;
zx_status_t io_status = fuchsia_io_DirectoryWatch(caller.borrow_channel(),
mask, 0, server.release(), &status);
if (io_status != ZX_OK || status != ZX_OK) {
FXL_LOG(ERROR) << "Failed to create device watcher for " << directory_path
<< ", status=" << status;
return nullptr;
}
// This weird handshake is necessary because fxl::UniqueFD != fbl::unique_fd.
fxl::UniqueFD dir_fd_alt(caller.release().release());
return std::unique_ptr<DeviceWatcher>(
new DeviceWatcher(std::move(dir_fd_alt), std::move(client),
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[fuchsia_io_MAX_BUF];
zx_status_t status =
dir_watch_.read(0, buf, sizeof(buf), &size, nullptr, 0, nullptr);
FXL_CHECK(status == ZX_OK) << "Failed to read from directory watch channel";
auto weak = weak_ptr_factory_.GetWeakPtr();
uint8_t* msg = buf;
while (size >= 2) {
unsigned event = *msg++;
unsigned namelen = *msg++;
if (size < (namelen + 2u)) {
break;
}
if ((event == fuchsia_io_WATCH_EVENT_ADDED) || (event == fuchsia_io_WATCH_EVENT_EXISTING)) {
exists_callback_(dir_fd_.get(),
std::string(reinterpret_cast<char*>(msg), namelen));
} else if (event == fuchsia_io_WATCH_EVENT_IDLE) {
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_watch_.reset();
return;
}
FXL_CHECK(false);
}
} // namespace fsl