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 "services/files/directory_impl.h" | 5 #include "services/files/directory_impl.h" |
| 6 | 6 |
| 7 #include <dirent.h> | |
| 7 #include <errno.h> | 8 #include <errno.h> |
| 8 #include <fcntl.h> | 9 #include <fcntl.h> |
| 9 #include <stdio.h> | 10 #include <stdio.h> |
| 10 #include <sys/stat.h> | 11 #include <sys/stat.h> |
| 11 #include <sys/types.h> | 12 #include <sys/types.h> |
| 12 #include <time.h> | 13 #include <time.h> |
| 13 #include <unistd.h> | 14 #include <unistd.h> |
| 14 | 15 |
| 15 #include "base/files/file_path.h" | |
| 16 #include "base/files/scoped_temp_dir.h" | 16 #include "base/files/scoped_temp_dir.h" |
| 17 #include "base/logging.h" | 17 #include "base/logging.h" |
| 18 #include "base/memory/scoped_ptr.h" | 18 #include "base/memory/scoped_ptr.h" |
| 19 #include "base/posix/eintr_wrapper.h" | 19 #include "base/posix/eintr_wrapper.h" |
| 20 #include "build/build_config.h" | 20 #include "build/build_config.h" |
| 21 #include "services/files/file_impl.h" | 21 #include "services/files/file_impl.h" |
| 22 #include "services/files/futimens.h" | |
| 22 #include "services/files/util.h" | 23 #include "services/files/util.h" |
| 23 | 24 |
| 24 namespace mojo { | 25 namespace mojo { |
| 25 namespace files { | 26 namespace files { |
| 26 | 27 |
| 27 namespace { | 28 namespace { |
| 28 | 29 |
| 30 // Calls |closedir()| on a |DIR*|. | |
| 31 struct DIRDeleter { | |
| 32 void operator()(DIR* dir) const { PCHECK(closedir(dir) == 0); } | |
| 33 }; | |
| 34 typedef scoped_ptr<DIR, DIRDeleter> ScopedDIR; | |
| 35 | |
| 29 Error ValidateOpenFlags(uint32_t open_flags, bool is_directory) { | 36 Error ValidateOpenFlags(uint32_t open_flags, bool is_directory) { |
| 30 // Treat unknown flags as "unimplemented". | 37 // Treat unknown flags as "unimplemented". |
| 31 if ((open_flags & | 38 if ((open_flags & |
| 32 ~(kOpenFlagRead | kOpenFlagWrite | kOpenFlagCreate | kOpenFlagExclusive | | 39 ~(kOpenFlagRead | kOpenFlagWrite | kOpenFlagCreate | kOpenFlagExclusive | |
| 33 kOpenFlagAppend | kOpenFlagTruncate))) | 40 kOpenFlagAppend | kOpenFlagTruncate))) |
| 34 return ERROR_UNIMPLEMENTED; | 41 return ERROR_UNIMPLEMENTED; |
| 35 | 42 |
| 36 // At least one of |kOpenFlagRead| or |kOpenFlagWrite| must be set. | 43 // At least one of |kOpenFlagRead| or |kOpenFlagWrite| must be set. |
| 37 if (!(open_flags & (kOpenFlagRead | kOpenFlagWrite))) | 44 if (!(open_flags & (kOpenFlagRead | kOpenFlagWrite))) |
| 38 return ERROR_INVALID_ARGUMENT; | 45 return ERROR_INVALID_ARGUMENT; |
| (...skipping 19 matching lines...) Expand all Loading... | |
| 58 if ((open_flags & kOpenFlagAppend) && !(open_flags & kOpenFlagWrite)) | 65 if ((open_flags & kOpenFlagAppend) && !(open_flags & kOpenFlagWrite)) |
| 59 return ERROR_INVALID_ARGUMENT; | 66 return ERROR_INVALID_ARGUMENT; |
| 60 | 67 |
| 61 // |kOpenFlagTruncate| requires |kOpenFlagWrite|. | 68 // |kOpenFlagTruncate| requires |kOpenFlagWrite|. |
| 62 if ((open_flags & kOpenFlagTruncate) && !(open_flags & kOpenFlagWrite)) | 69 if ((open_flags & kOpenFlagTruncate) && !(open_flags & kOpenFlagWrite)) |
| 63 return ERROR_INVALID_ARGUMENT; | 70 return ERROR_INVALID_ARGUMENT; |
| 64 | 71 |
| 65 return ERROR_OK; | 72 return ERROR_OK; |
| 66 } | 73 } |
| 67 | 74 |
| 75 Error ValidateDeleteFlags(uint32_t delete_flags) { | |
| 76 // Treat unknown flags as "unimplemented". | |
| 77 if ((delete_flags & | |
| 78 ~(kDeleteFlagFileOnly | kDeleteFlagDirectoryOnly | | |
| 79 kDeleteFlagRecursive))) | |
| 80 return ERROR_UNIMPLEMENTED; | |
| 81 | |
| 82 // Only one of the three currently-defined flags may be set. | |
| 83 if ((delete_flags & kDeleteFlagFileOnly) && | |
| 84 (delete_flags & (kDeleteFlagDirectoryOnly | kDeleteFlagRecursive))) | |
| 85 return ERROR_INVALID_ARGUMENT; | |
| 86 if ((delete_flags & kDeleteFlagDirectoryOnly) && | |
| 87 (delete_flags & (kDeleteFlagFileOnly | kDeleteFlagRecursive))) | |
| 88 return ERROR_INVALID_ARGUMENT; | |
| 89 if ((delete_flags & kDeleteFlagRecursive) && | |
| 90 (delete_flags & (kDeleteFlagFileOnly | kDeleteFlagDirectoryOnly))) | |
| 91 return ERROR_INVALID_ARGUMENT; | |
| 92 | |
| 93 return ERROR_OK; | |
| 94 } | |
| 95 | |
| 68 } // namespace | 96 } // namespace |
| 69 | 97 |
| 70 DirectoryImpl::DirectoryImpl(InterfaceRequest<Directory> request, | 98 DirectoryImpl::DirectoryImpl(InterfaceRequest<Directory> request, |
| 71 base::ScopedFD dir_fd, | 99 base::ScopedFD dir_fd, |
| 72 scoped_ptr<base::ScopedTempDir> temp_dir) | 100 scoped_ptr<base::ScopedTempDir> temp_dir) |
| 73 : binding_(this, request.Pass()), | 101 : binding_(this, request.Pass()), |
| 74 dir_fd_(dir_fd.Pass()), | 102 dir_fd_(dir_fd.Pass()), |
| 75 temp_dir_(temp_dir.Pass()) { | 103 temp_dir_(temp_dir.Pass()) { |
| 76 DCHECK(dir_fd_.is_valid()); | 104 DCHECK(dir_fd_.is_valid()); |
| 77 } | 105 } |
| 78 | 106 |
| 79 DirectoryImpl::~DirectoryImpl() { | 107 DirectoryImpl::~DirectoryImpl() { |
| 80 } | 108 } |
| 81 | 109 |
| 82 void DirectoryImpl::Read( | 110 void DirectoryImpl::Read( |
| 83 const Callback<void(Error, Array<DirectoryEntryPtr>)>& callback) { | 111 const Callback<void(Error, Array<DirectoryEntryPtr>)>& callback) { |
| 84 // TODO(vtl): FIXME sooner | 112 static const size_t kMaxReadCount = 1000; |
| 85 NOTIMPLEMENTED(); | 113 |
| 86 callback.Run(ERROR_UNIMPLEMENTED, Array<DirectoryEntryPtr>()); | 114 DCHECK(dir_fd_.is_valid()); |
| 115 | |
| 116 // |fdopendir()| takes ownership of the FD (giving it to the |DIR| -- | |
| 117 // |closedir()| will close the FD)), so we need to |dup()| ours. | |
| 118 base::ScopedFD fd(dup(dir_fd_.get())); | |
| 119 if (!fd.is_valid()) { | |
| 120 callback.Run(ErrnoToError(errno), Array<DirectoryEntryPtr>()); | |
| 121 return; | |
| 122 } | |
| 123 | |
| 124 ScopedDIR dir(fdopendir(fd.release())); | |
| 125 if (!dir) { | |
| 126 callback.Run(ErrnoToError(errno), Array<DirectoryEntryPtr>()); | |
| 127 return; | |
| 128 } | |
| 129 | |
| 130 Array<DirectoryEntryPtr> result(0); | |
| 131 | |
| 132 // Warning: This is not portable (per POSIX.1 -- |buffer| may not be large | |
| 133 // enough), but it's fine for Linux. | |
|
qsr
2015/03/06 13:01:04
Do you want to put a static assert on OS_ANDROID |
viettrungluu
2015/03/09 17:59:29
I'll use an #if !defined(...) and #error.
| |
| 134 struct dirent buffer; | |
| 135 for (size_t n = 0;;) { | |
| 136 struct dirent* entry = nullptr; | |
| 137 if (int error = readdir_r(dir.get(), &buffer, &entry)) { | |
| 138 // |error| is effectively an errno (for |readdir_r()|), AFAICT. | |
| 139 callback.Run(ErrnoToError(error), Array<DirectoryEntryPtr>()); | |
| 140 return; | |
| 141 } | |
| 142 | |
| 143 if (!entry) | |
| 144 break; | |
| 145 | |
| 146 n++; | |
| 147 if (n > kMaxReadCount) { | |
| 148 LOG(WARNING) << "Directory contents truncated"; | |
| 149 callback.Run(ERROR_OUT_OF_RANGE, result.Pass()); | |
| 150 return; | |
| 151 } | |
| 152 | |
| 153 DirectoryEntryPtr e = DirectoryEntry::New(); | |
| 154 switch (entry->d_type) { | |
| 155 case DT_DIR: | |
| 156 e->type = FILE_TYPE_DIRECTORY; | |
| 157 break; | |
| 158 case DT_REG: | |
| 159 e->type = FILE_TYPE_REGULAR_FILE; | |
| 160 break; | |
| 161 default: | |
| 162 e->type = FILE_TYPE_UNKNOWN; | |
|
qsr
2015/03/06 13:01:04
I wonder if we should not just hide all of those e
viettrungluu
2015/03/09 17:59:28
It's tempting, but then it'd be odd if someone tri
| |
| 163 break; | |
| 164 } | |
| 165 e->name = String(entry->d_name); | |
| 166 result.push_back(e.Pass()); | |
| 167 } | |
| 168 | |
| 169 callback.Run(ERROR_OK, result.Pass()); | |
| 87 } | 170 } |
| 88 | 171 |
| 89 void DirectoryImpl::Stat( | 172 void DirectoryImpl::Stat( |
| 90 const Callback<void(Error, FileInformationPtr)>& callback) { | 173 const Callback<void(Error, FileInformationPtr)>& callback) { |
| 91 // TODO(vtl): FIXME sooner | 174 DCHECK(dir_fd_.is_valid()); |
| 92 NOTIMPLEMENTED(); | 175 |
| 93 callback.Run(ERROR_UNIMPLEMENTED, nullptr); | 176 // TODO(vtl): The code below is copied from |FileImpl::Stat()|. |
|
qsr
2015/03/06 13:01:04
Could you extract it in a helper function then?
viettrungluu
2015/03/09 17:59:29
Done.
| |
| 177 struct stat buf; | |
| 178 if (fstat(dir_fd_.get(), &buf) != 0) { | |
| 179 callback.Run(ErrnoToError(errno), nullptr); | |
| 180 return; | |
| 181 } | |
| 182 | |
| 183 FileInformationPtr file_info(FileInformation::New()); | |
| 184 file_info->size = 0; // Always zero for directories. | |
| 185 file_info->atime = Timespec::New(); | |
| 186 file_info->mtime = Timespec::New(); | |
| 187 #if defined(OS_ANDROID) | |
| 188 file_info->atime->seconds = static_cast<int64_t>(buf.st_atime); | |
| 189 file_info->atime->nanoseconds = static_cast<int32_t>(buf.st_atime_nsec); | |
| 190 file_info->mtime->seconds = static_cast<int64_t>(buf.st_mtime); | |
| 191 file_info->mtime->nanoseconds = static_cast<int32_t>(buf.st_mtime_nsec); | |
| 192 #else | |
| 193 file_info->atime->seconds = static_cast<int64_t>(buf.st_atim.tv_sec); | |
| 194 file_info->atime->nanoseconds = static_cast<int32_t>(buf.st_atim.tv_nsec); | |
| 195 file_info->mtime->seconds = static_cast<int64_t>(buf.st_mtim.tv_sec); | |
| 196 file_info->mtime->nanoseconds = static_cast<int32_t>(buf.st_mtim.tv_nsec); | |
| 197 #endif | |
| 198 | |
| 199 callback.Run(ERROR_OK, file_info.Pass()); | |
| 94 } | 200 } |
| 95 | 201 |
| 96 void DirectoryImpl::Touch(TimespecOrNowPtr atime, | 202 void DirectoryImpl::Touch(TimespecOrNowPtr atime, |
| 97 TimespecOrNowPtr mtime, | 203 TimespecOrNowPtr mtime, |
| 98 const Callback<void(Error)>& callback) { | 204 const Callback<void(Error)>& callback) { |
| 99 // TODO(vtl): FIXME sooner | 205 DCHECK(dir_fd_.is_valid()); |
| 100 NOTIMPLEMENTED(); | 206 |
| 101 callback.Run(ERROR_UNIMPLEMENTED); | 207 // TODO(vtl): The code below is copied from |FileImpl::Stat()|. |
|
qsr
2015/03/06 13:01:04
"
viettrungluu
2015/03/09 17:59:29
Done.
| |
| 208 struct timespec times[2]; | |
| 209 if (Error error = TimespecOrNowToStandardTimespec(atime.get(), ×[0])) { | |
| 210 callback.Run(error); | |
| 211 return; | |
| 212 } | |
| 213 if (Error error = TimespecOrNowToStandardTimespec(mtime.get(), ×[1])) { | |
| 214 callback.Run(error); | |
| 215 return; | |
| 216 } | |
| 217 | |
| 218 if (futimens(dir_fd_.get(), times) != 0) { | |
| 219 callback.Run(ErrnoToError(errno)); | |
| 220 return; | |
| 221 } | |
| 222 | |
| 223 callback.Run(ERROR_OK); | |
| 102 } | 224 } |
| 103 | 225 |
| 104 // TODO(vtl): Move the implementation to a thread pool. | 226 // TODO(vtl): Move the implementation to a thread pool. |
| 105 void DirectoryImpl::OpenFile(const String& path, | 227 void DirectoryImpl::OpenFile(const String& path, |
| 106 InterfaceRequest<File> file, | 228 InterfaceRequest<File> file, |
| 107 uint32_t open_flags, | 229 uint32_t open_flags, |
| 108 const Callback<void(Error)>& callback) { | 230 const Callback<void(Error)>& callback) { |
| 109 DCHECK(!path.is_null()); | 231 DCHECK(!path.is_null()); |
| 110 DCHECK(dir_fd_.is_valid()); | 232 DCHECK(dir_fd_.is_valid()); |
| 111 | 233 |
| (...skipping 32 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 144 | 266 |
| 145 if (file.is_pending()) | 267 if (file.is_pending()) |
| 146 new FileImpl(file.Pass(), file_fd.Pass()); | 268 new FileImpl(file.Pass(), file_fd.Pass()); |
| 147 callback.Run(ERROR_OK); | 269 callback.Run(ERROR_OK); |
| 148 } | 270 } |
| 149 | 271 |
| 150 void DirectoryImpl::OpenDirectory(const String& path, | 272 void DirectoryImpl::OpenDirectory(const String& path, |
| 151 InterfaceRequest<Directory> directory, | 273 InterfaceRequest<Directory> directory, |
| 152 uint32_t open_flags, | 274 uint32_t open_flags, |
| 153 const Callback<void(Error)>& callback) { | 275 const Callback<void(Error)>& callback) { |
| 154 // TODO(vtl): FIXME sooner | 276 DCHECK(!path.is_null()); |
| 155 NOTIMPLEMENTED(); | 277 DCHECK(dir_fd_.is_valid()); |
| 156 callback.Run(ERROR_UNIMPLEMENTED); | 278 |
| 279 if (Error error = IsPathValid(path)) { | |
| 280 callback.Run(error); | |
| 281 return; | |
| 282 } | |
| 283 // TODO(vtl): Make sure the path doesn't exit this directory (if appropriate). | |
| 284 // TODO(vtl): Maybe allow absolute paths? | |
| 285 | |
| 286 if (Error error = ValidateOpenFlags(open_flags, false)) { | |
| 287 callback.Run(error); | |
| 288 return; | |
| 289 } | |
| 290 | |
| 291 // TODO(vtl): Implement read-only (whatever that means). | |
| 292 if (!(open_flags & kOpenFlagWrite)) { | |
| 293 callback.Run(ERROR_UNIMPLEMENTED); | |
| 294 return; | |
| 295 } | |
| 296 | |
| 297 if ((open_flags & kOpenFlagCreate)) { | |
| 298 if (mkdirat(dir_fd_.get(), path.get().c_str(), 0700) != 0) { | |
| 299 // Allow |EEXIST| if |kOpenFlagExclusive| is set. Note, however, that it | |
|
qsr
2015/03/06 13:01:04
is not set?
viettrungluu
2015/03/09 17:59:29
Done.
| |
| 300 // does not guarantee that |path| is a directory. | |
| 301 // TODO(vtl): Hrm, ponder if we should check that |path| is a directory. | |
| 302 if (errno != EEXIST || !(open_flags & kOpenFlagExclusive)) { | |
| 303 callback.Run(ErrnoToError(errno)); | |
| 304 return; | |
| 305 } | |
| 306 } | |
| 307 } | |
| 308 | |
| 309 base::ScopedFD new_dir_fd( | |
| 310 HANDLE_EINTR(openat(dir_fd_.get(), path.get().c_str(), O_DIRECTORY, 0))); | |
| 311 if (!new_dir_fd.is_valid()) { | |
| 312 callback.Run(ErrnoToError(errno)); | |
| 313 return; | |
| 314 } | |
| 315 | |
| 316 if (directory.is_pending()) | |
| 317 new DirectoryImpl(directory.Pass(), new_dir_fd.Pass(), nullptr); | |
| 318 callback.Run(ERROR_OK); | |
| 157 } | 319 } |
| 158 | 320 |
| 159 void DirectoryImpl::Rename(const String& path, | 321 void DirectoryImpl::Rename(const String& path, |
| 160 const String& new_path, | 322 const String& new_path, |
| 161 const Callback<void(Error)>& callback) { | 323 const Callback<void(Error)>& callback) { |
| 162 DCHECK(!path.is_null()); | 324 DCHECK(!path.is_null()); |
| 163 DCHECK(!new_path.is_null()); | 325 DCHECK(!new_path.is_null()); |
| 164 DCHECK(dir_fd_.is_valid()); | 326 DCHECK(dir_fd_.is_valid()); |
| 165 | 327 |
| 166 if (Error error = IsPathValid(path)) { | 328 if (Error error = IsPathValid(path)) { |
| (...skipping 11 matching lines...) Expand all Loading... | |
| 178 callback.Run(ErrnoToError(errno)); | 340 callback.Run(ErrnoToError(errno)); |
| 179 return; | 341 return; |
| 180 } | 342 } |
| 181 | 343 |
| 182 callback.Run(ERROR_OK); | 344 callback.Run(ERROR_OK); |
| 183 } | 345 } |
| 184 | 346 |
| 185 void DirectoryImpl::Delete(const String& path, | 347 void DirectoryImpl::Delete(const String& path, |
| 186 uint32_t delete_flags, | 348 uint32_t delete_flags, |
| 187 const Callback<void(Error)>& callback) { | 349 const Callback<void(Error)>& callback) { |
| 188 // TODO(vtl): FIXME sooner | 350 DCHECK(!path.is_null()); |
| 189 NOTIMPLEMENTED(); | 351 // TODO(vtl): Should we check that |path| is nonempty? |
|
qsr
2015/03/06 13:01:04
You would need more than non-empty. foo/.. is not
viettrungluu
2015/03/09 17:59:28
Errrr, right. I guess this falls under the "see TO
| |
| 190 callback.Run(ERROR_UNIMPLEMENTED); | 352 DCHECK(dir_fd_.is_valid()); |
| 353 | |
| 354 if (Error error = IsPathValid(path)) { | |
| 355 callback.Run(error); | |
| 356 return; | |
| 357 } | |
| 358 // TODO(vtl): see TODOs about |path| in OpenFile() | |
| 359 | |
| 360 if (Error error = ValidateDeleteFlags(delete_flags)) { | |
| 361 callback.Run(error); | |
| 362 return; | |
| 363 } | |
| 364 | |
| 365 // TODO(vtl): Recursive not yet supported. | |
| 366 if ((delete_flags & kDeleteFlagRecursive)) { | |
| 367 callback.Run(ERROR_UNIMPLEMENTED); | |
| 368 return; | |
| 369 } | |
| 370 | |
| 371 // First try deleting it as a file, unless we're told to do directory-only. | |
|
qsr
2015/03/06 13:01:04
You do not use fstatat because of the race?
Also,
viettrungluu
2015/03/09 17:59:28
Right.
| |
| 372 if (!(delete_flags & kDeleteFlagDirectoryOnly)) { | |
| 373 if (unlinkat(dir_fd_.get(), path.get().c_str(), 0) == 0) { | |
| 374 callback.Run(ERROR_OK); | |
| 375 return; | |
| 376 } | |
| 377 | |
| 378 // If file-only, don't continue. | |
| 379 if ((delete_flags & kDeleteFlagFileOnly)) { | |
| 380 callback.Run(ErrnoToError(errno)); | |
| 381 return; | |
| 382 } | |
| 383 } | |
| 384 | |
| 385 // Try deleting it as a directory. | |
| 386 if (unlinkat(dir_fd_.get(), path.get().c_str(), AT_REMOVEDIR) == 0) { | |
| 387 callback.Run(ERROR_OK); | |
| 388 return; | |
| 389 } | |
| 390 | |
| 391 callback.Run(ErrnoToError(errno)); | |
| 191 } | 392 } |
| 192 | 393 |
| 193 } // namespace files | 394 } // namespace files |
| 194 } // namespace mojo | 395 } // namespace mojo |
| OLD | NEW |