[pseudo-fs] extract FileConnection from simple pseudofile

The file connection can be used in other implementations of pseudo
files, such as the asynchronous pseudo file. This splits the file module
into multiple submodules, one of which contains that connection
implementation.

Test: fuchsia-vfs-pseudo-fs tests, CQ
Change-Id: Icdd5179b1d359fb61eae2d4a3c1b5ba055628005
diff --git a/garnet/public/rust/fuchsia-vfs/fuchsia-vfs-pseudo-fs/src/directory/controlled.rs b/garnet/public/rust/fuchsia-vfs/fuchsia-vfs-pseudo-fs/src/directory/controlled.rs
index b09ac5e..680bed5 100644
--- a/garnet/public/rust/fuchsia-vfs/fuchsia-vfs-pseudo-fs/src/directory/controlled.rs
+++ b/garnet/public/rust/fuchsia-vfs/fuchsia-vfs-pseudo-fs/src/directory/controlled.rs
@@ -562,7 +562,7 @@
 
     use {
         crate::directory::{simple, test_utils},
-        crate::file::read_only,
+        crate::file::simple::read_only,
         fidl::endpoints::{create_proxy, ServerEnd},
         fidl_fuchsia_io::{
             DirectoryMarker, DirectoryObject, DirectoryProxy, FileEvent, FileMarker, FileObject,
diff --git a/garnet/public/rust/fuchsia-vfs/fuchsia-vfs-pseudo-fs/src/directory/simple.rs b/garnet/public/rust/fuchsia-vfs/fuchsia-vfs-pseudo-fs/src/directory/simple.rs
index c438749..3dcc652 100644
--- a/garnet/public/rust/fuchsia-vfs/fuchsia-vfs-pseudo-fs/src/directory/simple.rs
+++ b/garnet/public/rust/fuchsia-vfs/fuchsia-vfs-pseudo-fs/src/directory/simple.rs
@@ -682,7 +682,7 @@
             run_server_client, run_server_client_with_open_requests_channel,
             DirentsSameInodeBuilder,
         },
-        crate::file::{read_only, read_write, write_only},
+        crate::file::simple::{read_only, read_write, write_only},
         crate::test_utils::open_get_proxy,
         fidl::endpoints::create_proxy,
         fidl_fuchsia_io::{
diff --git a/garnet/public/rust/fuchsia-vfs/fuchsia-vfs-pseudo-fs/src/file/connection.rs b/garnet/public/rust/fuchsia-vfs/fuchsia-vfs-pseudo-fs/src/file/connection.rs
new file mode 100644
index 0000000..38a6cb3
--- /dev/null
+++ b/garnet/public/rust/fuchsia-vfs/fuchsia-vfs-pseudo-fs/src/file/connection.rs
@@ -0,0 +1,81 @@
+// 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.
+
+//! Implementation of an individual connection to a file.
+
+use {
+    fidl::encoding::OutOfLine,
+    fidl::endpoints::ServerEnd,
+    fidl_fuchsia_io::{
+        FileMarker, FileObject, FileRequestStream, NodeInfo, NodeMarker, OPEN_FLAG_DESCRIBE,
+    },
+    fuchsia_zircon::Status,
+    futures::{
+        stream::{Stream, StreamExt, StreamFuture},
+        task::Waker,
+        Poll,
+    },
+    std::pin::Pin,
+};
+
+/// FileConnection represents the buffered connection of a single client to a pseudo file. It
+/// implements Stream, which proxies file requests from the contained FileRequestStream.
+pub struct FileConnection {
+    requests: FileRequestStream,
+    /// Either the "flags" value passed into [`DirectoryEntry::open()`], or the "flags" value
+    /// passed into FileRequest::Clone().
+    pub flags: u32,
+    /// Seek position.  Next byte to be read or written within the buffer.  This might be beyond
+    /// the current size of buffer, matching POSIX:
+    ///
+    ///     http://pubs.opengroup.org/onlinepubs/9699919799/functions/lseek.html
+    ///
+    /// It will cause the buffern to be extended with zeroes (if necessary) when write() is called.
+    // While the content in the buffer vector uses usize for the size, it is easier to use u64 to
+    // match the FIDL bindings API.  Pseudo files are not expected to cross the 2^64 bytes size
+    // limit.  And all the code is much simpler when we just assume that usize is the same as u64.
+    // Should we need to port to a 128 bit platform, there are static assertions in the code that
+    // would fail.
+    pub seek: u64,
+    /// Per connection buffer.  See module documentation for details.
+    pub buffer: Vec<u8>,
+    /// Starts as false, and causes the [`on_write()`] to be called when the connection is closed
+    /// if set to true during the lifetime of the connection.
+    pub was_written: bool,
+}
+
+impl FileConnection {
+    /// Initialized a file connection, checking flags and sending an `OnOpen` event if necessary.
+    /// Returns a [`FileConnection`] object as a [`StreamFuture`], or in the case of an error, sends
+    /// an appropriate `OnOpen` event (if requested) and returns `Err`.
+    pub fn connect(
+        flags: u32,
+        server_end: ServerEnd<NodeMarker>,
+        buffer: Vec<u8>,
+        was_written: bool,
+    ) -> Result<StreamFuture<FileConnection>, fidl::Error> {
+        let (requests, control_handle) = ServerEnd::<FileMarker>::new(server_end.into_channel())
+            .into_stream_and_control_handle()?;
+
+        let conn = (FileConnection { requests, flags, seek: 0, buffer, was_written }).into_future();
+
+        if flags & OPEN_FLAG_DESCRIBE != 0 {
+            let mut info = NodeInfo::File(FileObject { event: None });
+            control_handle.send_on_open_(Status::OK.into_raw(), Some(OutOfLine(&mut info)))?;
+        }
+
+        Ok(conn)
+    }
+}
+
+/// Allow [`FileConnection`] to be wrapped in a [`StreamFuture`], to be further contained inside
+/// [`FuturesUnordered`].
+impl Stream for FileConnection {
+    // We are just proxying the FileRequestStream requests.
+    type Item = <FileRequestStream as Stream>::Item;
+
+    fn poll_next(mut self: Pin<&mut Self>, lw: &Waker) -> Poll<Option<Self::Item>> {
+        self.requests.poll_next_unpin(lw)
+    }
+}
diff --git a/garnet/public/rust/fuchsia-vfs/fuchsia-vfs-pseudo-fs/src/file/mod.rs b/garnet/public/rust/fuchsia-vfs/fuchsia-vfs-pseudo-fs/src/file/mod.rs
new file mode 100644
index 0000000..04d83ce
--- /dev/null
+++ b/garnet/public/rust/fuchsia-vfs/fuchsia-vfs-pseudo-fs/src/file/mod.rs
@@ -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.
+
+//! Module holding different kinds of pseudo files and their building blocks.
+
+use {
+    crate::directory::entry::DirectoryEntry,
+    libc::{S_IRUSR, S_IWUSR},
+};
+
+pub mod simple;
+
+/// A base trait for all the pseudo files.  Most clients will probably just use the DirectoryEntry
+/// trait to deal with the pseudo files uniformly.
+pub trait PseudoFile: DirectoryEntry {}
+
+/// POSIX emulation layer access attributes set by default for files created with read_only().
+pub const DEFAULT_READ_ONLY_PROTECTION_ATTRIBUTES: u32 = S_IRUSR;
+
+/// POSIX emulation layer access attributes set by default for files created with write_only().
+pub const DEFAULT_WRITE_ONLY_PROTECTION_ATTRIBUTES: u32 = S_IWUSR;
+
+/// POSIX emulation layer access attributes set by default for files created with read_write().
+pub const DEFAULT_READ_WRITE_PROTECTION_ATTRIBUTES: u32 =
+    DEFAULT_READ_ONLY_PROTECTION_ATTRIBUTES | DEFAULT_WRITE_ONLY_PROTECTION_ATTRIBUTES;
+
+mod connection;
diff --git a/garnet/public/rust/fuchsia-vfs/fuchsia-vfs-pseudo-fs/src/file.rs b/garnet/public/rust/fuchsia-vfs/fuchsia-vfs-pseudo-fs/src/file/simple.rs
similarity index 93%
rename from garnet/public/rust/fuchsia-vfs/fuchsia-vfs-pseudo-fs/src/file.rs
rename to garnet/public/rust/fuchsia-vfs/fuchsia-vfs-pseudo-fs/src/file/simple.rs
index 0949282..4ac221f 100644
--- a/garnet/public/rust/fuchsia-vfs/fuchsia-vfs-pseudo-fs/src/file.rs
+++ b/garnet/public/rust/fuchsia-vfs/fuchsia-vfs-pseudo-fs/src/file/simple.rs
@@ -1,4 +1,4 @@
-// Copyright 2018 The Fuchsia Authors. All rights reserved.
+// 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.
 
@@ -35,15 +35,17 @@
 use {
     crate::common::send_on_open_with_error,
     crate::directory::entry::{DirectoryEntry, EntryInfo},
+    crate::file::{
+        connection::FileConnection, PseudoFile, DEFAULT_READ_ONLY_PROTECTION_ATTRIBUTES,
+        DEFAULT_READ_WRITE_PROTECTION_ATTRIBUTES, DEFAULT_WRITE_ONLY_PROTECTION_ATTRIBUTES,
+    },
     failure::Error,
-    fidl::encoding::OutOfLine,
     fidl::endpoints::ServerEnd,
     fidl_fuchsia_io::{
-        FileMarker, FileObject, FileRequest, FileRequestStream, NodeAttributes, NodeInfo,
-        NodeMarker, SeekOrigin, DIRENT_TYPE_FILE, INO_UNKNOWN, MODE_PROTECTION_MASK,
-        MODE_TYPE_DIRECTORY, MODE_TYPE_FILE, OPEN_FLAG_APPEND, OPEN_FLAG_DESCRIBE,
-        OPEN_FLAG_DIRECTORY, OPEN_FLAG_NODE_REFERENCE, OPEN_FLAG_TRUNCATE, OPEN_RIGHT_READABLE,
-        OPEN_RIGHT_WRITABLE,
+        FileObject, FileRequest, NodeAttributes, NodeInfo, NodeMarker, SeekOrigin,
+        DIRENT_TYPE_FILE, INO_UNKNOWN, MODE_PROTECTION_MASK, MODE_TYPE_DIRECTORY, MODE_TYPE_FILE,
+        OPEN_FLAG_APPEND, OPEN_FLAG_DESCRIBE, OPEN_FLAG_DIRECTORY, OPEN_FLAG_NODE_REFERENCE,
+        OPEN_FLAG_TRUNCATE, OPEN_RIGHT_READABLE, OPEN_RIGHT_WRITABLE,
     },
     fuchsia_zircon::{
         sys::{ZX_ERR_NOT_SUPPORTED, ZX_OK},
@@ -51,26 +53,18 @@
     },
     futures::{
         future::FusedFuture,
-        stream::{FuturesUnordered, Stream, StreamExt, StreamFuture},
+        stream::{FuturesUnordered, StreamExt, StreamFuture},
         task::Waker,
         Future, Poll,
     },
-    libc::{S_IRUSR, S_IWUSR},
     std::{io::Write, iter, iter::ExactSizeIterator, marker::Unpin, mem, pin::Pin},
     void::Void,
 };
 
-/// A base trait for all the pseudo files.  Most clients will probably just use the DirectoryEntry
-/// trait to deal with the pseudo files uniformly.
-pub trait PseudoFile: DirectoryEntry {}
-
 // TODO: When trait aliases are implemented (rust-lang/rfcs#1733)
 // trait OnReadHandler = FnMut() -> Result<Vec<u8>, Status>;
 // trait OnWriteHandler = FnMut(Vec<u8>) -> Result<(), Status>;
 
-/// POSIX emulation layer access attributes set by default for files created with read_only().
-pub const DEFAULT_READ_ONLY_PROTECTION_ATTRIBUTES: u32 = S_IRUSR;
-
 /// Creates a new read-only `PseudoFile` backed by the specified read handler.
 ///
 /// The handler is called every time a read operation is performed on the file.  It is only allowed
@@ -113,9 +107,6 @@
     )
 }
 
-/// POSIX emulation layer access attributes set by default for files created with write_only().
-pub const DEFAULT_WRITE_ONLY_PROTECTION_ATTRIBUTES: u32 = S_IWUSR;
-
 /// Creates a new write-only `PseudoFile` backed by the specified write handler.
 ///
 /// The handler is called every time a write operation is performed on the file.  It is only
@@ -166,10 +157,6 @@
     )
 }
 
-/// POSIX emulation layer access attributes set by default for files created with read_write().
-pub const DEFAULT_READ_WRITE_PROTECTION_ATTRIBUTES: u32 =
-    DEFAULT_READ_ONLY_PROTECTION_ATTRIBUTES | DEFAULT_WRITE_ONLY_PROTECTION_ATTRIBUTES;
-
 /// Creates new `PseudoFile` backed by the specified read and write handlers.
 ///
 /// The read handler is called every time a read operation is performed on the file.  It is only
@@ -239,54 +226,6 @@
     )
 }
 
-struct FileConnection {
-    requests: FileRequestStream,
-    /// Either the "flags" value passed into [`DirectoryEntry::open()`], or the "flags" value
-    /// passed into FileRequest::Clone().
-    flags: u32,
-    /// Seek position.  Next byte to be read or written within the buffer.  This might be beyond
-    /// the current size of buffer, matching POSIX:
-    ///
-    ///     http://pubs.opengroup.org/onlinepubs/9699919799/functions/lseek.html
-    ///
-    /// It will cause the buffern to be extended with zeroes (if necessary) when write() is called.
-    // While the content in the buffer vector uses usize for the size, it is easier to use u64 to
-    // match the FIDL bindings API.  Pseudo files are not expected to cross the 2^64 bytes size
-    // limit.  And all the code is much simpler when we just assume that usize is the same as u64.
-    // Should we need to port to a 128 bit platform, there are static assertions in the code that
-    // would fail.
-    seek: u64,
-    /// Per connection buffer.  See module documentation for details.
-    buffer: Vec<u8>,
-    /// Starts as false, and causes the [`on_write()`] to be called when the connection is closed
-    /// if set to true during the lifetime of the connection.
-    was_written: bool,
-}
-
-impl FileConnection {
-    /// Creates a new [`FileConnection`] instance, immediately wrapping it in a [`StreamFuture`].
-    /// This is how [`FileConnection`]s are used in the pseudo file implementation.
-    fn as_stream_future(
-        requests: FileRequestStream,
-        flags: u32,
-        buffer: Vec<u8>,
-        was_written: bool,
-    ) -> StreamFuture<FileConnection> {
-        (FileConnection { requests, flags, seek: 0, buffer, was_written }).into_future()
-    }
-}
-
-/// Allow [`FileConnection`] to be wrapped in a [`StreamFuture`], to be further contained inside
-/// [`FuturesUnordered`].
-impl Stream for FileConnection {
-    // We are just proxying the FileRequestStream requests.
-    type Item = <FileRequestStream as Stream>::Item;
-
-    fn poll_next(mut self: Pin<&mut Self>, lw: &Waker) -> Poll<Option<Self::Item>> {
-        self.requests.poll_next_unpin(lw)
-    }
-}
-
 struct PseudoFileImpl<OnRead, OnWrite>
 where
     OnRead: FnMut() -> Result<Vec<u8>, Status> + Send,
@@ -424,20 +363,9 @@
 
         match self.init_buffer(flags) {
             Ok((buffer, was_written)) => {
-                let (request_stream, control_handle) =
-                    ServerEnd::<FileMarker>::new(server_end.into_channel())
-                        .into_stream_and_control_handle()?;
-
-                let conn =
-                    FileConnection::as_stream_future(request_stream, flags, buffer, was_written);
+                let conn = FileConnection::connect(flags, server_end, buffer, was_written)?;
                 self.connections.push(conn);
 
-                if flags & OPEN_FLAG_DESCRIBE != 0 {
-                    let mut info = NodeInfo::File(FileObject { event: None });
-                    control_handle
-                        .send_on_open_(Status::OK.into_raw(), Some(OutOfLine(&mut info)))?;
-                }
-
                 Ok(())
             }
             Err(status) => send_on_open_with_error(flags, server_end, status),
@@ -794,11 +722,10 @@
     OnWrite: FnMut(Vec<u8>) -> Result<(), Status> + Send,
 {
     fn is_terminated(&self) -> bool {
-        // The `PseudoFileImpl` never completes, but once there are no
-        // more connections, it is blocked until more connections are
-        // added. If the object currently polling a `PseudoFile` with
-        // an empty set of connections is blocked on the `PseudoFile`
-        // completing, it will never terminate.
+        // The `PseudoFileImpl` never completes, but once there are no more connections, it is
+        // blocked until more connections are added. If the object currently polling a `PseudoFile`
+        // with an empty set of connections is blocked on the `PseudoFile` completing, it will never
+        // terminate.
         self.connections.len() == 0
     }
 }