utmp_rs/
parse.rs

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