[fdio] Add fdio_open, a variant of fdio_open_at

It uses fdio_root_ns with fdio_ns_connect, which traverses the root
namespace to then call fdio_open_at.

Test: Added a test to system/utest/memfs.
Change-Id: I5e08e69083a86227cf82efa2f69f86ea2c2f2a4b
diff --git a/system/ulib/fdio/include/lib/fdio/util.h b/system/ulib/fdio/include/lib/fdio/util.h
index eb68ee9..fd30748 100644
--- a/system/ulib/fdio/include/lib/fdio/util.h
+++ b/system/ulib/fdio/include/lib/fdio/util.h
@@ -94,7 +94,10 @@
 // an error is returned and the handle is closed.
 zx_status_t fdio_service_connect_at(zx_handle_t dir, const char* path, zx_handle_t h);
 
-// As above but allows the passing of flags
+// Same as |fdio_service_connect|, but allows the passing of flags.
+zx_status_t fdio_open(const char* path, uint32_t zxflags, zx_handle_t h);
+
+// Same as |fdio_service_connect_at, but allows the passing of flags.
 zx_status_t fdio_open_at(zx_handle_t dir, const char* path, uint32_t zxflags, zx_handle_t h);
 
 // Attempt to clone a service handle by doing a pipelined
diff --git a/system/ulib/fdio/remoteio.c b/system/ulib/fdio/remoteio.c
index 439443e..fbba602 100644
--- a/system/ulib/fdio/remoteio.c
+++ b/system/ulib/fdio/remoteio.c
@@ -270,6 +270,21 @@
 }
 
 __EXPORT
+zx_status_t fdio_open(const char* path, uint32_t flags, zx_handle_t h) {
+    if (path == NULL) {
+        zx_handle_close(h);
+        return ZX_ERR_INVALID_ARGS;
+    }
+    // Otherwise attempt to connect through the root namespace
+    if (fdio_root_ns != NULL) {
+        return fdio_ns_connect(fdio_root_ns, path, flags, h);
+    }
+    // Otherwise we fail
+    zx_handle_close(h);
+    return ZX_ERR_NOT_FOUND;
+}
+
+__EXPORT
 zx_status_t fdio_open_at(zx_handle_t dir, const char* path, uint32_t flags, zx_handle_t h) {
     if (path == NULL) {
         zx_handle_close(h);
diff --git a/system/utest/memfs/fidl-tests.cpp b/system/utest/memfs/fidl-tests.cpp
index 74426c7..1830bc2 100644
--- a/system/utest/memfs/fidl-tests.cpp
+++ b/system/utest/memfs/fidl-tests.cpp
@@ -36,19 +36,16 @@
     fbl::unique_fd fd(open("/fidltmp", O_DIRECTORY | O_RDONLY));
     ASSERT_GE(fd.get(), 0);
 
-    // Access files within the filesystem.
-    DIR* d = fdopendir(fd.release());
-
     // Create a file
     const char* filename = "file-a";
-    fd.reset(openat(dirfd(d), filename, O_CREAT | O_RDWR));
+    fd.reset(openat(fd.get(), filename, O_CREAT | O_RDWR));
     ASSERT_GE(fd.get(), 0);
     const char* data = "hello";
     ssize_t datalen = strlen(data);
     ASSERT_EQ(write(fd.get(), data, datalen), datalen);
     fd.reset();
 
-    zx_handle_t h, request = ZX_HANDLE_INVALID;
+    zx_handle_t h, request;
     ASSERT_EQ(zx_channel_create(0, &h, &request), ZX_OK);
     ASSERT_EQ(fdio_service_connect("/fidltmp/file-a", request), ZX_OK);
 
@@ -57,7 +54,40 @@
     ASSERT_EQ(info.tag, fuchsia_io_NodeInfoTag_file);
     ASSERT_EQ(info.file.event, ZX_HANDLE_INVALID);
     zx_handle_close(h);
-    closedir(d);
+
+    loop.Shutdown();
+
+    // No way to clean up the namespace entry. See ZX-2013 for more details.
+
+    END_TEST;
+}
+
+bool TestFidlOpenReadOnly() {
+    BEGIN_TEST;
+
+    async::Loop loop(&kAsyncLoopConfigNoAttachToThread);
+    ASSERT_EQ(loop.StartThread(), ZX_OK);
+
+    ASSERT_EQ(memfs_install_at(loop.dispatcher(), "/fidltmp-ro"), ZX_OK);
+    fbl::unique_fd fd(open("/fidltmp-ro", O_DIRECTORY | O_RDONLY));
+    ASSERT_GE(fd.get(), 0);
+
+    // Create a file
+    const char* filename = "file-ro";
+    fd.reset(openat(fd.get(), filename, O_CREAT | O_RDWR));
+    ASSERT_GE(fd.get(), 0);
+    fd.reset();
+
+    zx_handle_t h, request;
+    ASSERT_EQ(zx_channel_create(0, &h, &request), ZX_OK);
+    ASSERT_EQ(fdio_open("/fidltmp-ro/file-ro", ZX_FS_RIGHT_READABLE, request), ZX_OK);
+
+    zx_status_t status;
+    uint32_t flags;
+    ASSERT_EQ(fuchsia_io_FileGetFlags(h, &status, &flags), ZX_OK);
+    ASSERT_EQ(status, ZX_OK);
+    ASSERT_EQ(flags, ZX_FS_RIGHT_READABLE);
+    zx_handle_close(h);
 
     loop.Shutdown();
 
@@ -164,5 +194,6 @@
 
 BEGIN_TEST_CASE(fidl_tests)
 RUN_TEST(TestFidlBasic)
+RUN_TEST(TestFidlOpenReadOnly)
 RUN_TEST(TestFidlQueryFilesystem)
 END_TEST_CASE(fidl_tests)