| // 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 <fuchsia/device/c/fidl.h> |
| #include <fuchsia/hardware/block/c/fidl.h> |
| #include <fuchsia/hardware/block/partition/c/fidl.h> |
| #include <fuchsia/io/llcpp/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/zircon-internal/thread_annotations.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 <utility> |
| |
| #include <fbl/algorithm.h> |
| #include <fbl/auto_call.h> |
| #include <fbl/string.h> |
| #include <fbl/string_buffer.h> |
| #include <fbl/string_piece.h> |
| #include <fbl/unique_fd.h> |
| #include <fs-management/mount.h> |
| #include <gpt/gpt.h> |
| |
| #include "src/security/zxcrypt/fdio-volume.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/pkgfs-launcher.h" |
| #include "src/storage/minfs/minfs.h" |
| |
| namespace devmgr { |
| namespace { |
| |
| namespace fio = ::llcpp::fuchsia::io; |
| |
| constexpr char kPathBlockDeviceRoot[] = "/dev/class/block"; |
| |
| // 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 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() { |
| fbl::unique_fd dirfd(open(kPathBlockDeviceRoot, O_DIRECTORY | O_RDONLY)); |
| if (!dirfd) { |
| FX_LOGS(ERROR) << "failed to open block device dir: " << strerror(errno); |
| return; |
| } |
| fdio_cpp::FdioCaller caller(std::move(dirfd)); |
| auto cleanup = fbl::MakeAutoCall([this] { |
| pause_event_.reset(); |
| pause_condition_.notify_all(); |
| }); |
| |
| bool ignore_existing = false; |
| while (true) { |
| zx::channel watcher, server; |
| zx_status_t status = zx::channel::create(0, &watcher, &server); |
| if (status != ZX_OK) { |
| FX_LOGS(ERROR) << "failed to create watcher channel: " << zx_status_get_string(status); |
| return; |
| } |
| |
| auto mask = fio::WATCH_MASK_ALL; |
| if (ignore_existing) { |
| mask &= ~fio::WATCH_MASK_EXISTING; |
| } |
| auto result = fio::Directory::Call::Watch(caller.channel(), mask, 0, std::move(server)); |
| if (result.status() != ZX_OK) { |
| FX_LOGS(ERROR) << "failed to send watch: " << zx_status_get_string(result.status()); |
| return; |
| } |
| if (result->s != ZX_OK) { |
| FX_LOGS(ERROR) << "watch failed: " << zx_status_get_string(result->s); |
| return; |
| } |
| |
| { |
| 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::MAX_BUF + 1]; |
| fbl::Span buf_span(buf, std::size(buf) - 1); |
| |
| zx_signals_t signals; |
| while ((signals = WaitForWatchMessages(watcher.borrow(), ignore_existing, buf_span)) == |
| ZX_CHANNEL_READABLE) { |
| // Add an extra byte, so that ProcessWatchMessages can make C strings in the messages. |
| buf_span = fbl::Span(buf_span.data(), buf_span.size() + 1); |
| buf_span.back() = 0; |
| if (ProcessWatchMessages(buf_span, caller.fd().get())) { |
| ignore_existing = true; |
| } |
| |
| // reset the buffer for the next read. |
| buf_span = fbl::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(int dirfd, int event, const char* name) { |
| if (event != fio::WATCH_EVENT_ADDED && event != fio::WATCH_EVENT_EXISTING && |
| event != fio::WATCH_EVENT_IDLE) { |
| 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::WATCH_EVENT_IDLE && 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; |
| } |
| |
| BlockDevice device(&mounter_, std::move(device_fd), device_manager_.config()); |
| zx_status_t status = device_manager_.AddDevice(device); |
| if (status == ZX_ERR_NOT_SUPPORTED) { |
| // The femu tests watch for the following message and will need updating if this changes. |
| FX_LOGS(INFO) << "" << kPathBlockDeviceRoot << "/" << 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) << "" << kPathBlockDeviceRoot << "/" << name |
| << " failed: " << zx_status_get_string(status); |
| } |
| return false; |
| } |
| |
| bool BlockWatcher::ProcessWatchMessages(fbl::Span<uint8_t> buf, int dirfd) { |
| uint8_t* iter = buf.begin(); |
| bool did_receive_idle = false; |
| while (iter + 2 <= buf.end()) { |
| uint8_t event = *iter++; |
| uint8_t name_len = *iter++; |
| |
| if (iter + name_len > buf.end()) { |
| break; |
| } |
| |
| // Save the first byte of the next message, |
| // and null-terminate the name. |
| uint8_t tmp = iter[name_len]; |
| iter[name_len] = 0; |
| |
| if (Callback(dirfd, event, reinterpret_cast<const char*>(iter))) { |
| // We received a WATCH_EVENT_IDLE, and the watcher is paused. |
| // Bail out early. |
| return true; |
| } |
| if (event == fio::WATCH_EVENT_IDLE) { |
| // WATCH_EVENT_IDLE - but the watcher is not paused. Finish processing this set of messages, |
| // but return true once we're done to indicate that we should start checking for the pause |
| // signal. |
| did_receive_idle = true; |
| } |
| |
| iter[name_len] = tmp; |
| |
| iter += name_len; |
| } |
| return did_receive_idle; |
| } |
| |
| zx_signals_t BlockWatcher::WaitForWatchMessages(const zx::unowned_channel& watcher_chan, |
| bool finished_startup, fbl::Span<uint8_t>& buf) { |
| zx_status_t status; |
| zx_wait_item_t wait_items[2] = { |
| { |
| .handle = watcher_chan->get(), |
| .waitfor = ZX_CHANNEL_READABLE | ZX_CHANNEL_PEER_CLOSED, |
| .pending = 0, |
| }, |
| { |
| .handle = pause_event_.get(), |
| .waitfor = kSignalWatcherPaused, |
| .pending = 0, |
| }, |
| }; |
| |
| // We only want to check for kSignalWatcherPaused if finished_startup is true. |
| size_t wait_item_count = finished_startup ? 2 : 1; |
| |
| if ((status = zx_object_wait_many(wait_items, wait_item_count, zx::time::infinite().get())) != |
| ZX_OK) { |
| FX_LOGS(ERROR) << "failed to wait_many: " << zx_status_get_string(status); |
| return 0; |
| } |
| |
| if (wait_items[1].pending & kSignalWatcherPaused) { |
| return kSignalWatcherPaused; |
| } |
| if (wait_items[0].pending & ZX_CHANNEL_PEER_CLOSED) { |
| return ZX_CHANNEL_PEER_CLOSED; |
| } |
| |
| uint32_t read_len; |
| status = zx_channel_read(watcher_chan->get(), 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; |
| } |
| buf = buf.subspan(0, read_len); |
| return ZX_CHANNEL_READABLE; |
| } |
| |
| fbl::RefPtr<fs::Service> BlockWatcherServer::Create(async_dispatcher* dispatcher, |
| BlockWatcher& watcher) { |
| return fbl::MakeRefCounted<fs::Service>( |
| [dispatcher, &watcher](fidl::ServerEnd<llcpp::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(PauseCompleter::Sync& completer) { |
| completer.Reply(watcher_.Pause()); |
| } |
| |
| void BlockWatcherServer::Resume(ResumeCompleter::Sync& completer) { |
| completer.Reply(watcher_.Resume()); |
| } |
| |
| } // namespace devmgr |