| // Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file |
| // for details. All rights reserved. Use of this source code is governed by a |
| // BSD-style license that can be found in the LICENSE file. |
| |
| #include "platform/globals.h" |
| #if defined(DART_HOST_OS_MACOS) |
| |
| #include "bin/directory.h" |
| |
| #include <dirent.h> // NOLINT |
| #include <errno.h> // NOLINT |
| #include <string.h> // NOLINT |
| #include <sys/param.h> // NOLINT |
| #include <sys/stat.h> // NOLINT |
| #include <unistd.h> // NOLINT |
| |
| #include "bin/dartutils.h" |
| #include "bin/file.h" |
| #include "bin/namespace.h" |
| #include "bin/platform.h" |
| #include "platform/signal_blocker.h" |
| |
| namespace dart { |
| namespace bin { |
| |
| PathBuffer::PathBuffer() : length_(0) { |
| data_ = calloc(PATH_MAX + 1, sizeof(char)); // NOLINT |
| } |
| |
| PathBuffer::~PathBuffer() { |
| free(data_); |
| } |
| |
| bool PathBuffer::AddW(const wchar_t* name) { |
| UNREACHABLE(); |
| return false; |
| } |
| |
| char* PathBuffer::AsString() const { |
| return reinterpret_cast<char*>(data_); |
| } |
| |
| wchar_t* PathBuffer::AsStringW() const { |
| UNREACHABLE(); |
| return nullptr; |
| } |
| |
| const char* PathBuffer::AsScopedString() const { |
| return DartUtils::ScopedCopyCString(AsString()); |
| } |
| |
| bool PathBuffer::Add(const char* name) { |
| char* data = AsString(); |
| int written = snprintf(data + length_, PATH_MAX - length_, "%s", name); |
| data[PATH_MAX] = '\0'; |
| if ((written <= PATH_MAX - length_) && (written >= 0) && |
| (static_cast<size_t>(written) == strlen(name))) { |
| length_ += written; |
| return true; |
| } else { |
| errno = ENAMETOOLONG; |
| return false; |
| } |
| } |
| |
| void PathBuffer::Reset(intptr_t new_length) { |
| length_ = new_length; |
| AsString()[length_] = '\0'; |
| } |
| |
| // A linked list of symbolic links, with their unique file system identifiers. |
| // These are scanned to detect loops while doing a recursive directory listing. |
| struct LinkList { |
| dev_t dev; |
| ino_t ino; |
| LinkList* next; |
| }; |
| |
| ListType DirectoryListingEntry::Next(DirectoryListing* listing) { |
| if (done_) { |
| return kListDone; |
| } |
| |
| if (lister_ == 0) { |
| do { |
| lister_ = reinterpret_cast<intptr_t>( |
| opendir(listing->path_buffer().AsString())); |
| } while ((lister_ == 0) && (errno == EINTR)); |
| |
| if (lister_ == 0) { |
| done_ = true; |
| return kListError; |
| } |
| if (parent_ != nullptr) { |
| if (!listing->path_buffer().Add(File::PathSeparator())) { |
| return kListError; |
| } |
| } |
| path_length_ = listing->path_buffer().length(); |
| } |
| // Reset. |
| listing->path_buffer().Reset(path_length_); |
| ResetLink(); |
| |
| // Iterate the directory and post the directories and files to the |
| // ports. |
| int status = 0; |
| dirent entry; |
| dirent* result; |
| status = NO_RETRY_EXPECTED( |
| readdir_r(reinterpret_cast<DIR*>(lister_), &entry, &result)); |
| if ((status == 0) && (result != nullptr)) { |
| if (!listing->path_buffer().Add(entry.d_name)) { |
| done_ = true; |
| return kListError; |
| } |
| switch (entry.d_type) { |
| case DT_DIR: |
| if ((strcmp(entry.d_name, ".") == 0) || |
| (strcmp(entry.d_name, "..") == 0)) { |
| return Next(listing); |
| } |
| return kListDirectory; |
| case DT_BLK: |
| case DT_CHR: |
| case DT_FIFO: |
| case DT_SOCK: |
| case DT_REG: |
| return kListFile; |
| case DT_LNK: |
| if (!listing->follow_links()) { |
| return kListLink; |
| } |
| // Else fall through to next case. |
| FALL_THROUGH; |
| case DT_UNKNOWN: { |
| // On some file systems the entry type is not determined by |
| // readdir_r. For those and for links we use stat to determine |
| // the actual entry type. Notice that stat returns the type of |
| // the file pointed to. |
| struct stat entry_info; |
| int stat_success; |
| stat_success = NO_RETRY_EXPECTED( |
| lstat(listing->path_buffer().AsString(), &entry_info)); |
| if (stat_success == -1) { |
| return kListError; |
| } |
| if (listing->follow_links() && S_ISLNK(entry_info.st_mode)) { |
| // Check to see if we are in a loop created by a symbolic link. |
| LinkList current_link = {entry_info.st_dev, entry_info.st_ino, link_}; |
| LinkList* previous = link_; |
| while (previous != nullptr) { |
| if ((previous->dev == current_link.dev) && |
| (previous->ino == current_link.ino)) { |
| // Report the looping link as a link, rather than following it. |
| return kListLink; |
| } |
| previous = previous->next; |
| } |
| stat_success = NO_RETRY_EXPECTED( |
| stat(listing->path_buffer().AsString(), &entry_info)); |
| if (stat_success == -1 || (S_IFMT & entry_info.st_mode) == 0) { |
| // Report a broken link as a link, even if follow_links is true. |
| // A symbolic link can potentially point to an anon_inode. For |
| // example, an epoll file descriptor will have a symbolic link whose |
| // content is the string anon_inode:[eventpoll]. In this case, the |
| // target doesn't belong to any regular file category. |
| return kListLink; |
| } |
| if (S_ISDIR(entry_info.st_mode)) { |
| // Recurse into the subdirectory with current_link added to the |
| // linked list of seen file system links. |
| link_ = new LinkList(current_link); |
| if ((strcmp(entry.d_name, ".") == 0) || |
| (strcmp(entry.d_name, "..") == 0)) { |
| return Next(listing); |
| } |
| return kListDirectory; |
| } |
| } |
| if (S_ISDIR(entry_info.st_mode)) { |
| if ((strcmp(entry.d_name, ".") == 0) || |
| (strcmp(entry.d_name, "..") == 0)) { |
| return Next(listing); |
| } |
| return kListDirectory; |
| } else if (S_ISLNK(entry_info.st_mode)) { |
| return kListLink; |
| } else { |
| // Regular files, character devices, block devices, fifos, sockets and |
| // unknown types are all considered as files. |
| return kListFile; |
| } |
| } |
| |
| default: |
| // We should have covered all the bases. If not, let's get an error. |
| FATAL("Unexpected d_type: %d\n", entry.d_type); |
| return kListError; |
| } |
| } |
| done_ = true; |
| |
| if (status != 0) { |
| errno = status; |
| return kListError; |
| } |
| |
| return kListDone; |
| } |
| |
| DirectoryListingEntry::~DirectoryListingEntry() { |
| ResetLink(); |
| if (lister_ != 0) { |
| closedir(reinterpret_cast<DIR*>(lister_)); |
| } |
| } |
| |
| void DirectoryListingEntry::ResetLink() { |
| if ((link_ != nullptr) && |
| ((parent_ == nullptr) || (parent_->link_ != link_))) { |
| delete link_; |
| link_ = nullptr; |
| } |
| if (parent_ != nullptr) { |
| link_ = parent_->link_; |
| } |
| } |
| |
| static bool DeleteRecursively(PathBuffer* path); |
| |
| static bool DeleteFile(char* file_name, PathBuffer* path) { |
| return path->Add(file_name) && (unlink(path->AsString()) == 0); |
| } |
| |
| static bool DeleteDir(char* dir_name, PathBuffer* path) { |
| if ((strcmp(dir_name, ".") == 0) || (strcmp(dir_name, "..") == 0)) { |
| return true; |
| } |
| return path->Add(dir_name) && DeleteRecursively(path); |
| } |
| |
| static bool DeleteRecursively(PathBuffer* path) { |
| // Do not recurse into links for deletion. Instead delete the link. |
| // If it's a file, delete it. |
| struct stat st; |
| if (NO_RETRY_EXPECTED(lstat(path->AsString(), &st)) == -1) { |
| return false; |
| } else if (!S_ISDIR(st.st_mode)) { |
| return (unlink(path->AsString()) == 0); |
| } |
| |
| if (!path->Add(File::PathSeparator())) { |
| return false; |
| } |
| |
| // Not a link. Attempt to open as a directory and recurse into the |
| // directory. |
| DIR* dir_pointer; |
| do { |
| dir_pointer = opendir(path->AsString()); |
| } while ((dir_pointer == nullptr) && (errno == EINTR)); |
| if (dir_pointer == nullptr) { |
| return false; |
| } |
| |
| // Iterate the directory and delete all files and directories. |
| int path_length = path->length(); |
| dirent entry; |
| dirent* result; |
| while (NO_RETRY_EXPECTED(readdir_r(dir_pointer, &entry, &result)) == 0) { |
| if (result == nullptr) { |
| // End of directory. |
| return (NO_RETRY_EXPECTED(closedir(dir_pointer)) == 0) && |
| (NO_RETRY_EXPECTED(remove(path->AsString())) == 0); |
| } |
| bool ok = false; |
| switch (entry.d_type) { |
| case DT_DIR: |
| ok = DeleteDir(entry.d_name, path); |
| break; |
| case DT_BLK: |
| case DT_CHR: |
| case DT_FIFO: |
| case DT_SOCK: |
| case DT_REG: |
| case DT_LNK: |
| // Treat all links as files. This will delete the link which |
| // is what we want no matter if the link target is a file or a |
| // directory. |
| ok = DeleteFile(entry.d_name, path); |
| break; |
| case DT_UNKNOWN: { |
| if (!path->Add(entry.d_name)) { |
| break; |
| } |
| // On some file systems the entry type is not determined by |
| // readdir_r. For those we use lstat to determine the entry |
| // type. |
| struct stat entry_info; |
| if (NO_RETRY_EXPECTED(lstat(path->AsString(), &entry_info)) == -1) { |
| break; |
| } |
| path->Reset(path_length); |
| if (S_ISDIR(entry_info.st_mode)) { |
| ok = DeleteDir(entry.d_name, path); |
| } else { |
| // Treat links as files. This will delete the link which is |
| // what we want no matter if the link target is a file or a |
| // directory. |
| ok = DeleteFile(entry.d_name, path); |
| } |
| break; |
| } |
| default: |
| // We should have covered all the bases. If not, let's get an error. |
| FATAL("Unexpected d_type: %d\n", entry.d_type); |
| break; |
| } |
| if (!ok) { |
| break; |
| } |
| path->Reset(path_length); |
| } |
| // Only happens if an error. |
| ASSERT(errno != 0); |
| int err = errno; |
| VOID_NO_RETRY_EXPECTED(closedir(dir_pointer)); |
| errno = err; |
| return false; |
| } |
| |
| Directory::ExistsResult Directory::Exists(Namespace* namespc, |
| const char* dir_name) { |
| struct stat entry_info; |
| int success = NO_RETRY_EXPECTED(stat(dir_name, &entry_info)); |
| if (success == 0) { |
| if (S_ISDIR(entry_info.st_mode)) { |
| return EXISTS; |
| } else { |
| // An OSError may be constructed based on the return value of this |
| // function, so set errno to something that makes sense. |
| errno = ENOTDIR; |
| return DOES_NOT_EXIST; |
| } |
| } else { |
| if ((errno == EACCES) || (errno == EBADF) || (errno == EFAULT) || |
| (errno == ENOMEM) || (errno == EOVERFLOW)) { |
| // Search permissions denied for one of the directories in the |
| // path or a low level error occurred. We do not know if the |
| // directory exists. |
| return UNKNOWN; |
| } |
| ASSERT((errno == ELOOP) || (errno == ENAMETOOLONG) || (errno == ENOENT) || |
| (errno == ENOTDIR)); |
| return DOES_NOT_EXIST; |
| } |
| } |
| |
| char* Directory::CurrentNoScope() { |
| return getcwd(nullptr, 0); |
| } |
| |
| bool Directory::Create(Namespace* namespc, const char* dir_name) { |
| // Create the directory with the permissions specified by the |
| // process umask. |
| int result = NO_RETRY_EXPECTED(mkdir(dir_name, 0777)); |
| // If the directory already exists, treat it as a success. |
| if ((result == -1) && (errno == EEXIST)) { |
| return (Exists(namespc, dir_name) == EXISTS); |
| } |
| return (result == 0); |
| } |
| |
| const char* Directory::SystemTemp(Namespace* namespc) { |
| PathBuffer path; |
| const char* temp_dir = getenv("TMPDIR"); |
| if (temp_dir == nullptr) { |
| temp_dir = getenv("TMP"); |
| } |
| if (temp_dir == nullptr) { |
| temp_dir = "/tmp"; |
| } |
| if (!path.Add(temp_dir)) { |
| return nullptr; |
| } |
| // Remove any trailing slash. |
| char* result = path.AsString(); |
| int length = strlen(result); |
| if ((length > 1) && (result[length - 1] == '/')) { |
| result[length - 1] = '\0'; |
| } |
| return path.AsScopedString(); |
| } |
| |
| const char* Directory::CreateTemp(Namespace* namespc, const char* prefix) { |
| // Returns a new, unused directory name, adding characters to the end |
| // of prefix. Creates the directory with the permissions specified |
| // by the process umask. |
| // The return value is Dart_ScopeAllocated. |
| PathBuffer path; |
| if (!path.Add(prefix)) { |
| return nullptr; |
| } |
| if (!path.Add("XXXXXX")) { |
| // Pattern has overflowed. |
| return nullptr; |
| } |
| char* result; |
| do { |
| result = mkdtemp(path.AsString()); |
| } while ((result == nullptr) && (errno == EINTR)); |
| if (result == nullptr) { |
| return nullptr; |
| } |
| return path.AsScopedString(); |
| } |
| |
| bool Directory::Delete(Namespace* namespc, |
| const char* dir_name, |
| bool recursive) { |
| if (!recursive) { |
| if ((File::GetType(namespc, dir_name, false) == File::kIsLink) && |
| (File::GetType(namespc, dir_name, true) == File::kIsDirectory)) { |
| return (NO_RETRY_EXPECTED(unlink(dir_name)) == 0); |
| } |
| return (NO_RETRY_EXPECTED(rmdir(dir_name)) == 0); |
| } else { |
| PathBuffer path; |
| if (!path.Add(dir_name)) { |
| return false; |
| } |
| return DeleteRecursively(&path); |
| } |
| } |
| |
| bool Directory::Rename(Namespace* namespc, |
| const char* path, |
| const char* new_path) { |
| ExistsResult exists = Exists(namespc, path); |
| if (exists != EXISTS) { |
| return false; |
| } |
| return (NO_RETRY_EXPECTED(rename(path, new_path)) == 0); |
| } |
| |
| } // namespace bin |
| } // namespace dart |
| |
| #endif // defined(DART_HOST_OS_MACOS) |