WIP get to main

Change-Id: I41b89a83be8d5e0dd93a3f43b6dc63dd26726292
diff --git a/garnet/lib/rust/io_util/src/directory.rs b/garnet/lib/rust/io_util/src/directory.rs
index ab1c137..3a9a923 100644
--- a/garnet/lib/rust/io_util/src/directory.rs
+++ b/garnet/lib/rust/io_util/src/directory.rs
@@ -137,6 +137,27 @@
     node::verify_file_describe_event(file).await
 }
 
+/// Opens the given `path` from the given `parent` directory as a [`NodeProxy`], verifying that the
+/// target implements the fuchsia.io.Node protocol.
+pub async fn open_node(
+    parent: &DirectoryProxy,
+    path: &str,
+    flags: u32,
+    mode: u32,
+) -> Result<NodeProxy, OpenError> {
+    let (file, server_end) =
+        fidl::endpoints::create_proxy::<NodeMarker>().map_err(OpenError::CreateProxy)?;
+
+    let flags = flags | fidl_fuchsia_io::OPEN_FLAG_DESCRIBE;
+
+    parent
+        .open(flags, mode, path, ServerEnd::new(server_end.into_channel()))
+        .map_err(OpenError::SendOpenRequest)?;
+
+    // wait for the file to open and report success.
+    node::verify_node_describe_event(file).await
+}
+
 /// Opens the given `path` from the given `parent` directory as a [`NodeProxy`]. The target is not
 /// verified to be any particular type and may not implement the fuchsia.io.Node protocol.
 pub fn open_node_no_describe(
diff --git a/garnet/lib/rust/io_util/src/node.rs b/garnet/lib/rust/io_util/src/node.rs
index b283542..4f22198 100644
--- a/garnet/lib/rust/io_util/src/node.rs
+++ b/garnet/lib/rust/io_util/src/node.rs
@@ -7,7 +7,7 @@
 use {
     fidl_fuchsia_io::{
         DirectoryEvent, DirectoryObject, DirectoryProxy, FileEvent, FileObject, FileProxy,
-        NodeInfo, NodeProxy, Vmofile,
+        NodeEvent, NodeInfo, NodeProxy, Vmofile,
     },
     fuchsia_zircon_status as zx_status,
     futures::prelude::*,
@@ -147,6 +147,25 @@
     zx_status::Status::ok(status).map_err(CloseError::CloseError)
 }
 
+/// Consume the first event from this NodeProxy's event stream, returning the proxy if it is
+/// the expected type or an error otherwise.
+pub(crate) async fn verify_node_describe_event(
+    node: NodeProxy,
+) -> Result<NodeProxy, OpenError> {
+    let mut events = node.take_event_stream();
+    let NodeEvent::OnOpen_ { s: status, info } = events
+        .next()
+        .await
+        .ok_or(OpenError::OnOpenEventStreamClosed)?
+        .map_err(OpenError::OnOpenDecode)?;
+
+    let () = zx_status::Status::ok(status).map_err(OpenError::OpenError)?;
+
+    info.ok_or(OpenError::MissingOnOpenInfo)?;
+
+    Ok(node)
+}
+
 /// Consume the first event from this DirectoryProxy's event stream, returning the proxy if it is
 /// the expected type or an error otherwise.
 pub(crate) async fn verify_directory_describe_event(
diff --git a/src/proc/bin/starnix/BUILD.gn b/src/proc/bin/starnix/BUILD.gn
index 6ef88c4..5de6df85 100644
--- a/src/proc/bin/starnix/BUILD.gn
+++ b/src/proc/bin/starnix/BUILD.gn
@@ -4,6 +4,7 @@
 
 import("//build/rust/rustc_binary.gni")
 import("//build/rust/rustc_test.gni")
+import("//build/rust/rustc_macro.gni")
 import("//src/sys/build/components.gni")
 
 group("starnix") {
@@ -22,10 +23,12 @@
 
     source_root = "main.rs"
     deps = [
+      ":starnix_macros",
       "//garnet/lib/rust/io_util",
       "//sdk/fidl/fuchsia.component.runner:fuchsia.component.runner-rustc",
       "//sdk/fidl/fuchsia.io:fuchsia.io-rustc",
       "//sdk/fidl/fuchsia.ldsvc:fuchsia.ldsvc-rustc",
+      "//sdk/fidl/fuchsia.kernel:fuchsia.kernel-rustc",
       "//src/lib/fdio/rust:fdio",
       "//src/lib/fidl/rust/fidl",
       "//src/lib/fuchsia-async",
@@ -39,12 +42,17 @@
       "//src/sys/lib/runner",
       "//third_party/rust_crates:anyhow",
       "//third_party/rust_crates:futures",
+      "//third_party/rust_crates:lazy_static",
       "//third_party/rust_crates:log",
+      "//third_party/rust_crates:memchr",
       "//third_party/rust_crates:parking_lot",
     ]
 
     sources = [
       "executive.rs",
+      "fs/fd.rs",
+      "fs/fidl_file.rs",
+      "fs/mod.rs",
       "loader.rs",
       "main.rs",
       "syscall_table.rs",
@@ -65,6 +73,18 @@
   }
 }
 
+rustc_macro("starnix_macros") {
+  deps = [
+    "//third_party/rust_crates:proc-macro2",
+    "//third_party/rust_crates:quote",
+    "//third_party/rust_crates:syn",
+  ]
+  source_root = "macro.rs"
+  sources = [
+    "macro.rs",
+  ]
+}
+
 resource("hello_starnix_bin") {
   # Switch these two |sources| declarations to test locally.
   sources = [ "fixtures/hello_starnix.c" ]
diff --git a/src/proc/bin/starnix/executive.rs b/src/proc/bin/starnix/executive.rs
index d3c7402..8aa98fc 100644
--- a/src/proc/bin/starnix/executive.rs
+++ b/src/proc/bin/starnix/executive.rs
@@ -2,14 +2,13 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-use {
-    fuchsia_async as fasync,
-    fuchsia_zircon::{self as zx, sys::zx_thread_state_general_regs_t, HandleBased, Status},
-    parking_lot::RwLock,
-    std::sync::Arc,
-};
+use fidl_fuchsia_io as fio;
+use fuchsia_zircon::{self as zx, sys::zx_thread_state_general_regs_t, HandleBased, Status};
+use parking_lot::RwLock;
+use std::sync::Arc;
 
 use crate::types::*;
+use crate::fs::FdTable;
 
 pub struct ProgramBreak {
     vmar: zx::Vmar,
@@ -124,11 +123,18 @@
     pub egid: uid_t,
 }
 
+// TODO(tbodt): merge ProcessContext and ThreadContext into a single struct corresponding to struct
+// task_struct in Linux
+
 pub struct ProcessContext {
     pub handle: zx::Process,
-    pub exceptions: fasync::Channel,
+    pub exceptions: zx::Channel,
     pub security: SecurityContext,
     pub mm: MemoryManager,
+    // TODO: Replace with a real VFS. This can't last long.
+    pub root: fio::DirectoryProxy,
+    /// Corresponds to struct task_struct->files in Linux.
+    pub fd_table: FdTable,
 }
 
 impl ProcessContext {
@@ -140,6 +146,13 @@
         Ok(())
     }
 
+    pub fn read_c_string<'a>(&self, addr: UserAddress, buffer: &'a mut [u8]) -> Result<&'a [u8], Errno> {
+        let actual = self.handle.read_memory(addr.ptr(), buffer).map_err(|_| EFAULT)?;
+        let buffer = &mut buffer[..actual];
+        let null_index = memchr::memchr(b'\0', buffer).ok_or(ENAMETOOLONG)?;
+        Ok(&buffer[..null_index])
+    }
+
     pub fn write_memory(&self, addr: UserAddress, bytes: &[u8]) -> Result<(), Errno> {
         let actual = self.handle.write_memory(addr.ptr(), bytes).map_err(|_| EFAULT)?;
         if actual != bytes.len() {
diff --git a/src/proc/bin/starnix/fs/fd.rs b/src/proc/bin/starnix/fs/fd.rs
new file mode 100644
index 0000000..52c539a
--- /dev/null
+++ b/src/proc/bin/starnix/fs/fd.rs
@@ -0,0 +1,59 @@
+// Copyright 2021 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.
+
+use fuchsia_zircon as zx;
+use std::collections::HashMap;
+use std::sync::Arc;
+use std::ops::Deref;
+use parking_lot::RwLock;
+pub use starnix_macros::FileDesc;
+
+use crate::types::*;
+use crate::ThreadContext;
+
+pub type FdNumber = i32;
+
+/// Corresponds to struct file_operations in Linux, plus any filesystem-specific data.
+pub trait FileDesc: Deref<Target=FileCommon> {
+    fn write(&self, ctx: &ThreadContext, data: &[iovec]) -> Result<usize, Errno>;
+    fn read(&self, ctx: &ThreadContext, offset: &mut usize, data: &[iovec]) -> Result<usize, Errno>;
+    fn mmap(&self, _ctx: &ThreadContext, _prot: zx::VmarFlags, _flags: i32, _offset: usize) -> Result<(zx::Vmo, usize), Errno> {
+        Err(ENODEV)
+    }
+    // TODO(tbodt): This is actually an inode operation, and is only here because we don't have
+    // such a thing yet. Will need to be moved.
+    fn fstat(&self, ctx: &ThreadContext) -> Result<stat_t, Errno>;
+}
+
+/// Corresponds to struct file in Linux.
+#[derive(Default)]
+pub struct FileCommon {
+}
+
+pub type FdHandle = Arc<dyn FileDesc>;
+
+pub struct FdTable {
+    table: RwLock<HashMap<FdNumber, FdHandle>>,
+}
+
+impl FdTable {
+    pub fn new() -> FdTable {
+        FdTable { table: RwLock::new(HashMap::new()) }
+    }
+
+    pub fn install_fd(&self, fd: FdHandle) -> Result<FdNumber, Errno> {
+        let mut table = self.table.write();
+        let mut n = 0;
+        while table.contains_key(&n) {
+            n += 1;
+        }
+        table.insert(n, fd);
+        Ok(n)
+    }
+
+    pub fn get(&self, n: FdNumber) -> Result<FdHandle, Errno> {
+        let table = self.table.read();
+        table.get(&n).map(|handle| handle.clone()).ok_or_else(|| EBADF)
+    }
+}
diff --git a/src/proc/bin/starnix/fs/fidl_file.rs b/src/proc/bin/starnix/fs/fidl_file.rs
new file mode 100644
index 0000000..cc94549
--- /dev/null
+++ b/src/proc/bin/starnix/fs/fidl_file.rs
@@ -0,0 +1,137 @@
+// Copyright 2021 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.
+
+use fidl::endpoints::Proxy;
+use fidl_fuchsia_io as fio;
+use fidl_fuchsia_kernel as fkernel;
+use fuchsia_zircon as zx;
+use fuchsia_component::client::connect_to_service;
+use parking_lot::Mutex;
+use lazy_static::lazy_static;
+
+use crate::types::*;
+use crate::ThreadContext;
+use super::*;
+
+lazy_static! {
+    static ref VMEX_RESOURCE: zx::Resource = {
+        let service = connect_to_service::<fkernel::VmexResourceMarker>().expect("couldn't connect to fuchsia.kernel.VmexResource");
+        let mut service = fkernel::VmexResourceSynchronousProxy::new(service.into_channel().unwrap().into_zx_channel());
+        service.get(zx::Time::INFINITE).expect("couldn't talk to fuchsia.kernel.VmexResource")
+    };
+}
+
+#[derive(FileDesc)]
+pub struct FidlFile {
+    common: FileCommon,
+
+    // TODO(tbodt): whyyyyyy is this a mutex... whyyyy does fidl::client::sync::Proxy require
+    // mutability
+    node: Mutex<FidlNode>,
+}
+
+enum FidlNode {
+    File(fio::FileSynchronousProxy),
+    Directory(fio::DirectorySynchronousProxy),
+    Other(fio::NodeSynchronousProxy),
+}
+
+impl FidlNode {
+    fn get_attr(&mut self) -> Result<(i32, fio::NodeAttributes), fidl::Error> {
+        match self {
+            FidlNode::File(n) => n.get_attr(zx::Time::INFINITE),
+            FidlNode::Directory(n) => n.get_attr(zx::Time::INFINITE),
+            FidlNode::Other(n) => n.get_attr(zx::Time::INFINITE),
+        }
+    }
+}
+
+impl FidlFile {
+    pub fn from_node(node: fio::NodeProxy) -> Result<FdHandle, Errno> {
+        let mut node = fio::NodeSynchronousProxy::new(node.into_channel().unwrap().into_zx_channel());
+        let node = match node.describe(zx::Time::INFINITE).map_err(|_| EIO)? {
+            fio::NodeInfo::Directory(_) => FidlNode::Directory(fio::DirectorySynchronousProxy::new(node.into_channel())),
+            fio::NodeInfo::File(_) => FidlNode::File(fio::FileSynchronousProxy::new(node.into_channel())),
+            _ => FidlNode::Other(node),
+        };
+        Ok(Arc::new(FidlFile { common: FileCommon::default(), node: Mutex::new(node) }))
+    }
+}
+
+impl FileDesc for FidlFile {
+    fn write(&self, _ctx: &ThreadContext, _data: &[iovec]) -> Result<usize, Errno> {
+        Err(ENOSYS)
+    }
+    fn read(&self, ctx: &ThreadContext, offset: &mut usize, buf: &[iovec]) -> Result<usize, Errno> {
+        let mut total = 0;
+        for vec in buf {
+            total += vec.iov_len;
+        }
+        let (status, data) = match *self.node.lock() {
+            FidlNode::File(ref mut n) => n.read_at(total as u64, *offset as u64, zx::Time::INFINITE).map_err(|_| EIO),
+            FidlNode::Directory(_) => Err(EISDIR),
+            FidlNode::Other(_) => Err(EINVAL),
+        }?;
+        zx::Status::ok(status).map_err(|s| match s {
+            // TODO
+            _ => EIO,
+        })?;
+        let mut offset = 0;
+        for vec in buf {
+            ctx.process.write_memory(vec.iov_base, &data[offset..offset+vec.iov_len])?;
+            offset += vec.iov_len;
+        }
+        Ok(total)
+    }
+
+    fn mmap(&self, _ctx: &ThreadContext, mut prot: zx::VmarFlags, _flags: i32, offset: usize) -> Result<(zx::Vmo, usize), Errno> {
+        let has_execute = prot.contains(zx::VmarFlags::PERM_EXECUTE);
+        prot -= zx::VmarFlags::PERM_EXECUTE;
+
+        let (status, buffer) = match *self.node.lock() {
+            FidlNode::File(ref mut n) => n.get_buffer(prot.bits(), zx::Time::INFINITE).map_err(|e| {
+                info!("get_attr fidl error: {:?}", e);
+                EIO
+            }),
+            _ => Err(ENODEV),
+        }?;
+        zx::Status::ok(status).map_err(|s| match s {
+            // TODO
+            _ => {
+                info!("get_buffer error: {:?}", s);
+                EIO
+            }
+        })?;
+        let mut vmo = buffer.unwrap().vmo;
+        if has_execute {
+            vmo = vmo.replace_as_executable().unwrap();
+        }
+        Ok((vmo, offset))
+    }
+
+    fn fstat(&self, ctx: &ThreadContext) -> Result<stat_t, Errno> {
+        // TODO: log FIDL error
+        let (status, attrs) = self.node.lock().get_attr().map_err(|e| {
+            info!("get_attr fidl error: {:?}", e);
+            EIO
+        })?;
+        zx::Status::ok(status).map_err(|s| match s {
+            // TODO
+            _ => {
+                info!("get_attr error: {:?}", s);
+                EIO
+            }
+        })?;
+        Ok(stat_t {
+            st_mode: attrs.mode,
+            st_ino: attrs.id,
+            st_size: attrs.content_size as i64,
+            st_blocks: attrs.storage_size as i64 / 512,
+            st_uid: ctx.process.security.uid,
+            st_gid: ctx.process.security.gid,
+            st_nlink: attrs.link_count,
+            ..stat_t::default()
+        })
+    }
+}
diff --git a/src/proc/bin/starnix/fs/mod.rs b/src/proc/bin/starnix/fs/mod.rs
new file mode 100644
index 0000000..9e7d382
--- /dev/null
+++ b/src/proc/bin/starnix/fs/mod.rs
@@ -0,0 +1,57 @@
+// Copyright 2021 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.
+
+mod fd;
+mod fidl_file;
+pub use fd::*;
+pub use fidl_file::*;
+
+use std::sync::Arc;
+use log::info;
+
+use crate::types::*;
+use crate::ThreadContext;
+
+// misc
+
+#[derive(FileDesc)]
+pub struct StdioFile {
+    common: FileCommon,
+}
+
+impl StdioFile {
+    pub fn new() -> FdHandle {
+        Arc::new(StdioFile { common: FileCommon::default() })
+    }
+}
+
+impl FileDesc for StdioFile {
+    fn write(&self, ctx: &ThreadContext, data: &[iovec]) -> Result<usize, Errno> {
+        let mut size = 0;
+        for vec in data {
+            let mut local = vec![0; vec.iov_len];
+            ctx.process.read_memory(vec.iov_base, &mut local)?;
+            info!(target: "stdio", "{}", String::from_utf8_lossy(&local));
+            size += vec.iov_len;
+        }
+        Ok(size)
+    }
+
+    fn read(&self, _ctx: &ThreadContext, _offset: &mut usize, _data: &[iovec]) -> Result<usize, Errno> {
+        Ok(0)
+    }
+
+    fn fstat(&self, ctx: &ThreadContext) -> Result<stat_t, Errno> {
+        Ok(stat_t {
+            st_dev: 0x16,
+            st_ino: 3,
+            st_nlink: 1,
+            st_mode: 0x2190,
+            st_uid: ctx.process.security.uid,
+            st_gid: ctx.process.security.gid,
+            st_rdev: 0x8800,
+            ..stat_t::default()
+        })
+    }
+}
diff --git a/src/proc/bin/starnix/loader.rs b/src/proc/bin/starnix/loader.rs
index f602469..364c7f1 100644
--- a/src/proc/bin/starnix/loader.rs
+++ b/src/proc/bin/starnix/loader.rs
@@ -3,15 +3,16 @@
 // found in the LICENSE file.
 
 use anyhow::{anyhow, Context, Error};
-use fidl::endpoints::ClientEnd;
+use fidl::endpoints::{ClientEnd};
+use fidl_fuchsia_io as fio;
 use fidl_fuchsia_ldsvc as fldsvc;
-use fuchsia_async as fasync;
 use fuchsia_zircon::{self as zx, AsHandleRef, Status, Task};
 use process_builder::{elf_load, elf_parse};
 use std::ffi::{CStr, CString};
 
 use crate::executive::*;
 use crate::types::*;
+use crate::fs::{FdTable, StdioFile};
 
 pub struct ProcessParameters {
     pub name: CString,
@@ -99,19 +100,24 @@
     // This randomizes the load address without loading into a sub-vmar and breaking mprotect.
     // This is different from how Linux actually lays out the address space. We might need to
     // rewrite it eventually.
-    let (temp_vmar, base) = vmar.allocate(0, elf_info.high - elf_info.low, zx::VmarFlags::empty()).context("Couldn't allocate temporary VMAR")?;
+    let (temp_vmar, base) = vmar
+        .allocate(0, elf_info.high - elf_info.low, zx::VmarFlags::empty())
+        .context("Couldn't allocate temporary VMAR")?;
     unsafe { temp_vmar.destroy()? }; // Not unsafe, the vmar is not in the current process
     let bias = base.wrapping_sub(elf_info.low);
-    elf_load::map_elf_segments(&vmo, &headers, &vmar, vmar.info()?.base, bias).context("map_elf_segments failed")?;
+    elf_load::map_elf_segments(&vmo, &headers, &vmar, vmar.info()?.base, bias)
+        .context("map_elf_segments failed")?;
     Ok(LoadedElf { headers, base, bias })
 }
 
-// When it's time to implement execve, this should be changed to return an errno.
+// TODO(tbodt): change to return an errno when it's time to implement execve
+// TODO(tbodt): passing the root to this function doesn't make any sense
 pub async fn load_executable(
     job: &zx::Job,
     executable: zx::Vmo,
     loader_service: ClientEnd<fldsvc::LoaderMarker>,
     params: &ProcessParameters,
+    root: fio::DirectoryProxy,
 ) -> Result<ProcessContext, Error> {
     let loader_service = loader_service.into_proxy()?;
 
@@ -136,20 +142,30 @@
     let entry_elf = (&interp_elf).as_ref().unwrap_or(&main_elf);
     let entry = entry_elf.headers.file_header().entry.wrapping_add(entry_elf.bias);
 
-    let stack_size: usize = 0x4000;
+    let stack_size: usize = 0x5000;
     let stack_vmo = zx::Vmo::create(stack_size as u64)?;
     stack_vmo.set_name(CStr::from_bytes_with_nul(b"[stack]\0")?)?;
-    let stack_base = root_vmar.map(0, &stack_vmo, 0, stack_size, zx::VmarFlags::PERM_READ | zx::VmarFlags::PERM_WRITE).context("failed to map stack")?;
+    let stack_base = root_vmar
+        .map(0, &stack_vmo, 0, stack_size, zx::VmarFlags::PERM_READ | zx::VmarFlags::PERM_WRITE)
+        .context("failed to map stack")?;
     let stack = stack_base + stack_size - 8;
 
-    let exceptions = fasync::Channel::from_channel(process.create_exception_channel()?)?;
+    let exceptions = process.create_exception_channel()?;
     let process = ProcessContext {
         handle: process,
         exceptions,
         mm: MemoryManager::new(root_vmar),
         security: SecurityContext { uid: 3, gid: 3, euid: 3, egid: 3 },
+        root,
+        fd_table: FdTable::new(),
     };
 
+    // TODO(tbodt): this would fit better elsewhere
+    let stdio = StdioFile::new();
+    assert!(process.fd_table.install_fd(stdio.clone()).unwrap() == 0);
+    assert!(process.fd_table.install_fd(stdio.clone()).unwrap() == 1);
+    assert!(process.fd_table.install_fd(stdio).unwrap() == 2);
+
     let auxv = vec![
         (AT_UID, process.security.uid as u64),
         (AT_EUID, process.security.euid as u64),
@@ -158,6 +174,7 @@
         (AT_BASE, interp_elf.map_or(0, |interp| interp.base as u64)),
         (AT_PHDR, main_elf.bias.wrapping_add(main_elf.headers.file_header().phoff) as u64),
         (AT_PHNUM, main_elf.headers.file_header().phnum as u64),
+        (AT_ENTRY, main_elf.bias.wrapping_add(main_elf.headers.file_header().entry) as u64),
         (AT_SECURE, 0),
     ];
     let stack = populate_initial_stack(&stack_vmo, &params, auxv, stack_base, stack)?;
@@ -170,6 +187,7 @@
 
 #[cfg(test)]
 mod tests {
+    use fuchsia_async as fasync;
     use super::*;
 
     #[fasync::run_singlethreaded(test)]
diff --git a/src/proc/bin/starnix/macro.rs b/src/proc/bin/starnix/macro.rs
new file mode 100644
index 0000000..2ae3ca1
--- /dev/null
+++ b/src/proc/bin/starnix/macro.rs
@@ -0,0 +1,19 @@
+// Copyright 2021 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.
+
+use quote::quote;
+use syn::{parse_macro_input, DeriveInput};
+
+#[proc_macro_derive(FileDesc)]
+pub fn derive_file_desc(item: proc_macro::TokenStream) -> proc_macro::TokenStream {
+    let name = parse_macro_input!(item as DeriveInput).ident;
+    (quote! {
+        impl ::std::ops::Deref for #name {
+            type Target = crate::fs::FileCommon;
+            fn deref(&self) -> &Self::Target {
+                &self.common
+            }
+        }
+    }).into()
+}
diff --git a/src/proc/bin/starnix/main.rs b/src/proc/bin/starnix/main.rs
index 9d8564a..ad51de0 100644
--- a/src/proc/bin/starnix/main.rs
+++ b/src/proc/bin/starnix/main.rs
@@ -14,6 +14,7 @@
         self as zx, sys::zx_exception_info_t, sys::zx_thread_state_general_regs_t,
         sys::ZX_EXCEPTION_STATE_HANDLED, sys::ZX_EXCEPTION_STATE_TRY_NEXT,
         sys::ZX_EXCP_POLICY_CODE_BAD_SYSCALL,
+        AsHandleRef,
     },
     futures::{StreamExt, TryStreamExt},
     io_util::directory,
@@ -24,6 +25,7 @@
 };
 
 mod executive;
+mod fs;
 mod loader;
 mod syscall_table;
 mod syscalls;
@@ -42,10 +44,20 @@
     unsafe { mem::transmute(tmp) }
 }
 
-async fn run_process(process: Arc<ProcessContext>) -> Result<(), Error> {
+fn read_channel_sync(chan: &zx::Channel, buf: &mut zx::MessageBuf) -> Result<(), zx::Status> {
+    let res = chan.read(buf);
+    if let Err(zx::Status::SHOULD_WAIT) = res {
+        chan.wait_handle(zx::Signals::CHANNEL_READABLE | zx::Signals::CHANNEL_PEER_CLOSED, zx::Time::INFINITE)?;
+        chan.read(buf)
+    } else {
+        res
+    }
+}
+
+fn run_process(process: Arc<ProcessContext>) -> Result<(), Error> {
     let exceptions = &process.exceptions;
     let mut buffer = zx::MessageBuf::new();
-    while exceptions.recv_msg(&mut buffer).await.is_ok() {
+    while read_channel_sync(exceptions, &mut buffer).is_ok() {
         let info = as_exception_info(&buffer);
         assert!(buffer.n_handles() == 1);
         let exception = zx::Exception::from(buffer.take_handle(0).unwrap());
@@ -149,8 +161,13 @@
         environ: vec![],
     };
 
-    run_process(Arc::new(load_executable(&job, executable_vmo, ldsvc_client, &params).await?))
-        .await?;
+    std::thread::spawn(move || {
+        let err = (|| -> Result<(), Error> {
+            let proc = block_on(load_executable(&job, executable_vmo, ldsvc_client, &params, root_proxy))?;
+            run_process(Arc::new(proc))
+        })();
+        err.unwrap();
+    });
     Ok(())
 }
 
@@ -188,3 +205,8 @@
     fs.collect::<()>().await;
     Ok(())
 }
+
+pub fn block_on<F: std::future::Future>(fut: F) -> F::Output {
+    info!("awen lili");
+    fuchsia_async::Executor::new().unwrap().run_singlethreaded(fut)
+}
diff --git a/src/proc/bin/starnix/syscall_table.rs b/src/proc/bin/starnix/syscall_table.rs
index 9c1c0b2..a779045 100644
--- a/src/proc/bin/starnix/syscall_table.rs
+++ b/src/proc/bin/starnix/syscall_table.rs
@@ -19,6 +19,11 @@
         arg as usize
     }
 }
+impl FromSyscallArg for u64 {
+    fn from_arg(arg: u64) -> u64 {
+        arg
+    }
+}
 impl FromSyscallArg for UserAddress {
     fn from_arg(arg: u64) -> UserAddress {
         UserAddress::from(arg)
@@ -40,10 +45,10 @@
 macro_rules! syscall_match {
     {
         $ctx:ident; $syscall_number:ident; $args:ident;
-        $($call:ident => $func:ident[$num_args:tt],)*
+        $($call:ident => $func:ident $arg_spec:tt,)*
     } => {
         match $syscall_number {
-            $($call => syscall_match!(@call $ctx; $args; $func[$num_args]),)*
+            $($call => syscall_match!(@call $ctx; $args; $func $arg_spec),)*
             _ => sys_unknown($ctx, $syscall_number),
         }
     };
@@ -69,6 +74,7 @@
         SYS_MMAP => sys_mmap[6],
         SYS_MPROTECT => sys_mprotect[3],
         SYS_BRK => sys_brk[1],
+        SYS_PREAD64 => sys_pread64[4],
         SYS_WRITEV => sys_writev[3],
         SYS_ACCESS => sys_access[2],
         SYS_GETPID => sys_getpid[0],
@@ -79,9 +85,11 @@
         SYS_GETGID => sys_getgid[0],
         SYS_GETEUID => sys_geteuid[0],
         SYS_GETEGID => sys_getegid[0],
+        SYS_FSTATFS => sys_fstatfs[2],
         SYS_SCHED_GETSCHEDULER => sys_sched_getscheduler[1],
         SYS_ARCH_PRCTL => sys_arch_prctl[2],
         SYS_EXIT_GROUP => sys_exit_group[1],
+        SYS_OPENAT => sys_openat[4],
         SYS_GETRANDOM => sys_getrandom[3],
     }
 }
diff --git a/src/proc/bin/starnix/syscalls.rs b/src/proc/bin/starnix/syscalls.rs
index 1cc6bff..0487d07 100644
--- a/src/proc/bin/starnix/syscalls.rs
+++ b/src/proc/bin/starnix/syscalls.rs
@@ -3,43 +3,38 @@
 // found in the LICENSE file.
 
 use fuchsia_zircon::{self as zx, AsHandleRef, Task};
-use log::info;
+use fidl_fuchsia_io as fio;
+use io_util::directory;
+use io_util::node::OpenError;
+use log::{info, warn};
 use std::convert::TryInto;
 use std::ffi::CStr;
 use zerocopy::AsBytes;
 
 use crate::executive::*;
+use crate::fs::*;
 use crate::types::*;
+use crate::block_on;
 
 pub fn sys_write(
     ctx: &ThreadContext,
-    _fd: i32,
+    fd: FdNumber,
     buffer: UserAddress,
     count: usize,
 ) -> Result<SyscallResult, Errno> {
-    let process = &ctx.process;
-    let mut local = vec![0; count];
-    process.read_memory(buffer, &mut local)?;
-    info!("write: {}", String::from_utf8_lossy(&local));
-    Ok(count.into())
+    let fd = ctx.process.fd_table.get(fd)?;
+    Ok(fd.write(ctx, &[iovec{iov_base: buffer, iov_len: count}])?.into())
 }
 
 pub fn sys_fstat(
     ctx: &ThreadContext,
-    _fd: i32,
+    fd: i32,
     buffer: UserAddress,
 ) -> Result<SyscallResult, Errno> {
-    let process = &ctx.process;
-    let mut result = stat_t::default();
-    result.st_dev = 0x16;
-    result.st_ino = 3;
-    result.st_nlink = 1;
-    result.st_mode = 0x2190;
-    result.st_uid = process.security.uid;
-    result.st_gid = process.security.gid;
-    result.st_rdev = 0x8800;
+    let fd = ctx.process.fd_table.get(fd)?;
+    let result = fd.fstat(ctx)?;
     let bytes = result.as_bytes();
-    process.write_memory(buffer, bytes)?;
+    ctx.process.write_memory(buffer, bytes)?;
     return Ok(SUCCESS);
 }
 
@@ -79,7 +74,7 @@
     if prot & !(PROT_READ | PROT_WRITE | PROT_EXEC) != 0 {
         return Err(EINVAL);
     }
-    if flags & !(MAP_PRIVATE | MAP_ANONYMOUS) != 0 {
+    if flags & !(MAP_PRIVATE | MAP_ANONYMOUS | MAP_FIXED | MAP_NORESERVE) != 0 {
         return Err(EINVAL);
     }
 
@@ -91,23 +86,53 @@
     if length == 0 {
         return Err(EINVAL);
     }
-    if fd != -1 {
+
+    // TODO(tbodt): should we consider MAP_NORESERVE?
+
+    if flags & MAP_ANONYMOUS != 0 && fd != -1 {
         return Err(EINVAL);
     }
 
     let mut zx_flags = mmap_prot_to_vm_opt(prot) | zx::VmarFlags::ALLOW_FAULTS;
     if addr.ptr() != 0 {
+        // TODO(tbodt): if no MAP_FIXED, retry on EINVAL
         zx_flags |= zx::VmarFlags::SPECIFIC;
     }
+    if flags & MAP_FIXED != 0 {
+        // SAFETY: this is stupid
+        zx_flags |= unsafe { zx::VmarFlags::from_bits_unchecked(zx::VmarFlagsExtended::SPECIFIC_OVERWRITE.bits()) };
+    }
 
-    let vmo = zx::Vmo::create(length as u64).map_err(|s| match s {
-        zx::Status::NO_MEMORY => ENOMEM,
-        _ => impossible_error(s),
-    })?;
-    vmo.set_name(CStr::from_bytes_with_nul(b"starnix-anon\0").unwrap())
-        .map_err(impossible_error)?;
+    let (vmo, vmo_offset) = if flags & MAP_ANONYMOUS != 0 {
+        let vmo = zx::Vmo::create(length as u64).map_err(|s| match s {
+            zx::Status::NO_MEMORY => ENOMEM,
+            _ => impossible_error(s),
+        })?;
+        vmo.set_name(CStr::from_bytes_with_nul(b"starnix-anon\0").unwrap())
+            .map_err(impossible_error)?;
+        (vmo, 0)
+    } else {
+        // TODO(tbodt): maximize protection flags so that mprotect works
+        let fd = ctx.process.fd_table.get(fd)?;
+        let zx_prot = mmap_prot_to_vm_opt(prot);
+        if flags & MAP_PRIVATE != 0 {
+            let (mut vmo, vmo_offset) = fd.mmap(ctx, zx_prot - zx::VmarFlags::PERM_WRITE, flags, offset)?;
+            let mut clone_flags = zx::VmoChildOptions::COPY_ON_WRITE;
+            if !zx_prot.contains(zx::VmarFlags::PERM_WRITE) {
+                clone_flags |= zx::VmoChildOptions::NO_WRITE;
+            }
+            vmo = vmo.create_child(clone_flags, 0, vmo.get_size().map_err(impossible_error)?).map_err(|s| match s {
+                _ => impossible_error(s),
+            })?;
+            (vmo, vmo_offset)
+        } else {
+            fd.mmap(ctx, zx_prot, flags, offset)?
+        }
+    };
 
-    let addr = ctx.process.mm.root_vmar.map(addr.ptr(), &vmo, 0, length, zx_flags).map_err(
+    let root_base = ctx.process.mm.root_vmar.info().unwrap().base;
+    let ptr = if addr.ptr() == 0 { 0 } else { addr.ptr() - root_base };
+    let addr = ctx.process.mm.root_vmar.map(ptr, &vmo, vmo_offset as u64, length, zx_flags).map_err(
         |s| match s {
             zx::Status::INVALID_ARGS => EINVAL,
             zx::Status::ACCESS_DENIED => EACCES, // or EPERM?
@@ -143,9 +168,21 @@
     Ok(ctx.process.mm.set_program_break(addr).map_err(Errno::from)?.into())
 }
 
+pub fn sys_pread64(
+    ctx: &ThreadContext,
+    fd: FdNumber, 
+    buf: UserAddress,
+    count: usize,
+    mut offset: usize,
+) -> Result<SyscallResult, Errno> {
+    let fd = ctx.process.fd_table.get(fd)?;
+    let bytes = fd.read(ctx, &mut offset, &[iovec{iov_base: buf, iov_len: count}])?;
+    Ok(bytes.into())
+}
+
 pub fn sys_writev(
     ctx: &ThreadContext,
-    fd: i32,
+    fd: FdNumber,
     iovec_addr: UserAddress,
     iovec_count: i32,
 ) -> Result<SyscallResult, Errno> {
@@ -157,18 +194,10 @@
     let mut iovecs: Vec<iovec> = Vec::new();
     iovecs.reserve(iovec_count); // TODO: try_reserve
     iovecs.resize(iovec_count, iovec::default());
+
     ctx.process.read_memory(iovec_addr, iovecs.as_mut_slice().as_bytes_mut())?;
-
-    info!("writev: fd={} iovec={:?}", fd, iovecs);
-
-    let mut count = 0;
-    for iovec in iovecs {
-        let mut data = vec![0; iovec.iov_len];
-        ctx.process.read_memory(iovec.iov_base, &mut data)?;
-        info!("writev: {}", String::from_utf8_lossy(&data));
-        count += data.len();
-    }
-    Ok(count.into())
+    let fd = ctx.process.fd_table.get(fd)?;
+    Ok(fd.write(ctx, &iovecs)?.into())
 }
 
 pub fn sys_access(
@@ -247,6 +276,20 @@
     Ok(ctx.process.security.egid.into())
 }
 
+pub fn sys_fstatfs(
+    ctx: &ThreadContext,
+    _fd: FdNumber,
+    buf_addr: UserAddress,
+) -> Result<SyscallResult, Errno> {
+    let result = statfs::default();
+    ctx.process.write_memory(buf_addr, result.as_bytes())?;
+    Ok(SUCCESS)
+}
+
+pub fn sys_sched_getscheduler(_ctx: &ThreadContext, _pid: i32) -> Result<SyscallResult, Errno> {
+    Ok(SCHED_OTHER.into())
+}
+
 pub fn sys_arch_prctl(
     ctx: &mut ThreadContext,
     code: i32,
@@ -264,10 +307,6 @@
     }
 }
 
-pub fn sys_sched_getscheduler(_ctx: &ThreadContext, _pid: i32) -> Result<SyscallResult, Errno> {
-    Ok(SCHED_OTHER.into())
-}
-
 pub fn sys_exit_group(ctx: &ThreadContext, error_code: i32) -> Result<SyscallResult, Errno> {
     info!("exit_group: error_code={}", error_code);
     // TODO: Set the error_code on the process.
@@ -275,6 +314,36 @@
     Ok(SUCCESS)
 }
 
+pub fn sys_openat(
+    ctx: &ThreadContext,
+    dir_fd: i32,
+    path_addr: UserAddress,
+    flags: i32,
+    mode: i32,
+) -> Result<SyscallResult, Errno> {
+    if dir_fd != AT_FDCWD {
+        return Err(EINVAL);
+    }
+    let mut buf = [0u8; PATH_MAX];
+    let path = ctx.process.read_c_string(path_addr, &mut buf)?;
+    info!("openat({}, {}, {:#x}, {:#o})", dir_fd, String::from_utf8_lossy(path), flags, mode);
+    if path[0] != b'/' {
+        warn!("non-absolute paths are unimplemented");
+        return Err(ENOENT);
+    }
+    let path = &path[1..];
+    // TODO(tbodt): Need to switch to filesystem APIs that do not require UTF-8
+    let path = std::str::from_utf8(path).expect("bad UTF-8 in filename");
+    let node = block_on(directory::open_node(&ctx.process.root, path, fio::OPEN_RIGHT_READABLE, 0)).map_err(|e| match e {
+        OpenError::OpenError(zx::Status::NOT_FOUND) => ENOENT,
+        _ => {
+            warn!("open failed: {:?}", e);
+            EIO
+        }
+    })?;
+    Ok(ctx.process.fd_table.install_fd(FidlFile::from_node(node)?)?.into())
+}
+
 pub fn sys_getrandom(
     ctx: &ThreadContext,
     buf_addr: UserAddress,
diff --git a/src/proc/bin/starnix/types.rs b/src/proc/bin/starnix/types.rs
index d1690fd..48c23b5 100644
--- a/src/proc/bin/starnix/types.rs
+++ b/src/proc/bin/starnix/types.rs
@@ -21,6 +21,7 @@
 pub type mode_t = u16;
 pub type off_t = i64;
 
+#[derive(Debug)]
 pub struct Errno(i32);
 
 impl Errno {
@@ -231,6 +232,7 @@
 pub const SYS_MMAP: syscall_number_t = 9;
 pub const SYS_MPROTECT: syscall_number_t = 10;
 pub const SYS_BRK: syscall_number_t = 12;
+pub const SYS_PREAD64: syscall_number_t = 17;
 pub const SYS_WRITEV: syscall_number_t = 20;
 pub const SYS_ACCESS: syscall_number_t = 21;
 pub const SYS_GETPID: syscall_number_t = 39;
@@ -241,9 +243,11 @@
 pub const SYS_GETGID: syscall_number_t = 104;
 pub const SYS_GETEUID: syscall_number_t = 107;
 pub const SYS_GETEGID: syscall_number_t = 108;
+pub const SYS_FSTATFS: syscall_number_t = 138;
 pub const SYS_SCHED_GETSCHEDULER: syscall_number_t = 145;
 pub const SYS_ARCH_PRCTL: syscall_number_t = 158;
 pub const SYS_EXIT_GROUP: syscall_number_t = 231;
+pub const SYS_OPENAT: syscall_number_t = 257;
 pub const SYS_GETRANDOM: syscall_number_t = 318;
 
 pub const ARCH_SET_GS: i32 = 0x1001;
@@ -253,6 +257,7 @@
 pub const AT_PHDR: u64 = 3;
 pub const AT_PHNUM: u64 = 5;
 pub const AT_PAGESZ: u64 = 6;
+pub const AT_ENTRY: u64 = 9;
 pub const AT_BASE: u64 = 7;
 pub const AT_UID: u64 = 11;
 pub const AT_EUID: u64 = 12;
@@ -316,5 +321,26 @@
 pub const MAP_PRIVATE: i32 = 0x2;
 pub const MAP_FIXED: i32 = 0x10;
 pub const MAP_ANONYMOUS: i32 = 0x20;
+pub const MAP_NORESERVE: i32 = 0x4000;
 
 pub const SCHED_OTHER: i32 = 0;
+
+pub const AT_FDCWD: i32 = -100;
+pub const PATH_MAX: usize = 4096;
+
+#[derive(Debug, Default, AsBytes)]
+#[repr(C)]
+pub struct statfs {
+    f_type: i64,
+    f_bsize: i64,
+    f_blocks: i64,
+    f_bfree: i64,
+    f_bavail: i64,
+    f_files: i64,
+    f_ffree: i64,
+    f_fsid: i64,
+    f_namelen: i64,
+    f_frsize: i64,
+    f_flags: i64,
+    f_spare: [i64; 4],
+}
diff --git a/zircon/kernel/lib/syscalls/vmo.cc b/zircon/kernel/lib/syscalls/vmo.cc
index 0f151a2..46a4e0b 100644
--- a/zircon/kernel/lib/syscalls/vmo.cc
+++ b/zircon/kernel/lib/syscalls/vmo.cc
@@ -252,11 +252,6 @@
   auto up = ProcessDispatcher::GetCurrent();
 
   zx_status_t vmex_status = ZX_OK;
-  if (vmex != ZX_HANDLE_INVALID) {
-    vmex_status = validate_ranged_resource(vmex, ZX_RSRC_KIND_SYSTEM, ZX_RSRC_SYSTEM_VMEX_BASE, 1);
-  } else {
-    vmex_status = up->EnforceBasicPolicy(ZX_POL_AMBIENT_MARK_VMO_EXEC);
-  }
 
   Guard<BrwLockPi, BrwLockPi::Writer> guard{up->handle_table().get_lock()};
   auto source = up->handle_table().GetHandleLocked(handle);