Index: runtime/bin/directory_fuchsia.cc |
diff --git a/runtime/bin/directory_fuchsia.cc b/runtime/bin/directory_fuchsia.cc |
index 6b7fc5bd50128d802068bcf3d4500741bee6b334..c9a7c4e9438373df98ab338bf51bb7530411b036 100644 |
--- a/runtime/bin/directory_fuchsia.cc |
+++ b/runtime/bin/directory_fuchsia.cc |
@@ -7,16 +7,21 @@ |
#include "bin/directory.h" |
-#include <dirent.h> // NOLINT |
-#include <errno.h> // NOLINT |
-#include <stdlib.h> // NOLINT |
-#include <string.h> // NOLINT |
-#include <sys/param.h> // NOLINT |
-#include <sys/stat.h> // NOLINT |
-#include <unistd.h> // NOLINT |
- |
+#include <dirent.h> // NOLINT |
+#include <errno.h> // NOLINT |
+#include <fcntl.h> // NOLINT |
+#include <mxio/namespace.h> // NOLINT |
+#include <stdlib.h> // NOLINT |
+#include <string.h> // NOLINT |
+#include <sys/param.h> // NOLINT |
+#include <sys/stat.h> // NOLINT |
+#include <unistd.h> // 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" |
@@ -86,11 +91,23 @@ ListType DirectoryListingEntry::Next(DirectoryListing* listing) { |
return kListDone; |
} |
+ if (fd_ == -1) { |
+ ASSERT(lister_ == 0); |
+ NamespaceScope ns(listing->namespc(), listing->path_buffer().AsString()); |
+ const int listingfd = |
+ TEMP_FAILURE_RETRY(openat64(ns.fd(), ns.path(), O_DIRECTORY)); |
+ if (listingfd < 0) { |
+ done_ = true; |
+ return kListError; |
+ } |
+ fd_ = listingfd; |
+ } |
+ |
if (lister_ == 0) { |
- lister_ = |
- reinterpret_cast<intptr_t>(opendir(listing->path_buffer().AsString())); |
+ do { |
+ lister_ = reinterpret_cast<intptr_t>(fdopendir(fd_)); |
+ } while ((lister_ == 0) && (errno == EINTR)); |
if (lister_ == 0) { |
- perror("opendir failed: "); |
done_ = true; |
return kListError; |
} |
@@ -114,61 +131,91 @@ ListType DirectoryListingEntry::Next(DirectoryListing* listing) { |
done_ = true; |
return kListError; |
} |
- // TODO(MG-450): When entry->d_type is filled out correctly, we can avoid |
- // this call to stat(). |
- struct stat64 entry_info; |
- int stat_success; |
- stat_success = NO_RETRY_EXPECTED( |
- lstat64(listing->path_buffer().AsString(), &entry_info)); |
- if (stat_success == -1) { |
- perror("lstat64 failed: "); |
- 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 != NULL) { |
- 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( |
- stat64(listing->path_buffer().AsString(), &entry_info)); |
- if (stat_success == -1) { |
- perror("lstat64 failed"); |
- // Report a broken link as a link, even if follow_links is true. |
- 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); |
+ 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 stat64 entry_info; |
+ int stat_success; |
+ stat_success = TEMP_FAILURE_RETRY( |
+ fstatat64(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 != NULL) { |
+ 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(fstatat64(ns.fd(), ns.path(), &entry_info, 0)); |
+ if (stat_success == -1) { |
+ // Report a broken link as a link, even if follow_links is true. |
+ 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_ISREG(entry_info.st_mode) || S_ISCHR(entry_info.st_mode) || |
+ S_ISBLK(entry_info.st_mode) || |
+ S_ISFIFO(entry_info.st_mode) || |
+ S_ISSOCK(entry_info.st_mode)) { |
+ return kListFile; |
+ } else if (S_ISLNK(entry_info.st_mode)) { |
+ return kListLink; |
+ } else { |
+ FATAL1("Unexpected st_mode: %d\n", entry_info.st_mode); |
+ return kListError; |
+ } |
} |
- } |
- 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_ISREG(entry_info.st_mode) || S_ISCHR(entry_info.st_mode) || |
- S_ISBLK(entry_info.st_mode) || S_ISFIFO(entry_info.st_mode) || |
- S_ISSOCK(entry_info.st_mode)) { |
- return kListFile; |
- } else if (S_ISLNK(entry_info.st_mode)) { |
- return kListLink; |
- } else { |
- FATAL1("Unexpected st_mode: %d\n", entry_info.st_mode); |
- return kListError; |
+ |
+ default: |
+ // We should have covered all the bases. If not, let's get an error. |
+ FATAL1("Unexpected d_type: %d\n", entry->d_type); |
+ return kListError; |
} |
} |
done_ = true; |
@@ -183,6 +230,7 @@ ListType DirectoryListingEntry::Next(DirectoryListing* listing) { |
DirectoryListingEntry::~DirectoryListingEntry() { |
ResetLink(); |
if (lister_ != 0) { |
+ // This also closes fd_. |
VOID_NO_RETRY_EXPECTED(closedir(reinterpret_cast<DIR*>(lister_))); |
} |
} |
@@ -197,9 +245,12 @@ void DirectoryListingEntry::ResetLink() { |
} |
} |
-Directory::ExistsResult Directory::Exists(const char* dir_name) { |
- struct stat entry_info; |
- int success = NO_RETRY_EXPECTED(stat(dir_name, &entry_info)); |
+Directory::ExistsResult Directory::Exists(Namespace* namespc, |
+ const char* dir_name) { |
+ NamespaceScope ns(namespc, dir_name); |
+ struct stat64 entry_info; |
+ const int success = |
+ TEMP_FAILURE_RETRY(fstatat64(ns.fd(), ns.path(), &entry_info, 0)); |
if (success == 0) { |
if (S_ISDIR(entry_info.st_mode)) { |
return EXISTS; |
@@ -227,30 +278,19 @@ char* Directory::CurrentNoScope() { |
return getcwd(NULL, 0); |
} |
-const char* Directory::Current() { |
- char buffer[PATH_MAX]; |
- if (getcwd(buffer, PATH_MAX) == NULL) { |
- return NULL; |
- } |
- return DartUtils::ScopedCopyCString(buffer); |
-} |
- |
-bool Directory::SetCurrent(const char* path) { |
- return (NO_RETRY_EXPECTED(chdir(path)) == 0); |
-} |
- |
-bool Directory::Create(const char* dir_name) { |
+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. |
- int result = NO_RETRY_EXPECTED(mkdir(dir_name, 0777)); |
+ 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(dir_name) == EXISTS); |
+ return (Exists(namespc, dir_name) == EXISTS); |
} |
return (result == 0); |
} |
-const char* Directory::SystemTemp() { |
+const char* Directory::SystemTemp(Namespace* namespc) { |
PathBuffer path; |
const char* temp_dir = getenv("TMPDIR"); |
if (temp_dir == NULL) { |
@@ -259,7 +299,8 @@ const char* Directory::SystemTemp() { |
if (temp_dir == NULL) { |
temp_dir = "/tmp"; |
} |
- if (!path.Add(temp_dir)) { |
+ NamespaceScope ns(namespc, temp_dir); |
+ if (!path.Add(ns.path())) { |
return NULL; |
} |
@@ -272,48 +313,65 @@ const char* Directory::SystemTemp() { |
return path.AsScopedString(); |
} |
-const char* Directory::CreateTemp(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. |
+// 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 NULL; |
} |
- if (!path.Add("XXXXXX")) { |
- // Pattern has overflowed. |
- return NULL; |
- } |
- char* result = mkdtemp(path.AsString()); |
- if (result == NULL) { |
- return NULL; |
+ 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<char*>(random_bytes))) { |
+ return NULL; |
+ } |
+ 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 NULL; |
+ } |
} |
- return path.AsScopedString(); |
} |
-static bool DeleteRecursively(PathBuffer* path); |
+static bool DeleteRecursively(int dirfd, PathBuffer* path); |
-static bool DeleteFile(char* file_name, PathBuffer* path) { |
+static bool DeleteFile(int dirfd, char* file_name, PathBuffer* path) { |
return path->Add(file_name) && |
- (NO_RETRY_EXPECTED(unlink(path->AsString())) == 0); |
+ (NO_RETRY_EXPECTED(unlinkat(dirfd, path->AsString(), 0)) == 0); |
} |
-static bool DeleteDir(char* dir_name, PathBuffer* path) { |
+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(path); |
+ return path->Add(dir_name) && DeleteRecursively(dirfd, path); |
} |
-static bool DeleteRecursively(PathBuffer* 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 stat64 st; |
- if (NO_RETRY_EXPECTED(lstat64(path->AsString(), &st)) == -1) { |
+ if (TEMP_FAILURE_RETRY( |
+ fstatat64(dirfd, path->AsString(), &st, AT_SYMLINK_NOFOLLOW)) == -1) { |
return false; |
} else if (!S_ISDIR(st.st_mode)) { |
- return NO_RETRY_EXPECTED(unlink(path->AsString())) == 0; |
+ return (NO_RETRY_EXPECTED(unlinkat(dirfd, path->AsString(), 0)) == 0); |
} |
if (!path->Add(File::PathSeparator())) { |
@@ -322,8 +380,17 @@ static bool DeleteRecursively(PathBuffer* path) { |
// Not a link. Attempt to open as a directory and recurse into the |
// directory. |
- DIR* dir_pointer = opendir(path->AsString()); |
+ const int fd = |
+ TEMP_FAILURE_RETRY(openat64(dirfd, path->AsString(), O_DIRECTORY)); |
+ if (fd < 0) { |
+ return false; |
+ } |
+ DIR* dir_pointer; |
+ do { |
+ dir_pointer = fdopendir(fd); |
+ } while ((dir_pointer == NULL) && (errno == EINTR)); |
if (dir_pointer == NULL) { |
+ FDUtils::SaveErrorAndClose(fd); |
return false; |
} |
@@ -345,63 +412,103 @@ static bool DeleteRecursively(PathBuffer* path) { |
break; |
} |
// End of directory. |
- return (NO_RETRY_EXPECTED(closedir(dir_pointer)) == 0) && |
- (NO_RETRY_EXPECTED(remove(path->AsString())) == 0); |
+ int status = NO_RETRY_EXPECTED(closedir(dir_pointer)); |
+ FDUtils::SaveErrorAndClose(fd); |
+ if (status != 0) { |
+ return false; |
+ } |
+ status = |
+ NO_RETRY_EXPECTED(unlinkat(dirfd, path->AsString(), AT_REMOVEDIR)); |
+ return status == 0; |
} |
bool ok = false; |
- if (!path->Add(entry->d_name)) { |
- break; |
- } |
- // TODO(MG-450): When entry->d_type is filled out correctly, we can avoid |
- // this call to stat(). |
- struct stat64 entry_info; |
- if (NO_RETRY_EXPECTED(lstat64(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); |
+ 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 stat64 entry_info; |
+ if (TEMP_FAILURE_RETRY(fstatat64(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. |
+ FATAL1("Unexpected d_type: %d\n", entry->d_type); |
+ break; |
} |
if (!ok) { |
break; |
} |
path->Reset(path_length); |
} |
- // Only happens if there was an error. |
+ // Only happens if an error. |
ASSERT(errno != 0); |
int err = errno; |
VOID_NO_RETRY_EXPECTED(closedir(dir_pointer)); |
+ FDUtils::SaveErrorAndClose(fd); |
errno = err; |
return false; |
} |
-bool Directory::Delete(const char* dir_name, bool recursive) { |
+bool Directory::Delete(Namespace* namespc, |
+ const char* dir_name, |
+ bool recursive) { |
+ NamespaceScope ns(namespc, dir_name); |
if (!recursive) { |
- if ((File::GetType(dir_name, false) == File::kIsLink) && |
- (File::GetType(dir_name, true) == File::kIsDirectory)) { |
- return NO_RETRY_EXPECTED(unlink(dir_name)) == 0; |
+ 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(rmdir(dir_name)) == 0; |
+ return NO_RETRY_EXPECTED(unlinkat(ns.fd(), ns.path(), AT_REMOVEDIR)) == 0; |
} else { |
PathBuffer path; |
- if (!path.Add(dir_name)) { |
+ if (!path.Add(ns.path())) { |
return false; |
} |
- return DeleteRecursively(&path); |
+ return DeleteRecursively(ns.fd(), &path); |
} |
} |
-bool Directory::Rename(const char* path, const char* new_path) { |
- ExistsResult exists = Exists(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; |
} |
- return (NO_RETRY_EXPECTED(rename(path, new_path)) == 0); |
+ 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 |