// Copyright 2022 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/ash/fusebox/fusebox_read_writer.h"
#include <fcntl.h>
#include <unistd.h>
#include "base/files/file_util.h"
#include "base/posix/eintr_wrapper.h"
#include "base/strings/stringprintf.h"
#include "base/task/thread_pool.h"
#include "base/threading/scoped_blocking_call.h"
#include "chrome/browser/ash/fusebox/fusebox_copy_to_fd.h"
#include "chrome/browser/ash/fusebox/fusebox_errno.h"
#include "content/public/browser/browser_task_traits.h"
#include "content/public/browser/browser_thread.h"
#include "net/base/net_errors.h"
#include "storage/browser/file_system/file_system_operation_runner.h"
namespace fusebox {
namespace {
void RunFlushCallback(ReadWriter::FlushCallback callback,
int posix_error_code) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
FlushResponseProto response_proto;
if (posix_error_code) {
response_proto.set_posix_error_code(posix_error_code);
}
std::move(callback).Run(response_proto);
}
void RunRead2CallbackFailure(ReadWriter::Read2Callback callback,
base::File::Error error_code) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
Read2ResponseProto response_proto;
response_proto.set_posix_error_code(FileErrorToErrno(error_code));
std::move(callback).Run(response_proto);
}
void RunRead2CallbackTypical(ReadWriter::Read2Callback callback,
scoped_refptr<net::IOBuffer> buffer,
int length) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
Read2ResponseProto response_proto;
if (length < 0) {
response_proto.set_posix_error_code(NetErrorToErrno(length));
} else {
*response_proto.mutable_data() = std::string(buffer->data(), length);
}
std::move(callback).Run(response_proto);
content::GetIOThreadTaskRunner({})->ReleaseSoon(FROM_HERE, std::move(buffer));
}
void RunWrite2CallbackFailure(ReadWriter::Write2Callback callback,
int posix_error_code) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
Write2ResponseProto response_proto;
response_proto.set_posix_error_code(posix_error_code);
std::move(callback).Run(response_proto);
}
void RunWrite2CallbackTypical(ReadWriter::Write2Callback callback, int length) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
Write2ResponseProto response_proto;
if (length < 0) {
response_proto.set_posix_error_code(NetErrorToErrno(length));
}
std::move(callback).Run(response_proto);
}
ReadWriter::WriteTempFileResult WriteTempFile(
base::ScopedFD&& scoped_fd,
scoped_refptr<net::StringIOBuffer> buffer,
int64_t offset,
int length) {
base::ScopedBlockingCall scoped_blocking_call(FROM_HERE,
base::BlockingType::MAY_BLOCK);
int fd = scoped_fd.get();
char* ptr = buffer->data();
if ((HANDLE_EINTR(lseek(fd, static_cast<off_t>(offset), SEEK_SET)) == -1) ||
(HANDLE_EINTR(write(fd, ptr, static_cast<size_t>(length))) == -1)) {
return std::make_pair(std::move(scoped_fd), errno);
}
return std::make_pair(std::move(scoped_fd), 0);
}
void SaveCallback2(base::ScopedFD scoped_fd,
scoped_refptr<storage::FileSystemContext> fs_context,
ReadWriter::Close2Callback callback,
base::File::Error file_error) {
Close2ResponseProto response_proto;
if (file_error != base::File::Error::FILE_OK) {
response_proto.set_posix_error_code(FileErrorToErrno(file_error));
}
content::GetUIThreadTaskRunner({})->PostTask(
FROM_HERE, base::BindOnce(std::move(callback), response_proto));
}
void SaveCallback1(const std::string src_path,
storage::FileSystemURL fs_url,
base::ScopedFD scoped_fd,
scoped_refptr<storage::FileSystemContext> fs_context,
ReadWriter::Close2Callback callback,
base::File::Error file_error) {
// Ignore file_error. We're essentially doing "/usr/bin/rm -f" instead
// of a bare "/usr/bin/rm".
fs_context->operation_runner()->CopyInForeignFile(
base::FilePath(src_path), fs_url,
base::BindOnce(&SaveCallback2, std::move(scoped_fd), fs_context,
std::move(callback)));
}
using EOFFlushFsWriterCallback = base::OnceCallback<void(
std::unique_ptr<storage::FileStreamWriter> fs_writer,
int posix_error_code)>;
// Calls fs_writer->Flush(kEndOfFile), running the callback afterwards, if
// needs_eof_flushing is true. If false, it just runs the callback immediately.
//
// The fs_writer and write_offset are passed through to the callback.
void EOFFlushFsWriterIfNecessary(
std::unique_ptr<storage::FileStreamWriter> fs_writer,
int64_t write_offset,
bool needs_eof_flushing,
EOFFlushFsWriterCallback callback) {
DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
if (!fs_writer || !needs_eof_flushing) {
std::move(callback).Run(std::move(fs_writer), 0);
return;
}
// Save the pointer before we std::move fs_writer into a base::OnceCallback.
// The std::move keeps the underlying storage::FileStreamWriter alive while
// any network I/O is pending. Without the std::move, the underlying
// storage::FileStreamWriter would get destroyed at the end of this function.
storage::FileStreamWriter* fs_writer_ptr = fs_writer.get();
auto pair = base::SplitOnceCallback(base::BindOnce(
[](std::unique_ptr<storage::FileStreamWriter> fs_writer,
int64_t write_offset, EOFFlushFsWriterCallback callback, int result) {
int posix_error_code = (result < 0) ? NetErrorToErrno(result) : 0;
std::move(callback).Run(std::move(fs_writer), posix_error_code);
},
std::move(fs_writer), write_offset, std::move(callback)));
int result = fs_writer_ptr->Flush(storage::FlushMode::kEndOfFile,
std::move(pair.first));
if (result != net::ERR_IO_PENDING) { // The flush was synchronous.
std::move(pair.second).Run(result);
}
}
} // namespace
ReadWriter::ReadWriter(const storage::FileSystemURL& fs_url,
const std::string& profile_path,
bool use_temp_file,
bool temp_file_starts_with_copy)
: fs_url_(fs_url),
profile_path_(profile_path),
use_temp_file_(use_temp_file),
temp_file_starts_with_copy_(temp_file_starts_with_copy) {
DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
}
ReadWriter::~ReadWriter() {
DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
}
// Some functions (marked with a §) below, take an fs_context argument that
// looks unused, but we need to keep the storage::FileSystemContext reference
// alive until the callbacks are run.
void ReadWriter::Close(scoped_refptr<storage::FileSystemContext> fs_context,
Close2Callback callback) {
DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
if (closed_) {
Close2ResponseProto response_proto;
content::GetUIThreadTaskRunner({})->PostTask(
FROM_HERE, base::BindOnce(std::move(callback), response_proto));
return;
}
closed_ = true;
EOFFlushFsWriterIfNecessary(
std::move(fs_writer_), std::exchange(write_offset_, -1),
std::exchange(fs_writer_needs_eof_flushing_, false),
base::BindOnce(&ReadWriter::OnEOFFlushBeforeActualClose,
weak_ptr_factory_.GetWeakPtr(), std::move(callback),
std::move(fs_context)));
}
// static
void ReadWriter::OnEOFFlushBeforeActualClose(
base::WeakPtr<ReadWriter> weak_ptr,
Close2Callback callback,
scoped_refptr<storage::FileSystemContext> fs_context,
std::unique_ptr<storage::FileStreamWriter> fs_writer,
int flush_posix_error_code) {
DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
ReadWriter* self = weak_ptr.get();
if (!self) {
flush_posix_error_code = EBUSY;
}
if (flush_posix_error_code) {
Close2ResponseProto response_proto;
response_proto.set_posix_error_code(flush_posix_error_code);
content::GetUIThreadTaskRunner({})->PostTask(
FROM_HERE, base::BindOnce(std::move(callback), response_proto));
return;
}
if (!self->use_temp_file_) {
Close2ResponseProto response_proto;
content::GetUIThreadTaskRunner({})->PostTask(
FROM_HERE, base::BindOnce(std::move(callback), response_proto));
return;
}
self->close2_fs_context_ = std::move(fs_context);
self->close2_callback_ = std::move(callback);
if (!self->is_loaning_temp_file_scoped_fd_) {
self->Save();
}
}
void ReadWriter::Save() {
DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
DCHECK(close2_fs_context_);
DCHECK(close2_callback_);
DCHECK(!is_loaning_temp_file_scoped_fd_);
DCHECK(use_temp_file_);
if (write_posix_error_code_ != 0) {
Close2ResponseProto response_proto;
response_proto.set_posix_error_code(write_posix_error_code_);
content::GetUIThreadTaskRunner({})->PostTask(
FROM_HERE, base::BindOnce(std::move(close2_callback_), response_proto));
return;
}
std::string src_path =
created_temp_file_
? base::StringPrintf("/proc/self/fd/%d", temp_file_.get())
: "/dev/null";
// Delete the file if it already it exists, or a no-op if it doesn't. Either
// way, SaveCallback1 will then copy from src_path to fs_url_, before
// SaveCallback2 runs the close2_callback_.
close2_fs_context_->operation_runner()->RemoveFile(
fs_url_,
base::BindOnce(&SaveCallback1, src_path, fs_url_, std::move(temp_file_),
close2_fs_context_, std::move(close2_callback_)));
}
void ReadWriter::Flush(scoped_refptr<storage::FileSystemContext> fs_context,
FlushCallback callback) {
DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
DCHECK(!is_in_flight_);
is_in_flight_ = true;
if (closed_) {
is_in_flight_ = false;
content::GetUIThreadTaskRunner({})->PostTask(
FROM_HERE,
base::BindOnce(&RunFlushCallback, std::move(callback), EFAULT));
return;
}
if (use_temp_file_ || !fs_writer_) {
is_in_flight_ = false;
content::GetUIThreadTaskRunner({})->PostTask(
FROM_HERE, base::BindOnce(&RunFlushCallback, std::move(callback), 0));
return;
}
auto pair = base::SplitOnceCallback(base::BindOnce(
&ReadWriter::OnDefaultFlush, weak_ptr_factory_.GetWeakPtr(),
std::move(callback), fs_context));
int result =
fs_writer_->Flush(storage::FlushMode::kDefault, std::move(pair.first));
if (result != net::ERR_IO_PENDING) { // The flush was synchronous.
std::move(pair.second).Run(result);
}
}
void ReadWriter::Read(scoped_refptr<storage::FileSystemContext> fs_context,
int64_t offset,
int64_t length,
Read2Callback callback) {
DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
DCHECK(!is_in_flight_);
is_in_flight_ = true;
if (closed_) {
is_in_flight_ = false;
content::GetUIThreadTaskRunner({})->PostTask(
FROM_HERE, base::BindOnce(&RunRead2CallbackFailure, std::move(callback),
base::File::Error::FILE_ERROR_FAILED));
return;
}
// See if we can re-use the previous storage::FileStreamReader.
std::unique_ptr<storage::FileStreamReader> fs_reader;
if (fs_reader_ && (read_offset_ == offset)) {
fs_reader = std::move(fs_reader_);
read_offset_ = -1;
} else {
fs_reader = fs_context->CreateFileStreamReader(fs_url_, offset, INT64_MAX,
base::Time());
if (!fs_reader) {
is_in_flight_ = false;
content::GetUIThreadTaskRunner({})->PostTask(
FROM_HERE,
base::BindOnce(&RunRead2CallbackFailure, std::move(callback),
base::File::Error::FILE_ERROR_INVALID_URL));
return;
}
}
constexpr int64_t min_length = 256;
constexpr int64_t max_length = 262144; // 256 KiB.
auto buffer = base::MakeRefCounted<net::IOBufferWithSize>(
std::max(min_length, std::min(max_length, length)));
// Save the pointer before we std::move fs_reader into a base::OnceCallback.
// The std::move keeps the underlying storage::FileStreamReader alive while
// any network I/O is pending. Without the std::move, the underlying
// storage::FileStreamReader would get destroyed at the end of this function.
auto* saved_fs_reader = fs_reader.get();
auto pair = base::SplitOnceCallback(base::BindOnce(
&ReadWriter::OnRead, weak_ptr_factory_.GetWeakPtr(), std::move(callback),
fs_context, std::move(fs_reader), buffer, offset));
int result =
saved_fs_reader->Read(buffer.get(), length, std::move(pair.first));
if (result != net::ERR_IO_PENDING) { // The read was synchronous.
std::move(pair.second).Run(result);
}
}
// static
void ReadWriter::OnRead(
base::WeakPtr<ReadWriter> weak_ptr,
Read2Callback callback,
scoped_refptr<storage::FileSystemContext> fs_context, // See § above.
std::unique_ptr<storage::FileStreamReader> fs_reader,
scoped_refptr<net::IOBuffer> buffer,
int64_t offset,
int length) {
DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
ReadWriter* self = weak_ptr.get();
if (!self) {
Read2ResponseProto response_proto;
response_proto.set_posix_error_code(EBUSY);
content::GetUIThreadTaskRunner({})->PostTask(
FROM_HERE, base::BindOnce(std::move(callback), response_proto));
return;
}
DCHECK(self->is_in_flight_);
self->is_in_flight_ = false;
if (length >= 0) {
self->fs_reader_ = std::move(fs_reader);
self->read_offset_ = offset + length;
} else {
self->fs_reader_.reset();
self->read_offset_ = -1;
}
content::GetUIThreadTaskRunner({})->PostTask(
FROM_HERE, base::BindOnce(&RunRead2CallbackTypical, std::move(callback),
std::move(buffer), length));
}
void ReadWriter::Write(scoped_refptr<storage::FileSystemContext> fs_context,
scoped_refptr<net::StringIOBuffer> buffer,
int64_t offset,
int length,
Write2Callback callback) {
DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
DCHECK(!is_in_flight_);
is_in_flight_ = true;
if (closed_ || (write_posix_error_code_ != 0)) {
is_in_flight_ = false;
content::GetUIThreadTaskRunner({})->PostTask(
FROM_HERE,
base::BindOnce(&RunWrite2CallbackFailure, std::move(callback),
closed_ ? EFAULT : write_posix_error_code_));
return;
}
if (use_temp_file_) {
if (!temp_file_.is_valid()) {
// Create the temporary file via open and O_TMPFILE, instead of
// base::CreateTemporaryFile, to simplify clean-up. With the latter,
// there is a garbage collection problem of when to delete no-longer-used
// files (as base::File::DeleteOnClose is Windows only). That problem can
// be tricky if Chrome crashes before we're done with the temporary file.
// With O_TMPFILE, the kernel deletes the file automatically when the
// file descriptor is closed, whether via base::ScopedFD destructor or,
// for crashes, at process exit.
temp_file_.reset(open(profile_path_.c_str(),
O_CLOEXEC | O_EXCL | O_TMPFILE | O_RDWR, 0600));
if (!temp_file_.is_valid()) {
PLOG(WARNING) << "could not create O_TMPFILE file";
is_in_flight_ = false;
content::GetUIThreadTaskRunner({})->PostTask(
FROM_HERE, base::BindOnce(&RunWrite2CallbackFailure,
std::move(callback), ENOSPC));
return;
}
created_temp_file_ = true;
if (temp_file_starts_with_copy_) {
auto outer_callback = base::BindOnce(
OnTempFileInitialized, weak_ptr_factory_.GetWeakPtr(),
std::move(buffer), offset, length, std::move(callback));
is_loaning_temp_file_scoped_fd_ = true;
CopyToFileDescriptor(fs_context, fs_url_, std::move(temp_file_),
std::move(outer_callback));
return;
}
}
CallWriteTempFile(weak_ptr_factory_.GetWeakPtr(), std::move(buffer), offset,
length, std::move(callback));
return;
}
// See if we can re-use the previous storage::FileStreamWriter.
//
// If so, go on to CallWriteDirect immediately. Otherwise, go on to
// EOFFlushFsWriterIfNecessary, OnEOFFlushBeforeCallWriteDirect and then
// CallWriteDirect.
if (fs_writer_ && (write_offset_ == offset)) {
CallWriteDirect(std::move(callback), std::move(fs_context),
std::move(fs_writer_), std::move(buffer),
std::exchange(write_offset_, -1), length);
return;
}
// We will need a new storage::FileStreamWriter, so destroy the previous one
// saved to fs_writer_, if it is non-null. However, we might need to Flush it
// (with FlushMode::kEndOfFile) before destroying it.
//
// Calling EOFFlushFsWriterIfNecessary, with std::move(fs_writer_) and with
// OnEOFFlushBeforeCallWriteDirect, will Flush (if needed) and destroy that
// FileStreamWriter (positioned at write_offset_), if it is non-null.
//
// Afterwards, OnEOFFlushBeforeCallWriteDirect creates a new FileStreamWriter
// (positioned at offset) and passes that on to CallWriteDirect.
EOFFlushFsWriterIfNecessary(
std::move(fs_writer_), std::exchange(write_offset_, -1),
std::exchange(fs_writer_needs_eof_flushing_, false),
base::BindOnce(&ReadWriter::OnEOFFlushBeforeCallWriteDirect,
weak_ptr_factory_.GetWeakPtr(), std::move(callback),
std::move(fs_context), std::move(buffer), offset, length));
}
// static
void ReadWriter::OnTempFileInitialized(
base::WeakPtr<ReadWriter> weak_ptr,
scoped_refptr<net::StringIOBuffer> buffer,
int64_t offset,
int length,
Write2Callback callback,
base::expected<base::ScopedFD, int> result) {
DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
ReadWriter* self = weak_ptr.get();
if (!result.has_value() || !self) {
if (self) {
self->is_in_flight_ = false;
}
Write2ResponseProto response_proto;
response_proto.set_posix_error_code(result.error_or(EBUSY));
content::GetUIThreadTaskRunner({})->PostTask(
FROM_HERE, base::BindOnce(std::move(callback), response_proto));
return;
}
self->is_loaning_temp_file_scoped_fd_ = false;
self->temp_file_ = std::move(result.value());
CallWriteTempFile(std::move(weak_ptr), std::move(buffer), offset, length,
std::move(callback));
}
// static
void ReadWriter::CallWriteTempFile(base::WeakPtr<ReadWriter> weak_ptr,
scoped_refptr<net::StringIOBuffer> buffer,
int64_t offset,
int length,
Write2Callback callback) {
DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
ReadWriter* self = weak_ptr.get();
if (!self) {
Write2ResponseProto response_proto;
response_proto.set_posix_error_code(EBUSY);
content::GetUIThreadTaskRunner({})->PostTask(
FROM_HERE, base::BindOnce(std::move(callback), response_proto));
return;
}
self->is_loaning_temp_file_scoped_fd_ = true;
base::ThreadPool::PostTaskAndReplyWithResult(
FROM_HERE, {base::MayBlock()},
base::BindOnce(&WriteTempFile, std::move(self->temp_file_),
std::move(buffer), offset, length),
base::BindOnce(&OnWriteTempFile, std::move(weak_ptr),
std::move(callback)));
}
// static
void ReadWriter::OnWriteTempFile(base::WeakPtr<ReadWriter> weak_ptr,
Write2Callback callback,
WriteTempFileResult result) {
DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
ReadWriter* self = weak_ptr.get();
if (!self) {
Write2ResponseProto response_proto;
response_proto.set_posix_error_code(EBUSY);
content::GetUIThreadTaskRunner({})->PostTask(
FROM_HERE, base::BindOnce(std::move(callback), response_proto));
return;
}
DCHECK(self->is_in_flight_);
self->is_in_flight_ = false;
self->is_loaning_temp_file_scoped_fd_ = false;
self->temp_file_ = std::move(result.first);
Write2ResponseProto response_proto;
if (result.second) {
response_proto.set_posix_error_code(result.second);
self->write_posix_error_code_ = result.second;
}
content::GetUIThreadTaskRunner({})->PostTask(
FROM_HERE, base::BindOnce(std::move(callback), response_proto));
if (self->close2_callback_) {
self->Save();
}
}
// static
void ReadWriter::OnDefaultFlush(
base::WeakPtr<ReadWriter> weak_ptr,
FlushCallback callback,
scoped_refptr<storage::FileSystemContext> fs_context, // See § above.
int flush_posix_error_code) {
DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
ReadWriter* self = weak_ptr.get();
if (!self) {
flush_posix_error_code = EBUSY;
} else {
self->is_in_flight_ = false;
}
content::GetUIThreadTaskRunner({})->PostTask(
FROM_HERE, base::BindOnce(&RunFlushCallback, std::move(callback),
flush_posix_error_code));
}
// static
void ReadWriter::OnEOFFlushBeforeCallWriteDirect(
base::WeakPtr<ReadWriter> weak_ptr,
Write2Callback callback,
scoped_refptr<storage::FileSystemContext> fs_context, // See § above.
scoped_refptr<net::IOBuffer> buffer,
int64_t offset,
int length,
std::unique_ptr<storage::FileStreamWriter> fs_writer,
int flush_posix_error_code) {
DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
ReadWriter* self = weak_ptr.get();
if (!self) {
flush_posix_error_code = EBUSY;
}
if (flush_posix_error_code) {
if (self) {
self->is_in_flight_ = false;
}
content::GetUIThreadTaskRunner({})->PostTask(
FROM_HERE, base::BindOnce(&RunWrite2CallbackFailure,
std::move(callback), flush_posix_error_code));
return;
}
fs_writer = fs_context->CreateFileStreamWriter(self->fs_url_, offset);
if (!fs_writer) {
self->is_in_flight_ = false;
content::GetUIThreadTaskRunner({})->PostTask(
FROM_HERE,
base::BindOnce(&RunWrite2CallbackFailure, std::move(callback), EINVAL));
return;
}
self->CallWriteDirect(std::move(callback), std::move(fs_context),
std::move(fs_writer), std::move(buffer), offset,
length);
}
void ReadWriter::CallWriteDirect(
Write2Callback callback,
scoped_refptr<storage::FileSystemContext> fs_context, // See § above.
std::unique_ptr<storage::FileStreamWriter> fs_writer,
scoped_refptr<net::IOBuffer> buffer,
int64_t offset,
int length) {
DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
// Save the pointer before we std::move fs_writer into a base::OnceCallback.
// The std::move keeps the underlying storage::FileStreamWriter alive while
// any network I/O is pending. Without the std::move, the underlying
// storage::FileStreamWriter would get destroyed at the end of this function.
auto* saved_fs_writer = fs_writer.get();
auto pair = base::SplitOnceCallback(base::BindOnce(
&ReadWriter::OnWriteDirect, weak_ptr_factory_.GetWeakPtr(),
std::move(callback), fs_context, std::move(fs_writer), buffer, offset));
int result =
saved_fs_writer->Write(buffer.get(), length, std::move(pair.first));
if (result != net::ERR_IO_PENDING) { // The write was synchronous.
std::move(pair.second).Run(result);
}
}
// static
void ReadWriter::OnWriteDirect(
base::WeakPtr<ReadWriter> weak_ptr,
Write2Callback callback,
scoped_refptr<storage::FileSystemContext> fs_context, // See § above.
std::unique_ptr<storage::FileStreamWriter> fs_writer,
scoped_refptr<net::IOBuffer> buffer,
int64_t offset,
int length) {
DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
ReadWriter* self = weak_ptr.get();
if (!self) {
Write2ResponseProto response_proto;
response_proto.set_posix_error_code(EBUSY);
content::GetUIThreadTaskRunner({})->PostTask(
FROM_HERE, base::BindOnce(std::move(callback), response_proto));
return;
}
DCHECK(self->is_in_flight_);
self->is_in_flight_ = false;
if (length >= 0) {
self->fs_writer_ = std::move(fs_writer);
self->write_offset_ = offset + length;
self->fs_writer_needs_eof_flushing_ =
self->fs_writer_needs_eof_flushing_ ||
(self->fs_url_.mount_option().flush_policy() ==
storage::FlushPolicy::FLUSH_ON_COMPLETION);
} else {
self->fs_writer_.reset();
self->write_offset_ = -1;
self->write_posix_error_code_ = NetErrorToErrno(length);
}
content::GetUIThreadTaskRunner({})->PostTask(
FROM_HERE,
base::BindOnce(&RunWrite2CallbackTypical, std::move(callback), length));
}
} // namespace fusebox