Fix: mountinfo-Vergleich entschlüsselt oktal escapte Pfade

/proc/self/mountinfo escaped Leerzeichen/Tab/Newline/Backslash im
Mountpoint-Feld oktal (siehe man 5 proc), der Vergleich erfolgte aber
gegen den unescapten, tatsächlichen Pfad - ein Mountpoint mit z. B.
einem Leerzeichen wurde dadurch nie als gemountet erkannt.
parse_mountinfo_source() entschlüsselt das Feld jetzt vor dem
Vergleich (byteweise, um mehrbytige UTF-8-Zeichen im Pfad nicht zu
zerlegen).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LjyzpGWECyKSBWz5DtkTGz
This commit is contained in:
2026-09-15 23:06:11 +02:00
co-authored by Claude Sonnet 5
parent bc324dd4b7
commit 2b3514ab66
+43 -1
View File
@@ -30,7 +30,12 @@ fn parse_mountinfo_source(mountinfo: &str, mount_point: &Path) -> Option<String>
let fields: Vec<&str> = line.split_whitespace().collect();
// Feld 4 (Index 4) ist der Mountpoint; danach folgen optionale Felder bis zum
// Trenner "-", danach fs_type (Index+1) und source (Index+2).
if fields.len() < 5 || fields[4] != target {
//
// Der Kernel escaped Leerzeichen/Tab/Newline/Backslash in Pfadfeldern oktal
// (siehe `man 5 proc`, Abschnitt zu mountinfo) - ohne `unescape_octal_field` würde ein
// Mountpoint mit z. B. einem Leerzeichen im Pfad hier nie als "gemountet" erkannt, weil
// `\040` niemals gleich einem echten Leerzeichen ist.
if fields.len() < 5 || unescape_octal_field(fields[4]) != target {
continue;
}
let Some(dash_pos) = fields.iter().position(|&f| f == "-") else {
@@ -44,6 +49,36 @@ fn parse_mountinfo_source(mountinfo: &str, mount_point: &Path) -> Option<String>
result
}
/// Kehrt die oktale Escape-Kodierung um, die der Kernel in `/proc/self/mountinfo` für
/// Leerzeichen (`\040`), Tab (`\011`), Newline (`\012`) und Backslash (`\134`) in Pfadfeldern
/// verwendet (siehe `man 5 proc`).
fn unescape_octal_field(field: &str) -> std::borrow::Cow<'_, str> {
if !field.contains('\\') {
return std::borrow::Cow::Borrowed(field);
}
// Arbeitet auf rohen Bytes statt `char`s: ein Byte einer mehrbytigen UTF-8-Sequenz per
// `as char` in einen eigenständigen `char` umzuwandeln würde nicht-ASCII-Zeichen im Pfad
// (z. B. Umlaute) in mehrere falsche Codepoints zerlegen.
let bytes = field.as_bytes();
let mut out = Vec::with_capacity(bytes.len());
let mut i = 0;
while i < bytes.len() {
if bytes[i] == b'\\'
&& i + 3 < bytes.len()
&& let Ok(octal) = std::str::from_utf8(&bytes[i + 1..i + 4])
&& let Ok(value) = u8::from_str_radix(octal, 8)
{
out.push(value);
i += 4;
} else {
out.push(bytes[i]);
i += 1;
}
}
std::borrow::Cow::Owned(String::from_utf8_lossy(&out).into_owned())
}
#[cfg(test)]
mod tests {
use super::*;
@@ -66,6 +101,13 @@ mod tests {
assert_eq!(source, None);
}
#[test]
fn matches_a_mount_point_containing_a_space_escaped_by_the_kernel() {
let mountinfo = "43 36 0:26 / /mnt/my\\040drive rw,relatime shared:2 - cifs //server/share rw";
let source = parse_mountinfo_source(mountinfo, &PathBuf::from("/mnt/my drive"));
assert_eq!(source.as_deref(), Some("//server/share"));
}
#[test]
fn last_matching_entry_wins_for_stacked_mounts() {
let mountinfo = "36 35 98:0 / /mnt/x rw - nfs server:/export rw\n\