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
35pub type UtmpParser<R> = UtmpParserImpl<R, utmp>;
48
49pub type Utmp32Parser<R> = UtmpParserImpl<R, utmp32>;
51
52pub 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 Ok(0) if buf.len() == size => return Ok(None),
98 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
116pub fn parse_from_path<P: AsRef<Path>>(path: P) -> Result<Vec<UtmpEntry>, ParseError> {
120 UtmpParser::from_path(path)?.collect()
121}
122
123pub fn parse_from_file(file: File) -> Result<Vec<UtmpEntry>, ParseError> {
127 UtmpParser::from_file(file).collect()
128}
129
130pub 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}