chromium/chrome/browser/ash/fusebox/fusebox_server.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_server.h"

#include <fcntl.h>
#include <sys/stat.h>

#include <string_view>
#include <utility>

#include "base/files/file_util.h"
#include "base/functional/callback.h"
#include "base/logging.h"
#include "base/metrics/histogram_functions.h"
#include "base/strings/escape.h"
#include "base/strings/strcat.h"
#include "base/strings/string_util.h"
#include "base/strings/stringprintf.h"
#include "base/task/bind_post_task.h"
#include "base/task/sequenced_task_runner.h"
#include "base/task/thread_pool.h"
#include "base/types/expected.h"
#include "chrome/browser/ash/file_manager/fileapi_util.h"
#include "chrome/browser/ash/file_manager/path_util.h"
#include "chrome/browser/ash/fileapi/file_system_backend.h"
#include "chrome/browser/ash/fusebox/fusebox_copy_to_fd.h"
#include "chrome/browser/ash/fusebox/fusebox_errno.h"
#include "chrome/browser/ash/fusebox/fusebox_histograms.h"
#include "chrome/browser/ash/fusebox/fusebox_read_writer.h"
#include "chrome/browser/profiles/profile_manager.h"
#include "content/public/browser/browser_task_traits.h"
#include "content/public/browser/browser_thread.h"
#include "net/base/io_buffer.h"
#include "storage/browser/file_system/async_file_util.h"
#include "storage/browser/file_system/copy_or_move_hook_delegate.h"
#include "storage/browser/file_system/external_mount_points.h"
#include "storage/browser/file_system/file_system_url.h"
#include "storage/common/file_system/file_system_util.h"
#include "third_party/cros_system_api/dbus/fusebox/dbus-constants.h"
#include "url/url_util.h"

// This file provides the "business logic" half of the FuseBox server, coupled
// with the "D-Bus protocol logic" half in fusebox_service_provider.cc.

namespace fusebox {

namespace {

Server* g_server_instance = nullptr;

template <typename CallbackType, typename ResponseProtoType>
CallbackType HistogramWrap(
    const HistogramEnumFileSystemType histogram_enum_file_system_type,
    const char* rpc_method_name,
    CallbackType callback) {
  static constexpr auto func =
      [](const char* file_system_type_name, const char* rpc_method_name,
         CallbackType wrappee, const ResponseProtoType& response) {
        const std::string histogram_name =
            base::StrCat({"FileBrowser.Fusebox.RPC.", file_system_type_name,
                          ".", rpc_method_name});

        int32_t posix_error_code =
            response.has_posix_error_code() ? response.posix_error_code() : 0;

        base::UmaHistogramEnumeration(
            histogram_name, GetHistogramEnumPosixErrorCode(posix_error_code));

        if (wrappee) {
          std::move(wrappee).Run(response);
        }
      };

  return base::BindOnce(
      func, NameForHistogramEnumFileSystemType(histogram_enum_file_system_type),
      rpc_method_name, std::move(callback));
}

bool UseTempFile(const std::string_view fs_url_as_string) {
  // MTP (the protocol) does not support incremental writes. When creating an
  // MTP file (via FuseBox), we need to supply its contents as a whole. Up
  // until that transfer, spool incremental writes to a temporary file.
  return base::StartsWith(fs_url_as_string,
                          file_manager::util::kFuseBoxSubdirPrefixMTP);
}

bool UseEmptyTruncateWorkaround(const std::string_view fs_url_as_string,
                                int64_t length) {
  // Not all storage::AsyncFileUtil back-ends implement the CreateFile or
  // Truncate methods. When they don't, and truncating to a zero length, work
  // around it as a RemoveFile followed by copying in an empty file.
  return (length == 0) &&
         base::StartsWith(fs_url_as_string,
                          file_manager::util::kFuseBoxSubdirPrefixMTP);
}

std::pair<std::string, bool> ResolvePrefixMap(
    const fusebox::Server::PrefixMap& prefix_map,
    const std::string& s) {
  size_t i = s.find('/');
  if (i == std::string::npos) {
    i = s.size();
  }
  auto iter = prefix_map.find(s.substr(0, i));
  if (iter == prefix_map.end()) {
    return std::make_pair("", false);
  }
  return std::make_pair(base::StrCat({iter->second.fs_url_prefix, s.substr(i)}),
                        iter->second.read_only);
}

// Parsed is the T in the base::expected<T, E> type returned by
// ParseFileSystemURL. It holds a storage::FileSystemContext, a
// storage::FileSystemURL and read-only-ness.
struct Parsed {
  Parsed(scoped_refptr<storage::FileSystemContext> fs_context_arg,
         storage::FileSystemURL fs_url_arg,
         bool read_only_arg);
  ~Parsed();

  scoped_refptr<storage::FileSystemContext> fs_context;
  const storage::FileSystemURL fs_url;
  const bool read_only;
};

Parsed::Parsed(scoped_refptr<storage::FileSystemContext> fs_context_arg,
               storage::FileSystemURL fs_url_arg,
               bool read_only_arg)
    : fs_context(std::move(fs_context_arg)),
      fs_url(std::move(fs_url_arg)),
      read_only(read_only_arg) {}

Parsed::~Parsed() = default;

struct ParseError {
  explicit ParseError(int posix_error_code_arg,
                      bool is_moniker_root_arg = false);

  const int posix_error_code;
  // is_moniker_root is used for the special case where the file_system_url is
  // fusebox::kMonikerSubdir (also known as "moniker"). There is no
  // storage::FileSystemURL registered for "moniker" (as opposed to for
  // "moniker/1234etc"), so ParseFileSystemURL (which returns a valid
  // storage::FileSystemURL on success) must return an error. However, Stat2 or
  // ReadDir2 on "moniker" should succeed (but return an empty directory).
  const bool is_moniker_root;
};

ParseError::ParseError(int posix_error_code_arg, bool is_moniker_root_arg)
    : posix_error_code(posix_error_code_arg),
      is_moniker_root(is_moniker_root_arg) {}

// All of the Server methods' take a protobuf argument. Many protobufs have a
// file_system_url field (a string). This function parses that string as a
// storage::FileSystemURL (resolving it in the context of the MonikerMap and
// PrefixMap) as well as finding the storage::FileSystemContext we will need to
// serve those methods.
base::expected<Parsed, ParseError> ParseFileSystemURL(
    const fusebox::MonikerMap& moniker_map,
    const fusebox::Server::PrefixMap& prefix_map,
    const std::string& fs_url_as_string) {
  scoped_refptr<storage::FileSystemContext> fs_context =
      file_manager::util::GetFileManagerFileSystemContext(
          ProfileManager::GetActiveUserProfile());
  if (fs_url_as_string.empty()) {
    LOG(ERROR) << "No FileSystemURL";
    return base::unexpected(ParseError(EINVAL));
  } else if (!fs_context) {
    LOG(ERROR) << "No FileSystemContext";
    return base::unexpected(ParseError(EFAULT));
  }

  // encoded is fs_url_as_string transformed such that "fsp.hash/x/y#z.txt"
  // becomes "fsp.hash/x%2Fy%23z.txt". The "#" in particular would otherwise be
  // problematic, since the conversion from string to GURL does not consider
  // the "#y.txt" part of the URL path, even though "#" is a valid character
  // for ChromeOS (Linux) file names.
  //
  // The initial "/" stays a slash, not a "%2F", since that is what
  // ResolvePrefixMap and MonikerMap::ExtractToken expects to find.
  std::string encoded;
  size_t slash = fs_url_as_string.find('/');
  if (slash == std::string::npos) {
    encoded = fs_url_as_string;
  } else {
    url::RawCanonOutputT<char> canon_output;
    url::EncodeURIComponent(fs_url_as_string.substr(slash + 1), &canon_output);
    encoded = base::StrCat(
        {fs_url_as_string.substr(0, slash + 1), canon_output.view()});
  }

  storage::FileSystemURL fs_url;
  bool read_only = false;

  // Intercept any moniker names and replace them by their linked target.
  using ResultType = fusebox::MonikerMap::ExtractTokenResult::ResultType;
  auto extract_token_result = fusebox::MonikerMap::ExtractToken(encoded);
  switch (extract_token_result.result_type) {
    case ResultType::OK: {
      auto resolved = moniker_map.Resolve(extract_token_result.token);
      if (!resolved.first.is_valid()) {
        LOG(ERROR) << "Unresolvable Moniker";
        return base::unexpected(ParseError(ENOENT));
      }
      fs_url = std::move(resolved.first);
      read_only = resolved.second;
      break;
    }
    case ResultType::NOT_A_MONIKER_FS_URL: {
      auto resolved = ResolvePrefixMap(prefix_map, encoded);
      if (resolved.first.empty()) {
        LOG(ERROR) << "Unresolvable Prefix";
        return base::unexpected(ParseError(ENOENT));
      }
      read_only = resolved.second;
      fs_url = fs_context->CrackURLInFirstPartyContext(GURL(resolved.first));
      if (!fs_url.is_valid()) {
        LOG(ERROR) << "Invalid FileSystemURL";
        return base::unexpected(ParseError(EINVAL));
      }
      break;
    }
    case ResultType::MONIKER_FS_URL_BUT_ONLY_ROOT: {
      return base::unexpected(ParseError(ENOENT, true));
    }
    case ResultType::MONIKER_FS_URL_BUT_NOT_WELL_FORMED:
      return base::unexpected(ParseError(ENOENT));
  }

  if (!ash::FileSystemBackend::Get(*fs_context)->CanHandleType(fs_url.type())) {
    LOG(ERROR) << "Backend cannot handle "
               << storage::GetFileSystemTypeString(fs_url.type());
    return base::unexpected(ParseError(EINVAL));
  }
  return Parsed(std::move(fs_context), std::move(fs_url), read_only);
}

// 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 FillInDirEntryProto(DirEntryProto* dir_entry_proto,
                         const base::File::Info& info,
                         bool read_only) {
  dir_entry_proto->set_mode_bits(
      Server::MakeModeBits(info.is_directory, read_only));
  // The base::File::Info comment says that info.size is "undefined when
  // info.is_directory is true".
  dir_entry_proto->set_size(info.is_directory ? 0 : info.size);
  dir_entry_proto->set_mtime(
      info.last_modified.ToDeltaSinceWindowsEpoch().InMicroseconds());
}

void RunCreateAndThenStatCallback(
    Server::CreateCallback callback,
    scoped_refptr<storage::FileSystemContext> fs_context,  // See § above.
    bool read_only,
    uint64_t fuse_handle,
    base::OnceClosure on_failure,
    base::File::Error error_code,
    const base::File::Info& info) {
  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);

  int posix_error_code = FileErrorToErrno(error_code);
  if (posix_error_code) {
    std::move(on_failure).Run();
    CreateResponseProto response_proto;
    response_proto.set_posix_error_code(posix_error_code);
    std::move(callback).Run(response_proto);
    return;
  }

  CreateResponseProto response_proto;
  response_proto.set_fuse_handle(fuse_handle);
  FillInDirEntryProto(response_proto.mutable_stat(), info, read_only);
  std::move(callback).Run(response_proto);
}

void RunCreateCallback(
    Server::CreateCallback callback,
    scoped_refptr<storage::FileSystemContext> fs_context,  // See § above.
    storage::FileSystemURL fs_url,
    bool read_only,
    uint64_t fuse_handle,
    base::OnceClosure on_failure,
    base::File::Error error_code) {
  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);

  int posix_error_code = FileErrorToErrno(error_code);
  if (posix_error_code) {
    std::move(on_failure).Run();
    CreateResponseProto response_proto;
    response_proto.set_posix_error_code(posix_error_code);
    std::move(callback).Run(response_proto);
    return;
  }

  constexpr storage::FileSystemOperation::GetMetadataFieldSet metadata_fields =
      {storage::FileSystemOperation::GetMetadataField::kIsDirectory,
       storage::FileSystemOperation::GetMetadataField::kSize,
       storage::FileSystemOperation::GetMetadataField::kLastModified};

  auto outer_callback = base::BindPostTaskToCurrentDefault(base::BindOnce(
      &RunCreateAndThenStatCallback, std::move(callback), fs_context, read_only,
      fuse_handle, std::move(on_failure)));

  content::GetIOThreadTaskRunner({})->PostTask(
      FROM_HERE,
      base::BindOnce(
          base::IgnoreResult(&storage::FileSystemOperationRunner::GetMetadata),
          // Unretained is safe: fs_context owns its operation_runner.
          base::Unretained(fs_context->operation_runner()), fs_url,
          metadata_fields, std::move(outer_callback)));
}

void RunMkDirAndThenStatCallback(
    Server::MkDirCallback callback,
    scoped_refptr<storage::FileSystemContext> fs_context,  // See § above.
    bool read_only,
    base::File::Error error_code,
    const base::File::Info& info) {
  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);

  int posix_error_code = FileErrorToErrno(error_code);
  if (posix_error_code) {
    MkDirResponseProto response_proto;
    response_proto.set_posix_error_code(posix_error_code);
    std::move(callback).Run(response_proto);
    return;
  }

  MkDirResponseProto response_proto;
  FillInDirEntryProto(response_proto.mutable_stat(), info, read_only);
  std::move(callback).Run(response_proto);
}

void RunMkDirCallback(
    Server::MkDirCallback callback,
    scoped_refptr<storage::FileSystemContext> fs_context,  // See § above.
    storage::FileSystemURL fs_url,
    bool read_only,
    base::File::Error error_code) {
  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);

  int posix_error_code = FileErrorToErrno(error_code);
  if (posix_error_code) {
    MkDirResponseProto response_proto;
    response_proto.set_posix_error_code(posix_error_code);
    std::move(callback).Run(response_proto);
    return;
  }

  constexpr storage::FileSystemOperation::GetMetadataFieldSet metadata_fields =
      {storage::FileSystemOperation::GetMetadataField::kIsDirectory,
       storage::FileSystemOperation::GetMetadataField::kSize,
       storage::FileSystemOperation::GetMetadataField::kLastModified};

  auto outer_callback = base::BindPostTaskToCurrentDefault(
      base::BindOnce(&RunMkDirAndThenStatCallback, std::move(callback),
                     fs_context, read_only));

  content::GetIOThreadTaskRunner({})->PostTask(
      FROM_HERE,
      base::BindOnce(
          base::IgnoreResult(&storage::FileSystemOperationRunner::GetMetadata),
          // Unretained is safe: fs_context owns its operation_runner.
          base::Unretained(fs_context->operation_runner()), fs_url,
          metadata_fields, std::move(outer_callback)));
}

void RunRenameCallbackPosixErrorCode(
    Server::RenameCallback callback,
    scoped_refptr<storage::FileSystemContext> fs_context,  // See § above.
    int posix_error_code) {
  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);

  if (posix_error_code) {
    RenameResponseProto response_proto;
    response_proto.set_posix_error_code(posix_error_code);
    std::move(callback).Run(response_proto);
    return;
  }

  RenameResponseProto response_proto;
  std::move(callback).Run(response_proto);
}

void RunRenameCallbackBaseFileError(
    Server::RenameCallback callback,
    scoped_refptr<storage::FileSystemContext> fs_context,  // See § above.
    base::File::Error error_code) {
  RunRenameCallbackPosixErrorCode(std::move(callback), std::move(fs_context),
                                  FileErrorToErrno(error_code));
}

void RunRmDirCallback(
    Server::RmDirCallback callback,
    scoped_refptr<storage::FileSystemContext> fs_context,  // See § above.
    base::File::Error error_code) {
  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);

  int posix_error_code = FileErrorToErrno(error_code);
  if (posix_error_code) {
    RmDirResponseProto response_proto;
    response_proto.set_posix_error_code(posix_error_code);
    std::move(callback).Run(response_proto);
    return;
  }

  RmDirResponseProto response_proto;
  std::move(callback).Run(response_proto);
}

void RunTruncateAndThenStatCallback(
    Server::TruncateCallback callback,
    scoped_refptr<storage::FileSystemContext> fs_context,  // See § above.
    bool read_only,
    base::File::Error error_code,
    const base::File::Info& info) {
  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);

  int posix_error_code = FileErrorToErrno(error_code);
  if (posix_error_code) {
    TruncateResponseProto response_proto;
    response_proto.set_posix_error_code(posix_error_code);
    std::move(callback).Run(response_proto);
    return;
  }

  TruncateResponseProto response_proto;
  FillInDirEntryProto(response_proto.mutable_stat(), info, read_only);
  std::move(callback).Run(response_proto);
}

void RunTruncateCallback(
    Server::TruncateCallback callback,
    scoped_refptr<storage::FileSystemContext> fs_context,  // See § above.
    storage::FileSystemURL fs_url,
    bool read_only,
    base::File::Error error_code) {
  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);

  int posix_error_code = FileErrorToErrno(error_code);
  if (posix_error_code) {
    TruncateResponseProto response_proto;
    response_proto.set_posix_error_code(posix_error_code);
    std::move(callback).Run(response_proto);
    return;
  }

  constexpr storage::FileSystemOperation::GetMetadataFieldSet metadata_fields =
      {storage::FileSystemOperation::GetMetadataField::kIsDirectory,
       storage::FileSystemOperation::GetMetadataField::kSize,
       storage::FileSystemOperation::GetMetadataField::kLastModified};

  auto outer_callback = base::BindPostTaskToCurrentDefault(
      base::BindOnce(&RunTruncateAndThenStatCallback, std::move(callback),
                     fs_context, read_only));

  content::GetIOThreadTaskRunner({})->PostTask(
      FROM_HERE,
      base::BindOnce(
          base::IgnoreResult(&storage::FileSystemOperationRunner::GetMetadata),
          // Unretained is safe: fs_context owns its operation_runner.
          base::Unretained(fs_context->operation_runner()), fs_url,
          metadata_fields, std::move(outer_callback)));
}

void RunUnlinkCallback(
    Server::UnlinkCallback callback,
    scoped_refptr<storage::FileSystemContext> fs_context,  // See § above.
    base::File::Error error_code) {
  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);

  int posix_error_code = FileErrorToErrno(error_code);
  if (posix_error_code) {
    UnlinkResponseProto response_proto;
    response_proto.set_posix_error_code(posix_error_code);
    std::move(callback).Run(response_proto);
    return;
  }

  UnlinkResponseProto response_proto;
  std::move(callback).Run(response_proto);
}

void RunStat2Callback(
    Server::Stat2Callback callback,
    scoped_refptr<storage::FileSystemContext> fs_context,  // See § above.
    bool read_only,
    base::File::Error error_code,
    const base::File::Info& info) {
  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);

  int posix_error_code = FileErrorToErrno(error_code);
  if (posix_error_code) {
    Stat2ResponseProto response_proto;
    response_proto.set_posix_error_code(posix_error_code);
    std::move(callback).Run(response_proto);
    return;
  }

  Stat2ResponseProto response_proto;
  FillInDirEntryProto(response_proto.mutable_stat(), info, read_only);
  std::move(callback).Run(response_proto);
}

std::string SubdirForTempDir(base::ScopedTempDir& scoped_temp_dir) {
  std::string basename = scoped_temp_dir.GetPath().BaseName().AsUTF8Unsafe();
  while (!basename.empty() && (basename[0] == '.')) {  // Strip leading dots.
    basename = basename.substr(1);
  }
  return base::StrCat({file_manager::util::kFuseBoxSubdirPrefixTMP, basename});
}

void EmptyTruncateWorkaroundCallback2(Server::TruncateCallback callback,
                                      base::File::Error error_code) {
  DCHECK_CURRENTLY_ON(content::BrowserThread::IO);

  TruncateResponseProto response_proto;
  if (error_code != base::File::Error::FILE_OK) {
    response_proto.set_posix_error_code(FileErrorToErrno(error_code));
  } else {
    DirEntryProto* dir_entry_proto = response_proto.mutable_stat();
    constexpr bool is_directory = false;
    constexpr bool read_only = false;
    dir_entry_proto->set_mode_bits(
        Server::MakeModeBits(is_directory, read_only));
    dir_entry_proto->set_size(0);
    dir_entry_proto->set_mtime(
        base::Time::Now().ToDeltaSinceWindowsEpoch().InMicroseconds());
  }
  content::GetUIThreadTaskRunner({})->PostTask(
      FROM_HERE,
      base::BindOnce(std::move(callback), std::move(response_proto)));
}

void EmptyTruncateWorkaroundCallback1(
    scoped_refptr<storage::FileSystemContext> fs_context,
    const storage::FileSystemURL fs_url,
    Server::TruncateCallback callback,
    base::File::Error error_code) {
  DCHECK_CURRENTLY_ON(content::BrowserThread::IO);

  if (error_code != base::File::Error::FILE_OK) {
    EmptyTruncateWorkaroundCallback2(std::move(callback), error_code);
    return;
  }
  fs_context->operation_runner()->CopyInForeignFile(
      base::FilePath("/dev/null"), fs_url,
      base::BindOnce(&EmptyTruncateWorkaroundCallback2, std::move(callback)));
}

void DoEmptyTruncateWorkaround(
    scoped_refptr<storage::FileSystemContext> fs_context,
    const storage::FileSystemURL fs_url,
    Server::TruncateCallback callback) {
  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);

  content::GetIOThreadTaskRunner({})->PostTask(
      FROM_HERE,
      base::BindOnce(
          base::IgnoreResult(&storage::FileSystemOperationRunner::RemoveFile),
          // Unretained is safe: fs_context owns its operation runner.
          base::Unretained(fs_context->operation_runner()), fs_url,
          base::BindOnce(&EmptyTruncateWorkaroundCallback1, fs_context, fs_url,
                         std::move(callback))));
}

void CrossFileSystemRenameCallback3(
    scoped_refptr<storage::FileSystemContext> fs_context,
    base::OnceCallback<void(int posix_error_code)> callback,
    base::File::Error error_code) {
  DCHECK_CURRENTLY_ON(content::BrowserThread::IO);

  std::move(callback).Run(FileErrorToErrno(error_code));
}

void CrossFileSystemRenameCallback2(
    base::ScopedFD scoped_fd,
    scoped_refptr<storage::FileSystemContext> fs_context,
    const storage::FileSystemURL src_fs_url,
    base::OnceCallback<void(int posix_error_code)> callback,
    base::File::Error error_code) {
  DCHECK_CURRENTLY_ON(content::BrowserThread::IO);

  if (error_code != base::File::FILE_OK) {
    std::move(callback).Run(FileErrorToErrno(error_code));
    return;
  }

  fs_context->operation_runner()->RemoveFile(
      src_fs_url, base::BindOnce(&CrossFileSystemRenameCallback3, fs_context,
                                 std::move(callback)));
}

void CrossFileSystemRenameCallback1(
    scoped_refptr<storage::FileSystemContext> fs_context,
    const storage::FileSystemURL src_fs_url,
    const storage::FileSystemURL dst_fs_url,
    base::OnceCallback<void(int posix_error_code)> callback,
    base::expected<base::ScopedFD, int> result) {
  DCHECK_CURRENTLY_ON(content::BrowserThread::IO);

  if (!result.has_value()) {
    std::move(callback).Run(result.error());
    return;
  }

  std::string fd_path =
      base::StringPrintf("/proc/self/fd/%d", result.value().get());

  fs_context->operation_runner()->CopyInForeignFile(
      base::FilePath(fd_path), dst_fs_url,
      base::BindOnce(&CrossFileSystemRenameCallback2, std::move(result.value()),
                     fs_context, std::move(src_fs_url), std::move(callback)));
}

// Implement a cross-file-system rename (a move) as three steps:
// 1. CopyToFileDescriptor, from src to an O_TMPFILE file.
// 2. CopyInForeignFile, from the O_TMPFILE file to dst.
// 3. RemoveFile, of the src. The base::ScopedFD destructor will delete (both
//    in the C++ sense and in the file system sense) the O_TMPFILE file.
void DoCrossFileSystemRename(
    scoped_refptr<storage::FileSystemContext> fs_context,
    std::string profile_path,
    const storage::FileSystemURL src_fs_url,
    const storage::FileSystemURL dst_fs_url,
    base::OnceCallback<void(int posix_error_code)> callback) {
  DCHECK_CURRENTLY_ON(content::BrowserThread::IO);

  base::ScopedFD temp_file(open(profile_path.c_str(),
                                O_CLOEXEC | O_EXCL | O_TMPFILE | O_RDWR, 0600));
  if (!temp_file.is_valid()) {
    std::move(callback).Run(ENOSPC);
    return;
  }
  CopyToFileDescriptor(
      fs_context, src_fs_url, std::move(temp_file),
      base::BindOnce(&CrossFileSystemRenameCallback1, fs_context, src_fs_url,
                     std::move(dst_fs_url), std::move(callback)));
}

}  // namespace

Server::FuseFileMapEntry::FuseFileMapEntry(
    scoped_refptr<storage::FileSystemContext> fs_context_arg,
    storage::FileSystemURL fs_url_arg,
    const std::string& profile_path_arg,
    bool readable_arg,
    bool writable_arg,
    bool use_temp_file_arg,
    bool temp_file_starts_with_copy_arg)
    : fs_context_(fs_context_arg),
      histogram_enum_file_system_type_(
          GetHistogramEnumFileSystemType(fs_url_arg)),
      readable_(readable_arg),
      writable_(writable_arg),
      seqbnd_read_writer_(content::GetIOThreadTaskRunner({}),
                          fs_url_arg,
                          profile_path_arg,
                          use_temp_file_arg,
                          temp_file_starts_with_copy_arg) {}

Server::FuseFileMapEntry::FuseFileMapEntry(FuseFileMapEntry&&) = default;

Server::FuseFileMapEntry::~FuseFileMapEntry() = default;

void Server::FuseFileMapEntry::DoFlush(const FlushRequestProto& request,
                                       Server::FlushCallback callback) {
  seqbnd_read_writer_.AsyncCall(&ReadWriter::Flush)
      .WithArgs(fs_context_, std::move(callback));
}

void Server::FuseFileMapEntry::DoRead2(const Read2RequestProto& request,
                                       Server::Read2Callback callback) {
  int64_t offset = request.has_offset() ? request.offset() : 0;
  int64_t length = request.has_length() ? request.length() : 0;
  seqbnd_read_writer_.AsyncCall(&ReadWriter::Read)
      .WithArgs(fs_context_, offset, length, std::move(callback));
}

void Server::FuseFileMapEntry::DoWrite2(const Write2RequestProto& request,
                                        Server::Write2Callback callback) {
  if (!request.has_data() || request.data().empty()) {
    Write2ResponseProto response_proto;
    std::move(callback).Run(response_proto);
    return;
  }
  scoped_refptr<net::StringIOBuffer> buffer =
      base::MakeRefCounted<net::StringIOBuffer>(request.data());
  int64_t offset = request.has_offset() ? request.offset() : 0;
  seqbnd_read_writer_.AsyncCall(&ReadWriter::Write)
      .WithArgs(fs_context_, std::move(buffer), offset,
                static_cast<int>(request.data().size()), std::move(callback));
}

void Server::FuseFileMapEntry::Do(PendingOp& op,
                                  base::WeakPtr<Server> weak_ptr_server,
                                  uint64_t fuse_handle) {
  if (absl::holds_alternative<PendingFlush>(op)) {
    PendingFlush& pending = absl::get<PendingFlush>(op);
    DoFlush(pending.first,
            base::BindOnce(&Server::OnFlush, weak_ptr_server, fuse_handle,
                           std::move(pending.second)));
  } else if (absl::holds_alternative<PendingRead2>(op)) {
    PendingRead2& pending = absl::get<PendingRead2>(op);
    DoRead2(pending.first,
            base::BindOnce(&Server::OnRead2, weak_ptr_server, fuse_handle,
                           std::move(pending.second)));
  } else if (absl::holds_alternative<PendingWrite2>(op)) {
    PendingWrite2& pending = absl::get<PendingWrite2>(op);
    DoWrite2(pending.first,
             base::BindOnce(&Server::OnWrite2, weak_ptr_server, fuse_handle,
                            std::move(pending.second)));
  } else {
    NOTREACHED_IN_MIGRATION();
  }
}

Server::PrefixMapEntry::PrefixMapEntry(std::string fs_url_prefix_arg,
                                       bool read_only_arg)
    : fs_url_prefix(fs_url_prefix_arg), read_only(read_only_arg) {}

Server::ReadDir2MapEntry::ReadDir2MapEntry(Server::ReadDir2Callback callback)
    : callback_(std::move(callback)) {}

Server::ReadDir2MapEntry::ReadDir2MapEntry(ReadDir2MapEntry&&) = default;

Server::ReadDir2MapEntry::~ReadDir2MapEntry() = default;

bool Server::ReadDir2MapEntry::Reply(uint64_t cookie,
                                     ReadDir2Callback callback) {
  if (callback) {
    if (callback_) {
      ReadDir2ResponseProto response_proto;
      response_proto.set_posix_error_code(EINVAL);
      std::move(callback_).Run(response_proto);
    }
    callback_ = std::move(callback);
  } else if (!callback_) {
    return false;
  }

  if (posix_error_code_ != 0) {
    response_.set_posix_error_code(posix_error_code_);
  } else if (has_more_) {
    if (response_.entries().empty()) {
      // If we have nothing of interest to say (has_more_ is true but we have
      // no entries yet) other than a non-zero cookie, there's no need to send
      // a response. Otherwise, a non-zero cookie means that the RPC client
      // will just immediately send another request, and we'll immediately send
      // another empty response, and they'll immediately send another request,
      // etc., burning CPU bouncing back and forth until we finally have some
      // entries to send (or hit an error, or has_more_ becomes false).
      return false;
    }
    response_.set_cookie(cookie);
  }
  std::move(callback_).Run(std::move(response_));
  response_ = ReadDir2ResponseProto();
  return (posix_error_code_ != 0) || !has_more_;
}

// static
Server* Server::GetInstance() {
  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);

  return g_server_instance;
}

// static
uint32_t Server::MakeModeBits(bool is_directory, bool read_only) {
  uint32_t mode_bits = is_directory
                           ? (S_IFDIR | 0110)  // 0110 are the "--x--x---" bits.
                           : S_IFREG;
  mode_bits |= read_only ? 0440 : 0660;  // "r--r-----" versus "rw-rw----".
  return mode_bits;
}

Server::Server(Delegate* delegate) : delegate_(delegate) {
  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
  DCHECK(!g_server_instance);
  g_server_instance = this;
}

Server::~Server() {
  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
  DCHECK(g_server_instance);
  g_server_instance = nullptr;
}

fusebox::Moniker Server::CreateMoniker(const storage::FileSystemURL& target,
                                       bool read_only) {
  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);

  return moniker_map_.CreateMoniker(target, read_only);
}

void Server::DestroyMoniker(fusebox::Moniker moniker) {
  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);

  moniker_map_.DestroyMoniker(moniker);
}

void Server::RegisterFSURLPrefix(const std::string& subdir,
                                 const std::string& fs_url_prefix,
                                 bool read_only) {
  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);

  if (subdir.find('/') != std::string::npos) {
    LOG(ERROR) << "Invalid subdir: " << subdir;
    return;
  }
  std::string trimmed =
      std::string(base::TrimString(fs_url_prefix, "/", base::TRIM_TRAILING));
  prefix_map_.insert({subdir, PrefixMapEntry(trimmed, read_only)});
  if (delegate_) {
    delegate_->OnRegisterFSURLPrefix(subdir);
  }
}

void Server::UnregisterFSURLPrefix(const std::string& subdir) {
  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);

  auto iter = prefix_map_.find(subdir);
  if (iter != prefix_map_.end()) {
    prefix_map_.erase(iter);
  }
  if (delegate_) {
    delegate_->OnUnregisterFSURLPrefix(subdir);
  }
}

storage::FileSystemURL Server::ResolveFilename(Profile* profile,
                                               const std::string& filename) {
  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);

  if (!base::StartsWith(filename, file_manager::util::kFuseBoxMediaSlashPath)) {
    return storage::FileSystemURL();
  }
  auto resolved = ResolvePrefixMap(
      prefix_map_,
      filename.substr(strlen(file_manager::util::kFuseBoxMediaSlashPath)));
  if (resolved.first.empty()) {
    return storage::FileSystemURL();
  }
  return file_manager::util::GetFileManagerFileSystemContext(profile)
      ->CrackURLInFirstPartyContext(GURL(resolved.first));
}

base::FilePath Server::InverseResolveFSURL(
    const storage::FileSystemURL& fs_url) {
  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
  std::string fs_url_as_string = fs_url.ToGURL().spec();

  // Find the longest registered (in the "called Server::RegisterFSURLPrefix"
  // sense) FileSystemURL that is a prefix of fs_url.
  size_t best_size = 0;
  std::string_view best_subdir;
  for (const auto& i : prefix_map_) {
    if ((best_size < i.second.fs_url_prefix.size()) &&
        base::StartsWith(fs_url_as_string, i.second.fs_url_prefix)) {
      best_size = i.second.fs_url_prefix.size();
      best_subdir = i.first;
    }
  }

  if (best_size > 0) {
    const std::string relative_path = base::UnescapeURLComponent(
        fs_url_as_string.substr(best_size),
        base::UnescapeRule::SPACES |
            base::UnescapeRule::URL_SPECIAL_CHARS_EXCEPT_PATH_SEPARATORS);
    return storage::StringToFilePath(
        base::StrCat({file_manager::util::kFuseBoxMediaSlashPath, best_subdir,
                      relative_path}));
  }

  return base::FilePath();
}

void Server::GetDebugJSONForKey(
    std::string_view key,
    base::OnceCallback<void(JSONKeyValuePair)> callback) {
  base::Value::Dict subdirs;
  subdirs.Set(kMonikerSubdir, base::Value("[special]"));
  for (const auto& i : prefix_map_) {
    subdirs.Set(i.first,
                base::Value(base::StrCat(
                    {i.second.fs_url_prefix,
                     i.second.read_only ? " (read-only)" : " (read-write)"})));
  }

  base::Value::Dict dict;
  dict.Set("monikers", moniker_map_.GetDebugJSON());
  dict.Set("subdirs", std::move(subdirs));
  std::move(callback).Run(std::make_pair(key, base::Value(std::move(dict))));
}

void Server::Close2(const Close2RequestProto& request_proto,
                    Close2Callback callback) {
  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);

  uint64_t fuse_handle =
      request_proto.has_fuse_handle() ? request_proto.fuse_handle() : 0;
  auto iter = fuse_file_map_.find(fuse_handle);
  if (iter == fuse_file_map_.end()) {
    Close2ResponseProto response_proto;
    response_proto.set_posix_error_code(ENOENT);
    std::move(callback).Run(response_proto);
    return;
  }
  callback = HistogramWrap<Close2Callback, Close2ResponseProto>(
      iter->second.histogram_enum_file_system_type_, "Close2",
      std::move(callback));
  FuseFileMapEntry& entry = iter->second;
  base::circular_deque<PendingOp> pending_ops = std::move(entry.pending_ops_);
  entry.seqbnd_read_writer_.AsyncCall(&ReadWriter::Close)
      .WithArgs(entry.fs_context_, std::move(callback));

  fuse_file_map_.erase(iter);

  for (auto& pending_op : pending_ops) {
    if (absl::holds_alternative<PendingRead2>(pending_op)) {
      Read2ResponseProto read2_response_proto;
      read2_response_proto.set_posix_error_code(EBUSY);
      std::move(absl::get<PendingRead2>(pending_op).second)
          .Run(read2_response_proto);
    } else if (absl::holds_alternative<PendingWrite2>(pending_op)) {
      Write2ResponseProto write2_response_proto;
      write2_response_proto.set_posix_error_code(EBUSY);
      std::move(absl::get<PendingWrite2>(pending_op).second)
          .Run(write2_response_proto);
    } else {
      NOTREACHED_IN_MIGRATION();
    }
  }
}

void Server::Create(const CreateRequestProto& request_proto,
                    CreateCallback callback) {
  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);

  std::string fs_url_as_string = request_proto.has_file_system_url()
                                     ? request_proto.file_system_url()
                                     : std::string();

  auto parsed = ParseFileSystemURL(moniker_map_, prefix_map_, fs_url_as_string);
  if (!parsed.has_value()) {
    CreateResponseProto response_proto;
    response_proto.set_posix_error_code(parsed.error().posix_error_code);
    std::move(callback).Run(response_proto);
    return;
  }
  callback = HistogramWrap<CreateCallback, CreateResponseProto>(
      GetHistogramEnumFileSystemType(parsed->fs_url), "Create",
      std::move(callback));
  if (parsed->read_only) {
    CreateResponseProto response_proto;
    response_proto.set_posix_error_code(EACCES);
    std::move(callback).Run(response_proto);
    return;
  }

  constexpr bool readable = true;
  constexpr bool writable = true;
  bool use_temp_file = writable && UseTempFile(fs_url_as_string);

  uint64_t fuse_handle = InsertFuseFileMapEntry(FuseFileMapEntry(
      parsed->fs_context, parsed->fs_url,
      use_temp_file
          ? ProfileManager::GetActiveUserProfile()->GetPath().AsUTF8Unsafe()
          : std::string(),
      readable, writable, use_temp_file, false));

  if (use_temp_file) {
    base::Time now = base::Time::Now();
    base::File::Info info;
    info.last_modified = now;
    info.last_accessed = now;
    info.creation_time = now;
    CreateResponseProto response_proto;
    response_proto.set_fuse_handle(fuse_handle);
    FillInDirEntryProto(response_proto.mutable_stat(), info, parsed->read_only);
    std::move(callback).Run(response_proto);
    return;
  }

  auto on_failure = base::BindOnce(&Server::EraseFuseFileMapEntry,
                                   weak_ptr_factory_.GetWeakPtr(), fuse_handle);

  auto outer_callback = base::BindPostTaskToCurrentDefault(base::BindOnce(
      &RunCreateCallback, std::move(callback), parsed->fs_context,
      parsed->fs_url, parsed->read_only, fuse_handle, std::move(on_failure)));

  constexpr bool exclusive = true;
  content::GetIOThreadTaskRunner({})->PostTask(
      FROM_HERE,
      base::BindOnce(
          base::IgnoreResult(&storage::FileSystemOperationRunner::CreateFile),
          // Unretained is safe: fs_context owns its operation runner.
          base::Unretained(parsed->fs_context->operation_runner()),
          parsed->fs_url, exclusive, std::move(outer_callback)));
}

void Server::Flush(const FlushRequestProto& request_proto,
                   FlushCallback callback) {
  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);

  uint64_t fuse_handle =
      request_proto.has_fuse_handle() ? request_proto.fuse_handle() : 0;
  auto iter = fuse_file_map_.find(fuse_handle);
  if (iter == fuse_file_map_.end()) {
    FlushResponseProto response_proto;
    response_proto.set_posix_error_code(ENOENT);
    std::move(callback).Run(response_proto);
    return;
  }
  callback = HistogramWrap<FlushCallback, FlushResponseProto>(
      iter->second.histogram_enum_file_system_type_, "Flush",
      std::move(callback));
  if (!iter->second.writable_) {
    FlushResponseProto response_proto;
    response_proto.set_posix_error_code(EACCES);
    std::move(callback).Run(response_proto);
    return;
  } else if (iter->second.has_in_flight_op_) {
    iter->second.pending_ops_.emplace_back(
        PendingFlush(request_proto, std::move(callback)));
    return;
  }
  iter->second.has_in_flight_op_ = true;
  iter->second.DoFlush(
      request_proto,
      base::BindOnce(&Server::OnFlush, weak_ptr_factory_.GetWeakPtr(),
                     fuse_handle, std::move(callback)));
}

void Server::MkDir(const MkDirRequestProto& request_proto,
                   MkDirCallback callback) {
  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);

  std::string fs_url_as_string = request_proto.has_file_system_url()
                                     ? request_proto.file_system_url()
                                     : std::string();

  auto parsed = ParseFileSystemURL(moniker_map_, prefix_map_, fs_url_as_string);
  if (!parsed.has_value()) {
    MkDirResponseProto response_proto;
    response_proto.set_posix_error_code(parsed.error().posix_error_code);
    std::move(callback).Run(response_proto);
    return;
  }
  callback = HistogramWrap<MkDirCallback, MkDirResponseProto>(
      GetHistogramEnumFileSystemType(parsed->fs_url), "MkDir",
      std::move(callback));
  if (parsed->read_only) {
    MkDirResponseProto response_proto;
    response_proto.set_posix_error_code(EACCES);
    std::move(callback).Run(response_proto);
    return;
  }

  auto outer_callback = base::BindPostTaskToCurrentDefault(
      base::BindOnce(&RunMkDirCallback, std::move(callback), parsed->fs_context,
                     parsed->fs_url, parsed->read_only));

  constexpr bool exclusive = true;
  constexpr bool recursive = false;
  content::GetIOThreadTaskRunner({})->PostTask(
      FROM_HERE,
      base::BindOnce(
          base::IgnoreResult(
              &storage::FileSystemOperationRunner::CreateDirectory),
          // Unretained is safe: fs_context owns its operation runner.
          base::Unretained(parsed->fs_context->operation_runner()),
          parsed->fs_url, exclusive, recursive, std::move(outer_callback)));
}

void Server::Open2(const Open2RequestProto& request_proto,
                   Open2Callback callback) {
  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);

  std::string fs_url_as_string = request_proto.has_file_system_url()
                                     ? request_proto.file_system_url()
                                     : std::string();
  AccessMode access_mode = request_proto.has_access_mode()
                               ? request_proto.access_mode()
                               : AccessMode::NO_ACCESS;

  auto parsed = ParseFileSystemURL(moniker_map_, prefix_map_, fs_url_as_string);
  if (!parsed.has_value()) {
    Open2ResponseProto response_proto;
    response_proto.set_posix_error_code(parsed.error().posix_error_code);
    std::move(callback).Run(response_proto);
    return;
  }
  callback = HistogramWrap<Open2Callback, Open2ResponseProto>(
      GetHistogramEnumFileSystemType(parsed->fs_url), "Open2",
      std::move(callback));

  bool readable = (access_mode == AccessMode::READ_ONLY) ||
                  (access_mode == AccessMode::READ_WRITE);
  bool writable =
      !parsed->read_only && ((access_mode == AccessMode::WRITE_ONLY) ||
                             (access_mode == AccessMode::READ_WRITE));
  bool use_temp_file = writable && UseTempFile(fs_url_as_string);

  uint64_t fuse_handle = InsertFuseFileMapEntry(FuseFileMapEntry(
      std::move(parsed->fs_context), parsed->fs_url,
      use_temp_file
          ? ProfileManager::GetActiveUserProfile()->GetPath().AsUTF8Unsafe()
          : std::string(),
      readable, writable, use_temp_file, true));

  Open2ResponseProto response_proto;
  response_proto.set_fuse_handle(fuse_handle);
  std::move(callback).Run(response_proto);
}

void Server::Read2(const Read2RequestProto& request_proto,
                   Read2Callback callback) {
  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);

  uint64_t fuse_handle =
      request_proto.has_fuse_handle() ? request_proto.fuse_handle() : 0;
  auto iter = fuse_file_map_.find(fuse_handle);
  if (iter == fuse_file_map_.end()) {
    Read2ResponseProto response_proto;
    response_proto.set_posix_error_code(ENOENT);
    std::move(callback).Run(response_proto);
    return;
  }
  callback = HistogramWrap<Read2Callback, Read2ResponseProto>(
      iter->second.histogram_enum_file_system_type_, "Read2",
      std::move(callback));
  if (!iter->second.readable_) {
    Read2ResponseProto response_proto;
    response_proto.set_posix_error_code(EACCES);
    std::move(callback).Run(response_proto);
    return;
  } else if (iter->second.has_in_flight_op_) {
    iter->second.pending_ops_.emplace_back(
        PendingRead2(request_proto, std::move(callback)));
    return;
  }
  iter->second.has_in_flight_op_ = true;
  iter->second.DoRead2(
      request_proto,
      base::BindOnce(&Server::OnRead2, weak_ptr_factory_.GetWeakPtr(),
                     fuse_handle, std::move(callback)));
}

void Server::ReadDir2(const ReadDir2RequestProto& request_proto,
                      ReadDir2Callback callback) {
  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);

  std::string fs_url_as_string = request_proto.has_file_system_url()
                                     ? request_proto.file_system_url()
                                     : std::string();
  uint64_t cookie = request_proto.has_cookie() ? request_proto.cookie() : 0;
  int32_t cancel_error_code = request_proto.has_cancel_error_code()
                                  ? request_proto.cancel_error_code()
                                  : 0;

  auto parsed = ParseFileSystemURL(moniker_map_, prefix_map_, fs_url_as_string);
  if (!parsed.has_value()) {
    ReadDir2ResponseProto response_proto;
    if (parsed.error().is_moniker_root) {
      response_proto.set_posix_error_code(0);
    } else {
      response_proto.set_posix_error_code(parsed.error().posix_error_code);
    }
    std::move(callback).Run(response_proto);
    return;
  }
  callback = HistogramWrap<ReadDir2Callback, ReadDir2ResponseProto>(
      GetHistogramEnumFileSystemType(parsed->fs_url), "ReadDir2",
      std::move(callback));
  if (cancel_error_code) {
    ReadDir2ResponseProto response_proto;
    response_proto.set_posix_error_code(cancel_error_code);
    std::move(callback).Run(response_proto);
    return;
  }

  if (cookie) {
    auto iter = read_dir_2_map_.find(cookie);
    if (iter == read_dir_2_map_.end()) {
      ReadDir2ResponseProto response_proto;
      response_proto.set_posix_error_code(EINVAL);
      std::move(callback).Run(response_proto);
    } else if (iter->second.Reply(cookie, std::move(callback))) {
      read_dir_2_map_.erase(iter);
    }
    return;
  }

  static uint64_t next_cookie = 0;
  cookie = ++next_cookie;
  read_dir_2_map_.insert({cookie, ReadDir2MapEntry(std::move(callback))});

  auto outer_callback = base::BindPostTaskToCurrentDefault(base::BindRepeating(
      &Server::OnReadDirectory, weak_ptr_factory_.GetWeakPtr(),
      parsed->fs_context, parsed->read_only, cookie));

  content::GetIOThreadTaskRunner({})->PostTask(
      FROM_HERE,
      base::BindRepeating(
          base::IgnoreResult(
              &storage::FileSystemOperationRunner::ReadDirectory),
          // Unretained is safe: fs_context owns its operation_runner.
          base::Unretained(parsed->fs_context->operation_runner()),
          parsed->fs_url, std::move(outer_callback)));
}

void Server::Rename(const RenameRequestProto& request_proto,
                    RenameCallback callback) {
  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);

  std::string src_fs_url_as_string = request_proto.has_src_file_system_url()
                                         ? request_proto.src_file_system_url()
                                         : std::string();
  std::string dst_fs_url_as_string = request_proto.has_dst_file_system_url()
                                         ? request_proto.dst_file_system_url()
                                         : std::string();
  auto src_parsed =
      ParseFileSystemURL(moniker_map_, prefix_map_, src_fs_url_as_string);
  if (!src_parsed.has_value()) {
    RenameResponseProto response_proto;
    response_proto.set_posix_error_code(src_parsed.error().posix_error_code);
    std::move(callback).Run(response_proto);
    return;
  }
  callback = HistogramWrap<RenameCallback, RenameResponseProto>(
      GetHistogramEnumFileSystemType(src_parsed->fs_url), "Rename",
      std::move(callback));
  if (src_parsed->read_only) {
    RenameResponseProto response_proto;
    response_proto.set_posix_error_code(EACCES);
    std::move(callback).Run(response_proto);
    return;
  }

  auto dst_parsed =
      ParseFileSystemURL(moniker_map_, prefix_map_, dst_fs_url_as_string);
  if (!dst_parsed.has_value()) {
    RenameResponseProto response_proto;
    response_proto.set_posix_error_code(dst_parsed.error().posix_error_code);
    std::move(callback).Run(response_proto);
    return;
  } else if (dst_parsed->read_only) {
    RenameResponseProto response_proto;
    response_proto.set_posix_error_code(EACCES);
    std::move(callback).Run(response_proto);
    return;
  }

  // Use a temporary file (and CopyInForeignFile), for cross-file-system moves
  // where the destination file system doesn't support incremental writes, but
  // both source and destination are on Fusebox-served subdirs.
  //
  // Otherwise, the storage::FileSystemOperationRunner::Move call further below
  // would require the various backends to support either src
  // CreateSnapshotFile and dst CopyInForeignFile (when using
  // storage::SnapshotCopyOrMoveImpl) or src CreateFileStreamReader and dst
  // CreateFileStreamWriter (when using storage::StreamCopyOrMoveImpl).
  //
  // Care is needed to pick the right approach, based on both source and
  // destination file system types, as many backends subclass (with stub
  // methods to satisfy the C++ compiler) but don't completely satisfy the
  // storage::AsyncFileUtil or ash::FileSystemBackendDelegate interfaces.
  //
  // As of March 2023, these below are all unimplemented, often but not always
  // marked NOTIMPLEMENTED, NOTREACHED, or TODO:
  //   - ArcDocumentsProviderAsyncFileUtil::CopyInForeignFile
  //   - ArcDocumentsProviderAsyncFileUtil::CreateSnapshotFile
  //   - MTPFileSystemBackendDelegate::CreateFileStreamWriter
  //   - ProviderAsyncFileUtil::CopyInForeignFile (*)
  //   - ProviderAsyncFileUtil::CreateSnapshotFile
  //
  // (*) ProviderAsyncFileUtil::CopyInForeignFile was added in August 2023.
  if (!src_parsed->fs_url.IsInSameFileSystem(dst_parsed->fs_url) &&
      UseTempFile(dst_fs_url_as_string)) {
    auto outer_callback = base::BindPostTaskToCurrentDefault(
        base::BindOnce(&RunRenameCallbackPosixErrorCode, std::move(callback),
                       src_parsed->fs_context));

    content::GetIOThreadTaskRunner({})->PostTask(
        FROM_HERE,
        base::BindOnce(&DoCrossFileSystemRename, src_parsed->fs_context,
                       std::string(ProfileManager::GetActiveUserProfile()
                                       ->GetPath()
                                       .AsUTF8Unsafe()),
                       std::move(src_parsed->fs_url),
                       std::move(dst_parsed->fs_url),
                       std::move(outer_callback)));
    return;
  }

  auto outer_callback = base::BindPostTaskToCurrentDefault(
      base::BindOnce(&RunRenameCallbackBaseFileError, std::move(callback),
                     src_parsed->fs_context));

  constexpr storage::FileSystemOperation::CopyOrMoveOptionSet options = {
      storage::FileSystemOperation::CopyOrMoveOption::kPreserveLastModified,
      storage::FileSystemOperation::CopyOrMoveOption::
          kRemovePartiallyCopiedFilesOnError};

  content::GetIOThreadTaskRunner({})->PostTask(
      FROM_HERE,
      base::BindOnce(
          base::IgnoreResult(&storage::FileSystemOperationRunner::Move),
          // Unretained is safe: fs_context owns its operation runner.
          base::Unretained(src_parsed->fs_context->operation_runner()),
          src_parsed->fs_url, dst_parsed->fs_url, options,
          storage::FileSystemOperation::ERROR_BEHAVIOR_ABORT,
          std::make_unique<storage::CopyOrMoveHookDelegate>(),
          std::move(outer_callback)));
}

void Server::RmDir(const RmDirRequestProto& request_proto,
                   RmDirCallback callback) {
  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);

  std::string fs_url_as_string = request_proto.has_file_system_url()
                                     ? request_proto.file_system_url()
                                     : std::string();

  auto parsed = ParseFileSystemURL(moniker_map_, prefix_map_, fs_url_as_string);
  if (!parsed.has_value()) {
    RmDirResponseProto response_proto;
    response_proto.set_posix_error_code(parsed.error().posix_error_code);
    std::move(callback).Run(response_proto);
    return;
  }
  callback = HistogramWrap<RmDirCallback, RmDirResponseProto>(
      GetHistogramEnumFileSystemType(parsed->fs_url), "RmDir",
      std::move(callback));
  if (parsed->read_only) {
    RmDirResponseProto response_proto;
    response_proto.set_posix_error_code(EACCES);
    std::move(callback).Run(response_proto);
    return;
  }

  auto outer_callback = base::BindPostTaskToCurrentDefault(base::BindOnce(
      &RunRmDirCallback, std::move(callback), parsed->fs_context));

  content::GetIOThreadTaskRunner({})->PostTask(
      FROM_HERE,
      base::BindOnce(
          base::IgnoreResult(
              &storage::FileSystemOperationRunner::RemoveDirectory),
          // Unretained is safe: fs_context owns its operation runner.
          base::Unretained(parsed->fs_context->operation_runner()),
          parsed->fs_url, std::move(outer_callback)));
}

void Server::Stat2(const Stat2RequestProto& request_proto,
                   Stat2Callback callback) {
  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);

  std::string fs_url_as_string = request_proto.has_file_system_url()
                                     ? request_proto.file_system_url()
                                     : std::string();

  auto parsed = ParseFileSystemURL(moniker_map_, prefix_map_, fs_url_as_string);
  if (!parsed.has_value()) {
    Stat2ResponseProto response_proto;
    if (parsed.error().is_moniker_root) {
      DirEntryProto* stat = response_proto.mutable_stat();
      constexpr bool is_directory = true;
      constexpr bool read_only = true;
      stat->set_mode_bits(Server::MakeModeBits(is_directory, read_only));
    } else {
      response_proto.set_posix_error_code(parsed.error().posix_error_code);
    }
    std::move(callback).Run(response_proto);
    return;
  }
  callback = HistogramWrap<Stat2Callback, Stat2ResponseProto>(
      GetHistogramEnumFileSystemType(parsed->fs_url), "Stat2",
      std::move(callback));

  constexpr storage::FileSystemOperation::GetMetadataFieldSet metadata_fields =
      {storage::FileSystemOperation::GetMetadataField::kIsDirectory,
       storage::FileSystemOperation::GetMetadataField::kSize,
       storage::FileSystemOperation::GetMetadataField::kLastModified};

  auto outer_callback = base::BindPostTaskToCurrentDefault(
      base::BindOnce(&RunStat2Callback, std::move(callback), parsed->fs_context,
                     parsed->read_only));

  content::GetIOThreadTaskRunner({})->PostTask(
      FROM_HERE,
      base::BindOnce(
          base::IgnoreResult(&storage::FileSystemOperationRunner::GetMetadata),
          // Unretained is safe: fs_context owns its operation_runner.
          base::Unretained(parsed->fs_context->operation_runner()),
          parsed->fs_url, metadata_fields, std::move(outer_callback)));
}

void Server::Truncate(const TruncateRequestProto& request_proto,
                      TruncateCallback callback) {
  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);

  std::string fs_url_as_string = request_proto.has_file_system_url()
                                     ? request_proto.file_system_url()
                                     : std::string();

  auto parsed = ParseFileSystemURL(moniker_map_, prefix_map_, fs_url_as_string);
  if (!parsed.has_value()) {
    TruncateResponseProto response_proto;
    response_proto.set_posix_error_code(parsed.error().posix_error_code);
    std::move(callback).Run(response_proto);
    return;
  }
  callback = HistogramWrap<TruncateCallback, TruncateResponseProto>(
      GetHistogramEnumFileSystemType(parsed->fs_url), "Truncate",
      std::move(callback));
  if (parsed->read_only) {
    TruncateResponseProto response_proto;
    response_proto.set_posix_error_code(EACCES);
    std::move(callback).Run(response_proto);
    return;
  }

  int64_t length = request_proto.has_length() ? request_proto.length() : 0;
  if (UseEmptyTruncateWorkaround(fs_url_as_string, length)) {
    DoEmptyTruncateWorkaround(std::move(parsed->fs_context),
                              std::move(parsed->fs_url), std::move(callback));
    return;
  }

  auto outer_callback = base::BindPostTaskToCurrentDefault(
      base::BindOnce(&RunTruncateCallback, std::move(callback),
                     parsed->fs_context, parsed->fs_url, parsed->read_only));

  content::GetIOThreadTaskRunner({})->PostTask(
      FROM_HERE,
      base::BindOnce(
          base::IgnoreResult(&storage::FileSystemOperationRunner::Truncate),
          // Unretained is safe: fs_context owns its operation runner.
          base::Unretained(parsed->fs_context->operation_runner()),
          parsed->fs_url, length, std::move(outer_callback)));
}

void Server::Unlink(const UnlinkRequestProto& request_proto,
                    UnlinkCallback callback) {
  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);

  std::string fs_url_as_string = request_proto.has_file_system_url()
                                     ? request_proto.file_system_url()
                                     : std::string();

  auto parsed = ParseFileSystemURL(moniker_map_, prefix_map_, fs_url_as_string);
  if (!parsed.has_value()) {
    UnlinkResponseProto response_proto;
    response_proto.set_posix_error_code(parsed.error().posix_error_code);
    std::move(callback).Run(response_proto);
    return;
  }
  callback = HistogramWrap<UnlinkCallback, UnlinkResponseProto>(
      GetHistogramEnumFileSystemType(parsed->fs_url), "Unlink",
      std::move(callback));
  if (parsed->read_only) {
    UnlinkResponseProto response_proto;
    response_proto.set_posix_error_code(EACCES);
    std::move(callback).Run(response_proto);
    return;
  }

  auto outer_callback = base::BindPostTaskToCurrentDefault(base::BindOnce(
      &RunUnlinkCallback, std::move(callback), parsed->fs_context));

  content::GetIOThreadTaskRunner({})->PostTask(
      FROM_HERE,
      base::BindOnce(
          base::IgnoreResult(&storage::FileSystemOperationRunner::RemoveFile),
          // Unretained is safe: fs_context owns its operation runner.
          base::Unretained(parsed->fs_context->operation_runner()),
          parsed->fs_url, std::move(outer_callback)));
}

void Server::Write2(const Write2RequestProto& request_proto,
                    Write2Callback callback) {
  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);

  uint64_t fuse_handle =
      request_proto.has_fuse_handle() ? request_proto.fuse_handle() : 0;
  auto iter = fuse_file_map_.find(fuse_handle);
  if (iter == fuse_file_map_.end()) {
    Write2ResponseProto response_proto;
    response_proto.set_posix_error_code(ENOENT);
    std::move(callback).Run(response_proto);
    return;
  }
  callback = HistogramWrap<Write2Callback, Write2ResponseProto>(
      iter->second.histogram_enum_file_system_type_, "Write2",
      std::move(callback));
  if (!iter->second.writable_) {
    Write2ResponseProto response_proto;
    response_proto.set_posix_error_code(EACCES);
    std::move(callback).Run(response_proto);
    return;
  } else if (request_proto.has_data() &&
             (request_proto.data().size() > INT_MAX)) {
    Write2ResponseProto response_proto;
    response_proto.set_posix_error_code(EMSGSIZE);
    std::move(callback).Run(response_proto);
    return;
  } else if (iter->second.has_in_flight_op_) {
    iter->second.pending_ops_.emplace_back(
        PendingWrite2(request_proto, std::move(callback)));
    return;
  }
  iter->second.has_in_flight_op_ = true;
  iter->second.DoWrite2(
      request_proto,
      base::BindOnce(&Server::OnWrite2, weak_ptr_factory_.GetWeakPtr(),
                     fuse_handle, std::move(callback)));
}

void Server::ListStorages(const ListStoragesRequestProto& request,
                          ListStoragesCallback callback) {
  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);

  ListStoragesResponseProto response;
  response.add_storages(kMonikerSubdir);
  for (const auto& i : prefix_map_) {
    response.add_storages(i.first);
  }
  std::move(callback).Run(response);
}

void Server::MakeTempDir(MakeTempDirCallback callback) {
  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);

  constexpr auto make_temp_dir_on_worker_thread =
      [](base::WeakPtr<Server> weak_ptr_server, MakeTempDirCallback callback) {
        base::ScopedTempDir scoped_temp_dir;
        bool create_succeeded = scoped_temp_dir.CreateUniqueTempDir();
        content::GetUIThreadTaskRunner({})->PostTask(
            FROM_HERE, base::BindOnce(&Server::ReplyToMakeTempDir,
                                      std::move(weak_ptr_server),
                                      std::move(scoped_temp_dir),
                                      create_succeeded, std::move(callback)));
      };

  base::ThreadPool::PostTask(
      FROM_HERE, {base::MayBlock(), base::TaskPriority::BEST_EFFORT},
      base::BindOnce(make_temp_dir_on_worker_thread,
                     weak_ptr_factory_.GetWeakPtr(), std::move(callback)));
}

void Server::ReplyToMakeTempDir(base::ScopedTempDir scoped_temp_dir,
                                bool create_succeeded,
                                MakeTempDirCallback callback) {
  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);

  if (!create_succeeded) {
    std::move(callback).Run("CreateUniqueTempDir failed", "", "");
    return;
  }

  const std::string subdir = SubdirForTempDir(scoped_temp_dir);
  const std::string mount_name =
      base::StrCat({file_manager::util::kFuseBoxMountNamePrefix, subdir});
  const std::string fusebox_file_path =
      base::StrCat({file_manager::util::kFuseBoxMediaSlashPath, subdir});
  const base::FilePath underlying_file_path = scoped_temp_dir.GetPath();

  storage::ExternalMountPoints* const mount_points =
      storage::ExternalMountPoints::GetSystemInstance();
  if (!mount_points->RegisterFileSystem(
          mount_name, storage::kFileSystemTypeLocal,
          storage::FileSystemMountOption(), underlying_file_path)) {
    std::move(callback).Run("RegisterFileSystem failed", "", "");
    return;
  }

  scoped_refptr<storage::FileSystemContext> fs_context =
      file_manager::util::GetFileManagerFileSystemContext(
          ProfileManager::GetActiveUserProfile());
  const blink::StorageKey storage_key =
      blink::StorageKey::CreateFromStringForTesting(
          "http://fusebox-server.example.com");
  ash::FileSystemBackend::Get(*fs_context)
      ->GrantFileAccessToOrigin(storage_key.origin(),
                                base::FilePath(mount_name));

  storage::FileSystemURL fs_url =
      mount_points->CreateExternalFileSystemURL(storage_key, mount_name, {});
  constexpr bool read_only = false;
  RegisterFSURLPrefix(subdir, fs_url.ToGURL().spec(), read_only);

  temp_subdir_map_.insert({fusebox_file_path, std::move(scoped_temp_dir)});

  std::move(callback).Run("", fusebox_file_path,
                          underlying_file_path.AsUTF8Unsafe());
}

void Server::RemoveTempDir(const std::string& fusebox_file_path) {
  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);

  auto iter = temp_subdir_map_.find(fusebox_file_path);
  if (iter == temp_subdir_map_.end()) {
    return;
  }
  base::ScopedTempDir scoped_temp_dir = std::move(iter->second);
  const std::string subdir = SubdirForTempDir(scoped_temp_dir);
  const std::string mount_name =
      base::StrCat({file_manager::util::kFuseBoxMountNamePrefix, subdir});
  temp_subdir_map_.erase(iter);
  UnregisterFSURLPrefix(subdir);
  storage::ExternalMountPoints::GetSystemInstance()->RevokeFileSystem(
      mount_name);
  base::ThreadPool::PostTask(
      FROM_HERE, {base::MayBlock(), base::TaskPriority::BEST_EFFORT},
      base::BindOnce(
          [](base::ScopedTempDir) {
            // No-op other than running the base::ScopedTempDir destructor.
          },
          std::move(scoped_temp_dir)));
}

void Server::OnFlush(uint64_t fuse_handle,
                     FlushCallback callback,
                     const FlushResponseProto& response_proto) {
  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);

  auto iter = fuse_file_map_.find(fuse_handle);
  if (iter == fuse_file_map_.end()) {
    FlushResponseProto enoent_response_proto;
    enoent_response_proto.set_posix_error_code(ENOENT);
    std::move(callback).Run(enoent_response_proto);
    return;
  }
  FuseFileMapEntry& entry = iter->second;
  entry.has_in_flight_op_ = false;

  std::move(callback).Run(std::move(response_proto));

  if (entry.pending_ops_.empty()) {
    return;
  }
  PendingOp pending_op = std::move(entry.pending_ops_.front());
  entry.pending_ops_.pop_front();
  entry.has_in_flight_op_ = true;
  entry.Do(pending_op, weak_ptr_factory_.GetWeakPtr(), fuse_handle);
}

void Server::OnRead2(uint64_t fuse_handle,
                     Read2Callback callback,
                     const Read2ResponseProto& response_proto) {
  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);

  auto iter = fuse_file_map_.find(fuse_handle);
  if (iter == fuse_file_map_.end()) {
    Read2ResponseProto enoent_response_proto;
    enoent_response_proto.set_posix_error_code(ENOENT);
    std::move(callback).Run(enoent_response_proto);
    return;
  }
  FuseFileMapEntry& entry = iter->second;
  entry.has_in_flight_op_ = false;

  std::move(callback).Run(std::move(response_proto));

  if (entry.pending_ops_.empty()) {
    return;
  }
  PendingOp pending_op = std::move(entry.pending_ops_.front());
  entry.pending_ops_.pop_front();
  entry.has_in_flight_op_ = true;
  entry.Do(pending_op, weak_ptr_factory_.GetWeakPtr(), fuse_handle);
}

void Server::OnReadDirectory(
    scoped_refptr<storage::FileSystemContext> fs_context,  // See § above.
    bool read_only,
    uint64_t cookie,
    base::File::Error error_code,
    storage::AsyncFileUtil::EntryList entry_list,
    bool has_more) {
  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);

  auto iter = read_dir_2_map_.find(cookie);
  if (iter == read_dir_2_map_.end()) {
    return;
  }

  if (iter->second.posix_error_code_ == 0) {
    iter->second.posix_error_code_ = FileErrorToErrno(error_code);
  }

  for (const auto& entry : entry_list) {
    bool is_directory = entry.type == filesystem::mojom::FsFileType::DIRECTORY;
    auto* proto = iter->second.response_.add_entries();
    proto->set_name(entry.name.value());
    proto->set_mode_bits(MakeModeBits(is_directory, read_only));
  }

  iter->second.has_more_ = has_more;

  if (iter->second.Reply(cookie, ReadDir2Callback())) {
    read_dir_2_map_.erase(iter);
  }
}

void Server::OnWrite2(uint64_t fuse_handle,
                      Write2Callback callback,
                      const Write2ResponseProto& response_proto) {
  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);

  auto iter = fuse_file_map_.find(fuse_handle);
  if (iter == fuse_file_map_.end()) {
    Write2ResponseProto enoent_response_proto;
    enoent_response_proto.set_posix_error_code(ENOENT);
    std::move(callback).Run(enoent_response_proto);
    return;
  }
  FuseFileMapEntry& entry = iter->second;
  entry.has_in_flight_op_ = false;

  std::move(callback).Run(std::move(response_proto));

  if (entry.pending_ops_.empty()) {
    return;
  }
  PendingOp pending_op = std::move(entry.pending_ops_.front());
  entry.pending_ops_.pop_front();
  entry.has_in_flight_op_ = true;
  entry.Do(pending_op, weak_ptr_factory_.GetWeakPtr(), fuse_handle);
}

void Server::EraseFuseFileMapEntry(uint64_t fuse_handle) {
  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);

  fuse_file_map_.erase(fuse_handle);
}

uint64_t Server::InsertFuseFileMapEntry(FuseFileMapEntry&& entry) {
  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);

  static uint64_t next_fuse_handle = 0;
  uint64_t fuse_handle = ++next_fuse_handle;
  // As the fusebox.proto comment says, "The high bit (also known as the 1<<63
  // bit) is also always zero for valid values".
  DCHECK((fuse_handle >> 63) == 0);

  fuse_file_map_.insert({fuse_handle, std::move(entry)});
  return fuse_handle;
}

}  // namespace fusebox