blob: e853a91fd4219e7d346692ece69e8eded98db421 [file] [log] [blame]
// 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