1use std::boxed::Box;
5use std::fs::File;
6use std::io::BufRead;
7use std::io::Error;
8use std::io::ErrorKind;
9use std::io::Result;
10
11const DEFAULT_HOSTNAME: &str = "no.hostname.verneuil";
12
13fn compute_boot_time_slow() -> Result<u64> {
14 let file = File::open("/proc/stat")?;
15
16 for line_or in std::io::BufReader::new(file).lines() {
19 let line = line_or?;
20 if let Some(suffix) = line.strip_prefix("btime ") {
21 return suffix
22 .parse::<u64>()
23 .map_err(|_| Error::new(ErrorKind::Other, "failed to parse btime"));
24 }
25 }
26
27 Err(Error::new(ErrorKind::Other, "btime not in `/proc/stat`"))
28}
29
30pub(crate) fn boot_timestamp() -> u64 {
34 lazy_static::lazy_static! {
35 static ref TIMESTAMP: u64 = compute_boot_time_slow().unwrap_or(0);
36 }
37
38 *TIMESTAMP
39}
40
41fn find_boot_id() -> Result<&'static str> {
42 let file = File::open("/proc/sys/kernel/random/boot_id")?;
43
44 match std::io::BufReader::new(file).lines().next() {
45 None => Err(Error::new(ErrorKind::Other, "boot_id is empty")),
46 Some(Err(e)) => Err(e),
47 Some(Ok(line)) => Ok(Box::leak(line.into_boxed_str())),
48 }
49}
50
51pub(crate) fn boot_id() -> &'static str {
53 lazy_static::lazy_static! {
54 static ref ID: &'static str = find_boot_id().expect("`/proc/sys/kernel/random/boot_id` should be set");
55 }
56
57 &ID
58}
59
60fn find_hostname() -> Result<&'static str> {
61 let file = File::open("/etc/hostname")?;
62
63 match std::io::BufReader::new(file).lines().next() {
64 None => Err(Error::new(ErrorKind::Other, "hostname is empty")),
65 Some(Err(e)) => Err(e),
66 Some(Ok(line)) => Ok(Box::leak(line.into_boxed_str())),
67 }
68}
69
70pub fn hostname() -> &'static str {
72 lazy_static::lazy_static! {
73 static ref NAME: &'static str = find_hostname().unwrap_or(DEFAULT_HOSTNAME);
74 }
75
76 &NAME
77}
78
79pub(crate) fn hostname_hash(hostname: &str) -> String {
81 lazy_static::lazy_static! {
82 static ref PARAMS: umash::Params = umash::Params::derive(0, b"verneuil hostname params");
83 }
84
85 let hash = PARAMS.hasher(0).write(hostname.as_bytes()).digest();
86 format!("{:04x}", hash % (1 << (4 * 4)))
87}
88
89pub(crate) fn instance_id() -> &'static str {
94 lazy_static::lazy_static! {
95 static ref INSTANCE: &'static str = Box::leak(format!("{}.{}", boot_timestamp(), boot_id()).into_boxed_str());
96 }
97
98 &INSTANCE
99}
100
101pub(crate) fn likely_instance_ids(range: u64) -> Vec<String> {
113 let base_ts = boot_timestamp();
114 let boot_id = boot_id();
115
116 let mut ret = vec![instance_id().to_string()];
117
118 for delta in 1..=range {
119 if let Some(ts) = base_ts.checked_sub(delta) {
120 ret.push(format!("{}.{}", ts, boot_id));
121 }
122
123 if let Some(ts) = base_ts.checked_add(delta) {
124 ret.push(format!("{}.{}", ts, boot_id));
125 }
126 }
127
128 ret
129}
130
131#[test]
132fn print_boot_time() {
133 assert_ne!(boot_timestamp(), 0);
134 println!("Boot time = {}", boot_timestamp());
135}
136
137#[test]
138fn print_boot_id() {
139 assert_ne!(boot_id(), "");
140 println!("Boot id = {}", boot_id());
141}
142
143#[test]
144fn print_instance_id() {
145 println!("instance id = '{}'", instance_id());
146}
147
148#[test]
149fn print_hostname() {
150 assert_ne!(hostname(), DEFAULT_HOSTNAME);
151 println!(
152 "hostname = '{}', hash = '{}'",
153 hostname(),
154 hostname_hash(hostname())
155 );
156}
157
158#[test]
161fn test_hostname_hash() {
162 assert_eq!(hostname_hash("example.com"), "7010");
163}