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

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

#ifndef CHROME_BROWSER_ASH_FUSEBOX_FUSEBOX_READ_WRITER_H_
#define CHROME_BROWSER_ASH_FUSEBOX_FUSEBOX_READ_WRITER_H_

#include <utility>

#include "base/files/scoped_file.h"
#include "base/functional/callback_forward.h"
#include "base/memory/weak_ptr.h"
#include "base/types/expected.h"
#include "chrome/browser/ash/fusebox/fusebox.pb.h"
#include "net/base/io_buffer.h"
#include "storage/browser/file_system/file_stream_reader.h"
#include "storage/browser/file_system/file_stream_writer.h"
#include "storage/browser/file_system/file_system_context.h"

namespace fusebox {

// Caches a storage::FileStreamReader (and FileStreamWriter) and their offsets
// so that, when consecutive reads (or consecutive writes) are adjacent (the
// second one starts where the first one ends), the FileStreamReader (or
// FileStreamWriter) is re-used.
//
// When serving a "stateless" I/O API that passes an offset each time (such as
// the FUSE API), but the underlying storage::AsyncFileUtil doesn't support
// seeking, then re-using a cached FileStreamReader can often avoid "Shlemiel
// the Painter" quadratic performance. See
// https://wiki.c2.com/?ShlemielThePainter
//
// Each ReadWriter instance lives entirely on the I/O thread, but its owner
// (and the callbacks) must live on the UI thread (and wrap the ReadWriter in a
// base::SequenceBound).
//
// The owner is also responsible for ensuring that only one operation is in
// flight at any one time. "An operation" starts with a Read or Write call and
// ends just before the corresponding callback is run.
class ReadWriter {
 public:
  using Close2Callback =
      base::OnceCallback<void(const Close2ResponseProto& response)>;
  using FlushCallback =
      base::OnceCallback<void(const FlushResponseProto& response)>;
  using Read2Callback =
      base::OnceCallback<void(const Read2ResponseProto& response)>;
  using Write2Callback =
      base::OnceCallback<void(const Write2ResponseProto& response)>;

  // |use_temp_file| is for the case when the |fs_url| storage system does not
  // support incremental writes, only atomic "here's the entire contents"
  // writes, such as MTP (Media Transfer Protocol, e.g. phones attached to a
  // Chromebook, which is a file-oriented rather than block-oriented protocol).
  // In this case, the Write calls are diverted to a temporary file and the
  // Close call saves that temporary file to |fs_url|.
  //
  // |temp_file_starts_with_copy| states whether to initialize that temporary
  // file with a copy of the underlying file. If |temp_file_starts_with_copy|
  // false, the temporary file is initially empty. When |use_temp_file| is
  // false, |temp_file_starts_with_copy| is ignored.
  ReadWriter(const storage::FileSystemURL& fs_url,
             const std::string& profile_path,
             bool use_temp_file,
             bool temp_file_starts_with_copy);
  ~ReadWriter();

  void Close(scoped_refptr<storage::FileSystemContext> fs_context,
             Close2Callback callback);

  void Flush(scoped_refptr<storage::FileSystemContext> fs_context,
             FlushCallback callback);

  void Read(scoped_refptr<storage::FileSystemContext> fs_context,
            int64_t offset,
            int64_t length,
            Read2Callback callback);

  void Write(scoped_refptr<storage::FileSystemContext> fs_context,
             scoped_refptr<net::StringIOBuffer> buffer,
             int64_t offset,
             int length,
             Write2Callback callback);

  // The int is a POSIX error code.
  using WriteTempFileResult = std::pair<base::ScopedFD, int>;

 private:
  // Saves the |temp_file_| to the |fs_url_|.
  void Save();

  // The CallXxx and OnXxx methods are static (but take a WeakPtr) so that the
  // callback will run even if the WeakPtr is invalidated.

  static void OnDefaultFlush(
      base::WeakPtr<ReadWriter> weak_ptr,
      FlushCallback callback,
      scoped_refptr<storage::FileSystemContext> fs_context,
      int flush_posix_error_code);

  static void 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);

  static void OnTempFileInitialized(base::WeakPtr<ReadWriter> weak_ptr,
                                    scoped_refptr<net::StringIOBuffer> buffer,
                                    int64_t offset,
                                    int length,
                                    Write2Callback callback,
                                    base::expected<base::ScopedFD, int> result);

  static void CallWriteTempFile(base::WeakPtr<ReadWriter> weak_ptr,
                                scoped_refptr<net::StringIOBuffer> buffer,
                                int64_t offset,
                                int length,
                                Write2Callback callback);

  static void OnRead(base::WeakPtr<ReadWriter> weak_ptr,
                     Read2Callback callback,
                     scoped_refptr<storage::FileSystemContext> fs_context,
                     std::unique_ptr<storage::FileStreamReader> fs_reader,
                     scoped_refptr<net::IOBuffer> buffer,
                     int64_t offset,
                     int length);

  static void OnWriteTempFile(base::WeakPtr<ReadWriter> weak_ptr,
                              Write2Callback callback,
                              WriteTempFileResult result);

  static void OnEOFFlushBeforeCallWriteDirect(
      base::WeakPtr<ReadWriter> weak_ptr,
      Write2Callback callback,
      scoped_refptr<storage::FileSystemContext> fs_context,
      scoped_refptr<net::IOBuffer> buffer,
      int64_t offset,
      int length,
      std::unique_ptr<storage::FileStreamWriter> fs_writer,
      int flush_posix_error_code);

  void CallWriteDirect(Write2Callback callback,
                       scoped_refptr<storage::FileSystemContext> fs_context,
                       std::unique_ptr<storage::FileStreamWriter> fs_writer,
                       scoped_refptr<net::IOBuffer> buffer,
                       int64_t offset,
                       int length);

  static void OnWriteDirect(
      base::WeakPtr<ReadWriter> weak_ptr,
      Write2Callback callback,
      scoped_refptr<storage::FileSystemContext> fs_context,
      std::unique_ptr<storage::FileStreamWriter> fs_writer,
      scoped_refptr<net::IOBuffer> buffer,
      int64_t offset,
      int length);

  const storage::FileSystemURL fs_url_;
  const std::string profile_path_;

  std::unique_ptr<storage::FileStreamReader> fs_reader_;
  // Unused (and set to -1) whenever fs_reader_ is nullptr. When std::move'ing
  // (or otherwise changing) the fs_reader_, we therefore assign (via = or
  // std::exchange) to read_offset_ at the same time.
  int64_t read_offset_ = -1;

  std::unique_ptr<storage::FileStreamWriter> fs_writer_;
  // Unused (and set to -1) whenever fs_writer_ is nullptr. When std::move'ing
  // (or otherwise changing) the fs_writer_, we therefore assign (via = or
  // std::exchange) to write_offset_ at the same time.
  int64_t write_offset_ = -1;

  scoped_refptr<storage::FileSystemContext> close2_fs_context_;
  Close2Callback close2_callback_;

  base::ScopedFD temp_file_;

  // The first (if any) write error we encounter. When non-zero, all future
  // Write calls fail and Save-on-Close is a no-op (other than running the
  // Close2Callback).
  int write_posix_error_code_ = 0;

  // True when the FD in |temp_file_| has been loaned out to a separate thread
  // (separate from the content::BrowserThread::IO thread that this lives on,
  // which should not be used for blocking I/O).
  bool is_loaning_temp_file_scoped_fd_ = false;

  bool is_in_flight_ = false;
  bool closed_ = false;
  bool created_temp_file_ = false;
  // storage::FileStreamWriter::Flush takes a storage::FlushMode parameter.
  // This bool field is about calling with FlushMode::kEndOfFile, not with
  // FlushMode::kDefault.
  bool fs_writer_needs_eof_flushing_ = false;

  const bool use_temp_file_;
  const bool temp_file_starts_with_copy_;

  base::WeakPtrFactory<ReadWriter> weak_ptr_factory_{this};
};

}  // namespace fusebox

#endif  // CHROME_BROWSER_ASH_FUSEBOX_FUSEBOX_READ_WRITER_H_