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:
+43
-1
@@ -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\
|
||||
|
||||
Reference in New Issue
Block a user