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/file.h"
9
10#include <copyfile.h> // NOLINT
11#include <errno.h> // NOLINT
12#include <fcntl.h> // NOLINT
13#include <libgen.h> // NOLINT
14#include <limits.h> // NOLINT
15#include <sys/mman.h> // NOLINT
16#include <sys/stat.h> // NOLINT
17#include <unistd.h> // NOLINT
18#include <utime.h> // NOLINT
19
20#include "bin/builtin.h"
21#include "bin/fdutils.h"
22#include "bin/namespace.h"
23#include "platform/signal_blocker.h"
24#include "platform/syslog.h"
25#include "platform/utils.h"
26
27namespace dart {
28namespace bin {
29
30class FileHandle {
31 public:
32 explicit FileHandle(int fd) : fd_(fd) {}
33 ~FileHandle() {}
34 int fd() const { return fd_; }
35 void set_fd(int fd) { fd_ = fd; }
36
37 private:
38 int fd_;
39
40 DISALLOW_COPY_AND_ASSIGN(FileHandle);
41};
42
43File::~File() {
44 if (!IsClosed() && handle_->fd() != STDOUT_FILENO &&
45 handle_->fd() != STDERR_FILENO) {
46 Close();
47 }
48 delete handle_;
49}
50
51void File::Close() {
52 ASSERT(handle_->fd() >= 0);
53 if (handle_->fd() == STDOUT_FILENO) {
54 // If stdout, redirect fd to /dev/null.
55 intptr_t null_fd = TEMP_FAILURE_RETRY(open("/dev/null", O_WRONLY));
56 ASSERT(null_fd >= 0);
57 VOID_TEMP_FAILURE_RETRY(dup2(null_fd, handle_->fd()));
58 close(null_fd);
59 } else {
60 intptr_t err = close(handle_->fd());
61 if (err != 0) {
62 const int kBufferSize = 1024;
63 char error_message[kBufferSize];
64 Utils::StrError(errno, error_message, kBufferSize);
65 Syslog::PrintErr("%s\n", error_message);
66 }
67 }
68 handle_->set_fd(kClosedFd);
69}
70
71intptr_t File::GetFD() {
72 return handle_->fd();
73}
74
75bool File::IsClosed() {
76 return handle_->fd() == kClosedFd;
77}
78
79MappedMemory* File::Map(MapType type,
80 int64_t position,
81 int64_t length,
82 void* start) {
83 ASSERT(handle_->fd() >= 0);
84 ASSERT(length > 0);
85 int prot = PROT_NONE;
86 int map_flags = MAP_PRIVATE;
87 switch (type) {
88 case kReadOnly:
89 prot = PROT_READ;
90 break;
91 case kReadExecute:
92 prot = PROT_READ | PROT_EXEC;
93 if (IsAtLeastOS10_14()) {
94 map_flags |= (MAP_JIT | MAP_ANONYMOUS);
95 }
96 break;
97 case kReadWrite:
98 prot = PROT_READ | PROT_WRITE;
99 break;
100 }
101 if (start != nullptr) {
102 map_flags |= MAP_FIXED;
103 }
104 void* addr = start;
105 if ((type == kReadExecute) && IsAtLeastOS10_14()) {
106 // Due to codesigning restrictions, we cannot map the file as executable
107 // directly. We must first copy it into an anonymous mapping and then mark
108 // the mapping as executable.
109 if (addr == nullptr) {
110 addr = mmap(nullptr, length, (PROT_READ | PROT_WRITE), map_flags, -1, 0);
111 if (addr == MAP_FAILED) {
112 Syslog::PrintErr("mmap failed %s\n", strerror(errno));
113 return nullptr;
114 }
115 }
116
117 const int64_t remaining_length = Length() - position;
118 SetPosition(position);
119 if (!ReadFully(addr, Utils::Minimum(length, remaining_length))) {
120 Syslog::PrintErr("ReadFully failed\n");
121 if (start == nullptr) {
122 munmap(addr, length);
123 }
124 return nullptr;
125 }
126
127 // If the requested mapping is larger than the file size, we should fill the
128 // extra memory with zeros.
129 if (length > remaining_length) {
130 memset(reinterpret_cast<uint8_t*>(addr) + remaining_length, 0,
131 length - remaining_length);
132 }
133
134 if (mprotect(addr, length, prot) != 0) {
135 Syslog::PrintErr("mprotect failed %s\n", strerror(errno));
136 if (start == nullptr) {
137 munmap(addr, length);
138 }
139 return nullptr;
140 }
141 } else {
142 addr = mmap(addr, length, prot, map_flags, handle_->fd(), position);
143 if (addr == MAP_FAILED) {
144 Syslog::PrintErr("mmap failed %s\n", strerror(errno));
145 return nullptr;
146 }
147 }
148 return new MappedMemory(addr, length, /*should_unmap=*/start == nullptr);
149}
150
151void MappedMemory::Unmap() {
152 int result = munmap(address_, size_);
153 ASSERT(result == 0);
154 address_ = 0;
155 size_ = 0;
156}
157
158int64_t File::Read(void* buffer, int64_t num_bytes) {
159 ASSERT(handle_->fd() >= 0);
160 return TEMP_FAILURE_RETRY(read(handle_->fd(), buffer, num_bytes));
161}
162
163int64_t File::Write(const void* buffer, int64_t num_bytes) {
164 // Invalid argument error will pop if num_bytes exceeds the limit.
165 ASSERT(handle_->fd() >= 0 && num_bytes <= kMaxInt32);
166 return TEMP_FAILURE_RETRY(write(handle_->fd(), buffer, num_bytes));
167}
168
169bool File::VPrint(const char* format, va_list args) {
170 // Measure.
171 va_list measure_args;
172 va_copy(measure_args, args);
173 intptr_t len = vsnprintf(NULL, 0, format, measure_args);
174 va_end(measure_args);
175
176 char* buffer = reinterpret_cast<char*>(malloc(len + 1));
177
178 // Print.
179 va_list print_args;
180 va_copy(print_args, args);
181 vsnprintf(buffer, len + 1, format, print_args);
182 va_end(print_args);
183
184 bool result = WriteFully(buffer, len);
185 free(buffer);
186 return result;
187}
188
189int64_t File::Position() {
190 ASSERT(handle_->fd() >= 0);
191 return lseek(handle_->fd(), 0, SEEK_CUR);
192}
193
194bool File::SetPosition(int64_t position) {
195 ASSERT(handle_->fd() >= 0);
196 return lseek(handle_->fd(), position, SEEK_SET) >= 0;
197}
198
199bool File::Truncate(int64_t length) {
200 ASSERT(handle_->fd() >= 0);
201 return TEMP_FAILURE_RETRY(ftruncate(handle_->fd(), length)) != -1;
202}
203
204bool File::Flush() {
205 ASSERT(handle_->fd() >= 0);
206 return NO_RETRY_EXPECTED(fsync(handle_->fd())) != -1;
207}
208
209bool File::Lock(File::LockType lock, int64_t start, int64_t end) {
210 ASSERT(handle_->fd() >= 0);
211 ASSERT((end == -1) || (end > start));
212 struct flock fl;
213 switch (lock) {
214 case File::kLockUnlock:
215 fl.l_type = F_UNLCK;
216 break;
217 case File::kLockShared:
218 case File::kLockBlockingShared:
219 fl.l_type = F_RDLCK;
220 break;
221 case File::kLockExclusive:
222 case File::kLockBlockingExclusive:
223 fl.l_type = F_WRLCK;
224 break;
225 default:
226 return false;
227 }
228 fl.l_whence = SEEK_SET;
229 fl.l_start = start;
230 fl.l_len = end == -1 ? 0 : end - start;
231 int cmd = F_SETLK;
232 if ((lock == File::kLockBlockingShared) ||
233 (lock == File::kLockBlockingExclusive)) {
234 cmd = F_SETLKW;
235 }
236 return TEMP_FAILURE_RETRY(fcntl(handle_->fd(), cmd, &fl)) != -1;
237}
238
239int64_t File::Length() {
240 ASSERT(handle_->fd() >= 0);
241 struct stat st;
242 if (NO_RETRY_EXPECTED(fstat(handle_->fd(), &st)) == 0) {
243 return st.st_size;
244 }
245 return -1;
246}
247
248File* File::FileOpenW(const wchar_t* system_name, FileOpenMode mode) {
249 UNREACHABLE();
250 return NULL;
251}
252
253File* File::Open(Namespace* namespc, const char* name, FileOpenMode mode) {
254 // Report errors for non-regular files.
255 struct stat st;
256 if (NO_RETRY_EXPECTED(stat(name, &st)) == 0) {
257 // Only accept regular files, character devices, and pipes.
258 if (!S_ISREG(st.st_mode) && !S_ISCHR(st.st_mode) && !S_ISFIFO(st.st_mode)) {
259 errno = (S_ISDIR(st.st_mode)) ? EISDIR : ENOENT;
260 return NULL;
261 }
262 }
263 int flags = O_RDONLY;
264 if ((mode & kWrite) != 0) {
265 ASSERT((mode & kWriteOnly) == 0);
266 flags = (O_RDWR | O_CREAT);
267 }
268 if ((mode & kWriteOnly) != 0) {
269 ASSERT((mode & kWrite) == 0);
270 flags = (O_WRONLY | O_CREAT);
271 }
272 if ((mode & kTruncate) != 0) {
273 flags = flags | O_TRUNC;
274 }
275 int fd = TEMP_FAILURE_RETRY(open(name, flags, 0666));
276 if (fd < 0) {
277 return NULL;
278 }
279 FDUtils::SetCloseOnExec(fd);
280 if ((((mode & kWrite) != 0) && ((mode & kTruncate) == 0)) ||
281 (((mode & kWriteOnly) != 0) && ((mode & kTruncate) == 0))) {
282 int64_t position = lseek(fd, 0, SEEK_END);
283 if (position < 0) {
284 return NULL;
285 }
286 }
287 return new File(new FileHandle(fd));
288}
289
290Utils::CStringUniquePtr File::UriToPath(const char* uri) {
291 const char* path = (strlen(uri) >= 8 && strncmp(uri, "file:///", 8) == 0)
292 ? uri + 7 : uri;
293 UriDecoder uri_decoder(path);
294 if (uri_decoder.decoded() == nullptr) {
295 errno = EINVAL;
296 return Utils::CreateCStringUniquePtr(nullptr);
297 }
298 return Utils::CreateCStringUniquePtr(strdup(uri_decoder.decoded()));
299}
300
301File* File::OpenUri(Namespace* namespc, const char* uri, FileOpenMode mode) {
302 auto path = UriToPath(uri);
303 if (path == nullptr) {
304 return nullptr;
305 }
306 return File::Open(namespc, path.get(), mode);
307}
308
309File* File::OpenStdio(int fd) {
310 return new File(new FileHandle(fd));
311}
312
313bool File::Exists(Namespace* namespc, const char* name) {
314 struct stat st;
315 if (NO_RETRY_EXPECTED(stat(name, &st)) == 0) {
316 // Everything but a directory and a link is a file to Dart.
317 return !S_ISDIR(st.st_mode) && !S_ISLNK(st.st_mode);
318 } else {
319 return false;
320 }
321}
322
323bool File::ExistsUri(Namespace* namespc, const char* uri) {
324 auto path = UriToPath(uri);
325 if (path == nullptr) {
326 return false;
327 }
328 return File::Exists(namespc, path.get());
329}
330
331bool File::Create(Namespace* namespc, const char* name) {
332 int fd = TEMP_FAILURE_RETRY(open(name, O_RDONLY | O_CREAT, 0666));
333 if (fd < 0) {
334 return false;
335 }
336 // File.create returns a File, so we shouldn't be giving the illusion that the
337 // call has created a file or that a file already exists if there is already
338 // an entity at the same path that is a directory or a link.
339 bool is_file = true;
340 struct stat st;
341 if (NO_RETRY_EXPECTED(fstat(fd, &st)) == 0) {
342 if (S_ISDIR(st.st_mode)) {
343 errno = EISDIR;
344 is_file = false;
345 } else if (S_ISLNK(st.st_mode)) {
346 errno = ENOENT;
347 is_file = false;
348 }
349 }
350 FDUtils::SaveErrorAndClose(fd);
351 return is_file;
352}
353
354bool File::CreateLink(Namespace* namespc,
355 const char* name,
356 const char* target) {
357 int status = NO_RETRY_EXPECTED(symlink(target, name));
358 return (status == 0);
359}
360
361File::Type File::GetType(Namespace* namespc,
362 const char* pathname,
363 bool follow_links) {
364 struct stat entry_info;
365 int stat_success;
366 if (follow_links) {
367 stat_success = NO_RETRY_EXPECTED(stat(pathname, &entry_info));
368 } else {
369 stat_success = NO_RETRY_EXPECTED(lstat(pathname, &entry_info));
370 }
371 if (stat_success == -1) {
372 return File::kDoesNotExist;
373 }
374 if (S_ISDIR(entry_info.st_mode)) {
375 return File::kIsDirectory;
376 }
377 if (S_ISREG(entry_info.st_mode)) {
378 return File::kIsFile;
379 }
380 if (S_ISLNK(entry_info.st_mode)) {
381 return File::kIsLink;
382 }
383 return File::kDoesNotExist;
384}
385
386static bool CheckTypeAndSetErrno(Namespace* namespc,
387 const char* name,
388 File::Type expected,
389 bool follow_links) {
390 File::Type actual = File::GetType(namespc, name, follow_links);
391 if (actual == expected) {
392 return true;
393 }
394 switch (actual) {
395 case File::kIsDirectory:
396 errno = EISDIR;
397 break;
398 case File::kDoesNotExist:
399 errno = ENOENT;
400 break;
401 default:
402 errno = EINVAL;
403 break;
404 }
405 return false;
406}
407
408bool File::Delete(Namespace* namespc, const char* name) {
409 return CheckTypeAndSetErrno(namespc, name, kIsFile, true) &&
410 (NO_RETRY_EXPECTED(unlink(name)) == 0);
411}
412
413bool File::DeleteLink(Namespace* namespc, const char* name) {
414 return CheckTypeAndSetErrno(namespc, name, kIsLink, false) &&
415 (NO_RETRY_EXPECTED(unlink(name)) == 0);
416}
417
418bool File::Rename(Namespace* namespc,
419 const char* old_path,
420 const char* new_path) {
421 return CheckTypeAndSetErrno(namespc, old_path, kIsFile, true) &&
422 (NO_RETRY_EXPECTED(rename(old_path, new_path)) == 0);
423}
424
425bool File::RenameLink(Namespace* namespc,
426 const char* old_path,
427 const char* new_path) {
428 return CheckTypeAndSetErrno(namespc, old_path, kIsLink, false) &&
429 (NO_RETRY_EXPECTED(rename(old_path, new_path)) == 0);
430}
431
432bool File::Copy(Namespace* namespc,
433 const char* old_path,
434 const char* new_path) {
435 return CheckTypeAndSetErrno(namespc, old_path, kIsFile, true) &&
436 (copyfile(old_path, new_path, NULL, COPYFILE_ALL) == 0);
437}
438
439static bool StatHelper(Namespace* namespc, const char* name, struct stat* st) {
440 if (NO_RETRY_EXPECTED(stat(name, st)) != 0) {
441 return false;
442 }
443 // Signal an error if it's a directory.
444 if (S_ISDIR(st->st_mode)) {
445 errno = EISDIR;
446 return false;
447 }
448 // Otherwise assume the caller knows what it's doing.
449 return true;
450}
451
452int64_t File::LengthFromPath(Namespace* namespc, const char* name) {
453 struct stat st;
454 if (!StatHelper(namespc, name, &st)) {
455 return -1;
456 }
457 return st.st_size;
458}
459
460static int64_t TimespecToMilliseconds(const struct timespec& t) {
461 return static_cast<int64_t>(t.tv_sec) * 1000L +
462 static_cast<int64_t>(t.tv_nsec) / 1000000L;
463}
464
465void File::Stat(Namespace* namespc, const char* name, int64_t* data) {
466 struct stat st;
467 if (NO_RETRY_EXPECTED(stat(name, &st)) == 0) {
468 if (S_ISREG(st.st_mode)) {
469 data[kType] = kIsFile;
470 } else if (S_ISDIR(st.st_mode)) {
471 data[kType] = kIsDirectory;
472 } else if (S_ISLNK(st.st_mode)) {
473 data[kType] = kIsLink;
474 } else {
475 data[kType] = kDoesNotExist;
476 }
477 data[kCreatedTime] = st.st_ctime;
478 data[kModifiedTime] = st.st_mtime;
479 data[kAccessedTime] = st.st_atime;
480 data[kCreatedTime] = TimespecToMilliseconds(st.st_ctimespec);
481 data[kModifiedTime] = TimespecToMilliseconds(st.st_mtimespec);
482 data[kAccessedTime] = TimespecToMilliseconds(st.st_atimespec);
483 data[kMode] = st.st_mode;
484 data[kSize] = st.st_size;
485 } else {
486 data[kType] = kDoesNotExist;
487 }
488}
489
490time_t File::LastModified(Namespace* namespc, const char* name) {
491 struct stat st;
492 if (!StatHelper(namespc, name, &st)) {
493 return -1;
494 }
495 return st.st_mtime;
496}
497
498time_t File::LastAccessed(Namespace* namespc, const char* name) {
499 struct stat st;
500 if (!StatHelper(namespc, name, &st)) {
501 return -1;
502 }
503 return st.st_atime;
504}
505
506bool File::SetLastAccessed(Namespace* namespc,
507 const char* name,
508 int64_t millis) {
509 // First get the current times.
510 struct stat st;
511 if (!StatHelper(namespc, name, &st)) {
512 return false;
513 }
514
515 // Set the new time:
516 struct utimbuf times;
517 times.actime = millis / kMillisecondsPerSecond;
518 times.modtime = st.st_mtime;
519 return utime(name, &times) == 0;
520}
521
522bool File::SetLastModified(Namespace* namespc,
523 const char* name,
524 int64_t millis) {
525 // First get the current times.
526 struct stat st;
527 if (!StatHelper(namespc, name, &st)) {
528 return false;
529 }
530
531 // Set the new time:
532 struct utimbuf times;
533 times.actime = st.st_atime;
534 times.modtime = millis / kMillisecondsPerSecond;
535 return utime(name, &times) == 0;
536}
537
538const char* File::LinkTarget(Namespace* namespc,
539 const char* pathname,
540 char* dest,
541 int dest_size) {
542 struct stat link_stats;
543 if (lstat(pathname, &link_stats) != 0) {
544 return NULL;
545 }
546 if (!S_ISLNK(link_stats.st_mode)) {
547 errno = ENOENT;
548 return NULL;
549 }
550 // Don't rely on the link_stats.st_size for the size of the link
551 // target. The link might have changed before the readlink call.
552 const int kBufferSize = 1024;
553 char target[kBufferSize];
554 size_t target_size =
555 TEMP_FAILURE_RETRY(readlink(pathname, target, kBufferSize));
556 if (target_size <= 0) {
557 return NULL;
558 }
559 if (dest == NULL) {
560 dest = DartUtils::ScopedCString(target_size + 1);
561 } else {
562 ASSERT(dest_size > 0);
563 if ((size_t)dest_size <= target_size) {
564 return NULL;
565 }
566 }
567 memmove(dest, target, target_size);
568 dest[target_size] = '\0';
569 return dest;
570}
571
572bool File::IsAbsolutePath(const char* pathname) {
573 return (pathname != NULL && pathname[0] == '/');
574}
575
576const char* File::GetCanonicalPath(Namespace* namespc,
577 const char* pathname,
578 char* dest,
579 int dest_size) {
580 char* abs_path = NULL;
581 if (pathname != NULL) {
582 // On some older MacOs versions the default behaviour of realpath allocating
583 // space for the dest when a NULL is passed in does not seem to work, so we
584 // explicitly allocate space.
585 if (dest == NULL) {
586 dest = DartUtils::ScopedCString(PATH_MAX + 1);
587 } else {
588 ASSERT(dest_size >= PATH_MAX);
589 }
590 do {
591 abs_path = realpath(pathname, dest);
592 } while ((abs_path == NULL) && (errno == EINTR));
593 ASSERT((abs_path == NULL) || IsAbsolutePath(abs_path));
594 ASSERT((abs_path == NULL) || (abs_path == dest));
595 }
596 return abs_path;
597}
598
599const char* File::PathSeparator() {
600 return "/";
601}
602
603const char* File::StringEscapedPathSeparator() {
604 return "/";
605}
606
607File::StdioHandleType File::GetStdioHandleType(int fd) {
608 struct stat buf;
609 int result = fstat(fd, &buf);
610 if (result == -1) {
611 return kTypeError;
612 }
613 if (S_ISCHR(buf.st_mode)) {
614 return kTerminal;
615 }
616 if (S_ISFIFO(buf.st_mode)) {
617 return kPipe;
618 }
619 if (S_ISSOCK(buf.st_mode)) {
620 return kSocket;
621 }
622 if (S_ISREG(buf.st_mode)) {
623 return kFile;
624 }
625 return kOther;
626}
627
628File::Identical File::AreIdentical(Namespace* namespc_1,
629 const char* file_1,
630 Namespace* namespc_2,
631 const char* file_2) {
632 USE(namespc_1);
633 USE(namespc_2);
634 struct stat file_1_info;
635 struct stat file_2_info;
636 if ((NO_RETRY_EXPECTED(lstat(file_1, &file_1_info)) == -1) ||
637 (NO_RETRY_EXPECTED(lstat(file_2, &file_2_info)) == -1)) {
638 return File::kError;
639 }
640 return ((file_1_info.st_ino == file_2_info.st_ino) &&
641 (file_1_info.st_dev == file_2_info.st_dev))
642 ? File::kIdentical
643 : File::kDifferent;
644}
645
646} // namespace bin
647} // namespace dart
648
649#endif // defined(HOST_OS_MACOS)
650