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
28namespace dart {
29namespace bin {
30
31PathBuffer::PathBuffer() : length_(0) {
32 data_ = calloc(PATH_MAX + 1, sizeof(char)); // NOLINT
33}
34
35PathBuffer::~PathBuffer() {
36 free(data_);
37}
38
39bool PathBuffer::AddW(const wchar_t* name) {
40 UNREACHABLE();
41 return false;
42}
43
44char* PathBuffer::AsString() const {
45 return reinterpret_cast<char*>(data_);
46}
47
48wchar_t* PathBuffer::AsStringW() const {
49 UNREACHABLE();
50 return NULL;
51}
52
53const char* PathBuffer::AsScopedString() const {
54 return DartUtils::ScopedCopyCString(AsString());
55}
56
57bool 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
76void 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.
83struct LinkList {
84 dev_t dev;
85 ino_t ino;
86 LinkList* next;
87};
88
89ListType 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
230DirectoryListingEntry::~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
238void 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
248Directory::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
277char* Directory::CurrentNoScope() {
278 return getcwd(NULL, 0);
279}
280
281bool 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
293const 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.
320const 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
352static bool DeleteRecursively(int dirfd, PathBuffer* path);
353
354static 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
359static 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
366static 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
480bool 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
499bool 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