[libdm_rust] Enable writable loopdevice

Add option in loopdevice module to create a block device in writable
mode as well. This is required for unit-testing dm-crypt device.

Also, add test: attach_loop_device_with_dio_writable

Test: atest libdm_rust.test
Test: atest apkdmverity.test
Bug: 250880499
Change-Id: I793ca247ec3cb3258d16a6d9bb25aae31c127ede
diff --git a/apkdmverity/src/main.rs b/apkdmverity/src/main.rs
index 23457c4..a69b583 100644
--- a/apkdmverity/src/main.rs
+++ b/apkdmverity/src/main.rs
@@ -93,7 +93,7 @@
             bail!("The size of {:?} is not multiple of {}.", &apk, BLOCK_SIZE)
         }
         (
-            loopdevice::attach(&apk, 0, apk_size, /*direct_io*/ true)
+            loopdevice::attach(&apk, 0, apk_size, /*direct_io*/ true, /*writable*/ false)
                 .context("Failed to attach APK to a loop device")?,
             apk_size,
         )
@@ -108,8 +108,9 @@
     // Due to unknown reason(b/191344832), we can't enable "direct IO" for the IDSIG file (backing
     // the hash). For now we don't use "direct IO" but it seems OK since the IDSIG file is very
     // small and the benefit of direct-IO would be negliable.
-    let hash_device = loopdevice::attach(&idsig, offset, size, /*direct_io*/ false)
-        .context("Failed to attach idsig to a loop device")?;
+    let hash_device =
+        loopdevice::attach(&idsig, offset, size, /*direct_io*/ false, /*writable*/ false)
+            .context("Failed to attach idsig to a loop device")?;
 
     // Build a dm-verity target spec from the information from the idsig file. The apk and the
     // idsig files are used as the data device and the hash device, respectively.
@@ -324,9 +325,19 @@
         // already a block device, `enable_verity` uses the block device as it is. The detatching
         // of the data device is done in the scopeguard for the return value of `enable_verity`
         // below. Only the idsig_loop_device needs detatching.
-        let apk_loop_device = loopdevice::attach(&apk_path, 0, apk_size, true).unwrap();
+        let apk_loop_device = loopdevice::attach(
+            &apk_path, 0, apk_size, /*direct_io*/ true, /*writable*/ false,
+        )
+        .unwrap();
         let idsig_loop_device = scopeguard::guard(
-            loopdevice::attach(&idsig_path, 0, idsig_size, false).unwrap(),
+            loopdevice::attach(
+                &idsig_path,
+                0,
+                idsig_size,
+                /*direct_io*/ false,
+                /*writable*/ false,
+            )
+            .unwrap(),
             |dev| loopdevice::detach(dev).unwrap(),
         );
 
diff --git a/libs/devicemapper/src/loopdevice.rs b/libs/devicemapper/src/loopdevice.rs
index bdbc0f6..5533e17 100644
--- a/libs/devicemapper/src/loopdevice.rs
+++ b/libs/devicemapper/src/loopdevice.rs
@@ -66,6 +66,7 @@
     offset: u64,
     size_limit: u64,
     direct_io: bool,
+    writable: bool,
 ) -> Result<PathBuf> {
     // Attaching a file to a loop device can make a race condition; a loop device number obtained
     // from LOOP_CTL_GET_FREE might have been used by another thread or process. In that case the
@@ -80,7 +81,7 @@
 
     let begin = Instant::now();
     loop {
-        match try_attach(&path, offset, size_limit, direct_io) {
+        match try_attach(&path, offset, size_limit, direct_io, writable) {
             Ok(loop_dev) => return Ok(loop_dev),
             Err(e) => {
                 if begin.elapsed() > TIMEOUT {
@@ -103,6 +104,7 @@
     offset: u64,
     size_limit: u64,
     direct_io: bool,
+    writable: bool,
 ) -> Result<PathBuf> {
     // Get a free loop device
     wait_for_path(LOOP_CONTROL)?;
@@ -116,6 +118,7 @@
     // Construct the loop_info64 struct
     let backing_file = OpenOptions::new()
         .read(true)
+        .write(writable)
         .custom_flags(if direct_io { O_DIRECT } else { 0 })
         .open(&path)
         .context(format!("failed to open {:?}", path.as_ref()))?;
@@ -126,7 +129,11 @@
     config.block_size = 4096;
     config.info.lo_offset = offset;
     config.info.lo_sizelimit = size_limit;
-    config.info.lo_flags = Flag::LO_FLAGS_READ_ONLY;
+
+    if !writable {
+        config.info.lo_flags = Flag::LO_FLAGS_READ_ONLY;
+    }
+
     if direct_io {
         config.info.lo_flags.insert(Flag::LO_FLAGS_DIRECT_IO);
     }
@@ -168,13 +175,19 @@
         "1" == fs::read_to_string(&dio).unwrap().trim()
     }
 
+    // kernel exposes /sys/block/loop*/ro which gives the read-only value
+    fn is_direct_io_writable(dev: &Path) -> bool {
+        let ro = Path::new("/sys/block").join(dev.file_name().unwrap()).join("ro");
+        "0" == fs::read_to_string(&ro).unwrap().trim()
+    }
+
     #[test]
     fn attach_loop_device_with_dio() {
         let a_dir = tempfile::TempDir::new().unwrap();
         let a_file = a_dir.path().join("test");
         let a_size = 4096u64;
         create_empty_file(&a_file, a_size);
-        let dev = attach(a_file, 0, a_size, /*direct_io*/ true).unwrap();
+        let dev = attach(a_file, 0, a_size, /*direct_io*/ true, /*writable*/ false).unwrap();
         scopeguard::defer! {
             detach(&dev).unwrap();
         }
@@ -187,10 +200,24 @@
         let a_file = a_dir.path().join("test");
         let a_size = 4096u64;
         create_empty_file(&a_file, a_size);
-        let dev = attach(a_file, 0, a_size, /*direct_io*/ false).unwrap();
+        let dev = attach(a_file, 0, a_size, /*direct_io*/ false, /*writable*/ false).unwrap();
         scopeguard::defer! {
             detach(&dev).unwrap();
         }
         assert!(!is_direct_io(&dev));
     }
+
+    #[test]
+    fn attach_loop_device_with_dio_writable() {
+        let a_dir = tempfile::TempDir::new().unwrap();
+        let a_file = a_dir.path().join("test");
+        let a_size = 4096u64;
+        create_empty_file(&a_file, a_size);
+        let dev = attach(a_file, 0, a_size, /*direct_io*/ true, /*writable*/ true).unwrap();
+        scopeguard::defer! {
+            detach(&dev).unwrap();
+        }
+        assert!(is_direct_io(&dev));
+        assert!(is_direct_io_writable(&dev));
+    }
 }