1 | // Copyright (c) 2016, the Dart project authors. Please see the AUTHORS file |
2 | // for details. All rights reserved. Use of this source code is governed by a |
3 | // BSD-style license that can be found in the LICENSE file. |
4 | |
5 | #include "platform/globals.h" |
6 | #if defined(HOST_OS_FUCHSIA) |
7 | |
8 | #include "bin/directory.h" |
9 | |
10 | #include <dirent.h> // NOLINT |
11 | #include <errno.h> // NOLINT |
12 | #include <fcntl.h> // NOLINT |
13 | #include <lib/fdio/namespace.h> // NOLINT |
14 | #include <stdlib.h> // NOLINT |
15 | #include <string.h> // NOLINT |
16 | #include <sys/param.h> // NOLINT |
17 | #include <sys/stat.h> // NOLINT |
18 | #include <unistd.h> // NOLINT |
19 | |
20 | #include "bin/crypto.h" |
21 | #include "bin/dartutils.h" |
22 | #include "bin/fdutils.h" |
23 | #include "bin/file.h" |
24 | #include "bin/namespace.h" |
25 | #include "bin/platform.h" |
26 | #include "platform/signal_blocker.h" |
27 | |
28 | namespace dart { |
29 | namespace bin { |
30 | |
31 | PathBuffer::PathBuffer() : length_(0) { |
32 | data_ = calloc(PATH_MAX + 1, sizeof(char)); // NOLINT |
33 | } |
34 | |
35 | PathBuffer::~PathBuffer() { |
36 | free(data_); |
37 | } |
38 | |
39 | bool PathBuffer::AddW(const wchar_t* name) { |
40 | UNREACHABLE(); |
41 | return false; |
42 | } |
43 | |
44 | char* PathBuffer::AsString() const { |
45 | return reinterpret_cast<char*>(data_); |
46 | } |
47 | |
48 | wchar_t* PathBuffer::AsStringW() const { |
49 | UNREACHABLE(); |
50 | return NULL; |
51 | } |
52 | |
53 | const char* PathBuffer::AsScopedString() const { |
54 | return DartUtils::ScopedCopyCString(AsString()); |
55 | } |
56 | |
57 | bool PathBuffer::Add(const char* name) { |
58 | const intptr_t name_length = strnlen(name, PATH_MAX + 1); |
59 | if (name_length == 0) { |
60 | errno = EINVAL; |
61 | return false; |
62 | } |
63 | char* data = AsString(); |
64 | int written = snprintf(data + length_, PATH_MAX - length_, "%s" , name); |
65 | data[PATH_MAX] = '\0'; |
66 | if ((written <= (PATH_MAX - length_)) && (written > 0) && |
67 | (static_cast<size_t>(written) == strnlen(name, PATH_MAX + 1))) { |
68 | length_ += written; |
69 | return true; |
70 | } else { |
71 | errno = ENAMETOOLONG; |
72 | return false; |
73 | } |
74 | } |
75 | |
76 | void PathBuffer::Reset(intptr_t new_length) { |
77 | length_ = new_length; |
78 | AsString()[length_] = '\0'; |
79 | } |
80 | |
81 | // A linked list of symbolic links, with their unique file system identifiers. |
82 | // These are scanned to detect loops while doing a recursive directory listing. |
83 | struct LinkList { |
84 | dev_t dev; |
85 | ino_t ino; |
86 | LinkList* next; |
87 | }; |
88 | |
89 | ListType DirectoryListingEntry::Next(DirectoryListing* listing) { |
90 | if (done_) { |
91 | return kListDone; |
92 | } |
93 | |
94 | if (fd_ == -1) { |
95 | ASSERT(lister_ == 0); |
96 | NamespaceScope ns(listing->namespc(), listing->path_buffer().AsString()); |
97 | const int listingfd = |
98 | TEMP_FAILURE_RETRY(openat(ns.fd(), ns.path(), O_DIRECTORY)); |
99 | if (listingfd < 0) { |
100 | done_ = true; |
101 | return kListError; |
102 | } |
103 | fd_ = listingfd; |
104 | } |
105 | |
106 | if (lister_ == 0) { |
107 | do { |
108 | lister_ = reinterpret_cast<intptr_t>(fdopendir(fd_)); |
109 | } while ((lister_ == 0) && (errno == EINTR)); |
110 | if (lister_ == 0) { |
111 | done_ = true; |
112 | return kListError; |
113 | } |
114 | if (parent_ != NULL) { |
115 | if (!listing->path_buffer().Add(File::PathSeparator())) { |
116 | return kListError; |
117 | } |
118 | } |
119 | path_length_ = listing->path_buffer().length(); |
120 | } |
121 | // Reset. |
122 | listing->path_buffer().Reset(path_length_); |
123 | ResetLink(); |
124 | |
125 | // Iterate the directory and post the directories and files to the |
126 | // ports. |
127 | errno = 0; |
128 | dirent* entry = readdir(reinterpret_cast<DIR*>(lister_)); |
129 | if (entry != NULL) { |
130 | if (!listing->path_buffer().Add(entry->d_name)) { |
131 | done_ = true; |
132 | return kListError; |
133 | } |
134 | switch (entry->d_type) { |
135 | case DT_DIR: |
136 | if ((strcmp(entry->d_name, "." ) == 0) || |
137 | (strcmp(entry->d_name, ".." ) == 0)) { |
138 | return Next(listing); |
139 | } |
140 | return kListDirectory; |
141 | case DT_BLK: |
142 | case DT_CHR: |
143 | case DT_FIFO: |
144 | case DT_SOCK: |
145 | case DT_REG: |
146 | return kListFile; |
147 | case DT_LNK: |
148 | if (!listing->follow_links()) { |
149 | return kListLink; |
150 | } |
151 | // Else fall through to next case. |
152 | FALL_THROUGH; |
153 | case DT_UNKNOWN: { |
154 | // On some file systems the entry type is not determined by |
155 | // readdir. For those and for links we use stat to determine |
156 | // the actual entry type. Notice that stat returns the type of |
157 | // the file pointed to. |
158 | NamespaceScope ns(listing->namespc(), |
159 | listing->path_buffer().AsString()); |
160 | struct stat entry_info; |
161 | int stat_success; |
162 | stat_success = TEMP_FAILURE_RETRY( |
163 | fstatat(ns.fd(), ns.path(), &entry_info, AT_SYMLINK_NOFOLLOW)); |
164 | if (stat_success == -1) { |
165 | return kListError; |
166 | } |
167 | if (listing->follow_links() && S_ISLNK(entry_info.st_mode)) { |
168 | // Check to see if we are in a loop created by a symbolic link. |
169 | LinkList current_link = {entry_info.st_dev, entry_info.st_ino, link_}; |
170 | LinkList* previous = link_; |
171 | while (previous != NULL) { |
172 | if ((previous->dev == current_link.dev) && |
173 | (previous->ino == current_link.ino)) { |
174 | // Report the looping link as a link, rather than following it. |
175 | return kListLink; |
176 | } |
177 | previous = previous->next; |
178 | } |
179 | stat_success = |
180 | TEMP_FAILURE_RETRY(fstatat(ns.fd(), ns.path(), &entry_info, 0)); |
181 | if (stat_success == -1) { |
182 | // Report a broken link as a link, even if follow_links is true. |
183 | return kListLink; |
184 | } |
185 | if (S_ISDIR(entry_info.st_mode)) { |
186 | // Recurse into the subdirectory with current_link added to the |
187 | // linked list of seen file system links. |
188 | link_ = new LinkList(current_link); |
189 | if ((strcmp(entry->d_name, "." ) == 0) || |
190 | (strcmp(entry->d_name, ".." ) == 0)) { |
191 | return Next(listing); |
192 | } |
193 | return kListDirectory; |
194 | } |
195 | } |
196 | if (S_ISDIR(entry_info.st_mode)) { |
197 | if ((strcmp(entry->d_name, "." ) == 0) || |
198 | (strcmp(entry->d_name, ".." ) == 0)) { |
199 | return Next(listing); |
200 | } |
201 | return kListDirectory; |
202 | } else if (S_ISREG(entry_info.st_mode) || S_ISCHR(entry_info.st_mode) || |
203 | S_ISBLK(entry_info.st_mode) || |
204 | S_ISFIFO(entry_info.st_mode) || |
205 | S_ISSOCK(entry_info.st_mode)) { |
206 | return kListFile; |
207 | } else if (S_ISLNK(entry_info.st_mode)) { |
208 | return kListLink; |
209 | } else { |
210 | FATAL1("Unexpected st_mode: %d\n" , entry_info.st_mode); |
211 | return kListError; |
212 | } |
213 | } |
214 | |
215 | default: |
216 | // We should have covered all the bases. If not, let's get an error. |
217 | FATAL1("Unexpected d_type: %d\n" , entry->d_type); |
218 | return kListError; |
219 | } |
220 | } |
221 | done_ = true; |
222 | |
223 | if (errno != 0) { |
224 | return kListError; |
225 | } |
226 | |
227 | return kListDone; |
228 | } |
229 | |
230 | DirectoryListingEntry::~DirectoryListingEntry() { |
231 | ResetLink(); |
232 | if (lister_ != 0) { |
233 | // This also closes fd_. |
234 | VOID_NO_RETRY_EXPECTED(closedir(reinterpret_cast<DIR*>(lister_))); |
235 | } |
236 | } |
237 | |
238 | void DirectoryListingEntry::ResetLink() { |
239 | if ((link_ != NULL) && ((parent_ == NULL) || (parent_->link_ != link_))) { |
240 | delete link_; |
241 | link_ = NULL; |
242 | } |
243 | if (parent_ != NULL) { |
244 | link_ = parent_->link_; |
245 | } |
246 | } |
247 | |
248 | Directory::ExistsResult Directory::Exists(Namespace* namespc, |
249 | const char* dir_name) { |
250 | NamespaceScope ns(namespc, dir_name); |
251 | struct stat entry_info; |
252 | const int success = |
253 | TEMP_FAILURE_RETRY(fstatat(ns.fd(), ns.path(), &entry_info, 0)); |
254 | if (success == 0) { |
255 | if (S_ISDIR(entry_info.st_mode)) { |
256 | return EXISTS; |
257 | } else { |
258 | // An OSError may be constructed based on the return value of this |
259 | // function, so set errno to something that makes sense. |
260 | errno = ENOTDIR; |
261 | return DOES_NOT_EXIST; |
262 | } |
263 | } else { |
264 | if ((errno == EACCES) || (errno == EBADF) || (errno == EFAULT) || |
265 | (errno == ENOMEM) || (errno == EOVERFLOW)) { |
266 | // Search permissions denied for one of the directories in the |
267 | // path or a low level error occured. We do not know if the |
268 | // directory exists. |
269 | return UNKNOWN; |
270 | } |
271 | ASSERT((errno == ELOOP) || (errno == ENAMETOOLONG) || (errno == ENOENT) || |
272 | (errno == ENOTDIR)); |
273 | return DOES_NOT_EXIST; |
274 | } |
275 | } |
276 | |
277 | char* Directory::CurrentNoScope() { |
278 | return getcwd(NULL, 0); |
279 | } |
280 | |
281 | bool Directory::Create(Namespace* namespc, const char* dir_name) { |
282 | NamespaceScope ns(namespc, dir_name); |
283 | // Create the directory with the permissions specified by the |
284 | // process umask. |
285 | const int result = NO_RETRY_EXPECTED(mkdirat(ns.fd(), ns.path(), 0777)); |
286 | // If the directory already exists, treat it as a success. |
287 | if ((result == -1) && (errno == EEXIST)) { |
288 | return (Exists(namespc, dir_name) == EXISTS); |
289 | } |
290 | return (result == 0); |
291 | } |
292 | |
293 | const char* Directory::SystemTemp(Namespace* namespc) { |
294 | PathBuffer path; |
295 | const char* temp_dir = getenv("TMPDIR" ); |
296 | if (temp_dir == NULL) { |
297 | temp_dir = getenv("TMP" ); |
298 | } |
299 | if (temp_dir == NULL) { |
300 | temp_dir = "/tmp" ; |
301 | } |
302 | NamespaceScope ns(namespc, temp_dir); |
303 | if (!path.Add(ns.path())) { |
304 | return NULL; |
305 | } |
306 | |
307 | // Remove any trailing slash. |
308 | char* result = path.AsString(); |
309 | int length = strlen(result); |
310 | if ((length > 1) && (result[length - 1] == '/')) { |
311 | result[length - 1] = '\0'; |
312 | } |
313 | return path.AsScopedString(); |
314 | } |
315 | |
316 | // Returns a new, unused directory name, adding characters to the end |
317 | // of prefix. Creates the directory with the permissions specified |
318 | // by the process umask. |
319 | // The return value is Dart_ScopeAllocated. |
320 | const char* Directory::CreateTemp(Namespace* namespc, const char* prefix) { |
321 | PathBuffer path; |
322 | const int firstchar = 'A'; |
323 | const int numchars = 'Z' - 'A' + 1; |
324 | uint8_t random_bytes[7]; |
325 | |
326 | // mkdtemp doesn't have an "at" variant, so we have to simulate it. |
327 | if (!path.Add(prefix)) { |
328 | return NULL; |
329 | } |
330 | intptr_t prefix_length = path.length(); |
331 | while (true) { |
332 | Crypto::GetRandomBytes(6, random_bytes); |
333 | for (intptr_t i = 0; i < 6; i++) { |
334 | random_bytes[i] = (random_bytes[i] % numchars) + firstchar; |
335 | } |
336 | random_bytes[6] = '\0'; |
337 | if (!path.Add(reinterpret_cast<char*>(random_bytes))) { |
338 | return NULL; |
339 | } |
340 | NamespaceScope ns(namespc, path.AsString()); |
341 | const int result = NO_RETRY_EXPECTED(mkdirat(ns.fd(), ns.path(), 0777)); |
342 | if (result == 0) { |
343 | return path.AsScopedString(); |
344 | } else if (errno == EEXIST) { |
345 | path.Reset(prefix_length); |
346 | } else { |
347 | return NULL; |
348 | } |
349 | } |
350 | } |
351 | |
352 | static bool DeleteRecursively(int dirfd, PathBuffer* path); |
353 | |
354 | static bool DeleteFile(int dirfd, char* file_name, PathBuffer* path) { |
355 | return path->Add(file_name) && |
356 | (NO_RETRY_EXPECTED(unlinkat(dirfd, path->AsString(), 0)) == 0); |
357 | } |
358 | |
359 | static bool DeleteDir(int dirfd, char* dir_name, PathBuffer* path) { |
360 | if ((strcmp(dir_name, "." ) == 0) || (strcmp(dir_name, ".." ) == 0)) { |
361 | return true; |
362 | } |
363 | return path->Add(dir_name) && DeleteRecursively(dirfd, path); |
364 | } |
365 | |
366 | static bool DeleteRecursively(int dirfd, PathBuffer* path) { |
367 | // Do not recurse into links for deletion. Instead delete the link. |
368 | // If it's a file, delete it. |
369 | struct stat st; |
370 | if (TEMP_FAILURE_RETRY( |
371 | fstatat(dirfd, path->AsString(), &st, AT_SYMLINK_NOFOLLOW)) == -1) { |
372 | return false; |
373 | } else if (!S_ISDIR(st.st_mode)) { |
374 | return (NO_RETRY_EXPECTED(unlinkat(dirfd, path->AsString(), 0)) == 0); |
375 | } |
376 | |
377 | if (!path->Add(File::PathSeparator())) { |
378 | return false; |
379 | } |
380 | |
381 | // Not a link. Attempt to open as a directory and recurse into the |
382 | // directory. |
383 | const int fd = |
384 | TEMP_FAILURE_RETRY(openat(dirfd, path->AsString(), O_DIRECTORY)); |
385 | if (fd < 0) { |
386 | return false; |
387 | } |
388 | DIR* dir_pointer; |
389 | do { |
390 | dir_pointer = fdopendir(fd); |
391 | } while ((dir_pointer == NULL) && (errno == EINTR)); |
392 | if (dir_pointer == NULL) { |
393 | FDUtils::SaveErrorAndClose(fd); |
394 | return false; |
395 | } |
396 | |
397 | // Iterate the directory and delete all files and directories. |
398 | int path_length = path->length(); |
399 | while (true) { |
400 | // In case `readdir()` returns `NULL` we distinguish between end-of-stream |
401 | // and error by looking if `errno` was updated. |
402 | errno = 0; |
403 | // In glibc 2.24+, readdir_r is deprecated. |
404 | // According to the man page for readdir: |
405 | // "readdir(3) is not required to be thread-safe. However, in modern |
406 | // implementations (including the glibc implementation), concurrent calls to |
407 | // readdir(3) that specify different directory streams are thread-safe." |
408 | dirent* entry = readdir(dir_pointer); |
409 | if (entry == NULL) { |
410 | // Failed to read next directory entry. |
411 | if (errno != 0) { |
412 | break; |
413 | } |
414 | // End of directory. |
415 | int status = NO_RETRY_EXPECTED(closedir(dir_pointer)); |
416 | if (status != 0) { |
417 | return false; |
418 | } |
419 | status = |
420 | NO_RETRY_EXPECTED(unlinkat(dirfd, path->AsString(), AT_REMOVEDIR)); |
421 | return status == 0; |
422 | } |
423 | bool ok = false; |
424 | switch (entry->d_type) { |
425 | case DT_DIR: |
426 | ok = DeleteDir(dirfd, entry->d_name, path); |
427 | break; |
428 | case DT_BLK: |
429 | case DT_CHR: |
430 | case DT_FIFO: |
431 | case DT_SOCK: |
432 | case DT_REG: |
433 | case DT_LNK: |
434 | // Treat all links as files. This will delete the link which |
435 | // is what we want no matter if the link target is a file or a |
436 | // directory. |
437 | ok = DeleteFile(dirfd, entry->d_name, path); |
438 | break; |
439 | case DT_UNKNOWN: { |
440 | if (!path->Add(entry->d_name)) { |
441 | break; |
442 | } |
443 | // On some file systems the entry type is not determined by |
444 | // readdir. For those we use lstat to determine the entry |
445 | // type. |
446 | struct stat entry_info; |
447 | if (TEMP_FAILURE_RETRY(fstatat(dirfd, path->AsString(), &entry_info, |
448 | AT_SYMLINK_NOFOLLOW)) == -1) { |
449 | break; |
450 | } |
451 | path->Reset(path_length); |
452 | if (S_ISDIR(entry_info.st_mode)) { |
453 | ok = DeleteDir(dirfd, entry->d_name, path); |
454 | } else { |
455 | // Treat links as files. This will delete the link which is |
456 | // what we want no matter if the link target is a file or a |
457 | // directory. |
458 | ok = DeleteFile(dirfd, entry->d_name, path); |
459 | } |
460 | break; |
461 | } |
462 | default: |
463 | // We should have covered all the bases. If not, let's get an error. |
464 | FATAL1("Unexpected d_type: %d\n" , entry->d_type); |
465 | break; |
466 | } |
467 | if (!ok) { |
468 | break; |
469 | } |
470 | path->Reset(path_length); |
471 | } |
472 | // Only happens if an error. |
473 | ASSERT(errno != 0); |
474 | int err = errno; |
475 | VOID_NO_RETRY_EXPECTED(closedir(dir_pointer)); |
476 | errno = err; |
477 | return false; |
478 | } |
479 | |
480 | bool Directory::Delete(Namespace* namespc, |
481 | const char* dir_name, |
482 | bool recursive) { |
483 | NamespaceScope ns(namespc, dir_name); |
484 | if (!recursive) { |
485 | if ((File::GetType(namespc, dir_name, false) == File::kIsLink) && |
486 | (File::GetType(namespc, dir_name, true) == File::kIsDirectory)) { |
487 | return NO_RETRY_EXPECTED(unlinkat(ns.fd(), ns.path(), 0)) == 0; |
488 | } |
489 | return NO_RETRY_EXPECTED(unlinkat(ns.fd(), ns.path(), AT_REMOVEDIR)) == 0; |
490 | } else { |
491 | PathBuffer path; |
492 | if (!path.Add(ns.path())) { |
493 | return false; |
494 | } |
495 | return DeleteRecursively(ns.fd(), &path); |
496 | } |
497 | } |
498 | |
499 | bool Directory::Rename(Namespace* namespc, |
500 | const char* old_path, |
501 | const char* new_path) { |
502 | ExistsResult exists = Exists(namespc, old_path); |
503 | if (exists != EXISTS) { |
504 | return false; |
505 | } |
506 | NamespaceScope oldns(namespc, old_path); |
507 | NamespaceScope newns(namespc, new_path); |
508 | return (NO_RETRY_EXPECTED(renameat(oldns.fd(), oldns.path(), newns.fd(), |
509 | newns.path())) == 0); |
510 | } |
511 | |
512 | } // namespace bin |
513 | } // namespace dart |
514 | |
515 | #endif // defined(HOST_OS_FUCHSIA) |
516 | |