list disks without blkid

This commit is contained in:
Aiden McClelland
2021-09-10 19:39:03 -06:00
committed by Aiden McClelland
parent 9617c9149c
commit 8c1a01b306
6 changed files with 233 additions and 123 deletions

View File

@@ -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

View File

@@ -157,3 +157,13 @@ impl ActionImplementation {
}
}
}
pub struct NoOutput;
impl<'de> Deserialize<'de> for NoOutput {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
Ok(NoOutput)
}
}

View File

@@ -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<String, DiskInfo>);
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<String>,
pub partitions: IndexMap<String, PartitionInfo>,
logicalname: PathBuf,
partitions: Vec<PartitionInfo>,
capacity: usize,
embassy_os: Option<PartitionInfo>,
}
#[derive(Clone, Debug, Deserialize, Serialize)]
#[serde(rename_all = "kebab-case")]
pub struct PartitionInfo {
pub is_mounted: bool,
pub size: Option<String>,
pub label: Option<String>,
logicalname: PathBuf,
label: Option<String>,
capacity: usize,
used: Option<usize>,
}
pub async fn list() -> Result<Disks, Error> {
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<P: AsRef<Path>>(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<P: AsRef<Path>>(path: P) -> Result<usize, Error> {
Ok(String::from_utf8(
Command::new("blockdev")
.arg("--getsize64")
.arg(path.as_ref())
.invoke(crate::ErrorKind::BlockDev)
.await?,
)?
.parse()?)
}
pub async fn get_label<P: AsRef<Path>>(path: P) -> Result<Option<String>, 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<P: AsRef<Path>>(path: P) -> Result<usize, Error> {
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<Vec<DiskInfo>, 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<P0: AsRef<Path>, P1: AsRef<Path>>(
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<P: AsRef<Path>>(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(())

View File

@@ -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",

View File

@@ -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<String>,
}
#[command(rpc_only, metadata(encrypted = true))]
pub fn status() -> Result<StatusRes, Error> {
// 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<String>,
capacity: usize,
used: Option<usize>,
recovery: Option<RecoveryInfo>,
}
#[derive(Debug, Deserialize, Serialize)]
#[serde(rename_all = "kebab-case")]
pub struct RecoveryInfo {
version: Version,
name: String,
}
#[command(rpc_only)]
pub fn list() -> Result<Vec<DiskInfo>, Error> {
todo!()
}

View File

@@ -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 },