chromium/chrome/browser/ash/fusebox/fusebox_read_writer.cc

// 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