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::parse::hex_nibble;
|
||||
|
||||
pub mod mountinfo;
|
||||
|
||||
/// POSIX sysconf call
|
||||
pub fn sysconf(name: i32) -> i64 {
|
||||
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