Commit d13db7ec authored by Austin Sullivan's avatar Austin Sullivan Committed by Chromium LUCI CQ

Guarantee Close finishes regardless of mojo pipe

Old: FileWriter deleted when mojo pipe severed
New: FileWriter finishes operation regardless of status of mojo pipe

- Formerly static methods made non-static
- Pre-req for autoClose flag

Bug: 1135687
Change-Id: I3ae992a1c4015bc9ce1e19760633a83432825d16
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2552627
Commit-Queue: Austin Sullivan <asully@chromium.org>
Reviewed-by: default avatarDaniel Cheng <dcheng@chromium.org>
Reviewed-by: default avatarMarijn Kruisselbrink <mek@chromium.org>
Cr-Commit-Position: refs/heads/master@{#835888}
parent 6a148854
......@@ -79,6 +79,10 @@ class CONTENT_EXPORT NativeFileSystemFileWriterImpl
void OnDisconnect();
// Delete the FileWriter after Close if the mojo pipe is unbound.
void CallCloseCallbackAndMaybeDeleteThis(
blink::mojom::NativeFileSystemErrorPtr result);
void WriteImpl(uint64_t offset,
mojo::PendingRemote<blink::mojom::Blob> data,
WriteCallback callback);
......@@ -91,34 +95,18 @@ class CONTENT_EXPORT NativeFileSystemFileWriterImpl
bool complete);
void TruncateImpl(uint64_t length, TruncateCallback callback);
void CloseImpl(CloseCallback callback);
// The following two methods are static, because they need to be invoked to
// perform cleanup even if the writer was deleted before they were invoked.
static void DoAfterWriteCheck(
base::WeakPtr<NativeFileSystemFileWriterImpl> file_writer,
scoped_refptr<NativeFileSystemManagerImpl> manager,
const storage::FileSystemURL& swap_url,
NativeFileSystemFileWriterImpl::CloseCallback callback,
base::File::Error hash_result,
const std::string& hash,
int64_t size);
static void DidAfterWriteCheck(
base::WeakPtr<NativeFileSystemFileWriterImpl> file_writer,
scoped_refptr<NativeFileSystemManagerImpl> manager,
const storage::FileSystemURL& swap_url,
NativeFileSystemFileWriterImpl::CloseCallback callback,
void DoAfterWriteCheck(base::File::Error hash_result,
const std::string& hash,
int64_t size);
void DidAfterWriteCheck(
NativeFileSystemPermissionContext::AfterWriteCheckResult result);
void DidPassAfterWriteCheck(CloseCallback callback);
void DidSwapFileSkipQuarantine(CloseCallback callback,
base::File::Error result);
static void DidSwapFileDoQuarantine(
base::WeakPtr<NativeFileSystemFileWriterImpl> file_writer,
void DidSwapFileSkipQuarantine(base::File::Error result);
void DidSwapFileDoQuarantine(
const storage::FileSystemURL& target_url,
const GURL& referrer_url,
mojo::Remote<quarantine::mojom::Quarantine> quarantine_remote,
CloseCallback callback,
base::File::Error result);
void DidAnnotateFile(
CloseCallback callback,
mojo::Remote<quarantine::mojom::Quarantine> quarantine_remote,
quarantine::mojom::QuarantineFileResult result);
......@@ -156,7 +144,20 @@ class CONTENT_EXPORT NativeFileSystemFileWriterImpl
// execute a move operation from the swap URL to the target URL at `url_`. In
// most filesystems, this move operation is atomic.
storage::FileSystemURL swap_url_;
// NativeFileSystemWriter lifetime management has the following cases:
// 1) The mojo pipe is severed before Close() is invoked.
// - Abort the transaction from the OnDisconnect method.
// 2) The mojo pipe is severed before Close() finishes.
// - The Close() call is allowed to finish.
// - The Writer is destroyed immediately afterwards, via the
// CallCloseCallbackAndMaybeDeleteThis method.
// 3) The mojo pipe exists when Close() finishes.
// - The Writer will exist for as long as the mojo pipe is connected.
// - The Writer is destroyed via the OnDisconnect method.
State state_ = State::kOpen;
// This callback is non-null when the State is kClosePending or kCloseError.
CloseCallback close_callback_;
download::QuarantineConnectionCallback quarantine_connection_callback_;
......
......@@ -647,7 +647,8 @@ TEST_F(NativeFileSystemFileWriterAfterWriteChecksTest, Block) {
file_system_context_.get(), test_file_url_, 0));
}
TEST_F(NativeFileSystemFileWriterAfterWriteChecksTest, HandleCloseDuringCheck) {
TEST_F(NativeFileSystemFileWriterAfterWriteChecksTest,
HandleCloseDuringCheckOK) {
uint64_t bytes_written;
NativeFileSystemStatus result = WriteSync(0, "abc", &bytes_written);
EXPECT_EQ(result, NativeFileSystemStatus::kOk);
......@@ -679,6 +680,48 @@ TEST_F(NativeFileSystemFileWriterAfterWriteChecksTest, HandleCloseDuringCheck) {
std::move(sb_callback)
.Run(NativeFileSystemPermissionContext::AfterWriteCheckResult::kAllow);
// Swap file should now be deleted, target file should be written out.
task_environment_.RunUntilIdle();
EXPECT_FALSE(storage::AsyncFileTestHelper::FileExists(
file_system_context_.get(), test_swap_url_,
storage::AsyncFileTestHelper::kDontCheckSize));
EXPECT_TRUE(storage::AsyncFileTestHelper::FileExists(
file_system_context_.get(), test_file_url_, 3));
}
TEST_F(NativeFileSystemFileWriterAfterWriteChecksTest,
HandleCloseDuringCheckNotOK) {
uint64_t bytes_written;
NativeFileSystemStatus result = WriteSync(0, "abc", &bytes_written);
EXPECT_EQ(result, NativeFileSystemStatus::kOk);
EXPECT_EQ(bytes_written, 3u);
using SBCallback = base::OnceCallback<void(
NativeFileSystemPermissionContext::AfterWriteCheckResult)>;
SBCallback sb_callback;
base::RunLoop loop;
EXPECT_CALL(permission_context_, PerformAfterWriteChecks_)
.WillOnce(testing::Invoke([&](NativeFileSystemWriteItem* item,
GlobalFrameRoutingId frame_id,
SBCallback& callback) {
sb_callback = std::move(callback);
loop.Quit();
}));
handle_->Close(base::DoNothing());
loop.Run();
remote_.reset();
// Destructor should not have deleted swap file with an active safe browsing
// check pending.
task_environment_.RunUntilIdle();
EXPECT_TRUE(storage::AsyncFileTestHelper::FileExists(
file_system_context_.get(), test_swap_url_,
storage::AsyncFileTestHelper::kDontCheckSize));
std::move(sb_callback)
.Run(NativeFileSystemPermissionContext::AfterWriteCheckResult::kBlock);
// Swap file should now be deleted, target file should be unmodified.
task_environment_.RunUntilIdle();
EXPECT_FALSE(storage::AsyncFileTestHelper::FileExists(
......
......@@ -517,7 +517,7 @@ TEST_F(NativeFileSystemManagerImplTest,
storage::AsyncFileTestHelper::kDontCheckSize));
}
TEST_F(NativeFileSystemManagerImplTest, FileWriterCloseAbortsOnDestruct) {
TEST_F(NativeFileSystemManagerImplTest, FileWriterCloseDoesNotAbortOnDestruct) {
auto test_file_url = file_system_context_->CreateCrackedFileSystemURL(
kTestOrigin, storage::kFileSystemTypeTest,
base::FilePath::FromUTF8Unsafe("test"));
......@@ -541,18 +541,54 @@ TEST_F(NativeFileSystemManagerImplTest, FileWriterCloseAbortsOnDestruct) {
storage::AsyncFileTestHelper::kDontCheckSize));
writer_remote->Close(base::DoNothing());
// Severs the mojo pipe, causing the writer to be destroyed.
EXPECT_CALL(permission_context_,
PerformAfterWriteChecks_(testing::_, kFrameId, testing::_))
.WillOnce(base::test::RunOnceCallback<2>(
NativeFileSystemPermissionContext::AfterWriteCheckResult::kAllow));
// Severs the mojo pipe, but the writer should not be destroyed.
writer_remote.reset();
base::RunLoop().RunUntilIdle();
// Since the writer was destroyed before close completed, the swap file should
// have been destroyed and the target file should have been left untouched.
// Since the close should complete, the swap file should have been destroyed
// and the write should be reflected in the target file.
ASSERT_FALSE(storage::AsyncFileTestHelper::FileExists(
file_system_context_.get(), test_swap_url,
storage::AsyncFileTestHelper::kDontCheckSize));
ASSERT_TRUE(storage::AsyncFileTestHelper::FileExists(
file_system_context_.get(), test_file_url, 3));
}
TEST_F(NativeFileSystemManagerImplTest,
FileWriterNoWritesIfConnectionLostBeforeClose) {
auto test_file_url = file_system_context_->CreateCrackedFileSystemURL(
kTestOrigin, storage::kFileSystemTypeTest,
base::FilePath::FromUTF8Unsafe("test"));
auto test_swap_url = file_system_context_->CreateCrackedFileSystemURL(
kTestOrigin, storage::kFileSystemTypeTest,
base::FilePath::FromUTF8Unsafe("test.crswap"));
ASSERT_EQ(base::File::FILE_OK,
storage::AsyncFileTestHelper::CreateFileWithData(
file_system_context_.get(), test_swap_url, "foo", 3));
mojo::Remote<blink::mojom::NativeFileSystemFileWriter> writer_remote(
manager_->CreateFileWriter(kBindingContext, test_file_url, test_swap_url,
NativeFileSystemManagerImpl::SharedHandleState(
allow_grant_, allow_grant_, {})));
// Severs the mojo pipe. The writer should be destroyed.
writer_remote.reset();
base::RunLoop().RunUntilIdle();
// Neither the target file nor the swap file should exist.
ASSERT_FALSE(storage::AsyncFileTestHelper::FileExists(
file_system_context_.get(), test_file_url,
storage::AsyncFileTestHelper::kDontCheckSize));
ASSERT_FALSE(storage::AsyncFileTestHelper::FileExists(
file_system_context_.get(), test_swap_url,
storage::AsyncFileTestHelper::kDontCheckSize));
}
TEST_F(NativeFileSystemManagerImplTest, SerializeHandle_SandboxedFile) {
......
......@@ -32,9 +32,6 @@ interface NativeFileSystemFileWriter {
// Closes the file writer. This will materialize the writes operations on the
// intended file target in the case of atomic writes.
// Close() will close the mojo pipe after completion.
// If the mojo pipe closes before Close() is invoked, the write operation is deemed
// unsuccessful. Any temporary artifacts will be deleted.
// Returns whether the operation succeeded.
Close() => (NativeFileSystemError result);
};
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