diff --git a/samples/rust/puzzle/inode.rs b/samples/rust/puzzle/inode.rs
index f63cdbc1eac4..03c9f6bee75f 100644
--- a/samples/rust/puzzle/inode.rs
+++ b/samples/rust/puzzle/inode.rs
@@ -7,10 +7,10 @@
 use crate::puzzle::types as format;
 use crate::puzzle::types::{Digest, Inode, InodeMode};
 use alloc::vec::Vec;
+use core::cmp::min;
 use kernel::mount::Vfsmount;
-use kernel::prelude::ENOENT;
+use kernel::prelude::{ENOENT, ENOTDIR};
 use kernel::str::CStr;
-use kernel::sync::Arc;
 
 pub(crate) struct PuzzleFS {
     pub(crate) oci: Image,
@@ -18,8 +18,9 @@ pub(crate) struct PuzzleFS {
 }
 
 impl PuzzleFS {
-    pub(crate) fn open(vfsmount: Arc<Vfsmount>, rootfs_path: &CStr) -> Result<PuzzleFS> {
-        let oci = Image::open(vfsmount)?;
+    pub(crate) fn open(oci_root_dir: &CStr, rootfs_path: &CStr) -> Result<PuzzleFS> {
+        let vfs_mount = Vfsmount::new_private_mount(oci_root_dir)?;
+        let oci = Image::open(vfs_mount)?;
         let rootfs = oci.open_rootfs_blob(rootfs_path)?;
 
         let mut layers = Vec::new();
@@ -46,3 +47,55 @@ pub(crate) fn find_inode(&self, ino: u64) -> Result<Inode> {
         Err(WireFormatError::from_errno(ENOENT))
     }
 }
+
+pub(crate) fn file_read(
+    oci: &Image,
+    inode: &Inode,
+    offset: usize,
+    data: &mut [u8],
+) -> Result<usize> {
+    let chunks = match &inode.mode {
+        InodeMode::File { chunks } => chunks,
+        _ => return Err(WireFormatError::from_errno(ENOTDIR)),
+    };
+
+    // TODO: fix all this casting...
+    let end = offset + data.len();
+
+    let mut file_offset = 0;
+    let mut buf_offset = 0;
+    for chunk in chunks {
+        // have we read enough?
+        if file_offset > end {
+            break;
+        }
+
+        // should we skip this chunk?
+        if file_offset + (chunk.len as usize) < offset {
+            file_offset += chunk.len as usize;
+            continue;
+        }
+
+        let addl_offset = if offset > file_offset {
+            offset - file_offset
+        } else {
+            0
+        };
+
+        // ok, need to read this chunk; how much?
+        let left_in_buf = data.len() - buf_offset;
+        let to_read = min(left_in_buf, chunk.len as usize - addl_offset);
+
+        let start = buf_offset;
+        let finish = start + to_read;
+        file_offset += addl_offset;
+
+        // how many did we actually read?
+        let n = oci.fill_from_chunk(chunk.blob, addl_offset as u64, &mut data[start..finish])?;
+        file_offset += n;
+        buf_offset += n;
+    }
+
+    // discard any extra if we hit EOF
+    Ok(buf_offset)
+}
diff --git a/samples/rust/puzzle/oci.rs b/samples/rust/puzzle/oci.rs
index becb2b868450..5aa60ded8419 100644
--- a/samples/rust/puzzle/oci.rs
+++ b/samples/rust/puzzle/oci.rs
@@ -1,19 +1,21 @@
-use crate::puzzle::error::Result;
+use crate::puzzle::error::{Result, WireFormatError};
+use crate::puzzle::types as format;
 use crate::puzzle::types::{Digest, MetadataBlob, Rootfs};
 use kernel::c_str;
 use kernel::file;
 use kernel::file::RegularFile;
 use kernel::mount::Vfsmount;
-use kernel::pr_info;
+use kernel::pr_debug;
+use kernel::prelude::ENOTSUPP;
 use kernel::str::{CStr, CString};
-use kernel::sync::Arc;
 
+#[derive(Debug)]
 pub(crate) struct Image {
-    vfs_mount: Arc<Vfsmount>,
+    pub(crate) vfs_mount: Vfsmount,
 }
 
 impl Image {
-    pub(crate) fn open(vfsmount: Arc<Vfsmount>) -> Result<Self> {
+    pub(crate) fn open(vfsmount: Vfsmount) -> Result<Self> {
         Ok(Image {
             vfs_mount: vfsmount,
         })
@@ -26,7 +28,7 @@ pub(crate) fn blob_path_relative(&self) -> &CStr {
     fn open_raw_blob(&self, digest: &Digest) -> Result<RegularFile> {
         let filename =
             CString::try_from_fmt(format_args!("{}/{digest}", self.blob_path_relative()))?;
-        pr_info!("trying to open {:?}\n", &filename);
+        pr_debug!("trying to open {:?}\n", &*filename);
 
         let file = RegularFile::from_path_in_root_mnt(
             &self.vfs_mount,
@@ -48,4 +50,22 @@ pub(crate) fn open_rootfs_blob(&self, path: &CStr) -> Result<Rootfs> {
         let rootfs = Rootfs::open(self.open_raw_blob(&digest)?)?;
         Ok(rootfs)
     }
+
+    pub(crate) fn fill_from_chunk(
+        &self,
+        chunk: format::BlobRef,
+        addl_offset: u64,
+        buf: &mut [u8],
+    ) -> Result<usize> {
+        let digest = &<Digest>::try_from(chunk)?;
+
+        let blob = if chunk.compressed {
+            return Err(WireFormatError::KernelError(ENOTSUPP));
+        } else {
+            self.open_raw_blob(digest)?
+        };
+
+        let n = blob.read_with_offset(buf, chunk.offset + addl_offset)?;
+        Ok(n)
+    }
 }
diff --git a/samples/rust/puzzlefs.rs b/samples/rust/puzzlefs.rs
index 76dc59403db3..dad7ecc76eca 100644
--- a/samples/rust/puzzlefs.rs
+++ b/samples/rust/puzzlefs.rs
@@ -3,7 +3,6 @@
 //! Rust file system sample.
 
 use kernel::module_fs;
-use kernel::mount::Vfsmount;
 use kernel::prelude::*;
 use kernel::{
     c_str, file, fs,
@@ -13,7 +12,7 @@
 
 mod puzzle;
 // Required by the autogenerated '_capnp.rs' files
-use puzzle::inode::PuzzleFS;
+use puzzle::inode::{file_read, PuzzleFS};
 use puzzle::types::{Inode, InodeMode};
 use puzzle::{manifest_capnp, metadata_capnp};
 
@@ -28,9 +27,8 @@
 
 struct PuzzleFsModule;
 
-#[derive(Debug)]
 struct PuzzlefsInfo {
-    vfs_mount: Arc<Vfsmount>,
+    puzzlefs: Arc<PuzzleFS>,
 }
 
 #[vtable]
@@ -139,14 +137,20 @@ impl fs::Type for PuzzleFsModule {
     const DCACHE_BASED: bool = true;
 
     fn fill_super(_data: (), sb: fs::NewSuperBlock<'_, Self>) -> Result<&fs::SuperBlock<Self>> {
-        let vfs_mount = Vfsmount::new_private_mount(c_str!("/home/puzzlefs_oci"))?;
-        pr_info!("vfs_mount {:?}\n", vfs_mount);
+        let puzzlefs = PuzzleFS::open(
+            c_str!("/home/puzzlefs_oci"),
+            c_str!("2d6602d678140540dc7e96de652a76a8b16e8aca190bae141297bcffdcae901b"),
+        );
 
-        let arc_vfs_mount = Arc::try_new(vfs_mount)?;
+        if let Err(ref e) = puzzlefs {
+            pr_info!("error opening puzzlefs {e}\n");
+        }
+
+        let puzzlefs = Arc::try_new(puzzlefs?)?;
 
         let sb = sb.init(
             Box::try_new(PuzzlefsInfo {
-                vfs_mount: arc_vfs_mount.clone(),
+                puzzlefs: puzzlefs.clone(),
             })?,
             &fs::SuperParams {
                 magic: 0x72757374,
@@ -154,19 +158,9 @@ fn fill_super(_data: (), sb: fs::NewSuperBlock<'_, Self>) -> Result<&fs::SuperBl
             },
         )?;
 
-        let puzzlefs = PuzzleFS::open(
-            arc_vfs_mount,
-            c_str!("2d6602d678140540dc7e96de652a76a8b16e8aca190bae141297bcffdcae901b"),
-        );
-
-        if let Err(ref e) = puzzlefs {
-            pr_info!("error opening puzzlefs {e}\n");
-        }
-
-        let mut puzzlefs = puzzlefs?;
         let root_inode = Arc::try_new(puzzlefs.find_inode(1)?)?;
 
-        let root = try_new_populated_root_puzzlefs_dentry(&sb, &mut puzzlefs, root_inode)?;
+        let root = try_new_populated_root_puzzlefs_dentry(&sb, &puzzlefs, root_inode)?;
         let sb = sb.init_root(root)?;
         Ok(sb)
     }
@@ -180,32 +174,28 @@ impl file::Operations for FsFile {
     type OpenData = Arc<Inode>;
     type Filesystem = PuzzleFsModule;
     // this is an Arc because Data must be ForeignOwnable and the only implementors of it are Box,
-    // Arc and (); we cannot pass a reference to read, so we share Vfsmount using and Arc
-    type Data = Arc<Vfsmount>;
+    // Arc and (); we cannot pass a reference to the read callback, so we share PuzzleFS using Arc
+    type Data = Arc<PuzzleFS>;
 
     fn open(
         fs_info: &PuzzlefsInfo,
         _context: &Self::OpenData,
         _file: &file::File,
     ) -> Result<Self::Data> {
-        Ok(fs_info.vfs_mount.clone())
+        Ok(fs_info.puzzlefs.clone())
     }
 
     fn read(
-        data: ArcBorrow<'_, Vfsmount>,
-        _file: &file::File,
+        data: ArcBorrow<'_, PuzzleFS>,
+        file: &file::File,
         writer: &mut impl IoBufferWriter,
         offset: u64,
     ) -> Result<usize> {
+        let inode = file.inode::<PuzzleFsModule>().ok_or(EINVAL)?.fs_data();
         let mut buf = Vec::try_with_capacity(writer.len())?;
         buf.try_resize(writer.len(), 0)?;
-        let file = file::RegularFile::from_path_in_root_mnt(
-            &data,
-            c_str!("data"),
-            file::flags::O_RDONLY.try_into().unwrap(),
-            0,
-        )?;
-        let nr_bytes_read = file.read_with_offset(&mut buf[..], offset)?;
-        file::read_from_slice(&buf[..nr_bytes_read], writer, 0)
+        let read = file_read(&data.oci, inode, offset as usize, &mut buf)?;
+        buf.truncate(read);
+        file::read_from_slice(&buf, writer, 0)
     }
 }
