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/file.h" |
9 | |
10 | #include <errno.h> // NOLINT |
11 | #include <fcntl.h> // NOLINT |
12 | #include <libgen.h> // NOLINT |
13 | #include <sys/mman.h> // NOLINT |
14 | #include <sys/sendfile.h> // NOLINT |
15 | #include <sys/stat.h> // NOLINT |
16 | #include <sys/types.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 | |
27 | namespace dart { |
28 | namespace bin { |
29 | |
30 | class 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 | |
43 | File::~File() { |
44 | if (!IsClosed() && (handle_->fd() != STDOUT_FILENO) && |
45 | (handle_->fd() != STDERR_FILENO)) { |
46 | Close(); |
47 | } |
48 | delete handle_; |
49 | } |
50 | |
51 | void File::Close() { |
52 | ASSERT(handle_->fd() >= 0); |
53 | if (handle_->fd() == STDOUT_FILENO) { |
54 | // If stdout, redirect fd to /dev/null. |
55 | int 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 | int err = close(handle_->fd()); |
61 | if (err != 0) { |
62 | const int kBufferSize = 1024; |
63 | char error_buf[kBufferSize]; |
64 | Syslog::PrintErr("%s\n" , Utils::StrError(errno, error_buf, kBufferSize)); |
65 | } |
66 | } |
67 | handle_->set_fd(kClosedFd); |
68 | } |
69 | |
70 | intptr_t File::GetFD() { |
71 | return handle_->fd(); |
72 | } |
73 | |
74 | bool File::IsClosed() { |
75 | return handle_->fd() == kClosedFd; |
76 | } |
77 | |
78 | MappedMemory* File::Map(MapType type, |
79 | int64_t position, |
80 | int64_t length, |
81 | void* start) { |
82 | ASSERT(handle_->fd() >= 0); |
83 | ASSERT(length > 0); |
84 | int prot = PROT_NONE; |
85 | switch (type) { |
86 | case kReadOnly: |
87 | prot = PROT_READ; |
88 | break; |
89 | case kReadExecute: |
90 | prot = PROT_READ | PROT_EXEC; |
91 | break; |
92 | case kReadWrite: |
93 | prot = PROT_READ | PROT_WRITE; |
94 | break; |
95 | } |
96 | const int flags = MAP_PRIVATE | (start != nullptr ? MAP_FIXED : 0); |
97 | void* addr = mmap(start, length, prot, flags, handle_->fd(), position); |
98 | if (addr == MAP_FAILED) { |
99 | return NULL; |
100 | } |
101 | return new MappedMemory(addr, length, /*should_unmap=*/start == nullptr); |
102 | } |
103 | |
104 | void MappedMemory::Unmap() { |
105 | int result = munmap(address_, size_); |
106 | ASSERT(result == 0); |
107 | address_ = 0; |
108 | size_ = 0; |
109 | } |
110 | |
111 | int64_t File::Read(void* buffer, int64_t num_bytes) { |
112 | ASSERT(handle_->fd() >= 0); |
113 | return TEMP_FAILURE_RETRY(read(handle_->fd(), buffer, num_bytes)); |
114 | } |
115 | |
116 | int64_t File::Write(const void* buffer, int64_t num_bytes) { |
117 | ASSERT(handle_->fd() >= 0); |
118 | return TEMP_FAILURE_RETRY(write(handle_->fd(), buffer, num_bytes)); |
119 | } |
120 | |
121 | bool File::VPrint(const char* format, va_list args) { |
122 | // Measure. |
123 | va_list measure_args; |
124 | va_copy(measure_args, args); |
125 | intptr_t len = vsnprintf(NULL, 0, format, measure_args); |
126 | va_end(measure_args); |
127 | |
128 | char* buffer = reinterpret_cast<char*>(malloc(len + 1)); |
129 | |
130 | // Print. |
131 | va_list print_args; |
132 | va_copy(print_args, args); |
133 | vsnprintf(buffer, len + 1, format, print_args); |
134 | va_end(print_args); |
135 | |
136 | bool result = WriteFully(buffer, len); |
137 | free(buffer); |
138 | return result; |
139 | } |
140 | |
141 | int64_t File::Position() { |
142 | ASSERT(handle_->fd() >= 0); |
143 | return NO_RETRY_EXPECTED(lseek64(handle_->fd(), 0, SEEK_CUR)); |
144 | } |
145 | |
146 | bool File::SetPosition(int64_t position) { |
147 | ASSERT(handle_->fd() >= 0); |
148 | return NO_RETRY_EXPECTED(lseek64(handle_->fd(), position, SEEK_SET)) >= 0; |
149 | } |
150 | |
151 | bool File::Truncate(int64_t length) { |
152 | ASSERT(handle_->fd() >= 0); |
153 | return TEMP_FAILURE_RETRY(ftruncate64(handle_->fd(), length) != -1); |
154 | } |
155 | |
156 | bool File::Flush() { |
157 | ASSERT(handle_->fd() >= 0); |
158 | return NO_RETRY_EXPECTED(fsync(handle_->fd())) != -1; |
159 | } |
160 | |
161 | bool File::Lock(File::LockType lock, int64_t start, int64_t end) { |
162 | ASSERT(handle_->fd() >= 0); |
163 | ASSERT((end == -1) || (end > start)); |
164 | struct flock fl; |
165 | switch (lock) { |
166 | case File::kLockUnlock: |
167 | fl.l_type = F_UNLCK; |
168 | break; |
169 | case File::kLockShared: |
170 | case File::kLockBlockingShared: |
171 | fl.l_type = F_RDLCK; |
172 | break; |
173 | case File::kLockExclusive: |
174 | case File::kLockBlockingExclusive: |
175 | fl.l_type = F_WRLCK; |
176 | break; |
177 | default: |
178 | return false; |
179 | } |
180 | fl.l_whence = SEEK_SET; |
181 | fl.l_start = start; |
182 | fl.l_len = end == -1 ? 0 : end - start; |
183 | int cmd = F_SETLK; |
184 | if ((lock == File::kLockBlockingShared) || |
185 | (lock == File::kLockBlockingExclusive)) { |
186 | cmd = F_SETLKW; |
187 | } |
188 | return TEMP_FAILURE_RETRY(fcntl(handle_->fd(), cmd, &fl)) != -1; |
189 | } |
190 | |
191 | int64_t File::Length() { |
192 | ASSERT(handle_->fd() >= 0); |
193 | struct stat64 st; |
194 | if (TEMP_FAILURE_RETRY(fstat64(handle_->fd(), &st)) == 0) { |
195 | return st.st_size; |
196 | } |
197 | return -1; |
198 | } |
199 | |
200 | File* File::FileOpenW(const wchar_t* system_name, FileOpenMode mode) { |
201 | UNREACHABLE(); |
202 | return NULL; |
203 | } |
204 | |
205 | File* File::OpenFD(int fd) { |
206 | return new File(new FileHandle(fd)); |
207 | } |
208 | |
209 | File* File::Open(Namespace* namespc, const char* name, FileOpenMode mode) { |
210 | NamespaceScope ns(namespc, name); |
211 | // Report errors for non-regular files. |
212 | struct stat64 st; |
213 | if (TEMP_FAILURE_RETRY(fstatat64(ns.fd(), ns.path(), &st, 0)) == 0) { |
214 | // Only accept regular files, character devices, and pipes. |
215 | if (!S_ISREG(st.st_mode) && !S_ISCHR(st.st_mode) && !S_ISFIFO(st.st_mode)) { |
216 | errno = (S_ISDIR(st.st_mode)) ? EISDIR : ENOENT; |
217 | return NULL; |
218 | } |
219 | } |
220 | int flags = O_RDONLY; |
221 | if ((mode & kWrite) != 0) { |
222 | ASSERT((mode & kWriteOnly) == 0); |
223 | flags = (O_RDWR | O_CREAT); |
224 | } |
225 | if ((mode & kWriteOnly) != 0) { |
226 | ASSERT((mode & kWrite) == 0); |
227 | flags = (O_WRONLY | O_CREAT); |
228 | } |
229 | if ((mode & kTruncate) != 0) { |
230 | flags = flags | O_TRUNC; |
231 | } |
232 | flags |= O_CLOEXEC; |
233 | const int fd = TEMP_FAILURE_RETRY(openat64(ns.fd(), ns.path(), flags, 0666)); |
234 | if (fd < 0) { |
235 | return NULL; |
236 | } |
237 | if ((((mode & kWrite) != 0) && ((mode & kTruncate) == 0)) || |
238 | (((mode & kWriteOnly) != 0) && ((mode & kTruncate) == 0))) { |
239 | int64_t position = NO_RETRY_EXPECTED(lseek64(fd, 0, SEEK_END)); |
240 | if (position < 0) { |
241 | return NULL; |
242 | } |
243 | } |
244 | return OpenFD(fd); |
245 | } |
246 | |
247 | Utils::CStringUniquePtr File::UriToPath(const char* uri) { |
248 | const char* path = (strlen(uri) >= 8 && strncmp(uri, "file:///" , 8) == 0) |
249 | ? uri + 7 : uri; |
250 | UriDecoder uri_decoder(path); |
251 | if (uri_decoder.decoded() == nullptr) { |
252 | errno = EINVAL; |
253 | return Utils::CreateCStringUniquePtr(nullptr); |
254 | } |
255 | return Utils::CreateCStringUniquePtr(strdup(uri_decoder.decoded())); |
256 | } |
257 | |
258 | File* File::OpenUri(Namespace* namespc, const char* uri, FileOpenMode mode) { |
259 | auto path = UriToPath(uri); |
260 | if (path == nullptr) { |
261 | return nullptr; |
262 | } |
263 | return File::Open(namespc, path.get(), mode); |
264 | } |
265 | |
266 | File* File::OpenStdio(int fd) { |
267 | return new File(new FileHandle(fd)); |
268 | } |
269 | |
270 | bool File::Exists(Namespace* namespc, const char* name) { |
271 | NamespaceScope ns(namespc, name); |
272 | struct stat64 st; |
273 | if (TEMP_FAILURE_RETRY(fstatat64(ns.fd(), ns.path(), &st, 0)) == 0) { |
274 | // Everything but a directory and a link is a file to Dart. |
275 | return !S_ISDIR(st.st_mode) && !S_ISLNK(st.st_mode); |
276 | } else { |
277 | return false; |
278 | } |
279 | } |
280 | |
281 | bool File::ExistsUri(Namespace* namespc, const char* uri) { |
282 | auto path = UriToPath(uri); |
283 | if (path == nullptr) { |
284 | return false; |
285 | } |
286 | return File::Exists(namespc, path.get()); |
287 | } |
288 | |
289 | bool File::Create(Namespace* namespc, const char* name) { |
290 | NamespaceScope ns(namespc, name); |
291 | const int fd = TEMP_FAILURE_RETRY( |
292 | openat64(ns.fd(), ns.path(), O_RDONLY | O_CREAT | O_CLOEXEC, 0666)); |
293 | if (fd < 0) { |
294 | return false; |
295 | } |
296 | // File.create returns a File, so we shouldn't be giving the illusion that the |
297 | // call has created a file or that a file already exists if there is already |
298 | // an entity at the same path that is a directory or a link. |
299 | bool is_file = true; |
300 | struct stat64 st; |
301 | if (TEMP_FAILURE_RETRY(fstat64(fd, &st)) == 0) { |
302 | if (S_ISDIR(st.st_mode)) { |
303 | errno = EISDIR; |
304 | is_file = false; |
305 | } else if (S_ISLNK(st.st_mode)) { |
306 | errno = ENOENT; |
307 | is_file = false; |
308 | } |
309 | } |
310 | FDUtils::SaveErrorAndClose(fd); |
311 | return is_file; |
312 | } |
313 | |
314 | bool File::CreateLink(Namespace* namespc, |
315 | const char* name, |
316 | const char* target) { |
317 | NamespaceScope ns(namespc, name); |
318 | return NO_RETRY_EXPECTED(symlinkat(target, ns.fd(), ns.path())) == 0; |
319 | } |
320 | |
321 | File::Type File::GetType(Namespace* namespc, |
322 | const char* name, |
323 | bool follow_links) { |
324 | NamespaceScope ns(namespc, name); |
325 | struct stat64 entry_info; |
326 | int stat_success; |
327 | if (follow_links) { |
328 | stat_success = |
329 | TEMP_FAILURE_RETRY(fstatat64(ns.fd(), ns.path(), &entry_info, 0)); |
330 | } else { |
331 | stat_success = TEMP_FAILURE_RETRY( |
332 | fstatat64(ns.fd(), ns.path(), &entry_info, AT_SYMLINK_NOFOLLOW)); |
333 | } |
334 | if (stat_success == -1) { |
335 | return File::kDoesNotExist; |
336 | } |
337 | if (S_ISDIR(entry_info.st_mode)) { |
338 | return File::kIsDirectory; |
339 | } |
340 | if (S_ISREG(entry_info.st_mode)) { |
341 | return File::kIsFile; |
342 | } |
343 | if (S_ISLNK(entry_info.st_mode)) { |
344 | return File::kIsLink; |
345 | } |
346 | return File::kDoesNotExist; |
347 | } |
348 | |
349 | static bool CheckTypeAndSetErrno(Namespace* namespc, |
350 | const char* name, |
351 | File::Type expected, |
352 | bool follow_links) { |
353 | File::Type actual = File::GetType(namespc, name, follow_links); |
354 | if (actual == expected) { |
355 | return true; |
356 | } |
357 | switch (actual) { |
358 | case File::kIsDirectory: |
359 | errno = EISDIR; |
360 | break; |
361 | case File::kDoesNotExist: |
362 | errno = ENOENT; |
363 | break; |
364 | default: |
365 | errno = EINVAL; |
366 | break; |
367 | } |
368 | return false; |
369 | } |
370 | |
371 | bool File::Delete(Namespace* namespc, const char* name) { |
372 | NamespaceScope ns(namespc, name); |
373 | return CheckTypeAndSetErrno(namespc, name, kIsFile, true) && |
374 | (NO_RETRY_EXPECTED(unlinkat(ns.fd(), ns.path(), 0)) == 0); |
375 | } |
376 | |
377 | bool File::DeleteLink(Namespace* namespc, const char* name) { |
378 | NamespaceScope ns(namespc, name); |
379 | return CheckTypeAndSetErrno(namespc, name, kIsLink, false) && |
380 | (NO_RETRY_EXPECTED(unlinkat(ns.fd(), ns.path(), 0)) == 0); |
381 | } |
382 | |
383 | bool File::Rename(Namespace* namespc, |
384 | const char* old_path, |
385 | const char* new_path) { |
386 | NamespaceScope oldns(namespc, old_path); |
387 | NamespaceScope newns(namespc, new_path); |
388 | return CheckTypeAndSetErrno(namespc, old_path, kIsFile, true) && |
389 | (NO_RETRY_EXPECTED(renameat(oldns.fd(), oldns.path(), newns.fd(), |
390 | newns.path())) == 0); |
391 | } |
392 | |
393 | bool File::RenameLink(Namespace* namespc, |
394 | const char* old_path, |
395 | const char* new_path) { |
396 | NamespaceScope oldns(namespc, old_path); |
397 | NamespaceScope newns(namespc, new_path); |
398 | return CheckTypeAndSetErrno(namespc, old_path, kIsLink, false) && |
399 | (NO_RETRY_EXPECTED(renameat(oldns.fd(), oldns.path(), newns.fd(), |
400 | newns.path())) == 0); |
401 | } |
402 | |
403 | bool File::Copy(Namespace* namespc, |
404 | const char* old_path, |
405 | const char* new_path) { |
406 | if (!CheckTypeAndSetErrno(namespc, old_path, kIsFile, true)) { |
407 | return false; |
408 | } |
409 | NamespaceScope oldns(namespc, old_path); |
410 | struct stat64 st; |
411 | if (TEMP_FAILURE_RETRY(fstatat64(oldns.fd(), oldns.path(), &st, 0)) != 0) { |
412 | return false; |
413 | } |
414 | const int old_fd = TEMP_FAILURE_RETRY( |
415 | openat64(oldns.fd(), oldns.path(), O_RDONLY | O_CLOEXEC)); |
416 | if (old_fd < 0) { |
417 | return false; |
418 | } |
419 | NamespaceScope newns(namespc, new_path); |
420 | const int new_fd = TEMP_FAILURE_RETRY( |
421 | openat64(newns.fd(), newns.path(), |
422 | O_WRONLY | O_TRUNC | O_CREAT | O_CLOEXEC, st.st_mode)); |
423 | if (new_fd < 0) { |
424 | close(old_fd); |
425 | return false; |
426 | } |
427 | int64_t offset = 0; |
428 | intptr_t result = 1; |
429 | while (result > 0) { |
430 | // Loop to ensure we copy everything, and not only up to 2GB. |
431 | result = NO_RETRY_EXPECTED(sendfile64(new_fd, old_fd, &offset, kMaxUint32)); |
432 | } |
433 | // From sendfile man pages: |
434 | // Applications may wish to fall back to read(2)/write(2) in the case |
435 | // where sendfile() fails with EINVAL or ENOSYS. |
436 | if ((result < 0) && ((errno == EINVAL) || (errno == ENOSYS))) { |
437 | const intptr_t kBufferSize = 8 * KB; |
438 | uint8_t* buffer = reinterpret_cast<uint8_t*>(malloc(kBufferSize)); |
439 | while ((result = TEMP_FAILURE_RETRY(read(old_fd, buffer, kBufferSize))) > |
440 | 0) { |
441 | int wrote = TEMP_FAILURE_RETRY(write(new_fd, buffer, result)); |
442 | if (wrote != result) { |
443 | result = -1; |
444 | break; |
445 | } |
446 | } |
447 | free(buffer); |
448 | } |
449 | int e = errno; |
450 | close(old_fd); |
451 | close(new_fd); |
452 | if (result < 0) { |
453 | VOID_NO_RETRY_EXPECTED(unlinkat(newns.fd(), newns.path(), 0)); |
454 | errno = e; |
455 | return false; |
456 | } |
457 | return true; |
458 | } |
459 | |
460 | static bool StatHelper(Namespace* namespc, |
461 | const char* name, |
462 | struct stat64* st) { |
463 | NamespaceScope ns(namespc, name); |
464 | if (TEMP_FAILURE_RETRY(fstatat64(ns.fd(), ns.path(), st, 0)) != 0) { |
465 | return false; |
466 | } |
467 | // Signal an error if it's a directory. |
468 | if (S_ISDIR(st->st_mode)) { |
469 | errno = EISDIR; |
470 | return false; |
471 | } |
472 | // Otherwise assume the caller knows what it's doing. |
473 | return true; |
474 | } |
475 | |
476 | int64_t File::LengthFromPath(Namespace* namespc, const char* name) { |
477 | struct stat64 st; |
478 | if (!StatHelper(namespc, name, &st)) { |
479 | return -1; |
480 | } |
481 | return st.st_size; |
482 | } |
483 | |
484 | static int64_t TimespecToMilliseconds(const struct timespec& t) { |
485 | return static_cast<int64_t>(t.tv_sec) * 1000L + |
486 | static_cast<int64_t>(t.tv_nsec) / 1000000L; |
487 | } |
488 | |
489 | static void MillisecondsToTimespec(int64_t millis, struct timespec* t) { |
490 | ASSERT(t != NULL); |
491 | t->tv_sec = millis / kMillisecondsPerSecond; |
492 | t->tv_nsec = (millis % kMillisecondsPerSecond) * 1000L; |
493 | } |
494 | |
495 | void File::Stat(Namespace* namespc, const char* name, int64_t* data) { |
496 | NamespaceScope ns(namespc, name); |
497 | struct stat64 st; |
498 | if (TEMP_FAILURE_RETRY(fstatat64(ns.fd(), ns.path(), &st, 0)) == 0) { |
499 | if (S_ISREG(st.st_mode)) { |
500 | data[kType] = kIsFile; |
501 | } else if (S_ISDIR(st.st_mode)) { |
502 | data[kType] = kIsDirectory; |
503 | } else if (S_ISLNK(st.st_mode)) { |
504 | data[kType] = kIsLink; |
505 | } else { |
506 | data[kType] = kDoesNotExist; |
507 | } |
508 | data[kCreatedTime] = TimespecToMilliseconds(st.st_ctim); |
509 | data[kModifiedTime] = TimespecToMilliseconds(st.st_mtim); |
510 | data[kAccessedTime] = TimespecToMilliseconds(st.st_atim); |
511 | data[kMode] = st.st_mode; |
512 | data[kSize] = st.st_size; |
513 | } else { |
514 | data[kType] = kDoesNotExist; |
515 | } |
516 | } |
517 | |
518 | time_t File::LastModified(Namespace* namespc, const char* name) { |
519 | struct stat64 st; |
520 | if (!StatHelper(namespc, name, &st)) { |
521 | return -1; |
522 | } |
523 | return st.st_mtime; |
524 | } |
525 | |
526 | time_t File::LastAccessed(Namespace* namespc, const char* name) { |
527 | struct stat64 st; |
528 | if (!StatHelper(namespc, name, &st)) { |
529 | return -1; |
530 | } |
531 | return st.st_atime; |
532 | } |
533 | |
534 | bool File::SetLastAccessed(Namespace* namespc, |
535 | const char* name, |
536 | int64_t millis) { |
537 | // First get the current times. |
538 | struct stat64 st; |
539 | if (!StatHelper(namespc, name, &st)) { |
540 | return false; |
541 | } |
542 | |
543 | // Set the new time: |
544 | NamespaceScope ns(namespc, name); |
545 | struct timespec times[2]; |
546 | MillisecondsToTimespec(millis, ×[0]); |
547 | times[1] = st.st_mtim; |
548 | return utimensat(ns.fd(), ns.path(), times, 0) == 0; |
549 | } |
550 | |
551 | bool File::SetLastModified(Namespace* namespc, |
552 | const char* name, |
553 | int64_t millis) { |
554 | // First get the current times. |
555 | struct stat64 st; |
556 | if (!StatHelper(namespc, name, &st)) { |
557 | return false; |
558 | } |
559 | |
560 | // Set the new time: |
561 | NamespaceScope ns(namespc, name); |
562 | struct timespec times[2]; |
563 | times[0] = st.st_atim; |
564 | MillisecondsToTimespec(millis, ×[1]); |
565 | return utimensat(ns.fd(), ns.path(), times, 0) == 0; |
566 | } |
567 | |
568 | const char* File::LinkTarget(Namespace* namespc, |
569 | const char* name, |
570 | char* dest, |
571 | int dest_size) { |
572 | NamespaceScope ns(namespc, name); |
573 | struct stat64 link_stats; |
574 | const int status = TEMP_FAILURE_RETRY( |
575 | fstatat64(ns.fd(), ns.path(), &link_stats, AT_SYMLINK_NOFOLLOW)); |
576 | if (status != 0) { |
577 | return NULL; |
578 | } |
579 | if (!S_ISLNK(link_stats.st_mode)) { |
580 | errno = ENOENT; |
581 | return NULL; |
582 | } |
583 | // Don't rely on the link_stats.st_size for the size of the link |
584 | // target. For some filesystems, e.g. procfs, this value is always |
585 | // 0. Also the link might have changed before the readlink call. |
586 | const int kBufferSize = PATH_MAX + 1; |
587 | char target[kBufferSize]; |
588 | const int target_size = |
589 | TEMP_FAILURE_RETRY(readlinkat(ns.fd(), ns.path(), target, kBufferSize)); |
590 | if (target_size <= 0) { |
591 | return NULL; |
592 | } |
593 | if (dest == NULL) { |
594 | dest = DartUtils::ScopedCString(target_size + 1); |
595 | } else { |
596 | ASSERT(dest_size > 0); |
597 | if (dest_size <= target_size) { |
598 | return NULL; |
599 | } |
600 | } |
601 | memmove(dest, target, target_size); |
602 | dest[target_size] = '\0'; |
603 | return dest; |
604 | } |
605 | |
606 | bool File::IsAbsolutePath(const char* pathname) { |
607 | return (pathname != NULL) && (pathname[0] == '/'); |
608 | } |
609 | |
610 | intptr_t File::ReadLinkInto(const char* pathname, |
611 | char* result, |
612 | size_t result_size) { |
613 | ASSERT(pathname != NULL); |
614 | ASSERT(IsAbsolutePath(pathname)); |
615 | struct stat64 link_stats; |
616 | if (TEMP_FAILURE_RETRY(lstat64(pathname, &link_stats)) != 0) { |
617 | return -1; |
618 | } |
619 | if (!S_ISLNK(link_stats.st_mode)) { |
620 | errno = ENOENT; |
621 | return -1; |
622 | } |
623 | size_t target_size = |
624 | TEMP_FAILURE_RETRY(readlink(pathname, result, result_size)); |
625 | if (target_size <= 0) { |
626 | return -1; |
627 | } |
628 | // readlink returns non-zero terminated strings. Append. |
629 | if (target_size < result_size) { |
630 | result[target_size] = '\0'; |
631 | target_size++; |
632 | } |
633 | return target_size; |
634 | } |
635 | |
636 | const char* File::ReadLink(const char* pathname) { |
637 | // Don't rely on the link_stats.st_size for the size of the link |
638 | // target. For some filesystems, e.g. procfs, this value is always |
639 | // 0. Also the link might have changed before the readlink call. |
640 | const int kBufferSize = PATH_MAX + 1; |
641 | char target[kBufferSize]; |
642 | size_t target_size = ReadLinkInto(pathname, target, kBufferSize); |
643 | if (target_size <= 0) { |
644 | return NULL; |
645 | } |
646 | char* target_name = DartUtils::ScopedCString(target_size); |
647 | ASSERT(target_name != NULL); |
648 | memmove(target_name, target, target_size); |
649 | return target_name; |
650 | } |
651 | |
652 | const char* File::GetCanonicalPath(Namespace* namespc, |
653 | const char* name, |
654 | char* dest, |
655 | int dest_size) { |
656 | if (name == NULL) { |
657 | return NULL; |
658 | } |
659 | if (!Namespace::IsDefault(namespc)) { |
660 | // TODO(zra): There is no realpathat(). Also chasing a symlink might result |
661 | // in a path to something outside of the namespace, so canonicalizing paths |
662 | // would have to be done carefully. For now, don't do anything. |
663 | return name; |
664 | } |
665 | char* abs_path; |
666 | if (dest == NULL) { |
667 | dest = DartUtils::ScopedCString(PATH_MAX + 1); |
668 | } else { |
669 | ASSERT(dest_size >= PATH_MAX); |
670 | } |
671 | ASSERT(dest != NULL); |
672 | do { |
673 | abs_path = realpath(name, dest); |
674 | } while ((abs_path == NULL) && (errno == EINTR)); |
675 | ASSERT(abs_path == NULL || IsAbsolutePath(abs_path)); |
676 | ASSERT(abs_path == NULL || (abs_path == dest)); |
677 | return abs_path; |
678 | } |
679 | |
680 | const char* File::PathSeparator() { |
681 | return "/" ; |
682 | } |
683 | |
684 | const char* File::StringEscapedPathSeparator() { |
685 | return "/" ; |
686 | } |
687 | |
688 | File::StdioHandleType File::GetStdioHandleType(int fd) { |
689 | struct stat64 buf; |
690 | int result = TEMP_FAILURE_RETRY(fstat64(fd, &buf)); |
691 | if (result == -1) { |
692 | return kTypeError; |
693 | } |
694 | if (S_ISCHR(buf.st_mode)) { |
695 | return kTerminal; |
696 | } |
697 | if (S_ISFIFO(buf.st_mode)) { |
698 | return kPipe; |
699 | } |
700 | if (S_ISSOCK(buf.st_mode)) { |
701 | return kSocket; |
702 | } |
703 | if (S_ISREG(buf.st_mode)) { |
704 | return kFile; |
705 | } |
706 | return kOther; |
707 | } |
708 | |
709 | File::Identical File::AreIdentical(Namespace* namespc_1, |
710 | const char* file_1, |
711 | Namespace* namespc_2, |
712 | const char* file_2) { |
713 | struct stat64 file_1_info; |
714 | struct stat64 file_2_info; |
715 | int status; |
716 | { |
717 | NamespaceScope ns1(namespc_1, file_1); |
718 | status = TEMP_FAILURE_RETRY( |
719 | fstatat64(ns1.fd(), ns1.path(), &file_1_info, AT_SYMLINK_NOFOLLOW)); |
720 | if (status == -1) { |
721 | return File::kError; |
722 | } |
723 | } |
724 | { |
725 | NamespaceScope ns2(namespc_2, file_2); |
726 | status = TEMP_FAILURE_RETRY( |
727 | fstatat64(ns2.fd(), ns2.path(), &file_2_info, AT_SYMLINK_NOFOLLOW)); |
728 | if (status == -1) { |
729 | return File::kError; |
730 | } |
731 | } |
732 | return ((file_1_info.st_ino == file_2_info.st_ino) && |
733 | (file_1_info.st_dev == file_2_info.st_dev)) |
734 | ? File::kIdentical |
735 | : File::kDifferent; |
736 | } |
737 | |
738 | } // namespace bin |
739 | } // namespace dart |
740 | |
741 | #endif // defined(HOST_OS_LINUX) |
742 | |