Merge "zipfuse: fix on readdir"
diff --git a/zipfuse/src/main.rs b/zipfuse/src/main.rs
index 56d7cab..530f0f3 100644
--- a/zipfuse/src/main.rs
+++ b/zipfuse/src/main.rs
@@ -30,6 +30,7 @@
 use std::fs::{File, OpenOptions};
 use std::io;
 use std::io::Read;
+use std::mem::size_of;
 use std::os::unix::io::AsRawFd;
 use std::path::Path;
 use std::sync::Mutex;
@@ -286,7 +287,7 @@
             }
             open_dirs.insert(handle, OpenDirBuf { open_count: 1, buf: buf.into_boxed_slice() });
         }
-        Ok((Some(handle), fuse::filesystem::OpenOptions::empty()))
+        Ok((Some(handle), fuse::filesystem::OpenOptions::CACHE_DIR))
     }
 
     fn releasedir(
@@ -324,8 +325,18 @@
         }
         let buf = &odb.buf;
         let start = offset as usize;
-        let end = start + size as usize;
-        let end = std::cmp::min(end, buf.len());
+
+        // Estimate the size of each entry will take space in the buffer. See
+        // external/crosvm/fuse/src/server.rs#add_dirent
+        let mut estimate: usize = 0; // estimated number of bytes we will be writing
+        let mut end = start; // index in `buf`
+        while estimate < size as usize && end < buf.len() {
+            let dirent_size = size_of::<fuse::sys::Dirent>();
+            let name_size = buf[end].0.to_bytes().len();
+            estimate += (dirent_size + name_size + 7) & !7; // round to 8 byte boundary
+            end += 1;
+        }
+
         let mut new_buf = Vec::with_capacity(end - start);
         // The portion of `buf` is *copied* to the iterator. This is not ideal, but inevitable
         // because the `name` field in `fuse::filesystem::DirEntry` is `&CStr` not `CString`.
@@ -364,7 +375,7 @@
 mod tests {
     use anyhow::{bail, Result};
     use nix::sys::statfs::{statfs, FsType};
-    use std::collections::HashSet;
+    use std::collections::BTreeSet;
     use std::fs;
     use std::fs::File;
     use std::io::Write;
@@ -456,7 +467,7 @@
         assert_eq!(content, read_data.unwrap().as_slice());
     }
 
-    fn check_dir(root: &Path, dir: &str, files: &[&str], dirs: &[&str]) {
+    fn check_dir<S: AsRef<str>>(root: &Path, dir: &str, files: &[S], dirs: &[S]) {
         let dir_path = root.join(dir);
         assert!(dir_path.exists());
 
@@ -470,8 +481,8 @@
         assert!(iter.is_ok());
 
         let iter = iter.unwrap();
-        let mut actual_files = HashSet::new();
-        let mut actual_dirs = HashSet::new();
+        let mut actual_files = BTreeSet::new();
+        let mut actual_dirs = BTreeSet::new();
         for de in iter {
             let entry = de.unwrap();
             let path = entry.path();
@@ -481,8 +492,10 @@
                 actual_files.insert(path.strip_prefix(&dir_path).unwrap().to_path_buf());
             }
         }
-        let expected_files: HashSet<PathBuf> = files.iter().map(|&s| PathBuf::from(s)).collect();
-        let expected_dirs: HashSet<PathBuf> = dirs.iter().map(|&s| PathBuf::from(s)).collect();
+        let expected_files: BTreeSet<PathBuf> =
+            files.iter().map(|s| PathBuf::from(s.as_ref())).collect();
+        let expected_dirs: BTreeSet<PathBuf> =
+            dirs.iter().map(|s| PathBuf::from(s.as_ref())).collect();
 
         assert_eq!(expected_files, actual_files);
         assert_eq!(expected_dirs, actual_dirs);
@@ -493,7 +506,7 @@
         run_test(
             |_| {},
             |root| {
-                check_dir(root, "", &[], &[]);
+                check_dir::<String>(root, "", &[], &[]);
             },
         );
     }
@@ -520,7 +533,7 @@
             },
             |root| {
                 check_dir(root, "", &[], &["dir"]);
-                check_dir(root, "dir", &[], &[]);
+                check_dir::<String>(root, "dir", &[], &[]);
             },
         );
     }
@@ -564,11 +577,11 @@
             |root| {
                 check_dir(root, "", &["foo", "bar"], &["a", "x"]);
                 check_dir(root, "a", &[], &["b1", "b2"]);
-                check_dir(root, "a/b1", &[], &[]);
+                check_dir::<String>(root, "a/b1", &[], &[]);
                 check_dir(root, "a/b2", &["c1"], &["c2"]);
                 check_dir(root, "a/b2/c2", &["d1", "d2", "d3"], &[]);
                 check_dir(root, "x", &["y1", "y2"], &["y3"]);
-                check_dir(root, "x/y3", &[], &[]);
+                check_dir::<String>(root, "x/y3", &[], &[]);
                 check_file(root, "a/b2/c1", &[]);
                 check_file(root, "a/b2/c2/d1", &[]);
                 check_file(root, "a/b2/c2/d2", &[]);
@@ -595,4 +608,28 @@
             },
         );
     }
+
+    #[test]
+    fn large_dir() {
+        const NUM_FILES: usize = 1 << 10;
+        run_test(
+            |zip| {
+                let opt = FileOptions::default();
+                // create 1K files. Each file has a name of length 100. So total size is at least
+                // 100KB, which is bigger than the readdir buffer size of 4K.
+                for i in 0..NUM_FILES {
+                    zip.start_file(format!("dir/{:0100}", i), opt).unwrap();
+                }
+            },
+            |root| {
+                let dirs_expected: Vec<_> = (0..NUM_FILES).map(|i| format!("{:0100}", i)).collect();
+                check_dir(
+                    root,
+                    "dir",
+                    dirs_expected.iter().map(|s| s.as_str()).collect::<Vec<&str>>().as_slice(),
+                    &[],
+                );
+            },
+        );
+    }
 }