Commit b9573c4a authored by Przemek Pietrzkiewicz's avatar Przemek Pietrzkiewicz Committed by Chromium LUCI CQ

[fuchsia] Implement chromium.cast.DataReset in CastRunner

This patch implements chromium.cast.DataReset in CastRunner. Upon
request to reset persistent state, we:

 - Block launching new components in the main context
 - Move all data under /cache to a staging directory that is deleted
   upon next startup of CastRunner

Bug: 1146474, 1146480
Test: run_cast_runner_integration_tests
Test: verified manually on device that a file in the cache directory of CastRunner is gone after DataReset and reboot
Change-Id: I713133540302a1c0535c778e5957e2c87e0f669b
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2537840
Commit-Queue: Przemek Pietrzkiewicz <ppi@google.com>
Reviewed-by: default avatarWez <wez@chromium.org>
Reviewed-by: default avatarBenjamin Lerman <qsr@chromium.org>
Cr-Commit-Position: refs/heads/master@{#834193}
parent a9b2fd31
......@@ -12,6 +12,8 @@
#include <utility>
#include "base/bind.h"
#include "base/callback_forward.h"
#include "base/files/file_enumerator.h"
#include "base/files/file_path.h"
#include "base/files/file_util.h"
#include "base/fuchsia/file_utils.h"
......@@ -19,6 +21,7 @@
#include "base/fuchsia/fuchsia_logging.h"
#include "base/fuchsia/process_context.h"
#include "base/logging.h"
#include "base/time/time.h"
#include "base/values.h"
#include "fuchsia/base/agent_manager.h"
#include "fuchsia/base/config_reader.h"
......@@ -81,6 +84,41 @@ const uint16_t kEphemeralRemoteDebuggingPort = 0;
// Application URL for the pseudo-component providing fuchsia.web.FrameHost.
constexpr char kFrameHostComponentName[] = "cast:fuchsia.web.FrameHost";
// Application URL for the pseudo-component providing chromium.cast.DataReset.
constexpr char kDataResetComponentName[] = "cast:chromium.cast.DataReset";
// Subdirectory used to stage persistent directories to be deleted upon next
// startup.
const char kStagedForDeletionSubdirectory[] = "staged_for_deletion";
base::FilePath GetStagedForDeletionDirectoryPath() {
base::FilePath cache_directory(base::fuchsia::kPersistedCacheDirectoryPath);
return cache_directory.Append(kStagedForDeletionSubdirectory);
}
// Deletes files/directories staged for deletion during the previous run.
// We delete synchronously on main thread for simplicity. Note that this
// overall mechanism is a temporary solution. TODO(crbug.com/1146480): migrate
// to the framework mechanism of clearing session data when available.
void DeleteStagedForDeletionDirectoryIfExists() {
const base::FilePath staged_for_deletion_directory =
GetStagedForDeletionDirectoryPath();
if (!PathExists(staged_for_deletion_directory))
return;
const base::TimeTicks started_at = base::TimeTicks::Now();
bool result = base::DeletePathRecursively(staged_for_deletion_directory);
if (!result) {
LOG(ERROR) << "Failed to delete the staging directory";
return;
}
LOG(WARNING) << "Deleting old persistent data took "
<< (base::TimeTicks::Now() - started_at).InMillisecondsF()
<< " ms";
}
// Populates |params| with web data settings. Web data persistence is only
// enabled if a soft quota is explicitly specified via config-data.
void SetDataParamsForMainContext(fuchsia::web::CreateContextParams* params) {
......@@ -179,6 +217,62 @@ class FrameHostComponent : public fuchsia::sys::ComponentController {
base::WeakPtrFactory<const sys::ServiceDirectory> weak_incoming_services_;
};
// TODO(crbug.com/1120914): Remove this once Component Framework v2 can be
// used to route chromium.cast.DataReset capabilities cleanly.
class DataResetComponent : public fuchsia::sys::ComponentController,
public chromium::cast::DataReset {
public:
// Creates a DataResetComponent with lifetime managed by |controller_request|.
static void Start(
base::OnceCallback<bool()> delete_persistent_data,
std::unique_ptr<base::fuchsia::StartupContext> startup_context,
fidl::InterfaceRequest<fuchsia::sys::ComponentController>
controller_request) {
new DataResetComponent(std::move(delete_persistent_data),
std::move(startup_context),
std::move(controller_request));
}
private:
DataResetComponent(
base::OnceCallback<bool()> delete_persistent_data,
std::unique_ptr<base::fuchsia::StartupContext> startup_context,
fidl::InterfaceRequest<fuchsia::sys::ComponentController>
controller_request)
: delete_persistent_data_(std::move(delete_persistent_data)),
startup_context_(std::move(startup_context)),
data_reset_handler_binding_(startup_context_->outgoing(), this) {
DCHECK(delete_persistent_data_);
startup_context_->ServeOutgoingDirectory();
binding_.Bind(std::move(controller_request));
binding_.set_error_handler([this](zx_status_t) { Kill(); });
}
~DataResetComponent() final = default;
// fuchsia::sys::ComponentController interface.
void Kill() final { delete this; }
void Detach() final {
binding_.Close(ZX_ERR_NOT_SUPPORTED);
delete this;
}
// chromium::cast::DataReset interface.
void DeletePersistentData(DeletePersistentDataCallback callback) final {
if (!delete_persistent_data_) {
// Repeated requests to DeletePersistentData are not supported.
binding_.Close(ZX_ERR_NOT_SUPPORTED);
return;
}
callback(std::move(delete_persistent_data_).Run());
}
base::OnceCallback<bool()> delete_persistent_data_;
std::unique_ptr<base::fuchsia::StartupContext> startup_context_;
const base::fuchsia::ScopedServiceBinding<chromium::cast::DataReset>
data_reset_handler_binding_;
fidl::Binding<fuchsia::sys::ComponentController> binding_{this};
};
} // namespace
CastRunner::CastRunner(bool is_headless)
......@@ -191,6 +285,9 @@ CastRunner::CastRunner(bool is_headless)
isolated_services_(
std::make_unique<base::fuchsia::FilteredServiceDirectory>(
base::ComponentContextForProcess()->svc().get())) {
// Delete persisted data staged for deletion during the previous run.
DeleteStagedForDeletionDirectoryIfExists();
// Specify the services to connect via the Runner process' service directory.
for (const char* name : kServices) {
main_services_->AddService(name);
......@@ -271,6 +368,48 @@ void CastRunner::StartComponent(
std::move(startup_context), std::move(controller_request)));
}
bool CastRunner::DeletePersistentData() {
// Set data reset flag so that new components are not being started.
data_reset_in_progress_ = true;
// Create the staging directory.
base::FilePath staged_for_deletion_directory =
GetStagedForDeletionDirectoryPath();
base::File::Error file_error;
bool result = base::CreateDirectoryAndGetError(staged_for_deletion_directory,
&file_error);
if (!result) {
LOG(ERROR) << "Failed to create the staging directory, error: "
<< file_error;
return false;
}
// Stage everything under `/cache` for deletion.
const base::FilePath cache_directory(
base::fuchsia::kPersistedCacheDirectoryPath);
base::FileEnumerator enumerator(
cache_directory, /*recursive=*/false,
base::FileEnumerator::FileType::FILES |
base::FileEnumerator::FileType::DIRECTORIES);
for (base::FilePath current = enumerator.Next(); !current.empty();
current = enumerator.Next()) {
// Skip the staging directory itself.
if (current == staged_for_deletion_directory) {
continue;
}
base::FilePath destination =
staged_for_deletion_directory.Append(current.BaseName());
result = base::Move(current, destination);
if (!result) {
LOG(ERROR) << "Failed to move " << current << " to " << destination;
return false;
}
}
return true;
}
void CastRunner::LaunchPendingComponent(PendingCastComponent* pending_component,
CastComponent::Params params) {
DCHECK(cors_exempt_headers_);
......@@ -317,6 +456,14 @@ void CastRunner::LaunchPendingComponent(PendingCastComponent* pending_component,
}
}
// Do not launch new main context components while data reset is in progress,
// so that they don't create new persisted state. We expect the session
// to be restarted shortly after data reset completes.
if (data_reset_in_progress_ && component_owner == main_context_.get()) {
pending_components_.erase(pending_component);
return;
}
// Register the new component and clean up the |pending_component|.
component_owner->RegisterComponent(std::move(cast_component));
pending_components_.erase(pending_component);
......@@ -534,7 +681,17 @@ void CastRunner::StartComponentInternal(
return;
}
// TODO(crbug.com/1120914): Remove this once Component Framework v2 can be
// used to route chromium.cast.DataReset capabilities cleanly.
if (url.spec() == kDataResetComponentName) {
DataResetComponent::Start(base::BindOnce(&CastRunner::DeletePersistentData,
base::Unretained(this)),
std::move(startup_context),
std::move(controller_request));
return;
}
pending_components_.emplace(std::make_unique<PendingCastComponent>(
this, std::move(startup_context), std::move(controller_request),
url.GetContent()));
}
}
\ No newline at end of file
......@@ -20,7 +20,6 @@
#include "base/containers/flat_set.h"
#include "base/containers/unique_ptr_adapters.h"
#include "base/fuchsia/startup_context.h"
#include "base/memory/weak_ptr.h"
#include "base/optional.h"
#include "fuchsia/runners/cast/cast_component.h"
#include "fuchsia/runners/cast/pending_cast_component.h"
......@@ -112,6 +111,13 @@ class CastRunner : public fuchsia::sys::Runner,
fidl::InterfaceRequest<fuchsia::sys::ComponentController>
controller_request);
// Moves all data persisted by the main Context to a staging directory,
// which will be deleted the next time the Runner starts up.
// Requests to launch new components in the main Context will be rejected
// until this Runner instance is shutdown.
// Returns true on success and false in case of I/O error.
bool DeletePersistentData();
// True if this Runner uses Context(s) with the HEADLESS feature set.
const bool is_headless_;
......@@ -158,6 +164,11 @@ class CastRunner : public fuchsia::sys::Runner,
// True if Contexts should be created without VULKAN set.
bool disable_vulkan_for_test_ = false;
// True if cast runner entered data reset mode. Prevents new components
// in the main context from being launched. This is set to true once data
// reset starts and does not switch back to false upon completion.
bool data_reset_in_progress_ = false;
};
#endif // FUCHSIA_RUNNERS_CAST_CAST_RUNNER_H_
......@@ -1145,6 +1145,31 @@ TEST_F(CastRunnerIntegrationTest, MissingCorsExemptHeaderProvider) {
EXPECT_TRUE(!component_state_);
}
// Verifies that CastRunner offers a chromium.cast.DataReset service.
// TODO(crbug.com/1146474): Expand the test to verify that the persisted data is
// correctly cleared (e.g. using a custom test HTML app that uses persisted
// data).
TEST_F(CastRunnerIntegrationTest, DataReset) {
constexpr char kDataResetComponentName[] = "cast:chromium.cast.DataReset";
StartCastComponent(kDataResetComponentName);
base::RunLoop loop;
auto data_reset =
component_services_client_->Connect<chromium::cast::DataReset>();
data_reset.set_error_handler([quit_loop = loop.QuitClosure()](zx_status_t) {
quit_loop.Run();
ADD_FAILURE();
});
bool succeeded = false;
data_reset->DeletePersistentData([&succeeded, &loop](bool result) {
succeeded = result;
loop.Quit();
});
loop.Run();
EXPECT_TRUE(succeeded);
}
class CastRunnerFrameHostIntegrationTest : public CastRunnerIntegrationTest {
public:
CastRunnerFrameHostIntegrationTest()
......
......@@ -61,7 +61,7 @@ int main(int argc, char** argv) {
sys::OutgoingDirectory* const outgoing_directory =
base::ComponentContextForProcess()->outgoing().get();
// Publish the fuchsia.web.Runner implementation for Cast applications.
// Publish the fuchsia.sys.Runner implementation for Cast applications.
const bool enable_headless =
command_line->HasSwitch(kForceHeadlessForTestsSwitch) ||
GetConfigBool(kHeadlessConfigKey);
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment