sys: add /proc/self/mountinfo parsing
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
This commit is contained in:
parent
e128fc879f
commit
40da8098df
@ -15,6 +15,8 @@ use nix::unistd::Pid;
|
|||||||
use proxmox_tools::fs::file_read_firstline;
|
use proxmox_tools::fs::file_read_firstline;
|
||||||
use proxmox_tools::parse::hex_nibble;
|
use proxmox_tools::parse::hex_nibble;
|
||||||
|
|
||||||
|
pub mod mountinfo;
|
||||||
|
|
||||||
/// POSIX sysconf call
|
/// POSIX sysconf call
|
||||||
pub fn sysconf(name: i32) -> i64 {
|
pub fn sysconf(name: i32) -> i64 {
|
||||||
extern "C" {
|
extern "C" {
|
||||||
|
272
proxmox-sys/src/linux/procfs/mountinfo.rs
Normal file
272
proxmox-sys/src/linux/procfs/mountinfo.rs
Normal file
@ -0,0 +1,272 @@
|
|||||||
|
//! `/proc/PID/mountinfo` handling.
|
||||||
|
|
||||||
|
use std::ffi::{OsStr, OsString};
|
||||||
|
use std::os::unix::ffi::OsStrExt;
|
||||||
|
use std::path::PathBuf;
|
||||||
|
use std::str::FromStr;
|
||||||
|
|
||||||
|
use failure::{bail, format_err, Error};
|
||||||
|
use nix::sys::stat;
|
||||||
|
use nix::unistd::Pid;
|
||||||
|
|
||||||
|
/// A mount ID as found within `/proc/PID/mountinfo`.
|
||||||
|
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
|
||||||
|
#[repr(transparent)]
|
||||||
|
pub struct MountId(usize);
|
||||||
|
|
||||||
|
impl FromStr for MountId {
|
||||||
|
type Err = <usize as FromStr>::Err;
|
||||||
|
|
||||||
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||||
|
s.parse().map(Self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A device node entry (major:minor). This is a more strongly typed version of dev_t.
|
||||||
|
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
|
||||||
|
pub struct Device {
|
||||||
|
major: u32,
|
||||||
|
minor: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Device {
|
||||||
|
pub fn from_dev_t(dev: stat::dev_t) -> Self {
|
||||||
|
Self {
|
||||||
|
major: stat::major(dev) as u32,
|
||||||
|
minor: stat::minor(dev) as u32,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn into_dev_t(self) -> stat::dev_t {
|
||||||
|
stat::makedev(u64::from(self.major), u64::from(self.minor))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FromStr for Device {
|
||||||
|
type Err = Error;
|
||||||
|
|
||||||
|
fn from_str(s: &str) -> Result<Self, Error> {
|
||||||
|
let (major, minor) = s.split_at(
|
||||||
|
s.find(':')
|
||||||
|
.ok_or_else(|| format_err!("expected 'major:minor' format"))?,
|
||||||
|
);
|
||||||
|
Ok(Self {
|
||||||
|
major: major.parse()?,
|
||||||
|
minor: minor[1..].parse()?,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
#[cfg_attr(test, derive(Eq, PartialEq))]
|
||||||
|
pub struct Tag {
|
||||||
|
pub tag: OsString,
|
||||||
|
pub value: Option<OsString>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Tag {
|
||||||
|
fn parse(tag: &[u8]) -> Result<Self, Error> {
|
||||||
|
Ok(match tag.iter().position(|b| *b == b':') {
|
||||||
|
Some(pos) => {
|
||||||
|
let (tag, value) = tag.split_at(pos);
|
||||||
|
Self {
|
||||||
|
tag: OsStr::from_bytes(tag).to_owned(),
|
||||||
|
value: Some(OsStr::from_bytes(&value[1..]).to_owned()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None => Self {
|
||||||
|
tag: OsStr::from_bytes(tag).to_owned(),
|
||||||
|
value: None,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Entry {
|
||||||
|
/// unique identifier of the mount (may be reused after being unmounted)
|
||||||
|
pub id: MountId,
|
||||||
|
|
||||||
|
/// id of the parent (or of self for the top of the mount tree)
|
||||||
|
pub parent: MountId,
|
||||||
|
|
||||||
|
/// value of st_dev for files on this file system
|
||||||
|
pub device: Device,
|
||||||
|
|
||||||
|
/// root of the mount within the file system
|
||||||
|
pub root: PathBuf,
|
||||||
|
|
||||||
|
/// mount point relative to the process' root
|
||||||
|
pub mount_point: PathBuf,
|
||||||
|
|
||||||
|
/// per-mount options
|
||||||
|
pub mount_options: OsString,
|
||||||
|
|
||||||
|
/// tags
|
||||||
|
pub tags: Vec<Tag>,
|
||||||
|
|
||||||
|
/// Name of the file system in the form "type[.subtype]".
|
||||||
|
pub fs_type: String,
|
||||||
|
|
||||||
|
/// File system specific mount source information.
|
||||||
|
pub mount_source: Option<OsString>,
|
||||||
|
|
||||||
|
/// superblock options
|
||||||
|
pub super_options: OsString,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Entry {
|
||||||
|
/// Parse a line from a `mountinfo` file.
|
||||||
|
pub fn parse(line: &[u8]) -> Result<Self, Error> {
|
||||||
|
let mut parts = line.split(u8::is_ascii_whitespace);
|
||||||
|
|
||||||
|
let mut next = || {
|
||||||
|
parts
|
||||||
|
.next()
|
||||||
|
.ok_or_else(|| format_err!("incomplete mountinfo line"))
|
||||||
|
};
|
||||||
|
|
||||||
|
let this = Self {
|
||||||
|
id: std::str::from_utf8(next()?)?.parse()?,
|
||||||
|
parent: std::str::from_utf8(next()?)?.parse()?,
|
||||||
|
device: std::str::from_utf8(next()?)?.parse()?,
|
||||||
|
root: OsStr::from_bytes(next()?).to_owned().into(),
|
||||||
|
mount_point: OsStr::from_bytes(next()?).to_owned().into(),
|
||||||
|
mount_options: OsStr::from_bytes(next()?).to_owned(),
|
||||||
|
tags: next()?.split(|b| *b == b',').try_fold(
|
||||||
|
Vec::new(),
|
||||||
|
|mut acc, tag| -> Result<_, Error> {
|
||||||
|
acc.push(Tag::parse(tag)?);
|
||||||
|
Ok(acc)
|
||||||
|
},
|
||||||
|
)?,
|
||||||
|
fs_type: std::str::from_utf8({
|
||||||
|
next()?;
|
||||||
|
next()?
|
||||||
|
})?
|
||||||
|
.to_string(),
|
||||||
|
mount_source: next().map(|src| match src {
|
||||||
|
b"none" => None,
|
||||||
|
other => Some(OsStr::from_bytes(other).to_owned()),
|
||||||
|
})?,
|
||||||
|
super_options: OsStr::from_bytes(next()?).to_owned(),
|
||||||
|
};
|
||||||
|
|
||||||
|
if parts.next().is_some() {
|
||||||
|
bail!("excess data in mountinfo line");
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(this)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Add some structure to this? Eg. sort by parent/child relation? Make a tree?
|
||||||
|
/// Mount info found in `/proc/PID/mountinfo`.
|
||||||
|
pub struct MountInfo {
|
||||||
|
entries: Vec<Entry>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub type Iter<'a> = std::slice::Iter<'a, Entry>;
|
||||||
|
|
||||||
|
impl MountInfo {
|
||||||
|
/// Read the current mount point information.
|
||||||
|
pub fn read() -> Result<Self, Error> {
|
||||||
|
Self::parse(&std::fs::read("/proc/self/mountinfo")?)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Read the mount point information of a specific pid.
|
||||||
|
pub fn read_for_pid(pid: Pid) -> Result<Self, Error> {
|
||||||
|
Self::parse(&std::fs::read(format!("/proc/{}/mountinfo", pid))?)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Parse a `mountinfo` file.
|
||||||
|
pub fn parse(statstr: &[u8]) -> Result<Self, Error> {
|
||||||
|
let entries = statstr.split(|b| *b == b'\n').try_fold(
|
||||||
|
Vec::new(),
|
||||||
|
|mut acc, line| -> Result<_, Error> {
|
||||||
|
acc.push(Entry::parse(line)?);
|
||||||
|
Ok(acc)
|
||||||
|
},
|
||||||
|
)?;
|
||||||
|
|
||||||
|
Ok(Self { entries })
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Iterate over mount entries.
|
||||||
|
pub fn iter(&self) -> Iter {
|
||||||
|
self.entries.iter()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_entry() {
|
||||||
|
use std::path::Path;
|
||||||
|
|
||||||
|
let l1: &[u8] =
|
||||||
|
b"48 32 0:43 / /sys/fs/cgroup/blkio rw,nosuid,nodev,noexec,relatime shared:26 - cgroup \
|
||||||
|
cgroup rw,blkio";
|
||||||
|
let entry = Entry::parse(l1).expect("failed to parse first mountinfo test entry");
|
||||||
|
|
||||||
|
assert_eq!(entry.id, MountId(48));
|
||||||
|
assert_eq!(entry.parent, MountId(32));
|
||||||
|
assert_eq!(
|
||||||
|
entry.device,
|
||||||
|
Device {
|
||||||
|
major: 0,
|
||||||
|
minor: 43,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
assert_eq!(entry.root, Path::new("/"));
|
||||||
|
assert_eq!(entry.mount_point, Path::new("/sys/fs/cgroup/blkio"));
|
||||||
|
assert_eq!(
|
||||||
|
entry.mount_options,
|
||||||
|
OsStr::new("rw,nosuid,nodev,noexec,relatime")
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
entry.tags,
|
||||||
|
&[Tag {
|
||||||
|
tag: OsString::from("shared"),
|
||||||
|
value: Some(OsString::from("26")),
|
||||||
|
}]
|
||||||
|
);
|
||||||
|
assert_eq!(entry.fs_type, "cgroup");
|
||||||
|
assert_eq!(
|
||||||
|
entry.mount_source.as_ref().map(|s| s.as_os_str()),
|
||||||
|
Some(OsStr::new("cgroup"))
|
||||||
|
);
|
||||||
|
assert_eq!(entry.super_options, "rw,blkio");
|
||||||
|
|
||||||
|
let l2 = b"49 28 0:44 / /proxmox/debian rw,relatime shared:27 - autofs systemd-1 \
|
||||||
|
rw,fd=26,pgrp=1,timeout=0,minproto=5,maxproto=5,direct,pipe_ino=27726";
|
||||||
|
let entry = Entry::parse(l2).expect("failed to parse second mountinfo test entry");
|
||||||
|
assert_eq!(entry.id, MountId(49));
|
||||||
|
assert_eq!(entry.parent, MountId(28));
|
||||||
|
assert_eq!(
|
||||||
|
entry.device,
|
||||||
|
Device {
|
||||||
|
major: 0,
|
||||||
|
minor: 44,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
assert_eq!(entry.root, Path::new("/"));
|
||||||
|
assert_eq!(entry.mount_point, Path::new("/proxmox/debian"));
|
||||||
|
assert_eq!(entry.mount_options, OsStr::new("rw,relatime"));
|
||||||
|
assert_eq!(
|
||||||
|
entry.tags,
|
||||||
|
&[Tag {
|
||||||
|
tag: OsString::from("shared"),
|
||||||
|
value: Some(OsString::from("27")),
|
||||||
|
}]
|
||||||
|
);
|
||||||
|
assert_eq!(entry.fs_type, "autofs");
|
||||||
|
assert_eq!(
|
||||||
|
entry.mount_source.as_ref().map(|s| s.as_os_str()),
|
||||||
|
Some(OsStr::new("systemd-1"))
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
entry.super_options,
|
||||||
|
"rw,fd=26,pgrp=1,timeout=0,minproto=5,maxproto=5,direct,pipe_ino=27726"
|
||||||
|
);
|
||||||
|
|
||||||
|
let mount_info = [l1, l2].join(&b"\n"[..]);
|
||||||
|
MountInfo::parse(&mount_info).expect("failed to parse mount info file");
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user