// Copyright (c) 2016, 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_FUCHSIA) #include "bin/directory.h" #include // NOLINT #include // NOLINT #include // NOLINT #include // NOLINT #include // NOLINT #include // NOLINT #include // NOLINT #include // NOLINT #include "bin/crypto.h" #include "bin/dartutils.h" #include "bin/fdutils.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(data_); } wchar_t* PathBuffer::AsStringW() const { UNREACHABLE(); return nullptr; } const char* PathBuffer::AsScopedString() const { return DartUtils::ScopedCopyCString(AsString()); } bool PathBuffer::Add(const char* name) { const intptr_t name_length = strnlen(name, PATH_MAX + 1); if (name_length == 0) { errno = EINVAL; return false; } 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(written) == strnlen(name, PATH_MAX + 1))) { 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 (fd_ == -1) { ASSERT(lister_ == 0); NamespaceScope ns(listing->namespc(), listing->path_buffer().AsString()); const int listingfd = TEMP_FAILURE_RETRY(openat(ns.fd(), ns.path(), O_DIRECTORY)); if (listingfd < 0) { done_ = true; return kListError; } fd_ = listingfd; } if (lister_ == 0) { do { lister_ = reinterpret_cast(fdopendir(fd_)); } 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. errno = 0; dirent* entry = readdir(reinterpret_cast(lister_)); if (entry != 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. 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. NamespaceScope ns(listing->namespc(), listing->path_buffer().AsString()); struct stat entry_info; int stat_success; stat_success = TEMP_FAILURE_RETRY( fstatat(ns.fd(), ns.path(), &entry_info, AT_SYMLINK_NOFOLLOW)); 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 = TEMP_FAILURE_RETRY(fstatat(ns.fd(), ns.path(), &entry_info, 0)); 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 (errno != 0) { return kListError; } return kListDone; } DirectoryListingEntry::~DirectoryListingEntry() { ResetLink(); if (lister_ != 0) { // This also closes fd_. VOID_NO_RETRY_EXPECTED(closedir(reinterpret_cast(lister_))); } } void DirectoryListingEntry::ResetLink() { if ((link_ != nullptr) && ((parent_ == nullptr) || (parent_->link_ != link_))) { delete link_; link_ = nullptr; } if (parent_ != nullptr) { link_ = parent_->link_; } } Directory::ExistsResult Directory::Exists(Namespace* namespc, const char* dir_name) { NamespaceScope ns(namespc, dir_name); struct stat entry_info; const int success = TEMP_FAILURE_RETRY(fstatat(ns.fd(), ns.path(), &entry_info, 0)); 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) { NamespaceScope ns(namespc, dir_name); // Create the directory with the permissions specified by the // process umask. const int result = NO_RETRY_EXPECTED(mkdirat(ns.fd(), ns.path(), 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"; } NamespaceScope ns(namespc, temp_dir); if (!path.Add(ns.path())) { 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(); } // 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. const char* Directory::CreateTemp(Namespace* namespc, const char* prefix) { PathBuffer path; const int firstchar = 'A'; const int numchars = 'Z' - 'A' + 1; uint8_t random_bytes[7]; // mkdtemp doesn't have an "at" variant, so we have to simulate it. if (!path.Add(prefix)) { return nullptr; } intptr_t prefix_length = path.length(); while (true) { Crypto::GetRandomBytes(6, random_bytes); for (intptr_t i = 0; i < 6; i++) { random_bytes[i] = (random_bytes[i] % numchars) + firstchar; } random_bytes[6] = '\0'; if (!path.Add(reinterpret_cast(random_bytes))) { return nullptr; } NamespaceScope ns(namespc, path.AsString()); const int result = NO_RETRY_EXPECTED(mkdirat(ns.fd(), ns.path(), 0777)); if (result == 0) { return path.AsScopedString(); } else if (errno == EEXIST) { path.Reset(prefix_length); } else { return nullptr; } } } static bool DeleteRecursively(int dirfd, PathBuffer* path); static bool DeleteFile(int dirfd, char* file_name, PathBuffer* path) { return path->Add(file_name) && (NO_RETRY_EXPECTED(unlinkat(dirfd, path->AsString(), 0)) == 0); } static bool DeleteDir(int dirfd, char* dir_name, PathBuffer* path) { if ((strcmp(dir_name, ".") == 0) || (strcmp(dir_name, "..") == 0)) { return true; } return path->Add(dir_name) && DeleteRecursively(dirfd, path); } static bool DeleteRecursively(int dirfd, PathBuffer* path) { // Do not recurse into links for deletion. Instead delete the link. // If it's a file, delete it. struct stat st; if (TEMP_FAILURE_RETRY( fstatat(dirfd, path->AsString(), &st, AT_SYMLINK_NOFOLLOW)) == -1) { return false; } else if (!S_ISDIR(st.st_mode)) { return (NO_RETRY_EXPECTED(unlinkat(dirfd, path->AsString(), 0)) == 0); } if (!path->Add(File::PathSeparator())) { return false; } // Not a link. Attempt to open as a directory and recurse into the // directory. const int fd = TEMP_FAILURE_RETRY(openat(dirfd, path->AsString(), O_DIRECTORY)); if (fd < 0) { return false; } DIR* dir_pointer; do { dir_pointer = fdopendir(fd); } while ((dir_pointer == nullptr) && (errno == EINTR)); if (dir_pointer == nullptr) { FDUtils::SaveErrorAndClose(fd); return false; } // Iterate the directory and delete all files and directories. int path_length = path->length(); while (true) { // In case `readdir()` returns `nullptr` we distinguish between // end-of-stream and error by looking if `errno` was updated. errno = 0; // In glibc 2.24+, readdir_r is deprecated. // According to the man page for readdir: // "readdir(3) is not required to be thread-safe. However, in modern // implementations (including the glibc implementation), concurrent calls to // readdir(3) that specify different directory streams are thread-safe." dirent* entry = readdir(dir_pointer); if (entry == nullptr) { // Failed to read next directory entry. if (errno != 0) { break; } // End of directory. int status = NO_RETRY_EXPECTED(closedir(dir_pointer)); if (status != 0) { return false; } status = NO_RETRY_EXPECTED(unlinkat(dirfd, path->AsString(), AT_REMOVEDIR)); return status == 0; } bool ok = false; switch (entry->d_type) { case DT_DIR: ok = DeleteDir(dirfd, 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(dirfd, 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. For those we use lstat to determine the entry // type. struct stat entry_info; if (TEMP_FAILURE_RETRY(fstatat(dirfd, path->AsString(), &entry_info, AT_SYMLINK_NOFOLLOW)) == -1) { break; } path->Reset(path_length); if (S_ISDIR(entry_info.st_mode)) { ok = DeleteDir(dirfd, 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(dirfd, 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; } bool Directory::Delete(Namespace* namespc, const char* dir_name, bool recursive) { NamespaceScope ns(namespc, dir_name); if (!recursive) { if ((File::GetType(namespc, dir_name, false) == File::kIsLink) && (File::GetType(namespc, dir_name, true) == File::kIsDirectory)) { return NO_RETRY_EXPECTED(unlinkat(ns.fd(), ns.path(), 0)) == 0; } return NO_RETRY_EXPECTED(unlinkat(ns.fd(), ns.path(), AT_REMOVEDIR)) == 0; } else { PathBuffer path; if (!path.Add(ns.path())) { return false; } return DeleteRecursively(ns.fd(), &path); } } bool Directory::Rename(Namespace* namespc, const char* old_path, const char* new_path) { ExistsResult exists = Exists(namespc, old_path); if (exists != EXISTS) { return false; } NamespaceScope oldns(namespc, old_path); NamespaceScope newns(namespc, new_path); return (NO_RETRY_EXPECTED(renameat(oldns.fd(), oldns.path(), newns.fd(), newns.path())) == 0); } } // namespace bin } // namespace dart #endif // defined(DART_HOST_OS_FUCHSIA)