Doug
Doug

Reputation: 35186

How do I remove the \\?\ prefix from a canonical Windows path?

On Windows, Path::canonicalize() returns the path in the format:

\\\\?\\C:\\projects\\3rdparty\\rust...

This is because it is the correct canonical path, and allows 'long' paths on Windows (see Why does my canonicalized path get prefixed with \\?\).

However, this is not a user-friendly path, and people do not understand it.

For display and logging purposes how can I easily remove this prefix in a generic platform independent way?

Path::components will return a component \\?\C: as the first component...

Should I convert this to a &str and use a regex? Is there some other more ergonomic method for removing the prefix, e.g. some type with a Display implementation that automatically does the right thing?

My requirements specifically are:

Example:

use std::path::{Path, PathBuf};

fn simple_path<P: AsRef<Path>>(p: P) -> String {
    String::from(p.as_ref().to_str().unwrap()) // <-- ?? What to do here?
}

pub fn main() {
    let path = PathBuf::from("C:\temp").canonicalize().unwrap();
    let display_path = simple_path(path);
    println!("Output: {}", display_path);
}

Upvotes: 14

Views: 3863

Answers (4)

Kornel
Kornel

Reputation: 100170

Use the dunce crate:

let compatible_path = dunce::canonicalize(&any_path);

Just stripping \\?\ may give wrong/invalid paths. The dunce crate checks whether the UNC path is compatible and converts the path accurately whenever possible. It passes through all other paths. It compiles to plain fs::canonicalize() on non-Windows.

Upvotes: 13

jerliol
jerliol

Reputation: 195

You can implement a StripCanonicalization trait for PathBuf, which helps you keep a chainable calling.

pub trait StripCanonicalization where Self: AsRef<Path> {
    #[cfg(not(target_os = "windows"))]
    fn strip_canonicalization(&self) -> PathBuf {
        self.as_ref().to_path_buf()
    }

    #[cfg(target_os = "windows")]
    fn strip_canonicalization(&self) -> PathBuf {
        const VERBATIM_PREFIX: &str = r#"\\?\"#;
        let p = self.as_ref().display().to_string();
        if p.starts_with(VERBATIM_PREFIX) {
            PathBuf::from(&p[VERBATIM_PREFIX.len()..])
        } else {
            self.as_ref().to_path_buf()
        }
    }
}

impl StripCanonicalization for PathBuf {}

Calling like

let p = Path::new(s).canonicalize()?;
let p = p.strip_canonicalization();

Upvotes: -1

ArtemGr
ArtemGr

Reputation: 12547

Here's a version that reconstructs the path from the components.

It helps with std::fs::canonicalize on Windows, but a naive Path::new(r"\\?\C:\projects\3rdparty\rust") at play.rust-lang.org will produce a single-component Path.

use std::path::{Component, Path, PathBuf, Prefix};

// Should remove the “\\?” prefix from the canonical path
// in order to avoid CMD bailing with “UNC paths are not supported”.
let head = path.components().next().ok_or("empty path?")?;
let diskˢ;
let head = if let Component::Prefix(prefix) = head {
    if let Prefix::VerbatimDisk(disk) = prefix.kind() {
        diskˢ = format!("{}:", disk as char);
        Path::new(&diskˢ).components().next().ok_or("empty path?")?
    } else {
        head
    }
} else {
    head
};
println!("{:?}", head);
let path = std::iter::once(head)
    .chain(path.components().skip(1))
    .collect::<PathBuf>();

Upvotes: 1

Shepmaster
Shepmaster

Reputation: 431529

The straightforward answer is to do platform-specific string munging:

use std::path::{Path, PathBuf};

#[cfg(not(target_os = "windows"))]
fn adjust_canonicalization<P: AsRef<Path>>(p: P) -> String {
    p.as_ref().display().to_string()
}

#[cfg(target_os = "windows")]
fn adjust_canonicalization<P: AsRef<Path>>(p: P) -> String {
    const VERBATIM_PREFIX: &str = r#"\\?\"#;
    let p = p.as_ref().display().to_string();
    if p.starts_with(VERBATIM_PREFIX) {
        p[VERBATIM_PREFIX.len()..].to_string()
    } else {
        p
    }
}

pub fn main() {
    let path = PathBuf::from(r#"C:\Windows\System32"#)
        .canonicalize()
        .unwrap();
    let display_path = adjust_canonicalization(path);
    println!("Output: {}", display_path);
}

For the record, I don't agree that your premise is a good idea. Windows Explorer handles these verbatim paths just fine, and I think users are capable of handling it as well.

For [...] logging purposes

This sounds like a terrible idea. If you are logging something, you want to know the exact path, not some potentially incorrect path.

Upvotes: 8

Related Questions