|  | // 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(); | 
|  | } | 
|  |  | 
|  | base::File::Error FileImpl::RawLockFile() { | 
|  | return file_.Lock(); | 
|  | } | 
|  |  | 
|  | base::File::Error FileImpl::RawUnlockFile() { | 
|  | return file_.Unlock(); | 
|  | } | 
|  |  | 
|  | void FileImpl::Close(const CloseCallback& callback) { | 
|  | if (!file_.IsValid()) { | 
|  | callback.Run(GetError(file_)); | 
|  | return; | 
|  | } | 
|  |  | 
|  | lock_table_->RemoveFromLockTable(path_); | 
|  | file_.Close(); | 
|  | 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, | 
|  | const ReadCallback& callback) { | 
|  | if (!file_.IsValid()) { | 
|  | callback.Run(GetError(file_), base::nullopt); | 
|  | return; | 
|  | } | 
|  | if (num_bytes_to_read > kMaxReadSize) { | 
|  | callback.Run(mojom::FileError::INVALID_OPERATION, base::nullopt); | 
|  | return; | 
|  | } | 
|  | mojom::FileError error = IsOffsetValid(offset); | 
|  | if (error != mojom::FileError::OK) { | 
|  | callback.Run(error, base::nullopt); | 
|  | return; | 
|  | } | 
|  | error = IsWhenceValid(whence); | 
|  | if (error != mojom::FileError::OK) { | 
|  | callback.Run(error, base::nullopt); | 
|  | return; | 
|  | } | 
|  |  | 
|  | if (file_.Seek(static_cast<base::File::Whence>(whence), offset) == -1) { | 
|  | 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) { | 
|  | 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)); | 
|  | 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, | 
|  | const WriteCallback& callback) { | 
|  | if (!file_.IsValid()) { | 
|  | 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 | 
|  | callback.Run(mojom::FileError::INVALID_OPERATION, 0); | 
|  | return; | 
|  | } | 
|  | mojom::FileError error = IsOffsetValid(offset); | 
|  | if (error != mojom::FileError::OK) { | 
|  | callback.Run(error, 0); | 
|  | return; | 
|  | } | 
|  | error = IsWhenceValid(whence); | 
|  | if (error != mojom::FileError::OK) { | 
|  | callback.Run(error, 0); | 
|  | return; | 
|  | } | 
|  |  | 
|  | if (file_.Seek(static_cast<base::File::Whence>(whence), offset) == -1) { | 
|  | 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) { | 
|  | callback.Run(mojom::FileError::FAILED, 0); | 
|  | return; | 
|  | } | 
|  |  | 
|  | DCHECK_LE(static_cast<size_t>(num_bytes_written), | 
|  | std::numeric_limits<uint32_t>::max()); | 
|  | callback.Run(mojom::FileError::OK, static_cast<uint32_t>(num_bytes_written)); | 
|  | } | 
|  |  | 
|  | void FileImpl::Tell(const TellCallback& callback) { | 
|  | Seek(0, mojom::Whence::FROM_CURRENT, callback); | 
|  | } | 
|  |  | 
|  | void FileImpl::Seek(int64_t offset, | 
|  | mojom::Whence whence, | 
|  | const SeekCallback& callback) { | 
|  | if (!file_.IsValid()) { | 
|  | callback.Run(GetError(file_), 0); | 
|  | return; | 
|  | } | 
|  | mojom::FileError error = IsOffsetValid(offset); | 
|  | if (error != mojom::FileError::OK) { | 
|  | callback.Run(error, 0); | 
|  | return; | 
|  | } | 
|  | error = IsWhenceValid(whence); | 
|  | if (error != mojom::FileError::OK) { | 
|  | callback.Run(error, 0); | 
|  | return; | 
|  | } | 
|  |  | 
|  | int64_t position = | 
|  | file_.Seek(static_cast<base::File::Whence>(whence), offset); | 
|  | if (position < 0) { | 
|  | callback.Run(mojom::FileError::FAILED, 0); | 
|  | return; | 
|  | } | 
|  |  | 
|  | callback.Run(mojom::FileError::OK, static_cast<int64_t>(position)); | 
|  | } | 
|  |  | 
|  | void FileImpl::Stat(const StatCallback& callback) { | 
|  | if (!file_.IsValid()) { | 
|  | callback.Run(GetError(file_), nullptr); | 
|  | return; | 
|  | } | 
|  |  | 
|  | base::File::Info info; | 
|  | if (!file_.GetInfo(&info)) { | 
|  | callback.Run(mojom::FileError::FAILED, nullptr); | 
|  | return; | 
|  | } | 
|  |  | 
|  | callback.Run(mojom::FileError::OK, MakeFileInformation(info)); | 
|  | } | 
|  |  | 
|  | void FileImpl::Truncate(int64_t size, const TruncateCallback& callback) { | 
|  | if (!file_.IsValid()) { | 
|  | callback.Run(GetError(file_)); | 
|  | return; | 
|  | } | 
|  | if (size < 0) { | 
|  | callback.Run(mojom::FileError::INVALID_OPERATION); | 
|  | return; | 
|  | } | 
|  | mojom::FileError error = IsOffsetValid(size); | 
|  | if (error != mojom::FileError::OK) { | 
|  | callback.Run(error); | 
|  | return; | 
|  | } | 
|  |  | 
|  | if (!file_.SetLength(size)) { | 
|  | callback.Run(mojom::FileError::NOT_FOUND); | 
|  | return; | 
|  | } | 
|  |  | 
|  | callback.Run(mojom::FileError::OK); | 
|  | } | 
|  |  | 
|  | void FileImpl::Touch(mojom::TimespecOrNowPtr atime, | 
|  | mojom::TimespecOrNowPtr mtime, | 
|  | const TouchCallback& callback) { | 
|  | if (!file_.IsValid()) { | 
|  | callback.Run(GetError(file_)); | 
|  | return; | 
|  | } | 
|  |  | 
|  | base::Time base_atime = Time::Now(); | 
|  | if (!atime) { | 
|  | base::File::Info info; | 
|  | if (!file_.GetInfo(&info)) { | 
|  | 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)) { | 
|  | 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); | 
|  | callback.Run(mojom::FileError::OK); | 
|  | } | 
|  |  | 
|  | void FileImpl::Dup(mojom::FileRequest file, const DupCallback& callback) { | 
|  | if (!file_.IsValid()) { | 
|  | callback.Run(GetError(file_)); | 
|  | return; | 
|  | } | 
|  |  | 
|  | base::File new_file = file_.Duplicate(); | 
|  | if (!new_file.IsValid()) { | 
|  | 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)); | 
|  | } | 
|  | callback.Run(mojom::FileError::OK); | 
|  | } | 
|  |  | 
|  | void FileImpl::Flush(const FlushCallback& callback) { | 
|  | if (!file_.IsValid()) { | 
|  | callback.Run(GetError(file_)); | 
|  | return; | 
|  | } | 
|  |  | 
|  | bool ret = file_.Flush(); | 
|  | callback.Run(ret ? mojom::FileError::OK : mojom::FileError::FAILED); | 
|  | } | 
|  |  | 
|  | void FileImpl::Lock(const LockCallback& callback) { | 
|  | callback.Run( | 
|  | static_cast<filesystem::mojom::FileError>(lock_table_->LockFile(this))); | 
|  | } | 
|  |  | 
|  | void FileImpl::Unlock(const UnlockCallback& callback) { | 
|  | callback.Run( | 
|  | static_cast<filesystem::mojom::FileError>(lock_table_->UnlockFile(this))); | 
|  | } | 
|  |  | 
|  | void FileImpl::AsHandle(const AsHandleCallback& callback) { | 
|  | if (!file_.IsValid()) { | 
|  | callback.Run(GetError(file_), base::File()); | 
|  | return; | 
|  | } | 
|  |  | 
|  | base::File new_file = file_.Duplicate(); | 
|  | if (!new_file.IsValid()) { | 
|  | callback.Run(GetError(new_file), base::File()); | 
|  | return; | 
|  | } | 
|  |  | 
|  | base::File::Info info; | 
|  | if (!new_file.GetInfo(&info)) { | 
|  | 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) { | 
|  | callback.Run(mojom::FileError::NOT_A_FILE, base::File()); | 
|  | return; | 
|  | } | 
|  |  | 
|  | callback.Run(mojom::FileError::OK, std::move(new_file)); | 
|  | } | 
|  |  | 
|  | }  // namespace filesystem |