| // Copyright 2020 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "components/services/storage/public/cpp/filesystem/filesystem_proxy.h" |
| |
| #include <memory> |
| #include <utility> |
| #include <vector> |
| |
| #include "base/files/file.h" |
| #include "base/files/file_util.h" |
| #include "base/files/important_file_writer.h" |
| #include "base/task/sequenced_task_runner.h" |
| #include "base/types/expected.h" |
| #include "base/types/expected_macros.h" |
| #include "build/build_config.h" |
| #include "components/services/storage/public/cpp/filesystem/filesystem_impl.h" |
| #include "mojo/public/cpp/bindings/pending_receiver.h" |
| |
| namespace storage { |
| |
| namespace { |
| |
| class LocalFileLockImpl : public FilesystemProxy::FileLock { |
| public: |
| LocalFileLockImpl(base::FilePath path, base::File lock) |
| : path_(std::move(path)), lock_(std::move(lock)) {} |
| ~LocalFileLockImpl() override { |
| if (lock_.IsValid()) |
| Release(); |
| } |
| |
| // FilesystemProxy::FileLock implementation: |
| base::File::Error Release() override { |
| base::File::Error error = base::File::FILE_OK; |
| #if !BUILDFLAG(IS_FUCHSIA) |
| error = lock_.Unlock(); |
| #endif |
| lock_.Close(); |
| FilesystemImpl::UnlockFileLocal(path_); |
| return error; |
| } |
| |
| private: |
| const base::FilePath path_; |
| base::File lock_; |
| }; |
| |
| class RemoteFileLockImpl : public FilesystemProxy::FileLock { |
| public: |
| explicit RemoteFileLockImpl(mojo::PendingRemote<mojom::FileLock> remote_lock) |
| : remote_lock_(std::move(remote_lock)) {} |
| ~RemoteFileLockImpl() override { |
| if (remote_lock_) |
| Release(); |
| } |
| |
| // FilesystemProxy::FileLock implementation: |
| base::File::Error Release() override { |
| DCHECK(remote_lock_); |
| base::File::Error error = base::File::FILE_ERROR_IO; |
| mojo::Remote<mojom::FileLock>(std::move(remote_lock_))->Release(&error); |
| return error; |
| } |
| |
| private: |
| mojo::PendingRemote<mojom::FileLock> remote_lock_; |
| }; |
| |
| } // namespace |
| |
| FilesystemProxy::FilesystemProxy(decltype(UNRESTRICTED), |
| const base::FilePath& root) |
| : root_(root) { |
| DCHECK(root_.IsAbsolute() || root_.empty()); |
| } |
| |
| FilesystemProxy::FilesystemProxy( |
| decltype(RESTRICTED), |
| const base::FilePath& root, |
| mojo::PendingRemote<mojom::Directory> directory, |
| scoped_refptr<base::SequencedTaskRunner> ipc_task_runner) |
| : root_(root), |
| remote_directory_(std::move(directory), ipc_task_runner) { |
| DCHECK(root_.IsAbsolute()); |
| } |
| |
| FilesystemProxy::~FilesystemProxy() = default; |
| |
| bool FilesystemProxy::PathExists(const base::FilePath& path) { |
| if (!remote_directory_) |
| return base::PathExists(MaybeMakeAbsolute(path)); |
| |
| bool exists = false; |
| remote_directory_->PathExists(MakeRelative(path), &exists); |
| return exists; |
| } |
| |
| base::FileErrorOr<std::vector<base::FilePath>> |
| FilesystemProxy::GetDirectoryEntries(const base::FilePath& path, |
| DirectoryEntryType type) { |
| const mojom::GetEntriesMode mode = |
| type == DirectoryEntryType::kFilesOnly |
| ? mojom::GetEntriesMode::kFilesOnly |
| : mojom::GetEntriesMode::kFilesAndDirectories; |
| if (!remote_directory_) |
| return FilesystemImpl::GetDirectoryEntries(MaybeMakeAbsolute(path), mode); |
| |
| base::File::Error error = base::File::FILE_ERROR_IO; |
| std::vector<base::FilePath> entries; |
| remote_directory_->GetEntries(MakeRelative(path), mode, &error, &entries); |
| if (error != base::File::FILE_OK) |
| return base::unexpected(error); |
| |
| // Fix up all the relative paths to be absolute. |
| const base::FilePath root = path.IsAbsolute() ? path : root_.Append(path); |
| for (auto& entry : entries) |
| entry = root.Append(entry); |
| return entries; |
| } |
| |
| base::FileErrorOr<base::File> FilesystemProxy::OpenFile( |
| const base::FilePath& path, |
| int flags) { |
| if (!remote_directory_) { |
| base::File file(MaybeMakeAbsolute(path), flags); |
| if (!file.IsValid()) |
| return base::unexpected(file.error_details()); |
| return file; |
| } |
| |
| // NOTE: Remote directories only support a subset of |flags| values. |
| const int kModeMask = base::File::FLAG_OPEN | base::File::FLAG_CREATE | |
| base::File::FLAG_OPEN_ALWAYS | |
| base::File::FLAG_CREATE_ALWAYS | |
| base::File::FLAG_OPEN_TRUNCATED; |
| const int kWriteMask = base::File::FLAG_WRITE | base::File::FLAG_APPEND; |
| const int kSupportedFlagsMask = |
| kModeMask | kWriteMask | base::File::FLAG_READ; |
| DCHECK((flags & ~kSupportedFlagsMask) == 0) << "Unsupported flags: " << flags; |
| |
| const int mode_flags = flags & kModeMask; |
| mojom::FileOpenMode mode; |
| switch (mode_flags) { |
| case base::File::FLAG_OPEN: |
| mode = mojom::FileOpenMode::kOpenIfExists; |
| break; |
| case base::File::FLAG_CREATE: |
| mode = mojom::FileOpenMode::kCreateAndOpenOnlyIfNotExists; |
| break; |
| case base::File::FLAG_OPEN_ALWAYS: |
| mode = mojom::FileOpenMode::kAlwaysOpen; |
| break; |
| case base::File::FLAG_CREATE_ALWAYS: |
| mode = mojom::FileOpenMode::kAlwaysCreate; |
| break; |
| case base::File::FLAG_OPEN_TRUNCATED: |
| mode = mojom::FileOpenMode::kOpenIfExistsAndTruncate; |
| break; |
| default: |
| NOTREACHED() << "Invalid open mode flags: " << mode_flags; |
| } |
| |
| mojom::FileReadAccess read_access = |
| (flags & base::File::FLAG_READ) != 0 |
| ? mojom::FileReadAccess::kReadAllowed |
| : mojom::FileReadAccess::kReadNotAllowed; |
| |
| const int write_flags = flags & kWriteMask; |
| mojom::FileWriteAccess write_access; |
| switch (write_flags) { |
| case 0: |
| write_access = mojom::FileWriteAccess::kWriteNotAllowed; |
| break; |
| case base::File::FLAG_WRITE: |
| write_access = mojom::FileWriteAccess::kWriteAllowed; |
| break; |
| case base::File::FLAG_APPEND: |
| write_access = mojom::FileWriteAccess::kAppendOnly; |
| break; |
| default: |
| NOTREACHED() << "Invalid write access flags: " << write_flags; |
| } |
| |
| base::File::Error error = base::File::FILE_ERROR_IO; |
| base::File file; |
| remote_directory_->OpenFile(MakeRelative(path), mode, read_access, |
| write_access, &error, &file); |
| if (error != base::File::FILE_OK) |
| return base::unexpected(error); |
| return file; |
| } |
| |
| base::File::Error FilesystemProxy::CreateDirectory(const base::FilePath& path) { |
| base::File::Error error = base::File::FILE_ERROR_IO; |
| if (!remote_directory_) { |
| if (!base::CreateDirectoryAndGetError(MaybeMakeAbsolute(path), &error)) |
| return error; |
| return base::File::FILE_OK; |
| } |
| |
| remote_directory_->CreateDirectory(MakeRelative(path), &error); |
| return error; |
| } |
| |
| bool FilesystemProxy::DeleteFile(const base::FilePath& path) { |
| if (!remote_directory_) { |
| const base::FilePath full_path = MaybeMakeAbsolute(path); |
| return base::DeleteFile(full_path); |
| } |
| |
| bool success = false; |
| remote_directory_->DeleteFile(MakeRelative(path), &success); |
| return success; |
| } |
| |
| std::optional<base::File::Info> FilesystemProxy::GetFileInfo( |
| const base::FilePath& path) { |
| if (!remote_directory_) { |
| base::File::Info info; |
| if (base::GetFileInfo(MaybeMakeAbsolute(path), &info)) |
| return info; |
| return std::nullopt; |
| } |
| |
| std::optional<base::File::Info> info; |
| remote_directory_->GetFileInfo(MakeRelative(path), &info); |
| return info; |
| } |
| |
| std::optional<FilesystemProxy::PathAccessInfo> FilesystemProxy::GetPathAccess( |
| const base::FilePath& path) { |
| mojom::PathAccessInfoPtr info; |
| if (!remote_directory_) |
| info = FilesystemImpl::GetPathAccessLocal(MaybeMakeAbsolute(path)); |
| else |
| remote_directory_->GetPathAccess(MakeRelative(path), &info); |
| |
| if (!info) |
| return std::nullopt; |
| |
| return PathAccessInfo{info->can_read, info->can_write}; |
| } |
| |
| base::File::Error FilesystemProxy::RenameFile(const base::FilePath& old_path, |
| const base::FilePath& new_path) { |
| base::File::Error error = base::File::FILE_ERROR_IO; |
| if (!remote_directory_) { |
| if (!base::ReplaceFile(MaybeMakeAbsolute(old_path), |
| MaybeMakeAbsolute(new_path), &error)) { |
| return error; |
| } |
| return base::File::FILE_OK; |
| } |
| |
| remote_directory_->RenameFile(MakeRelative(old_path), MakeRelative(new_path), |
| &error); |
| return error; |
| } |
| |
| base::FileErrorOr<std::unique_ptr<FilesystemProxy::FileLock>> |
| FilesystemProxy::LockFile(const base::FilePath& path, |
| bool* same_process_failure) { |
| if (!remote_directory_) { |
| base::FilePath full_path = MaybeMakeAbsolute(path); |
| ASSIGN_OR_RETURN(base::File result, FilesystemImpl::LockFileLocal( |
| full_path, same_process_failure)); |
| return std::make_unique<LocalFileLockImpl>(std::move(full_path), |
| std::move(result)); |
| } |
| |
| mojo::PendingRemote<mojom::FileLock> remote_lock; |
| base::File::Error error = base::File::FILE_ERROR_IO; |
| if (!remote_directory_->LockFile(MakeRelative(path), &error, &remote_lock)) |
| return base::unexpected(error); |
| if (error != base::File::FILE_OK) |
| return base::unexpected(error); |
| |
| return std::make_unique<RemoteFileLockImpl>(std::move(remote_lock)); |
| } |
| |
| base::FilePath FilesystemProxy::MakeRelative(const base::FilePath& path) const { |
| DCHECK(remote_directory_); |
| DCHECK(!path.ReferencesParent()); |
| |
| // For a remote Directory, returned paths must always be relative to |root|. |
| if (!path.IsAbsolute() || path.empty()) |
| return path; |
| |
| if (path == root_) |
| return base::FilePath(); |
| |
| base::FilePath relative_path; |
| CHECK(root_.AppendRelativePath(path, &relative_path)) |
| << " Failed making " << path << " relative to " << root_; |
| return relative_path; |
| } |
| |
| base::FilePath FilesystemProxy::MaybeMakeAbsolute( |
| const base::FilePath& path) const { |
| DCHECK(!remote_directory_); |
| if (path.IsAbsolute() || root_.empty()) |
| return path; |
| |
| return root_.Append(path); |
| } |
| |
| } // namespace storage |