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