use std::ffi::CString;
use yash_env::path::Path;
use yash_env::path::PathBuf;
use yash_env::str::UnixString;
use yash_env::variable::CDPATH;
use yash_env::Env;
use yash_env::System;
pub fn search(env: &Env, path: &Path) -> Option<PathBuf> {
if path.is_absolute() || path.starts_with(".") || path.starts_with("..") {
return None;
}
for base in env.variables.get(CDPATH)?.value.as_ref()?.split() {
let full_path = Path::new(base).join(path);
if let Some(full_path) = ensure_directory(&env.system, full_path) {
return Some(full_path).filter(|_| !base.is_empty());
}
}
None
}
fn ensure_directory<S: System>(system: &S, path: PathBuf) -> Option<PathBuf> {
match CString::new(path.into_unix_string().into_vec()) {
Ok(path) if system.is_directory(&path) => {
Some(UnixString::from_vec(path.into_bytes()).into())
}
_ => None,
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::rc::Rc;
use yash_env::system::r#virtual::Inode;
use yash_env::variable::Scope::Global;
use yash_env::variable::Value;
use yash_env::VirtualSystem;
fn create_dummy_file(system: &VirtualSystem, path: &str) {
system
.state
.borrow_mut()
.file_system
.save(path, Rc::new(Inode::default().into()))
.unwrap();
}
#[test]
fn unset_cdpath() {
let env = Env::new_virtual();
assert_eq!(search(&env, Path::new("foo")), None);
assert_eq!(search(&env, Path::new("/bar")), None);
}
#[test]
fn directory_not_found_from_cdpath() {
let mut env = Env::new_virtual();
env.get_or_create_variable(CDPATH, Global)
.assign("/foo:/bar", None)
.unwrap();
assert_eq!(search(&env, Path::new("one")), None);
assert_eq!(search(&env, Path::new("/two")), None);
}
#[test]
fn directory_found_from_scalar_cdpath() {
let system = Box::new(VirtualSystem::new());
create_dummy_file(&system, "/foo/one/file");
create_dummy_file(&system, "/bar/two/file");
let mut env = Env::with_system(system);
env.get_or_create_variable(CDPATH, Global)
.assign("/foo:/bar:/x", None)
.unwrap();
assert_eq!(
search(&env, Path::new("one")),
Some(PathBuf::from("/foo/one")),
);
assert_eq!(
search(&env, Path::new("two")),
Some(PathBuf::from("/bar/two")),
);
}
#[test]
fn directory_found_from_array_cdpath() {
let system = Box::new(VirtualSystem::new());
create_dummy_file(&system, "/foo/one/file");
create_dummy_file(&system, "/bar/two/file");
let mut env = Env::with_system(system);
env.get_or_create_variable(CDPATH, Global)
.assign(Value::array(["/foo", "/bar", "/x"]), None)
.unwrap();
assert_eq!(
search(&env, Path::new("one")),
Some(PathBuf::from("/foo/one")),
);
assert_eq!(
search(&env, Path::new("two")),
Some(PathBuf::from("/bar/two")),
);
}
#[test]
fn empty_directory_name_in_cdpath() {
let mut system = Box::new(VirtualSystem::new());
create_dummy_file(&system, "/foo/one/file");
create_dummy_file(&system, "/bar/two/file");
system.current_process_mut().chdir("/bar".into());
let mut env = Env::with_system(system);
env.get_or_create_variable(CDPATH, Global)
.assign("/foo::/baz", None)
.unwrap();
assert_eq!(search(&env, Path::new("two")), None);
}
#[test]
fn path_starting_with_dot() {
let mut system = Box::new(VirtualSystem::new());
create_dummy_file(&system, "/foo/one/file");
create_dummy_file(&system, "/bar/two/file");
system.current_process_mut().chdir("/".into());
let mut env = Env::with_system(system);
env.get_or_create_variable(CDPATH, Global)
.assign("/foo:/bar:/x", None)
.unwrap();
assert_eq!(search(&env, Path::new("./one")), None);
}
#[test]
fn path_starting_with_dot_dot() {
let mut system = Box::new(VirtualSystem::new());
create_dummy_file(&system, "/foo/one/file");
create_dummy_file(&system, "/bar/two/file");
system.current_process_mut().chdir("/bar/two".into());
let mut env = Env::with_system(system);
env.get_or_create_variable(CDPATH, Global)
.assign("/foo:/bar:/x", None)
.unwrap();
assert_eq!(search(&env, Path::new("../foo")), None);
}
#[test]
fn absolute_path() {
let system = Box::new(VirtualSystem::new());
create_dummy_file(&system, "/foo/one/file");
let mut env = Env::with_system(system);
env.get_or_create_variable(CDPATH, Global)
.assign("/foo:/bar:/x", None)
.unwrap();
assert_eq!(search(&env, Path::new("/foo")), None);
}
}