ClementTsang/bottom · error

stat string is malformed

Error message

stat string is malformed

What it means

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.

Solutions

  1. Re-read the stat file; a transient truncation usually resolves on retry because the process either completed or disappeared.
  2. Skip the malformed entry during process enumeration instead of failing the whole scan.
  3. If constructing test fixtures, always emit a full stat line: '(comm) R ppid ...' with a closing ') ' after the comm field.

Example fix

// before
let (comm, rest) = line[start_paren + 1..]
    .rsplit_once(") ")
    .ok_or_else(|| anyhow!("stat string is malformed"))?;
// after (caller-side retry for transient truncation)
let proc = (0..3).find_map(|_| {
    Process::from_file(&stat_path).ok()
}).unwrap_or_else(|| panic!("process {:?} unreadable", stat_path));
Defensive patterns

Strategy: retry

Validate before calling

fn stat_is_complete(line: &str) -> bool {
    line.find('(').and_then(|s| line[s..].find(") ")).is_some()
}
if !stat_is_complete(&line) { /* re-read or skip */ }

Type guard

fn split_stat(line: &str) -> Option<(&str, &str)> {
    let s = line.find('(')? + 1;
    line[s..].rsplit_once(") ")
}

Try / catch

for _ in 0..2 {
    match Process::from_file(path) {
        Ok(p) => break Some(p),
        Err(e) if e.to_string().contains("malformed") => continue,
        Err(e) => return Err(e.into()),
    }
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Understand the failure class

Background: "Invalid ... format", "must be in format X", "does not look like a ..." — invalid argument format errors across CLI tools and libraries — this error's family across 17 libraries.

Related errors


AI-assisted analysis of ClementTsang/bottom@b77d317502 (2026-09-07). Data as JSON: /api/errors/8006b0f626d9898d. Report an issue: GitHub.

Appendix: source

Thrown at src/collection/processes/linux/process.rs:107

        // "last" closing parentheses.
        let (comm, rest) = {
            let start_paren = line
                .find('(')
                .ok_or_else(|| anyhow!("start paren missing"))?;

            // So, we _could_ try and be smart and only parse a limited slice of
            // the string - however, there appears to be no ABI
            // guarantees of comm length anymore, so we just take the hit and do
            // an rsplit over the full string.
            //
            // Sources/discussion:
            // - https://man.archlinux.org/man/proc_pid_stat.5.en
            // - https://stackoverflow.com/questions/23534263/what-is-the-maximum-allowed-limit-on-the-length-of-a-process-name#comment138697304_23534499
            // - https://elixir.bootlin.com/linux/v7.1.3/source/fs/proc/array.c#L100
            // - https://github.com/ClementTsang/bottom/pull/2163#issuecomment-5017857303
            let (comm, rest) = line[start_paren + 1..]
                .rsplit_once(") ")
                .ok_or_else(|| anyhow!("stat string is malformed"))?;

            (comm.to_string(), rest)
        };

        let mut rest = rest.split(' ');
        let state = next_part(&mut rest)?
            .chars()
            .next()
            .ok_or_else(|| anyhow!("missing state"))?;
        let ppid: Pid = next_part(&mut rest)?.parse()?;

        // Skip 4 fields (pgrp, session, tty_nr, tpgid)
        let mut rest = rest.skip(4);

        // read flags for kernel thread (PF_KTHREAD from include/linux/sched.h)
        let flags: u32 = next_part(&mut rest)?.parse()?;
        let is_kernel_thread: bool = flags & 0x00200000 != 0;

View on GitHub (pinned to b77d317502)