| // 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 <lib/async/default.h> |
| #include <lib/fdio/io.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; |
| } |
| fxl::UniqueFD dir_fd(open_result); // take ownership of fd here |
| |
| // Create the directory watch channel. |
| vfs_watch_dir_t wd; |
| wd.mask = |
| VFS_WATCH_MASK_ADDED | VFS_WATCH_MASK_EXISTING | VFS_WATCH_MASK_IDLE; |
| wd.options = 0; |
| zx_handle_t dir_watch_handle; |
| if (zx_channel_create(0, &wd.channel, &dir_watch_handle) < 0) { |
| return nullptr; |
| } |
| ssize_t ioctl_result = ioctl_vfs_watch_dir(dir_fd.get(), &wd); |
| if (ioctl_result < 0) { |
| zx_handle_close(wd.channel); |
| zx_handle_close(dir_watch_handle); |
| FXL_LOG(ERROR) << "Failed to create device watcher for " << directory_path |
| << ", result=" << ioctl_result; |
| return nullptr; |
| } |
| zx::channel dir_watch(dir_watch_handle); // take ownership of handle here |
| |
| return std::unique_ptr<DeviceWatcher>( |
| new DeviceWatcher(std::move(dir_fd), std::move(dir_watch), |
| 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[VFS_WATCH_MSG_MAX]; |
| 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 == VFS_WATCH_EVT_ADDED) || (event == VFS_WATCH_EVT_EXISTING)) { |
| exists_callback_(dir_fd_.get(), |
| std::string(reinterpret_cast<char*>(msg), namelen)); |
| } else if (event == VFS_WATCH_EVT_IDLE) { |
| idle_callback_(); |
| } |
| // Note: Callback may have destroyed the DeviceWatcher before returning. |
| if (!weak) { |
| return; |
| } |
| msg += namelen; |
| size -= namelen; |
| } |
| 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 |