| // 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 "block-watcher.h" |
| |
| #include <fcntl.h> |
| #include <fidl/fuchsia.device/cpp/wire.h> |
| #include <fidl/fuchsia.io/cpp/wire.h> |
| #include <fuchsia/device/c/fidl.h> |
| #include <fuchsia/hardware/block/c/fidl.h> |
| #include <fuchsia/hardware/block/partition/c/fidl.h> |
| #include <inttypes.h> |
| #include <lib/fdio/cpp/caller.h> |
| #include <lib/fdio/directory.h> |
| #include <lib/fdio/fd.h> |
| #include <lib/fdio/fdio.h> |
| #include <lib/fdio/unsafe.h> |
| #include <lib/fdio/watcher.h> |
| #include <lib/fidl-async/cpp/bind.h> |
| #include <lib/fzl/time.h> |
| #include <lib/syslog/cpp/macros.h> |
| #include <lib/zx/channel.h> |
| #include <lib/zx/process.h> |
| #include <lib/zx/time.h> |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <string.h> |
| #include <zircon/device/block.h> |
| #include <zircon/errors.h> |
| #include <zircon/processargs.h> |
| #include <zircon/status.h> |
| #include <zircon/syscalls.h> |
| #include <zircon/types.h> |
| |
| #include <condition_variable> |
| #include <fstream> |
| #include <mutex> |
| #include <string_view> |
| #include <utility> |
| |
| #include <fbl/algorithm.h> |
| #include <fbl/string.h> |
| #include <fbl/string_buffer.h> |
| #include <fbl/unique_fd.h> |
| #include <gpt/gpt.h> |
| |
| #include "src/lib/storage/fs_management/cpp/mount.h" |
| #include "src/security/zxcrypt/client.h" |
| #include "src/storage/fshost/block-device-manager.h" |
| #include "src/storage/fshost/block-device.h" |
| #include "src/storage/fshost/fs-manager.h" |
| #include "src/storage/fshost/nand-device.h" |
| #include "src/storage/minfs/minfs.h" |
| |
| namespace fshost { |
| namespace { |
| |
| namespace fio = fuchsia_io; |
| |
| // Signal that is set on the watcher channel we want to stop watching. |
| constexpr zx_signals_t kSignalWatcherPaused = ZX_USER_SIGNAL_0; |
| constexpr zx_signals_t kSignalWatcherShutDown = ZX_USER_SIGNAL_1; |
| |
| } // namespace |
| |
| BlockWatcher::BlockWatcher(FsManager& fshost, const fshost_config::Config* config) |
| : mounter_(fshost, config), device_manager_(config) { |
| zx_status_t status = zx::event::create(0, &pause_event_); |
| if (status != ZX_OK) { |
| FX_LOGS(ERROR) << "failed to create block watcher pause event: " |
| << zx_status_get_string(status); |
| } |
| } |
| |
| void BlockWatcher::Run() { |
| thread_ = std::thread([this] { Thread(); }); |
| } |
| |
| void BlockWatcher::Thread() { |
| auto watchers = Watcher::CreateWatchers(); |
| if (watchers.empty()) { |
| FX_LOGS(ERROR) << "failed to start any watchers"; |
| return; |
| } |
| auto cleanup = fit::defer([this] { |
| pause_event_.reset(); |
| pause_condition_.notify_all(); |
| }); |
| |
| while (true) { |
| for (auto& watcher : watchers) { |
| watcher.ReinitWatcher(); |
| } |
| { |
| std::scoped_lock guard(lock_); |
| if (is_paused_) { |
| FX_LOGS(INFO) << "block watcher resumed"; |
| is_paused_ = false; |
| pause_condition_.notify_all(); |
| } |
| } |
| |
| // +1 for the NUL terminator at the end of the last name. |
| uint8_t buf[fio::wire::kMaxBuf + 1]; |
| cpp20::span buf_span(buf, std::size(buf) - 1); |
| |
| zx_signals_t signals; |
| Watcher* selected = nullptr; |
| while ((signals = WaitForWatchMessages(watchers, buf_span, &selected)) == ZX_CHANNEL_READABLE) { |
| // Add an extra byte, so that ProcessWatchMessages can make C strings in the messages. |
| buf_span = cpp20::span(buf_span.data(), buf_span.size() + 1); |
| buf_span.back() = 0; |
| selected->ProcessWatchMessages( |
| buf_span, [this](Watcher& watcher, int dirfd, fio::wire::WatchEvent event, |
| const char* name) { return Callback(watcher, dirfd, event, name); }); |
| |
| // reset the buffer for the next read. |
| buf_span = cpp20::span(buf, std::size(buf) - 1); |
| } |
| switch (signals) { |
| case kSignalWatcherPaused: { |
| std::scoped_lock guard(lock_); |
| is_paused_ = true; |
| FX_LOGS(INFO) << "block watcher paused"; |
| pause_condition_.notify_all(); |
| // We were told to pause. Wait until we're resumed before re-starting the watch. |
| while (pause_count_ > 0) { |
| pause_condition_.wait(lock_); |
| } |
| break; |
| } |
| case kSignalWatcherShutDown: |
| return; |
| default: |
| FX_LOGS(ERROR) << "watch failed with signal " << signals; |
| return; |
| } |
| } |
| } |
| |
| void BlockWatcher::ShutDown() { |
| if (thread_.joinable()) { |
| { |
| std::scoped_lock guard(lock_); |
| pause_count_ = -1; |
| } |
| pause_condition_.notify_all(); |
| pause_event_.signal(0, kSignalWatcherShutDown); |
| thread_.join(); |
| } |
| } |
| |
| // Increment the pause count for the block watcher. |
| // This function will not return until the block watcher |
| // is no longer running. |
| // The block watcher will not receive any new device events while paused. |
| zx_status_t BlockWatcher::Pause() { |
| auto guard = std::lock_guard(lock_); |
| |
| // Wait to resume before continuing. |
| while (pause_count_ == 0 && is_paused_ && pause_event_) |
| pause_condition_.wait(lock_); |
| |
| if (pause_count_ == std::numeric_limits<int>::max() || pause_count_ < 0) { |
| return ZX_ERR_UNAVAILABLE; |
| } |
| if (!pause_event_) { |
| // Refuse to pause -- the watcher won't actually stop. |
| return ZX_ERR_BAD_STATE; |
| } |
| if (pause_count_ == 0) { |
| // Tell the watcher to pause. |
| zx_status_t status = pause_event_.signal(0, kSignalWatcherPaused); |
| if (status != ZX_OK) { |
| FX_LOGS(ERROR) << "failed to set paused signal: " << zx_status_get_string(status); |
| return status; |
| } |
| |
| pause_count_++; |
| } else { |
| pause_count_++; |
| } |
| |
| while (!is_paused_) { |
| if (!pause_event_) |
| return ZX_ERR_BAD_STATE; |
| pause_condition_.wait(lock_); |
| } |
| |
| return ZX_OK; |
| } |
| |
| zx_status_t BlockWatcher::Resume() { |
| auto guard = std::lock_guard(lock_); |
| |
| // Wait to pause before continuing. |
| while (pause_count_ > 0 && !is_paused_ && pause_event_) |
| pause_condition_.wait(lock_); |
| |
| if (pause_count_ <= 0 || !pause_event_) { |
| return ZX_ERR_BAD_STATE; |
| } |
| |
| pause_count_--; |
| if (pause_count_ == 0) { |
| // Clear the pause signal. |
| pause_event_.signal(kSignalWatcherPaused, 0); |
| pause_condition_.notify_all(); |
| } |
| |
| // If this resume would cause the watcher to resume, wait until the watcher has actually resumed. |
| // This helps avoid races in tests where they immediately create devices after resuming and |
| // expecting fshost to have noticed. |
| while (pause_count_ == 0 && is_paused_) { |
| if (!pause_event_) |
| return ZX_ERR_BAD_STATE; |
| pause_condition_.wait(lock_); |
| } |
| return ZX_OK; |
| } |
| |
| bool BlockWatcher::Callback(Watcher& watcher, int dirfd, fio::wire::WatchEvent event, |
| const char* name) { |
| if (event != fio::wire::WatchEvent::kAdded && event != fio::wire::WatchEvent::kExisting && |
| event != fio::wire::WatchEvent::kIdle) { |
| return false; |
| } |
| |
| // Lock the block watcher, so any pause operations wait until after we're done. |
| // Note that WATCH_EVENT_EXISTING is only received on the first run of the watcher, |
| // so we don't need to worry about ignoring it on subsequent runs. |
| std::lock_guard guard(lock_); |
| if (event == fio::wire::WatchEvent::kIdle && pause_count_ > 0) { |
| return true; |
| } |
| // If we lost the race and the watcher was paused sometime between |
| // zx_object_wait_many returning and us acquiring the lock, bail out. |
| if (pause_count_ != 0) { |
| return false; |
| } |
| |
| fbl::unique_fd device_fd(openat(dirfd, name, O_RDWR)); |
| if (!device_fd) { |
| return false; |
| } |
| |
| zx_status_t status = watcher.AddDevice(device_manager_, &mounter_, std::move(device_fd)); |
| if (status == ZX_ERR_NOT_SUPPORTED) { |
| // The femu tests watch for the following message and will need updating if this changes. |
| FX_LOGS(INFO) << "" << watcher.path() << "/" << name << " ignored (not supported)"; |
| } else if (status != ZX_OK) { |
| // There's not much we can do if this fails - we want to keep seeing future block device |
| // events, so we just log loudly that we failed to do something. |
| FX_LOGS(ERROR) << "" << watcher.path() << "/" << name |
| << " failed: " << zx_status_get_string(status); |
| } |
| |
| return false; |
| } |
| |
| zx_signals_t BlockWatcher::WaitForWatchMessages(cpp20::span<Watcher> watchers, |
| cpp20::span<uint8_t>& buf, Watcher** selected) { |
| *selected = nullptr; |
| zx_status_t status; |
| std::vector<zx_wait_item_t> wait_items; |
| bool can_pause = true; |
| for (auto& watcher : watchers) { |
| if (!watcher.ignore_existing()) { |
| can_pause = false; |
| } |
| wait_items.emplace_back(zx_wait_item_t{ |
| .handle = watcher.borrow_watcher().channel()->get(), |
| .waitfor = ZX_CHANNEL_READABLE | ZX_CHANNEL_PEER_CLOSED, |
| .pending = 0, |
| }); |
| } |
| |
| // We only want to check for kSignalWatcherPaused and kSignalWatcherShutDown if all watchers |
| // are ignoring existing items. |
| if (can_pause) { |
| wait_items.emplace_back(zx_wait_item_t{ |
| .handle = pause_event_.get(), |
| .waitfor = kSignalWatcherPaused | kSignalWatcherShutDown, |
| .pending = 0, |
| }); |
| } |
| |
| if ((status = zx_object_wait_many(wait_items.data(), wait_items.size(), |
| zx::time::infinite().get())) != ZX_OK) { |
| FX_LOGS(ERROR) << "failed to wait_many: " << zx_status_get_string(status); |
| return 0; |
| } |
| |
| if (can_pause) { |
| if (wait_items.back().pending & kSignalWatcherShutDown) { |
| return kSignalWatcherShutDown; |
| } |
| if (wait_items.back().pending & kSignalWatcherPaused) { |
| return kSignalWatcherPaused; |
| } |
| } |
| |
| for (size_t i = 0; i < watchers.size(); ++i) { |
| if (wait_items[i].pending & ZX_CHANNEL_PEER_CLOSED) { |
| return ZX_CHANNEL_PEER_CLOSED; |
| } |
| |
| if (wait_items[i].pending & ZX_CHANNEL_READABLE) { |
| uint32_t read_len; |
| status = watchers[i].borrow_watcher().channel()->read( |
| 0, buf.begin(), nullptr, static_cast<uint32_t>(buf.size()), 0, &read_len, nullptr); |
| if (status != ZX_OK) { |
| FX_LOGS(ERROR) << "failed to read from channel:" << zx_status_get_string(status); |
| return 0; |
| } |
| *selected = &watchers[i]; |
| buf = buf.subspan(0, read_len); |
| return ZX_CHANNEL_READABLE; |
| } |
| } |
| |
| ZX_ASSERT_MSG(false, "watcher got event but nothing is pending"); |
| } |
| |
| fbl::RefPtr<fs::Service> BlockWatcherServer::Create(async_dispatcher* dispatcher, |
| BlockWatcher& watcher) { |
| return fbl::MakeRefCounted<fs::Service>( |
| [dispatcher, &watcher](fidl::ServerEnd<fuchsia_fshost::BlockWatcher> chan) { |
| zx_status_t status = fidl::BindSingleInFlightOnly( |
| dispatcher, std::move(chan), |
| std::unique_ptr<BlockWatcherServer>(new BlockWatcherServer(watcher))); |
| if (status != ZX_OK) { |
| FX_LOGS(ERROR) << "failed to bind admin service:" << zx_status_get_string(status); |
| return status; |
| } |
| return ZX_OK; |
| }); |
| } |
| |
| void BlockWatcherServer::Pause(PauseRequestView request, PauseCompleter::Sync& completer) { |
| completer.Reply(watcher_.Pause()); |
| } |
| |
| void BlockWatcherServer::Resume(ResumeRequestView request, ResumeCompleter::Sync& completer) { |
| completer.Reply(watcher_.Resume()); |
| } |
| |
| } // namespace fshost |