mirror of
https://github.com/Start9Labs/start-os.git
synced 2026-03-30 12:11:56 +00:00
list disks without blkid
This commit is contained in:
@@ -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'
|
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 ../..
|
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
|
cd embassy-os/appmgr
|
||||||
#rust-arm64-builder aarch64-linux-gnu-strip target/aarch64-unknown-linux-gnu/release/embassyd
|
#rust-arm64-builder aarch64-linux-gnu-strip target/aarch64-unknown-linux-gnu/release/embassyd
|
||||||
|
|||||||
@@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,139 +1,185 @@
|
|||||||
use std::path::Path;
|
use std::path::{Path, PathBuf};
|
||||||
|
|
||||||
use anyhow::anyhow;
|
use anyhow::anyhow;
|
||||||
use futures::future::try_join_all;
|
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 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 _};
|
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)]
|
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||||
#[serde(rename_all = "kebab-case")]
|
#[serde(rename_all = "kebab-case")]
|
||||||
pub struct DiskInfo {
|
pub struct DiskInfo {
|
||||||
pub size: String,
|
logicalname: PathBuf,
|
||||||
pub description: Option<String>,
|
partitions: Vec<PartitionInfo>,
|
||||||
pub partitions: IndexMap<String, PartitionInfo>,
|
capacity: usize,
|
||||||
|
embassy_os: Option<PartitionInfo>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, Deserialize, Serialize)]
|
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||||
#[serde(rename_all = "kebab-case")]
|
#[serde(rename_all = "kebab-case")]
|
||||||
pub struct PartitionInfo {
|
pub struct PartitionInfo {
|
||||||
pub is_mounted: bool,
|
logicalname: PathBuf,
|
||||||
pub size: Option<String>,
|
label: Option<String>,
|
||||||
pub label: Option<String>,
|
capacity: usize,
|
||||||
|
used: Option<usize>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn list() -> Result<Disks, Error> {
|
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||||
let output = tokio::process::Command::new("parted")
|
#[serde(rename_all = "kebab-case")]
|
||||||
.arg("-lm")
|
pub struct EmbassyOsDiskInfo {
|
||||||
.invoke(crate::ErrorKind::GParted)
|
version: Version,
|
||||||
.await?;
|
name: String,
|
||||||
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(),
|
|
||||||
))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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")
|
let is_mountpoint = tokio::process::Command::new("mountpoint")
|
||||||
.arg(mount_point.as_ref())
|
.arg(mount_point.as_ref())
|
||||||
.stdout(std::process::Stdio::null())
|
.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?;
|
tokio::fs::create_dir_all(&mount_point).await?;
|
||||||
let mount_output = tokio::process::Command::new("mount")
|
let mount_output = tokio::process::Command::new("mount")
|
||||||
.arg(logicalname)
|
.arg(logicalname.as_ref())
|
||||||
.arg(mount_point.as_ref())
|
.arg(mount_point.as_ref())
|
||||||
.output()
|
.output()
|
||||||
.await?;
|
.await?;
|
||||||
crate::ensure_code!(
|
crate::ensure_code!(
|
||||||
mount_output.status.success(),
|
mount_output.status.success(),
|
||||||
crate::ErrorKind::Filesystem,
|
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")
|
std::str::from_utf8(&mount_output.stderr).unwrap_or("Unknown Error")
|
||||||
);
|
);
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ pub enum ErrorKind {
|
|||||||
ParseS9pk = 18,
|
ParseS9pk = 18,
|
||||||
ParseUrl = 19,
|
ParseUrl = 19,
|
||||||
GParted = 20,
|
GParted = 20,
|
||||||
Blkid = 21,
|
BlockDev = 21,
|
||||||
InvalidOnionAddress = 22,
|
InvalidOnionAddress = 22,
|
||||||
Pack = 23,
|
Pack = 23,
|
||||||
ValidateS9pk = 24,
|
ValidateS9pk = 24,
|
||||||
@@ -79,7 +79,7 @@ impl ErrorKind {
|
|||||||
ParseS9pk => "S9PK Parsing Error",
|
ParseS9pk => "S9PK Parsing Error",
|
||||||
ParseUrl => "URL Parsing Error",
|
ParseUrl => "URL Parsing Error",
|
||||||
GParted => "GNU Parted Error",
|
GParted => "GNU Parted Error",
|
||||||
Blkid => "BLKID Error",
|
BlockDev => "BlockDev Error",
|
||||||
InvalidOnionAddress => "Invalid Onion Address",
|
InvalidOnionAddress => "Invalid Onion Address",
|
||||||
Pack => "Pack Error",
|
Pack => "Pack Error",
|
||||||
ValidateS9pk => "S9PK Validation Error",
|
ValidateS9pk => "S9PK Validation Error",
|
||||||
|
|||||||
@@ -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!()
|
||||||
|
}
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ use chrono::{DateTime, Utc};
|
|||||||
use indexmap::IndexMap;
|
use indexmap::IndexMap;
|
||||||
use serde::{Deserialize, Deserializer, Serialize};
|
use serde::{Deserialize, Deserializer, Serialize};
|
||||||
|
|
||||||
use crate::action::ActionImplementation;
|
use crate::action::{ActionImplementation, NoOutput};
|
||||||
use crate::context::RpcContext;
|
use crate::context::RpcContext;
|
||||||
use crate::id::Id;
|
use crate::id::Id;
|
||||||
use crate::s9pk::manifest::PackageId;
|
use crate::s9pk::manifest::PackageId;
|
||||||
@@ -97,7 +97,7 @@ impl HealthCheck {
|
|||||||
Ok(HealthCheckResult {
|
Ok(HealthCheckResult {
|
||||||
time: Utc::now(),
|
time: Utc::now(),
|
||||||
result: match res {
|
result: match res {
|
||||||
Ok(()) => HealthCheckResultVariant::Success,
|
Ok(NoOutput) => HealthCheckResultVariant::Success,
|
||||||
Err((59, _)) => HealthCheckResultVariant::Disabled,
|
Err((59, _)) => HealthCheckResultVariant::Disabled,
|
||||||
Err((60, _)) => HealthCheckResultVariant::Starting,
|
Err((60, _)) => HealthCheckResultVariant::Starting,
|
||||||
Err((61, message)) => HealthCheckResultVariant::Loading { message },
|
Err((61, message)) => HealthCheckResultVariant::Loading { message },
|
||||||
|
|||||||
Reference in New Issue
Block a user