Flutter Engine
The Flutter Engine
Loading...
Searching...
No Matches
directory_fuchsia.cc
Go to the documentation of this file.
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(DART_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"
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) {
41 return false;
42}
43
44char* PathBuffer::AsString() const {
45 return reinterpret_cast<char*>(data_);
46}
47
48wchar_t* PathBuffer::AsStringW() const {
50 return nullptr;
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_ != nullptr) {
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 != nullptr) {
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.
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 != nullptr) {
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 || (S_IFMT & entry_info.st_mode) == 0) {
182 // Report a broken link as a link, even if follow_links is true.
183 // A symbolic link can potentially point to an anon_inode. For
184 // example, an epoll file descriptor will have a symbolic link whose
185 // content is the string anon_inode:[eventpoll]. In this case, the
186 // target doesn't belong to any regular file category.
187 return kListLink;
188 }
189 if (S_ISDIR(entry_info.st_mode)) {
190 // Recurse into the subdirectory with current_link added to the
191 // linked list of seen file system links.
192 link_ = new LinkList(current_link);
193 if ((strcmp(entry->d_name, ".") == 0) ||
194 (strcmp(entry->d_name, "..") == 0)) {
195 return Next(listing);
196 }
197 return kListDirectory;
198 }
199 }
200 if (S_ISDIR(entry_info.st_mode)) {
201 if ((strcmp(entry->d_name, ".") == 0) ||
202 (strcmp(entry->d_name, "..") == 0)) {
203 return Next(listing);
204 }
205 return kListDirectory;
206 } else if (S_ISLNK(entry_info.st_mode)) {
207 return kListLink;
208 } else {
209 // Regular files, character devices, block devices, fifos, sockets and
210 // unknown types are all considered as files.
211 return kListFile;
212 }
213 }
214
215 default:
216 // We should have covered all the bases. If not, let's get an error.
217 FATAL("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_ != nullptr) &&
240 ((parent_ == nullptr) || (parent_->link_ != link_))) {
241 delete link_;
242 link_ = nullptr;
243 }
244 if (parent_ != nullptr) {
245 link_ = parent_->link_;
246 }
247}
248
249Directory::ExistsResult Directory::Exists(Namespace* namespc,
250 const char* dir_name) {
251 NamespaceScope ns(namespc, dir_name);
252 struct stat entry_info;
253 const int success =
254 TEMP_FAILURE_RETRY(fstatat(ns.fd(), ns.path(), &entry_info, 0));
255 if (success == 0) {
256 if (S_ISDIR(entry_info.st_mode)) {
257 return EXISTS;
258 } else {
259 // An OSError may be constructed based on the return value of this
260 // function, so set errno to something that makes sense.
261 errno = ENOTDIR;
262 return DOES_NOT_EXIST;
263 }
264 } else {
265 if ((errno == EACCES) || (errno == EBADF) || (errno == EFAULT) ||
266 (errno == ENOMEM) || (errno == EOVERFLOW)) {
267 // Search permissions denied for one of the directories in the
268 // path or a low level error occurred. We do not know if the
269 // directory exists.
270 return UNKNOWN;
271 }
272 ASSERT((errno == ELOOP) || (errno == ENAMETOOLONG) || (errno == ENOENT) ||
273 (errno == ENOTDIR));
274 return DOES_NOT_EXIST;
275 }
276}
277
278char* Directory::CurrentNoScope() {
279 return getcwd(nullptr, 0);
280}
281
282bool Directory::Create(Namespace* namespc, const char* dir_name) {
283 NamespaceScope ns(namespc, dir_name);
284 // Create the directory with the permissions specified by the
285 // process umask.
286 const int result = NO_RETRY_EXPECTED(mkdirat(ns.fd(), ns.path(), 0777));
287 // If the directory already exists, treat it as a success.
288 if ((result == -1) && (errno == EEXIST)) {
289 return (Exists(namespc, dir_name) == EXISTS);
290 }
291 return (result == 0);
292}
293
294const char* Directory::SystemTemp(Namespace* namespc) {
295 PathBuffer path;
296 const char* temp_dir = getenv("TMPDIR");
297 if (temp_dir == nullptr) {
298 temp_dir = getenv("TMP");
299 }
300 if (temp_dir == nullptr) {
301 temp_dir = "/tmp";
302 }
303 NamespaceScope ns(namespc, temp_dir);
304 if (!path.Add(ns.path())) {
305 return nullptr;
306 }
307
308 // Remove any trailing slash.
309 char* result = path.AsString();
310 int length = strlen(result);
311 if ((length > 1) && (result[length - 1] == '/')) {
312 result[length - 1] = '\0';
313 }
314 return path.AsScopedString();
315}
316
317// Returns a new, unused directory name, adding characters to the end
318// of prefix. Creates the directory with the permissions specified
319// by the process umask.
320// The return value is Dart_ScopeAllocated.
321const char* Directory::CreateTemp(Namespace* namespc, const char* prefix) {
322 PathBuffer path;
323 const int firstchar = 'A';
324 const int numchars = 'Z' - 'A' + 1;
325 uint8_t random_bytes[7];
326
327 // mkdtemp doesn't have an "at" variant, so we have to simulate it.
328 if (!path.Add(prefix)) {
329 return nullptr;
330 }
331 intptr_t prefix_length = path.length();
332 while (true) {
333 Crypto::GetRandomBytes(6, random_bytes);
334 for (intptr_t i = 0; i < 6; i++) {
335 random_bytes[i] = (random_bytes[i] % numchars) + firstchar;
336 }
337 random_bytes[6] = '\0';
338 if (!path.Add(reinterpret_cast<char*>(random_bytes))) {
339 return nullptr;
340 }
341 NamespaceScope ns(namespc, path.AsString());
342 const int result = NO_RETRY_EXPECTED(mkdirat(ns.fd(), ns.path(), 0777));
343 if (result == 0) {
344 return path.AsScopedString();
345 } else if (errno == EEXIST) {
346 path.Reset(prefix_length);
347 } else {
348 return nullptr;
349 }
350 }
351}
352
353static bool DeleteRecursively(int dirfd, PathBuffer* path);
354
355static bool DeleteFile(int dirfd, char* file_name, PathBuffer* path) {
356 return path->Add(file_name) &&
357 (NO_RETRY_EXPECTED(unlinkat(dirfd, path->AsString(), 0)) == 0);
358}
359
360static bool DeleteDir(int dirfd, char* dir_name, PathBuffer* path) {
361 if ((strcmp(dir_name, ".") == 0) || (strcmp(dir_name, "..") == 0)) {
362 return true;
363 }
364 return path->Add(dir_name) && DeleteRecursively(dirfd, path);
365}
366
367static bool DeleteRecursively(int dirfd, PathBuffer* path) {
368 // Do not recurse into links for deletion. Instead delete the link.
369 // If it's a file, delete it.
370 struct stat st;
372 fstatat(dirfd, path->AsString(), &st, AT_SYMLINK_NOFOLLOW)) == -1) {
373 return false;
374 } else if (!S_ISDIR(st.st_mode)) {
375 return (NO_RETRY_EXPECTED(unlinkat(dirfd, path->AsString(), 0)) == 0);
376 }
377
378 if (!path->Add(File::PathSeparator())) {
379 return false;
380 }
381
382 // Not a link. Attempt to open as a directory and recurse into the
383 // directory.
384 const int fd =
385 TEMP_FAILURE_RETRY(openat(dirfd, path->AsString(), O_DIRECTORY));
386 if (fd < 0) {
387 return false;
388 }
389 DIR* dir_pointer;
390 do {
391 dir_pointer = fdopendir(fd);
392 } while ((dir_pointer == nullptr) && (errno == EINTR));
393 if (dir_pointer == nullptr) {
394 FDUtils::SaveErrorAndClose(fd);
395 return false;
396 }
397
398 // Iterate the directory and delete all files and directories.
399 int path_length = path->length();
400 while (true) {
401 // In case `readdir()` returns `nullptr` we distinguish between
402 // end-of-stream and error by looking if `errno` was updated.
403 errno = 0;
404 // In glibc 2.24+, readdir_r is deprecated.
405 // According to the man page for readdir:
406 // "readdir(3) is not required to be thread-safe. However, in modern
407 // implementations (including the glibc implementation), concurrent calls to
408 // readdir(3) that specify different directory streams are thread-safe."
409 dirent* entry = readdir(dir_pointer);
410 if (entry == nullptr) {
411 // Failed to read next directory entry.
412 if (errno != 0) {
413 break;
414 }
415 // End of directory.
416 int status = NO_RETRY_EXPECTED(closedir(dir_pointer));
417 if (status != 0) {
418 return false;
419 }
420 status =
421 NO_RETRY_EXPECTED(unlinkat(dirfd, path->AsString(), AT_REMOVEDIR));
422 return status == 0;
423 }
424 bool ok = false;
425 switch (entry->d_type) {
426 case DT_DIR:
427 ok = DeleteDir(dirfd, entry->d_name, path);
428 break;
429 case DT_BLK:
430 case DT_CHR:
431 case DT_FIFO:
432 case DT_SOCK:
433 case DT_REG:
434 case DT_LNK:
435 // Treat all links as files. This will delete the link which
436 // is what we want no matter if the link target is a file or a
437 // directory.
438 ok = DeleteFile(dirfd, entry->d_name, path);
439 break;
440 case DT_UNKNOWN: {
441 if (!path->Add(entry->d_name)) {
442 break;
443 }
444 // On some file systems the entry type is not determined by
445 // readdir. For those we use lstat to determine the entry
446 // type.
447 struct stat entry_info;
448 if (TEMP_FAILURE_RETRY(fstatat(dirfd, path->AsString(), &entry_info,
449 AT_SYMLINK_NOFOLLOW)) == -1) {
450 break;
451 }
452 path->Reset(path_length);
453 if (S_ISDIR(entry_info.st_mode)) {
454 ok = DeleteDir(dirfd, entry->d_name, path);
455 } else {
456 // Treat links as files. This will delete the link which is
457 // what we want no matter if the link target is a file or a
458 // directory.
459 ok = DeleteFile(dirfd, entry->d_name, path);
460 }
461 break;
462 }
463 default:
464 // We should have covered all the bases. If not, let's get an error.
465 FATAL("Unexpected d_type: %d\n", entry->d_type);
466 break;
467 }
468 if (!ok) {
469 break;
470 }
471 path->Reset(path_length);
472 }
473 // Only happens if an error.
474 ASSERT(errno != 0);
475 int err = errno;
476 VOID_NO_RETRY_EXPECTED(closedir(dir_pointer));
477 errno = err;
478 return false;
479}
480
481bool Directory::Delete(Namespace* namespc,
482 const char* dir_name,
483 bool recursive) {
484 NamespaceScope ns(namespc, dir_name);
485 if (!recursive) {
486 if ((File::GetType(namespc, dir_name, false) == File::kIsLink) &&
487 (File::GetType(namespc, dir_name, true) == File::kIsDirectory)) {
488 return NO_RETRY_EXPECTED(unlinkat(ns.fd(), ns.path(), 0)) == 0;
489 }
490 return NO_RETRY_EXPECTED(unlinkat(ns.fd(), ns.path(), AT_REMOVEDIR)) == 0;
491 } else {
492 PathBuffer path;
493 if (!path.Add(ns.path())) {
494 return false;
495 }
496 return DeleteRecursively(ns.fd(), &path);
497 }
498}
499
500bool Directory::Rename(Namespace* namespc,
501 const char* old_path,
502 const char* new_path) {
503 ExistsResult exists = Exists(namespc, old_path);
504 if (exists != EXISTS) {
505 return false;
506 }
507 NamespaceScope oldns(namespc, old_path);
508 NamespaceScope newns(namespc, new_path);
509 return (NO_RETRY_EXPECTED(renameat(oldns.fd(), oldns.path(), newns.fd(),
510 newns.path())) == 0);
511}
512
513} // namespace bin
514} // namespace dart
515
516#endif // defined(DART_HOST_OS_FUCHSIA)
static float next(float f)
static bool ok(int result)
#define UNREACHABLE()
Definition assert.h:248
#define ASSERT(E)
#define FATAL(error)
GAsyncResult * result
const char * name
Definition fuchsia.cc:50
size_t length
@ kListDirectory
Definition directory.h:20
void * calloc(size_t n, size_t size)
Definition allocation.cc:11
DEF_SWITCHES_START aot vmservice shared library Name of the *so containing AOT compiled Dart assets for launching the service isolate vm snapshot The VM snapshot data that will be memory mapped as read only SnapshotAssetPath must be present isolate snapshot The isolate snapshot data that will be memory mapped as read only SnapshotAssetPath must be present cache dir path
Definition switches.h:57
DEF_SWITCHES_START aot vmservice shared library Name of the *so containing AOT compiled Dart assets for launching the service isolate vm snapshot data
Definition switches.h:41
#define FALL_THROUGH
Definition globals.h:15
#define PATH_MAX
Definition globals.h:708
#define NO_RETRY_EXPECTED(expression)
#define VOID_NO_RETRY_EXPECTED(expression)
#define TEMP_FAILURE_RETRY(expression)
#define DeleteFile