use std::io::{self, Write};
use crate::types::*;
fn csv_quote(field: &str) -> String {
if field.contains(',') || field.contains('"') || field.contains('\n') {
let escaped = field.replace('"', "\"\"");
format!("\"{escaped}\"")
} else {
field.to_string()
}
}
pub fn print_csv(procs: &[Process]) {
let out = io::stdout();
let mut out = out.lock();
let _ = writeln!(out, "COMMAND,PID,USER,FD,TYPE,DEVICE,SIZE/OFF,NODE,NAME");
for p in procs {
let user = p.username();
for f in &p.files {
let _ = writeln!(
out,
"{},{},{},{},{},{},{},{},{}",
csv_quote(&p.command),
p.pid,
csv_quote(&user),
csv_quote(&f.fd.with_access(f.access)),
csv_quote(f.file_type.as_str()),
csv_quote(&f.device_str()),
csv_quote(&f.size_or_offset_str()),
csv_quote(&f.node_str()),
csv_quote(&f.full_name()),
);
}
}
}
#[cfg(test)]
mod tests {
use super::*;
fn make_proc(pid: i32, cmd: &str, files: Vec<OpenFile>) -> Process {
Process {
pid,
ppid: 1,
pgid: pid,
uid: 0,
command: cmd.to_string(),
files,
sel_flags: 0,
sel_state: 0,
}
}
fn make_file(fd: i32, ft: FileType, name: &str) -> OpenFile {
OpenFile {
fd: FdName::Number(fd),
access: Access::ReadWrite,
file_type: ft,
name: name.to_string(),
..Default::default()
}
}
#[test]
fn csv_quote_no_special() {
assert_eq!(csv_quote("hello"), "hello");
}
#[test]
fn csv_quote_with_comma() {
assert_eq!(csv_quote("hello,world"), "\"hello,world\"");
}
#[test]
fn csv_quote_with_quotes() {
assert_eq!(csv_quote("say \"hi\""), "\"say \"\"hi\"\"\"");
}
#[test]
fn csv_quote_with_newline() {
assert_eq!(csv_quote("line1\nline2"), "\"line1\nline2\"");
}
#[test]
fn csv_quote_empty() {
assert_eq!(csv_quote(""), "");
}
#[test]
fn print_csv_empty_no_panic() {
print_csv(&[]);
}
#[test]
fn print_csv_with_data_no_panic() {
let procs = vec![make_proc(
42,
"test",
vec![make_file(3, FileType::Reg, "/tmp/foo")],
)];
print_csv(&procs);
}
#[test]
fn print_csv_special_chars_no_panic() {
let procs = vec![make_proc(
42,
"my,cmd",
vec![make_file(3, FileType::Reg, "/path/with \"quotes\"")],
)];
print_csv(&procs);
}
#[test]
fn print_csv_multiple_files_no_panic() {
let procs = vec![make_proc(
42,
"test",
vec![
make_file(0, FileType::Chr, "/dev/null"),
make_file(1, FileType::Chr, "/dev/tty"),
make_file(3, FileType::Reg, "/tmp/data.txt"),
],
)];
print_csv(&procs);
}
#[test]
fn print_csv_multiple_processes_no_panic() {
let procs = vec![
make_proc(1, "init", vec![make_file(0, FileType::Chr, "/dev/null")]),
make_proc(2, "bash", vec![make_file(3, FileType::Reg, "/tmp/x")]),
];
print_csv(&procs);
}
}