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