blob: 3309cdb5adae6d2983fdf6061e238388b335cd08 [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 "services/files/directory_impl.h"
#include <dirent.h>
#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
#include "base/files/scoped_temp_dir.h"
#include "base/logging.h"
#include "base/memory/scoped_ptr.h"
#include "base/posix/eintr_wrapper.h"
#include "build/build_config.h"
#include "services/files/file_impl.h"
#include "services/files/shared_impl.h"
#include "services/files/util.h"
namespace mojo {
namespace files {
namespace {
// Calls |closedir()| on a |DIR*|.
struct DIRDeleter {
void operator()(DIR* dir) const { PCHECK(closedir(dir) == 0); }
};
using ScopedDIR = scoped_ptr<DIR, DIRDeleter>;
Error ValidateOpenFlags(uint32_t open_flags, bool is_directory) {
// Treat unknown flags as "unimplemented".
if ((open_flags &
~(kOpenFlagRead | kOpenFlagWrite | kOpenFlagCreate | kOpenFlagExclusive |
kOpenFlagAppend | kOpenFlagTruncate)))
return Error::UNIMPLEMENTED;
// At least one of |kOpenFlagRead| or |kOpenFlagWrite| must be set.
if (!(open_flags & (kOpenFlagRead | kOpenFlagWrite)))
return Error::INVALID_ARGUMENT;
// |kOpenFlagCreate| requires |kOpenFlagWrite|.
if ((open_flags & kOpenFlagCreate) && !(open_flags & kOpenFlagWrite))
return Error::INVALID_ARGUMENT;
// |kOpenFlagExclusive| requires |kOpenFlagCreate|.
if ((open_flags & kOpenFlagExclusive) && !(open_flags & kOpenFlagCreate))
return Error::INVALID_ARGUMENT;
if (is_directory) {
// Check that file-only flags aren't set.
if ((open_flags & (kOpenFlagAppend | kOpenFlagTruncate)))
return Error::INVALID_ARGUMENT;
return Error::OK;
}
// File-only flags:
// |kOpenFlagAppend| requires |kOpenFlagWrite|.
if ((open_flags & kOpenFlagAppend) && !(open_flags & kOpenFlagWrite))
return Error::INVALID_ARGUMENT;
// |kOpenFlagTruncate| requires |kOpenFlagWrite|.
if ((open_flags & kOpenFlagTruncate) && !(open_flags & kOpenFlagWrite))
return Error::INVALID_ARGUMENT;
return Error::OK;
}
Error ValidateDeleteFlags(uint32_t delete_flags) {
// Treat unknown flags as "unimplemented".
if ((delete_flags &
~(kDeleteFlagFileOnly | kDeleteFlagDirectoryOnly |
kDeleteFlagRecursive)))
return Error::UNIMPLEMENTED;
// Only one of the three currently-defined flags may be set.
if ((delete_flags & kDeleteFlagFileOnly) &&
(delete_flags & (kDeleteFlagDirectoryOnly | kDeleteFlagRecursive)))
return Error::INVALID_ARGUMENT;
if ((delete_flags & kDeleteFlagDirectoryOnly) &&
(delete_flags & (kDeleteFlagFileOnly | kDeleteFlagRecursive)))
return Error::INVALID_ARGUMENT;
if ((delete_flags & kDeleteFlagRecursive) &&
(delete_flags & (kDeleteFlagFileOnly | kDeleteFlagDirectoryOnly)))
return Error::INVALID_ARGUMENT;
return Error::OK;
}
} // namespace
DirectoryImpl::DirectoryImpl(InterfaceRequest<Directory> request,
base::ScopedFD dir_fd,
scoped_ptr<base::ScopedTempDir> temp_dir)
: binding_(this, request.Pass()),
dir_fd_(dir_fd.Pass()),
temp_dir_(temp_dir.Pass()) {
DCHECK(dir_fd_.is_valid());
}
DirectoryImpl::~DirectoryImpl() {
}
void DirectoryImpl::Read(const ReadCallback& callback) {
static const size_t kMaxReadCount = 1000;
DCHECK(dir_fd_.is_valid());
// |fdopendir()| takes ownership of the FD (giving it to the |DIR| --
// |closedir()| will close the FD)), so we need to |dup()| ours.
base::ScopedFD fd(dup(dir_fd_.get()));
if (!fd.is_valid()) {
callback.Run(ErrnoToError(errno), nullptr);
return;
}
ScopedDIR dir(fdopendir(fd.release()));
if (!dir) {
callback.Run(ErrnoToError(errno), nullptr);
return;
}
auto result = Array<DirectoryEntryPtr>::New(0);
// Warning: This is not portable (per POSIX.1 -- |buffer| may not be large
// enough), but it's fine for Linux.
#if !defined(OS_ANDROID) && !defined(OS_LINUX)
#error "Use of struct dirent for readdir_r() buffer not portable; please check."
#endif
struct dirent buffer;
for (size_t n = 0;;) {
struct dirent* entry = nullptr;
if (int error = readdir_r(dir.get(), &buffer, &entry)) {
// |error| is effectively an errno (for |readdir_r()|), AFAICT.
callback.Run(ErrnoToError(error), nullptr);
return;
}
if (!entry)
break;
n++;
if (n > kMaxReadCount) {
LOG(WARNING) << "Directory contents truncated";
callback.Run(Error::OUT_OF_RANGE, result.Pass());
return;
}
DirectoryEntryPtr e = DirectoryEntry::New();
switch (entry->d_type) {
case DT_DIR:
e->type = FileType::DIRECTORY;
break;
case DT_REG:
e->type = FileType::REGULAR_FILE;
break;
default:
e->type = FileType::UNKNOWN;
break;
}
e->name = String(entry->d_name);
result.push_back(e.Pass());
}
callback.Run(Error::OK, result.Pass());
}
void DirectoryImpl::Stat(const StatCallback& callback) {
DCHECK(dir_fd_.is_valid());
StatFD(dir_fd_.get(), FileType::DIRECTORY, callback);
}
void DirectoryImpl::Touch(TimespecOrNowPtr atime,
TimespecOrNowPtr mtime,
const TouchCallback& callback) {
DCHECK(dir_fd_.is_valid());
TouchFD(dir_fd_.get(), atime.Pass(), mtime.Pass(), callback);
}
// TODO(vtl): Move the implementation to a thread pool.
void DirectoryImpl::OpenFile(const String& path,
InterfaceRequest<File> file,
uint32_t open_flags,
const OpenFileCallback& callback) {
DCHECK(!path.is_null());
DCHECK(dir_fd_.is_valid());
Error error = IsPathValid(path);
if (error != Error::OK) {
callback.Run(error);
return;
}
// TODO(vtl): Make sure the path doesn't exit this directory (if appropriate).
// TODO(vtl): Maybe allow absolute paths?
error = ValidateOpenFlags(open_flags, false);
if (error != Error::OK) {
callback.Run(error);
return;
}
int flags = 0;
if ((open_flags & kOpenFlagRead))
flags |= (open_flags & kOpenFlagWrite) ? O_RDWR : O_RDONLY;
else
flags |= O_WRONLY;
if ((open_flags & kOpenFlagCreate))
flags |= O_CREAT;
if ((open_flags & kOpenFlagExclusive))
flags |= O_EXCL;
if ((open_flags & kOpenFlagAppend))
flags |= O_APPEND;
if ((open_flags & kOpenFlagTruncate))
flags |= O_TRUNC;
base::ScopedFD file_fd(
HANDLE_EINTR(openat(dir_fd_.get(), path.get().c_str(), flags, 0600)));
if (!file_fd.is_valid()) {
callback.Run(ErrnoToError(errno));
return;
}
if (file.is_pending())
new FileImpl(file.Pass(), file_fd.Pass());
callback.Run(Error::OK);
}
void DirectoryImpl::OpenDirectory(const String& path,
InterfaceRequest<Directory> directory,
uint32_t open_flags,
const OpenDirectoryCallback& callback) {
DCHECK(!path.is_null());
DCHECK(dir_fd_.is_valid());
Error error = IsPathValid(path);
if (error != Error::OK) {
callback.Run(error);
return;
}
// TODO(vtl): Make sure the path doesn't exit this directory (if appropriate).
// TODO(vtl): Maybe allow absolute paths?
error = ValidateOpenFlags(open_flags, false);
if (error != Error::OK) {
callback.Run(error);
return;
}
// TODO(vtl): Implement read-only (whatever that means).
if (!(open_flags & kOpenFlagWrite)) {
callback.Run(Error::UNIMPLEMENTED);
return;
}
if ((open_flags & kOpenFlagCreate)) {
if (mkdirat(dir_fd_.get(), path.get().c_str(), 0700) != 0) {
// Allow |EEXIST| if |kOpenFlagExclusive| is not set. Note, however, that
// it does not guarantee that |path| is a directory.
// TODO(vtl): Hrm, ponder if we should check that |path| is a directory.
if ((errno != EEXIST) || (open_flags & kOpenFlagExclusive)) {
callback.Run(ErrnoToError(errno));
return;
}
}
}
base::ScopedFD new_dir_fd(
HANDLE_EINTR(openat(dir_fd_.get(), path.get().c_str(), O_DIRECTORY, 0)));
if (!new_dir_fd.is_valid()) {
callback.Run(ErrnoToError(errno));
return;
}
if (directory.is_pending())
new DirectoryImpl(directory.Pass(), new_dir_fd.Pass(), nullptr);
callback.Run(Error::OK);
}
void DirectoryImpl::Rename(const String& path,
const String& new_path,
const RenameCallback& callback) {
DCHECK(!path.is_null());
DCHECK(!new_path.is_null());
DCHECK(dir_fd_.is_valid());
Error error = IsPathValid(path);
if (error != Error::OK) {
callback.Run(error);
return;
}
error = IsPathValid(new_path);
if (error != Error::OK) {
callback.Run(error);
return;
}
// TODO(vtl): See TODOs about |path| in OpenFile().
if (renameat(dir_fd_.get(), path.get().c_str(), dir_fd_.get(),
new_path.get().c_str())) {
callback.Run(ErrnoToError(errno));
return;
}
callback.Run(Error::OK);
}
void DirectoryImpl::Delete(const String& path,
uint32_t delete_flags,
const DeleteCallback& callback) {
DCHECK(!path.is_null());
DCHECK(dir_fd_.is_valid());
Error error = IsPathValid(path);
if (error != Error::OK) {
callback.Run(error);
return;
}
// TODO(vtl): See TODOs about |path| in OpenFile().
error = ValidateDeleteFlags(delete_flags);
if (error != Error::OK) {
callback.Run(error);
return;
}
// TODO(vtl): Recursive not yet supported.
if ((delete_flags & kDeleteFlagRecursive)) {
callback.Run(Error::UNIMPLEMENTED);
return;
}
// First try deleting it as a file, unless we're told to do directory-only.
if (!(delete_flags & kDeleteFlagDirectoryOnly)) {
if (unlinkat(dir_fd_.get(), path.get().c_str(), 0) == 0) {
callback.Run(Error::OK);
return;
}
// If file-only, don't continue.
if ((delete_flags & kDeleteFlagFileOnly)) {
callback.Run(ErrnoToError(errno));
return;
}
}
// Try deleting it as a directory.
if (unlinkat(dir_fd_.get(), path.get().c_str(), AT_REMOVEDIR) == 0) {
callback.Run(Error::OK);
return;
}
callback.Run(ErrnoToError(errno));
}
} // namespace files
} // namespace mojo