blob: 28487184c5eb6d18f2af04ee614a4c487ae2e73e [file] [log] [blame]
// Copyright 2019 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 "src/storage/blobfs/pager/page-watcher.h"
#include <lib/syslog/cpp/macros.h>
#include <zircon/status.h>
#include <fbl/auto_lock.h>
#include <fs/trace.h>
#include "src/storage/blobfs/format.h"
namespace blobfs {
namespace pager {
// Called from the blobfs main thread.
zx_status_t PageWatcher::CreatePagedVmo(size_t vmo_size, zx::vmo* vmo_out) {
TRACE_DURATION("blobfs", "PageWatcher::CreatePagedVmo", "vmo_size", vmo_size);
uint32_t vmo_options = 0;
zx::vmo vmo;
zx_status_t status = page_request_handler_.CreateVmo(
user_pager_->Dispatcher(), zx::unowned_pager(user_pager_->Pager().get()), vmo_options,
vmo_size, &vmo);
if (status != ZX_OK) {
FX_LOGS(ERROR) << "Failed to create paged VMO: " << zx_status_get_string(status);
return status;
}
{
// The call to |CreateVmo| succeeded. The VMO is now attached to the pager, so we need to
// wait for a detach before we can destroy the PageWatcher cleanly - |vmo_attached_to_pager_|
// tracks that and is set to false on receiving a ZX_PAGER_VMO_COMPLETE packet (on the pager
// detach path).
fbl::AutoLock guard(&vmo_attached_mutex_);
vmo_attached_to_pager_ = true;
}
vmo_ = zx::unowned_vmo(vmo);
*vmo_out = std::move(vmo);
return ZX_OK;
}
// Called from the blobfs main thread.
void PageWatcher::DetachPagedVmoSync() {
TRACE_DURATION("blobfs", "PageWatcher::DetachPagedVmoSync");
page_request_handler_.Detach();
// Wait on signal from the page request handler.
fbl::AutoLock guard(&vmo_attached_mutex_);
while (vmo_attached_to_pager_) {
vmo_attached_condvar_.Wait(&vmo_attached_mutex_);
}
}
// Called from the singleton userpager thread.
void PageWatcher::HandlePageRequest(async_dispatcher_t* dispatcher, async::PagedVmoBase* paged_vmo,
zx_status_t status, const zx_packet_page_request_t* request) {
TRACE_DURATION("blobfs", "PageWatcher::HandlePageRequest", "command", request->command, "offset",
request->offset, "length", request->length);
// The async loop is shutting down. The VMO has been detached from the pager, mark it safe to
// destroy.
if (status == ZX_ERR_CANCELED) {
// Signal here without waiting for a ZX_PAGER_VMO_COMPLETE packet, to prevent holding up
// destruction indefinitely. The pager async loop is shutting down, so we won't receive any more
// packets on its port.
SignalPagerDetach();
return;
}
// The only other |status| we expect is ZX_OK.
ZX_DEBUG_ASSERT(status == ZX_OK);
ZX_DEBUG_ASSERT(request->flags == 0);
switch (request->command) {
case ZX_PAGER_VMO_READ: {
PopulateAndVerifyPagesInRange(request->offset, request->length);
return;
}
case ZX_PAGER_VMO_COMPLETE: {
SignalPagerDetach();
return;
}
default:
FX_LOGS(ERROR) << "Invalid pager request on vmo " << vmo_->get() << ". [" << request->command
<< ", " << request->offset << ", " << request->length << ", " << request->flags
<< "]";
return;
}
}
// Called from the singleton userpager thread.
void PageWatcher::PopulateAndVerifyPagesInRange(uint64_t offset, uint64_t length) {
TRACE_DURATION("blobfs", "PageWatcher::PopulateAndVerifyPagesInRange", "offset", offset, "length",
length);
if (!vmo_->is_valid()) {
FX_LOGS(ERROR) << "pager VMO is not valid.";
// Return without calling op_range(ZX_PAGER_OP_FAIL), since that requires a valid pager VMO
// handle. This could potentially cause the faulting thread to hang, but there's no way for us
// to recover gracefully from this state.
return;
}
PagerErrorStatus pager_error_status;
if (is_corrupt_) {
pager_error_status = PagerErrorStatus::kErrBadState;
FX_LOGS(ERROR) << "Pager failed page request because blob is corrupt, error: "
<< zx_status_get_string(static_cast<zx_status_t>(pager_error_status));
} else {
pager_error_status = user_pager_->TransferPagesToVmo(offset, length, *vmo_, userpager_info_);
if (pager_error_status != PagerErrorStatus::kOK) {
FX_LOGS(ERROR) << "Pager failed to transfer pages to the blob, error: "
<< zx_status_get_string(static_cast<zx_status_t>(pager_error_status));
}
}
if (pager_error_status != PagerErrorStatus::kOK) {
zx_status_t status = user_pager_->Pager().op_range(
ZX_PAGER_OP_FAIL, *vmo_, offset, length, static_cast<zx_status_t>(pager_error_status));
if (status != ZX_OK) {
FX_LOGS(ERROR) << "op_range ZX_PAGER_OP_FAIL failed with " << zx_status_get_string(status);
return;
}
// We've signaled a failure and unblocked outstanding page requests for this range. If the pager
// error was a verification error, fail future requests as well - we should not service further
// page requests on a corrupt blob.
//
// Note that we cannot simply detach the VMO from the pager here. There might be outstanding
// page requests which have been queued but are yet to be serviced. These need to be handled
// correctly - if the VMO is detached, there will be no way for us to communicate failure to
// the kernel, since zx_pager_op_range() requires a valid pager VMO handle. Without being able
// to make a call to zx_pager_op_range() to indicate a failed page request, the faulting thread
// would hang indefinitely.
if (pager_error_status == PagerErrorStatus::kErrDataIntegrity) {
is_corrupt_ = true;
}
return;
}
}
// Called from the singleton userpager thread.
void PageWatcher::SignalPagerDetach() {
TRACE_DURATION("blobfs", "PageWatcher::SignalPagerDetach");
// Reset the mapping so that future read requests on this VMO will be ignored.
vmo_ = zx::unowned_vmo(ZX_HANDLE_INVALID);
// Complete the paged vmo detach. Any in-flight read requests that arrive after this will be
// ignored.
fbl::AutoLock guard(&vmo_attached_mutex_);
vmo_attached_to_pager_ = false;
vmo_attached_condvar_.Signal();
}
} // namespace pager
} // namespace blobfs