src/tail/engine/kqueue.rs

//! macOS-specific tailing engine backed by kqueue.
//!
//! Implemented on top of the `notify` crate, which picks FSEvents or kqueue
//! automatically depending on the macOS version.

#![cfg(target_os = "macos")]

use std::path::PathBuf;

use anyhow::{Context, Result};
use notify::event::{ModifyKind, RemoveKind, RenameMode};
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 kqueue 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() {
            if let Some(e) = translate(&event.kind, path) {
                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(RenameMode::Any)) => Some(Event::Rotated(path)),
        EventKind::Remove(RemoveKind::Any) => Some(Event::Gone(path)),
        _ => None,
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use std::path::Path;

    #[test]
    fn unhandled_kind_is_ignored() {
        let out = translate(
            &EventKind::Access(notify::event::AccessKind::Open(notify::event::AccessMode::Read)),
            Path::new("/tmp/foo.log").to_path_buf(),
        );
        assert!(out.is_none());
    }
}