{"record":{"id":"8006b0f626d9898d","repo":"ClementTsang/bottom","slug":"stat-string-is-malformed","errorCode":null,"errorMessage":"stat string is malformed","messagePattern":"stat string is malformed","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src/collection/processes/linux/process.rs","lineNumber":107,"sourceCode":"        // \"last\" closing parentheses.\n        let (comm, rest) = {\n            let start_paren = line\n                .find('(')\n                .ok_or_else(|| anyhow!(\"start paren missing\"))?;\n\n            // So, we _could_ try and be smart and only parse a limited slice of\n            // the string - however, there appears to be no ABI\n            // guarantees of comm length anymore, so we just take the hit and do\n            // an rsplit over the full string.\n            //\n            // Sources/discussion:\n            // - https://man.archlinux.org/man/proc_pid_stat.5.en\n            // - https://stackoverflow.com/questions/23534263/what-is-the-maximum-allowed-limit-on-the-length-of-a-process-name#comment138697304_23534499\n            // - https://elixir.bootlin.com/linux/v7.1.3/source/fs/proc/array.c#L100\n            // - https://github.com/ClementTsang/bottom/pull/2163#issuecomment-5017857303\n            let (comm, rest) = line[start_paren + 1..]\n                .rsplit_once(\") \")\n                .ok_or_else(|| anyhow!(\"stat string is malformed\"))?;\n\n            (comm.to_string(), rest)\n        };\n\n        let mut rest = rest.split(' ');\n        let state = next_part(&mut rest)?\n            .chars()\n            .next()\n            .ok_or_else(|| anyhow!(\"missing state\"))?;\n        let ppid: Pid = next_part(&mut rest)?.parse()?;\n\n        // Skip 4 fields (pgrp, session, tty_nr, tpgid)\n        let mut rest = rest.skip(4);\n\n        // read flags for kernel thread (PF_KTHREAD from include/linux/sched.h)\n        let flags: u32 = next_part(&mut rest)?.parse()?;\n        let is_kernel_thread: bool = flags & 0x00200000 != 0;\n","sourceCodeStart":89,"sourceCodeEnd":125,"githubUrl":"https://github.com/ClementTsang/bottom/blob/b77d3175028849824e987c35177e8f61450d72e7/src/collection/processes/linux/process.rs#L89-L125","documentation":"After finding the opening parenthesis, the parser uses rsplit_once(\") \") to split the comm field from the remaining stat fields; this requires a closing parenthesis followed by a space. If the closing ') ' is absent the stat line is structurally invalid, so the library raises this error.","triggerScenarios":"Process::from_file receives a stat line where the comm field is never closed — e.g. truncated file content (read raced with process death), a comm name containing malformed data, or hand-crafted/invalid stat content in tests.","commonSituations":"Reading /proc/[pid]/stat for a process that is terminating while being read, yielding partial lines; synthetic stat fixtures in tests that only include the opening paren; corrupted or non-standard procfs output.","solutions":["Re-read the stat file; a transient truncation usually resolves on retry because the process either completed or disappeared.","Skip the malformed entry during process enumeration instead of failing the whole scan.","If constructing test fixtures, always emit a full stat line: '(comm) R ppid ...' with a closing ') ' after the comm field."],"exampleFix":"// before\nlet (comm, rest) = line[start_paren + 1..]\n    .rsplit_once(\") \")\n    .ok_or_else(|| anyhow!(\"stat string is malformed\"))?;\n// after (caller-side retry for transient truncation)\nlet proc = (0..3).find_map(|_| {\n    Process::from_file(&stat_path).ok()\n}).unwrap_or_else(|| panic!(\"process {:?} unreadable\", stat_path));","handlingStrategy":"retry","validationCode":"fn stat_is_complete(line: &str) -> bool {\n    line.find('(').and_then(|s| line[s..].find(\") \")).is_some()\n}\nif !stat_is_complete(&line) { /* re-read or skip */ }","typeGuard":"fn split_stat(line: &str) -> Option<(&str, &str)> {\n    let s = line.find('(')? + 1;\n    line[s..].rsplit_once(\") \")\n}","tryCatchPattern":"for _ in 0..2 {\n    match Process::from_file(path) {\n        Ok(p) => break Some(p),\n        Err(e) if e.to_string().contains(\"malformed\") => continue,\n        Err(e) => return Err(e.into()),\n    }\n}","preventionTips":["Retry stat reads once on parse failure — truncation from process exit is common","In tests, always generate full stat lines including the ') ' separator","Treat per-entry parse failure as 'process gone' during enumeration"],"tags":["linux","procfs","parsing","processes"],"backgroundTag":"invalid-argument-format","analyzedSha":"b77d3175028849824e987c35177e8f61450d72e7","analyzedAt":"2026-09-07T14:53:21.246Z","contentChangedAt":"2026-09-07T14:53:21.246Z","schemaVersion":2},"datasetVersion":"2026-09-16T04:17:20.429Z"}