1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
use crate::{UtmpEntry, UtmpError};
use std::convert::TryFrom;
use std::fs::File;
use std::io::{self, BufReader, Read};
use std::path::Path;
use thiserror::Error;
use utmp_raw::utmp;
use zerocopy::LayoutVerified;

const UTMP_SIZE: usize = std::mem::size_of::<utmp>();

#[repr(align(4))]
struct UtmpBuffer([u8; UTMP_SIZE]);

/// Parser to parse a utmp file. It can be used as an iterator.
///
/// ```
/// # use utmp_rs::UtmpParser;
/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
/// for entry in UtmpParser::from_path("/var/run/utmp")? {
///     let entry = entry?;
///     // handle entry
/// }
/// # Ok(())
/// # }
/// ```
pub struct UtmpParser<R>(R);

impl<R: Read> UtmpParser<R> {
    pub fn from_reader(reader: R) -> Self {
        UtmpParser(reader)
    }

    pub fn into_inner(self) -> R {
        self.0
    }
}

impl UtmpParser<BufReader<File>> {
    pub fn from_file(file: File) -> Self {
        UtmpParser(BufReader::new(file))
    }

    pub fn from_path<P: AsRef<Path>>(path: P) -> Result<Self, io::Error> {
        Ok(Self::from_file(File::open(path)?))
    }
}

impl<R: Read> Iterator for UtmpParser<R> {
    type Item = Result<UtmpEntry, ParseError>;

    fn next(&mut self) -> Option<Self::Item> {
        let mut buffer = UtmpBuffer([0; UTMP_SIZE]);
        let mut buf = buffer.0.as_mut();
        loop {
            match self.0.read(buf) {
                // If the buffer has not been filled, then we just passed the last item.
                Ok(0) if buf.len() == UTMP_SIZE => return None,
                // Otherwise this is an unexpected EOF.
                Ok(0) => {
                    let inner = io::Error::new(io::ErrorKind::UnexpectedEof, "size not aligned");
                    return Some(Err(inner.into()));
                }
                Ok(n) => {
                    buf = &mut buf[n..];
                    if buf.is_empty() {
                        break;
                    }
                }
                Err(e) if e.kind() == io::ErrorKind::Interrupted => {}
                Err(e) => return Some(Err(e.into())),
            }
        }
        let buffer = buffer.0.as_ref();
        let entry = LayoutVerified::<_, utmp>::new(buffer).unwrap().into_ref();
        Some(UtmpEntry::try_from(entry).map_err(ParseError::Utmp))
    }
}

/// Parse utmp entries from the given path.
pub fn parse_from_path<P: AsRef<Path>>(path: P) -> Result<Vec<UtmpEntry>, ParseError> {
    UtmpParser::from_path(path)?.collect()
}

/// Parse utmp entries from the given file.
pub fn parse_from_file(file: File) -> Result<Vec<UtmpEntry>, ParseError> {
    UtmpParser::from_file(file).collect()
}

/// Parse utmp entries from the given reader.
pub fn parse_from_reader<R: Read>(reader: R) -> Result<Vec<UtmpEntry>, ParseError> {
    UtmpParser::from_reader(reader).collect()
}

#[derive(Debug, Error)]
#[non_exhaustive]
pub enum ParseError {
    #[error(transparent)]
    Utmp(#[from] UtmpError),
    #[error(transparent)]
    Io(#[from] io::Error),
}