[recovery-diagnostics] Add proof of concept to check filesystem health

Change-Id: Ib373e3d2f30e3ea9d235077db91752fa7a91d81e

[recovery] Add block listing to diagnostic checks

Also adding additional error handling and robustness in the case
unexpected parts fail.

Change-Id: I52746ef61682078aacf7edb1f58fa1d5884f37f1

[recovery-diagnostics] Wait for file system to bind

It seems release builds often bind the ramdisk before the sys partition.
In this case the diagnostics just need to wait an extra few hundred
milliseconds. Adding a 5 seconds sleep to be extra generous.

Change-Id: Ib7ee584ab39859f179080038aac8b5250898b2b7

[recovery-diagnostics] Read blobfs entries

Reading all of the entries in the root blobfs and trying to hash their
data in case it indicates bad data in the returned file.

Change-Id: I989070db6e36ebeb59c6af4370171606d460a94d
diff --git a/build/images/BUILD.gn b/build/images/BUILD.gn
index 5f2e0f1..6433d5a 100644
--- a/build/images/BUILD.gn
+++ b/build/images/BUILD.gn
@@ -2715,7 +2715,6 @@
       ":record_filesystem_sizes",
       ":root_component_index_metadata",
       ":updates",
-      "recovery",
     ]
   }
 }
diff --git a/src/recovery/system/BUILD.gn b/src/recovery/system/BUILD.gn
index fb4a032..c690813 100644
--- a/src/recovery/system/BUILD.gn
+++ b/src/recovery/system/BUILD.gn
@@ -51,6 +51,7 @@
   edition = "2018"
   with_unit_tests = true
   deps = [
+    "//sdk/fidl/fuchsia.device:fuchsia.device-rustc",
     "//sdk/fidl/fuchsia.hardware.block.volume:fuchsia.hardware.block.volume-rustc",
     "//sdk/fidl/fuchsia.input.report:fuchsia.input.report-rustc",
     "//sdk/fidl/fuchsia.io:fuchsia.io-rustc",
@@ -74,6 +75,7 @@
     "//third_party/rust_crates:hyper",
     "//third_party/rust_crates:png",
     "//third_party/rust_crates:rouille",
+    "//third_party/rust_crates:rust-crypto",
     "//third_party/rust_crates:serde",
     "//third_party/rust_crates:serde_json",
     "//third_party/rust_crates:tempfile",
@@ -120,6 +122,7 @@
   edition = "2018"
   with_unit_tests = true
   deps = [
+    "//sdk/fidl/fuchsia.device:fuchsia.device-rustc",
     "//sdk/fidl/fuchsia.hardware.block.volume:fuchsia.hardware.block.volume-rustc",
     "//sdk/fidl/fuchsia.input.report:fuchsia.input.report-rustc",
     "//sdk/fidl/fuchsia.paver:fuchsia.paver-rustc",
@@ -139,6 +142,7 @@
     "//third_party/rust_crates:futures",
     "//third_party/rust_crates:png",
     "//third_party/rust_crates:rouille",
+    "//third_party/rust_crates:rust-crypto",
     "//third_party/rust_crates:thiserror",
   ]
   sources = [
diff --git a/src/recovery/system/meta/system_recovery_fdr.cmx b/src/recovery/system/meta/system_recovery_fdr.cmx
index ff26cab..b1dd4d0 100644
--- a/src/recovery/system/meta/system_recovery_fdr.cmx
+++ b/src/recovery/system/meta/system_recovery_fdr.cmx
@@ -11,6 +11,7 @@
     },
     "sandbox": {
         "dev": [
+            "class/block",
             "class/display-controller",
             "class/input",
             "class/input-report",
diff --git a/src/recovery/system/src/main.rs b/src/recovery/system/src/main.rs
index 82ef3d9..9bebaf1 100644
--- a/src/recovery/system/src/main.rs
+++ b/src/recovery/system/src/main.rs
@@ -94,7 +94,7 @@
     ResetFailed,
 }
 
-const RECOVERY_MODE_HEADLINE: &'static str = "Recovery mode";
+const RECOVERY_MODE_HEADLINE: &'static str = "Recovery Diagnostic Mode";
 const RECOVERY_MODE_BODY: &'static str = "Press and hold both volume keys to factory reset.";
 
 const COUNTDOWN_MODE_HEADLINE: &'static str = "Factory reset device";
@@ -240,6 +240,10 @@
 
     #[cfg(not(feature = "http_setup_server"))]
     fn setup(_: &AppContext, _: ViewKey) -> Result<(), Error> {
+        let f = async {
+            check_blobfs_health().await;
+        };
+        fasync::Task::local(f).detach();
         Ok(())
     }
 
@@ -653,6 +657,156 @@
     Box::new(make_app_assistant_fut)
 }
 
+const DEV_BLOCK: &'static str = "/dev/class/block";
+use anyhow::Context as _;
+use fidl::endpoints::Proxy;
+use fidl_fuchsia_device::ControllerProxy;
+use fs_management::{Blobfs, Filesystem};
+use fuchsia_zircon::{self as zx};
+use std::fs;
+async fn connect_to_fdio_service(path: &str) -> Result<fidl::AsyncChannel, Error> {
+    let (local, remote) = zx::Channel::create().context("Creating channel")?;
+    fdio::service_connect(path, remote).context("Connecting to service")?;
+    let local = fidl::AsyncChannel::from_channel(local).context("Creating AsyncChannel")?;
+    Ok(local)
+}
+async fn get_topo_path(channel: fidl::AsyncChannel) -> Option<String> {
+    let controller = ControllerProxy::from_channel(channel);
+    match controller.get_topological_path().await {
+        Ok(res) => match res {
+            Ok(path) => {
+                println!("Returned path: {}", path);
+                Some(path)
+            }
+            Err(errno) => {
+                println!("Received topo_path error value: {}", errno);
+                None
+            }
+        },
+        Err(e) => {
+            println!("Error getting topological path {:#}", e);
+            None
+        }
+    }
+}
+async fn check_blobfs_health() {
+    println!("In diagnostics section, sleeping for 5 seconds");
+    let five_seconds = std::time::Duration::from_secs(5);
+    std::thread::sleep(five_seconds);
+    println!("Sleep complete: running diagnostics");
+    // lsblk
+    match fs::read_dir(DEV_BLOCK) {
+        Ok(rd) => {
+            println!("Read block: OK");
+            for entry in rd {
+                let entry = entry.unwrap();
+                let pathbuf = entry.path();
+                let path = pathbuf.to_str().unwrap();
+                println!("Found entry {:?} path {:?}", entry, path);
+
+                match connect_to_fdio_service(path).await {
+                    Ok(channel) => {
+                        if let Some(topo_path) = get_topo_path(channel).await {
+                            if topo_path.contains("/fvm/blobfs-p-1/block")
+                                && !topo_path.contains("ramdisk")
+                            {
+                                println!("This is the expected blobfs, mount it! {}", topo_path);
+                                check_blobfs(&topo_path, true);
+                            } else {
+                                println!("Not the fvm, skip");
+                            }
+                        }
+                    }
+                    Err(e) => {
+                        println!("Error connected to service for path {}, {:#}", path, e);
+                    }
+                }
+            }
+        }
+        Err(e) => {
+            println!("Couldn't read block: {:#}", e);
+        }
+    }
+}
+
+fn check_blobfs(path: &str, readonly: bool) {
+    let config = Blobfs { verbose: true, readonly: readonly, metrics: true, ..Blobfs::default() };
+    let res = Filesystem::from_path(path, config).context("Filesystem::from_path");
+    match res {
+        Ok(b) => {
+            println!("Attempting to run fsck on Blobfs");
+            let mut b: Filesystem<Blobfs> = b;
+            match b.fsck() {
+                Ok(_) => {
+                    println!("Blobfs-fsck OK");
+                }
+                Err(e) => println!("Error occurred during fsck() {:#}", e),
+            }
+            println!("Attempting to mount Blobfs");
+            match b.mount("/fuchsia-blob-existing") {
+                Ok(_) => {
+                    println!("Mount succeeded");
+                    if let Err(e) = traverse_blobfs(b, "/fuchsia-blob-existing") {
+                        println!("Traverse blobfs had an unexpected error {:#}", e);
+                    }
+                }
+                Err(e) => println!("Mount failed: {:#}", e),
+            }
+        }
+        Err(e) => println!("Mount failed: {:#}", e),
+    }
+}
+
+use crypto::digest::Digest;
+use crypto::sha2::Sha256;
+use std::fs::{DirEntry, File};
+use std::io::Read;
+fn read_entry(entry: Result<DirEntry, std::io::Error>) -> Result<(), Error> {
+    println!("Check: {:?}", entry);
+    let entry = entry.context("Error reading dir entry")?;
+    let path_buf = entry.path();
+    let path = path_buf.to_str().context("Error reading entry path")?;
+    println!(" path: {}", &path);
+
+    match fs::metadata(&path) {
+        Ok(metadata) => {
+            println!(" metadata: {:?}", metadata);
+        }
+        Err(error) => {
+            println!("  Error getting file metadata {:#}", error);
+        }
+    };
+    let mut f = File::open(path).context("Error opening file")?;
+    let mut buffer = Vec::new();
+
+    let bytes_read = f.read_to_end(&mut buffer)?;
+    println!("  bytes read: {}", bytes_read);
+
+    let mut hasher = Sha256::new();
+    hasher.input(&buffer[..bytes_read]);
+    let hex_val = hasher.result_str();
+    println!("  Sha256: {}", hex_val);
+    Ok(())
+}
+
+fn traverse_blobfs(_blobfs: Filesystem<Blobfs>, mount_path: &str) -> Result<(), Error> {
+    let mut error_count = 0;
+    let rd = fs::read_dir(mount_path)?;
+    for entry_res in rd {
+        if let Err(error) = read_entry(entry_res) {
+            println!("  Unexpected error reading dir entry: {:#}", error);
+            error_count += 1;
+        }
+    }
+    if error_count == 0 {
+        println!("No errors traversing blobs");
+    } else {
+        println!("WARNING!");
+        println!("There were {} errors traversing blobs", error_count);
+    }
+    Ok(())
+}
+
 fn main() -> Result<(), Error> {
     println!("recovery: started");
     App::run(make_app_assistant())