[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: Wez <wez@chromium.org> Reviewed-by: Benjamin Lerman <qsr@chromium.org> Cr-Commit-Position: refs/heads/master@{#834193}
This commit is contained in:

committed by
Chromium LUCI CQ

parent
a9b2fd310e
commit
b9573c4ae2
fuchsia/runners/cast
@ -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()));
|
||||
}
|
||||
}
|
@ -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);
|
||||
|
Reference in New Issue
Block a user