zipfuse: supports zip on block device

Add a test case to ensure that zipfuse works with a block device whose
content is backed by a zip file. Fortunately, zip-rs already supports
reading a zip file from a block device: it locates the end of central
directory record by reverse-scanning a file from the end.

Bug: 186377508
Test: cargo test (android test missing because we don't have the
loopdevice crate for Android, which is fine)

Change-Id: I8fbbfd18a2a0df2a550e3f679ba74ba603b59efd
diff --git a/zipfuse/.cargo/config.toml b/zipfuse/.cargo/config.toml
index b87b13a..2fc1783 100644
--- a/zipfuse/.cargo/config.toml
+++ b/zipfuse/.cargo/config.toml
@@ -1,4 +1,3 @@
-# Mounting requires root privilege. The fuse library in crosvm doesn't support
-# fusermount yet.
+# losetup requires root privilege.
 [target.x86_64-unknown-linux-gnu]
-runner = 'unshare -mUr'
+runner = 'sudo -E'
diff --git a/zipfuse/Cargo.toml b/zipfuse/Cargo.toml
index 220d352..c8f2f3a 100644
--- a/zipfuse/Cargo.toml
+++ b/zipfuse/Cargo.toml
@@ -12,3 +12,7 @@
 zip = "0.5"
 tempfile = "3.2"
 nix = "0.20"
+
+[dev-dependencies]
+loopdev = "0.2"
+scopeguard = "1.1"
diff --git a/zipfuse/src/main.rs b/zipfuse/src/main.rs
index 21f7f9c..12c891c 100644
--- a/zipfuse/src/main.rs
+++ b/zipfuse/src/main.rs
@@ -633,14 +633,8 @@
         );
     }
 
-    #[test]
-    fn supports_deflate() {
-        let test_dir = tempfile::TempDir::new().unwrap();
-        let zip_path = test_dir.path().join("test.zip");
-        let mut zip_file = File::create(&zip_path).unwrap();
-        zip_file.write_all(include_bytes!("../testdata/test.zip")).unwrap();
-
-        let mnt_path = test_dir.path().join("mnt");
+    fn run_fuse_and_check_test_zip(test_dir: &Path, zip_path: &Path) {
+        let mnt_path = test_dir.join("mnt");
         assert!(fs::create_dir(&mnt_path).is_ok());
 
         start_fuse(&zip_path, &mnt_path);
@@ -654,4 +648,44 @@
         check_file(&mnt_path, "dir/file2", include_bytes!("../testdata/dir/file2"));
         assert!(nix::mount::umount2(&mnt_path, nix::mount::MntFlags::empty()).is_ok());
     }
+
+    #[test]
+    fn supports_deflate() {
+        let test_dir = tempfile::TempDir::new().unwrap();
+        let zip_path = test_dir.path().join("test.zip");
+        let mut zip_file = File::create(&zip_path).unwrap();
+        zip_file.write_all(include_bytes!("../testdata/test.zip")).unwrap();
+
+        run_fuse_and_check_test_zip(&test_dir.path(), &zip_path);
+    }
+
+    #[cfg(not(target_os = "android"))] // Android doesn't have the loopdev crate
+    #[test]
+    fn supports_zip_on_block_device() {
+        // Write test.zip to the test directory
+        let test_dir = tempfile::TempDir::new().unwrap();
+        let zip_path = test_dir.path().join("test.zip");
+        let mut zip_file = File::create(&zip_path).unwrap();
+        let data = include_bytes!("../testdata/test.zip");
+        zip_file.write_all(data).unwrap();
+
+        // Pad 0 to test.zip so that its size is multiple of 4096.
+        const BLOCK_SIZE: usize = 4096;
+        let size = (data.len() + BLOCK_SIZE) & !BLOCK_SIZE;
+        let pad_size = size - data.len();
+        assert!(pad_size != 0);
+        let pad = vec![0; pad_size];
+        zip_file.write_all(pad.as_slice()).unwrap();
+        drop(zip_file);
+
+        // Attach test.zip to a loop device
+        let lc = loopdev::LoopControl::open().unwrap();
+        let ld = scopeguard::guard(lc.next_free().unwrap(), |ld| {
+            ld.detach().unwrap();
+        });
+        ld.attach_file(&zip_path).unwrap();
+
+        // Start zipfuse over to the loop device (not the zip file)
+        run_fuse_and_check_test_zip(&test_dir.path(), &ld.path().unwrap());
+    }
 }