| // 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_impl.h" |
| |
| #include <set> |
| #include <vector> |
| |
| #include "base/check.h" |
| #include "base/containers/contains.h" |
| #include "base/files/file.h" |
| #include "base/files/file_enumerator.h" |
| #include "base/files/file_util.h" |
| #include "base/files/important_file_writer.h" |
| #include "base/no_destructor.h" |
| #include "base/notreached.h" |
| #include "base/synchronization/lock.h" |
| #include "build/build_config.h" |
| #include "mojo/public/cpp/bindings/self_owned_receiver.h" |
| |
| #if BUILDFLAG(IS_WIN) |
| #include <windows.h> |
| #endif |
| |
| namespace storage { |
| |
| namespace { |
| |
| // Retains a mapping of lock file paths which have been locked by |
| // |FilesystemImpl::LockFile| and not yet released. |
| class LockTable { |
| public: |
| LockTable() = default; |
| LockTable(const LockTable&) = delete; |
| LockTable& operator=(const LockTable&) = delete; |
| ~LockTable() = default; |
| |
| bool AddLock(const base::FilePath& path) { |
| DCHECK(path.IsAbsolute()); |
| base::AutoLock lock(lock_); |
| auto result = lock_paths_.insert(path.NormalizePathSeparators()); |
| return result.second; |
| } |
| |
| void RemoveLock(const base::FilePath& path) { |
| const base::FilePath normalized_path = path.NormalizePathSeparators(); |
| base::AutoLock lock(lock_); |
| DCHECK(base::Contains(lock_paths_, normalized_path)); |
| lock_paths_.erase(normalized_path); |
| } |
| |
| private: |
| base::Lock lock_; |
| std::set<base::FilePath> lock_paths_ GUARDED_BY(lock_); |
| }; |
| |
| // Get the global singleton instance of LockTable. This returned object is |
| // thread-safe. |
| LockTable& GetLockTable() { |
| static base::NoDestructor<LockTable> table; |
| return *table; |
| } |
| |
| class FileLockImpl : public mojom::FileLock { |
| public: |
| FileLockImpl(const base::FilePath& path, base::File file) |
| : path_(path), file_(std::move(file)) { |
| DCHECK(file_.IsValid()); |
| } |
| |
| ~FileLockImpl() override { |
| if (file_.IsValid()) |
| GetLockTable().RemoveLock(path_); |
| } |
| |
| // mojom::FileLock implementation: |
| void Release(ReleaseCallback callback) override { |
| if (!file_.IsValid()) { |
| std::move(callback).Run(base::File::FILE_ERROR_INVALID_OPERATION); |
| return; |
| } |
| |
| #if BUILDFLAG(IS_FUCHSIA) |
| std::move(callback).Run(base::File::FILE_OK); |
| #else |
| std::move(callback).Run(file_.Unlock()); |
| #endif |
| GetLockTable().RemoveLock(path_); |
| file_.Close(); |
| } |
| |
| private: |
| const base::FilePath path_; |
| base::File file_; |
| }; |
| |
| } // namespace |
| |
| FilesystemImpl::FilesystemImpl(const base::FilePath& root, |
| ClientType client_type) |
| : root_(root), client_type_(client_type) {} |
| |
| FilesystemImpl::~FilesystemImpl() = default; |
| |
| void FilesystemImpl::Clone(mojo::PendingReceiver<mojom::Directory> receiver) { |
| mojo::MakeSelfOwnedReceiver( |
| std::make_unique<FilesystemImpl>(root_, client_type_), |
| std::move(receiver)); |
| } |
| |
| void FilesystemImpl::PathExists(const base::FilePath& path, |
| PathExistsCallback callback) { |
| std::move(callback).Run(base::PathExists(MakeAbsolute(path))); |
| } |
| |
| void FilesystemImpl::GetEntries(const base::FilePath& path, |
| mojom::GetEntriesMode mode, |
| GetEntriesCallback callback) { |
| const base::FilePath full_path = MakeAbsolute(path); |
| base::FileErrorOr<std::vector<base::FilePath>> result = |
| GetDirectoryEntries(full_path, mode); |
| if (!result.has_value()) { |
| std::move(callback).Run(result.error(), std::vector<base::FilePath>()); |
| return; |
| } |
| |
| // Fix up the absolute paths to be relative to |path|. |
| std::vector<base::FilePath> entries; |
| std::vector<base::FilePath::StringType> root_components = |
| full_path.GetComponents(); |
| const size_t num_components_to_strip = root_components.size(); |
| for (const auto& entry : result.value()) { |
| std::vector<base::FilePath::StringType> components = entry.GetComponents(); |
| base::FilePath relative_path; |
| for (size_t i = num_components_to_strip; i < components.size(); ++i) |
| relative_path = relative_path.Append(components[i]); |
| entries.push_back(std::move(relative_path)); |
| } |
| std::move(callback).Run(base::File::FILE_OK, entries); |
| } |
| |
| void FilesystemImpl::OpenFile(const base::FilePath& path, |
| mojom::FileOpenMode mode, |
| mojom::FileReadAccess read_access, |
| mojom::FileWriteAccess write_access, |
| OpenFileCallback callback) { |
| uint32_t flags = 0; |
| switch (mode) { |
| case mojom::FileOpenMode::kOpenIfExists: |
| flags |= base::File::FLAG_OPEN; |
| break; |
| case mojom::FileOpenMode::kCreateAndOpenOnlyIfNotExists: |
| flags |= base::File::FLAG_CREATE; |
| break; |
| case mojom::FileOpenMode::kAlwaysOpen: |
| flags |= base::File::FLAG_OPEN_ALWAYS; |
| break; |
| case mojom::FileOpenMode::kAlwaysCreate: |
| flags |= base::File::FLAG_CREATE_ALWAYS; |
| break; |
| case mojom::FileOpenMode::kOpenIfExistsAndTruncate: |
| flags |= base::File::FLAG_OPEN_TRUNCATED; |
| break; |
| default: |
| NOTREACHED(); |
| return; |
| } |
| |
| switch (read_access) { |
| case mojom::FileReadAccess::kReadNotAllowed: |
| break; |
| case mojom::FileReadAccess::kReadAllowed: |
| flags |= base::File::FLAG_READ; |
| break; |
| default: |
| NOTREACHED(); |
| break; |
| } |
| |
| switch (write_access) { |
| case mojom::FileWriteAccess::kWriteNotAllowed: |
| break; |
| case mojom::FileWriteAccess::kWriteAllowed: |
| flags |= base::File::FLAG_WRITE; |
| break; |
| case mojom::FileWriteAccess::kAppendOnly: |
| flags |= base::File::FLAG_APPEND; |
| break; |
| default: |
| NOTREACHED(); |
| break; |
| } |
| |
| if (client_type_ == ClientType::kUntrusted) { |
| // This file may be passed to an untrusted process. |
| flags = base::File::AddFlagsForPassingToUntrustedProcess(flags); |
| } |
| |
| const base::FilePath full_path = MakeAbsolute(path); |
| base::File file(full_path, flags); |
| base::File::Error error = base::File::FILE_OK; |
| if (!file.IsValid()) |
| error = file.error_details(); |
| std::move(callback).Run(error, std::move(file)); |
| } |
| |
| void FilesystemImpl::WriteFileAtomically(const base::FilePath& path, |
| const std::string& contents, |
| WriteFileAtomicallyCallback callback) { |
| std::move(callback).Run(base::ImportantFileWriter::WriteFileAtomically( |
| MakeAbsolute(path), std::move(contents))); |
| } |
| |
| void FilesystemImpl::CreateDirectory(const base::FilePath& path, |
| CreateDirectoryCallback callback) { |
| base::File::Error error = base::File::FILE_OK; |
| base::CreateDirectoryAndGetError(MakeAbsolute(path), &error); |
| std::move(callback).Run(error); |
| } |
| |
| void FilesystemImpl::DeleteFile(const base::FilePath& path, |
| DeleteFileCallback callback) { |
| std::move(callback).Run(base::DeleteFile(MakeAbsolute(path))); |
| } |
| |
| void FilesystemImpl::DeletePathRecursively( |
| const base::FilePath& path, |
| DeletePathRecursivelyCallback callback) { |
| std::move(callback).Run(base::DeletePathRecursively(MakeAbsolute(path))); |
| } |
| |
| void FilesystemImpl::GetFileInfo(const base::FilePath& path, |
| GetFileInfoCallback callback) { |
| base::File::Info info; |
| if (base::GetFileInfo(MakeAbsolute(path), &info)) |
| std::move(callback).Run(std::move(info)); |
| else |
| std::move(callback).Run(absl::nullopt); |
| } |
| |
| void FilesystemImpl::GetPathAccess(const base::FilePath& path, |
| GetPathAccessCallback callback) { |
| std::move(callback).Run(GetPathAccessLocal(MakeAbsolute(path))); |
| } |
| |
| void FilesystemImpl::GetMaximumPathComponentLength( |
| const base::FilePath& path, |
| GetMaximumPathComponentLengthCallback callback) { |
| int len = base::GetMaximumPathComponentLength(MakeAbsolute(path)); |
| bool success = len != -1; |
| return std::move(callback).Run(success, len); |
| } |
| |
| void FilesystemImpl::RenameFile(const base::FilePath& old_path, |
| const base::FilePath& new_path, |
| RenameFileCallback callback) { |
| base::File::Error error = base::File::FILE_OK; |
| base::ReplaceFile(MakeAbsolute(old_path), MakeAbsolute(new_path), &error); |
| std::move(callback).Run(error); |
| } |
| |
| void FilesystemImpl::LockFile(const base::FilePath& path, |
| LockFileCallback callback) { |
| base::FileErrorOr<base::File> result = LockFileLocal(MakeAbsolute(path)); |
| if (!result.has_value()) { |
| std::move(callback).Run(result.error(), mojo::NullRemote()); |
| return; |
| } |
| |
| mojo::PendingRemote<mojom::FileLock> lock; |
| mojo::MakeSelfOwnedReceiver( |
| std::make_unique<FileLockImpl>(MakeAbsolute(path), |
| std::move(result.value())), |
| lock.InitWithNewPipeAndPassReceiver()); |
| std::move(callback).Run(base::File::FILE_OK, std::move(lock)); |
| } |
| |
| void FilesystemImpl::SetOpenedFileLength(base::File file, |
| uint64_t length, |
| SetOpenedFileLengthCallback callback) { |
| bool success = file.SetLength(length); |
| std::move(callback).Run(success, std::move(file)); |
| } |
| |
| // static |
| base::FileErrorOr<base::File> FilesystemImpl::LockFileLocal( |
| const base::FilePath& path) { |
| DCHECK(path.IsAbsolute()); |
| base::File file(path, base::File::FLAG_OPEN_ALWAYS | base::File::FLAG_READ | |
| base::File::FLAG_WRITE); |
| if (!file.IsValid()) |
| return base::unexpected(file.error_details()); |
| |
| if (!GetLockTable().AddLock(path)) |
| return base::unexpected(base::File::FILE_ERROR_IN_USE); |
| |
| #if !BUILDFLAG(IS_FUCHSIA) |
| base::File::Error error = file.Lock(base::File::LockMode::kExclusive); |
| if (error != base::File::FILE_OK) |
| return base::unexpected(error); |
| #endif |
| |
| return file; |
| } |
| |
| // static |
| void FilesystemImpl::UnlockFileLocal(const base::FilePath& path) { |
| GetLockTable().RemoveLock(path); |
| } |
| |
| // static |
| mojom::PathAccessInfoPtr FilesystemImpl::GetPathAccessLocal( |
| const base::FilePath& path) { |
| mojom::PathAccessInfoPtr info; |
| #if BUILDFLAG(IS_WIN) |
| uint32_t attributes = ::GetFileAttributes(path.value().c_str()); |
| if (attributes != INVALID_FILE_ATTRIBUTES) { |
| info = mojom::PathAccessInfo::New(); |
| info->can_read = true; |
| if ((attributes & FILE_ATTRIBUTE_READONLY) == 0) |
| info->can_write = true; |
| } |
| #elif BUILDFLAG(IS_POSIX) || BUILDFLAG(IS_FUCHSIA) |
| const char* const c_path = path.value().c_str(); |
| if (!access(c_path, F_OK)) { |
| info = mojom::PathAccessInfo::New(); |
| info->can_read = !access(c_path, R_OK); |
| info->can_write = !access(c_path, W_OK); |
| } |
| #endif |
| return info; |
| } |
| |
| // static |
| base::FileErrorOr<std::vector<base::FilePath>> |
| FilesystemImpl::GetDirectoryEntries(const base::FilePath& path, |
| mojom::GetEntriesMode mode) { |
| DCHECK(path.IsAbsolute()); |
| int file_types = base::FileEnumerator::FILES; |
| if (mode == mojom::GetEntriesMode::kFilesAndDirectories) |
| file_types |= base::FileEnumerator::DIRECTORIES; |
| base::FileEnumerator enumerator( |
| path, /*recursive=*/false, file_types, |
| /*pattern=*/base::FilePath::StringType(), |
| base::FileEnumerator::FolderSearchPolicy::ALL, |
| base::FileEnumerator::ErrorPolicy::STOP_ENUMERATION); |
| std::vector<base::FilePath> entries; |
| for (base::FilePath entry = enumerator.Next(); !entry.empty(); |
| entry = enumerator.Next()) { |
| entries.push_back(entry); |
| } |
| if (enumerator.GetError() != base::File::FILE_OK) |
| return base::unexpected(enumerator.GetError()); |
| return entries; |
| } |
| |
| base::FilePath FilesystemImpl::MakeAbsolute(const base::FilePath& path) const { |
| // The DCHECK is a reasonable assertion: this object is only called into via |
| // Mojo, and type-map traits for |storage.mojom.StrictRelativePath| ensure |
| // that messages can only reach this object if they carry strictly relative |
| // paths. |
| DCHECK(!path.IsAbsolute()); |
| return root_.Append(path); |
| } |
| |
| } // namespace storage |