| // 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 |