//! Linux-specific tailing engine that uses inotify via the `notify` crate.
//!
//! This module is only compiled on Linux hosts. For other platforms see
//! [`crate::tail::engine::kqueue`] or the polling fallback.
#![cfg(target_os = "linux")]
use std::path::PathBuf;
use anyhow::{Context, Result};
use notify::event::{ModifyKind, RemoveKind};
use notify::{EventKind, RecommendedWatcher, RecursiveMode, Watcher};
use tokio::sync::mpsc;
use super::Event;
pub async fn run(paths: Vec<PathBuf>, tx: mpsc::Sender<Event>) -> Result<()> {
let (raw_tx, mut raw_rx) = mpsc::unbounded_channel();
let mut watcher = RecommendedWatcher::new(
move |res: notify::Result<notify::Event>| {
if let Ok(ev) = res {
let _ = raw_tx.send(ev);
}
},
notify::Config::default(),
)
.context("creating inotify watcher")?;
for p in &paths {
watcher
.watch(p, RecursiveMode::NonRecursive)
.with_context(|| format!("watching {}", p.display()))?;
}
while let Some(event) = raw_rx.recv().await {
for path in event.paths.iter().cloned() {
let translated = translate(&event.kind, path);
if let Some(e) = translated {
if tx.send(e).await.is_err() {
return Ok(());
}
}
}
}
Ok(())
}
fn translate(kind: &EventKind, path: PathBuf) -> Option<Event> {
match kind {
EventKind::Modify(ModifyKind::Data(_)) => Some(Event::Grew(path)),
EventKind::Modify(ModifyKind::Name(_)) => Some(Event::Rotated(path)),
EventKind::Modify(ModifyKind::Metadata(_)) => None,
EventKind::Remove(RemoveKind::File)
| EventKind::Remove(RemoveKind::Folder)
| EventKind::Remove(RemoveKind::Any) => Some(Event::Gone(path)),
EventKind::Create(_) => Some(Event::Rotated(path)),
_ => None,
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::path::Path;
#[test]
fn translate_data_modify_is_grew() {
let ev = translate(
&EventKind::Modify(ModifyKind::Data(notify::event::DataChange::Content)),
Path::new("/tmp/foo.log").to_path_buf(),
);
assert!(matches!(ev, Some(Event::Grew(_))));
}
#[test]
fn translate_remove_is_gone() {
let ev = translate(
&EventKind::Remove(RemoveKind::File),
Path::new("/tmp/foo.log").to_path_buf(),
);
assert!(matches!(ev, Some(Event::Gone(_))));
}
}