1use sentry_core::protocol::{Context, DeviceContext, Map, OsContext, RuntimeContext};
2
3include!(concat!(env!("OUT_DIR"), "/constants.gen.rs"));
4
5#[cfg(target_os = "macos")]
6mod model_support {
7 use libc::c_void;
8 use std::ffi::CString;
9 use std::ptr;
10
11 fn sysctlbyname_call(name: &str) -> Option<String> {
12 unsafe {
13 let c_name = match CString::new(name) {
14 Ok(name) => name.into_bytes_with_nul(),
15 Err(_e) => return None,
16 };
17 let mut size = 0;
18 let res = libc::sysctlbyname(
19 c_name.as_ptr() as _,
20 ptr::null_mut(),
21 &mut size,
22 ptr::null_mut(),
23 0,
24 );
25 if res != 0 {
26 return None;
27 }
28
29 let mut buf = vec![0u8; size];
30 let res = libc::sysctlbyname(
31 c_name.as_ptr() as _,
32 buf.as_mut_ptr() as *mut c_void,
33 &mut size,
34 ptr::null_mut(),
35 0,
36 );
37 if res != 0 {
38 return None;
39 }
40
41 Some(
42 buf.into_iter()
43 .take(size)
44 .take_while(|&c| c != b'\0')
45 .map(|c| c as char)
46 .collect(),
47 )
48 }
49 }
50
51 pub fn get_model() -> Option<String> {
52 sysctlbyname_call("hw.model")
53 }
54
55 pub fn get_macos_version() -> Option<String> {
56 let version = sysctlbyname_call("kern.osproductversion")?;
57 let dot_count = version.split('.').count() - 1;
58 if dot_count < 2 {
59 return Some(version + ".0");
60 }
61 Some(version)
62 }
63
64 pub fn get_macos_build() -> Option<String> {
65 sysctlbyname_call("kern.osversion")
66 }
67
68 pub fn get_family() -> Option<String> {
69 get_model().map(|mut s| {
70 let len = s
71 .as_bytes()
72 .iter()
73 .take_while(|c| c.is_ascii_alphabetic())
74 .count();
75 s.truncate(len);
76 s
77 })
78 }
79
80 #[test]
81 fn test_macos_hw_model() {
82 let m = get_model().unwrap();
83 assert!(m.chars().all(|c| c != '\0'));
84 let f = get_family().unwrap();
85 assert!(f.chars().all(|c| !c.is_ascii_digit()));
86 }
87
88 #[test]
89 fn test_macos_version_and_build() {
90 let v = get_macos_version().unwrap();
91 assert!(v.chars().all(|c| c.is_ascii_digit() || c == '.'));
92 let dot_count = v.split('.').count() - 1;
93 assert_eq!(dot_count, 2);
94 let b = get_macos_build().unwrap();
95 assert!(b
96 .chars()
97 .all(|c| c.is_ascii_alphabetic() || c.is_ascii_digit()));
98 }
99}
100
101#[cfg(not(target_os = "macos"))]
102mod model_support {
103 pub fn get_model() -> Option<String> {
104 None
105 }
106
107 pub fn get_family() -> Option<String> {
108 None
109 }
110}
111
112pub fn server_name() -> Option<String> {
114 hostname::get().ok().and_then(|s| s.into_string().ok())
115}
116
117#[cfg(not(windows))]
119pub fn os_context() -> Option<Context> {
120 use uname::uname;
121 if let Ok(info) = uname() {
122 #[cfg(target_os = "macos")]
123 {
124 Some(
125 OsContext {
126 name: Some("macOS".into()),
127 kernel_version: Some(info.version),
128 version: model_support::get_macos_version(),
129 build: model_support::get_macos_build(),
130 ..Default::default()
131 }
132 .into(),
133 )
134 }
135 #[cfg(not(target_os = "macos"))]
136 {
137 Some(
138 OsContext {
139 name: Some(info.sysname),
140 kernel_version: Some(info.version),
141 version: Some(info.release),
142 ..Default::default()
143 }
144 .into(),
145 )
146 }
147 } else {
148 None
149 }
150}
151
152#[cfg(windows)]
154pub fn os_context() -> Option<Context> {
155 use os_info::Version;
156 let version = match os_info::get().version() {
157 Version::Unknown => None,
158 version => Some(version.to_string()),
159 };
160
161 Some(
162 OsContext {
163 name: Some(PLATFORM.into()),
164 version,
165 ..Default::default()
166 }
167 .into(),
168 )
169}
170
171pub fn rust_context() -> Context {
173 RuntimeContext {
174 name: Some("rustc".into()),
175 version: RUSTC_VERSION.map(|x| x.into()),
176 other: {
177 let mut map = Map::default();
178 if let Some(channel) = RUSTC_CHANNEL {
179 map.insert("channel".to_string(), channel.into());
180 }
181 map
182 },
183 }
184 .into()
185}
186
187pub fn device_context() -> Context {
189 DeviceContext {
190 model: model_support::get_model(),
191 family: model_support::get_family(),
192 arch: Some(ARCH.into()),
193 ..Default::default()
194 }
195 .into()
196}
197
198#[cfg(test)]
199mod tests {
200 #[cfg(windows)]
201 #[test]
202 fn windows_os_version_not_empty() {
203 use super::*;
204 let context = os_context();
205 match context {
206 Some(Context::Os(os_context)) => {
207 let version = os_context.version.expect("OS version to be some");
209 assert!(!version.is_empty());
210
211 let unknown_version = os_info::Version::Unknown.to_string();
213 assert_ne!(version, unknown_version);
214 }
215 _ => unreachable!("os_context() should return a Context::Os"),
216 }
217 }
218}