Chromium Code Reviews| OLD | NEW |
|---|---|
| 1 // Copyright 2015 The Chromium Authors. All rights reserved. | 1 // Copyright 2015 The Chromium Authors. All rights reserved. |
| 2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
| 4 | 4 |
| 5 #include "components/filesystem/directory_impl.h" | 5 #include "components/filesystem/directory_impl.h" |
| 6 | 6 |
| 7 #include <dirent.h> | 7 #include "base/files/file.h" |
| 8 #include <errno.h> | 8 #include "base/files/file_enumerator.h" |
| 9 #include <fcntl.h> | 9 #include "base/files/file_util.h" |
| 10 #include <stdio.h> | |
| 11 #include <sys/stat.h> | |
| 12 #include <sys/types.h> | |
| 13 #include <unistd.h> | |
| 14 | |
| 15 #include "base/files/scoped_temp_dir.h" | 10 #include "base/files/scoped_temp_dir.h" |
| 16 #include "base/logging.h" | 11 #include "base/logging.h" |
| 17 #include "base/memory/scoped_ptr.h" | 12 #include "base/memory/scoped_ptr.h" |
| 18 #include "base/posix/eintr_wrapper.h" | |
| 19 #include "build/build_config.h" | 13 #include "build/build_config.h" |
| 20 #include "components/filesystem/file_impl.h" | 14 #include "components/filesystem/file_impl.h" |
| 21 #include "components/filesystem/shared_impl.h" | |
| 22 #include "components/filesystem/util.h" | 15 #include "components/filesystem/util.h" |
| 23 | 16 |
| 24 namespace filesystem { | 17 namespace filesystem { |
| 25 | 18 |
| 26 namespace { | |
| 27 | |
| 28 // Calls |closedir()| on a |DIR*|. | |
| 29 struct DIRDeleter { | |
| 30 void operator()(DIR* dir) const { PCHECK(closedir(dir) == 0); } | |
| 31 }; | |
| 32 using ScopedDIR = scoped_ptr<DIR, DIRDeleter>; | |
| 33 | |
| 34 } // namespace | |
| 35 | |
| 36 DirectoryImpl::DirectoryImpl(mojo::InterfaceRequest<Directory> request, | 19 DirectoryImpl::DirectoryImpl(mojo::InterfaceRequest<Directory> request, |
| 37 base::ScopedFD dir_fd, | 20 base::FilePath directory_path, |
| 38 scoped_ptr<base::ScopedTempDir> temp_dir) | 21 scoped_ptr<base::ScopedTempDir> temp_dir) |
| 39 : binding_(this, request.Pass()), | 22 : binding_(this, request.Pass()), |
| 40 dir_fd_(dir_fd.Pass()), | 23 directory_path_(directory_path), |
| 41 temp_dir_(temp_dir.Pass()) { | 24 temp_dir_(temp_dir.Pass()) { |
| 42 DCHECK(dir_fd_.is_valid()); | |
| 43 } | 25 } |
| 44 | 26 |
| 45 DirectoryImpl::~DirectoryImpl() { | 27 DirectoryImpl::~DirectoryImpl() { |
| 46 } | 28 } |
| 47 | 29 |
| 48 void DirectoryImpl::Read(const ReadCallback& callback) { | 30 void DirectoryImpl::Read(const ReadCallback& callback) { |
| 49 static const size_t kMaxReadCount = 1000; | 31 mojo::Array<DirectoryEntryPtr> entries(0); |
| 50 | 32 base::FileEnumerator directory_enumerator(directory_path_, false, |
| 51 DCHECK(dir_fd_.is_valid()); | 33 base::FileEnumerator::DIRECTORIES); |
| 52 | 34 for (base::FilePath name = directory_enumerator.Next(); !name.empty(); |
|
jam
2015/05/28 06:40:33
why enumerate twice instead of once with the bitma
Elliot Glaysher
2015/05/28 17:46:58
Done.
| |
| 53 // |fdopendir()| takes ownership of the FD (giving it to the |DIR| -- | 35 name = directory_enumerator.Next()) { |
| 54 // |closedir()| will close the FD)), so we need to |dup()| ours. | 36 DirectoryEntryPtr entry = DirectoryEntry::New(); |
| 55 base::ScopedFD fd(dup(dir_fd_.get())); | 37 entry->type = FILE_TYPE_DIRECTORY; |
| 56 if (!fd.is_valid()) { | 38 // TODO(erg): See the comments about when we can stop caring about non-utf8 |
| 57 callback.Run(ErrnoToError(errno), mojo::Array<DirectoryEntryPtr>()); | 39 // names in base/files/file_path.h. |
| 58 return; | 40 entry->name = name.BaseName().AsUTF8Unsafe(); |
| 41 entries.push_back(entry.Pass()); | |
| 59 } | 42 } |
| 60 | 43 |
| 61 ScopedDIR dir(fdopendir(fd.release())); | 44 base::FileEnumerator file_enumerator(directory_path_, false, |
| 62 if (!dir) { | 45 base::FileEnumerator::FILES); |
| 63 callback.Run(ErrnoToError(errno), mojo::Array<DirectoryEntryPtr>()); | 46 for (base::FilePath name = file_enumerator.Next(); !name.empty(); |
| 64 return; | 47 name = file_enumerator.Next()) { |
| 48 DirectoryEntryPtr entry = DirectoryEntry::New(); | |
| 49 entry->type = FILE_TYPE_REGULAR_FILE; | |
| 50 entry->name = name.BaseName().AsUTF8Unsafe(); | |
| 51 entries.push_back(entry.Pass()); | |
| 65 } | 52 } |
| 66 | 53 |
| 67 mojo::Array<DirectoryEntryPtr> result(0); | 54 callback.Run(ERROR_OK, entries.Pass()); |
| 68 | |
| 69 // Warning: This is not portable (per POSIX.1 -- |buffer| may not be large | |
| 70 // enough), but it's fine for Linux. | |
| 71 #if !defined(OS_ANDROID) && !defined(OS_LINUX) | |
| 72 #error "Use of struct dirent for readdir_r() buffer not portable; please check." | |
| 73 #endif | |
| 74 struct dirent buffer; | |
| 75 for (size_t n = 0;;) { | |
| 76 struct dirent* entry = nullptr; | |
| 77 if (int error = readdir_r(dir.get(), &buffer, &entry)) { | |
| 78 // |error| is effectively an errno (for |readdir_r()|), AFAICT. | |
| 79 callback.Run(ErrnoToError(error), mojo::Array<DirectoryEntryPtr>()); | |
| 80 return; | |
| 81 } | |
| 82 | |
| 83 if (!entry) | |
| 84 break; | |
| 85 | |
| 86 n++; | |
| 87 if (n > kMaxReadCount) { | |
| 88 LOG(WARNING) << "Directory contents truncated"; | |
| 89 callback.Run(ERROR_OUT_OF_RANGE, result.Pass()); | |
| 90 return; | |
| 91 } | |
| 92 | |
| 93 DirectoryEntryPtr e = DirectoryEntry::New(); | |
| 94 switch (entry->d_type) { | |
| 95 case DT_DIR: | |
| 96 e->type = FILE_TYPE_DIRECTORY; | |
| 97 break; | |
| 98 case DT_REG: | |
| 99 e->type = FILE_TYPE_REGULAR_FILE; | |
| 100 break; | |
| 101 default: | |
| 102 e->type = FILE_TYPE_UNKNOWN; | |
| 103 break; | |
| 104 } | |
| 105 e->name = mojo::String(entry->d_name); | |
| 106 result.push_back(e.Pass()); | |
| 107 } | |
| 108 | |
| 109 callback.Run(ERROR_OK, result.Pass()); | |
| 110 } | 55 } |
| 111 | 56 |
| 112 void DirectoryImpl::Stat(const StatCallback& callback) { | 57 // TODO(erg): Consider adding an implementation of Stat()/Touch() to the |
| 113 DCHECK(dir_fd_.is_valid()); | 58 // directory, too. Right now, the base::File abstractions do not really deal |
| 114 StatFD(dir_fd_.get(), FILE_TYPE_DIRECTORY, callback); | 59 // with directories properly, so these are broken for now. |
| 115 } | |
| 116 | |
| 117 void DirectoryImpl::Touch(TimespecOrNowPtr atime, | |
| 118 TimespecOrNowPtr mtime, | |
| 119 const TouchCallback& callback) { | |
| 120 DCHECK(dir_fd_.is_valid()); | |
| 121 TouchFD(dir_fd_.get(), atime.Pass(), mtime.Pass(), callback); | |
| 122 } | |
| 123 | 60 |
| 124 // TODO(vtl): Move the implementation to a thread pool. | 61 // TODO(vtl): Move the implementation to a thread pool. |
| 125 void DirectoryImpl::OpenFile(const mojo::String& path, | 62 void DirectoryImpl::OpenFile(const mojo::String& raw_path, |
| 126 mojo::InterfaceRequest<File> file, | 63 mojo::InterfaceRequest<File> file, |
| 127 uint32_t open_flags, | 64 uint32_t open_flags, |
| 128 const OpenFileCallback& callback) { | 65 const OpenFileCallback& callback) { |
| 129 DCHECK(!path.is_null()); | 66 base::FilePath path; |
| 130 DCHECK(dir_fd_.is_valid()); | 67 if (Error error = ValidatePath(raw_path, directory_path_, &path)) { |
| 131 | |
| 132 if (Error error = IsPathValid(path)) { | |
| 133 callback.Run(error); | |
| 134 return; | |
| 135 } | |
| 136 // TODO(vtl): Make sure the path doesn't exit this directory (if appropriate). | |
| 137 // TODO(vtl): Maybe allow absolute paths? | |
| 138 | |
| 139 if (Error error = ValidateOpenFlags(open_flags, false)) { | |
| 140 callback.Run(error); | 68 callback.Run(error); |
| 141 return; | 69 return; |
| 142 } | 70 } |
| 143 | 71 |
| 144 int flags = 0; | 72 base::File base_file(path, open_flags); |
| 145 if ((open_flags & kOpenFlagRead)) | 73 if (!base_file.IsValid()) { |
| 146 flags |= (open_flags & kOpenFlagWrite) ? O_RDWR : O_RDONLY; | 74 callback.Run(ERROR_FAILED); |
| 147 else | |
| 148 flags |= O_WRONLY; | |
| 149 if ((open_flags & kOpenFlagCreate)) | |
| 150 flags |= O_CREAT; | |
| 151 if ((open_flags & kOpenFlagExclusive)) | |
| 152 flags |= O_EXCL; | |
| 153 if ((open_flags & kOpenFlagAppend)) | |
| 154 flags |= O_APPEND; | |
| 155 if ((open_flags & kOpenFlagTruncate)) | |
| 156 flags |= O_TRUNC; | |
| 157 | |
| 158 base::ScopedFD file_fd( | |
| 159 HANDLE_EINTR(openat(dir_fd_.get(), path.get().c_str(), flags, 0600))); | |
| 160 if (!file_fd.is_valid()) { | |
| 161 callback.Run(ErrnoToError(errno)); | |
| 162 return; | 75 return; |
| 163 } | 76 } |
| 164 | 77 |
| 165 if (file.is_pending()) | 78 if (file.is_pending()) { |
| 166 new FileImpl(file.Pass(), file_fd.Pass()); | 79 new FileImpl(file.Pass(), base_file.Pass()); |
| 80 } | |
| 167 callback.Run(ERROR_OK); | 81 callback.Run(ERROR_OK); |
| 168 } | 82 } |
| 169 | 83 |
| 170 void DirectoryImpl::OpenDirectory(const mojo::String& path, | 84 void DirectoryImpl::OpenDirectory(const mojo::String& raw_path, |
| 171 mojo::InterfaceRequest<Directory> directory, | 85 mojo::InterfaceRequest<Directory> directory, |
| 172 uint32_t open_flags, | 86 uint32_t open_flags, |
| 173 const OpenDirectoryCallback& callback) { | 87 const OpenDirectoryCallback& callback) { |
| 174 DCHECK(!path.is_null()); | 88 base::FilePath path; |
| 175 DCHECK(dir_fd_.is_valid()); | 89 if (Error error = ValidatePath(raw_path, directory_path_, &path)) { |
| 176 | |
| 177 if (Error error = IsPathValid(path)) { | |
| 178 callback.Run(error); | |
| 179 return; | |
| 180 } | |
| 181 // TODO(vtl): Make sure the path doesn't exit this directory (if appropriate). | |
| 182 // TODO(vtl): Maybe allow absolute paths? | |
| 183 | |
| 184 if (Error error = ValidateOpenFlags(open_flags, false)) { | |
| 185 callback.Run(error); | 90 callback.Run(error); |
| 186 return; | 91 return; |
| 187 } | 92 } |
| 188 | 93 |
| 189 // TODO(vtl): Implement read-only (whatever that means). | 94 if (!base::DirectoryExists(path)) { |
| 190 if (!(open_flags & kOpenFlagWrite)) { | 95 if (base::PathExists(path)) { |
| 191 callback.Run(ERROR_UNIMPLEMENTED); | 96 callback.Run(ERROR_NOT_A_DIRECTORY); |
| 97 return; | |
| 98 } | |
| 99 | |
| 100 if (!(open_flags & kFlagOpenAlways || open_flags & kFlagCreate)) { | |
| 101 // The directory doesn't exist, and we weren't passed parameters to | |
| 102 // create it. | |
| 103 callback.Run(ERROR_NOT_FOUND); | |
| 104 return; | |
| 105 } | |
| 106 | |
| 107 base::File::Error error; | |
| 108 if (!base::CreateDirectoryAndGetError(path, &error)) { | |
| 109 callback.Run(static_cast<filesystem::Error>(error)); | |
| 110 return; | |
| 111 } | |
| 112 } | |
| 113 | |
| 114 if (directory.is_pending()) | |
| 115 new DirectoryImpl(directory.Pass(), path, | |
| 116 scoped_ptr<base::ScopedTempDir>()); | |
| 117 callback.Run(ERROR_OK); | |
| 118 } | |
| 119 | |
| 120 void DirectoryImpl::Rename(const mojo::String& raw_old_path, | |
| 121 const mojo::String& raw_new_path, | |
| 122 const RenameCallback& callback) { | |
| 123 base::FilePath old_path; | |
| 124 if (Error error = ValidatePath(raw_old_path, directory_path_, &old_path)) { | |
| 125 callback.Run(error); | |
| 192 return; | 126 return; |
| 193 } | 127 } |
| 194 | 128 |
| 195 if ((open_flags & kOpenFlagCreate)) { | 129 base::FilePath new_path; |
| 196 if (mkdirat(dir_fd_.get(), path.get().c_str(), 0700) != 0) { | 130 if (Error error = ValidatePath(raw_new_path, directory_path_, &new_path)) { |
| 197 // Allow |EEXIST| if |kOpenFlagExclusive| is not set. Note, however, that | 131 callback.Run(error); |
| 198 // it does not guarantee that |path| is a directory. | |
| 199 // TODO(vtl): Hrm, ponder if we should check that |path| is a directory. | |
| 200 if (errno != EEXIST || !(open_flags & kOpenFlagExclusive)) { | |
| 201 callback.Run(ErrnoToError(errno)); | |
| 202 return; | |
| 203 } | |
| 204 } | |
| 205 } | |
| 206 | |
| 207 base::ScopedFD new_dir_fd( | |
| 208 HANDLE_EINTR(openat(dir_fd_.get(), path.get().c_str(), O_DIRECTORY, 0))); | |
| 209 if (!new_dir_fd.is_valid()) { | |
| 210 callback.Run(ErrnoToError(errno)); | |
| 211 return; | 132 return; |
| 212 } | 133 } |
| 213 | 134 |
| 214 if (directory.is_pending()) | 135 if (!base::Move(old_path, new_path)) { |
| 215 new DirectoryImpl(directory.Pass(), new_dir_fd.Pass(), nullptr); | 136 callback.Run(ERROR_FAILED); |
| 216 callback.Run(ERROR_OK); | |
| 217 } | |
| 218 | |
| 219 void DirectoryImpl::Rename(const mojo::String& path, | |
| 220 const mojo::String& new_path, | |
| 221 const RenameCallback& callback) { | |
| 222 DCHECK(!path.is_null()); | |
| 223 DCHECK(!new_path.is_null()); | |
| 224 DCHECK(dir_fd_.is_valid()); | |
| 225 | |
| 226 if (Error error = IsPathValid(path)) { | |
| 227 callback.Run(error); | |
| 228 return; | |
| 229 } | |
| 230 if (Error error = IsPathValid(new_path)) { | |
| 231 callback.Run(error); | |
| 232 return; | |
| 233 } | |
| 234 // TODO(vtl): See TODOs about |path| in OpenFile(). | |
| 235 | |
| 236 if (renameat(dir_fd_.get(), path.get().c_str(), dir_fd_.get(), | |
| 237 new_path.get().c_str())) { | |
| 238 callback.Run(ErrnoToError(errno)); | |
| 239 return; | 137 return; |
| 240 } | 138 } |
| 241 | 139 |
| 242 callback.Run(ERROR_OK); | 140 callback.Run(ERROR_OK); |
| 243 } | 141 } |
| 244 | 142 |
| 245 void DirectoryImpl::Delete(const mojo::String& path, | 143 void DirectoryImpl::Delete(const mojo::String& raw_path, |
| 246 uint32_t delete_flags, | 144 uint32_t delete_flags, |
| 247 const DeleteCallback& callback) { | 145 const DeleteCallback& callback) { |
| 248 DCHECK(!path.is_null()); | 146 base::FilePath path; |
| 249 DCHECK(dir_fd_.is_valid()); | 147 if (Error error = ValidatePath(raw_path, directory_path_, &path)) { |
| 250 | |
| 251 if (Error error = IsPathValid(path)) { | |
| 252 callback.Run(error); | |
| 253 return; | |
| 254 } | |
| 255 // TODO(vtl): See TODOs about |path| in OpenFile(). | |
| 256 | |
| 257 if (Error error = ValidateDeleteFlags(delete_flags)) { | |
| 258 callback.Run(error); | 148 callback.Run(error); |
| 259 return; | 149 return; |
| 260 } | 150 } |
| 261 | 151 |
| 262 // TODO(vtl): Recursive not yet supported. | 152 bool recursive = delete_flags & kDeleteFlagRecursive; |
| 263 if ((delete_flags & kDeleteFlagRecursive)) { | 153 if (!base::DeleteFile(path, recursive)) { |
| 264 callback.Run(ERROR_UNIMPLEMENTED); | 154 callback.Run(ERROR_FAILED); |
| 265 return; | 155 return; |
| 266 } | 156 } |
| 267 | 157 |
| 268 // First try deleting it as a file, unless we're told to do directory-only. | 158 callback.Run(ERROR_OK); |
| 269 if (!(delete_flags & kDeleteFlagDirectoryOnly)) { | |
| 270 if (unlinkat(dir_fd_.get(), path.get().c_str(), 0) == 0) { | |
| 271 callback.Run(ERROR_OK); | |
| 272 return; | |
| 273 } | |
| 274 | |
| 275 // If file-only, don't continue. | |
| 276 if ((delete_flags & kDeleteFlagFileOnly)) { | |
| 277 callback.Run(ErrnoToError(errno)); | |
| 278 return; | |
| 279 } | |
| 280 } | |
| 281 | |
| 282 // Try deleting it as a directory. | |
| 283 if (unlinkat(dir_fd_.get(), path.get().c_str(), AT_REMOVEDIR) == 0) { | |
| 284 callback.Run(ERROR_OK); | |
| 285 return; | |
| 286 } | |
| 287 | |
| 288 callback.Run(ErrnoToError(errno)); | |
| 289 } | 159 } |
| 290 | 160 |
| 291 } // namespace filesystem | 161 } // namespace filesystem |
| OLD | NEW |