Input paths w/spaces when inputting into AsRef<Path>, Into<PathBuf>, etc

If I use a String to formulate my paths, I can start off with this:

let path = "C:/Users/My User/file.txt".to_string();

However, I need to use double quotes on the outside to ensure "My User" doesn't get split:

let path = format!(r#""C:/Users/My User/file.txt""#);

using r# allows me to input double-quotes. However, when I need to pass the String through an Into<PathBuf> or AsRef<Path>, I get an error as such:

Error Message: could not open `"C:/Users/My User/file.txt"`;

How can I input paths that may or may not have spaces into a function that expects to transform the input into a Path or PathBuf?

You need not use the double quotes it seems, as though Path and friends have included the double quotes as part of the path. What is the use case here? In general you should be doing path manipulations through PathBuf paired with format, and not format entirely. Additionally, are you sure that having spaces does not work?

The version presented above is simplified from the real use-case, but ...

it appears that other files can be created in the path that has spaces. I will continue debugging. For now, this could just be a discussion thread for ideas related to the post

Quoting the path to avoid splitting on spaces should only be necessary when interacting with the filesystem through the command shell. (Or maybe a textual configuration format, but that should be handled automatically by deserialization.) System calls only care about path separators, and interfaces like PathBuf abstract them away.

1 Like

Quotes are needed on the command line to ensure that arguments with spaces in them are passed as a single argument, however the command.arg method allows passing command line arguments with whitespace without extra quotes:

https://doc.rust-lang.org/std/process/struct.Command.html#method.arg

Quote characters are only needed in PathBufs when the path itself contains a literal quote character.

1 Like

Still unable to figure out the issue.

I will show the code. First, I start off by calling this:

    /// Saves the file with blocking
    pub fn save_locally_blocking(&self) -> Result<PathBuf, FsError<String>> where Self: SyncIO {
        let real_file_path = get_pathbuf(format!("{}{}.{}", *HYXE_VIRTUAL_DIR, generate_random_string(HYXE_FILE_OBFUSCATED_LEN), HYXE_FILE_EXT));
        log::info!("[HyxeFile] Saving {} to {}", &self.file_name, real_file_path.to_str().unwrap());
        SyncIO::serialize_to_local_fs(self, &real_file_path)
            .and_then(|_| Ok(real_file_path))
    }

The log::info! prints this out to the console:

[2020-07-15T13:12:55Z INFO  hyxe_fs::hyxe_file] [HyxeFile] Saving password_file to C:\Users\Thomas Braun\.HyxeWave\virtual\UfRY5gIjHOUNvItWQsLZvbSlt6mZCUzO2Sn9DjBQd4ZxqS2muJ93SuI0Nt0azNxOxA0MyjuvDFXJaSNQtX17imhDp14mK0Cqa3acUAmkxgpTPree7lApfKphI2pajJfAi1xkK00zXawuGgu679HtBydApGv4efLTNPyRpDaBSXya8vXZoUv45CpNtTRLFJl40WqWtaZpxKrNtGtLaXphEZ5MwGCZg8.vxh

Then, the SyncIO::serialize ... function looks like this:

fn serialize_to_local_fs<P: AsRef<Path>>(&self, location: P) -> Result<(), FsError<String>> {
        if let Some(parent_path) = location.as_ref().parent() {
            crate::system_file_manager::make_dir_all_blocking(parent_path)?;
        }
        // the above executes successfully, then we go into the function below
        crate::system_file_manager::write(self, location)
    }

the crate::system_file_manager::write looks like this:

/// Writes a serializable object to the desired path
pub fn write<D: Sized + Serialize, P: AsRef<Path>>(object: &D, path: P) -> Result<(), FsError<String>> {
    log::info!("J3");
    std::fs::File::create(path)
        .map_err(|err| FsError::IoError(err.to_string()))
        .and_then(|file| {
            log::info!("J4 || {:?}", &file);
            let ref mut buf_writer = std::io::BufWriter::new(file);
            log::info!("J5");
            // change: use BufWriter, as it's "50x" faster https://stackoverflow.com/questions/49983101/serialization-of-large-struct-to-disk-with-serde-and-bincode-is-slow?noredirect=1&lq=1
            bincode2::serialize_into(buf_writer, object).map_err(|err| FsError::IoError(err.to_string()))
        })
}

The printlns! print only J3, then the closure exists with an error, specifically:

The system cannot find the path specified. (os error 3)

Not sure why this is the case

Does the directory the file is being created in exist? In cases like this I usually do

if let Some(parent) = path.parent() {
    std::fs::create_dir_all(parent)?;
}

edit: Sorry, I missed the make_dir_all_blocking call. I guess I would still make sure it exists just in case there's something wrong with it.

Yes, I make_dir_all just incase the parent directory doesn't exist

You might be running into a file name length issue. Can you try it with a shorter file name to see if that works? There is a way to handle longer paths in windows by prepending \\?\ to the path name. I forget if that would help in this circumstance though.

3 Likes

Okay, I'll check it out. FYI,

pub const HYXE_FILE_OBFUSCATED_LEN: usize = 222;

I just looked it up and there’s a std lib function path.canonicalize() that I believe does the trick I’m talking about for you. So you can also try passing the result of that to `create'.

1 Like

You were right! It appears that when I set the length to 50, it works as expected.

I'll give this canonicalize fn a try

Nice spot, details about the limits here: https://docs.microsoft.com/en-us/windows/win32/fileio/naming-a-file#maximum-path-length-limitation

2 Likes

As someone who has spent way too much time debugging that same issue in the past, I’m happy to help.

1 Like

Just for completeness, I believe the issue can also be worked around in this case by changing the current directory to the parent directory and then creating the file with just the file name. That brings the path length passed to the system write call under 260 and makes windows happy. But I think just using the canonicalized version everywhere is easier and avoids changing the current directory.

1 Like

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.