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
|
use std::collections::HashMap;
use std::process::Command;
use uuid;
pub const PRODUCT_VERSION: &str = concat!(
include_str!(concat!(env!("OUT_DIR"), "/product-version.txt")),
" ",
include_str!(concat!(env!("OUT_DIR"), "/git-commit-date.txt"))
);
pub fn collect() -> HashMap<String, String> {
let mut metadata = HashMap::new();
metadata.insert("id".to_owned(), uuid::Uuid::new_v4().to_string());
metadata.insert(
"mullvad-product-version".to_owned(),
PRODUCT_VERSION.to_owned(),
);
metadata.insert("os".to_owned(), os::version());
metadata
}
#[cfg(target_os = "linux")]
mod os {
extern crate rs_release;
pub fn version() -> String {
// The OS version information is obtained first from the os-release file. If that
// information is incomplete or unavailable, an attempt is made to obtain the
// version information from the lsb_release command. If that fails, any partial
// information from os-release is used if available, or a fallback message if
// reading from the os-release file produced
// no version information.
let version = read_os_release_file().unwrap_or_else(|incomplete_info| {
parse_lsb_release().unwrap_or_else(|| {
incomplete_info.unwrap_or_else(|| String::from("[Failed to detect version]"))
})
});
format!("Linux {}", version)
}
fn read_os_release_file() -> Result<String, Option<String>> {
let mut os_release_info = rs_release::get_os_release().map_err(|_| None)?;
let os_name = os_release_info.remove("NAME");
let os_version = os_release_info.remove("VERSION");
if os_name.is_some() || os_version.is_some() {
let full_info_available = os_name.is_some() && os_version.is_some();
let gathered_info = format!(
"{} {}",
os_name.unwrap_or_else(|| "[unknown distribution]".to_owned()),
os_version.unwrap_or_else(|| "[unknown version]".to_owned())
);
if full_info_available {
Ok(gathered_info)
} else {
// Partial version information
Err(Some(gathered_info))
}
} else {
// No information was obtained
Err(None)
}
}
fn parse_lsb_release() -> Option<String> {
super::command_stdout_lossy("lsb_release", &["-ds"]).and_then(|output| {
if output.is_empty() {
None
} else {
Some(output)
}
})
}
}
#[cfg(target_os = "macos")]
mod os {
pub fn version() -> String {
format!(
"macOS {}",
super::command_stdout_lossy("sw_vers", &["-productVersion"])
.unwrap_or(String::from("[Failed to detect version]"))
)
}
}
#[cfg(windows)]
mod os {
pub fn version() -> String {
let system_info =
super::command_stdout_lossy("systeminfo", &["/FO", "LIST"]).unwrap_or_else(String::new);
let mut version = None;
let mut full_version = None;
for info_line in system_info.lines() {
let mut info_parts = info_line.split(":");
match info_parts.next() {
Some("OS Name") => {
version = info_parts
.next()
.map(|s| s.trim().trim_left_matches("Microsoft Windows "))
}
Some("OS Version") => full_version = info_parts.next().map(str::trim),
_ => {}
}
}
let version = version.unwrap_or("N/A");
let full_version = full_version.unwrap_or("N/A");
format!("Windows {} ({})", version, full_version)
}
}
/// Helper for getting stdout of some command as a String. Ignores the exit code of the command.
fn command_stdout_lossy(cmd: &str, args: &[&str]) -> Option<String> {
Command::new(cmd)
.args(args)
.output()
.map(|output| String::from_utf8_lossy(&output.stdout).trim().to_string())
.ok()
}
|