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_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"
21#include "platform/signal_blocker.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) {
35 UNREACHABLE();
36 return false;
37}
38
39char* PathBuffer::AsString() const {
40 return reinterpret_cast<char*>(data_);
41}
42
43wchar_t* PathBuffer::AsStringW() const {
44 UNREACHABLE();
45 return NULL;
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_ != NULL) {
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 != NULL)) {
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.
135 FALL_THROUGH;
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 != NULL) {
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) {
163 // Report a broken link as a link, even if follow_links is true.
164 return kListLink;
165 }
166 if (S_ISDIR(entry_info.st_mode)) {
167 // Recurse into the subdirectory with current_link added to the
168 // linked list of seen file system links.
169 link_ = new LinkList(current_link);
170 if ((strcmp(entry.d_name, ".") == 0) ||
171 (strcmp(entry.d_name, "..") == 0)) {
172 return Next(listing);
173 }
174 return kListDirectory;
175 }
176 }
177 if (S_ISDIR(entry_info.st_mode)) {
178 if ((strcmp(entry.d_name, ".") == 0) ||
179 (strcmp(entry.d_name, "..") == 0)) {
180 return Next(listing);
181 }
182 return kListDirectory;
183 } else if (S_ISREG(entry_info.st_mode) || S_ISCHR(entry_info.st_mode) ||
184 S_ISBLK(entry_info.st_mode) ||
185 S_ISFIFO(entry_info.st_mode) ||
186 S_ISSOCK(entry_info.st_mode)) {
187 return kListFile;
188 } else if (S_ISLNK(entry_info.st_mode)) {
189 return kListLink;
190 } else {
191 FATAL1("Unexpected st_mode: %d\n", entry_info.st_mode);
192 return kListError;
193 }
194 }
195
196 default:
197 // We should have covered all the bases. If not, let's get an error.
198 FATAL1("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_ != NULL) && ((parent_ == NULL) || (parent_->link_ != link_))) {
221 delete link_;
222 link_ = NULL;
223 }
224 if (parent_ != NULL) {
225 link_ = parent_->link_;
226 }
227}
228
229static bool DeleteRecursively(PathBuffer* path);
230
231static bool DeleteFile(char* file_name, PathBuffer* path) {
232 return path->Add(file_name) && (unlink(path->AsString()) == 0);
233}
234
235static bool DeleteDir(char* dir_name, PathBuffer* path) {
236 if ((strcmp(dir_name, ".") == 0) || (strcmp(dir_name, "..") == 0)) {
237 return true;
238 }
239 return path->Add(dir_name) && DeleteRecursively(path);
240}
241
242static bool DeleteRecursively(PathBuffer* path) {
243 // Do not recurse into links for deletion. Instead delete the link.
244 // If it's a file, delete it.
245 struct stat st;
246 if (NO_RETRY_EXPECTED(lstat(path->AsString(), &st)) == -1) {
247 return false;
248 } else if (!S_ISDIR(st.st_mode)) {
249 return (unlink(path->AsString()) == 0);
250 }
251
252 if (!path->Add(File::PathSeparator())) {
253 return false;
254 }
255
256 // Not a link. Attempt to open as a directory and recurse into the
257 // directory.
258 DIR* dir_pointer;
259 do {
260 dir_pointer = opendir(path->AsString());
261 } while ((dir_pointer == NULL) && (errno == EINTR));
262 if (dir_pointer == NULL) {
263 return false;
264 }
265
266 // Iterate the directory and delete all files and directories.
267 int path_length = path->length();
268 dirent entry;
269 dirent* result;
270 while (NO_RETRY_EXPECTED(readdir_r(dir_pointer, &entry, &result)) == 0) {
271 if (result == NULL) {
272 // End of directory.
273 return (NO_RETRY_EXPECTED(closedir(dir_pointer)) == 0) &&
274 (NO_RETRY_EXPECTED(remove(path->AsString())) == 0);
275 }
276 bool ok = false;
277 switch (entry.d_type) {
278 case DT_DIR:
279 ok = DeleteDir(entry.d_name, path);
280 break;
281 case DT_BLK:
282 case DT_CHR:
283 case DT_FIFO:
284 case DT_SOCK:
285 case DT_REG:
286 case DT_LNK:
287 // Treat all links as files. This will delete the link which
288 // is what we want no matter if the link target is a file or a
289 // directory.
290 ok = DeleteFile(entry.d_name, path);
291 break;
292 case DT_UNKNOWN: {
293 if (!path->Add(entry.d_name)) {
294 break;
295 }
296 // On some file systems the entry type is not determined by
297 // readdir_r. For those we use lstat to determine the entry
298 // type.
299 struct stat entry_info;
300 if (NO_RETRY_EXPECTED(lstat(path->AsString(), &entry_info)) == -1) {
301 break;
302 }
303 path->Reset(path_length);
304 if (S_ISDIR(entry_info.st_mode)) {
305 ok = DeleteDir(entry.d_name, path);
306 } else {
307 // Treat links as files. This will delete the link which is
308 // what we want no matter if the link target is a file or a
309 // directory.
310 ok = DeleteFile(entry.d_name, path);
311 }
312 break;
313 }
314 default:
315 // We should have covered all the bases. If not, let's get an error.
316 FATAL1("Unexpected d_type: %d\n", entry.d_type);
317 break;
318 }
319 if (!ok) {
320 break;
321 }
322 path->Reset(path_length);
323 }
324 // Only happens if an error.
325 ASSERT(errno != 0);
326 int err = errno;
327 VOID_NO_RETRY_EXPECTED(closedir(dir_pointer));
328 errno = err;
329 return false;
330}
331
332Directory::ExistsResult Directory::Exists(Namespace* namespc,
333 const char* dir_name) {
334 struct stat entry_info;
335 int success = NO_RETRY_EXPECTED(stat(dir_name, &entry_info));
336 if (success == 0) {
337 if (S_ISDIR(entry_info.st_mode)) {
338 return EXISTS;
339 } else {
340 // An OSError may be constructed based on the return value of this
341 // function, so set errno to something that makes sense.
342 errno = ENOTDIR;
343 return DOES_NOT_EXIST;
344 }
345 } else {
346 if ((errno == EACCES) || (errno == EBADF) || (errno == EFAULT) ||
347 (errno == ENOMEM) || (errno == EOVERFLOW)) {
348 // Search permissions denied for one of the directories in the
349 // path or a low level error occured. We do not know if the
350 // directory exists.
351 return UNKNOWN;
352 }
353 ASSERT((errno == ELOOP) || (errno == ENAMETOOLONG) || (errno == ENOENT) ||
354 (errno == ENOTDIR));
355 return DOES_NOT_EXIST;
356 }
357}
358
359char* Directory::CurrentNoScope() {
360 return getcwd(NULL, 0);
361}
362
363bool Directory::Create(Namespace* namespc, const char* dir_name) {
364 // Create the directory with the permissions specified by the
365 // process umask.
366 int result = NO_RETRY_EXPECTED(mkdir(dir_name, 0777));
367 // If the directory already exists, treat it as a success.
368 if ((result == -1) && (errno == EEXIST)) {
369 return (Exists(namespc, dir_name) == EXISTS);
370 }
371 return (result == 0);
372}
373
374const char* Directory::SystemTemp(Namespace* namespc) {
375 PathBuffer path;
376 const char* temp_dir = getenv("TMPDIR");
377 if (temp_dir == NULL) {
378 temp_dir = getenv("TMP");
379 }
380 if (temp_dir == NULL) {
381 temp_dir = "/tmp";
382 }
383 if (!path.Add(temp_dir)) {
384 return NULL;
385 }
386 // Remove any trailing slash.
387 char* result = path.AsString();
388 int length = strlen(result);
389 if ((length > 1) && (result[length - 1] == '/')) {
390 result[length - 1] = '\0';
391 }
392 return path.AsScopedString();
393}
394
395const char* Directory::CreateTemp(Namespace* namespc, const char* prefix) {
396 // Returns a new, unused directory name, adding characters to the end
397 // of prefix. Creates the directory with the permissions specified
398 // by the process umask.
399 // The return value is Dart_ScopeAllocated.
400 PathBuffer path;
401 if (!path.Add(prefix)) {
402 return NULL;
403 }
404 if (!path.Add("XXXXXX")) {
405 // Pattern has overflowed.
406 return NULL;
407 }
408 char* result;
409 do {
410 result = mkdtemp(path.AsString());
411 } while ((result == NULL) && (errno == EINTR));
412 if (result == NULL) {
413 return NULL;
414 }
415 return path.AsScopedString();
416}
417
418bool Directory::Delete(Namespace* namespc,
419 const char* dir_name,
420 bool recursive) {
421 if (!recursive) {
422 if ((File::GetType(namespc, dir_name, false) == File::kIsLink) &&
423 (File::GetType(namespc, dir_name, true) == File::kIsDirectory)) {
424 return (NO_RETRY_EXPECTED(unlink(dir_name)) == 0);
425 }
426 return (NO_RETRY_EXPECTED(rmdir(dir_name)) == 0);
427 } else {
428 PathBuffer path;
429 if (!path.Add(dir_name)) {
430 return false;
431 }
432 return DeleteRecursively(&path);
433 }
434}
435
436bool Directory::Rename(Namespace* namespc,
437 const char* path,
438 const char* new_path) {
439 ExistsResult exists = Exists(namespc, path);
440 if (exists != EXISTS) {
441 return false;
442 }
443 return (NO_RETRY_EXPECTED(rename(path, new_path)) == 0);
444}
445
446} // namespace bin
447} // namespace dart
448
449#endif // defined(HOST_OS_MACOS)
450