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 |