Flutter Engine
The Flutter Engine
Loading...
Searching...
No Matches
directory_macos.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_MACOS)
7
8#include "bin/directory.h"
9
10#include <dirent.h> // NOLINT
11#include <errno.h> // NOLINT
12#include <string.h> // NOLINT
13#include <sys/param.h> // NOLINT
14#include <sys/stat.h> // NOLINT
15#include <unistd.h> // NOLINT
16
17#include "bin/dartutils.h"
18#include "bin/file.h"
19#include "bin/namespace.h"
20#include "bin/platform.h"
22
23namespace dart {
24namespace bin {
25
26PathBuffer::PathBuffer() : length_(0) {
27 data_ = calloc(PATH_MAX + 1, sizeof(char)); // NOLINT
28}
29
30PathBuffer::~PathBuffer() {
31 free(data_);
32}
33
34bool PathBuffer::AddW(const wchar_t* name) {
36 return false;
37}
38
39char* PathBuffer::AsString() const {
40 return reinterpret_cast<char*>(data_);
41}
42
43wchar_t* PathBuffer::AsStringW() const {
45 return nullptr;
46}
47
48const char* PathBuffer::AsScopedString() const {
49 return DartUtils::ScopedCopyCString(AsString());
50}
51
52bool PathBuffer::Add(const char* name) {
53 char* data = AsString();
54 int written = snprintf(data + length_, PATH_MAX - length_, "%s", name);
55 data[PATH_MAX] = '\0';
56 if ((written <= PATH_MAX - length_) && (written >= 0) &&
57 (static_cast<size_t>(written) == strlen(name))) {
58 length_ += written;
59 return true;
60 } else {
61 errno = ENAMETOOLONG;
62 return false;
63 }
64}
65
66void PathBuffer::Reset(intptr_t new_length) {
67 length_ = new_length;
68 AsString()[length_] = '\0';
69}
70
71// A linked list of symbolic links, with their unique file system identifiers.
72// These are scanned to detect loops while doing a recursive directory listing.
73struct LinkList {
74 dev_t dev;
75 ino_t ino;
76 LinkList* next;
77};
78
79ListType DirectoryListingEntry::Next(DirectoryListing* listing) {
80 if (done_) {
81 return kListDone;
82 }
83
84 if (lister_ == 0) {
85 do {
86 lister_ = reinterpret_cast<intptr_t>(
87 opendir(listing->path_buffer().AsString()));
88 } while ((lister_ == 0) && (errno == EINTR));
89
90 if (lister_ == 0) {
91 done_ = true;
92 return kListError;
93 }
94 if (parent_ != nullptr) {
95 if (!listing->path_buffer().Add(File::PathSeparator())) {
96 return kListError;
97 }
98 }
99 path_length_ = listing->path_buffer().length();
100 }
101 // Reset.
102 listing->path_buffer().Reset(path_length_);
103 ResetLink();
104
105 // Iterate the directory and post the directories and files to the
106 // ports.
107 int status = 0;
108 dirent entry;
109 dirent* result;
110 status = NO_RETRY_EXPECTED(
111 readdir_r(reinterpret_cast<DIR*>(lister_), &entry, &result));
112 if ((status == 0) && (result != nullptr)) {
113 if (!listing->path_buffer().Add(entry.d_name)) {
114 done_ = true;
115 return kListError;
116 }
117 switch (entry.d_type) {
118 case DT_DIR:
119 if ((strcmp(entry.d_name, ".") == 0) ||
120 (strcmp(entry.d_name, "..") == 0)) {
121 return Next(listing);
122 }
123 return kListDirectory;
124 case DT_BLK:
125 case DT_CHR:
126 case DT_FIFO:
127 case DT_SOCK:
128 case DT_REG:
129 return kListFile;
130 case DT_LNK:
131 if (!listing->follow_links()) {
132 return kListLink;
133 }
134 // Else fall through to next case.
136 case DT_UNKNOWN: {
137 // On some file systems the entry type is not determined by
138 // readdir_r. For those and for links we use stat to determine
139 // the actual entry type. Notice that stat returns the type of
140 // the file pointed to.
141 struct stat entry_info;
142 int stat_success;
143 stat_success = NO_RETRY_EXPECTED(
144 lstat(listing->path_buffer().AsString(), &entry_info));
145 if (stat_success == -1) {
146 return kListError;
147 }
148 if (listing->follow_links() && S_ISLNK(entry_info.st_mode)) {
149 // Check to see if we are in a loop created by a symbolic link.
150 LinkList current_link = {entry_info.st_dev, entry_info.st_ino, link_};
151 LinkList* previous = link_;
152 while (previous != nullptr) {
153 if ((previous->dev == current_link.dev) &&
154 (previous->ino == current_link.ino)) {
155 // Report the looping link as a link, rather than following it.
156 return kListLink;
157 }
158 previous = previous->next;
159 }
160 stat_success = NO_RETRY_EXPECTED(
161 stat(listing->path_buffer().AsString(), &entry_info));
162 if (stat_success == -1 || (S_IFMT & entry_info.st_mode) == 0) {
163 // Report a broken link as a link, even if follow_links is true.
164 // A symbolic link can potentially point to an anon_inode. For
165 // example, an epoll file descriptor will have a symbolic link whose
166 // content is the string anon_inode:[eventpoll]. In this case, the
167 // target doesn't belong to any regular file category.
168 return kListLink;
169 }
170 if (S_ISDIR(entry_info.st_mode)) {
171 // Recurse into the subdirectory with current_link added to the
172 // linked list of seen file system links.
173 link_ = new LinkList(current_link);
174 if ((strcmp(entry.d_name, ".") == 0) ||
175 (strcmp(entry.d_name, "..") == 0)) {
176 return Next(listing);
177 }
178 return kListDirectory;
179 }
180 }
181 if (S_ISDIR(entry_info.st_mode)) {
182 if ((strcmp(entry.d_name, ".") == 0) ||
183 (strcmp(entry.d_name, "..") == 0)) {
184 return Next(listing);
185 }
186 return kListDirectory;
187 } else if (S_ISLNK(entry_info.st_mode)) {
188 return kListLink;
189 } else {
190 // Regular files, character devices, block devices, fifos, sockets and
191 // unknown types are all considered as files.
192 return kListFile;
193 }
194 }
195
196 default:
197 // We should have covered all the bases. If not, let's get an error.
198 FATAL("Unexpected d_type: %d\n", entry.d_type);
199 return kListError;
200 }
201 }
202 done_ = true;
203
204 if (status != 0) {
205 errno = status;
206 return kListError;
207 }
208
209 return kListDone;
210}
211
212DirectoryListingEntry::~DirectoryListingEntry() {
213 ResetLink();
214 if (lister_ != 0) {
215 closedir(reinterpret_cast<DIR*>(lister_));
216 }
217}
218
219void DirectoryListingEntry::ResetLink() {
220 if ((link_ != nullptr) &&
221 ((parent_ == nullptr) || (parent_->link_ != link_))) {
222 delete link_;
223 link_ = nullptr;
224 }
225 if (parent_ != nullptr) {
226 link_ = parent_->link_;
227 }
228}
229
230static bool DeleteRecursively(PathBuffer* path);
231
232static bool DeleteFile(char* file_name, PathBuffer* path) {
233 return path->Add(file_name) && (unlink(path->AsString()) == 0);
234}
235
236static bool DeleteDir(char* dir_name, PathBuffer* path) {
237 if ((strcmp(dir_name, ".") == 0) || (strcmp(dir_name, "..") == 0)) {
238 return true;
239 }
240 return path->Add(dir_name) && DeleteRecursively(path);
241}
242
243static bool DeleteRecursively(PathBuffer* path) {
244 // Do not recurse into links for deletion. Instead delete the link.
245 // If it's a file, delete it.
246 struct stat st;
247 if (NO_RETRY_EXPECTED(lstat(path->AsString(), &st)) == -1) {
248 return false;
249 } else if (!S_ISDIR(st.st_mode)) {
250 return (unlink(path->AsString()) == 0);
251 }
252
253 if (!path->Add(File::PathSeparator())) {
254 return false;
255 }
256
257 // Not a link. Attempt to open as a directory and recurse into the
258 // directory.
259 DIR* dir_pointer;
260 do {
261 dir_pointer = opendir(path->AsString());
262 } while ((dir_pointer == nullptr) && (errno == EINTR));
263 if (dir_pointer == nullptr) {
264 return false;
265 }
266
267 // Iterate the directory and delete all files and directories.
268 int path_length = path->length();
269 dirent entry;
270 dirent* result;
271 while (NO_RETRY_EXPECTED(readdir_r(dir_pointer, &entry, &result)) == 0) {
272 if (result == nullptr) {
273 // End of directory.
274 return (NO_RETRY_EXPECTED(closedir(dir_pointer)) == 0) &&
275 (NO_RETRY_EXPECTED(remove(path->AsString())) == 0);
276 }
277 bool ok = false;
278 switch (entry.d_type) {
279 case DT_DIR:
280 ok = DeleteDir(entry.d_name, path);
281 break;
282 case DT_BLK:
283 case DT_CHR:
284 case DT_FIFO:
285 case DT_SOCK:
286 case DT_REG:
287 case DT_LNK:
288 // Treat all links as files. This will delete the link which
289 // is what we want no matter if the link target is a file or a
290 // directory.
291 ok = DeleteFile(entry.d_name, path);
292 break;
293 case DT_UNKNOWN: {
294 if (!path->Add(entry.d_name)) {
295 break;
296 }
297 // On some file systems the entry type is not determined by
298 // readdir_r. For those we use lstat to determine the entry
299 // type.
300 struct stat entry_info;
301 if (NO_RETRY_EXPECTED(lstat(path->AsString(), &entry_info)) == -1) {
302 break;
303 }
304 path->Reset(path_length);
305 if (S_ISDIR(entry_info.st_mode)) {
306 ok = DeleteDir(entry.d_name, path);
307 } else {
308 // Treat links as files. This will delete the link which is
309 // what we want no matter if the link target is a file or a
310 // directory.
311 ok = DeleteFile(entry.d_name, path);
312 }
313 break;
314 }
315 default:
316 // We should have covered all the bases. If not, let's get an error.
317 FATAL("Unexpected d_type: %d\n", entry.d_type);
318 break;
319 }
320 if (!ok) {
321 break;
322 }
323 path->Reset(path_length);
324 }
325 // Only happens if an error.
326 ASSERT(errno != 0);
327 int err = errno;
328 VOID_NO_RETRY_EXPECTED(closedir(dir_pointer));
329 errno = err;
330 return false;
331}
332
333Directory::ExistsResult Directory::Exists(Namespace* namespc,
334 const char* dir_name) {
335 struct stat entry_info;
336 int success = NO_RETRY_EXPECTED(stat(dir_name, &entry_info));
337 if (success == 0) {
338 if (S_ISDIR(entry_info.st_mode)) {
339 return EXISTS;
340 } else {
341 // An OSError may be constructed based on the return value of this
342 // function, so set errno to something that makes sense.
343 errno = ENOTDIR;
344 return DOES_NOT_EXIST;
345 }
346 } else {
347 if ((errno == EACCES) || (errno == EBADF) || (errno == EFAULT) ||
348 (errno == ENOMEM) || (errno == EOVERFLOW)) {
349 // Search permissions denied for one of the directories in the
350 // path or a low level error occurred. We do not know if the
351 // directory exists.
352 return UNKNOWN;
353 }
354 ASSERT((errno == ELOOP) || (errno == ENAMETOOLONG) || (errno == ENOENT) ||
355 (errno == ENOTDIR));
356 return DOES_NOT_EXIST;
357 }
358}
359
360char* Directory::CurrentNoScope() {
361 return getcwd(nullptr, 0);
362}
363
364bool Directory::Create(Namespace* namespc, const char* dir_name) {
365 // Create the directory with the permissions specified by the
366 // process umask.
367 int result = NO_RETRY_EXPECTED(mkdir(dir_name, 0777));
368 // If the directory already exists, treat it as a success.
369 if ((result == -1) && (errno == EEXIST)) {
370 return (Exists(namespc, dir_name) == EXISTS);
371 }
372 return (result == 0);
373}
374
375const char* Directory::SystemTemp(Namespace* namespc) {
376 PathBuffer path;
377 const char* temp_dir = getenv("TMPDIR");
378 if (temp_dir == nullptr) {
379 temp_dir = getenv("TMP");
380 }
381 if (temp_dir == nullptr) {
382 temp_dir = "/tmp";
383 }
384 if (!path.Add(temp_dir)) {
385 return nullptr;
386 }
387 // Remove any trailing slash.
388 char* result = path.AsString();
389 int length = strlen(result);
390 if ((length > 1) && (result[length - 1] == '/')) {
391 result[length - 1] = '\0';
392 }
393 return path.AsScopedString();
394}
395
396const char* Directory::CreateTemp(Namespace* namespc, const char* prefix) {
397 // Returns a new, unused directory name, adding characters to the end
398 // of prefix. Creates the directory with the permissions specified
399 // by the process umask.
400 // The return value is Dart_ScopeAllocated.
401 PathBuffer path;
402 if (!path.Add(prefix)) {
403 return nullptr;
404 }
405 if (!path.Add("XXXXXX")) {
406 // Pattern has overflowed.
407 return nullptr;
408 }
409 char* result;
410 do {
411 result = mkdtemp(path.AsString());
412 } while ((result == nullptr) && (errno == EINTR));
413 if (result == nullptr) {
414 return nullptr;
415 }
416 return path.AsScopedString();
417}
418
419bool Directory::Delete(Namespace* namespc,
420 const char* dir_name,
421 bool recursive) {
422 if (!recursive) {
423 if ((File::GetType(namespc, dir_name, false) == File::kIsLink) &&
424 (File::GetType(namespc, dir_name, true) == File::kIsDirectory)) {
425 return (NO_RETRY_EXPECTED(unlink(dir_name)) == 0);
426 }
427 return (NO_RETRY_EXPECTED(rmdir(dir_name)) == 0);
428 } else {
429 PathBuffer path;
430 if (!path.Add(dir_name)) {
431 return false;
432 }
433 return DeleteRecursively(&path);
434 }
435}
436
437bool Directory::Rename(Namespace* namespc,
438 const char* path,
439 const char* new_path) {
440 ExistsResult exists = Exists(namespc, path);
441 if (exists != EXISTS) {
442 return false;
443 }
444 return (NO_RETRY_EXPECTED(rename(path, new_path)) == 0);
445}
446
447} // namespace bin
448} // namespace dart
449
450#endif // defined(DART_HOST_OS_MACOS)
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 DeleteFile