1 use syscall::{data::Stat, error::*, flag::*}; 2 3 use alloc::{ 4 borrow::{Cow, ToOwned}, 5 boxed::Box, 6 string::String, 7 vec::Vec, 8 }; 9 10 use super::FdGuard; 11 use crate::sync::Mutex; 12 13 // TODO: Define in syscall 14 const PATH_MAX: usize = 4096; 15 16 /// Make a relative path absolute. 17 /// 18 /// Given a cwd of "scheme:/path", this his function will turn "foo" into "scheme:/path/foo". 19 /// "/foo" will turn into "scheme:/foo". "bar:/foo" will be used directly, as it is already 20 /// absolute 21 pub fn canonicalize_using_cwd<'a>(cwd_opt: Option<&str>, path: &'a str) -> Option<String> { 22 let mut canon = if path.find(':').is_none() { 23 let cwd = cwd_opt?; 24 let path_start = cwd.find(':')? + 1; 25 26 let mut canon = if !path.starts_with('/') { 27 let mut c = cwd.to_owned(); 28 if !c.ends_with('/') { 29 c.push('/'); 30 } 31 c 32 } else { 33 cwd[..path_start].to_owned() 34 }; 35 36 canon.push_str(&path); 37 canon 38 } else { 39 path.to_owned() 40 }; 41 42 // NOTE: assumes the scheme does not include anything like "../" or "./" 43 let mut result = { 44 let parts = canon 45 .split('/') 46 .rev() 47 .scan(0, |nskip, part| { 48 if part == "." { 49 Some(None) 50 } else if part == ".." { 51 *nskip += 1; 52 Some(None) 53 } else if *nskip > 0 { 54 *nskip -= 1; 55 Some(None) 56 } else { 57 Some(Some(part)) 58 } 59 }) 60 .filter_map(|x| x) 61 .filter(|x| !x.is_empty()) 62 .collect::<Vec<_>>(); 63 parts.iter().rev().fold(String::new(), |mut string, &part| { 64 string.push_str(part); 65 string.push('/'); 66 string 67 }) 68 }; 69 result.pop(); // remove extra '/' 70 71 // replace with the root of the scheme if it's empty 72 Some(if result.is_empty() { 73 let pos = canon.find(':').map_or(canon.len(), |p| p + 1); 74 canon.truncate(pos); 75 canon 76 } else { 77 result 78 }) 79 } 80 81 // XXX: chdir is not marked thread-safe (MT-safe) by POSIX. But on Linux it is simply run as a 82 // syscall and is therefore atomic, which is presumably why Rust's libstd doesn't synchronize 83 // access to this. 84 // 85 // https://internals.rust-lang.org/t/synchronized-ffi-access-to-posix-environment-variable-functions/15475 86 // 87 // chdir is however signal-safe, forbidding the use of locks. We therefore call sigprocmask before 88 // and after acquiring the locks. (TODO: ArcSwap? That will need to be ported to no_std first, 89 // though). 90 pub fn chdir(path: &str) -> Result<()> { 91 let _siglock = SignalMask::lock(); 92 let mut cwd_guard = CWD.lock(); 93 94 let canonicalized = 95 canonicalize_using_cwd(cwd_guard.as_deref(), path).ok_or(Error::new(ENOENT))?; 96 97 let fd = syscall::open(&canonicalized, O_STAT | O_CLOEXEC)?; 98 let mut stat = Stat::default(); 99 if syscall::fstat(fd, &mut stat).is_err() || (stat.st_mode & MODE_TYPE) != MODE_DIR { 100 return Err(Error::new(ENOTDIR)); 101 } 102 let _ = syscall::close(fd); 103 104 *cwd_guard = Some(canonicalized.into_boxed_str()); 105 106 // TODO: Check that the dir exists and is a directory. 107 108 Ok(()) 109 } 110 111 pub fn clone_cwd() -> Option<Box<str>> { 112 let _siglock = SignalMask::lock(); 113 CWD.lock().clone() 114 } 115 116 // TODO: MaybeUninit 117 pub fn getcwd(buf: &mut [u8]) -> Option<usize> { 118 let _siglock = SignalMask::lock(); 119 let cwd_guard = CWD.lock(); 120 let cwd = cwd_guard.as_deref().unwrap_or("").as_bytes(); 121 122 // But is already checked not to be empty. 123 if buf.len() - 1 < cwd.len() { 124 return None; 125 } 126 127 buf[..cwd.len()].copy_from_slice(&cwd); 128 buf[cwd.len()..].fill(0_u8); 129 130 Some(cwd.len()) 131 } 132 133 // TODO: Move cwd from kernel to libc. It is passed via auxiliary vectors. 134 pub fn canonicalize(path: &str) -> Result<String> { 135 let _siglock = SignalMask::lock(); 136 let cwd = CWD.lock(); 137 canonicalize_using_cwd(cwd.as_deref(), path).ok_or(Error::new(ENOENT)) 138 } 139 140 // TODO: arraystring? 141 static CWD: Mutex<Option<Box<str>>> = Mutex::new(None); 142 143 pub fn setcwd_manual(cwd: Box<str>) { 144 let _siglock = SignalMask::lock(); 145 *CWD.lock() = Some(cwd); 146 } 147 148 /// RAII guard able to magically fix signal unsafety, by disabling signals during a critical 149 /// section. 150 pub struct SignalMask { 151 oldset: [u64; 2], 152 } 153 impl SignalMask { 154 pub fn lock() -> Self { 155 let mut oldset = [0; 2]; 156 syscall::sigprocmask(syscall::SIG_SETMASK, Some(&[!0, !0]), Some(&mut oldset)) 157 .expect("failed to run sigprocmask"); 158 Self { oldset } 159 } 160 } 161 impl Drop for SignalMask { 162 fn drop(&mut self) { 163 let _ = syscall::sigprocmask(syscall::SIG_SETMASK, Some(&self.oldset), None); 164 } 165 } 166 167 pub fn open(path: &str, flags: usize) -> Result<usize> { 168 // TODO: SYMLOOP_MAX 169 const MAX_LEVEL: usize = 64; 170 171 let mut resolve_buf = [0_u8; 4096]; 172 let mut path = path; 173 174 for _ in 0..MAX_LEVEL { 175 let canon = canonicalize(path)?; 176 match syscall::open(&*canon, flags) { 177 Ok(fd) => return Ok(fd), 178 Err(error) if error == Error::new(EXDEV) => { 179 let resolve_flags = O_CLOEXEC | O_SYMLINK | O_RDONLY; 180 let resolve_fd = FdGuard::new(syscall::open(&*canon, resolve_flags)?); 181 182 let bytes_read = syscall::read(*resolve_fd, &mut resolve_buf)?; 183 // TODO: make resolve_buf PATH_MAX + 1 bytes? 184 if bytes_read == resolve_buf.len() { 185 return Err(Error::new(ENAMETOOLONG)); 186 } 187 188 // If the symbolic link path is non-UTF8, it cannot be opened, and is thus 189 // considered a "dangling symbolic link". 190 path = core::str::from_utf8(&resolve_buf[..bytes_read]) 191 .map_err(|_| Error::new(ENOENT))?; 192 } 193 Err(other_error) => return Err(other_error), 194 } 195 } 196 Err(Error::new(ELOOP)) 197 } 198