utmp_classic/
parse.rs

1use crate::{UtmpEntry, UtmpError};
2use std::fs::File;
3use std::io::{self, BufReader, Read};
4use std::marker::PhantomData;
5use std::mem;
6use std::path::Path;
7use thiserror::Error;
8use utmp_classic_raw::{utmp, x32::utmp as utmp32, x64::utmp as utmp64};
9use zerocopy::{FromBytes, Ref};
10
11#[doc(hidden)]
12pub struct UtmpParserImpl<R, T = utmp>(R, PhantomData<T>);
13
14impl<R: Read, T> UtmpParserImpl<R, T> {
15    pub fn from_reader(reader: R) -> Self {
16        UtmpParserImpl(reader, PhantomData)
17    }
18
19    pub fn into_inner(self) -> R {
20        self.0
21    }
22}
23
24impl<T> UtmpParserImpl<BufReader<File>, T> {
25    pub fn from_file(file: File) -> Self {
26        UtmpParserImpl(BufReader::new(file), PhantomData)
27    }
28
29    pub fn from_path<P: AsRef<Path>>(path: P) -> Result<Self, io::Error> {
30        Ok(Self::from_file(File::open(path)?))
31    }
32}
33
34/// Parser to parse a utmp file. It can be used as an iterator.
35///
36/// ```
37/// # use utmp_classic::UtmpParser;
38/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
39/// for entry in UtmpParser::from_path("tests/samples/basic.utmp")? {
40///     let entry = entry?;
41///     // handle entry
42/// }
43/// # Ok(())
44/// # }
45/// ```
46pub type UtmpParser<R> = UtmpParserImpl<R, utmp>;
47
48/// Parser to parse a 32-bit utmp file.
49pub type Utmp32Parser<R> = UtmpParserImpl<R, utmp32>;
50
51/// Parser to parse a 64-bit utmp file.
52pub type Utmp64Parser<R> = UtmpParserImpl<R, utmp64>;
53
54const UTMP32_SIZE: usize = mem::size_of::<utmp32>();
55const UTMP64_SIZE: usize = mem::size_of::<utmp64>();
56
57impl<R: Read> Iterator for UtmpParserImpl<R, utmp32> {
58    type Item = Result<UtmpEntry, ParseError>;
59
60    fn next(&mut self) -> Option<Self::Item> {
61        #[repr(align(4))]
62        struct Buffer([u8; UTMP32_SIZE]);
63        let mut buffer = Buffer([0; UTMP32_SIZE]);
64        match read_entry::<_, utmp32>(&mut self.0, buffer.0.as_mut()) {
65            Ok(None) => None,
66            Ok(Some(entry)) => Some(UtmpEntry::try_from(entry).map_err(ParseError::Utmp)),
67            Err(e) => Some(Err(e)),
68        }
69    }
70}
71
72impl<R: Read> Iterator for UtmpParserImpl<R, utmp64> {
73    type Item = Result<UtmpEntry, ParseError>;
74
75    fn next(&mut self) -> Option<Self::Item> {
76        #[repr(align(8))]
77        struct Buffer([u8; UTMP64_SIZE]);
78        let mut buffer = Buffer([0; UTMP64_SIZE]);
79        match read_entry::<_, utmp64>(&mut self.0, buffer.0.as_mut()) {
80            Ok(None) => None,
81            Ok(Some(entry)) => Some(UtmpEntry::try_from(entry).map_err(ParseError::Utmp)),
82            Err(e) => Some(Err(e)),
83        }
84    }
85}
86
87fn read_entry<R: Read, T: FromBytes>(
88    mut reader: R,
89    buffer: &mut [u8],
90) -> Result<Option<&T>, ParseError> {
91    let size = buffer.len();
92    let mut buf = &mut buffer[..];
93    loop {
94        match reader.read(buf) {
95            // If the buffer has not been filled, then we just passed the last item.
96            Ok(0) if buf.len() == size => return Ok(None),
97            // Otherwise this is an unexpected EOF.
98            Ok(0) => {
99                let inner = io::Error::new(io::ErrorKind::UnexpectedEof, "size not aligned");
100                return Err(inner.into());
101            }
102            Ok(n) => {
103                buf = &mut buf[n..];
104                if buf.is_empty() {
105                    break;
106                }
107            }
108            Err(e) if e.kind() == io::ErrorKind::Interrupted => {}
109            Err(e) => return Err(e.into()),
110        }
111    }
112    Ok(Some(
113        Ref::<_, T>::new(buffer).unwrap().into_ref(),
114    ))
115}
116
117/// Parse utmp entries from the given path.
118///
119/// It parses the given path using the native utmp format in the target platform.
120pub fn parse_from_path<P: AsRef<Path>>(path: P) -> Result<Vec<UtmpEntry>, ParseError> {
121    UtmpParser::from_path(path)?.collect()
122}
123
124/// Parse utmp entries from the given file.
125///
126/// It parses the given file using the native utmp format in the target platform.
127pub fn parse_from_file(file: File) -> Result<Vec<UtmpEntry>, ParseError> {
128    UtmpParser::from_file(file).collect()
129}
130
131/// Parse utmp entries from the given reader.
132///
133/// It parses from the given reader using the native utmp format in the target platform.
134pub fn parse_from_reader<R: Read>(reader: R) -> Result<Vec<UtmpEntry>, ParseError> {
135    UtmpParser::from_reader(reader).collect()
136}
137
138#[derive(Debug, Error)]
139#[non_exhaustive]
140pub enum ParseError {
141    #[error(transparent)]
142    Utmp(#[from] UtmpError),
143    #[error(transparent)]
144    Io(#[from] io::Error),
145}