0

Background

I tried to develop a Rust lib to assist with swapping filenames for any two files, but I had problems when I tried to dealing with paths on WSL.

Examples

Normal Situation

File1 File2
Before D:\test\1.ext1 D:\test\2.ext2
After D:\test\2.ext1 D:\test\1.ext2

Relative Path

File1 File2
Before ./1.ext1 ~/2.ext2
After D:\test\2.ext1 C:\Users\xxx\1.ext2

WSL Situation

File1 File2
Before \\wsl.localhost\Debian\home\user1\1.ext1 \\wsl.localhost\Debian\home\user1\2.ext2
After (WHAT I WANT) \\wsl.localhost\Debian\home\user1\2.ext1 \\wsl.localhost\Debian\home\user1\1.ext2

What I've done

MWE:

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

fn main() {
    // Please create this file in your wsl for test.
    // `~/1.ext1`
    // MODIFY THIS with your wsl user name
    let wsl_user_name = "";

    // Please create this on working dir (windows) for test.
    // `./2.ext2`

    let current_exe = std::env::current_exe().unwrap();
    let base_dir = current_exe.parent().unwrap();

    let wsl_file = PathBuf::from(format!(
        r"\\wsl.localhost\Debian\home\{}\1.ext1",
        wsl_user_name
    ));
    let win_file = PathBuf::from(r"./2.ext2");

    let result_wsl = path_get_absolute_exist(&wsl_file, base_dir);
    dbg!(result_wsl);

    let result_win = path_get_absolute_exist(&win_file, base_dir);
    dbg!(result_win);
}

fn path_get_absolute_exist(path: &Path, base_dir: &Path) -> (bool, PathBuf) {
    if path.as_os_str().is_empty() {
        return (false, path.to_path_buf());
    }

    let mut path = path.to_path_buf();

    #[cfg(windows)]
    {
        use std::path::{Component, Prefix};

        path = {
            let temp = path.to_str().unwrap_or("").replace("/", "\\");
            PathBuf::from(temp)
        };

        let is_absolute = {
            let mut components = path.components();
            if let Some(Component::Prefix(prefix_component)) = components.next() {
                let has_root_dir = matches!(components.next(), Some(Component::RootDir));
                dbg!(has_root_dir);
                if !has_root_dir {
                    false
                } else {
                    dbg!(prefix_component.kind());
                    matches!(
                        prefix_component.kind(), //Useful for judge `\\wsl.localhost\` but useless when test exist()
                        Prefix::VerbatimUNC(..)
                            | Prefix::UNC(..)
                            | Prefix::VerbatimDisk(..)
                            | Prefix::Disk(_)
                            | Prefix::DeviceNS(..)
                            | Prefix::Verbatim(_)
                    )
                }
            } else {
                dbg!(path.is_absolute());
                path.is_absolute()
            }
        };

        if !is_absolute {
            if path.starts_with("~") {
                if let Ok(home_dir) = std::env::var("USERPROFILE") {
                    let mut new_path = PathBuf::from(home_dir);
                    let remaining = path.strip_prefix("~/").ok();
                    if let Some(rem) = remaining {
                        new_path.push(rem);
                        path = new_path;
                    } else if path.to_string_lossy() == "~" {
                        path = new_path;
                    } else {
                        // "~something"
                        path = base_dir.join(path);
                    }
                }
            } else {
                path = base_dir.join(path);
            }
        }
        dbg!(format!("Path Final: {}", &path.display()));
    }

    #[cfg(not(windows))]
    {
        path = {
            let temp = path.to_str().unwrap_or("").replace("\\", "/");
            PathBuf::from(temp)
        };

        if !path.is_absolute() {
            if path.starts_with("~") {
                if let Ok(home_dir) = std::env::var("HOME") {
                    let mut new_path = PathBuf::from(home_dir);
                    if let Some(remaining) = path.strip_prefix("~/") {
                        new_path.push(remaining);
                    } else if path.to_string_lossy() == "~" {
                        // Just "~", so it's the home directory
                    }
                    path = new_path;
                }
            } else {
                path = base_dir.join(path);
            }
        }
        dbg!(format!("Path Final: {}", &path.display()));
    }

    let canonical = path.canonicalize();
    match canonical {
        Ok(x) => (x.exists(), x),
        Err(e) => {
            eprintln!("{}", e);
            (path.exists(), path)
        }
    }
}

error info

[src\exchange.rs:161:13] is_absolute = true
[src\exchange.rs:190:9] &path = "\\\\wsl.localhost\\Debian\\home\\user1\\1.ext1" 
[src\exchange.rs:161:13] is_absolute = true
[src\exchange.rs:190:9] &path = "\\\\wsl.localhost\\Debian\\home\\user1\\2.ext2"
File does not exist

I didn't give all the code here for length reasons, if you need all the code, please check it out at https://github.com/Mikachu2333/exchange_name_lib

1
  • 2
    Please add a minimal reproducible example within the question, external links are not sufficient for various reasons. Commented Oct 19 at 11:29

1 Answer 1

-1
  1. For recognize WSL path, one should use std::path::{Component, Prefix} in order to recognize WSL path such as \\\\wsl.localhost\\Debian\\home\\LinkChou\\1.ext1

  2. Although WSL path is "absolute" in the view of users, rust would not recognize it as 'absolute', but PathBuf.join(new) would replace the old as the following example shows.

    let dir = =std::env::current_dir().unwrap();
    let file = r"\\\\wsl.localhost\\Debian\\home\\LinkChou\\1.ext1";
    let path = PathBuf::from(file);
    assert(path.is_absolute());//panic
    
    let result = dir.join(path);
    assert(result.to_path_buf(),dir.to_path_buf())//true
    
  3. Complete code as the following shows.

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

fn main() {
    // Please create this file in your wsl for test before run.
    // `~/1.ext1`
    // MODIFY THIS with your wsl user name
    let wsl_user_name = "LinkChou";
    let wsl_type = "Debian";

    // Please create this on working dir (windows) for test.
    // `./2.ext2`

    let current_exe = std::env::current_exe().unwrap();
    let base_dir = current_exe.parent().unwrap();
    
    // test files path
    let wsl_file = PathBuf::from(format!(
        r"\\wsl.localhost\{}\home\{}\1.ext1",
        wsl_type, wsl_user_name
    ));
    let win_file = PathBuf::from(r"2.ext2");

    let result_wsl = path_get_absolute_exist(&wsl_file, base_dir);
    dbg!(&result_wsl);//true, wsl file path(same as original str)
    assert!(result_wsl.1.exists());//true

    let result_win = path_get_absolute_exist(&win_file, base_dir);
    dbg!(result_win);// false, return absolute path
}

fn path_get_absolute_exist(path: &Path, base_dir: &Path) -> (bool, PathBuf) {
    if path.as_os_str().is_empty() {
        return (false, path.to_path_buf());
    }

    let mut path = path.to_path_buf();
    
    //only judge wsl,... .etc on win
    #[cfg(windows)]
    {
        use std::path::{Component, Prefix};

        path = {
            let temp = path.to_str().unwrap_or("").replace("/", "\\");
            PathBuf::from(temp)
        };

        let is_absolute = {
            let mut components = path.components();
            if let Some(Component::Prefix(prefix_component)) = components.next() {
                let has_root_dir = matches!(components.next(), Some(Component::RootDir));
                dbg!(has_root_dir);
                if !has_root_dir {
                    false
                } else {
                    dbg!(prefix_component.kind());
                    matches!(
                        prefix_component.kind(), //Useful for judge `\\wsl.localhost\` but useless when test exist()
                        Prefix::VerbatimUNC(..)
                            | Prefix::UNC(..)
                            | Prefix::VerbatimDisk(..)
                            | Prefix::Disk(_)
                            | Prefix::DeviceNS(..)
                            | Prefix::Verbatim(_)
                    )
                }
            } else {
                dbg!(path.is_absolute());
                path.is_absolute()
            }
        };

        if !is_absolute {
            if path.starts_with("~") {
                if let Ok(home_dir) = std::env::var("USERPROFILE") {
                    let mut new_path = PathBuf::from(home_dir);
                    let remaining = path.strip_prefix("~/").ok();
                    if let Some(rem) = remaining {
                        new_path.push(rem);
                        path = new_path;
                    } else if path.to_string_lossy() == "~" {
                        path = new_path;
                    } else {
                        // "~something"
                        path = base_dir.join(path);
                    }
                }
            } else if path.starts_with(".") {
                let remaining = path.strip_prefix(".\\").ok();
                path = base_dir.join(remaining.unwrap());
            } else {
                path = base_dir.join(path);
            }
        }
        dbg!(format!("Path Final: {}", &path.display()));
    }

    #[cfg(not(windows))]
    {
        path = {
            let temp = path.to_str().unwrap_or("").replace("\\", "/");
            PathBuf::from(temp)
        };

        if !path.is_absolute() {
            if path.starts_with("~") {
                if let Ok(home_dir) = std::env::var("HOME") {
                    let mut new_path = PathBuf::from(home_dir);
                    if let Some(remaining) = path.strip_prefix("~/") {
                        new_path.push(remaining);
                    } else if path.to_string_lossy() == "~" {
                        // Just "~", so it's the home directory
                    }
                    path = new_path;
                }
            } else if path.starts_with(".") {
                let remaining = path.strip_prefix("./").ok();
                path = base_dir.join(remaining.unwrap());
            } else {
                path = base_dir.join(path);
            }
        }
        dbg!(format!("Path Final: {}", &path.display()));
    }

    let canonical = path.canonicalize();//we have to use canonicalize here because it will panic if path's type is wsl
    match canonical {
        Ok(x) => (x.exists(), x),
        Err(e) => {
            eprintln!("{}", e);
            (path.exists(), path)
        }
    }
}

  1. Use the code shown above to replace path.is_absolute() and path.canonicalize() for avoiding panic and resolve wsl like odd path.
Sign up to request clarification or add additional context in comments.

1 Comment

As it’s currently written, your answer is unclear. Please edit to add additional details that will help others understand how this addresses the question asked. You can find more information on how to write good answers in the help center.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.