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