1
use std::path::Path;
2

            
3
use chrono::{DateTime, Duration, Timelike, Utc};
4
use nix::{
5
    sys::{stat::stat, sysinfo::sysinfo},
6
    unistd::gethostname,
7
};
8
use uucore::utmpx::Utmpx;
9

            
10
use crate::{
11
    proto::{WhodStatusUpdate, WhodUserEntry},
12
    server::ignore_list::IgnoreList,
13
};
14

            
15
/// Reads utmp entries to determine currently logged-in users.
16
1
pub fn generate_rwhod_user_entries(
17
1
    now: DateTime<Utc>,
18
1
    ignore_list: Option<&IgnoreList>,
19
1
) -> anyhow::Result<Vec<WhodUserEntry>> {
20
1
    Utmpx::iter_all_records()
21
1
        .filter(|entry| entry.is_user_process())
22
1
        .filter(|entry| {
23
            !ignore_list.is_some_and(|ignore_list| ignore_list.ignores_username(&entry.user()))
24
        })
25
1
        .map(|entry| {
26
            let login_time = entry
27
                .login_time()
28
                .checked_to_utc()
29
                .and_then(|t| DateTime::<Utc>::from_timestamp_secs(t.unix_timestamp()))
30
                .ok_or_else(|| anyhow::anyhow!("Failed to convert login time to UTC"))?;
31

            
32
            let idle_time = stat(&Path::new("/dev").join(entry.tty_device()))
33
                .ok()
34
                .and_then(|st| {
35
                    let last_active = DateTime::<Utc>::from_timestamp_secs(st.st_atime)?;
36
                    Some((now - last_active).max(Duration::zero()))
37
                })
38
                .unwrap_or(Duration::zero());
39

            
40
            debug_assert!(
41
                idle_time.num_seconds() >= 0,
42
                "Idle time should never be negative"
43
            );
44

            
45
            Ok(WhodUserEntry::new(
46
                entry.tty_device(),
47
                entry.user(),
48
                login_time,
49
                idle_time,
50
            ))
51
        })
52
1
        .collect()
53
1
}
54

            
55
/// Generate a rwhod status update packet representing the current system state.
56
1
pub fn generate_rwhod_status_update(
57
1
    ignore_list: Option<&IgnoreList>,
58
1
) -> anyhow::Result<WhodStatusUpdate> {
59
1
    let sysinfo = sysinfo().unwrap();
60
1
    let load_average = sysinfo.load_average();
61
1
    let uptime = sysinfo.uptime();
62
1
    let hostname = gethostname()?.to_str().unwrap().to_string();
63
1
    let now = Utc::now().with_nanosecond(0).unwrap_or(Utc::now());
64

            
65
1
    let result = WhodStatusUpdate::new(
66
1
        now,
67
1
        None,
68
1
        hostname,
69
1
        (
70
1
            (load_average.0 * 100.0).abs() as i32,
71
1
            (load_average.1 * 100.0).abs() as i32,
72
1
            (load_average.2 * 100.0).abs() as i32,
73
1
        ),
74
1
        now - uptime,
75
1
        generate_rwhod_user_entries(now, ignore_list)?,
76
    );
77

            
78
1
    Ok(result)
79
1
}
80

            
81
#[cfg(test)]
82
mod tests {
83
    use super::*;
84

            
85
    #[test]
86
1
    fn test_generate_rwhod_status_update() {
87
1
        let status_update = generate_rwhod_status_update(None).unwrap();
88
1
        println!("{:?}", status_update);
89
1
    }
90
}