diff --git a/Cargo.lock b/Cargo.lock index 020c2726f3..1c37aeefa4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -198,6 +198,10 @@ dependencies = [ "redox_syscall", ] +[[package]] +name = "redox-path" +version = "0.1.0" + [[package]] name = "redox_syscall" version = "0.4.1" @@ -224,6 +228,7 @@ dependencies = [ "rand_jitter", "rand_xorshift", "redox-exec", + "redox-path", "redox_syscall", "sc", "unicode-width", diff --git a/Cargo.toml b/Cargo.toml index bac37d9691..963a5f137a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,7 +9,14 @@ name = "relibc" crate-type = ["staticlib"] [workspace] -members = ["src/crt0", "src/crti", "src/crtn", "src/ld_so", "src/platform/redox/redox-exec"] +members = [ + "src/crt0", + "src/crti", + "src/crtn", + "src/ld_so", + "src/platform/redox/redox-exec", + "src/platform/redox/redox-path", +] exclude = ["tests", "dlmalloc-rs"] [build-dependencies] @@ -47,6 +54,7 @@ sc = "0.2.3" [target.'cfg(target_os = "redox")'.dependencies] redox_syscall = "0.4" redox-exec = { path = "src/platform/redox/redox-exec" } +redox-path = { path = "src/platform/redox/redox-path" } [features] default = ["check_against_libc_crate"] diff --git a/src/platform/redox/path.rs b/src/platform/redox/path.rs index 7e4c202e29..0586f68255 100644 --- a/src/platform/redox/path.rs +++ b/src/platform/redox/path.rs @@ -1,78 +1,14 @@ -use syscall::{data::Stat, error::*, flag::*}; - use alloc::{borrow::ToOwned, boxed::Box, string::String, vec::Vec}; +use syscall::{data::Stat, error::*, flag::*}; use super::{libcscheme, FdGuard}; use crate::sync::Mutex; +pub use redox_path::canonicalize_using_cwd; + // TODO: Define in syscall const PATH_MAX: usize = 4096; -/// Make a relative path absolute. -/// -/// Given a cwd of "scheme:/path", this his function will turn "foo" into "scheme:/path/foo". -/// "/foo" will turn into "file:/foo". "bar:/foo" will be used directly, as it is already -/// absolute -pub fn canonicalize_using_cwd<'a>(cwd_opt: Option<&str>, path: &'a str) -> Option { - let mut canon = if path.find(':').is_none() { - let cwd = cwd_opt?; - - let mut canon = if !path.starts_with('/') { - let mut c = cwd.to_owned(); - if !c.ends_with('/') { - c.push('/'); - } - c - } else { - // Fall back to the file scheme if no scheme provided - "file:".to_owned() - }; - - canon.push_str(&path); - canon - } else { - path.to_owned() - }; - - // NOTE: assumes the scheme does not include anything like "../" or "./" - let mut result = { - let parts = canon - .split('/') - .rev() - .scan(0, |nskip, part| { - if part == "." { - Some(None) - } else if part == ".." { - *nskip += 1; - Some(None) - } else if *nskip > 0 { - *nskip -= 1; - Some(None) - } else { - Some(Some(part)) - } - }) - .filter_map(|x| x) - .filter(|x| !x.is_empty()) - .collect::>(); - parts.iter().rev().fold(String::new(), |mut string, &part| { - string.push_str(part); - string.push('/'); - string - }) - }; - result.pop(); // remove extra '/' - - // replace with the root of the scheme if it's empty - Some(if result.is_empty() { - let pos = canon.find(':').map_or(canon.len(), |p| p + 1); - canon.truncate(pos); - canon - } else { - result - }) -} - // XXX: chdir is not marked thread-safe (MT-safe) by POSIX. But on Linux it is simply run as a // syscall and is therefore atomic, which is presumably why Rust's libstd doesn't synchronize // access to this. diff --git a/src/platform/redox/redox-path/Cargo.toml b/src/platform/redox/redox-path/Cargo.toml new file mode 100644 index 0000000000..17343a5248 --- /dev/null +++ b/src/platform/redox/redox-path/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "redox-path" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] diff --git a/src/platform/redox/redox-path/src/lib.rs b/src/platform/redox/redox-path/src/lib.rs new file mode 100644 index 0000000000..c84761ff1d --- /dev/null +++ b/src/platform/redox/redox-path/src/lib.rs @@ -0,0 +1,171 @@ +#![no_std] + +extern crate alloc; + +use alloc::{borrow::ToOwned, format, string::String, vec::Vec}; + +pub fn split_path<'a>(path: &'a str) -> Option<(&'a str, &'a str)> { + let mut parts = path.splitn(2, ':'); + let scheme = parts.next()?; + let reference = parts.next()?; + Some((scheme, reference)) +} + +/// Make a relative path absolute. +/// +/// Given a cwd of "scheme:/path", this his function will turn "foo" into "scheme:/path/foo". +/// "/foo" will turn into "file:/foo". "bar:/foo" will be used directly, as it is already +/// absolute +pub fn canonicalize_using_cwd<'a>(cwd_opt: Option<&str>, path: &'a str) -> Option { + let (scheme, reference) = match split_path(path) { + Some((scheme, reference)) => (scheme, reference.to_owned()), + None => { + let cwd = cwd_opt?; + let (scheme, reference) = split_path(cwd)?; + if path.starts_with('/') { + (scheme, path.to_owned()) + } else { + let mut reference = reference.to_owned(); + if !reference.ends_with('/') { + reference.push('/'); + } + reference.push_str(path); + (scheme, reference) + } + } + }; + + let mut canonical = { + let parts = reference + .split('/') + .rev() + .scan(0, |nskip, part| { + if part == "." { + Some(None) + } else if part == ".." { + *nskip += 1; + Some(None) + } else if *nskip > 0 { + *nskip -= 1; + Some(None) + } else { + Some(Some(part)) + } + }) + .filter_map(|x| x) + .filter(|x| !x.is_empty()) + .collect::>(); + parts.iter().rev().fold(String::new(), |mut string, &part| { + string.push_str(part); + string.push('/'); + string + }) + }; + canonical.pop(); // remove extra '/' + + Some(format!("{}:{}", scheme, canonical)) +} + +#[cfg(test)] +mod tests { + use super::*; + use alloc::string::ToString; + + // Tests absolute paths without scheme + #[test] + fn test_absolute() { + let cwd_opt = Some("foo:"); + assert_eq!( + canonicalize_using_cwd(cwd_opt, "/"), + Some("foo:".to_string()) + ); + assert_eq!( + canonicalize_using_cwd(cwd_opt, "/file"), + Some("foo:file".to_string()) + ); + assert_eq!( + canonicalize_using_cwd(cwd_opt, "/folder/file"), + Some("foo:folder/file".to_string()) + ); + assert_eq!( + canonicalize_using_cwd(cwd_opt, "/folder/../file"), + Some("foo:file".to_string()) + ); + assert_eq!( + canonicalize_using_cwd(cwd_opt, "/folder/../.."), + Some("foo:".to_string()) + ); + assert_eq!( + canonicalize_using_cwd(cwd_opt, "/folder/../../../.."), + Some("foo:".to_string()) + ); + assert_eq!( + canonicalize_using_cwd(cwd_opt, "/.."), + Some("foo:".to_string()) + ); + } + + // Test relative paths + #[test] + fn test_relative() { + let cwd_opt = Some("foo:"); + assert_eq!( + canonicalize_using_cwd(cwd_opt, "file"), + Some("foo:file".to_string()) + ); + assert_eq!( + canonicalize_using_cwd(cwd_opt, "folder/file"), + Some("foo:folder/file".to_string()) + ); + assert_eq!( + canonicalize_using_cwd(cwd_opt, "folder/../file"), + Some("foo:file".to_string()) + ); + assert_eq!( + canonicalize_using_cwd(cwd_opt, "folder/../.."), + Some("foo:".to_string()) + ); + assert_eq!( + canonicalize_using_cwd(cwd_opt, "folder/../../../.."), + Some("foo:".to_string()) + ); + assert_eq!( + canonicalize_using_cwd(cwd_opt, ".."), + Some("foo:".to_string()) + ); + } + + // Tests paths prefixed with scheme + #[test] + fn test_scheme() { + let cwd_opt = Some("foo:"); + assert_eq!( + canonicalize_using_cwd(cwd_opt, "bar:"), + Some("bar:".to_string()) + ); + assert_eq!( + canonicalize_using_cwd(cwd_opt, "bar:file"), + Some("bar:file".to_string()) + ); + assert_eq!( + canonicalize_using_cwd(cwd_opt, "bar:folder/file"), + Some("bar:folder/file".to_string()) + ); + assert_eq!( + canonicalize_using_cwd(cwd_opt, "bar:folder/../file"), + Some("bar:file".to_string()) + ); + assert_eq!( + canonicalize_using_cwd(cwd_opt, "bar:folder/../.."), + Some("bar:".to_string()) + ); + assert_eq!( + canonicalize_using_cwd(cwd_opt, "bar:folder/../../../.."), + Some("bar:".to_string()) + ); + assert_eq!( + canonicalize_using_cwd(cwd_opt, "bar:.."), + Some("bar:".to_string()) + ); + } +}