Skip to content

Commit cf2bfcc

Browse files
blockdev: use lsblk --json output format
Fixes #516
1 parent 46f9093 commit cf2bfcc

File tree

2 files changed

+98
-115
lines changed

2 files changed

+98
-115
lines changed

src/bin/rdcore/rootmap.rs

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -65,8 +65,8 @@ pub fn rootmap(config: &RootmapConfig) -> Result<()> {
6565
})
6666
.context("appending rootmap kargs")?;
6767
eprintln!("Injected kernel arguments into BLS: {}", kargs.join(" "));
68-
// Note here we're not calling `zipl` on s390x; it will be called anyway on firstboot by
69-
// `coreos-ignition-firstboot-complete.service`, so might as well batch them.
68+
// Note here we're not calling `zipl` on s390x; it will be called anyway on firstboot by
69+
// `coreos-ignition-firstboot-complete.service`, so might as well batch them.
7070
} else {
7171
// without /boot options, we just print the kargs; note we output to stdout here
7272
println!("{}", kargs.join(" "));
@@ -83,13 +83,13 @@ pub fn get_boot_mount_from_cmdline_args(
8383
if let Some(path) = boot_mount {
8484
Ok(Some(Mount::from_existing(path)?))
8585
} else if let Some(devpath) = boot_device {
86-
let devinfo = lsblk_single(Path::new(devpath))?;
86+
let devinfo = Device::lsblk(Path::new(devpath), false)?;
8787
let fs = devinfo
88-
.get("FSTYPE")
89-
.with_context(|| format!("failed to query filesystem for {}", devpath))?;
88+
.fstype
89+
.ok_or_else(|| anyhow::anyhow!("failed to query filesystem for {}", devpath))?;
9090
Ok(Some(Mount::try_mount(
9191
devpath,
92-
fs,
92+
&fs,
9393
mount::MsFlags::empty(),
9494
)?))
9595
} else {
@@ -98,19 +98,19 @@ pub fn get_boot_mount_from_cmdline_args(
9898
}
9999

100100
fn device_to_kargs(root: &Mount, device: PathBuf) -> Result<Option<Vec<String>>> {
101-
let blkinfo = lsblk_single(&device)?;
102-
let blktype = blkinfo
103-
.get("TYPE")
104-
.with_context(|| format!("missing TYPE for {}", device.display()))?;
101+
let blkinfo = Device::lsblk(&device, false)?;
102+
let blktypeinfo = blkinfo
103+
.blktype
104+
.ok_or_else(|| anyhow::anyhow!("missing type for {}", device.display()))?;
105105
// a `match {}` construct would be nice here, but for RAID it's a prefix match
106-
if blktype.starts_with("raid") {
106+
if blktypeinfo.starts_with("raid") {
107107
Ok(Some(get_raid_kargs(&device)?))
108-
} else if blktype == "crypt" {
108+
} else if blktypeinfo == "crypt" {
109109
Ok(Some(get_luks_kargs(root, &device)?))
110-
} else if blktype == "part" || blktype == "disk" || blktype == "mpath" {
110+
} else if blktypeinfo == "part" || blktypeinfo == "disk" || blktypeinfo == "mpath" {
111111
Ok(None)
112112
} else {
113-
bail!("unknown block device type {}", blktype)
113+
bail!("unknown block device type {}", blktypeinfo)
114114
}
115115
}
116116

src/blockdev.rs

Lines changed: 84 additions & 101 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ use gptman::{GPTPartitionEntry, GPT};
1717
use nix::sys::stat::{major, minor};
1818
use nix::{errno::Errno, mount, sched};
1919
use regex::Regex;
20-
use std::collections::HashMap;
20+
use serde::Deserialize;
2121
use std::convert::TryInto;
2222
use std::env;
2323
use std::fs::{
@@ -42,6 +42,50 @@ use crate::util::*;
4242

4343
use crate::{runcmd, runcmd_output};
4444

45+
#[derive(Debug, Deserialize)]
46+
struct DevicesOutput {
47+
blockdevices: Vec<Device>,
48+
}
49+
50+
#[derive(Debug, Deserialize)]
51+
pub struct Device {
52+
pub name: String,
53+
pub label: Option<String>,
54+
pub fstype: Option<String>,
55+
#[serde(rename = "type")]
56+
pub blktype: Option<String>,
57+
pub mountpoint: Option<String>,
58+
pub uuid: Option<String>,
59+
pub children: Option<Vec<Device>>,
60+
}
61+
62+
impl Device {
63+
pub fn lsblk(dev: &Path, with_children: bool) -> Result<Device> {
64+
let mut cmd = Command::new("lsblk");
65+
cmd.args(&[
66+
"-J",
67+
"--paths",
68+
"-o",
69+
"NAME,LABEL,FSTYPE,TYPE,MOUNTPOINT,UUID",
70+
])
71+
.arg(dev);
72+
if !with_children {
73+
cmd.arg("--nodeps");
74+
}
75+
let output = cmd_output(&mut cmd)?;
76+
let devs: DevicesOutput = serde_json::from_str(&output)?;
77+
if devs.blockdevices.len() > 1 {
78+
bail!("found more than one device for {:?}", dev);
79+
}
80+
let devinfo = devs
81+
.blockdevices
82+
.into_iter()
83+
.next()
84+
.ok_or_else(|| anyhow!("failed to get device information"))?;
85+
Ok(devinfo)
86+
}
87+
}
88+
4589
#[derive(Debug)]
4690
pub struct Disk {
4791
pub path: String,
@@ -100,29 +144,45 @@ impl Disk {
100144
}
101145
}
102146

147+
fn compute_partition(&self, devinfo: &Device) -> Result<Vec<Partition>> {
148+
let mut result: Vec<Partition> = Vec::new();
149+
// Only return partitions. Skip the whole-disk device, as well
150+
// as holders like LVM or RAID devices using one of the partitions.
151+
if !(devinfo.blktype != Some("part".to_string())) {
152+
let (mountpoint, swap) = match &devinfo.mountpoint {
153+
Some(mp) if mp == "[SWAP]" => (None, true),
154+
Some(mp) => (Some(mp.to_string()), false),
155+
None => (None, false),
156+
};
157+
result.push(Partition {
158+
path: devinfo.name.to_owned(),
159+
label: devinfo.label.clone(),
160+
fstype: devinfo.fstype.clone(),
161+
parent: self.path.to_owned(),
162+
mountpoint,
163+
swap,
164+
});
165+
}
166+
167+
Ok(result)
168+
}
169+
103170
fn get_partitions(&self) -> Result<Vec<Partition>> {
104171
// walk each device in the output
105172
let mut result: Vec<Partition> = Vec::new();
106-
for devinfo in lsblk(Path::new(&self.path), true)? {
107-
if let Some(name) = devinfo.get("NAME") {
108-
// Only return partitions. Skip the whole-disk device, as well
109-
// as holders like LVM or RAID devices using one of the partitions.
110-
if devinfo.get("TYPE").map(|s| s.as_str()) != Some("part") {
111-
continue;
173+
let deviceinfo = Device::lsblk(Path::new(&self.path), true)?;
174+
let mut partition = self.compute_partition(&deviceinfo)?;
175+
if !partition.is_empty() {
176+
result.append(&mut partition);
177+
}
178+
if let Some(children) = deviceinfo.children.as_ref() {
179+
if !children.is_empty() {
180+
for child in children {
181+
let mut childpartition = self.compute_partition(child)?;
182+
if !childpartition.is_empty() {
183+
result.append(&mut childpartition);
184+
}
112185
}
113-
let (mountpoint, swap) = match devinfo.get("MOUNTPOINT") {
114-
Some(mp) if mp == "[SWAP]" => (None, true),
115-
Some(mp) => (Some(mp.to_string()), false),
116-
None => (None, false),
117-
};
118-
result.push(Partition {
119-
path: name.to_owned(),
120-
label: devinfo.get("LABEL").map(<_>::to_string),
121-
fstype: devinfo.get("FSTYPE").map(<_>::to_string),
122-
parent: self.path.to_owned(),
123-
mountpoint,
124-
swap,
125-
});
126186
}
127187
}
128188
Ok(result)
@@ -493,11 +553,10 @@ impl Mount {
493553
}
494554

495555
pub fn get_filesystem_uuid(&self) -> Result<String> {
496-
let devinfo = lsblk_single(Path::new(&self.device))?;
497-
devinfo
498-
.get("UUID")
499-
.map(String::from)
500-
.with_context(|| format!("filesystem {} has no UUID", self.device))
556+
let uuid = Device::lsblk(Path::new(&self.device), false)?
557+
.uuid
558+
.ok_or_else(|| anyhow!("failed to get uuid"))?;
559+
Ok(uuid)
501560
}
502561
}
503562

@@ -800,46 +859,6 @@ fn read_sysfs_dev_block_value(maj: u64, min: u64, field: &str) -> Result<String>
800859
Ok(read_to_string(&path)?.trim_end().into())
801860
}
802861

803-
pub fn lsblk_single(dev: &Path) -> Result<HashMap<String, String>> {
804-
let mut devinfos = lsblk(Path::new(dev), false)?;
805-
if devinfos.is_empty() {
806-
// this should never happen because `lsblk` itself would've failed
807-
bail!("no lsblk results for {}", dev.display());
808-
}
809-
Ok(devinfos.remove(0))
810-
}
811-
812-
pub fn lsblk(dev: &Path, with_deps: bool) -> Result<Vec<HashMap<String, String>>> {
813-
let mut cmd = Command::new("lsblk");
814-
// Older lsblk, e.g. in CentOS 7.6, doesn't support PATH, but --paths option
815-
cmd.arg("--pairs")
816-
.arg("--paths")
817-
.arg("--output")
818-
.arg("NAME,LABEL,FSTYPE,TYPE,MOUNTPOINT,UUID")
819-
.arg(dev);
820-
if !with_deps {
821-
cmd.arg("--nodeps");
822-
}
823-
let output = cmd_output(&mut cmd)?;
824-
let mut result: Vec<HashMap<String, String>> = Vec::new();
825-
for line in output.lines() {
826-
// parse key-value pairs
827-
result.push(split_lsblk_line(line));
828-
}
829-
Ok(result)
830-
}
831-
832-
/// Parse key-value pairs from lsblk --pairs.
833-
/// Newer versions of lsblk support JSON but the one in CentOS 7 doesn't.
834-
fn split_lsblk_line(line: &str) -> HashMap<String, String> {
835-
let re = Regex::new(r#"([A-Z-]+)="([^"]+)""#).unwrap();
836-
let mut fields: HashMap<String, String> = HashMap::new();
837-
for cap in re.captures_iter(line) {
838-
fields.insert(cap[1].to_string(), cap[2].to_string());
839-
}
840-
fields
841-
}
842-
843862
pub fn get_blkdev_deps(device: &Path) -> Result<Vec<PathBuf>> {
844863
let deps = {
845864
let mut p = PathBuf::from("/sys/block");
@@ -1028,46 +1047,10 @@ mod ioctl {
10281047
#[cfg(test)]
10291048
mod tests {
10301049
use super::*;
1031-
use maplit::hashmap;
10321050
use std::io::copy;
10331051
use tempfile::tempfile;
10341052
use xz2::read::XzDecoder;
10351053

1036-
#[test]
1037-
fn lsblk_split() {
1038-
assert_eq!(
1039-
split_lsblk_line(r#"NAME="sda" LABEL="" FSTYPE="""#),
1040-
hashmap! {
1041-
String::from("NAME") => String::from("sda"),
1042-
}
1043-
);
1044-
assert_eq!(
1045-
split_lsblk_line(r#"NAME="sda1" LABEL="" FSTYPE="vfat""#),
1046-
hashmap! {
1047-
String::from("NAME") => String::from("sda1"),
1048-
String::from("FSTYPE") => String::from("vfat")
1049-
}
1050-
);
1051-
assert_eq!(
1052-
split_lsblk_line(r#"NAME="sda2" LABEL="boot" FSTYPE="ext4""#),
1053-
hashmap! {
1054-
String::from("NAME") => String::from("sda2"),
1055-
String::from("LABEL") => String::from("boot"),
1056-
String::from("FSTYPE") => String::from("ext4"),
1057-
}
1058-
);
1059-
assert_eq!(
1060-
split_lsblk_line(r#"NAME="sda3" LABEL="foo=\x22bar\x22 baz" FSTYPE="ext4""#),
1061-
hashmap! {
1062-
String::from("NAME") => String::from("sda3"),
1063-
// for now, we don't care about resolving lsblk's hex escapes,
1064-
// so we just pass them through
1065-
String::from("LABEL") => String::from(r#"foo=\x22bar\x22 baz"#),
1066-
String::from("FSTYPE") => String::from("ext4"),
1067-
}
1068-
);
1069-
}
1070-
10711054
#[test]
10721055
fn disk_sector_size_reader() {
10731056
struct Test {

0 commit comments

Comments
 (0)