1use std::path::{Path, PathBuf};
8
9pub fn per_host_path(db_path: &Path) -> PathBuf {
12 let Some(host) = host_discriminator() else {
13 return db_path.to_path_buf();
14 };
15 tagged(db_path, &host)
16}
17
18pub(crate) fn tagged(db_path: &Path, host: &str) -> PathBuf {
19 let Some(name) = db_path
20 .file_name()
21 .map(|s| s.to_string_lossy().into_owned())
22 else {
23 return db_path.to_path_buf();
24 };
25 let tag = format!(".h-{host}");
26 if name.ends_with(&tag) || name.contains(&format!("{tag}.")) {
27 return db_path.to_path_buf();
28 }
29 let new_name = match name.rsplit_once('.') {
31 Some((stem, ext)) if !stem.is_empty() => format!("{stem}{tag}.{ext}"),
32 _ => format!("{name}{tag}"),
33 };
34 db_path.with_file_name(new_name)
35}
36
37pub(crate) fn host_discriminator() -> Option<String> {
39 let raw = hostname_raw()?;
40 let mut s: String = raw
41 .trim()
42 .chars()
43 .map(|c| {
44 if c.is_ascii_alphanumeric() {
45 c.to_ascii_lowercase()
46 } else {
47 '-'
48 }
49 })
50 .collect();
51 s.truncate(24);
52 let s = s.trim_matches('-');
53 (!s.is_empty()).then(|| s.to_string())
54}
55
56#[cfg(unix)]
57fn hostname_raw() -> Option<String> {
58 let mut buf = [0u8; 256];
59 if unsafe { libc::gethostname(buf.as_mut_ptr().cast(), buf.len()) } != 0 {
61 return None;
62 }
63 let len = buf.iter().position(|&b| b == 0).unwrap_or(buf.len());
64 String::from_utf8(buf[..len].to_vec()).ok()
65}
66
67#[cfg(windows)]
68fn hostname_raw() -> Option<String> {
69 if let Ok(host) = std::env::var("COMPUTERNAME") {
70 if !host.trim().is_empty() {
71 return Some(host);
72 }
73 }
74 #[link(name = "kernel32")]
75 unsafe extern "system" {
76 fn GetComputerNameW(buffer: *mut u16, size: *mut u32) -> i32;
77 }
78 let mut buffer = [0u16; 256];
79 let mut size = buffer.len() as u32;
80 if unsafe { GetComputerNameW(buffer.as_mut_ptr(), &mut size) } == 0 || size == 0 {
82 return None;
83 }
84 String::from_utf16(&buffer[..size as usize]).ok()
85}
86
87#[cfg(not(any(unix, windows)))]
88fn hostname_raw() -> Option<String> {
89 None
90}