blob: 8d631ba728aff1b7ede4f22e25163ded766bcc97 [file] [log] [blame]
// 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