// 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 #include #include #include #ifdef __Fuchsia__ #include #include #include #endif #include #include #include #include #include namespace fs { WatcherContainer::WatcherContainer() = default; WatcherContainer::~WatcherContainer() = default; WatcherContainer::VnodeWatcher::VnodeWatcher(zx::channel h, uint32_t mask) : h(fbl::move(h)), mask(mask & ~(fuchsia_io_WATCH_MASK_EXISTING | fuchsia_io_WATCH_MASK_IDLE)) {} WatcherContainer::VnodeWatcher::~VnodeWatcher() {} // 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 zx::channel& c, unsigned event, fbl::StringPiece name); zx_status_t Send(const zx::channel& c); private: size_t watch_buf_size_ = 0; char watch_buf_[fuchsia_io_MAX_BUF]{}; }; zx_status_t WatchBuffer::AddMsg(const zx::channel& c, unsigned event, fbl::StringPiece 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(c); if (status != ZX_OK) { return status; } } vfs_watch_msg_t* vmsg = reinterpret_cast((uintptr_t)watch_buf_ + watch_buf_size_); vmsg->event = static_cast(event); vmsg->len = static_cast(slen); memcpy(vmsg->name, name.data(), slen); watch_buf_size_ += mlen; return ZX_OK; } zx_status_t WatchBuffer::Send(const zx::channel& c) { if (watch_buf_size_ > 0) { // Only write if we have something to write zx_status_t status = c.write(0, watch_buf_, static_cast(watch_buf_size_), nullptr, 0); watch_buf_size_ = 0; if (status != ZX_OK) { return status; } } return ZX_OK; } zx_status_t WatcherContainer::WatchDir(Vfs* vfs, Vnode* vn, uint32_t mask, uint32_t options, zx::channel channel) { if ((mask & fuchsia_io_WATCH_MASK_ALL) == 0) { // No events to watch return ZX_ERR_INVALID_ARGS; } fbl::AllocChecker ac; fbl::unique_ptr watcher(new (&ac) VnodeWatcher(fbl::move(channel), mask)); if (!ac.check()) { return ZX_ERR_NO_MEMORY; } if (mask & fuchsia_io_WATCH_MASK_EXISTING) { vdircookie_t dircookie; memset(&dircookie, 0, sizeof(dircookie)); char readdir_buf[FDIO_CHUNK_SIZE]; WatchBuffer wb; { // Send "fuchsia_io_WATCH_EVENT_EXISTING" 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; } void* ptr = readdir_buf; while (actual >= sizeof(vdirent_t)) { auto dirent = reinterpret_cast(ptr); if (dirent->name[0]) { wb.AddMsg(watcher->h, fuchsia_io_WATCH_EVENT_EXISTING, fbl::StringPiece(dirent->name, dirent->size)); } size_t entry_len = dirent->size + sizeof(vdirent_t); ZX_ASSERT(entry_len <= actual); // Prevent underflow actual -= entry_len; ptr = reinterpret_cast( static_cast(entry_len) + reinterpret_cast(ptr)); } } } // Send fuchsia_io_WATCH_EVENT_IDLE to signify that readdir has completed if (mask & fuchsia_io_WATCH_MASK_IDLE) { wb.AddMsg(watcher->h, fuchsia_io_WATCH_EVENT_IDLE, ""); } wb.Send(watcher->h); } fbl::AutoLock lock(&lock_); watch_list_.push_back(fbl::move(watcher)); return ZX_OK; } void WatcherContainer::Notify(fbl::StringPiece name, unsigned event) { if (name.length() > fuchsia_io_MAX_FILENAME) { return; } fbl::AutoLock lock(&lock_); if (watch_list_.is_empty()) { return; } uint8_t msg[sizeof(vfs_watch_msg_t) + name.length()]; vfs_watch_msg_t* vmsg = reinterpret_cast(msg); vmsg->event = static_cast(event); vmsg->len = static_cast(name.length()); memcpy(vmsg->name, name.data(), name.length()); for (auto it = watch_list_.begin(); it != watch_list_.end();) { if (!(it->mask & (1 << event))) { ++it; continue; } zx_status_t status = it->h.write(0, msg, static_cast(sizeof(msg)), nullptr, 0); if (status < 0) { // Lazily remove watchers when their handles cannot accept incoming // watch messages. auto to_remove = it; ++it; watch_list_.erase(to_remove); } else { ++it; } } } } // namespace fs