blob: 3d61f2ca1de079f79abd9d819b241e3311a09aff [file] [log] [blame] [edit]
// Copyright 2021 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 "device-watcher.h"
#include <dirent.h>
#include <fcntl.h>
#include <lib/fdio/directory.h>
#include <lib/fdio/unsafe.h>
#include <lib/fdio/watcher.h>
#include <lib/fit/defer.h>
#include <lib/zx/clock.h>
#include <string.h>
namespace device_watcher {
namespace {
constexpr char kDevPath[] = "/dev/";
}
__EXPORT
zx_status_t DirWatcher::Create(int dir_fd, std::unique_ptr<DirWatcher>* out_dir_watcher) {
zx::result endpoints = fidl::CreateEndpoints<fuchsia_io::DirectoryWatcher>();
if (endpoints.is_error()) {
return endpoints.status_value();
}
fdio_t* io = fdio_unsafe_fd_to_io(dir_fd);
zx::unowned_channel channel{fdio_unsafe_borrow_channel(io)};
// Make a one-off call to fuchsia.io.Directory.Watch to open a channel from
// which watch events can be read.
auto result = fidl::WireCall(fidl::UnownedClientEnd<fuchsia_io::Directory>(channel))
->Watch(fuchsia_io::wire::WatchMask::kRemoved, 0, std::move(endpoints->server));
fdio_unsafe_release(io);
if (!result.ok()) {
return result.status();
}
*out_dir_watcher = std::make_unique<DirWatcher>(std::move(endpoints->client));
return ZX_OK;
}
__EXPORT
zx_status_t DirWatcher::WaitForRemoval(std::string_view filename, zx::duration timeout) {
auto deadline = zx::deadline_after(timeout);
// Loop until we see the removal event, or wait_one fails due to timeout.
for (;;) {
zx_signals_t observed;
zx_status_t status = client_.channel().wait_one(ZX_CHANNEL_READABLE, deadline, &observed);
if (status != ZX_OK) {
return status;
}
if (!(observed & ZX_CHANNEL_READABLE)) {
return ZX_ERR_IO;
}
// Messages are of the form:
// uint8_t event
// uint8_t len
// char* name
uint8_t buf[fuchsia_io::wire::kMaxBuf];
uint32_t actual_len;
status = client_.channel().read(0, buf, nullptr, sizeof(buf), 0, &actual_len, nullptr);
if (status != ZX_OK) {
return status;
}
if (static_cast<fuchsia_io::wire::WatchEvent>(buf[0]) !=
fuchsia_io::wire::WatchEvent::kRemoved) {
continue;
}
if (filename.length() == 0) {
// Waiting on any file.
return ZX_OK;
}
if ((buf[1] == filename.length()) &&
(memcmp(buf + 2, filename.data(), filename.length()) == 0)) {
return ZX_OK;
}
}
return ZX_ERR_NOT_FOUND;
}
__EXPORT
zx_status_t IterateDirectory(const int fd, FileCallback callback) {
struct dirent* entry;
DIR* dir = fdopendir(fd);
if (dir == nullptr) {
return ZX_ERR_IO;
}
fdio_t* io = fdio_unsafe_fd_to_io(fd);
zx::unowned_channel dir_channel{fdio_unsafe_borrow_channel(io)};
zx_status_t status = ZX_OK;
while ((entry = readdir(dir)) != nullptr) {
std::string filename(entry->d_name);
if (filename == "." || filename == "..") {
continue;
}
auto endpoints = fidl::CreateEndpoints<fuchsia_io::Node>();
if (endpoints.is_error()) {
status = endpoints.status_value();
goto finish;
}
// Open a channel to the file.
status = fdio_open_at(dir_channel->get(), entry->d_name, 0,
endpoints->server.TakeChannel().release());
if (status != ZX_OK) {
goto finish;
}
// Invoke the user-provided callback.
status = callback(filename, endpoints->client.TakeChannel());
if (status != ZX_OK) {
goto finish;
}
}
finish:
fdio_unsafe_release(io);
closedir(dir);
return status;
}
__EXPORT
zx::result<zx::channel> WaitForFile(const int dir_fd, const char* file) {
auto watch_func = [](int dirfd, int event, const char* fn, void* cookie) -> zx_status_t {
const std::string_view& file = *static_cast<std::string_view*>(cookie);
if (event != WATCH_EVENT_ADD_FILE) {
return ZX_OK;
}
if (std::string_view{fn} == file) {
return ZX_ERR_STOP;
}
return ZX_OK;
};
std::string_view file_view{file};
zx_status_t status = fdio_watch_directory(dir_fd, watch_func, ZX_TIME_INFINITE, &file_view);
if (status != ZX_ERR_STOP) {
return zx::error(status);
}
// Open a channel to the file.
zx::channel chan;
status = fdio_get_service_handle(openat(dir_fd, file, O_RDWR), chan.reset_and_get_address());
if (status != ZX_OK) {
return zx::error(status);
}
return zx::ok(std::move(chan));
}
namespace {
// This variant of WaitForFile opens the file specified relative to the rootdir,
// using the full_path. This is a workaround to deal with the fact that devhosts
// do not implement open_at.
zx::result<zx::channel> WaitForFile2(const int rootdir_fd, const int dir_fd, const char* full_path,
const char* file, bool last, bool readonly) {
auto watch_func = [](int dirfd, int event, const char* fn, void* cookie) -> zx_status_t {
const std::string_view& file = *static_cast<std::string_view*>(cookie);
if (event != WATCH_EVENT_ADD_FILE) {
return ZX_OK;
}
if (std::string_view{fn} == file) {
return ZX_ERR_STOP;
}
return ZX_OK;
};
std::string_view file_view{file};
zx_status_t status = fdio_watch_directory(dir_fd, watch_func, ZX_TIME_INFINITE, &file_view);
if (status != ZX_ERR_STOP) {
return zx::error(status);
}
int flags = O_RDWR;
if (readonly) {
flags = O_RDONLY;
}
if (!last) {
flags = O_RDONLY | O_DIRECTORY;
}
// Open a channel to the file.
zx::channel chan;
status =
fdio_get_service_handle(openat(rootdir_fd, full_path, flags), chan.reset_and_get_address());
if (status != ZX_OK) {
return zx::error(status);
}
return zx::ok(std::move(chan));
}
// Version of RecursiveWaitForFile that can mutate its path
zx::result<zx::channel> RecursiveWaitForFileHelper(const int rootdir_fd, const int dir_fd,
const char* full_path, char* path,
bool readonly) {
char* first_slash = strchr(path, '/');
if (first_slash == nullptr) {
// If there's no first slash, then we're just waiting for the file
// itself to appear.
return WaitForFile2(rootdir_fd, dir_fd, full_path, path, true, readonly);
}
*first_slash = 0;
auto result = WaitForFile2(rootdir_fd, dir_fd, full_path, path, false, readonly);
if (!result.is_ok()) {
return result.take_error();
}
int next_fd;
zx_status_t status = fdio_fd_create(result.value().release(), &next_fd);
if (status != ZX_OK) {
return zx::error(status);
}
auto close_action = fit::defer([next_fd]() { close(next_fd); });
*first_slash = '/';
return RecursiveWaitForFileHelper(rootdir_fd, next_fd, full_path, first_slash + 1, readonly);
}
} // namespace
__EXPORT
zx::result<zx::channel> RecursiveWaitForFile(const int dir_fd, const char* path) {
char path_copy[PATH_MAX];
if (strlen(path) >= sizeof(path_copy)) {
return zx::error(ZX_ERR_INVALID_ARGS);
}
strcpy(path_copy, path);
return RecursiveWaitForFileHelper(dir_fd, dir_fd, path_copy, path_copy, false);
}
__EXPORT
zx::result<zx::channel> RecursiveWaitForFile(const char* path) {
if (strncmp(kDevPath, path, strlen(kDevPath) - 1) != 0) {
return zx::error(ZX_ERR_NOT_SUPPORTED);
}
const int dev_fd = open(kDevPath, O_RDONLY | O_DIRECTORY);
return RecursiveWaitForFile(dev_fd, path + strlen(kDevPath));
}
__EXPORT
zx::result<zx::channel> RecursiveWaitForFileReadOnly(const char* path) {
if (strncmp(kDevPath, path, strlen(kDevPath) - 1) != 0) {
return zx::error(ZX_ERR_NOT_SUPPORTED);
}
const int dev_fd = open(kDevPath, O_RDONLY | O_DIRECTORY);
return RecursiveWaitForFileReadOnly(dev_fd, path + strlen(kDevPath));
}
__EXPORT
zx::result<zx::channel> RecursiveWaitForFileReadOnly(const int dir_fd, const char* path) {
char path_copy[PATH_MAX];
if (strlen(path) >= sizeof(path_copy)) {
return zx::error(ZX_ERR_INVALID_ARGS);
}
strcpy(path_copy, path);
return RecursiveWaitForFileHelper(dir_fd, dir_fd, path_copy, path_copy, true);
}
} // namespace device_watcher