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