| // Copyright 2015 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "components/filesystem/file_impl.h" |
| |
| #include <stddef.h> |
| #include <stdint.h> |
| #include <limits> |
| #include <utility> |
| |
| #include "base/files/file_path.h" |
| #include "base/files/scoped_file.h" |
| #include "base/logging.h" |
| #include "base/memory/ptr_util.h" |
| #include "build/build_config.h" |
| #include "components/filesystem/lock_table.h" |
| #include "components/filesystem/shared_temp_dir.h" |
| #include "components/filesystem/util.h" |
| #include "mojo/public/cpp/bindings/strong_binding.h" |
| |
| static_assert(sizeof(off_t) <= sizeof(int64_t), "off_t too big"); |
| static_assert(sizeof(size_t) >= sizeof(uint32_t), "size_t too small"); |
| |
| using base::Time; |
| |
| namespace filesystem { |
| namespace { |
| |
| const size_t kMaxReadSize = 1 * 1024 * 1024; // 1 MB. |
| |
| } // namespace |
| |
| FileImpl::FileImpl(const base::FilePath& path, |
| uint32_t flags, |
| scoped_refptr<SharedTempDir> temp_dir, |
| scoped_refptr<LockTable> lock_table) |
| : file_(path, flags), |
| path_(path), |
| temp_dir_(std::move(temp_dir)), |
| lock_table_(std::move(lock_table)) { |
| DCHECK(file_.IsValid()); |
| } |
| |
| FileImpl::FileImpl(const base::FilePath& path, |
| base::File file, |
| scoped_refptr<SharedTempDir> temp_dir, |
| scoped_refptr<LockTable> lock_table) |
| : file_(std::move(file)), |
| path_(path), |
| temp_dir_(std::move(temp_dir)), |
| lock_table_(std::move(lock_table)) { |
| DCHECK(file_.IsValid()); |
| } |
| |
| FileImpl::~FileImpl() { |
| if (file_.IsValid()) |
| lock_table_->RemoveFromLockTable(path_); |
| } |
| |
| bool FileImpl::IsValid() const { |
| return file_.IsValid(); |
| } |
| |
| #if !defined(OS_FUCHSIA) |
| base::File::Error FileImpl::RawLockFile() { |
| return file_.Lock(); |
| } |
| |
| base::File::Error FileImpl::RawUnlockFile() { |
| return file_.Unlock(); |
| } |
| #endif // !OS_FUCHSIA |
| |
| void FileImpl::Close(CloseCallback callback) { |
| if (!file_.IsValid()) { |
| std::move(callback).Run(GetError(file_)); |
| return; |
| } |
| |
| lock_table_->RemoveFromLockTable(path_); |
| file_.Close(); |
| std::move(callback).Run(mojom::FileError::OK); |
| } |
| |
| // TODO(vtl): Move the implementation to a thread pool. |
| void FileImpl::Read(uint32_t num_bytes_to_read, |
| int64_t offset, |
| mojom::Whence whence, |
| ReadCallback callback) { |
| if (!file_.IsValid()) { |
| std::move(callback).Run(GetError(file_), base::nullopt); |
| return; |
| } |
| if (num_bytes_to_read > kMaxReadSize) { |
| std::move(callback).Run(mojom::FileError::INVALID_OPERATION, base::nullopt); |
| return; |
| } |
| mojom::FileError error = IsOffsetValid(offset); |
| if (error != mojom::FileError::OK) { |
| std::move(callback).Run(error, base::nullopt); |
| return; |
| } |
| error = IsWhenceValid(whence); |
| if (error != mojom::FileError::OK) { |
| std::move(callback).Run(error, base::nullopt); |
| return; |
| } |
| |
| if (file_.Seek(static_cast<base::File::Whence>(whence), offset) == -1) { |
| std::move(callback).Run(mojom::FileError::FAILED, base::nullopt); |
| return; |
| } |
| |
| std::vector<uint8_t> bytes_read(num_bytes_to_read); |
| int num_bytes_read = file_.ReadAtCurrentPos( |
| reinterpret_cast<char*>(&bytes_read.front()), num_bytes_to_read); |
| if (num_bytes_read < 0) { |
| std::move(callback).Run(mojom::FileError::FAILED, base::nullopt); |
| return; |
| } |
| |
| DCHECK_LE(static_cast<size_t>(num_bytes_read), num_bytes_to_read); |
| bytes_read.resize(static_cast<size_t>(num_bytes_read)); |
| std::move(callback).Run(mojom::FileError::OK, std::move(bytes_read)); |
| } |
| |
| // TODO(vtl): Move the implementation to a thread pool. |
| void FileImpl::Write(const std::vector<uint8_t>& bytes_to_write, |
| int64_t offset, |
| mojom::Whence whence, |
| WriteCallback callback) { |
| if (!file_.IsValid()) { |
| std::move(callback).Run(GetError(file_), 0); |
| return; |
| } |
| // Who knows what |write()| would return if the size is that big (and it |
| // actually wrote that much). |
| if (bytes_to_write.size() > |
| #if defined(OS_WIN) |
| static_cast<size_t>(std::numeric_limits<int>::max())) { |
| #else |
| static_cast<size_t>(std::numeric_limits<ssize_t>::max())) { |
| #endif |
| std::move(callback).Run(mojom::FileError::INVALID_OPERATION, 0); |
| return; |
| } |
| mojom::FileError error = IsOffsetValid(offset); |
| if (error != mojom::FileError::OK) { |
| std::move(callback).Run(error, 0); |
| return; |
| } |
| error = IsWhenceValid(whence); |
| if (error != mojom::FileError::OK) { |
| std::move(callback).Run(error, 0); |
| return; |
| } |
| |
| if (file_.Seek(static_cast<base::File::Whence>(whence), offset) == -1) { |
| std::move(callback).Run(mojom::FileError::FAILED, 0); |
| return; |
| } |
| |
| const char* buf = (bytes_to_write.size() > 0) |
| ? reinterpret_cast<const char*>(&bytes_to_write.front()) |
| : nullptr; |
| int num_bytes_written = file_.WriteAtCurrentPos( |
| buf, static_cast<int>(bytes_to_write.size())); |
| if (num_bytes_written < 0) { |
| std::move(callback).Run(mojom::FileError::FAILED, 0); |
| return; |
| } |
| |
| DCHECK_LE(static_cast<size_t>(num_bytes_written), |
| std::numeric_limits<uint32_t>::max()); |
| std::move(callback).Run(mojom::FileError::OK, |
| static_cast<uint32_t>(num_bytes_written)); |
| } |
| |
| void FileImpl::Tell(TellCallback callback) { |
| Seek(0, mojom::Whence::FROM_CURRENT, std::move(callback)); |
| } |
| |
| void FileImpl::Seek(int64_t offset, |
| mojom::Whence whence, |
| SeekCallback callback) { |
| if (!file_.IsValid()) { |
| std::move(callback).Run(GetError(file_), 0); |
| return; |
| } |
| mojom::FileError error = IsOffsetValid(offset); |
| if (error != mojom::FileError::OK) { |
| std::move(callback).Run(error, 0); |
| return; |
| } |
| error = IsWhenceValid(whence); |
| if (error != mojom::FileError::OK) { |
| std::move(callback).Run(error, 0); |
| return; |
| } |
| |
| int64_t position = |
| file_.Seek(static_cast<base::File::Whence>(whence), offset); |
| if (position < 0) { |
| std::move(callback).Run(mojom::FileError::FAILED, 0); |
| return; |
| } |
| |
| std::move(callback).Run(mojom::FileError::OK, static_cast<int64_t>(position)); |
| } |
| |
| void FileImpl::Stat(StatCallback callback) { |
| if (!file_.IsValid()) { |
| std::move(callback).Run(GetError(file_), nullptr); |
| return; |
| } |
| |
| base::File::Info info; |
| if (!file_.GetInfo(&info)) { |
| std::move(callback).Run(mojom::FileError::FAILED, nullptr); |
| return; |
| } |
| |
| std::move(callback).Run(mojom::FileError::OK, MakeFileInformation(info)); |
| } |
| |
| void FileImpl::Truncate(int64_t size, TruncateCallback callback) { |
| if (!file_.IsValid()) { |
| std::move(callback).Run(GetError(file_)); |
| return; |
| } |
| if (size < 0) { |
| std::move(callback).Run(mojom::FileError::INVALID_OPERATION); |
| return; |
| } |
| mojom::FileError error = IsOffsetValid(size); |
| if (error != mojom::FileError::OK) { |
| std::move(callback).Run(error); |
| return; |
| } |
| |
| if (!file_.SetLength(size)) { |
| std::move(callback).Run(mojom::FileError::NOT_FOUND); |
| return; |
| } |
| |
| std::move(callback).Run(mojom::FileError::OK); |
| } |
| |
| void FileImpl::Touch(mojom::TimespecOrNowPtr atime, |
| mojom::TimespecOrNowPtr mtime, |
| TouchCallback callback) { |
| if (!file_.IsValid()) { |
| std::move(callback).Run(GetError(file_)); |
| return; |
| } |
| |
| base::Time base_atime = Time::Now(); |
| if (!atime) { |
| base::File::Info info; |
| if (!file_.GetInfo(&info)) { |
| std::move(callback).Run(mojom::FileError::FAILED); |
| return; |
| } |
| |
| base_atime = info.last_accessed; |
| } else if (!atime->now) { |
| base_atime = Time::FromDoubleT(atime->seconds); |
| } |
| |
| base::Time base_mtime = Time::Now(); |
| if (!mtime) { |
| base::File::Info info; |
| if (!file_.GetInfo(&info)) { |
| std::move(callback).Run(mojom::FileError::FAILED); |
| return; |
| } |
| |
| base_mtime = info.last_modified; |
| } else if (!mtime->now) { |
| base_mtime = Time::FromDoubleT(mtime->seconds); |
| } |
| |
| file_.SetTimes(base_atime, base_mtime); |
| std::move(callback).Run(mojom::FileError::OK); |
| } |
| |
| void FileImpl::Dup(mojom::FileRequest file, DupCallback callback) { |
| if (!file_.IsValid()) { |
| std::move(callback).Run(GetError(file_)); |
| return; |
| } |
| |
| base::File new_file = file_.Duplicate(); |
| if (!new_file.IsValid()) { |
| std::move(callback).Run(GetError(new_file)); |
| return; |
| } |
| |
| if (file.is_pending()) { |
| mojo::MakeStrongBinding( |
| base::MakeUnique<FileImpl>(path_, std::move(new_file), temp_dir_, |
| lock_table_), |
| std::move(file)); |
| } |
| std::move(callback).Run(mojom::FileError::OK); |
| } |
| |
| void FileImpl::Flush(FlushCallback callback) { |
| if (!file_.IsValid()) { |
| std::move(callback).Run(GetError(file_)); |
| return; |
| } |
| |
| bool ret = file_.Flush(); |
| std::move(callback).Run(ret ? mojom::FileError::OK |
| : mojom::FileError::FAILED); |
| } |
| |
| void FileImpl::Lock(LockCallback callback) { |
| std::move(callback).Run( |
| static_cast<filesystem::mojom::FileError>(lock_table_->LockFile(this))); |
| } |
| |
| void FileImpl::Unlock(UnlockCallback callback) { |
| std::move(callback).Run( |
| static_cast<filesystem::mojom::FileError>(lock_table_->UnlockFile(this))); |
| } |
| |
| void FileImpl::AsHandle(AsHandleCallback callback) { |
| if (!file_.IsValid()) { |
| std::move(callback).Run(GetError(file_), base::File()); |
| return; |
| } |
| |
| base::File new_file = file_.Duplicate(); |
| if (!new_file.IsValid()) { |
| std::move(callback).Run(GetError(new_file), base::File()); |
| return; |
| } |
| |
| base::File::Info info; |
| if (!new_file.GetInfo(&info)) { |
| std::move(callback).Run(mojom::FileError::FAILED, base::File()); |
| return; |
| } |
| |
| // Perform one additional check right before we send the file's file |
| // descriptor over mojo. This is theoretically redundant, but given that |
| // passing a file descriptor to a directory is a sandbox escape on Windows, |
| // we should be absolutely paranoid. |
| if (info.is_directory) { |
| std::move(callback).Run(mojom::FileError::NOT_A_FILE, base::File()); |
| return; |
| } |
| |
| std::move(callback).Run(mojom::FileError::OK, std::move(new_file)); |
| } |
| |
| } // namespace filesystem |