From 8c1a01b30644a0412d6ac80d9bb80f00ec24b4b7 Mon Sep 17 00:00:00 2001 From: Aiden McClelland Date: Fri, 10 Sep 2021 19:39:03 -0600 Subject: [PATCH] list disks without blkid --- appmgr/build-prod.sh | 2 +- appmgr/src/action/mod.rs | 10 ++ appmgr/src/disk/util.rs | 284 +++++++++++++++++------------- appmgr/src/error.rs | 4 +- appmgr/src/setup.rs | 52 ++++++ appmgr/src/status/health_check.rs | 4 +- 6 files changed, 233 insertions(+), 123 deletions(-) diff --git a/appmgr/build-prod.sh b/appmgr/build-prod.sh index ca812c44f..534c5b236 100755 --- a/appmgr/build-prod.sh +++ b/appmgr/build-prod.sh @@ -11,6 +11,6 @@ fi alias 'rust-arm64-builder'='docker run --rm -it -v "$HOME/.cargo/registry":/root/.cargo/registry -v "$(pwd)":/home/rust/src start9/rust-arm-cross:aarch64' cd ../.. -rust-arm64-builder sh -c "(cd embassy-os/appmgr && cargo +beta build --release --features=production)" +rust-arm64-builder sh -c "(cd embassy-os/appmgr && cargo +beta build --release)" cd embassy-os/appmgr #rust-arm64-builder aarch64-linux-gnu-strip target/aarch64-unknown-linux-gnu/release/embassyd diff --git a/appmgr/src/action/mod.rs b/appmgr/src/action/mod.rs index 49991218e..e2c5359e3 100644 --- a/appmgr/src/action/mod.rs +++ b/appmgr/src/action/mod.rs @@ -157,3 +157,13 @@ impl ActionImplementation { } } } + +pub struct NoOutput; +impl<'de> Deserialize<'de> for NoOutput { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + Ok(NoOutput) + } +} diff --git a/appmgr/src/disk/util.rs b/appmgr/src/disk/util.rs index a063fbe5a..6a97b5d28 100644 --- a/appmgr/src/disk/util.rs +++ b/appmgr/src/disk/util.rs @@ -1,139 +1,185 @@ -use std::path::Path; +use std::path::{Path, PathBuf}; use anyhow::anyhow; use futures::future::try_join_all; -use indexmap::IndexMap; +use futures::TryStreamExt; +use indexmap::{IndexMap, IndexSet}; +use regex::Regex; +use rpc_toolkit::command; use serde::{Deserialize, Serialize}; -use tokio::io::{AsyncReadExt, AsyncWriteExt}; +use tokio::io::{AsyncBufReadExt, AsyncReadExt, AsyncWriteExt}; +use tokio::process::Command; -use crate::util::Invoke; +use crate::util::{Invoke, Version}; use crate::{Error, ResultExt as _}; -pub struct Disks(IndexMap); +pub const TMP_MOUNTPOINT: &'static str = "/media/embassy-os"; #[derive(Clone, Debug, Deserialize, Serialize)] #[serde(rename_all = "kebab-case")] pub struct DiskInfo { - pub size: String, - pub description: Option, - pub partitions: IndexMap, + logicalname: PathBuf, + partitions: Vec, + capacity: usize, + embassy_os: Option, } #[derive(Clone, Debug, Deserialize, Serialize)] #[serde(rename_all = "kebab-case")] pub struct PartitionInfo { - pub is_mounted: bool, - pub size: Option, - pub label: Option, + logicalname: PathBuf, + label: Option, + capacity: usize, + used: Option, } -pub async fn list() -> Result { - let output = tokio::process::Command::new("parted") - .arg("-lm") - .invoke(crate::ErrorKind::GParted) - .await?; - let output_str = std::str::from_utf8(&output)?; - let disks = output_str - .split("\n\n") - .filter_map(|s| -> Option<(String, DiskInfo)> { - let mut lines = s.split("\n"); - let has_size = lines.next()? == "BYT;"; - let disk_info_line = lines.next()?; - let mut disk_info_iter = disk_info_line.split(":"); - let logicalname = disk_info_iter.next()?.to_owned(); - let partition_prefix = if logicalname.ends_with(|c: char| c.is_digit(10)) { - logicalname.clone() + "p" - } else { - logicalname.clone() - }; - let size = disk_info_iter.next()?.to_owned(); - disk_info_iter.next()?; // transport-type - disk_info_iter.next()?; // logical-sector-size - disk_info_iter.next()?; // physical-sector-size - disk_info_iter.next()?; // partition-table-type - let description = disk_info_iter.next()?; - let description = if description.is_empty() { - None - } else { - Some(description.to_owned()) - }; - Some(( - logicalname, - DiskInfo { - size, - description, - partitions: lines - .filter_map(|partition_info_line| -> Option<(String, PartitionInfo)> { - let mut partition_info_iter = partition_info_line.split(":"); - let partition_idx = partition_info_iter.next()?; - let logicalname = partition_prefix.clone() + partition_idx; - let size = if has_size { - partition_info_iter.next()?; // begin - partition_info_iter.next()?; // end - Some(partition_info_iter.next()?.to_owned()) - } else { - None - }; - Some(( - logicalname, - PartitionInfo { - is_mounted: false, - size, - label: None, - }, - )) - }) - .collect(), - }, - )) - }); - Ok(Disks( - try_join_all(disks.map(|(logicalname, disk)| async move { - Ok::<_, Error>(( - logicalname, - DiskInfo { - partitions: try_join_all(disk.partitions.into_iter().map( - |(logicalname, mut partition)| async move { - let mut blkid_command = tokio::process::Command::new("blkid"); - let (blkid_res, findmnt_status) = futures::join!( - blkid_command - .arg(&logicalname) - .arg("-s") - .arg("LABEL") - .arg("-o") - .arg("value") - .invoke(crate::ErrorKind::Blkid), - tokio::process::Command::new("findmnt") - .arg(&logicalname) - .stdout(std::process::Stdio::null()) - .stderr(std::process::Stdio::null()) - .status() - ); - let blkid_output = blkid_res?; - let label = std::str::from_utf8(&blkid_output)?.trim(); - if !label.is_empty() { - partition.label = Some(label.to_owned()); - } - if findmnt_status?.success() { - partition.is_mounted = true; - } - Ok::<_, Error>((logicalname, partition)) - }, - )) - .await? - .into_iter() - .collect(), - ..disk - }, - )) - })) - .await? - .into_iter() - .collect(), - )) +#[derive(Clone, Debug, Deserialize, Serialize)] +#[serde(rename_all = "kebab-case")] +pub struct EmbassyOsDiskInfo { + version: Version, + name: String, } -pub async fn mount>(logicalname: &str, mount_point: P) -> Result<(), Error> { +const DISK_PATH: &'static str = "/dev/disk/by-path"; + +lazy_static::lazy_static! { + static ref PARTITION_REGEX: Regex = Regex::new("-part[0-9]+$").unwrap(); +} + +pub async fn get_capacity>(path: P) -> Result { + Ok(String::from_utf8( + Command::new("blockdev") + .arg("--getsize64") + .arg(path.as_ref()) + .invoke(crate::ErrorKind::BlockDev) + .await?, + )? + .parse()?) +} + +pub async fn get_label>(path: P) -> Result, Error> { + let label = String::from_utf8( + Command::new("lsblk ") + .arg("-no") + .arg("label") + .arg(path.as_ref()) + .invoke(crate::ErrorKind::BlockDev) // TODO: error kind + .await?, + )?; + Ok(if label.is_empty() { None } else { Some(label) }) +} + +pub async fn get_used>(path: P) -> Result { + Ok(String::from_utf8( + Command::new("df") + .arg("--output=used") + .arg(path.as_ref()) + .invoke(crate::ErrorKind::Unknown) + .await?, + )? + .lines() + .skip(1) + .next() + .unwrap_or_default() + .trim() + .parse()?) +} + +pub async fn list() -> Result, Error> { + if tokio::fs::metadata(TMP_MOUNTPOINT).await.is_err() { + tokio::fs::create_dir_all(TMP_MOUNTPOINT) + .await + .with_ctx(|_| (crate::ErrorKind::Filesystem, TMP_MOUNTPOINT))?; + } + + let disks = tokio_stream::wrappers::ReadDirStream::new( + tokio::fs::read_dir(DISK_PATH) + .await + .with_ctx(|_| (crate::ErrorKind::Filesystem, DISK_PATH))?, + ) + .map_err(|e| { + Error::new( + anyhow::Error::from(e).context(DISK_PATH), + crate::ErrorKind::Filesystem, + ) + }) + .try_fold(IndexMap::new(), |mut disks, dir_entry| async move { + if let Some(disk_path) = dir_entry.path().file_name().and_then(|s| s.to_str()) { + let (disk_path, part_path) = if let Some(end) = PARTITION_REGEX.find(disk_path) { + ( + disk_path.strip_suffix(end.as_str()).unwrap_or_default(), + Some(disk_path), + ) + } else { + (disk_path, None) + }; + let disk = tokio::fs::canonicalize(Path::new(DISK_PATH).join(disk_path)).await?; + if !disks.contains_key(&disk) { + disks.insert(disk.clone(), IndexSet::new()); + } + if let Some(part_path) = part_path { + let part = tokio::fs::canonicalize(part_path).await?; + disks[&disk].insert(part); + } + } + Ok(disks) + }) + .await?; + + let mut res = Vec::with_capacity(disks.len()); + for (disk, parts) in disks { + let mut partitions = Vec::with_capacity(parts.len()); + let capacity = get_capacity(&disk) + .await + .map_err(|e| log::warn!("Could not get capacity of {}: {}", disk.display(), e.source)) + .unwrap_or_default(); + let mut embassy_os = None; + for part in parts { + let label = get_label(&part).await?; + let capacity = get_capacity(&part) + .await + .map_err(|e| { + log::warn!("Could not get capacity of {}: {}", part.display(), e.source) + }) + .unwrap_or_default(); + let mut used = None; + + let tmp_mountpoint = Path::new(TMP_MOUNTPOINT).join(&part); + if let Err(e) = mount(&part, &tmp_mountpoint).await { + log::warn!("Could not collect usage information: {}", e.source) + } else { + used = get_used(&tmp_mountpoint) + .await + .map_err(|e| { + log::warn!("Could not get usage of {}: {}", part.display(), e.source) + }) + .ok(); + // todo!("check embassy-os"); + unmount(&tmp_mountpoint).await?; // TODO: mount guard + } + + partitions.push(PartitionInfo { + logicalname: part, + label, + capacity, + used, + }); + } + res.push(DiskInfo { + logicalname: disk, + partitions, + capacity, + embassy_os, + }) + } + + Ok(res) +} + +pub async fn mount, P1: AsRef>( + logicalname: P0, + mount_point: P1, +) -> Result<(), Error> { let is_mountpoint = tokio::process::Command::new("mountpoint") .arg(mount_point.as_ref()) .stdout(std::process::Stdio::null()) @@ -145,14 +191,16 @@ pub async fn mount>(logicalname: &str, mount_point: P) -> Result< } tokio::fs::create_dir_all(&mount_point).await?; let mount_output = tokio::process::Command::new("mount") - .arg(logicalname) + .arg(logicalname.as_ref()) .arg(mount_point.as_ref()) .output() .await?; crate::ensure_code!( mount_output.status.success(), crate::ErrorKind::Filesystem, - "Error Mounting Drive: {}", + "Error Mounting {} to {}: {}", + logicalname.as_ref().display(), + mount_point.as_ref().display(), std::str::from_utf8(&mount_output.stderr).unwrap_or("Unknown Error") ); Ok(()) diff --git a/appmgr/src/error.rs b/appmgr/src/error.rs index 31a6b6d9f..cbf77bf90 100644 --- a/appmgr/src/error.rs +++ b/appmgr/src/error.rs @@ -26,7 +26,7 @@ pub enum ErrorKind { ParseS9pk = 18, ParseUrl = 19, GParted = 20, - Blkid = 21, + BlockDev = 21, InvalidOnionAddress = 22, Pack = 23, ValidateS9pk = 24, @@ -79,7 +79,7 @@ impl ErrorKind { ParseS9pk => "S9PK Parsing Error", ParseUrl => "URL Parsing Error", GParted => "GNU Parted Error", - Blkid => "BLKID Error", + BlockDev => "BlockDev Error", InvalidOnionAddress => "Invalid Onion Address", Pack => "Pack Error", ValidateS9pk => "S9PK Validation Error", diff --git a/appmgr/src/setup.rs b/appmgr/src/setup.rs index 8b1378917..a8249befb 100644 --- a/appmgr/src/setup.rs +++ b/appmgr/src/setup.rs @@ -1 +1,53 @@ +use rpc_toolkit::command; +use serde::{Deserialize, Serialize}; +use crate::util::Version; +use crate::Error; + +#[command(subcommands(status, disk))] +pub fn setup() -> Result<(), Error> { + Ok(()) +} + +#[derive(Debug, Deserialize, Serialize)] +#[serde(rename_all = "kebab-case")] +pub struct StatusRes { + is_recovering: bool, + tor_address: Option, +} + +#[command(rpc_only, metadata(encrypted = true))] +pub fn status() -> Result { + // TODO + Ok(StatusRes { + is_recovering: false, + tor_address: None, + }) +} + +#[command(subcommands(list))] +pub fn disk() -> Result<(), Error> { + Ok(()) +} + +#[derive(Debug, Deserialize, Serialize)] +#[serde(rename_all = "kebab-case")] +pub struct DiskInfo { + logicalname: String, + labels: Vec, + capacity: usize, + used: Option, + recovery: Option, +} + +#[derive(Debug, Deserialize, Serialize)] +#[serde(rename_all = "kebab-case")] +pub struct RecoveryInfo { + version: Version, + name: String, +} + +#[command(rpc_only)] +pub fn list() -> Result, Error> { + todo!() +} diff --git a/appmgr/src/status/health_check.rs b/appmgr/src/status/health_check.rs index 243d07cbd..280a34c70 100644 --- a/appmgr/src/status/health_check.rs +++ b/appmgr/src/status/health_check.rs @@ -4,7 +4,7 @@ use chrono::{DateTime, Utc}; use indexmap::IndexMap; use serde::{Deserialize, Deserializer, Serialize}; -use crate::action::ActionImplementation; +use crate::action::{ActionImplementation, NoOutput}; use crate::context::RpcContext; use crate::id::Id; use crate::s9pk::manifest::PackageId; @@ -97,7 +97,7 @@ impl HealthCheck { Ok(HealthCheckResult { time: Utc::now(), result: match res { - Ok(()) => HealthCheckResultVariant::Success, + Ok(NoOutput) => HealthCheckResultVariant::Success, Err((59, _)) => HealthCheckResultVariant::Disabled, Err((60, _)) => HealthCheckResultVariant::Starting, Err((61, message)) => HealthCheckResultVariant::Loading { message },