Skip to content

Commit 90fccd4

Browse files
src/* : use lsblk --json output format
Fixes: #516
1 parent 7338d48 commit 90fccd4

File tree

2 files changed

+125
-124
lines changed

2 files changed

+125
-124
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: 111 additions & 110 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,47 @@ 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+
let devinfo = devs
78+
.blockdevices
79+
.into_iter()
80+
.next()
81+
.ok_or_else(|| anyhow!("failed to get device information"))?;
82+
Ok(devinfo)
83+
}
84+
}
85+
4586
#[derive(Debug)]
4687
pub struct Disk {
4788
pub path: String,
@@ -105,37 +146,72 @@ impl Disk {
105146
}
106147
}
107148

149+
fn validate_blktype(&self, blktype: &Option<String>, with_holders: bool) -> bool {
150+
if let Some(blktype) = &blktype {
151+
match blktype.as_str() {
152+
// If whole-disk device, skip.
153+
"disk" => false,
154+
// If partition, allow.
155+
"part" => true,
156+
// If with_holders is true, allow anything else.
157+
_ if with_holders => true,
158+
// Default case
159+
_ => false,
160+
}
161+
} else {
162+
false
163+
}
164+
}
165+
166+
fn compute_partition(&self, devinfo: &Device, with_holders: bool) -> Result<Vec<Partition>> {
167+
let mut result: Vec<Partition> = Vec::new();
168+
let isblktypeavailable: bool = self.validate_blktype(&devinfo.blktype, with_holders);
169+
if isblktypeavailable {
170+
let (mountpoint, swap) = match &devinfo.mountpoint {
171+
Some(mp) if mp == "[SWAP]" => (None, true),
172+
Some(mp) => (Some(mp.to_string()), false),
173+
None => (None, false),
174+
};
175+
result.push(Partition {
176+
path: devinfo.name.to_owned(),
177+
label: devinfo.label.clone(),
178+
fstype: devinfo.fstype.clone(),
179+
parent: self.path.to_owned(),
180+
mountpoint,
181+
swap,
182+
});
183+
}
184+
Ok(result)
185+
}
186+
108187
fn get_partitions(&self, with_holders: bool) -> Result<Vec<Partition>> {
109188
// walk each device in the output
110189
let mut result: Vec<Partition> = Vec::new();
111-
for devinfo in lsblk(Path::new(&self.path), true)? {
112-
if let Some(name) = devinfo.get("NAME") {
113-
match devinfo.get("TYPE") {
114-
// If unknown type, skip.
115-
None => continue,
116-
// If whole-disk device, skip.
117-
Some(t) if t == &"disk".to_string() => continue,
118-
// If partition, allow.
119-
Some(t) if t == &"part".to_string() => (),
120-
// If with_holders is true, allow anything else.
121-
Some(_) if with_holders => (),
122-
// Ignore LVM or RAID devices which are using one of the
123-
// partitions but aren't a partition themselves.
124-
_ => continue,
125-
};
126-
let (mountpoint, swap) = match devinfo.get("MOUNTPOINT") {
127-
Some(mp) if mp == "[SWAP]" => (None, true),
128-
Some(mp) => (Some(mp.to_string()), false),
129-
None => (None, false),
130-
};
131-
result.push(Partition {
132-
path: name.to_owned(),
133-
label: devinfo.get("LABEL").map(<_>::to_string),
134-
fstype: devinfo.get("FSTYPE").map(<_>::to_string),
135-
parent: self.path.to_owned(),
136-
mountpoint,
137-
swap,
138-
});
190+
let deviceinfo = Device::lsblk(Path::new(&self.path), true)?;
191+
let mut partition = self.compute_partition(&deviceinfo, with_holders)?;
192+
if !partition.is_empty() {
193+
result.append(&mut partition);
194+
}
195+
let mut childpartitioninfo = self.get_partitions_recurse(&deviceinfo, with_holders)?;
196+
if !childpartitioninfo.is_empty() {
197+
result.append(&mut childpartitioninfo);
198+
}
199+
Ok(result)
200+
}
201+
202+
fn get_partitions_recurse(
203+
&self,
204+
deviceinfo: &Device,
205+
with_holders: bool,
206+
) -> Result<Vec<Partition>> {
207+
let mut result: Vec<Partition> = Vec::new();
208+
if let Some(children) = deviceinfo.children.as_ref() {
209+
if !children.is_empty() {
210+
for child in children {
211+
let mut childpartitions = self.compute_partition(child, with_holders)?;
212+
result.append(&mut childpartitions);
213+
self.get_partitions_recurse(child, with_holders)?;
214+
}
139215
}
140216
}
141217
Ok(result)
@@ -506,11 +582,12 @@ impl Mount {
506582
}
507583

508584
pub fn get_filesystem_uuid(&self) -> Result<String> {
509-
let devinfo = lsblk_single(Path::new(&self.device))?;
510-
devinfo
511-
.get("UUID")
512-
.map(String::from)
513-
.with_context(|| format!("filesystem {} has no UUID", self.device))
585+
let devinfo = Device::lsblk(Path::new(&self.device), false)?;
586+
let uuid = devinfo
587+
.uuid
588+
.as_ref()
589+
.ok_or_else(|| anyhow!("missing uuid in {}", devinfo.name))?;
590+
Ok(uuid.to_string())
514591
}
515592
}
516593

@@ -813,46 +890,6 @@ fn read_sysfs_dev_block_value(maj: u64, min: u64, field: &str) -> Result<String>
813890
Ok(read_to_string(&path)?.trim_end().into())
814891
}
815892

816-
pub fn lsblk_single(dev: &Path) -> Result<HashMap<String, String>> {
817-
let mut devinfos = lsblk(Path::new(dev), false)?;
818-
if devinfos.is_empty() {
819-
// this should never happen because `lsblk` itself would've failed
820-
bail!("no lsblk results for {}", dev.display());
821-
}
822-
Ok(devinfos.remove(0))
823-
}
824-
825-
pub fn lsblk(dev: &Path, with_deps: bool) -> Result<Vec<HashMap<String, String>>> {
826-
let mut cmd = Command::new("lsblk");
827-
// Older lsblk, e.g. in CentOS 7.6, doesn't support PATH, but --paths option
828-
cmd.arg("--pairs")
829-
.arg("--paths")
830-
.arg("--output")
831-
.arg("NAME,LABEL,FSTYPE,TYPE,MOUNTPOINT,UUID")
832-
.arg(dev);
833-
if !with_deps {
834-
cmd.arg("--nodeps");
835-
}
836-
let output = cmd_output(&mut cmd)?;
837-
let mut result: Vec<HashMap<String, String>> = Vec::new();
838-
for line in output.lines() {
839-
// parse key-value pairs
840-
result.push(split_lsblk_line(line));
841-
}
842-
Ok(result)
843-
}
844-
845-
/// Parse key-value pairs from lsblk --pairs.
846-
/// Newer versions of lsblk support JSON but the one in CentOS 7 doesn't.
847-
fn split_lsblk_line(line: &str) -> HashMap<String, String> {
848-
let re = Regex::new(r#"([A-Z-]+)="([^"]+)""#).unwrap();
849-
let mut fields: HashMap<String, String> = HashMap::new();
850-
for cap in re.captures_iter(line) {
851-
fields.insert(cap[1].to_string(), cap[2].to_string());
852-
}
853-
fields
854-
}
855-
856893
pub fn get_blkdev_deps(device: &Path) -> Result<Vec<PathBuf>> {
857894
let deps = {
858895
let mut p = PathBuf::from("/sys/block");
@@ -1041,46 +1078,10 @@ mod ioctl {
10411078
#[cfg(test)]
10421079
mod tests {
10431080
use super::*;
1044-
use maplit::hashmap;
10451081
use std::io::copy;
10461082
use tempfile::tempfile;
10471083
use xz2::read::XzDecoder;
10481084

1049-
#[test]
1050-
fn lsblk_split() {
1051-
assert_eq!(
1052-
split_lsblk_line(r#"NAME="sda" LABEL="" FSTYPE="""#),
1053-
hashmap! {
1054-
String::from("NAME") => String::from("sda"),
1055-
}
1056-
);
1057-
assert_eq!(
1058-
split_lsblk_line(r#"NAME="sda1" LABEL="" FSTYPE="vfat""#),
1059-
hashmap! {
1060-
String::from("NAME") => String::from("sda1"),
1061-
String::from("FSTYPE") => String::from("vfat")
1062-
}
1063-
);
1064-
assert_eq!(
1065-
split_lsblk_line(r#"NAME="sda2" LABEL="boot" FSTYPE="ext4""#),
1066-
hashmap! {
1067-
String::from("NAME") => String::from("sda2"),
1068-
String::from("LABEL") => String::from("boot"),
1069-
String::from("FSTYPE") => String::from("ext4"),
1070-
}
1071-
);
1072-
assert_eq!(
1073-
split_lsblk_line(r#"NAME="sda3" LABEL="foo=\x22bar\x22 baz" FSTYPE="ext4""#),
1074-
hashmap! {
1075-
String::from("NAME") => String::from("sda3"),
1076-
// for now, we don't care about resolving lsblk's hex escapes,
1077-
// so we just pass them through
1078-
String::from("LABEL") => String::from(r#"foo=\x22bar\x22 baz"#),
1079-
String::from("FSTYPE") => String::from("ext4"),
1080-
}
1081-
);
1082-
}
1083-
10841085
#[test]
10851086
fn disk_sector_size_reader() {
10861087
struct Test {

0 commit comments

Comments
 (0)