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