Implement scandirat and scandirat64.

Bug: http://b/12612339
Change-Id: Id3b249a884fe08964b26a017ae9574961f0cb441
diff --git a/libc/bionic/scandir.cpp b/libc/bionic/scandir.cpp
index ee62fee..e55be42 100644
--- a/libc/bionic/scandir.cpp
+++ b/libc/bionic/scandir.cpp
@@ -16,9 +16,11 @@
 
 #include <dirent.h>
 
+#include <fcntl.h>
 #include <errno.h>
 #include <stdlib.h>
 #include <string.h>
+#include <unistd.h>
 
 #include "private/bionic_macros.h"
 #include "private/ScopedReaddir.h"
@@ -26,7 +28,7 @@
 // A smart pointer to the scandir dirent**.
 class ScandirResult {
  public:
-  ScandirResult() : names_(NULL), size_(0), capacity_(0) {
+  ScandirResult() : names_(nullptr), size_(0), capacity_(0) {
   }
 
   ~ScandirResult() {
@@ -42,7 +44,7 @@
 
   dirent** release() {
     dirent** result = names_;
-    names_ = NULL;
+    names_ = nullptr;
     size_ = capacity_ = 0;
     return result;
   }
@@ -52,7 +54,7 @@
       size_t new_capacity = capacity_ + 32;
       dirent** new_names =
           reinterpret_cast<dirent**>(realloc(names_, new_capacity * sizeof(dirent*)));
-      if (new_names == NULL) {
+      if (new_names == nullptr) {
         return false;
       }
       names_ = new_names;
@@ -60,7 +62,7 @@
     }
 
     dirent* copy = CopyDirent(entry);
-    if (copy == NULL) {
+    if (copy == nullptr) {
       return false;
     }
     names_[size_++] = copy;
@@ -69,7 +71,7 @@
 
   void Sort(int (*comparator)(const dirent**, const dirent**)) {
     // If we have entries and a comparator, sort them.
-    if (size_ > 0 && comparator != NULL) {
+    if (size_ > 0 && comparator != nullptr) {
       qsort(names_, size_, sizeof(dirent*),
             reinterpret_cast<int (*)(const void*, const void*)>(comparator));
     }
@@ -91,19 +93,29 @@
   DISALLOW_COPY_AND_ASSIGN(ScandirResult);
 };
 
-int scandir(const char* dirname, dirent*** name_list,
-            int (*filter)(const dirent*),
-            int (*comparator)(const dirent**, const dirent**)) {
-  ScopedReaddir reader(dirname);
+int scandirat(int parent_fd, const char* dir_name, dirent*** name_list,
+              int (*filter)(const dirent*),
+              int (*comparator)(const dirent**, const dirent**)) {
+  DIR* dir = nullptr;
+  if (parent_fd == AT_FDCWD) {
+    dir = opendir(dir_name);
+  } else {
+    int dir_fd = openat(parent_fd, dir_name, O_CLOEXEC | O_DIRECTORY | O_RDONLY);
+    if (dir_fd != -1) {
+      dir = fdopendir(dir_fd);
+    }
+  }
+
+  ScopedReaddir reader(dir);
   if (reader.IsBad()) {
     return -1;
   }
 
   ScandirResult names;
   dirent* entry;
-  while ((entry = reader.ReadEntry()) != NULL) {
+  while ((entry = reader.ReadEntry()) != nullptr) {
     // If we have a filter, skip names that don't match.
-    if (filter != NULL && !(*filter)(entry)) {
+    if (filter != nullptr && !(*filter)(entry)) {
       continue;
     }
     names.Add(entry);
@@ -115,4 +127,11 @@
   *name_list = names.release();
   return size;
 }
+__strong_alias(scandirat64, scandirat);
+
+int scandir(const char* dir_path, dirent*** name_list,
+            int (*filter)(const dirent*),
+            int (*comparator)(const dirent**, const dirent**)) {
+  return scandirat(AT_FDCWD, dir_path, name_list, filter, comparator);
+}
 __strong_alias(scandir64, scandir);
diff --git a/libc/include/dirent.h b/libc/include/dirent.h
index 63716a4..3cdfa68 100644
--- a/libc/include/dirent.h
+++ b/libc/include/dirent.h
@@ -81,8 +81,13 @@
 extern int dirfd(DIR*);
 extern int alphasort(const struct dirent**, const struct dirent**);
 extern int alphasort64(const struct dirent64**, const struct dirent64**);
-extern int scandir(const char*, struct dirent***, int (*)(const struct dirent*), int (*)(const struct dirent**, const struct dirent**));
 extern int scandir64(const char*, struct dirent64***, int (*)(const struct dirent64*), int (*)(const struct dirent64**, const struct dirent64**));
+extern int scandir(const char*, struct dirent***, int (*)(const struct dirent*), int (*)(const struct dirent**, const struct dirent**));
+
+#if defined(__USE_GNU)
+int scandirat64(int, const char*, struct dirent64***, int (*)(const struct dirent64*), int (*)(const struct dirent64**, const struct dirent64**));
+int scandirat(int, const char*, struct dirent***, int (*)(const struct dirent*), int (*)(const struct dirent**, const struct dirent**));
+#endif
 
 __END_DECLS
 
diff --git a/libc/libc.map b/libc/libc.map
index fbceb83..bb67b5c 100644
--- a/libc/libc.map
+++ b/libc/libc.map
@@ -1346,6 +1346,8 @@
     preadv64;
     pwritev;
     pwritev64;
+    scandirat;
+    scandirat64;
     strchrnul;
 } LIBC;
 
diff --git a/libc/private/ScopedReaddir.h b/libc/private/ScopedReaddir.h
index 84c1b93..3d77a40 100644
--- a/libc/private/ScopedReaddir.h
+++ b/libc/private/ScopedReaddir.h
@@ -23,8 +23,11 @@
 
 class ScopedReaddir {
  public:
-  ScopedReaddir(const char* path) {
-    dir_ = opendir(path);
+  ScopedReaddir(const char* path) : ScopedReaddir(opendir(path)) {
+  }
+
+  ScopedReaddir(DIR* dir) {
+    dir_ = dir;
   }
 
   ~ScopedReaddir() {
diff --git a/tests/dirent_test.cpp b/tests/dirent_test.cpp
index 214dd78..fa05ca1 100644
--- a/tests/dirent_test.cpp
+++ b/tests/dirent_test.cpp
@@ -81,6 +81,72 @@
   CheckProcSelf(name_set);
 }
 
+TEST(dirent, scandirat_scandirat64) {
+  // Get everything from /proc/self...
+  dirent** entries;
+  int entry_count = scandir("/proc/self", &entries, NULL, alphasort);
+  ASSERT_GE(entry_count, 0);
+
+  int proc_fd = open("/proc", O_DIRECTORY);
+  ASSERT_NE(-1, proc_fd);
+
+  dirent** entries_at;
+  int entry_count_at = scandirat(proc_fd, "self", &entries_at, NULL, alphasort);
+  ASSERT_EQ(entry_count, entry_count_at);
+
+  dirent64** entries_at64;
+  int entry_count_at64 = scandirat64(proc_fd, "self", &entries_at64, NULL, alphasort64);
+  ASSERT_EQ(entry_count, entry_count_at64);
+
+  close(proc_fd);
+
+  // scandirat and scandirat64 should return the same results as scandir.
+  std::set<std::string> name_set, name_set_at, name_set_at64;
+  std::vector<std::string> unsorted_name_list, unsorted_name_list_at, unsorted_name_list_at64;
+  ScanEntries(entries, entry_count, name_set, unsorted_name_list);
+  ScanEntries(entries_at, entry_count_at, name_set_at, unsorted_name_list_at);
+  ScanEntries(entries_at64, entry_count_at64, name_set_at64, unsorted_name_list_at64);
+
+  ASSERT_EQ(name_set, name_set_at);
+  ASSERT_EQ(name_set, name_set_at64);
+  ASSERT_EQ(unsorted_name_list, unsorted_name_list_at);
+  ASSERT_EQ(unsorted_name_list, unsorted_name_list_at64);
+}
+
+TEST(dirent, scandir_ENOENT) {
+  dirent** entries;
+  errno = 0;
+  ASSERT_EQ(-1, scandir("/does-not-exist", &entries, nullptr, nullptr));
+  ASSERT_EQ(ENOENT, errno);
+}
+
+TEST(dirent, scandir64_ENOENT) {
+  dirent64** entries;
+  errno = 0;
+  ASSERT_EQ(-1, scandir64("/does-not-exist", &entries, nullptr, nullptr));
+  ASSERT_EQ(ENOENT, errno);
+}
+
+TEST(dirent, scandirat_ENOENT) {
+  int root_fd = open("/", O_DIRECTORY | O_RDONLY);
+  ASSERT_NE(-1, root_fd);
+  dirent** entries;
+  errno = 0;
+  ASSERT_EQ(-1, scandirat(root_fd, "does-not-exist", &entries, nullptr, nullptr));
+  ASSERT_EQ(ENOENT, errno);
+  close(root_fd);
+}
+
+TEST(dirent, scandirat64_ENOENT) {
+  int root_fd = open("/", O_DIRECTORY | O_RDONLY);
+  ASSERT_NE(-1, root_fd);
+  dirent64** entries;
+  errno = 0;
+  ASSERT_EQ(-1, scandirat64(root_fd, "does-not-exist", &entries, nullptr, nullptr));
+  ASSERT_EQ(ENOENT, errno);
+  close(root_fd);
+}
+
 TEST(dirent, fdopendir_invalid) {
   ASSERT_TRUE(fdopendir(-1) == NULL);
   ASSERT_EQ(EBADF, errno);