| 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( | 
| 51   DCHECK(dir_fd_.is_valid()); | 33       directory_path_, false, | 
| 52 | 34       base::FileEnumerator::DIRECTORIES | base::FileEnumerator::FILES); | 
| 53   // |fdopendir()| takes ownership of the FD (giving it to the |DIR| -- | 35   for (base::FilePath name = directory_enumerator.Next(); !name.empty(); | 
| 54   // |closedir()| will close the FD)), so we need to |dup()| ours. | 36        name = directory_enumerator.Next()) { | 
| 55   base::ScopedFD fd(dup(dir_fd_.get())); | 37     base::FileEnumerator::FileInfo info = directory_enumerator.GetInfo(); | 
| 56   if (!fd.is_valid()) { | 38     DirectoryEntryPtr entry = DirectoryEntry::New(); | 
| 57     callback.Run(ErrnoToError(errno), mojo::Array<DirectoryEntryPtr>()); | 39     entry->type = info.IsDirectory() | 
| 58     return; | 40                   ? FILE_TYPE_DIRECTORY : FILE_TYPE_REGULAR_FILE; | 
|  | 41     entry->name = info.GetName().AsUTF8Unsafe(); | 
|  | 42     entries.push_back(entry.Pass()); | 
| 59   } | 43   } | 
| 60 | 44 | 
| 61   ScopedDIR dir(fdopendir(fd.release())); | 45   callback.Run(ERROR_OK, entries.Pass()); | 
| 62   if (!dir) { |  | 
| 63     callback.Run(ErrnoToError(errno), mojo::Array<DirectoryEntryPtr>()); |  | 
| 64     return; |  | 
| 65   } |  | 
| 66 |  | 
| 67   mojo::Array<DirectoryEntryPtr> result(0); |  | 
| 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 } | 46 } | 
| 111 | 47 | 
| 112 void DirectoryImpl::Stat(const StatCallback& callback) { | 48 // TODO(erg): Consider adding an implementation of Stat()/Touch() to the | 
| 113   DCHECK(dir_fd_.is_valid()); | 49 // directory, too. Right now, the base::File abstractions do not really deal | 
| 114   StatFD(dir_fd_.get(), FILE_TYPE_DIRECTORY, callback); | 50 // 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 | 51 | 
| 124 // TODO(vtl): Move the implementation to a thread pool. | 52 // TODO(vtl): Move the implementation to a thread pool. | 
| 125 void DirectoryImpl::OpenFile(const mojo::String& path, | 53 void DirectoryImpl::OpenFile(const mojo::String& raw_path, | 
| 126                              mojo::InterfaceRequest<File> file, | 54                              mojo::InterfaceRequest<File> file, | 
| 127                              uint32_t open_flags, | 55                              uint32_t open_flags, | 
| 128                              const OpenFileCallback& callback) { | 56                              const OpenFileCallback& callback) { | 
| 129   DCHECK(!path.is_null()); | 57   base::FilePath path; | 
| 130   DCHECK(dir_fd_.is_valid()); | 58   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); | 59     callback.Run(error); | 
| 141     return; | 60     return; | 
| 142   } | 61   } | 
| 143 | 62 | 
| 144   int flags = 0; | 63   base::File base_file(path, open_flags); | 
| 145   if ((open_flags & kOpenFlagRead)) | 64   if (!base_file.IsValid()) { | 
| 146     flags |= (open_flags & kOpenFlagWrite) ? O_RDWR : O_RDONLY; | 65     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; | 66     return; | 
| 163   } | 67   } | 
| 164 | 68 | 
| 165   if (file.is_pending()) | 69   if (file.is_pending()) { | 
| 166     new FileImpl(file.Pass(), file_fd.Pass()); | 70     new FileImpl(file.Pass(), base_file.Pass()); | 
|  | 71   } | 
| 167   callback.Run(ERROR_OK); | 72   callback.Run(ERROR_OK); | 
| 168 } | 73 } | 
| 169 | 74 | 
| 170 void DirectoryImpl::OpenDirectory(const mojo::String& path, | 75 void DirectoryImpl::OpenDirectory(const mojo::String& raw_path, | 
| 171                                   mojo::InterfaceRequest<Directory> directory, | 76                                   mojo::InterfaceRequest<Directory> directory, | 
| 172                                   uint32_t open_flags, | 77                                   uint32_t open_flags, | 
| 173                                   const OpenDirectoryCallback& callback) { | 78                                   const OpenDirectoryCallback& callback) { | 
| 174   DCHECK(!path.is_null()); | 79   base::FilePath path; | 
| 175   DCHECK(dir_fd_.is_valid()); | 80   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); | 81     callback.Run(error); | 
| 186     return; | 82     return; | 
| 187   } | 83   } | 
| 188 | 84 | 
| 189   // TODO(vtl): Implement read-only (whatever that means). | 85   if (!base::DirectoryExists(path)) { | 
| 190   if (!(open_flags & kOpenFlagWrite)) { | 86     if (base::PathExists(path)) { | 
| 191     callback.Run(ERROR_UNIMPLEMENTED); | 87       callback.Run(ERROR_NOT_A_DIRECTORY); | 
|  | 88       return; | 
|  | 89     } | 
|  | 90 | 
|  | 91     if (!(open_flags & kFlagOpenAlways || open_flags & kFlagCreate)) { | 
|  | 92       // The directory doesn't exist, and we weren't passed parameters to | 
|  | 93       // create it. | 
|  | 94       callback.Run(ERROR_NOT_FOUND); | 
|  | 95       return; | 
|  | 96     } | 
|  | 97 | 
|  | 98     base::File::Error error; | 
|  | 99     if (!base::CreateDirectoryAndGetError(path, &error)) { | 
|  | 100       callback.Run(static_cast<filesystem::Error>(error)); | 
|  | 101       return; | 
|  | 102     } | 
|  | 103   } | 
|  | 104 | 
|  | 105   if (directory.is_pending()) | 
|  | 106     new DirectoryImpl(directory.Pass(), path, | 
|  | 107                       scoped_ptr<base::ScopedTempDir>()); | 
|  | 108   callback.Run(ERROR_OK); | 
|  | 109 } | 
|  | 110 | 
|  | 111 void DirectoryImpl::Rename(const mojo::String& raw_old_path, | 
|  | 112                            const mojo::String& raw_new_path, | 
|  | 113                            const RenameCallback& callback) { | 
|  | 114   base::FilePath old_path; | 
|  | 115   if (Error error = ValidatePath(raw_old_path, directory_path_, &old_path)) { | 
|  | 116     callback.Run(error); | 
| 192     return; | 117     return; | 
| 193   } | 118   } | 
| 194 | 119 | 
| 195   if ((open_flags & kOpenFlagCreate)) { | 120   base::FilePath new_path; | 
| 196     if (mkdirat(dir_fd_.get(), path.get().c_str(), 0700) != 0) { | 121   if (Error error = ValidatePath(raw_new_path, directory_path_, &new_path)) { | 
| 197       // Allow |EEXIST| if |kOpenFlagExclusive| is not set. Note, however, that | 122     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; | 123     return; | 
| 212   } | 124   } | 
| 213 | 125 | 
| 214   if (directory.is_pending()) | 126   if (!base::Move(old_path, new_path)) { | 
| 215     new DirectoryImpl(directory.Pass(), new_dir_fd.Pass(), nullptr); | 127     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; | 128     return; | 
| 240   } | 129   } | 
| 241 | 130 | 
| 242   callback.Run(ERROR_OK); | 131   callback.Run(ERROR_OK); | 
| 243 } | 132 } | 
| 244 | 133 | 
| 245 void DirectoryImpl::Delete(const mojo::String& path, | 134 void DirectoryImpl::Delete(const mojo::String& raw_path, | 
| 246                            uint32_t delete_flags, | 135                            uint32_t delete_flags, | 
| 247                            const DeleteCallback& callback) { | 136                            const DeleteCallback& callback) { | 
| 248   DCHECK(!path.is_null()); | 137   base::FilePath path; | 
| 249   DCHECK(dir_fd_.is_valid()); | 138   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); | 139     callback.Run(error); | 
| 259     return; | 140     return; | 
| 260   } | 141   } | 
| 261 | 142 | 
| 262   // TODO(vtl): Recursive not yet supported. | 143   bool recursive = delete_flags & kDeleteFlagRecursive; | 
| 263   if ((delete_flags & kDeleteFlagRecursive)) { | 144   if (!base::DeleteFile(path, recursive)) { | 
| 264     callback.Run(ERROR_UNIMPLEMENTED); | 145     callback.Run(ERROR_FAILED); | 
| 265     return; | 146     return; | 
| 266   } | 147   } | 
| 267 | 148 | 
| 268   // First try deleting it as a file, unless we're told to do directory-only. | 149   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 } | 150 } | 
| 290 | 151 | 
| 291 }  // namespace filesystem | 152 }  // namespace filesystem | 
| OLD | NEW | 
|---|