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
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
use crate::{UtmpEntry, UtmpError};
use std::convert::TryFrom;
use std::fs::File;
use std::io::{self, BufReader, Read};
use std::marker::PhantomData;
use std::mem;
use std::path::Path;
use thiserror::Error;
use utmp_raw::{utmp, x32::utmp as utmp32, x64::utmp as utmp64};
use zerocopy::{FromBytes, LayoutVerified};

#[doc(hidden)]
pub struct UtmpParserImpl<R, T = utmp>(R, PhantomData<T>);

impl<R: Read, T> UtmpParserImpl<R, T> {
    pub fn from_reader(reader: R) -> Self {
        UtmpParserImpl(reader, PhantomData)
    }

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

impl<T> UtmpParserImpl<BufReader<File>, T> {
    pub fn from_file(file: File) -> Self {
        UtmpParserImpl(BufReader::new(file), PhantomData)
    }

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

/// Parser to parse a utmp file. It can be used as an iterator.
///
/// ```
/// # use utwt::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 type UtmpParser<R> = UtmpParserImpl<R, utmp>;

/// Parser to parse a 32-bit utmp file.
pub type Utmp32Parser<R> = UtmpParserImpl<R, utmp32>;

/// Parser to parse a 64-bit utmp file.
pub type Utmp64Parser<R> = UtmpParserImpl<R, utmp64>;

const UTMP32_SIZE: usize = mem::size_of::<utmp32>();
const UTMP64_SIZE: usize = mem::size_of::<utmp64>();

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

    fn next(&mut self) -> Option<Self::Item> {
        #[repr(align(4))]
        struct Buffer([u8; UTMP32_SIZE]);
        let mut buffer = Buffer([0; UTMP32_SIZE]);
        match read_entry::<_, utmp32>(&mut self.0, buffer.0.as_mut()) {
            Ok(None) => None,
            Ok(Some(entry)) => Some(UtmpEntry::try_from(entry).map_err(ParseError::Utmp)),
            Err(e) => Some(Err(e)),
        }
    }
}

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

    fn next(&mut self) -> Option<Self::Item> {
        #[repr(align(8))]
        struct Buffer([u8; UTMP64_SIZE]);
        let mut buffer = Buffer([0; UTMP64_SIZE]);
        match read_entry::<_, utmp64>(&mut self.0, buffer.0.as_mut()) {
            Ok(None) => None,
            Ok(Some(entry)) => Some(UtmpEntry::try_from(entry).map_err(ParseError::Utmp)),
            Err(e) => Some(Err(e)),
        }
    }
}

fn read_entry<R: Read, T: FromBytes>(
    mut reader: R,
    buffer: &mut [u8],
) -> Result<Option<&T>, ParseError> {
    let size = buffer.len();
    let mut buf = &mut buffer[..];
    loop {
        match reader.read(buf) {
            // If the buffer has not been filled, then we just passed the last item.
            Ok(0) if buf.len() == size => return Ok(None),
            // Otherwise this is an unexpected EOF.
            Ok(0) => {
                let inner = io::Error::new(io::ErrorKind::UnexpectedEof, "size not aligned");
                return 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 Err(e.into()),
        }
    }
    Ok(Some(
        LayoutVerified::<_, T>::new(buffer).unwrap().into_ref(),
    ))
}

/// Parse utmp entries.
///
/// It parses utmp entries using the native utmp format in the target platform.
pub fn parse_utmp() -> Result<Vec<UtmpEntry>, ParseError> {
    UtmpParser::from_path("/var/run/utmp")?.collect()
}

/// Parse wtmp entries.
///
/// It parses wtmp entries using the native utmp format in the target platform.
pub fn parse_wtmp() -> Result<Vec<UtmpEntry>, ParseError> {
    UtmpParser::from_path("/var/log/wtmp")?.collect()
}

/// Parse utmp entries from the given path.
///
/// It parses the given path using the native utmp format in the target platform.
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.
///
/// It parses the given file using the native utmp format in the target platform.
pub fn parse_from_file(file: File) -> Result<Vec<UtmpEntry>, ParseError> {
    UtmpParser::from_file(file).collect()
}

/// Parse utmp entries from the given reader.
///
/// It parses from the given reader using the native utmp format in the target platform.
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),
}