| // Copyright 2017 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/storage/lib/vfs/cpp/watcher.h" |
| |
| #include <lib/async/cpp/wait.h> |
| #include <lib/fdio/limits.h> |
| #include <lib/fit/defer.h> |
| #include <string.h> |
| #include <sys/stat.h> |
| |
| #include <memory> |
| #include <string_view> |
| #include <utility> |
| |
| #include <fbl/alloc_checker.h> |
| |
| #include "src/storage/lib/vfs/cpp/fuchsia_vfs.h" |
| #include "src/storage/lib/vfs/cpp/vnode.h" |
| |
| namespace fio = fuchsia_io; |
| |
| namespace fs { |
| |
| // A simple structure which holds a channel to a watching client, as well as a mask of signals |
| // they are interested in hearing about. |
| struct WatcherContainer::VnodeWatcher |
| : public fbl::DoublyLinkedListable<VnodeWatcher*, fbl::NodeOptions::AllowRemoveFromContainer> { |
| VnodeWatcher(fidl::ServerEnd<fuchsia_io::DirectoryWatcher> server_end, |
| fuchsia_io::wire::WatchMask mask) |
| : server_end(std::move(server_end)), |
| mask(mask - (fio::wire::WatchMask::kExisting | fio::wire::WatchMask::kIdle)), |
| waiter(this->server_end.channel().get(), ZX_CHANNEL_PEER_CLOSED | ZX_USER_SIGNAL_0) {} |
| |
| fidl::ServerEnd<fuchsia_io::DirectoryWatcher> server_end; |
| fuchsia_io::WatchMask mask; |
| async::WaitOnce waiter; |
| }; |
| |
| WatcherContainer::WatcherContainer() = default; |
| WatcherContainer::~WatcherContainer() { |
| std::unique_lock guard(lock_); |
| for (VnodeWatcher& watcher : watch_list_) { |
| watcher.server_end.channel().signal(0, ZX_USER_SIGNAL_0); |
| } |
| watch_list_.clear(); |
| } |
| |
| std::shared_mutex WatcherContainer::lock_; |
| |
| namespace { |
| |
| // Watch event messages are sent via the provided channel and take the form: |
| // { uint8_t event; uint8_t namelen; uint8_t name[namelen]; } |
| // Multiple events may arrive in one message, one after another. |
| // Names do not include a terminating null. |
| using vfs_watch_msg_t = struct { |
| uint8_t event; |
| uint8_t len; |
| char name[]; |
| }; |
| |
| } // namespace |
| |
| // Transmission buffer for sending directory watcher notifications to clients. Allows enqueueing |
| // multiple messages in a buffer before sending an IPC message to a client. |
| class WatchBuffer { |
| public: |
| DISALLOW_COPY_ASSIGN_AND_MOVE(WatchBuffer); |
| WatchBuffer() = default; |
| |
| zx_status_t AddMsg(const fidl::ServerEnd<fuchsia_io::DirectoryWatcher>& server_end, |
| fio::wire::WatchEvent event, std::string_view name) { |
| size_t slen = name.length(); |
| size_t mlen = sizeof(vfs_watch_msg_t) + slen; |
| if (mlen + watch_buf_size_ > sizeof(watch_buf_)) { |
| // This message won't fit in the watch_buf; transmit first. |
| zx_status_t status = Send(server_end); |
| if (status != ZX_OK) { |
| return status; |
| } |
| } |
| vfs_watch_msg_t& vmsg = *reinterpret_cast<vfs_watch_msg_t*>(&watch_buf_[watch_buf_size_]); |
| vmsg.event = static_cast<uint8_t>(event); |
| vmsg.len = static_cast<uint8_t>(slen); |
| memcpy(vmsg.name, name.data(), slen); |
| watch_buf_size_ += mlen; |
| return ZX_OK; |
| } |
| zx_status_t Send(const fidl::ServerEnd<fuchsia_io::DirectoryWatcher>& server_end) { |
| if (watch_buf_size_ > 0) { |
| // Only write if we have something to write |
| zx_status_t status = server_end.channel().write( |
| 0, watch_buf_, static_cast<uint32_t>(watch_buf_size_), nullptr, 0); |
| watch_buf_size_ = 0; |
| if (status != ZX_OK) { |
| return status; |
| } |
| } |
| return ZX_OK; |
| } |
| |
| private: |
| size_t watch_buf_size_ = 0; |
| char watch_buf_[fio::wire::kMaxBuf]{}; |
| }; |
| |
| zx_status_t WatcherContainer::WatchDir(FuchsiaVfs* vfs, Vnode* vn, fio::wire::WatchMask mask, |
| uint32_t options, |
| fidl::ServerEnd<fuchsia_io::DirectoryWatcher> server_end) { |
| if (!mask) { |
| // No events to watch |
| return ZX_ERR_INVALID_ARGS; |
| } |
| |
| fbl::AllocChecker ac; |
| std::unique_ptr<VnodeWatcher> watcher(new (&ac) VnodeWatcher(std::move(server_end), mask)); |
| if (!ac.check()) { |
| return ZX_ERR_NO_MEMORY; |
| } |
| |
| if (mask & fio::wire::WatchMask::kExisting) { |
| VdirCookie dircookie; |
| memset(&dircookie, 0, sizeof(dircookie)); |
| char readdir_buf[FDIO_CHUNK_SIZE]; |
| WatchBuffer wb; |
| { |
| // Send "fio::wire::WatchEvent::kExisting" for all entries in readdir. |
| while (true) { |
| size_t actual; |
| zx_status_t status = |
| vfs->Readdir(vn, &dircookie, readdir_buf, sizeof(readdir_buf), &actual); |
| if (status != ZX_OK || actual == 0) { |
| break; |
| } |
| char* ptr = readdir_buf; |
| // TODO(b/293947862): Remove use of deprecated `vdirent_t` when transitioning ReadDir to Enumerate |
| // as part of io2 migration. |
| #pragma clang diagnostic push |
| #pragma clang diagnostic ignored "-Wdeprecated-declarations" |
| while (actual >= sizeof(vdirent_t)) { |
| auto dirent = reinterpret_cast<vdirent_t*>(ptr); |
| if (dirent->name[0]) { |
| wb.AddMsg(watcher->server_end, fio::wire::WatchEvent::kExisting, |
| std::string_view(dirent->name, dirent->size)); |
| } |
| size_t entry_len = dirent->size + sizeof(vdirent_t); |
| ZX_ASSERT(entry_len <= actual); // Prevent underflow |
| actual -= entry_len; |
| ptr += entry_len; |
| } |
| #pragma clang diagnostic pop |
| } |
| } |
| |
| // Send fio::wire::WatchEvent::kIdle to signify that readdir has completed. |
| if (mask & fio::wire::WatchMask::kIdle) { |
| wb.AddMsg(watcher->server_end, fio::wire::WatchEvent::kIdle, ""); |
| } |
| |
| wb.Send(watcher->server_end); |
| } |
| |
| VnodeWatcher* watcher_ptr = watcher.get(); |
| { |
| std::unique_lock guard(lock_); |
| watch_list_.push_back(watcher_ptr); |
| } |
| |
| auto cleanup = fit::defer([watcher = std::move(watcher)] { |
| std::unique_lock guard(lock_); |
| if (watcher->InContainer()) { |
| watcher->RemoveFromContainer(); |
| } |
| }); |
| |
| // Start watching for signals on the channel. This takes ownership of the cleanup callback. |
| return watcher_ptr->waiter.Begin( |
| vfs->dispatcher(), [cleanup = std::move(cleanup)](async_dispatcher_t*, async::WaitOnce*, |
| zx_status_t, const zx_packet_signal_t*) {}); |
| } |
| |
| void WatcherContainer::Notify(std::string_view name, fio::wire::WatchEvent event) { |
| if (name.length() > fio::wire::kMaxFilename) { |
| return; |
| } |
| |
| std::shared_lock guard(lock_); |
| |
| if (watch_list_.is_empty()) { |
| return; |
| } |
| |
| uint8_t msg[sizeof(vfs_watch_msg_t) + fio::wire::kMaxFilename]; |
| size_t msg_length = sizeof(vfs_watch_msg_t) + name.length(); |
| vfs_watch_msg_t* vmsg = reinterpret_cast<vfs_watch_msg_t*>(msg); |
| vmsg->event = static_cast<uint8_t>(event); |
| vmsg->len = static_cast<uint8_t>(name.length()); |
| memcpy(vmsg->name, name.data(), name.length()); |
| |
| for (VnodeWatcher& watcher : watch_list_) { |
| if (!(watcher.mask & fio::wire::WatchMask(1 << static_cast<uint8_t>(event)))) { |
| continue; |
| } |
| |
| zx_status_t status = |
| watcher.server_end.channel().write(0, msg, static_cast<uint32_t>(msg_length), nullptr, 0); |
| if (status < 0) { |
| // Signal so the watcher can be cleaned up. |
| watcher.server_end.channel().signal(0, ZX_USER_SIGNAL_0); |
| // Clear the mask so that we don't try and send to this watcher again. |
| watcher.mask = fio::wire::WatchMask(0); |
| } |
| } |
| } |
| |
| } // namespace fs |