dart-sdk/runtime/bin/directory_macos.cc
Ryan Macnak b68351fbc3 [vm] Update NULL to nullptr in runtime/bin.
TEST=build
Change-Id: Ie3be570c274b0275a995a0f54b5e6ccdfc77ccd3
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/292287
Commit-Queue: Ryan Macnak <rmacnak@google.com>
Reviewed-by: Brian Quinlan <bquinlan@google.com>
2023-04-12 01:11:05 +00:00

450 lines
13 KiB
C++

// 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)