]>
Commit | Line | Data |
---|---|---|
4c9a8b5c | 1 | use std::collections::HashSet; |
ce18d8c0 DM |
2 | use std::fs::OpenOptions; |
3 | use std::io::{BufRead, BufReader}; | |
ce18d8c0 | 4 | use std::net::{Ipv4Addr, Ipv6Addr}; |
db83a675 | 5 | use std::str::FromStr; |
4c9a8b5c WB |
6 | |
7 | use failure::*; | |
ce18d8c0 | 8 | use lazy_static::lazy_static; |
ce18d8c0 | 9 | use libc; |
4c9a8b5c WB |
10 | |
11 | use proxmox_tools::fs::file_read_firstline; | |
6cf330c9 | 12 | use proxmox_tools::parse::hex_nibble; |
ce18d8c0 DM |
13 | |
14 | /// POSIX sysconf call | |
15 | pub fn sysconf(name: i32) -> i64 { | |
ccce46eb WB |
16 | extern "C" { |
17 | fn sysconf(name: i32) -> i64; | |
18 | } | |
ce18d8c0 DM |
19 | unsafe { sysconf(name) } |
20 | } | |
21 | ||
22 | lazy_static! { | |
23 | static ref CLOCK_TICKS: f64 = sysconf(libc::_SC_CLK_TCK) as f64; | |
24 | } | |
25 | ||
26 | pub struct ProcFsPidStat { | |
27 | pub status: u8, | |
28 | pub utime: u64, | |
29 | pub stime: u64, | |
30 | pub starttime: u64, | |
31 | pub vsize: u64, | |
32 | pub rss: i64, | |
33 | } | |
34 | ||
35 | pub fn read_proc_pid_stat(pid: libc::pid_t) -> Result<ProcFsPidStat, Error> { | |
b294fb13 WB |
36 | parse_proc_pid_stat( |
37 | pid, | |
38 | std::str::from_utf8(&std::fs::read(format!("/proc/{}/stat", pid))?)?, | |
39 | ) | |
40 | } | |
ce18d8c0 | 41 | |
b294fb13 | 42 | fn parse_proc_pid_stat(pid: libc::pid_t, statstr: &str) -> Result<ProcFsPidStat, Error> { |
db83a675 WB |
43 | // It starts with the pid followed by a '('. |
44 | let cmdbeg = statstr | |
45 | .find('(') | |
46 | .ok_or_else(|| format_err!("missing '(' in /proc/PID/stat"))?; | |
47 | ||
48 | if !statstr[..=cmdbeg].ends_with(" (") { | |
49 | bail!("bad /proc/PID/stat line before the '('"); | |
ce18d8c0 DM |
50 | } |
51 | ||
db83a675 WB |
52 | let stat_pid: u32 = statstr[..(cmdbeg - 1)] |
53 | .parse() | |
54 | .map_err(|e| format_err!("bad pid in /proc/PID/stat: {}", e))?; | |
55 | ||
56 | if (pid as u32) != stat_pid { | |
57 | bail!( | |
58 | "unexpected pid for process: found pid {} in /proc/{}/stat", | |
59 | stat_pid, | |
60 | pid | |
61 | ); | |
62 | } | |
63 | ||
64 | // After the '(' we have an arbitrary command name, then ')' and the remaining values | |
65 | let cmdend = statstr | |
66 | .rfind(')') | |
67 | .ok_or_else(|| format_err!("missing ')' in /proc/PID/stat"))?; | |
68 | let mut parts = statstr[cmdend + 1..].trim_start().split_ascii_whitespace(); | |
69 | ||
70 | // helpers: | |
71 | fn required<'a>(value: Option<&'a str>, what: &'static str) -> Result<&'a str, Error> { | |
72 | value.ok_or_else(|| format_err!("missing '{}' in /proc/PID/stat", what)) | |
73 | } | |
74 | ||
75 | fn req_num<T>(value: Option<&str>, what: &'static str) -> Result<T, Error> | |
76 | where | |
77 | T: FromStr, | |
78 | <T as FromStr>::Err: Into<Error>, | |
79 | { | |
80 | required(value, what)?.parse::<T>().map_err(|e| e.into()) | |
81 | } | |
ce18d8c0 | 82 | |
db83a675 WB |
83 | fn req_byte(value: Option<&str>, what: &'static str) -> Result<u8, Error> { |
84 | let value = required(value, what)?; | |
85 | if value.len() != 1 { | |
86 | bail!("invalid '{}' in /proc/PID/stat", what); | |
87 | } | |
88 | Ok(value.as_bytes()[0]) | |
ce18d8c0 DM |
89 | } |
90 | ||
db83a675 WB |
91 | let out = ProcFsPidStat { |
92 | status: req_byte(parts.next(), "status")?, | |
93 | utime: req_num::<u64>(parts.nth(10), "utime")?, | |
94 | stime: req_num::<u64>(parts.next(), "stime")?, | |
95 | starttime: req_num::<u64>(parts.nth(6), "start_time")?, | |
96 | vsize: req_num::<u64>(parts.next(), "vsize")?, | |
97 | rss: req_num::<i64>(parts.next(), "rss")? * 4096, | |
98 | }; | |
99 | ||
100 | let _ = req_num::<u64>(parts.next(), "it_real_value")?; | |
101 | // and more... | |
102 | ||
103 | Ok(out) | |
ce18d8c0 DM |
104 | } |
105 | ||
b294fb13 WB |
106 | #[test] |
107 | fn test_read_proc_pid_stat() { | |
108 | let stat = parse_proc_pid_stat( | |
109 | 28900, | |
110 | "28900 (zsh) S 22489 28900 28900 34826 10252 4194304 6851 5946551 0 2344 6 3 25205 1413 \ | |
111 | 20 0 1 0 287592 12496896 1910 18446744073709551615 93999319244800 93999319938061 \ | |
112 | 140722897984224 0 0 0 2 3686404 134295555 1 0 0 17 10 0 0 0 0 0 93999320079088 \ | |
113 | 93999320108360 93999343271936 140722897992565 140722897992570 140722897992570 \ | |
114 | 140722897993707 0", | |
115 | ) | |
116 | .expect("successful parsing of a sample /proc/PID/stat entry"); | |
117 | assert_eq!(stat.status, b'S'); | |
118 | assert_eq!(stat.utime, 6); | |
119 | assert_eq!(stat.stime, 3); | |
120 | assert_eq!(stat.starttime, 287592); | |
121 | assert_eq!(stat.vsize, 12496896); | |
122 | assert_eq!(stat.rss, 1910 * 4096); | |
123 | } | |
124 | ||
ce18d8c0 | 125 | pub fn read_proc_starttime(pid: libc::pid_t) -> Result<u64, Error> { |
ce18d8c0 DM |
126 | let info = read_proc_pid_stat(pid)?; |
127 | ||
128 | Ok(info.starttime) | |
129 | } | |
130 | ||
131 | pub fn check_process_running(pid: libc::pid_t) -> Option<ProcFsPidStat> { | |
132 | if let Ok(info) = read_proc_pid_stat(pid) { | |
a11f6e88 | 133 | if info.status != b'Z' { |
ce18d8c0 DM |
134 | return Some(info); |
135 | } | |
136 | } | |
137 | None | |
138 | } | |
139 | ||
140 | pub fn check_process_running_pstart(pid: libc::pid_t, pstart: u64) -> Option<ProcFsPidStat> { | |
141 | if let Some(info) = check_process_running(pid) { | |
142 | if info.starttime == pstart { | |
143 | return Some(info); | |
144 | } | |
145 | } | |
146 | None | |
147 | } | |
148 | ||
149 | pub fn read_proc_uptime() -> Result<(f64, f64), Error> { | |
150 | let path = "/proc/uptime"; | |
151 | let line = file_read_firstline(&path)?; | |
152 | let mut values = line.split_whitespace().map(|v| v.parse::<f64>()); | |
153 | ||
154 | match (values.next(), values.next()) { | |
a11f6e88 | 155 | (Some(Ok(up)), Some(Ok(idle))) => Ok((up, idle)), |
ce18d8c0 DM |
156 | _ => bail!("Error while parsing '{}'", path), |
157 | } | |
158 | } | |
159 | ||
160 | pub fn read_proc_uptime_ticks() -> Result<(u64, u64), Error> { | |
161 | let (mut up, mut idle) = read_proc_uptime()?; | |
162 | up *= *CLOCK_TICKS; | |
163 | idle *= *CLOCK_TICKS; | |
164 | Ok((up as u64, idle as u64)) | |
165 | } | |
166 | ||
167 | #[derive(Debug)] | |
168 | pub struct ProcFsMemInfo { | |
169 | pub memtotal: u64, | |
170 | pub memfree: u64, | |
171 | pub memused: u64, | |
172 | pub memshared: u64, | |
173 | pub swaptotal: u64, | |
174 | pub swapfree: u64, | |
175 | pub swapused: u64, | |
176 | } | |
177 | ||
178 | pub fn read_meminfo() -> Result<ProcFsMemInfo, Error> { | |
179 | let path = "/proc/meminfo"; | |
180 | let file = OpenOptions::new().read(true).open(&path)?; | |
181 | ||
182 | let mut meminfo = ProcFsMemInfo { | |
183 | memtotal: 0, | |
184 | memfree: 0, | |
185 | memused: 0, | |
186 | memshared: 0, | |
187 | swaptotal: 0, | |
188 | swapfree: 0, | |
189 | swapused: 0, | |
190 | }; | |
191 | ||
192 | let (mut buffers, mut cached) = (0, 0); | |
193 | for line in BufReader::new(&file).lines() { | |
194 | let content = line?; | |
195 | let mut content_iter = content.split_whitespace(); | |
196 | if let (Some(key), Some(value)) = (content_iter.next(), content_iter.next()) { | |
197 | match key { | |
198 | "MemTotal:" => meminfo.memtotal = value.parse::<u64>()? * 1024, | |
199 | "MemFree:" => meminfo.memfree = value.parse::<u64>()? * 1024, | |
200 | "SwapTotal:" => meminfo.swaptotal = value.parse::<u64>()? * 1024, | |
201 | "SwapFree:" => meminfo.swapfree = value.parse::<u64>()? * 1024, | |
202 | "Buffers:" => buffers = value.parse::<u64>()? * 1024, | |
203 | "Cached:" => cached = value.parse::<u64>()? * 1024, | |
204 | _ => continue, | |
205 | } | |
206 | } | |
207 | } | |
208 | ||
209 | meminfo.memfree += buffers + cached; | |
210 | meminfo.memused = meminfo.memtotal - meminfo.memfree; | |
211 | ||
212 | meminfo.swapused = meminfo.swaptotal - meminfo.swapfree; | |
213 | ||
214 | let spages_line = file_read_firstline("/sys/kernel/mm/ksm/pages_sharing")?; | |
215 | meminfo.memshared = spages_line.trim_end().parse::<u64>()? * 4096; | |
216 | ||
217 | Ok(meminfo) | |
218 | } | |
219 | ||
220 | #[derive(Clone, Debug)] | |
221 | pub struct ProcFsCPUInfo { | |
222 | pub user_hz: f64, | |
223 | pub mhz: f64, | |
224 | pub model: String, | |
225 | pub hvm: bool, | |
226 | pub sockets: usize, | |
227 | pub cpus: usize, | |
228 | } | |
229 | ||
230 | static CPU_INFO: Option<ProcFsCPUInfo> = None; | |
231 | ||
232 | pub fn read_cpuinfo() -> Result<ProcFsCPUInfo, Error> { | |
ccce46eb WB |
233 | if let Some(cpu_info) = &CPU_INFO { |
234 | return Ok(cpu_info.clone()); | |
235 | } | |
ce18d8c0 DM |
236 | |
237 | let path = "/proc/cpuinfo"; | |
238 | let file = OpenOptions::new().read(true).open(&path)?; | |
239 | ||
240 | let mut cpuinfo = ProcFsCPUInfo { | |
241 | user_hz: *CLOCK_TICKS, | |
242 | mhz: 0.0, | |
243 | model: String::new(), | |
244 | hvm: false, | |
245 | sockets: 0, | |
246 | cpus: 0, | |
247 | }; | |
248 | ||
249 | let mut socket_ids = HashSet::new(); | |
250 | for line in BufReader::new(&file).lines() { | |
251 | let content = line?; | |
ccce46eb WB |
252 | if content.is_empty() { |
253 | continue; | |
254 | } | |
a11f6e88 | 255 | let mut content_iter = content.split(':'); |
ce18d8c0 | 256 | match (content_iter.next(), content_iter.next()) { |
ccce46eb WB |
257 | (Some(key), Some(value)) => match key.trim_end() { |
258 | "processor" => cpuinfo.cpus += 1, | |
259 | "model name" => cpuinfo.model = value.trim().to_string(), | |
260 | "cpu MHz" => cpuinfo.mhz = value.trim().parse::<f64>()?, | |
261 | "flags" => cpuinfo.hvm = value.contains(" vmx ") || value.contains(" svm "), | |
262 | "physical id" => { | |
263 | let id = value.trim().parse::<u8>()?; | |
264 | socket_ids.insert(id); | |
ce18d8c0 | 265 | } |
ccce46eb | 266 | _ => continue, |
ce18d8c0 DM |
267 | }, |
268 | _ => bail!("Error while parsing '{}'", path), | |
269 | } | |
270 | } | |
271 | cpuinfo.sockets = socket_ids.len(); | |
272 | ||
273 | Ok(cpuinfo) | |
274 | } | |
275 | ||
276 | #[derive(Debug)] | |
277 | pub struct ProcFsMemUsage { | |
278 | pub size: u64, | |
279 | pub resident: u64, | |
280 | pub shared: u64, | |
281 | } | |
282 | ||
283 | pub fn read_memory_usage() -> Result<ProcFsMemUsage, Error> { | |
284 | let path = format!("/proc/{}/statm", std::process::id()); | |
285 | let line = file_read_firstline(&path)?; | |
286 | let mut values = line.split_whitespace().map(|v| v.parse::<u64>()); | |
287 | ||
288 | let ps = 4096; | |
289 | match (values.next(), values.next(), values.next()) { | |
ccce46eb WB |
290 | (Some(Ok(size)), Some(Ok(resident)), Some(Ok(shared))) => Ok(ProcFsMemUsage { |
291 | size: size * ps, | |
292 | resident: resident * ps, | |
293 | shared: shared * ps, | |
294 | }), | |
ce18d8c0 DM |
295 | _ => bail!("Error while parsing '{}'", path), |
296 | } | |
297 | } | |
298 | ||
299 | #[derive(Debug)] | |
300 | pub struct ProcFsNetDev { | |
301 | pub device: String, | |
302 | pub receive: u64, | |
303 | pub send: u64, | |
304 | } | |
305 | ||
306 | pub fn read_proc_net_dev() -> Result<Vec<ProcFsNetDev>, Error> { | |
307 | let path = "/proc/net/dev"; | |
308 | let file = OpenOptions::new().read(true).open(&path)?; | |
309 | ||
310 | let mut result = Vec::new(); | |
311 | for line in BufReader::new(&file).lines().skip(2) { | |
312 | let content = line?; | |
313 | let mut iter = content.split_whitespace(); | |
a11f6e88 | 314 | match (iter.next(), iter.next(), iter.nth(7)) { |
ce18d8c0 DM |
315 | (Some(device), Some(receive), Some(send)) => { |
316 | result.push(ProcFsNetDev { | |
ccce46eb | 317 | device: device[..device.len() - 1].to_string(), |
ce18d8c0 DM |
318 | receive: receive.parse::<u64>()?, |
319 | send: send.parse::<u64>()?, | |
320 | }); | |
ccce46eb | 321 | } |
ce18d8c0 DM |
322 | _ => bail!("Error while parsing '{}'", path), |
323 | } | |
324 | } | |
325 | ||
326 | Ok(result) | |
327 | } | |
328 | ||
ce18d8c0 DM |
329 | fn hexstr_to_ipv4addr<T: AsRef<[u8]>>(hex: T) -> Result<Ipv4Addr, Error> { |
330 | let hex = hex.as_ref(); | |
331 | if hex.len() != 8 { | |
332 | bail!("Error while converting hex string to IPv4 address: unexpected string length"); | |
333 | } | |
334 | ||
45b5839e | 335 | let mut addr = [0u8; 4]; |
ce18d8c0 DM |
336 | for i in 0..4 { |
337 | addr[3 - i] = (hex_nibble(hex[i * 2])? << 4) + hex_nibble(hex[i * 2 + 1])?; | |
338 | } | |
339 | ||
340 | Ok(Ipv4Addr::from(addr)) | |
341 | } | |
342 | ||
343 | #[derive(Debug)] | |
344 | pub struct ProcFsNetRoute { | |
345 | pub dest: Ipv4Addr, | |
346 | pub gateway: Ipv4Addr, | |
347 | pub mask: Ipv4Addr, | |
348 | pub metric: u32, | |
349 | pub mtu: u32, | |
350 | pub iface: String, | |
351 | } | |
352 | ||
353 | pub fn read_proc_net_route() -> Result<Vec<ProcFsNetRoute>, Error> { | |
354 | let path = "/proc/net/route"; | |
355 | let file = OpenOptions::new().read(true).open(&path)?; | |
356 | ||
357 | let mut result = Vec::new(); | |
358 | for line in BufReader::new(&file).lines().skip(1) { | |
359 | let content = line?; | |
ccce46eb WB |
360 | if content.is_empty() { |
361 | continue; | |
362 | } | |
ce18d8c0 DM |
363 | let mut iter = content.split_whitespace(); |
364 | ||
ccce46eb WB |
365 | let mut next = || { |
366 | iter.next() | |
a11f6e88 | 367 | .ok_or_else(|| format_err!("Error while parsing '{}'", path)) |
ccce46eb | 368 | }; |
ce18d8c0 DM |
369 | |
370 | let (iface, dest, gateway) = (next()?, next()?, next()?); | |
ccce46eb WB |
371 | for _ in 0..3 { |
372 | next()?; | |
373 | } | |
ce18d8c0 DM |
374 | let (metric, mask, mtu) = (next()?, next()?, next()?); |
375 | ||
376 | result.push(ProcFsNetRoute { | |
377 | dest: hexstr_to_ipv4addr(dest)?, | |
378 | gateway: hexstr_to_ipv4addr(gateway)?, | |
379 | mask: hexstr_to_ipv4addr(mask)?, | |
380 | metric: metric.parse()?, | |
381 | mtu: mtu.parse()?, | |
382 | iface: iface.to_string(), | |
383 | }); | |
384 | } | |
385 | ||
386 | Ok(result) | |
387 | } | |
388 | ||
389 | fn hexstr_to_ipv6addr<T: AsRef<[u8]>>(hex: T) -> Result<Ipv6Addr, Error> { | |
390 | let hex = hex.as_ref(); | |
391 | if hex.len() != 32 { | |
392 | bail!("Error while converting hex string to IPv6 address: unexpected string length"); | |
393 | } | |
394 | ||
45b5839e WB |
395 | let mut addr = std::mem::MaybeUninit::<[u8; 16]>::uninit(); |
396 | let addr = unsafe { | |
397 | let ap = &mut *addr.as_mut_ptr(); | |
398 | for i in 0..16 { | |
399 | ap[i] = (hex_nibble(hex[i * 2])? << 4) + hex_nibble(hex[i * 2 + 1])?; | |
400 | } | |
401 | addr.assume_init() | |
402 | }; | |
ce18d8c0 DM |
403 | |
404 | Ok(Ipv6Addr::from(addr)) | |
405 | } | |
406 | ||
407 | fn hexstr_to_u8<T: AsRef<[u8]>>(hex: T) -> Result<u8, Error> { | |
408 | let hex = hex.as_ref(); | |
409 | if hex.len() != 2 { | |
410 | bail!("Error while converting hex string to u8: unexpected string length"); | |
411 | } | |
412 | ||
413 | Ok((hex_nibble(hex[0])? << 4) + hex_nibble(hex[1])?) | |
414 | } | |
415 | ||
416 | fn hexstr_to_u32<T: AsRef<[u8]>>(hex: T) -> Result<u32, Error> { | |
417 | let hex = hex.as_ref(); | |
418 | if hex.len() != 8 { | |
419 | bail!("Error while converting hex string to u32: unexpected string length"); | |
420 | } | |
421 | ||
45b5839e | 422 | let mut bytes = [0u8; 4]; |
ce18d8c0 DM |
423 | for i in 0..4 { |
424 | bytes[i] = (hex_nibble(hex[i * 2])? << 4) + hex_nibble(hex[i * 2 + 1])?; | |
425 | } | |
426 | ||
427 | Ok(u32::from_be_bytes(bytes)) | |
428 | } | |
429 | ||
430 | #[derive(Debug)] | |
431 | pub struct ProcFsNetIPv6Route { | |
432 | pub dest: Ipv6Addr, | |
433 | pub prefix: u8, | |
434 | pub gateway: Ipv6Addr, | |
435 | pub metric: u32, | |
436 | pub iface: String, | |
437 | } | |
438 | ||
439 | pub fn read_proc_net_ipv6_route() -> Result<Vec<ProcFsNetIPv6Route>, Error> { | |
440 | let path = "/proc/net/ipv6_route"; | |
441 | let file = OpenOptions::new().read(true).open(&path)?; | |
442 | ||
443 | let mut result = Vec::new(); | |
444 | for line in BufReader::new(&file).lines() { | |
445 | let content = line?; | |
ccce46eb WB |
446 | if content.is_empty() { |
447 | continue; | |
448 | } | |
ce18d8c0 DM |
449 | let mut iter = content.split_whitespace(); |
450 | ||
ccce46eb WB |
451 | let mut next = || { |
452 | iter.next() | |
453 | .ok_or_else(|| format_err!("Error while parsing '{}'", path)) | |
454 | }; | |
ce18d8c0 DM |
455 | |
456 | let (dest, prefix) = (next()?, next()?); | |
ccce46eb WB |
457 | for _ in 0..2 { |
458 | next()?; | |
459 | } | |
ce18d8c0 | 460 | let (nexthop, metric) = (next()?, next()?); |
ccce46eb WB |
461 | for _ in 0..3 { |
462 | next()?; | |
463 | } | |
ce18d8c0 DM |
464 | let iface = next()?; |
465 | ||
466 | result.push(ProcFsNetIPv6Route { | |
467 | dest: hexstr_to_ipv6addr(dest)?, | |
468 | prefix: hexstr_to_u8(prefix)?, | |
469 | gateway: hexstr_to_ipv6addr(nexthop)?, | |
470 | metric: hexstr_to_u32(metric)?, | |
471 | iface: iface.to_string(), | |
472 | }); | |
473 | } | |
474 | ||
475 | Ok(result) | |
476 | } | |
477 | ||
478 | #[cfg(test)] | |
479 | mod tests { | |
480 | use super::*; | |
481 | ||
482 | #[test] | |
483 | fn test_read_proc_net_route() { | |
484 | read_proc_net_route().unwrap(); | |
485 | } | |
863a16a5 | 486 | |
ce18d8c0 DM |
487 | #[test] |
488 | fn test_read_proc_net_ipv6_route() { | |
489 | read_proc_net_ipv6_route().unwrap(); | |
490 | } | |
491 | } |