use super::Env;
use crate::system::AtFlags;
use crate::system::Errno;
use crate::system::AT_FDCWD;
use crate::variable::AssignError;
use crate::variable::Scope::Global;
use crate::variable::PWD;
use crate::System;
use nix::sys::stat::FileStat;
use std::ffi::CString;
use std::path::Path;
use thiserror::Error;
fn has_dot_or_dot_dot(path: &str) -> bool {
path.split('/').any(|c| c == "." || c == "..")
}
fn same_files(a: &FileStat, b: &FileStat) -> bool {
a.st_dev == b.st_dev && a.st_ino == b.st_ino
}
#[derive(Clone, Debug, Eq, Error, PartialEq)]
pub enum PreparePwdError {
#[error(transparent)]
AssignError(#[from] AssignError),
#[error("cannot obtain the current working directory path: {0}")]
GetCwdError(#[from] Errno),
}
impl Env {
#[must_use]
pub fn get_pwd_if_correct(&self) -> Option<&str> {
self.variables.get_scalar(PWD).filter(|pwd| {
if !Path::new(pwd).is_absolute() {
return false;
}
if has_dot_or_dot_dot(pwd) {
return false;
}
let Ok(cstr_pwd) = CString::new(pwd.as_bytes()) else {
return false;
};
const AT_FLAGS: AtFlags = AtFlags::empty();
let Ok(s1) = self.system.fstatat(AT_FDCWD, &cstr_pwd, AT_FLAGS) else {
return false;
};
let Ok(s2) = self.system.fstatat(AT_FDCWD, c".", AT_FLAGS) else {
return false;
};
same_files(&s1, &s2)
})
}
#[inline]
#[must_use]
fn has_correct_pwd(&self) -> bool {
self.get_pwd_if_correct().is_some()
}
pub fn prepare_pwd(&mut self) -> Result<(), PreparePwdError> {
if !self.has_correct_pwd() {
let dir = self
.system
.getcwd()?
.into_os_string()
.into_string()
.map_err(|_| Errno::EILSEQ)?;
let mut var = self.variables.get_or_new(PWD, Global);
var.assign(dir, None)?;
var.export(true);
}
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::system::r#virtual::FileBody;
use crate::system::r#virtual::INode;
use crate::variable::Value;
use crate::VirtualSystem;
use std::cell::RefCell;
use std::path::PathBuf;
use std::rc::Rc;
#[test]
fn has_dot_or_dot_dot_cases() {
assert!(!has_dot_or_dot_dot(""));
assert!(!has_dot_or_dot_dot("foo"));
assert!(!has_dot_or_dot_dot(".foo"));
assert!(!has_dot_or_dot_dot("foo.bar"));
assert!(!has_dot_or_dot_dot("..."));
assert!(!has_dot_or_dot_dot("/"));
assert!(!has_dot_or_dot_dot("/bar"));
assert!(!has_dot_or_dot_dot("/bar/baz"));
assert!(has_dot_or_dot_dot("."));
assert!(has_dot_or_dot_dot("/."));
assert!(has_dot_or_dot_dot("./"));
assert!(has_dot_or_dot_dot("/./"));
assert!(has_dot_or_dot_dot("foo/.//bar"));
assert!(has_dot_or_dot_dot(".."));
assert!(has_dot_or_dot_dot("/.."));
assert!(has_dot_or_dot_dot("../"));
assert!(has_dot_or_dot_dot("/../"));
assert!(has_dot_or_dot_dot("/foo//../bar"));
}
fn env_with_symlink_to_dir() -> Env {
let mut system = Box::new(VirtualSystem::new());
let mut state = system.state.borrow_mut();
state
.file_system
.save(
"/foo/bar/dir",
Rc::new(RefCell::new(INode {
body: FileBody::Directory {
files: Default::default(),
},
permissions: Default::default(),
})),
)
.unwrap();
state
.file_system
.save(
"/foo/link",
Rc::new(RefCell::new(INode {
body: FileBody::Symlink {
target: "bar/dir".into(),
},
permissions: Default::default(),
})),
)
.unwrap();
drop(state);
system.current_process_mut().cwd = PathBuf::from("/foo/bar/dir");
Env::with_system(system)
}
#[test]
fn prepare_pwd_no_value() {
let mut env = env_with_symlink_to_dir();
let result = env.prepare_pwd();
assert_eq!(result, Ok(()));
let pwd = env.variables.get(PWD).unwrap();
assert_eq!(pwd.value, Some(Value::scalar("/foo/bar/dir")));
assert!(pwd.is_exported);
}
#[test]
fn prepare_pwd_with_correct_path() {
let mut env = env_with_symlink_to_dir();
env.variables
.get_or_new(PWD, Global)
.assign("/foo/link", None)
.unwrap();
let result = env.prepare_pwd();
assert_eq!(result, Ok(()));
let pwd = env.variables.get(PWD).unwrap();
assert_eq!(pwd.value, Some(Value::scalar("/foo/link")));
}
#[test]
fn prepare_pwd_with_dot() {
let mut env = env_with_symlink_to_dir();
env.variables
.get_or_new(PWD, Global)
.assign("/foo/./link", None)
.unwrap();
let result = env.prepare_pwd();
assert_eq!(result, Ok(()));
let pwd = env.variables.get(PWD).unwrap();
assert_eq!(pwd.value, Some(Value::scalar("/foo/bar/dir")));
assert!(pwd.is_exported);
}
#[test]
fn prepare_pwd_with_dot_dot() {
let mut env = env_with_symlink_to_dir();
env.variables
.get_or_new(PWD, Global)
.assign("/foo/./link", None)
.unwrap();
let result = env.prepare_pwd();
assert_eq!(result, Ok(()));
let pwd = env.variables.get(PWD).unwrap();
assert_eq!(pwd.value, Some(Value::scalar("/foo/bar/dir")));
assert!(pwd.is_exported);
}
#[test]
fn prepare_pwd_with_wrong_path() {
let mut env = env_with_symlink_to_dir();
env.variables
.get_or_new(PWD, Global)
.assign("/foo/bar", None)
.unwrap();
let result = env.prepare_pwd();
assert_eq!(result, Ok(()));
let pwd = env.variables.get(PWD).unwrap();
assert_eq!(pwd.value, Some(Value::scalar("/foo/bar/dir")));
assert!(pwd.is_exported);
}
#[test]
fn prepare_pwd_with_non_absolute_path() {
let mut system = Box::new(VirtualSystem::new());
let mut state = system.state.borrow_mut();
state
.file_system
.save(
"/link",
Rc::new(RefCell::new(INode {
body: FileBody::Symlink { target: ".".into() },
permissions: Default::default(),
})),
)
.unwrap();
drop(state);
system.current_process_mut().cwd = PathBuf::from("/");
let mut env = Env::with_system(system);
env.variables
.get_or_new(PWD, Global)
.assign("link", None)
.unwrap();
let result = env.prepare_pwd();
assert_eq!(result, Ok(()));
let pwd = env.variables.get(PWD).unwrap();
assert_eq!(pwd.value, Some(Value::scalar("/")));
assert!(pwd.is_exported);
}
}