blob: 7b45e2b457cb6b37425eb3eda5847656dc6f81d1 [file] [log] [blame]
// 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 <sys/stat.h>
#include <utility>
#include "base/callback.h"
#include "base/files/file_util.h"
#include "base/logging.h"
#include "base/strings/strcat.h"
#include "base/strings/string_util.h"
#include "base/task/bind_post_task.h"
#include "base/task/sequenced_task_runner.h"
#include "base/task/thread_pool.h"
#include "chrome/browser/ash/file_manager/fileapi_util.h"
#include "chrome/browser/ash/file_manager/path_util.h"
#include "chrome/browser/ash/fusebox/fusebox_errno.h"
#include "chrome/browser/profiles/profile_manager.h"
#include "content/public/browser/browser_task_traits.h"
#include "content/public/browser/browser_thread.h"
#include "storage/browser/file_system/async_file_util.h"
#include "storage/browser/file_system/external_mount_points.h"
#include "storage/browser/file_system/file_system_backend.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"
// 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;
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);
}
// ParseResult is the type returned by ParseFileSystemURL. It is a result type
// (see https://en.wikipedia.org/wiki/Result_type), being either an error or a
// value. In this case, the error type is a base::File::Error (a numeric code)
// and the value type is the storage::FileSystemContext and the
// storage::FileSystemURL (and some other incidental fields).
struct ParseResult {
explicit ParseResult(base::File::Error error_code_arg);
ParseResult(scoped_refptr<storage::FileSystemContext> fs_context_arg,
storage::FileSystemURL fs_url_arg,
bool read_only_arg);
~ParseResult();
base::File::Error error_code;
scoped_refptr<storage::FileSystemContext> fs_context;
storage::FileSystemURL fs_url;
bool read_only = false;
// is_moniker_root is used for the special case where the server is passed
// fusebox::kMonikerSubdir (also known as "moniker"). There is no
// FileSystemURL registered for "moniker" (as opposed to for
// "moniker/1234etc"), so ParseFileSystemURL (which returns a valid
// FileSystemURL on success) must return an error. However, Stat2 or ReadDir2
// on "moniker" should succeed (but return an empty directory).
bool is_moniker_root = false;
};
ParseResult::ParseResult(base::File::Error error_code_arg)
: error_code(error_code_arg) {}
ParseResult::ParseResult(
scoped_refptr<storage::FileSystemContext> fs_context_arg,
storage::FileSystemURL fs_url_arg,
bool read_only_arg)
: error_code(base::File::Error::FILE_OK),
fs_context(std::move(fs_context_arg)),
fs_url(std::move(fs_url_arg)),
read_only(read_only_arg) {}
ParseResult::~ParseResult() = default;
// All of the Server methods' arguments start with a FileSystemURL (as a
// string). This function parses that first argument as well as finding the
// FileSystemContext we will need to serve those methods.
ParseResult 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 ParseResult(base::File::Error::FILE_ERROR_INVALID_URL);
} else if (!fs_context) {
LOG(ERROR) << "No FileSystemContext";
return ParseResult(base::File::Error::FILE_ERROR_FAILED);
}
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(fs_url_as_string);
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 ParseResult(base::File::Error::FILE_ERROR_NOT_FOUND);
}
fs_url = std::move(resolved.first);
read_only = resolved.second;
break;
}
case ResultType::NOT_A_MONIKER_FS_URL: {
auto resolved = ResolvePrefixMap(prefix_map, fs_url_as_string);
if (resolved.first.empty()) {
LOG(ERROR) << "Unresolvable Prefix";
return ParseResult(base::File::Error::FILE_ERROR_NOT_FOUND);
}
read_only = resolved.second;
fs_url = fs_context->CrackURLInFirstPartyContext(GURL(resolved.first));
if (!fs_url.is_valid()) {
LOG(ERROR) << "Invalid FileSystemURL";
return ParseResult(base::File::Error::FILE_ERROR_INVALID_URL);
}
break;
}
case ResultType::MONIKER_FS_URL_BUT_ONLY_ROOT: {
ParseResult result = ParseResult(base::File::Error::FILE_ERROR_NOT_FOUND);
result.is_moniker_root = true;
return result;
}
case ResultType::MONIKER_FS_URL_BUT_NOT_WELL_FORMED:
return ParseResult(base::File::Error::FILE_ERROR_NOT_FOUND);
}
if (!fs_context->external_backend()->CanHandleType(fs_url.type())) {
LOG(ERROR) << "Backend cannot handle "
<< storage::GetFileSystemTypeString(fs_url.type());
return ParseResult(base::File::Error::FILE_ERROR_INVALID_URL);
}
return ParseResult(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(fusebox_staging::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));
dir_entry_proto->set_size(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();
fusebox_staging::CreateResponseProto response_proto;
response_proto.set_posix_error_code(posix_error_code);
std::move(callback).Run(response_proto);
return;
}
fusebox_staging::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();
fusebox_staging::CreateResponseProto response_proto;
response_proto.set_posix_error_code(posix_error_code);
std::move(callback).Run(response_proto);
return;
}
constexpr auto metadata_fields =
storage::FileSystemOperation::GET_METADATA_FIELD_IS_DIRECTORY |
storage::FileSystemOperation::GET_METADATA_FIELD_SIZE |
storage::FileSystemOperation::GET_METADATA_FIELD_LAST_MODIFIED;
auto outer_callback = base::BindPostTask(
base::SequencedTaskRunnerHandle::Get(),
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) {
fusebox_staging::MkDirResponseProto response_proto;
response_proto.set_posix_error_code(posix_error_code);
std::move(callback).Run(response_proto);
return;
}
fusebox_staging::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) {
fusebox_staging::MkDirResponseProto response_proto;
response_proto.set_posix_error_code(posix_error_code);
std::move(callback).Run(response_proto);
return;
}
constexpr auto metadata_fields =
storage::FileSystemOperation::GET_METADATA_FIELD_IS_DIRECTORY |
storage::FileSystemOperation::GET_METADATA_FIELD_SIZE |
storage::FileSystemOperation::GET_METADATA_FIELD_LAST_MODIFIED;
auto outer_callback = base::BindPostTask(
base::SequencedTaskRunner::GetCurrentDefault(),
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 RunReadCallbackFailure(Server::ReadCallback callback,
base::File::Error error_code) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
std::move(callback).Run(FileErrorToErrno(error_code), nullptr, 0);
}
void RunReadCallbackTypical(
Server::ReadCallback callback,
scoped_refptr<storage::FileSystemContext> fs_context, // See § above.
std::unique_ptr<storage::FileStreamReader> fs_reader,
scoped_refptr<net::IOBuffer> buffer,
int length) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
if (length < 0) {
std::move(callback).Run(NetErrorToErrno(length), nullptr, 0);
} else {
std::move(callback).Run(0, reinterpret_cast<uint8_t*>(buffer->data()),
length);
}
auto task_runner = content::GetIOThreadTaskRunner({});
task_runner->DeleteSoon(FROM_HERE, fs_reader.release());
task_runner->ReleaseSoon(FROM_HERE, std::move(buffer));
}
void RunRead2CallbackFailure(Server::Read2Callback callback,
base::File::Error error_code) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
fusebox_staging::Read2ResponseProto response_proto;
response_proto.set_posix_error_code(FileErrorToErrno(error_code));
std::move(callback).Run(response_proto);
}
void RunRead2CallbackTypical(Server::Read2Callback callback,
scoped_refptr<net::IOBuffer> buffer,
int length) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
fusebox_staging::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 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) {
fusebox_staging::RmDirResponseProto response_proto;
response_proto.set_posix_error_code(posix_error_code);
std::move(callback).Run(response_proto);
return;
}
fusebox_staging::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) {
fusebox_staging::TruncateResponseProto response_proto;
response_proto.set_posix_error_code(posix_error_code);
std::move(callback).Run(response_proto);
return;
}
fusebox_staging::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) {
fusebox_staging::TruncateResponseProto response_proto;
response_proto.set_posix_error_code(posix_error_code);
std::move(callback).Run(response_proto);
return;
}
constexpr auto metadata_fields =
storage::FileSystemOperation::GET_METADATA_FIELD_IS_DIRECTORY |
storage::FileSystemOperation::GET_METADATA_FIELD_SIZE |
storage::FileSystemOperation::GET_METADATA_FIELD_LAST_MODIFIED;
auto outer_callback = base::BindPostTask(
base::SequencedTaskRunner::GetCurrentDefault(),
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) {
fusebox_staging::UnlinkResponseProto response_proto;
response_proto.set_posix_error_code(posix_error_code);
std::move(callback).Run(response_proto);
return;
}
fusebox_staging::UnlinkResponseProto response_proto;
std::move(callback).Run(response_proto);
}
void RunWrite2CallbackFailure(Server::Write2Callback callback,
base::File::Error error_code) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
fusebox_staging::Write2ResponseProto response_proto;
response_proto.set_posix_error_code(FileErrorToErrno(error_code));
std::move(callback).Run(response_proto);
}
void RunWrite2CallbackTypical(Server::Write2Callback callback, int length) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
fusebox_staging::Write2ResponseProto response_proto;
if (length < 0) {
response_proto.set_posix_error_code(NetErrorToErrno(length));
}
std::move(callback).Run(response_proto);
}
void ReadOnIOThread(scoped_refptr<storage::FileSystemContext> fs_context,
storage::FileSystemURL fs_url,
int64_t offset,
int64_t length,
Server::ReadCallback callback) {
DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
std::unique_ptr<storage::FileStreamReader> fs_reader =
fs_context->CreateFileStreamReader(fs_url, offset, length, base::Time());
if (!fs_reader) {
content::GetUIThreadTaskRunner({})->PostTask(
FROM_HERE, base::BindOnce(&RunReadCallbackFailure, std::move(callback),
base::File::Error::FILE_ERROR_INVALID_URL));
return;
}
scoped_refptr<net::IOBuffer> buffer =
base::MakeRefCounted<net::IOBuffer>(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::BindPostTask(
content::GetUIThreadTaskRunner({}),
base::BindOnce(&RunReadCallbackTypical, std::move(callback), fs_context,
std::move(fs_reader), buffer)));
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);
}
}
void RunStatCallback(
Server::StatCallback 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);
std::move(callback).Run(FileErrorToErrno(error_code), info, read_only);
}
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) {
fusebox_staging::Stat2ResponseProto response_proto;
response_proto.set_posix_error_code(posix_error_code);
std::move(callback).Run(response_proto);
return;
}
fusebox_staging::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});
}
} // namespace
Server::ReadWriter::ReadWriter(const storage::FileSystemURL& fs_url)
: fs_url_(fs_url) {
DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
}
Server::ReadWriter::~ReadWriter() {
DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
}
void Server::ReadWriter::Read(
scoped_refptr<storage::FileSystemContext> fs_context,
int64_t offset,
int64_t length,
Server::Read2Callback callback) {
DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
// 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) {
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.
scoped_refptr<net::IOBuffer> buffer = base::MakeRefCounted<net::IOBuffer>(
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(
&Server::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);
}
}
void Server::ReadWriter::OnRead(
Server::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);
if (length >= 0) {
fs_reader_ = std::move(fs_reader);
read_offset_ = offset + length;
} else {
fs_reader_.reset();
read_offset_ = -1;
}
content::GetUIThreadTaskRunner({})->PostTask(
FROM_HERE, base::BindOnce(&RunRead2CallbackTypical, std::move(callback),
std::move(buffer), length));
}
void Server::ReadWriter::Write(
scoped_refptr<storage::FileSystemContext> fs_context,
scoped_refptr<net::StringIOBuffer> buffer,
int64_t offset,
int length,
Server::Write2Callback callback) {
DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
// See if we can re-use the previous storage::FileStreamWriter.
std::unique_ptr<storage::FileStreamWriter> fs_writer;
if (fs_writer_ && (write_offset_ == offset)) {
fs_writer = std::move(fs_writer_);
write_offset_ = -1;
} else {
fs_writer = fs_context->CreateFileStreamWriter(fs_url_, offset);
if (!fs_writer) {
content::GetUIThreadTaskRunner({})->PostTask(
FROM_HERE,
base::BindOnce(&RunWrite2CallbackFailure, std::move(callback),
base::File::Error::FILE_ERROR_INVALID_URL));
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.
auto* saved_fs_writer = fs_writer.get();
auto pair = base::SplitOnceCallback(base::BindOnce(
&Server::ReadWriter::OnWrite, 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);
}
}
void Server::ReadWriter::OnWrite(
Server::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) {
DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
if (length >= 0) {
fs_writer_ = std::move(fs_writer);
write_offset_ = offset + length;
} else {
fs_writer_.reset();
write_offset_ = -1;
}
content::GetUIThreadTaskRunner({})->PostTask(
FROM_HERE,
base::BindOnce(&RunWrite2CallbackTypical, std::move(callback), length));
}
Server::FuseFileMapEntry::FuseFileMapEntry(
scoped_refptr<storage::FileSystemContext> fs_context_arg,
storage::FileSystemURL fs_url_arg,
bool readable_arg,
bool writable_arg)
: fs_context_(fs_context_arg),
readable_(readable_arg),
writable_(writable_arg),
seqbnd_read_writer_(content::GetIOThreadTaskRunner({}), fs_url_arg) {}
Server::FuseFileMapEntry::FuseFileMapEntry(FuseFileMapEntry&&) = default;
Server::FuseFileMapEntry::~FuseFileMapEntry() = default;
void Server::FuseFileMapEntry::DoRead2(
const fusebox_staging::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(&Server::ReadWriter::Read)
.WithArgs(fs_context_, offset, length, std::move(callback));
}
void Server::FuseFileMapEntry::DoWrite2(
const fusebox_staging::Write2RequestProto& request,
Server::Write2Callback callback) {
if (!request.has_data() || request.data().empty()) {
fusebox_staging::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(&Server::ReadWriter::Write)
.WithArgs(fs_context_, std::move(buffer), offset,
static_cast<int>(request.data().size()), std::move(callback));
}
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 {
response_.set_cookie(has_more_ ? cookie : 0);
}
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::Value Server::GetDebugJSON() {
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));
return base::Value(std::move(dict));
}
void Server::Close(const std::string& fs_url_as_string,
CloseCallback callback) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
auto common = ParseFileSystemURL(moniker_map_, prefix_map_, fs_url_as_string);
if (common.error_code != base::File::Error::FILE_OK) {
std::move(callback).Run(FileErrorToErrno(common.error_code));
return;
}
// Fail with an invalid operation error for now. TODO(crbug.com/1249754)
// implement MTP device writing.
std::move(callback).Run(ENOTSUP);
}
void Server::Close2(const fusebox_staging::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()) {
fusebox_staging::Close2ResponseProto response_proto;
response_proto.set_posix_error_code(ENOENT);
std::move(callback).Run(response_proto);
return;
}
FuseFileMapEntry& entry = iter->second;
base::circular_deque<PendingRead2> pending_reads =
std::move(entry.pending_reads_);
base::circular_deque<PendingWrite2> pending_writes =
std::move(entry.pending_writes_);
fuse_file_map_.erase(iter);
fusebox_staging::Close2ResponseProto response_proto;
std::move(callback).Run(response_proto);
if (!pending_reads.empty()) {
fusebox_staging::Read2ResponseProto read2_response_proto;
read2_response_proto.set_posix_error_code(EBUSY);
for (auto& pending_read : pending_reads) {
std::move(pending_read.second).Run(read2_response_proto);
}
}
if (!pending_writes.empty()) {
fusebox_staging::Write2ResponseProto write2_esponse_proto;
write2_esponse_proto.set_posix_error_code(EBUSY);
for (auto& pending_read : pending_writes) {
std::move(pending_read.second).Run(write2_esponse_proto);
}
}
}
void Server::Create(const fusebox_staging::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 common = ParseFileSystemURL(moniker_map_, prefix_map_, fs_url_as_string);
if (common.error_code != base::File::Error::FILE_OK) {
fusebox_staging::CreateResponseProto response_proto;
response_proto.set_posix_error_code(FileErrorToErrno(common.error_code));
std::move(callback).Run(response_proto);
return;
} else if (common.read_only) {
fusebox_staging::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;
uint64_t fuse_handle = InsertFuseFileMapEntry(
FuseFileMapEntry(common.fs_context, common.fs_url, readable, writable));
auto on_failure = base::BindOnce(&Server::EraseFuseFileMapEntry,
weak_ptr_factory_.GetWeakPtr(), fuse_handle);
auto outer_callback = base::BindPostTask(
base::SequencedTaskRunnerHandle::Get(),
base::BindOnce(&RunCreateCallback, std::move(callback), common.fs_context,
common.fs_url, common.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: context owns operation runner.
base::Unretained(common.fs_context->operation_runner()),
common.fs_url, exclusive, std::move(outer_callback)));
}
void Server::MkDir(const fusebox_staging::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 common = ParseFileSystemURL(moniker_map_, prefix_map_, fs_url_as_string);
if (common.error_code != base::File::Error::FILE_OK) {
fusebox_staging::MkDirResponseProto response_proto;
response_proto.set_posix_error_code(FileErrorToErrno(common.error_code));
std::move(callback).Run(response_proto);
return;
} else if (common.read_only) {
fusebox_staging::MkDirResponseProto response_proto;
response_proto.set_posix_error_code(EACCES);
std::move(callback).Run(response_proto);
return;
}
auto outer_callback = base::BindPostTask(
base::SequencedTaskRunner::GetCurrentDefault(),
base::BindOnce(&RunMkDirCallback, std::move(callback), common.fs_context,
common.fs_url, common.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: context owns operation runner.
base::Unretained(common.fs_context->operation_runner()),
common.fs_url, exclusive, recursive,
std::move(outer_callback)));
}
void Server::Open(const std::string& fs_url_as_string, OpenCallback callback) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
auto common = ParseFileSystemURL(moniker_map_, prefix_map_, fs_url_as_string);
if (common.error_code != base::File::Error::FILE_OK) {
std::move(callback).Run(FileErrorToErrno(common.error_code));
return;
}
// Fail with an invalid operation error for now. TODO(crbug.com/1249754)
// implement MTP device writing.
std::move(callback).Run(ENOTSUP);
}
void Server::Open2(const fusebox_staging::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();
fusebox_staging::AccessMode access_mode =
request_proto.has_access_mode() ? request_proto.access_mode()
: fusebox_staging::AccessMode::NO_ACCESS;
auto common = ParseFileSystemURL(moniker_map_, prefix_map_, fs_url_as_string);
if (common.error_code != base::File::Error::FILE_OK) {
fusebox_staging::Open2ResponseProto response_proto;
response_proto.set_posix_error_code(FileErrorToErrno(common.error_code));
std::move(callback).Run(response_proto);
return;
}
bool readable = (access_mode == fusebox_staging::AccessMode::READ_ONLY) ||
(access_mode == fusebox_staging::AccessMode::READ_WRITE);
bool writable = !common.read_only &&
((access_mode == fusebox_staging::AccessMode::WRITE_ONLY) ||
(access_mode == fusebox_staging::AccessMode::READ_WRITE));
uint64_t fuse_handle = InsertFuseFileMapEntry(
FuseFileMapEntry(std::move(common.fs_context), std::move(common.fs_url),
readable, writable));
fusebox_staging::Open2ResponseProto response_proto;
response_proto.set_fuse_handle(fuse_handle);
std::move(callback).Run(response_proto);
}
void Server::Read(const std::string& fs_url_as_string,
int64_t offset,
int32_t length,
ReadCallback callback) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
auto common = ParseFileSystemURL(moniker_map_, prefix_map_, fs_url_as_string);
if (common.error_code != base::File::Error::FILE_OK) {
std::move(callback).Run(FileErrorToErrno(common.error_code), nullptr, 0);
return;
}
content::GetIOThreadTaskRunner({})->PostTask(
FROM_HERE,
base::BindOnce(&ReadOnIOThread, common.fs_context, common.fs_url, offset,
static_cast<int64_t>(length), std::move(callback)));
}
void Server::Read2(const fusebox_staging::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()) {
fusebox_staging::Read2ResponseProto response_proto;
response_proto.set_posix_error_code(ENOENT);
std::move(callback).Run(response_proto);
return;
} else if (!iter->second.readable_) {
fusebox_staging::Read2ResponseProto response_proto;
response_proto.set_posix_error_code(EACCES);
std::move(callback).Run(response_proto);
return;
} else if (iter->second.has_in_flight_read_) {
iter->second.pending_reads_.emplace_back(request_proto,
std::move(callback));
return;
}
iter->second.has_in_flight_read_ = 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 common = ParseFileSystemURL(moniker_map_, prefix_map_, fs_url_as_string);
if (common.is_moniker_root) {
ReadDir2ResponseProto response_proto;
response_proto.set_posix_error_code(0);
std::move(callback).Run(response_proto);
return;
} else if (common.error_code != base::File::Error::FILE_OK) {
ReadDir2ResponseProto response_proto;
response_proto.set_posix_error_code(FileErrorToErrno(common.error_code));
std::move(callback).Run(response_proto);
return;
} else 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::BindPostTask(
base::SequencedTaskRunner::GetCurrentDefault(),
base::BindRepeating(&Server::OnReadDirectory,
weak_ptr_factory_.GetWeakPtr(), common.fs_context,
common.read_only, cookie));
content::GetIOThreadTaskRunner({})->PostTask(
FROM_HERE,
base::BindRepeating(
base::IgnoreResult(
&storage::FileSystemOperationRunner::ReadDirectory),
// Unretained is safe: common.fs_context owns its operation_runner.
base::Unretained(common.fs_context->operation_runner()),
common.fs_url, std::move(outer_callback)));
}
void Server::RmDir(const fusebox_staging::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 common = ParseFileSystemURL(moniker_map_, prefix_map_, fs_url_as_string);
if (common.error_code != base::File::Error::FILE_OK) {
fusebox_staging::RmDirResponseProto response_proto;
response_proto.set_posix_error_code(FileErrorToErrno(common.error_code));
std::move(callback).Run(response_proto);
return;
} else if (common.read_only) {
fusebox_staging::RmDirResponseProto response_proto;
response_proto.set_posix_error_code(EACCES);
std::move(callback).Run(response_proto);
return;
}
auto outer_callback =
base::BindPostTask(base::SequencedTaskRunner::GetCurrentDefault(),
base::BindOnce(&RunRmDirCallback, std::move(callback),
common.fs_context));
content::GetIOThreadTaskRunner({})->PostTask(
FROM_HERE,
base::BindOnce(base::IgnoreResult(
&storage::FileSystemOperationRunner::RemoveDirectory),
// Unretained is safe: context owns operation runner.
base::Unretained(common.fs_context->operation_runner()),
common.fs_url, std::move(outer_callback)));
}
void Server::Stat(const std::string& fs_url_as_string, StatCallback callback) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
auto common = ParseFileSystemURL(moniker_map_, prefix_map_, fs_url_as_string);
if (common.is_moniker_root) {
base::File::Info info;
info.is_directory = true;
std::move(callback).Run(0, info, false);
return;
} else if (common.error_code != base::File::Error::FILE_OK) {
std::move(callback).Run(FileErrorToErrno(common.error_code),
base::File::Info(), false);
return;
}
constexpr auto metadata_fields =
storage::FileSystemOperation::GET_METADATA_FIELD_IS_DIRECTORY |
storage::FileSystemOperation::GET_METADATA_FIELD_SIZE |
storage::FileSystemOperation::GET_METADATA_FIELD_LAST_MODIFIED;
auto outer_callback =
base::BindPostTask(base::SequencedTaskRunner::GetCurrentDefault(),
base::BindOnce(&RunStatCallback, std::move(callback),
common.fs_context, common.read_only));
content::GetIOThreadTaskRunner({})->PostTask(
FROM_HERE,
base::BindOnce(
base::IgnoreResult(&storage::FileSystemOperationRunner::GetMetadata),
// Unretained is safe: common.fs_context owns its operation_runner.
base::Unretained(common.fs_context->operation_runner()),
common.fs_url, metadata_fields, std::move(outer_callback)));
}
void Server::Stat2(const fusebox_staging::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 common = ParseFileSystemURL(moniker_map_, prefix_map_, fs_url_as_string);
if (common.is_moniker_root) {
constexpr bool is_directory = true;
constexpr bool read_only = true;
fusebox_staging::Stat2ResponseProto response_proto;
fusebox_staging::DirEntryProto* stat = response_proto.mutable_stat();
stat->set_mode_bits(Server::MakeModeBits(is_directory, read_only));
std::move(callback).Run(response_proto);
return;
} else if (common.error_code != base::File::Error::FILE_OK) {
fusebox_staging::Stat2ResponseProto response_proto;
response_proto.set_posix_error_code(FileErrorToErrno(common.error_code));
std::move(callback).Run(response_proto);
return;
}
constexpr auto metadata_fields =
storage::FileSystemOperation::GET_METADATA_FIELD_IS_DIRECTORY |
storage::FileSystemOperation::GET_METADATA_FIELD_SIZE |
storage::FileSystemOperation::GET_METADATA_FIELD_LAST_MODIFIED;
auto outer_callback =
base::BindPostTask(base::SequencedTaskRunner::GetCurrentDefault(),
base::BindOnce(&RunStat2Callback, std::move(callback),
common.fs_context, common.read_only));
content::GetIOThreadTaskRunner({})->PostTask(
FROM_HERE,
base::BindOnce(
base::IgnoreResult(&storage::FileSystemOperationRunner::GetMetadata),
// Unretained is safe: common.fs_context owns its operation_runner.
base::Unretained(common.fs_context->operation_runner()),
common.fs_url, metadata_fields, std::move(outer_callback)));
}
void Server::Truncate(
const fusebox_staging::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 common = ParseFileSystemURL(moniker_map_, prefix_map_, fs_url_as_string);
if (common.error_code != base::File::Error::FILE_OK) {
fusebox_staging::TruncateResponseProto response_proto;
response_proto.set_posix_error_code(FileErrorToErrno(common.error_code));
std::move(callback).Run(response_proto);
return;
} else if (common.read_only) {
fusebox_staging::TruncateResponseProto response_proto;
response_proto.set_posix_error_code(EACCES);
std::move(callback).Run(response_proto);
return;
}
auto outer_callback = base::BindPostTask(
base::SequencedTaskRunner::GetCurrentDefault(),
base::BindOnce(&RunTruncateCallback, std::move(callback),
common.fs_context, common.fs_url, common.read_only));
content::GetIOThreadTaskRunner({})->PostTask(
FROM_HERE,
base::BindOnce(
base::IgnoreResult(&storage::FileSystemOperationRunner::Truncate),
// Unretained is safe: context owns operation runner.
base::Unretained(common.fs_context->operation_runner()),
common.fs_url,
request_proto.has_length() ? request_proto.length() : 0,
std::move(outer_callback)));
}
void Server::Unlink(const fusebox_staging::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 common = ParseFileSystemURL(moniker_map_, prefix_map_, fs_url_as_string);
if (common.error_code != base::File::Error::FILE_OK) {
fusebox_staging::UnlinkResponseProto response_proto;
response_proto.set_posix_error_code(FileErrorToErrno(common.error_code));
std::move(callback).Run(response_proto);
return;
} else if (common.read_only) {
fusebox_staging::UnlinkResponseProto response_proto;
response_proto.set_posix_error_code(EACCES);
std::move(callback).Run(response_proto);
return;
}
auto outer_callback =
base::BindPostTask(base::SequencedTaskRunner::GetCurrentDefault(),
base::BindOnce(&RunUnlinkCallback, std::move(callback),
common.fs_context));
content::GetIOThreadTaskRunner({})->PostTask(
FROM_HERE,
base::BindOnce(
base::IgnoreResult(&storage::FileSystemOperationRunner::RemoveFile),
// Unretained is safe: context owns operation runner.
base::Unretained(common.fs_context->operation_runner()),
common.fs_url, std::move(outer_callback)));
}
void Server::Write2(const fusebox_staging::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()) {
fusebox_staging::Write2ResponseProto response_proto;
response_proto.set_posix_error_code(ENOENT);
std::move(callback).Run(response_proto);
return;
} else if (!iter->second.writable_) {
fusebox_staging::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)) {
fusebox_staging::Write2ResponseProto response_proto;
response_proto.set_posix_error_code(EMSGSIZE);
std::move(callback).Run(response_proto);
return;
} else if (iter->second.has_in_flight_write_) {
iter->second.pending_writes_.emplace_back(request_proto,
std::move(callback));
return;
}
iter->second.has_in_flight_write_ = 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");
fs_context->external_backend()->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::OnRead2(
uint64_t fuse_handle,
Read2Callback callback,
const fusebox_staging::Read2ResponseProto& response_proto) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
auto iter = fuse_file_map_.find(fuse_handle);
if (iter == fuse_file_map_.end()) {
fusebox_staging::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_read_ = false;
std::move(callback).Run(std::move(response_proto));
if (entry.pending_reads_.empty()) {
return;
}
PendingRead2 pending = std::move(entry.pending_reads_.front());
entry.pending_reads_.pop_front();
entry.has_in_flight_read_ = true;
entry.DoRead2(pending.first,
base::BindOnce(&Server::OnRead2, weak_ptr_factory_.GetWeakPtr(),
fuse_handle, std::move(pending.second)));
}
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 fusebox_staging::Write2ResponseProto& response_proto) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
auto iter = fuse_file_map_.find(fuse_handle);
if (iter == fuse_file_map_.end()) {
fusebox_staging::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_write_ = false;
std::move(callback).Run(std::move(response_proto));
if (entry.pending_writes_.empty()) {
return;
}
PendingWrite2 pending = std::move(entry.pending_writes_.front());
entry.pending_writes_.pop_front();
entry.has_in_flight_write_ = true;
entry.DoWrite2(
pending.first,
base::BindOnce(&Server::OnWrite2, weak_ptr_factory_.GetWeakPtr(),
fuse_handle, std::move(pending.second)));
}
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