OSDN Git Service

Reimplement scandir(3).
authorElliott Hughes <enh@google.com>
Mon, 25 Feb 2013 21:14:31 +0000 (13:14 -0800)
committerElliott Hughes <enh@google.com>
Mon, 25 Feb 2013 21:14:31 +0000 (13:14 -0800)
The old scandir implementation didn't take into account the varying
size of directory entries, and didn't correctly clean up on its
error exits.

Bug: 7339844
Change-Id: Ib40e3564709752241a3119a496cbb2192e3f9abe

libc/Android.mk
libc/bionic/dirent.cpp
libc/bionic/scandir.cpp [new file with mode: 0644]
libc/bionic/sysconf.cpp
libc/private/ScopedReaddir.h [new file with mode: 0644]

index 5a1356a..7219211 100644 (file)
@@ -293,6 +293,7 @@ libc_bionic_src_files := \
     bionic/pthread_sigmask.cpp \
     bionic/raise.cpp \
     bionic/sbrk.cpp \
+    bionic/scandir.cpp \
     bionic/__set_errno.cpp \
     bionic/setlocale.cpp \
     bionic/signalfd.cpp \
index 74297d8..0565698 100644 (file)
  */
 
 #include <dirent.h>
+
 #include <errno.h>
 #include <fcntl.h>
-#include <pthread.h>
-#include <stdlib.h>
-#include <string.h>
 #include <sys/stat.h>
 #include <sys/types.h>
 #include <unistd.h>
@@ -146,51 +144,6 @@ void rewinddir(DIR* d) {
   d->available_bytes_ = 0;
 }
 
-int scandir(const char* path, dirent*** namelist,
-            int(*filter)(const dirent*),
-            int(*compar)(const dirent**, const dirent**))
-{
-  int n_elem = 0;
-  dirent* this_de, *de;
-  dirent** de_list = NULL;
-  int de_list_size = 0;
-
-  DIR* d = opendir(path);
-  if (d == NULL) {
-    return -1;
-  }
-
-  while ((this_de = readdir(d)) != NULL) {
-    if (filter != NULL && (*filter)(this_de) == 0) {
-      continue;
-    }
-    if (n_elem == 0) {
-      de_list_size = 4;
-      de_list = (dirent**) malloc(sizeof(dirent*) * de_list_size);
-      if (de_list == NULL) {
-        return -1;
-      }
-    } else if (n_elem == de_list_size) {
-      de_list_size += 10;
-      dirent** de_list_new = (dirent**) realloc(de_list, sizeof(dirent*) * de_list_size);
-      if (de_list_new == NULL) {
-        free(de_list);
-        return -1;
-      }
-      de_list = de_list_new;
-    }
-    de = (dirent*) malloc(sizeof(dirent));
-    *de = *this_de;
-    de_list[n_elem++] = de;
-  }
-  closedir(d);
-  if (n_elem && compar) {
-    qsort(de_list, n_elem, sizeof(dirent*), (int (*)(const void*, const void*)) compar);
-  }
-  *namelist = de_list;
-  return n_elem;
-}
-
-int alphasort(const struct dirent** a, const struct dirent** b) {
+int alphasort(const dirent** a, const dirent** b) {
   return strcoll((*a)->d_name, (*b)->d_name);
 }
diff --git a/libc/bionic/scandir.cpp b/libc/bionic/scandir.cpp
new file mode 100644 (file)
index 0000000..dd22b22
--- /dev/null
@@ -0,0 +1,115 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <dirent.h>
+
+#include <errno.h>
+#include <stdlib.h>
+
+#include "private/ScopedReaddir.h"
+
+// A smart pointer to the scandir dirent**.
+class ScandirResult {
+ public:
+  ScandirResult() : names_(NULL), size_(0), capacity_(0) {
+  }
+
+  ~ScandirResult() {
+    while (size_ > 0) {
+      free(names_[--size_]);
+    }
+    free(names_);
+  }
+
+  size_t size() {
+    return size_;
+  }
+
+  dirent** release() {
+    dirent** result = names_;
+    names_ = NULL;
+    size_ = capacity_ = 0;
+    return result;
+  }
+
+  bool Add(dirent* entry) {
+    if (size_ >= capacity_) {
+      size_t new_capacity = capacity_ + 32;
+      dirent** new_names = (dirent**) realloc(names_, new_capacity * sizeof(dirent*));
+      if (new_names == NULL) {
+        return false;
+      }
+      names_ = new_names;
+      capacity_ = new_capacity;
+    }
+
+    dirent* copy = CopyDirent(entry);
+    if (copy == NULL) {
+      return false;
+    }
+    names_[size_++] = copy;
+    return true;
+  }
+
+  void Sort(int (*comparator)(const dirent**, const dirent**)) {
+    // If we have entries and a comparator, sort them.
+    if (size_ > 0 && comparator != NULL) {
+      qsort(names_, size_, sizeof(dirent*), (int (*)(const void*, const void*)) comparator);
+    }
+  }
+
+ private:
+  dirent** names_;
+  size_t size_;
+  size_t capacity_;
+
+  static dirent* CopyDirent(dirent* original) {
+    // Allocate the minimum number of bytes necessary, rounded up to a 4-byte boundary.
+    size_t size = ((original->d_reclen + 3) & ~3);
+    dirent* copy = (dirent*) malloc(size);
+    memcpy(copy, original, original->d_reclen);
+    return copy;
+  }
+
+  // Disallow copy and assignment.
+  ScandirResult(const ScandirResult&);
+  void operator=(const ScandirResult&);
+};
+
+int scandir(const char* dirname, dirent*** name_list,
+            int (*filter)(const dirent*),
+            int (*comparator)(const dirent**, const dirent**)) {
+  ScopedReaddir reader(dirname);
+  if (reader.IsBad()) {
+    return -1;
+  }
+
+  ScandirResult names;
+  dirent* entry;
+  while ((entry = reader.ReadEntry()) != NULL) {
+    // If we have a filter, skip names that don't match.
+    if (filter != NULL && !(*filter)(entry)) {
+      continue;
+    }
+    names.Add(entry);
+  }
+
+  names.Sort(comparator);
+
+  size_t size = names.size();
+  *name_list = names.release();
+  return size;
+}
index 869faef..5e39e61 100644 (file)
@@ -39,6 +39,8 @@
 #include <time.h>
 #include <unistd.h>
 
+#include "private/ScopedReaddir.h"
+
 /* seems to be the default on Linux, per the GLibc sources and my own digging */
 
 #define  SYSTEM_CLK_TCK         100
@@ -78,19 +80,18 @@ static bool __matches_cpuN(const char* s) {
 static int __sysconf_nprocessors_conf() {
   // On x86 kernels you can use /proc/cpuinfo for this, but on ARM kernels offline CPUs disappear
   // from there. This method works on both.
-  DIR* d = opendir("/sys/devices/system/cpu");
-  if (!d) {
+  ScopedReaddir reader("/sys/devices/system/cpu");
+  if (reader.IsBad()) {
     return 1;
   }
 
   int result = 0;
-  struct dirent* e;
-  while ((e = readdir(d)) != NULL) {
-    if (e->d_type == DT_DIR && __matches_cpuN(e->d_name)) {
+  dirent* entry;
+  while ((entry = reader.ReadEntry()) != NULL) {
+    if (entry->d_type == DT_DIR && __matches_cpuN(entry->d_name)) {
       ++result;
     }
   }
-  closedir(d);
   return result;
 }
 
diff --git a/libc/private/ScopedReaddir.h b/libc/private/ScopedReaddir.h
new file mode 100644 (file)
index 0000000..797809a
--- /dev/null
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef SCOPED_READDIR_H
+#define SCOPED_READDIR_H
+
+#include <dirent.h>
+
+class ScopedReaddir {
+ public:
+  ScopedReaddir(const char* path) {
+    dir_ = opendir(path);
+  }
+
+  ~ScopedReaddir() {
+    if (dir_ != NULL) {
+      closedir(dir_);
+    }
+  }
+
+  bool IsBad() {
+    return dir_ == NULL;
+  }
+
+  dirent* ReadEntry() {
+    return readdir(dir_);
+  }
+
+ private:
+  DIR* dir_;
+
+  // Disallow copy and assignment.
+  ScopedReaddir(const ScopedReaddir&);
+  void operator=(const ScopedReaddir&);
+};
+
+#endif // SCOPED_READDIR_H