[util] Add os_dirent for device enumeration

This is used to enumerate physical devices on Fuchsia
as we migrate away from depending on libfdio.

The implementation uses the Fuchsia specific callback
to gain access to the directory in the namespace.

Bug: 13095

Change-Id: Icb4b898735b033b6feea88c6fa9040401dcd4a39
diff --git a/src/intel/vulkan/BUILD.gn b/src/intel/vulkan/BUILD.gn
index ddde301..10b8dc2 100644
--- a/src/intel/vulkan/BUILD.gn
+++ b/src/intel/vulkan/BUILD.gn
@@ -96,6 +96,7 @@
     "$mesa_build_root/src/intel/blorp",
     "$mesa_build_root/src/intel/common",
     "$mesa_build_root/src/intel/dev",
+    "$mesa_build_root/src/os",
     "$mesa_build_root/src/util",
     "$mesa_build_root/src/vulkan/util",
     "$msd_intel_gen_build_root/include",
diff --git a/src/intel/vulkan/anv_device.c b/src/intel/vulkan/anv_device.c
index 2dca6b7..455b5b3 100644
--- a/src/intel/vulkan/anv_device.c
+++ b/src/intel/vulkan/anv_device.c
@@ -31,7 +31,7 @@
 #include <unistd.h>
 #include <fcntl.h>
 #if defined(ANV_MAGMA)
-#include <dirent.h>
+#include "util/os_dirent.h"
 #else
 #include <xf86drm.h>
 #include "drm-uapi/drm_fourcc.h"
@@ -896,29 +896,26 @@
    result = anv_physical_device_init(&instance->physicalDevice, instance,
       DEV_GPU_PATH_OVERRIDE, DEV_GPU_PATH_OVERRIDE);
 #else
-   struct dirent* de;
    const char DEV_GPU[] = "/dev/class/gpu";
-   DIR* dir = opendir(DEV_GPU);
+
+   struct os_dirent* de;
+   os_dir_t* dir = os_opendir(DEV_GPU);
    if (!dir) {
       printf("Error opening %s\n", DEV_GPU);
       return VK_ERROR_INCOMPATIBLE_DRIVER;
    }
 
-   while ((de = readdir(dir)) != NULL) {
+   while ((de = os_readdir(dir)) != NULL) {
       // extra +1 ensures space for null termination
       char name[sizeof(DEV_GPU) + sizeof('/') + (NAME_MAX + 1) + 1];
       snprintf(name, sizeof(name), "%s/%s", DEV_GPU, de->d_name);
 
-      struct stat path_stat;
-      stat(name, &path_stat);
-      if (!S_ISDIR(path_stat.st_mode)) {
-         result = anv_physical_device_init(&instance->physicalDevice, instance, 
-            name, name);
-         if (result != VK_ERROR_INCOMPATIBLE_DRIVER)
-            break;
-      }
+     result = anv_physical_device_init(&instance->physicalDevice, instance,
+        name, name);
+     if (result != VK_ERROR_INCOMPATIBLE_DRIVER)
+        break;
    }
-   closedir(dir);
+   os_closedir(dir);
 #endif
 #else
    /* TODO: Check for more devices ? */
diff --git a/src/intel/vulkan/anv_magma.c b/src/intel/vulkan/anv_magma.c
index c33b2bb..0b4642c 100644
--- a/src/intel/vulkan/anv_magma.c
+++ b/src/intel/vulkan/anv_magma.c
@@ -16,7 +16,7 @@
 #endif
 
 #if defined(__Fuchsia__)
-#include <zircon/syscalls.h>
+#include "os/fuchsia.h"
 #endif
 
 static magma_connection_t magma_connection(struct anv_device* device)
@@ -200,53 +200,12 @@
 #if VK_USE_PLATFORM_FUCHSIA
 typedef VkResult(VKAPI_PTR* PFN_vkGetServiceAddr)(const char* pName, uint32_t handle);
 
-static PFN_vkGetServiceAddr vulkan_lookup_func;
-static pthread_once_t tracing_initialize = PTHREAD_ONCE_INIT;
-
-static void initialize_tracing()
-{
-   uint32_t client_handle;
-   VkResult result =
-       anv_magma_connect_to_service("/svc/fuchsia.tracing.provider.Registry", &client_handle);
-   if (result != VK_SUCCESS) {
-      DLOG("Connecting to trace provider failed: %d", result);
-      return;
-   }
-   magma_status_t status = magma_initialize_tracing(client_handle);
-   if (status != MAGMA_STATUS_OK) {
-      DLOG("Initializing tracing failed: %d", status);
-   }
-}
-
 PUBLIC VKAPI_ATTR void VKAPI_CALL
 vk_icdInitializeConnectToServiceCallback(PFN_vkGetServiceAddr get_services_addr)
 {
-   vulkan_lookup_func = get_services_addr;
-
-   // Multiple loader instances may call this multiple times, but we only ever
-   // support initializing tracing once.
-   pthread_once(&tracing_initialize, &initialize_tracing);
+   fuchsia_init(get_services_addr);
 }
 
-VkResult anv_magma_connect_to_service(const char* path, uint32_t* client_handle_out)
-{
-   if (!vulkan_lookup_func) {
-      DLOG("No vulkan lookup function");
-      return VK_ERROR_INITIALIZATION_FAILED;
-   }
-   zx_handle_t client_handle, server_handle;
-   zx_status_t status = zx_channel_create(0, &client_handle, &server_handle);
-   if (status != ZX_OK) {
-      DLOG("Channel create failed: %d", status);
-      return VK_ERROR_INITIALIZATION_FAILED;
-   }
-   VkResult result = vulkan_lookup_func(path, server_handle);
-   if (result != VK_SUCCESS) {
-      return result;
-   }
-   *client_handle_out = client_handle;
-   return VK_SUCCESS;
-}
 #endif // VK_USE_PLATFORM_FUCHSIA
 
 VkResult anv_magma_open_device_handle(const char* path, anv_device_handle_t* device_out)
@@ -254,9 +213,8 @@
    magma_device_t device;
 #if defined(__Fuchsia__)
    zx_handle_t client_handle;
-   VkResult result = anv_magma_connect_to_service(path, &client_handle);
-   if (result != VK_SUCCESS) {
-      return result;
+   if (!fuchsia_open(path, &client_handle)) {
+      return VK_ERROR_INCOMPATIBLE_DRIVER;
    }
    if (magma_device_import(client_handle, &device) != MAGMA_STATUS_OK) {
       return VK_ERROR_INCOMPATIBLE_DRIVER;
diff --git a/src/intel/vulkan/anv_magma.h b/src/intel/vulkan/anv_magma.h
index 3e7f8b6..050e923 100644
--- a/src/intel/vulkan/anv_magma.h
+++ b/src/intel/vulkan/anv_magma.h
@@ -7,11 +7,10 @@
 
 // Don't include anv_private.h here; this header is included by the
 // c++ implementation anv_magma_connection.cc.
-#include "i915_drm.h"
+#include "drm-uapi/i915_drm.h"
 #include "magma.h"
 
 #include <stdio.h>
-#include <vulkan/vulkan.h>
 
 #if DEBUG
 #define ANV_MAGMA_DRET(ret) (ret == 0 ? ret : anv_magma_dret(__FILE__, __LINE__, ret))
@@ -59,8 +58,6 @@
    return ret;
 }
 
-VkResult anv_magma_connect_to_service(const char* path, uint32_t* client_handle_out);
-
 #ifdef __cplusplus
 } // extern "C"
 #endif
diff --git a/src/intel/vulkan/anv_magma_connection.cc b/src/intel/vulkan/anv_magma_connection.cc
index cdb4597..79db997 100644
--- a/src/intel/vulkan/anv_magma_connection.cc
+++ b/src/intel/vulkan/anv_magma_connection.cc
@@ -11,6 +11,10 @@
 #include <map>
 #include <vector>
 
+#if VK_USE_PLATFORM_FUCHSIA
+#include "os/fuchsia.h"
+#endif
+
 class Buffer : public anv_magma_buffer {
 public:
    Buffer(magma_buffer_t buffer) { anv_magma_buffer::buffer = buffer; }
@@ -78,10 +82,8 @@
    magma_status_t GetSysmemConnection(magma_sysmem_connection_t* sysmem_connection_out)
    {
       if (!sysmem_connection_) {
-         uint32_t client_handle;
-         VkResult result =
-             anv_magma_connect_to_service("/svc/fuchsia.sysmem.Allocator", &client_handle);
-         if (result != VK_SUCCESS)
+         zx_handle_t client_handle;
+         if (!fuchsia_open("/svc/fuchsia.sysmem.Allocator", &client_handle))
             return DRET(MAGMA_STATUS_INTERNAL_ERROR);
          magma_status_t status = magma_sysmem_connection_import(client_handle, &sysmem_connection_);
          if (status != MAGMA_STATUS_OK)
diff --git a/src/os/BUILD.gn b/src/os/BUILD.gn
new file mode 100644
index 0000000..e5297b0
--- /dev/null
+++ b/src/os/BUILD.gn
@@ -0,0 +1,18 @@
+# Copyright 2019 The Fuchsia Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+import("../../mesa.gni")
+
+mesa_source_set("os") {
+  if (current_os == "fuchsia") {
+    sources = [
+      "fuchsia.cpp",
+      "fuchsia.h",
+    ]
+    deps = [
+      "//garnet/lib/magma/src/libmagma",
+      "//garnet/lib/magma/src/magma_util",
+    ]
+  }
+}
diff --git a/src/os/fuchsia.cpp b/src/os/fuchsia.cpp
new file mode 100644
index 0000000..6a176c7
--- /dev/null
+++ b/src/os/fuchsia.cpp
@@ -0,0 +1,53 @@
+// Copyright 2019 The Fuchsia Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "fuchsia.h"
+
+#include <magma.h>
+#include <magma_util/dlog.h>
+#include <magma_util/macros.h>
+#include <pthread.h>
+#include <zircon/syscalls.h>
+
+static fuchsia_open_callback_t g_open_callback;
+static pthread_once_t g_initialize_flag = PTHREAD_ONCE_INIT;
+
+static void initialize()
+{
+   uint32_t client_handle;
+   if (!fuchsia_open("/svc/fuchsia.tracing.provider.Registry", &client_handle)) {
+      DLOG("Connecting to trace provider failed");
+      return;
+   }
+   magma_status_t status = magma_initialize_tracing(client_handle);
+   if (status != MAGMA_STATUS_OK) {
+      DLOG("Initializing tracing failed: %d", status);
+   }
+}
+
+void fuchsia_init(fuchsia_open_callback_t open_callback)
+{
+   g_open_callback = open_callback;
+
+   // Multiple loader instances may call this multiple times.
+   pthread_once(&g_initialize_flag, &initialize);
+}
+
+bool fuchsia_open(const char* name, zx_handle_t* channel_out)
+{
+   if (!g_open_callback)
+      return DRETF(false, "No open callback");
+
+   zx_handle_t client_handle, service_handle;
+   zx_status_t status = zx_channel_create(0, &client_handle, &service_handle);
+   if (status != ZX_OK)
+      return DRETF(false, "Channel create failed: %d", status);
+
+   int result = g_open_callback(name, service_handle);
+   if (result != 0)
+      return DRETF(false, "g_open_callback failed: %d", result);
+
+   *channel_out = client_handle;
+   return true;
+}
diff --git a/src/os/fuchsia.h b/src/os/fuchsia.h
new file mode 100644
index 0000000..7c22ab5
--- /dev/null
+++ b/src/os/fuchsia.h
@@ -0,0 +1,28 @@
+// Copyright 2019 The Fuchsia Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef _FUCHSIA_H_
+#define _FUCHSIA_H_
+
+#include <stdbool.h>
+#include <stdio.h>
+#include <zircon/types.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+typedef int (*fuchsia_open_callback_t)(const char* name, zx_handle_t handle);
+
+void fuchsia_init(fuchsia_open_callback_t open_callback);
+
+// Open a channel to the given namespace element, returns a channel handle
+// to the backing service.
+bool fuchsia_open(const char* name, zx_handle_t* channel_out);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif // _FUCHSIA_H_
diff --git a/src/util/BUILD.gn b/src/util/BUILD.gn
index 751a872..26a2f63 100644
--- a/src/util/BUILD.gn
+++ b/src/util/BUILD.gn
@@ -44,6 +44,7 @@
     "list.h",
     "macros.h",
     "mesa-sha1.h",
+    "os_dirent.h",
     "os_file.h",
     "os_time.h",
     "ralloc.h",
@@ -103,8 +104,17 @@
   ]
 
   if (current_os == "fuchsia") {
-    sources += [ "futex_fuchsia.cpp" ]
-    deps += [ "$magma_build_root/src/magma_util/platform:futex" ]
+    sources += [
+      "futex_fuchsia.cpp",
+      "os_dirent_fuchsia.cpp",
+    ]
+    deps += [
+      "$mesa_build_root/src/os",
+      "$magma_build_root/src/magma_util/platform:futex",
+      "$magma_build_root/src/magma_util:common",
+      "//zircon/public/lib/zx",
+      "//zircon/system/fidl/fuchsia-io:llcpp",
+    ]
   } else {
     sources += [ "anon_file.c" ]
   }
diff --git a/src/util/os_dirent.h b/src/util/os_dirent.h
new file mode 100644
index 0000000..48e18b5
--- /dev/null
+++ b/src/util/os_dirent.h
@@ -0,0 +1,31 @@
+// Copyright 2019 The Fuchsia Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef _OS_DIRENT_H_
+#define _OS_DIRENT_H_
+
+#include <sys/types.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+struct os_dirent {
+   ino_t d_ino;
+   char d_name[];
+};
+
+typedef struct os_dir os_dir_t;
+
+os_dir_t* os_opendir(const char* path);
+
+struct os_dirent* os_readdir(os_dir_t* dir);
+
+int os_closedir(os_dir_t* dir);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _OS_DIRENT_H_ */
diff --git a/src/util/os_dirent_fuchsia.cpp b/src/util/os_dirent_fuchsia.cpp
new file mode 100644
index 0000000..11efa39
--- /dev/null
+++ b/src/util/os_dirent_fuchsia.cpp
@@ -0,0 +1,128 @@
+// Copyright 2019 The Fuchsia Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "os_dirent.h"
+
+#include "list.h"
+#include "os/fuchsia.h"
+
+#include <fuchsia/io/llcpp/fidl.h>
+#include <lib/zx/channel.h>
+#include <magma_util/dlog.h>
+
+namespace fio = llcpp::fuchsia::io;
+
+// Offsets must match struct os_dir, static_asserts below.
+struct os_dirent_impl {
+   ino_t d_ino;
+   char d_name[NAME_MAX];
+};
+
+static_assert(offsetof(struct os_dirent_impl, d_ino) == offsetof(struct os_dirent, d_ino),
+              "d_ino offset mismatch");
+static_assert(offsetof(struct os_dirent_impl, d_name) == offsetof(struct os_dirent, d_name),
+              "d_ino offset mismatch");
+
+// From io.fidl:
+struct __attribute__((__packed__)) dirent {
+   uint64_t ino;
+   uint8_t size;
+   uint8_t type;
+   char name[0];
+};
+
+static zx_status_t readdir(const zx::channel& control_channel, void* buffer, size_t capacity,
+                           size_t* out_actual)
+{
+   // Explicitly allocating message buffers to avoid heap allocation.
+   fidl::Buffer<fio::Directory::ReadDirentsRequest> request_buffer;
+   fidl::Buffer<fio::Directory::ReadDirentsResponse> response_buffer;
+   auto result =
+       fio::Directory::Call::ReadDirents(zx::unowned_channel(control_channel),
+                                         request_buffer.view(), capacity, response_buffer.view());
+   if (result.status() != ZX_OK)
+      return result.status();
+
+   fio::Directory::ReadDirentsResponse* response = result.Unwrap();
+   if (response->s != ZX_OK)
+      return response->s;
+
+   const auto& dirents = response->dirents;
+   if (dirents.count() > capacity)
+      return ZX_ERR_IO;
+
+   memcpy(buffer, dirents.data(), dirents.count());
+   *out_actual = dirents.count();
+   return response->s;
+}
+
+struct os_dir {
+   os_dir() { buffer = new char[buffer_size()]; }
+   ~os_dir() { delete [] buffer; }
+
+   static size_t buffer_size() { return 4096; }
+
+   zx::channel control_channel;
+   struct os_dirent_impl entry;
+   char* buffer = nullptr;
+   size_t count = 0;
+   size_t index = 0;
+};
+
+os_dir_t* os_opendir(const char* path)
+{
+   zx::channel control_channel;
+   if (!fuchsia_open(path, control_channel.reset_and_get_address())) {
+      DLOG("fuchsia_open(%s) failed\n", path);
+      return nullptr;
+   }
+
+   auto dir = new os_dir();
+   dir->control_channel = std::move(control_channel);
+
+   return dir;
+}
+
+int os_closedir(os_dir_t* dir)
+{
+   // Assume param is valid.
+   delete dir;
+   return 0;
+}
+
+struct os_dirent* os_readdir(os_dir_t* dir)
+{
+   if (dir->index >= dir->count) {
+      size_t count;
+      zx_status_t status =
+          readdir(dir->control_channel, dir->buffer, os_dir::buffer_size(), &count);
+      if (status != ZX_OK) {
+         DLOG("os_readdir: readdir failed: %d\n", status);
+         return nullptr;
+      }
+
+      dir->count = count;
+      dir->index = 0;
+   }
+
+   if (dir->index + sizeof(dirent) > dir->count) {
+      DLOG("os_readdir: no more entries");
+      return nullptr;
+   }
+
+   dirent* entry = reinterpret_cast<dirent*>(&dir->buffer[dir->index]);
+   size_t entry_size = sizeof(dirent) + entry->size;
+
+   if (dir->index + entry_size > dir->count) {
+      DLOG("os_readdir: last entry incomplete (this shouldn't happen)");
+      return nullptr;
+   }
+   dir->index += entry_size;
+
+   dir->entry.d_ino = entry->ino;
+   strncpy(dir->entry.d_name, entry->name, entry->size);
+   dir->entry.d_name[entry->size] = 0;
+
+   return reinterpret_cast<os_dirent*>(&dir->entry);
+}
diff --git a/src/util/tests/os_dirent/BUILD.gn b/src/util/tests/os_dirent/BUILD.gn
new file mode 100644
index 0000000..28c4e5a
--- /dev/null
+++ b/src/util/tests/os_dirent/BUILD.gn
@@ -0,0 +1,24 @@
+# Copyright 2019 The Fuchsia Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+import("../../../../mesa.gni")
+
+mesa_source_set("os_dirent") {
+  testonly = true
+
+  sources = [
+    "os_dirent_test.cpp",
+  ]
+
+  deps = [ "$mesa_build_root/src/util" ]
+
+  if (is_fuchsia) {
+    deps += [
+      "//third_party/googletest:gtest",
+      "//zircon/public/lib/fdio",
+      "//zircon/public/lib/zx",
+      "//zircon/system/fidl/fuchsia-io",
+    ]
+  }
+}
diff --git a/src/util/tests/os_dirent/os_dirent_test.cpp b/src/util/tests/os_dirent/os_dirent_test.cpp
new file mode 100644
index 0000000..dbd267b
--- /dev/null
+++ b/src/util/tests/os_dirent/os_dirent_test.cpp
@@ -0,0 +1,97 @@
+// Copyright 2019 The Fuchsia Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "util/os_dirent.h"
+#include <gtest/gtest.h>
+
+constexpr int kFileCount = 1000;
+
+#ifdef __Fuchsia__
+#include "os/fuchsia.h"
+
+#include <fuchsia/io/llcpp/fidl.h>
+#include <lib/fdio/directory.h>
+#include <lib/zx/channel.h>
+
+static int fuchsia_open_callback(const char* name, zx_handle_t handle)
+{
+   return fdio_service_connect(name, handle);
+}
+
+struct String {
+   ~String() { Reset(); }
+
+   void Alloc(int size) { ptr = new char[size]; }
+
+   void Reset()
+   {
+      delete[] ptr;
+      ptr = nullptr;
+   }
+
+   char* ptr = nullptr;
+};
+
+static bool fuchsia_create_files(String files[kFileCount])
+{
+   for (uint32_t i = 0; i < kFileCount; i++) {
+      zx::channel channel0, channel1;
+      zx_status_t status = zx::channel::create(0, &channel0, &channel1);
+
+      status = fdio_open(files[i].ptr,
+                         ::llcpp::fuchsia::io::OPEN_FLAG_CREATE |
+                             ::llcpp::fuchsia::io::OPEN_RIGHT_WRITABLE,
+                         channel0.release());
+      if (status != ZX_OK) {
+         printf("fdio_open(%s) failed: %d\n", files[i].ptr, status);
+         return false;
+      }
+   }
+   return true;
+}
+#endif
+
+TEST(os_dirent, readdir)
+{
+   const char* path = "/tmp";
+   const int path_len = strlen(path);
+
+   String files[kFileCount];
+   for (uint32_t i = 0; i < kFileCount; i++) {
+      files[i].Alloc(path_len + 16);
+      sprintf(files[i].ptr, "%s/%u", path, i);
+   }
+
+#ifdef __Fuchsia__
+   EXPECT_TRUE(fuchsia_create_files(files));
+   fuchsia_init(fuchsia_open_callback);
+#endif
+
+   os_dir_t* dir = os_opendir(path);
+
+   while (struct os_dirent* entry = os_readdir(dir)) {
+      EXPECT_NE(0, entry->d_ino);
+
+      String found_path;
+      found_path.Alloc(path_len + strlen(entry->d_name) + 16);
+      sprintf(found_path.ptr, "%s/%s", path, entry->d_name);
+
+      for (uint32_t i = 0; i < kFileCount; i++) {
+         if (files[i].ptr && strcmp(files[i].ptr, found_path.ptr) == 0) {
+            files[i].Reset();
+            break;
+         }
+      }
+   }
+
+   for (uint32_t i = 0; i < kFileCount; i++) {
+      EXPECT_EQ(nullptr, files[i].ptr) << "Didn't find: " << files[i].ptr;
+   }
+
+   os_closedir(dir);
+
+#ifdef __Fuchsia__
+   // Don't bother removing tmp files
+#endif
+}
diff --git a/tests/BUILD.gn b/tests/BUILD.gn
index d76c52e..094439c 100644
--- a/tests/BUILD.gn
+++ b/tests/BUILD.gn
@@ -19,6 +19,8 @@
 # FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
 # IN THE SOFTWARE.
 
+import("../mesa.gni")
+
 group("tests") {
   testonly = true
 
@@ -36,6 +38,7 @@
 
   deps = [
     "unit_tests",
+    "$mesa_build_root/src/util/tests/os_dirent",
     "//third_party/googletest:gtest",
   ]
 }