1
0
Fork 0

Initial commit

master
Dustin 2020-01-17 19:15:13 -06:00
commit 636f7dd408
9 changed files with 839 additions and 0 deletions

1
.gitignore vendored Normal file
View File

@ -0,0 +1 @@
target/

277
Cargo.lock generated Normal file
View File

@ -0,0 +1,277 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
[[package]]
name = "ansi_term"
version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "atty"
version = "0.2.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"hermit-abi 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.2.66 (registry+https://github.com/rust-lang/crates.io-index)",
"winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "autocfg"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "bitflags"
version = "1.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "chrono"
version = "0.4.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"num-integer 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)",
"num-traits 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)",
"serde 1.0.104 (registry+https://github.com/rust-lang/crates.io-index)",
"time 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "clap"
version = "2.33.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"ansi_term 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)",
"atty 0.2.14 (registry+https://github.com/rust-lang/crates.io-index)",
"bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
"strsim 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)",
"textwrap 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)",
"unicode-width 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)",
"vec_map 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "hermit-abi"
version = "0.1.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"libc 0.2.66 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "itoa"
version = "0.4.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "libc"
version = "0.2.66"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "libudev"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"libc 0.2.66 (registry+https://github.com/rust-lang/crates.io-index)",
"libudev-sys 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "libudev-sys"
version = "0.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"libc 0.2.66 (registry+https://github.com/rust-lang/crates.io-index)",
"pkg-config 0.3.17 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "mizule"
version = "0.1.0"
dependencies = [
"chrono 0.4.10 (registry+https://github.com/rust-lang/crates.io-index)",
"clap 2.33.0 (registry+https://github.com/rust-lang/crates.io-index)",
"libudev 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
"serde 1.0.104 (registry+https://github.com/rust-lang/crates.io-index)",
"serde_json 1.0.44 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "num-integer"
version = "0.1.42"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"autocfg 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
"num-traits 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "num-traits"
version = "0.2.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"autocfg 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "pkg-config"
version = "0.3.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "proc-macro2"
version = "1.0.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"unicode-xid 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "quote"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"proc-macro2 1.0.8 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "redox_syscall"
version = "0.1.56"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "ryu"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "serde"
version = "1.0.104"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"serde_derive 1.0.104 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "serde_derive"
version = "1.0.104"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"proc-macro2 1.0.8 (registry+https://github.com/rust-lang/crates.io-index)",
"quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
"syn 1.0.13 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "serde_json"
version = "1.0.44"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"itoa 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)",
"ryu 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
"serde 1.0.104 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "strsim"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "syn"
version = "1.0.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"proc-macro2 1.0.8 (registry+https://github.com/rust-lang/crates.io-index)",
"quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
"unicode-xid 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "textwrap"
version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"unicode-width 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "time"
version = "0.1.42"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"libc 0.2.66 (registry+https://github.com/rust-lang/crates.io-index)",
"redox_syscall 0.1.56 (registry+https://github.com/rust-lang/crates.io-index)",
"winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "unicode-width"
version = "0.1.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "unicode-xid"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "vec_map"
version = "0.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "winapi"
version = "0.3.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
"winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "winapi-i686-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "winapi-x86_64-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
[metadata]
"checksum ansi_term 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ee49baf6cb617b853aa8d93bf420db2383fab46d314482ca2803b40d5fde979b"
"checksum atty 0.2.14 (registry+https://github.com/rust-lang/crates.io-index)" = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8"
"checksum autocfg 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "f8aac770f1885fd7e387acedd76065302551364496e46b3dd00860b2f8359b9d"
"checksum bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693"
"checksum chrono 0.4.10 (registry+https://github.com/rust-lang/crates.io-index)" = "31850b4a4d6bae316f7a09e691c944c28299298837edc0a03f755618c23cbc01"
"checksum clap 2.33.0 (registry+https://github.com/rust-lang/crates.io-index)" = "5067f5bb2d80ef5d68b4c87db81601f0b75bca627bc2ef76b141d7b846a3c6d9"
"checksum hermit-abi 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "eff2656d88f158ce120947499e971d743c05dbcbed62e5bd2f38f1698bbc3772"
"checksum itoa 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)" = "501266b7edd0174f8530248f87f99c88fbe60ca4ef3dd486835b8d8d53136f7f"
"checksum libc 0.2.66 (registry+https://github.com/rust-lang/crates.io-index)" = "d515b1f41455adea1313a4a2ac8a8a477634fbae63cc6100e3aebb207ce61558"
"checksum libudev 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ea626d3bdf40a1c5aee3bcd4f40826970cae8d80a8fec934c82a63840094dcfe"
"checksum libudev-sys 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "3c8469b4a23b962c1396b9b451dda50ef5b283e8dd309d69033475fa9b334324"
"checksum num-integer 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)" = "3f6ea62e9d81a77cd3ee9a2a5b9b609447857f3d358704331e4ef39eb247fcba"
"checksum num-traits 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)" = "c62be47e61d1842b9170f0fdeec8eba98e60e90e5446449a0545e5152acd7096"
"checksum pkg-config 0.3.17 (registry+https://github.com/rust-lang/crates.io-index)" = "05da548ad6865900e60eaba7f589cc0783590a92e940c26953ff81ddbab2d677"
"checksum proc-macro2 1.0.8 (registry+https://github.com/rust-lang/crates.io-index)" = "3acb317c6ff86a4e579dfa00fc5e6cca91ecbb4e7eb2df0468805b674eb88548"
"checksum quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "053a8c8bcc71fcce321828dc897a98ab9760bef03a4fc36693c231e5b3216cfe"
"checksum redox_syscall 0.1.56 (registry+https://github.com/rust-lang/crates.io-index)" = "2439c63f3f6139d1b57529d16bc3b8bb855230c8efcc5d3a896c8bea7c3b1e84"
"checksum ryu 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "bfa8506c1de11c9c4e4c38863ccbe02a305c8188e85a05a784c9e11e1c3910c8"
"checksum serde 1.0.104 (registry+https://github.com/rust-lang/crates.io-index)" = "414115f25f818d7dfccec8ee535d76949ae78584fc4f79a6f45a904bf8ab4449"
"checksum serde_derive 1.0.104 (registry+https://github.com/rust-lang/crates.io-index)" = "128f9e303a5a29922045a830221b8f78ec74a5f544944f3d5984f8ec3895ef64"
"checksum serde_json 1.0.44 (registry+https://github.com/rust-lang/crates.io-index)" = "48c575e0cc52bdd09b47f330f646cf59afc586e9c4e3ccd6fc1f625b8ea1dad7"
"checksum strsim 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a"
"checksum syn 1.0.13 (registry+https://github.com/rust-lang/crates.io-index)" = "1e4ff033220a41d1a57d8125eab57bf5263783dfdcc18688b1dacc6ce9651ef8"
"checksum textwrap 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060"
"checksum time 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)" = "db8dcfca086c1143c9270ac42a2bbd8a7ee477b78ac8e45b19abfb0cbede4b6f"
"checksum unicode-width 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)" = "caaa9d531767d1ff2150b9332433f32a24622147e5ebb1f26409d5da67afd479"
"checksum unicode-xid 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "826e7639553986605ec5979c7dd957c7895e93eabed50ab2ffa7f6128a75097c"
"checksum vec_map 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)" = "05c78687fb1a80548ae3250346c3db86a80a7cdd77bda190189f2d0a0987c81a"
"checksum winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)" = "8093091eeb260906a183e6ae1abdba2ef5ef2257a21801128899c3fc699229c6"
"checksum winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
"checksum winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"

19
Cargo.toml Normal file
View File

@ -0,0 +1,19 @@
[package]
name = "mizule"
version = "0.1.0"
authors = [
"Dustin C. Hatch <dustin@hatch.name>",
]
[dependencies]
clap = "2"
libudev = "0.2"
serde_json = "1.0"
[dependencies.chrono]
version = "0.4"
features = ["serde"]
[dependencies.serde]
version = "1.0"
features = ["derive"]

73
README.md Normal file
View File

@ -0,0 +1,73 @@
# Mizule
*Mizule* is a simple tool that checks the UUID of the filesystem mounted at
the specified path, and issues a warning if it has not changed within a given
time period (default 30 days). This is useful, for example, to remind backup
operators to switch out the backup disk periodically.
## Usage
Check `/var/spool/burp` and warn if it has not changed in the last 30 days:
```sh
mizule /var/spool/burp
```
Check `/var/spool/burp` and warn if it has not changed in the last 10 days,
sending an email to `burp-admin@example.org`:
```sh
mizule /var/spool/burp --ttl 30 --mailto burp-admin@example.org
```
## Cache File
*Mizule* keeps track of the UUID of each of the filesystems it has seen in a
cache file. This file is stored at `${XDG_CACHE_HOME}/mizule.json`. If the
`XDG_CACHE_HOME` environment variable is not set, `${HOME}/.cache` is used. If
the `HOME` environment variable is also not set, the cache file will be created
in the current working directory.
## Periodic Check with systemd Timer Unit
*Mizule* works best if it is scheduled to check the filesystem periodically.
One way to set up this schedule is to use a systemd timer unit.
`mizule@.service`:
```ini
[Unit]
Description=Check last filesystem change for %I
RequiresMountsFor=%I
After=network-online.target
Wants=network-online.target
[Service]
Type=oneshot
Environment=XDG_CACHE_HOME=/var/cache/mizule
ExecStart=/usr/local/bin/mizule %I --mailto burp-admin@example.org
```
`mizule@.timer`:
```ini
[Unit]
Description=Schedule filesystem check for %I
[Timer]
OnBootSec=5m
OnUnitInactiveSec=12h
[Install]
WantedBy=multi-user.target
```
To enable:
```sh
systemctl enable mizule@-var-spool-burp.timer
```
This will trigger the check 5 minutes after the machine boots, and then again
every 12 hours.

1
rustfmt.toml Normal file
View File

@ -0,0 +1 @@
max_width = 80

108
src/cache.rs Normal file
View File

@ -0,0 +1,108 @@
use chrono::Duration;
use chrono;
use serde::{Deserialize, Serialize};
use serde_json;
use std::collections::HashMap;
use std::fmt;
use std::fs;
use std::io;
use std::io::prelude::*;
#[derive(Serialize, Deserialize, Debug)]
pub struct CacheEntry {
uuid: String,
changed: chrono::DateTime<chrono::Utc>,
}
impl CacheEntry {
pub fn changed(&self) -> chrono::DateTime<chrono::Utc> {
self.changed
}
pub fn expired(&self, ttl: Duration) -> bool {
let now = chrono::Utc::now();
now > self.changed + ttl
}
}
#[derive(Serialize, Deserialize, Debug)]
pub struct Cache {
paths: HashMap<String, CacheEntry>,
}
#[derive(Debug)]
pub enum Error {
Io(io::Error),
Json(serde_json::Error),
}
impl Error {
pub fn message(&self) -> String {
match *self {
Self::Io(ref e) => format!("{}", e),
Self::Json(ref e) => format!("{}", e),
}
}
}
impl From<io::Error> for Error {
fn from(error: io::Error) -> Self {
Self::Io(error)
}
}
impl From<serde_json::Error> for Error {
fn from(error: serde_json::Error) -> Self {
Self::Json(error)
}
}
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.write_str(&self.message())
}
}
impl Cache {
pub fn new() -> Self {
Self {
paths: HashMap::new(),
}
}
pub fn load(path: &str) -> Result<Self, Error> {
if let Ok(mut file) = fs::File::open(path) {
let mut contents = String::new();
file.read_to_string(&mut contents)?;
let cache = serde_json::from_str(&contents);
match cache {
Ok(c) => Ok(c),
Err(e) => Err(e.into()),
}
} else {
Ok(Self::new())
}
}
pub fn get(&self, path: &str) -> Option<&CacheEntry> {
if let Some(entry) = self.paths.get(path) {
return Some(&entry);
}
None
}
pub fn update(&mut self, path: &str, uuid: &str) {
let entry = CacheEntry {
uuid: uuid.into(),
changed: chrono::Utc::now(),
};
self.paths.insert(path.into(), entry);
}
pub fn save(&self, path: &str) -> Result<(), Error> {
let mut file = fs::File::create(path)?;
let contents = serde_json::to_string(&self)?;
file.write_all(&contents.as_bytes())?;
Ok(())
}
}

51
src/error.rs Normal file
View File

@ -0,0 +1,51 @@
use std::fmt;
use std::io;
#[derive(Debug)]
pub enum Error {
Io(io::Error),
Udev(libudev::Error),
NotMounted,
NotFound,
InvalidUuid,
}
impl Error {
pub fn message(&self) -> String {
match *self {
Self::Io(ref e) => format!("{}", e),
Self::Udev(ref e) => format!("{}", e),
Self::NotMounted => "Path is not a mount point".into(),
Self::NotFound => "Device not found".into(),
Self::InvalidUuid => "Invalid device UUID".into(),
}
}
}
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.write_str(&self.message())
}
}
impl From<io::Error> for Error {
fn from(err: io::Error) -> Self {
Self::Io(err)
}
}
impl From<libudev::Error> for Error {
fn from(err: libudev::Error) -> Self {
Self::Udev(err)
}
}
impl From<Error> for io::Error {
fn from(error: Error) -> Self {
match error {
Error::Io(e) => e,
Error::Udev(e) => e.into(),
_ => io::Error::new(io::ErrorKind::Other, error.message()),
}
}
}

165
src/main.rs Normal file
View File

@ -0,0 +1,165 @@
extern crate chrono;
extern crate clap;
extern crate libudev;
extern crate serde;
extern crate serde_json;
use chrono::Duration;
use clap::App;
use clap::Arg;
use std::env;
use std::io::prelude::*;
use std::num::ParseIntError;
use std::path::PathBuf;
use std::process;
mod cache;
mod error;
mod mountinfo;
use mountinfo::get_fs_uuid;
const CACHE_FILENAME: &'static str = "mizule.json";
fn validate_int(v: String) -> Result<(), String> {
let i: Result<i64, ParseIntError> = v.parse();
match i {
Ok(_) => Ok(()),
Err(e) => Err(format!("Invalid number: {}", e)),
}
}
fn main() {
let matches = App::new("Mizule")
.version(env!("CARGO_PKG_VERSION"))
.author(env!("CARGO_PKG_AUTHORS"))
.about("Warns when the same filesystem has been mounted too long")
.arg(
Arg::with_name("mountpoint")
.help("Path to the mounted filesystem to check")
.required(true),
)
.arg(
Arg::with_name("ttl")
.short("t")
.long("ttl")
.takes_value(true)
.validator(validate_int)
.default_value("30")
.help("Number of days before warning"),
)
.arg(
Arg::with_name("mailto")
.short("m")
.long("mailto")
.alias("mail-to")
.takes_value(true)
.help("Send warning to this email address"),
)
.get_matches();
let mountpoint = matches.value_of("mountpoint").unwrap();
let ttl: i64 = matches.value_of("ttl").unwrap().parse().unwrap();
let ttl = Duration::days(ttl);
let mailto = matches.value_of("mailto");
match get_fs_uuid(mountpoint) {
Ok(uuid) => {
check_and_notify(mountpoint, &uuid, ttl, mailto);
}
Err(e) => {
eprintln!("Error getting filesystem UUID: {}", e);
process::exit(1);
}
}
}
fn check_and_notify(
mountpoint: &str,
uuid: &str,
ttl: Duration,
mailto: Option<&str>,
) {
let mut cache_path = match env::var("XDG_CACHE_HOME") {
Ok(v) => PathBuf::from(&v),
Err(_) => match env::var("HOME") {
Ok(v) => [v, ".cache".into()].iter().collect(),
Err(_) => ".".into(),
},
};
cache_path.push(CACHE_FILENAME);
let cache_path = cache_path.to_str().unwrap();
let cache = cache::Cache::load(cache_path);
match cache {
Ok(mut cache) => {
if let Some(entry) = cache.get(&mountpoint) {
if entry.expired(ttl) {
notify(mountpoint, uuid, entry.changed(), mailto);
}
} else {
cache.update(&mountpoint, &uuid);
if let Err(e) = cache.save(&cache_path) {
eprintln!("Failed to save cache: {}", e);
}
}
}
Err(e) => {
eprintln!("Error loading cache: {}", e);
process::exit(1);
}
}
}
fn notify(
mountpoint: &str,
uuid: &str,
changed: chrono::DateTime<chrono::Utc>,
mailto: Option<&str>,
) {
let now = chrono::Utc::now();
let delta = now - changed;
let message = format!(
concat!(
"The filesystem mounted at {} (UUID {}) ",
"was last changed on {} ({} days ago)",
),
mountpoint,
uuid,
changed,
delta.num_days()
);
println!("{}", message);
if let Some(mailto) = mailto {
println!("Sending notification to {}", mailto);
let subject = format!(
"{}: {} needs to be changed!",
env!("CARGO_PKG_NAME"),
mountpoint
);
if let Err(e) = sendmail(mailto, &subject, &message) {
eprintln!("Failed to send email: {}", e);
}
}
}
fn sendmail(
mailto: &str,
subject: &str,
message: &str,
) -> Result<(), std::io::Error> {
let mut cmd = process::Command::new("sendmail")
.arg("-t")
.stdin(process::Stdio::piped())
.spawn()?;
{
let mut stdin = cmd.stdin.take().unwrap();
stdin.write_all(format!("To: {}\n", mailto).as_bytes())?;
stdin.write_all(format!("Subject: {}\n", subject).as_bytes())?;
stdin.write_all("\n".as_bytes())?;
stdin.write_all(message.as_bytes())?;
stdin.write_all("\n".as_bytes())?;
stdin.flush()?;
}
cmd.wait()?;
Ok(())
}

144
src/mountinfo.rs Normal file
View File

@ -0,0 +1,144 @@
use std::fs;
use std::io;
use std::io::prelude::*;
use std::path;
use error::Error;
pub struct MountInfo {
pub mount_id: u32,
pub parent_id: u32,
pub major: u32,
pub minor: u32,
pub root: String,
pub mount_point: String,
pub mount_opts: String,
pub fields: String,
pub fstype: String,
pub source: String,
pub super_options: String,
}
impl MountInfo {
pub fn from_line(line: String) -> Self {
let mut parts = line.split_whitespace();
let mount_id = parts.next().unwrap().parse().expect("invalid mount id");
let parent_id =
parts.next().unwrap().parse().expect("invalid parent id");
let mut majmin = parts.next().unwrap().split(":");
let major = majmin
.next()
.unwrap()
.parse()
.expect("invalid major number");
let minor = majmin
.next()
.unwrap()
.parse()
.expect("invalid minor number");
let root = parts.next().unwrap().into();
let mount_point = parts.next().unwrap().into();
let mount_opts = parts.next().unwrap().into();
let mut fields = String::new();
loop {
let next = parts.next().unwrap();
if next == "-" {
break;
} else {
fields.push_str(" ");
fields.push_str(next);
}
}
let fstype = parts.next().unwrap().into();
let source = parts.next().unwrap().into();
let super_options = parts.next().unwrap().into();
MountInfo {
mount_id,
parent_id,
major,
minor,
root,
mount_point,
mount_opts,
fields,
fstype,
source,
super_options,
}
}
}
pub struct MountInfoIterator {
reader: io::BufReader<fs::File>,
}
impl Iterator for MountInfoIterator {
type Item = MountInfo;
fn next(&mut self) -> Option<MountInfo> {
let mut line = String::new();
let n = self.reader.read_line(&mut line);
match n {
Ok(0) => None,
Ok(_) => Some(MountInfo::from_line(line)),
Err(_) => None,
}
}
}
pub fn mountinfo() -> std::io::Result<MountInfoIterator> {
let file = fs::File::open("/proc/self/mountinfo")?;
Ok(MountInfoIterator {
reader: io::BufReader::new(file),
})
}
pub fn get_mountinfo(mountpoint: &str) -> Option<MountInfo> {
if let Ok(mounts) = mountinfo() {
for mi in mounts {
if mi.mount_point == mountpoint {
return Some(mi);
}
}
}
return None;
}
pub fn get_fs_uuid(mountpoint: &str) -> Result<String, Error> {
if let Some(mi) = get_mountinfo(mountpoint) {
let udev = libudev::Context::new()?;
let mut enumerator = libudev::Enumerator::new(&udev)?;
let mut realpath = path::PathBuf::from(&mi.source);
let stat = fs::symlink_metadata(&mi.source)?;
let file_type = stat.file_type();
if file_type.is_symlink() {
realpath.push(fs::read_link(&mi.source)?);
}
let sysname = path::Path::new(&realpath);
if let Some(sysname) = sysname.file_name() {
enumerator.match_sysname(sysname)?;
} else {
return Err(Error::NotFound);
}
for device in enumerator.scan_devices()? {
if let Some(uuid) = device.property_value("ID_FS_UUID") {
if let Some(uuid) = uuid.to_str() {
return Ok(uuid.into());
} else {
return Err(Error::InvalidUuid);
}
}
}
return Err(Error::NotFound);
}
Err(Error::NotMounted)
}