]> git.proxmox.com Git - proxmox.git/blame - proxmox-sys/src/linux/procfs.rs
add tools::parse submodule, move hex_nibble to it
[proxmox.git] / proxmox-sys / src / linux / procfs.rs
CommitLineData
4c9a8b5c 1use std::collections::HashSet;
ce18d8c0
DM
2use std::fs::OpenOptions;
3use std::io::{BufRead, BufReader};
ce18d8c0 4use std::net::{Ipv4Addr, Ipv6Addr};
db83a675 5use std::str::FromStr;
4c9a8b5c
WB
6
7use failure::*;
ce18d8c0 8use lazy_static::lazy_static;
ce18d8c0 9use libc;
4c9a8b5c
WB
10
11use proxmox_tools::fs::file_read_firstline;
6cf330c9 12use proxmox_tools::parse::hex_nibble;
ce18d8c0
DM
13
14/// POSIX sysconf call
15pub 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
22lazy_static! {
23 static ref CLOCK_TICKS: f64 = sysconf(libc::_SC_CLK_TCK) as f64;
24}
25
26pub 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
35pub 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 42fn 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]
107fn 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 125pub 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
131pub 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
140pub 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
149pub 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
160pub 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)]
168pub 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
178pub 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)]
221pub 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
230static CPU_INFO: Option<ProcFsCPUInfo> = None;
231
232pub 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)]
277pub struct ProcFsMemUsage {
278 pub size: u64,
279 pub resident: u64,
280 pub shared: u64,
281}
282
283pub 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)]
300pub struct ProcFsNetDev {
301 pub device: String,
302 pub receive: u64,
303 pub send: u64,
304}
305
306pub 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
329fn 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)]
344pub 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
353pub 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
389fn 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
407fn 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
416fn 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)]
431pub struct ProcFsNetIPv6Route {
432 pub dest: Ipv6Addr,
433 pub prefix: u8,
434 pub gateway: Ipv6Addr,
435 pub metric: u32,
436 pub iface: String,
437}
438
439pub 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)]
479mod 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}