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 ...@@ -79,6 +79,10 @@ class CONTENT_EXPORT NativeFileSystemFileWriterImpl
void OnDisconnect(); void OnDisconnect();
// Delete the FileWriter after Close if the mojo pipe is unbound.
void CallCloseCallbackAndMaybeDeleteThis(
blink::mojom::NativeFileSystemErrorPtr result);
void WriteImpl(uint64_t offset, void WriteImpl(uint64_t offset,
mojo::PendingRemote<blink::mojom::Blob> data, mojo::PendingRemote<blink::mojom::Blob> data,
WriteCallback callback); WriteCallback callback);
...@@ -91,34 +95,18 @@ class CONTENT_EXPORT NativeFileSystemFileWriterImpl ...@@ -91,34 +95,18 @@ class CONTENT_EXPORT NativeFileSystemFileWriterImpl
bool complete); bool complete);
void TruncateImpl(uint64_t length, TruncateCallback callback); void TruncateImpl(uint64_t length, TruncateCallback callback);
void CloseImpl(CloseCallback callback); void CloseImpl(CloseCallback callback);
// The following two methods are static, because they need to be invoked to void DoAfterWriteCheck(base::File::Error hash_result,
// perform cleanup even if the writer was deleted before they were invoked. const std::string& hash,
static void DoAfterWriteCheck( int64_t size);
base::WeakPtr<NativeFileSystemFileWriterImpl> file_writer, void DidAfterWriteCheck(
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,
NativeFileSystemPermissionContext::AfterWriteCheckResult result); NativeFileSystemPermissionContext::AfterWriteCheckResult result);
void DidPassAfterWriteCheck(CloseCallback callback); void DidSwapFileSkipQuarantine(base::File::Error result);
void DidSwapFileSkipQuarantine(CloseCallback callback, void DidSwapFileDoQuarantine(
base::File::Error result);
static void DidSwapFileDoQuarantine(
base::WeakPtr<NativeFileSystemFileWriterImpl> file_writer,
const storage::FileSystemURL& target_url, const storage::FileSystemURL& target_url,
const GURL& referrer_url, const GURL& referrer_url,
mojo::Remote<quarantine::mojom::Quarantine> quarantine_remote, mojo::Remote<quarantine::mojom::Quarantine> quarantine_remote,
CloseCallback callback,
base::File::Error result); base::File::Error result);
void DidAnnotateFile( void DidAnnotateFile(
CloseCallback callback,
mojo::Remote<quarantine::mojom::Quarantine> quarantine_remote, mojo::Remote<quarantine::mojom::Quarantine> quarantine_remote,
quarantine::mojom::QuarantineFileResult result); quarantine::mojom::QuarantineFileResult result);
...@@ -156,7 +144,20 @@ class CONTENT_EXPORT NativeFileSystemFileWriterImpl ...@@ -156,7 +144,20 @@ class CONTENT_EXPORT NativeFileSystemFileWriterImpl
// execute a move operation from the swap URL to the target URL at `url_`. In // execute a move operation from the swap URL to the target URL at `url_`. In
// most filesystems, this move operation is atomic. // most filesystems, this move operation is atomic.
storage::FileSystemURL swap_url_; 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; State state_ = State::kOpen;
// This callback is non-null when the State is kClosePending or kCloseError.
CloseCallback close_callback_;
download::QuarantineConnectionCallback quarantine_connection_callback_; download::QuarantineConnectionCallback quarantine_connection_callback_;
......
...@@ -647,7 +647,8 @@ TEST_F(NativeFileSystemFileWriterAfterWriteChecksTest, Block) { ...@@ -647,7 +647,8 @@ TEST_F(NativeFileSystemFileWriterAfterWriteChecksTest, Block) {
file_system_context_.get(), test_file_url_, 0)); file_system_context_.get(), test_file_url_, 0));
} }
TEST_F(NativeFileSystemFileWriterAfterWriteChecksTest, HandleCloseDuringCheck) { TEST_F(NativeFileSystemFileWriterAfterWriteChecksTest,
HandleCloseDuringCheckOK) {
uint64_t bytes_written; uint64_t bytes_written;
NativeFileSystemStatus result = WriteSync(0, "abc", &bytes_written); NativeFileSystemStatus result = WriteSync(0, "abc", &bytes_written);
EXPECT_EQ(result, NativeFileSystemStatus::kOk); EXPECT_EQ(result, NativeFileSystemStatus::kOk);
...@@ -679,6 +680,48 @@ TEST_F(NativeFileSystemFileWriterAfterWriteChecksTest, HandleCloseDuringCheck) { ...@@ -679,6 +680,48 @@ TEST_F(NativeFileSystemFileWriterAfterWriteChecksTest, HandleCloseDuringCheck) {
std::move(sb_callback) std::move(sb_callback)
.Run(NativeFileSystemPermissionContext::AfterWriteCheckResult::kAllow); .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. // Swap file should now be deleted, target file should be unmodified.
task_environment_.RunUntilIdle(); task_environment_.RunUntilIdle();
EXPECT_FALSE(storage::AsyncFileTestHelper::FileExists( EXPECT_FALSE(storage::AsyncFileTestHelper::FileExists(
......
...@@ -517,7 +517,7 @@ TEST_F(NativeFileSystemManagerImplTest, ...@@ -517,7 +517,7 @@ TEST_F(NativeFileSystemManagerImplTest,
storage::AsyncFileTestHelper::kDontCheckSize)); storage::AsyncFileTestHelper::kDontCheckSize));
} }
TEST_F(NativeFileSystemManagerImplTest, FileWriterCloseAbortsOnDestruct) { TEST_F(NativeFileSystemManagerImplTest, FileWriterCloseDoesNotAbortOnDestruct) {
auto test_file_url = file_system_context_->CreateCrackedFileSystemURL( auto test_file_url = file_system_context_->CreateCrackedFileSystemURL(
kTestOrigin, storage::kFileSystemTypeTest, kTestOrigin, storage::kFileSystemTypeTest,
base::FilePath::FromUTF8Unsafe("test")); base::FilePath::FromUTF8Unsafe("test"));
...@@ -541,18 +541,54 @@ TEST_F(NativeFileSystemManagerImplTest, FileWriterCloseAbortsOnDestruct) { ...@@ -541,18 +541,54 @@ TEST_F(NativeFileSystemManagerImplTest, FileWriterCloseAbortsOnDestruct) {
storage::AsyncFileTestHelper::kDontCheckSize)); storage::AsyncFileTestHelper::kDontCheckSize));
writer_remote->Close(base::DoNothing()); 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(); writer_remote.reset();
base::RunLoop().RunUntilIdle(); base::RunLoop().RunUntilIdle();
// Since the writer was destroyed before close completed, the swap file should // Since the close should complete, the swap file should have been destroyed
// have been destroyed and the target file should have been left untouched. // and the write should be reflected in the target file.
ASSERT_FALSE(storage::AsyncFileTestHelper::FileExists( ASSERT_FALSE(storage::AsyncFileTestHelper::FileExists(
file_system_context_.get(), test_swap_url, file_system_context_.get(), test_swap_url,
storage::AsyncFileTestHelper::kDontCheckSize)); 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( ASSERT_FALSE(storage::AsyncFileTestHelper::FileExists(
file_system_context_.get(), test_file_url, file_system_context_.get(), test_file_url,
storage::AsyncFileTestHelper::kDontCheckSize)); storage::AsyncFileTestHelper::kDontCheckSize));
ASSERT_FALSE(storage::AsyncFileTestHelper::FileExists(
file_system_context_.get(), test_swap_url,
storage::AsyncFileTestHelper::kDontCheckSize));
} }
TEST_F(NativeFileSystemManagerImplTest, SerializeHandle_SandboxedFile) { TEST_F(NativeFileSystemManagerImplTest, SerializeHandle_SandboxedFile) {
......
...@@ -32,9 +32,6 @@ interface NativeFileSystemFileWriter { ...@@ -32,9 +32,6 @@ interface NativeFileSystemFileWriter {
// Closes the file writer. This will materialize the writes operations on the // Closes the file writer. This will materialize the writes operations on the
// intended file target in the case of atomic writes. // 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. // Returns whether the operation succeeded.
Close() => (NativeFileSystemError result); 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