diff --git a/Cargo.lock b/Cargo.lock index 4f792bf3b8..80b6ede390 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -447,9 +447,9 @@ dependencies = [ [[package]] name = "redox-path" -version = "0.3.1" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "436d45c2b6a5b159d43da708e62b25be3a4a3d5550d654b72216ade4c4bfd717" +checksum = "54ec1b67c01c63545205ea6d218b336751399f0d8974c1a635c28604bedb81bc" [[package]] name = "redox-rt" diff --git a/Cargo.toml b/Cargo.toml index 4891f6a806..038a9dbd4f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -68,7 +68,7 @@ workspace = true bitflags = "2" ioslice = { version = "0.6", default-features = false } plain = "0.2" -redox-path = "0.3" +redox-path = "0.4.0" redox_protocols = { package = "libredox", version = "0.1.18", default-features = false, features = ["protocol"] } redox_syscall = "0.9.0" diff --git a/src/c_str.rs b/src/c_str.rs index 37880314a7..8ed3cdea7a 100644 --- a/src/c_str.rs +++ b/src/c_str.rs @@ -268,7 +268,7 @@ impl<'a, T: Kind> NulStr<'a, T> { } impl<'a> CStr<'a> { pub fn to_owned_cstring(self) -> CString { - CString::from(unsafe { core::ffi::CStr::from_ptr(self.ptr.as_ptr()) }) + CString::from(self.to_cstr()) } pub fn borrow(string: &'a CString) -> Self { unsafe { Self::from_ptr(string.as_ptr()) } @@ -284,6 +284,9 @@ impl<'a> CStr<'a> { pub fn to_str(self) -> Result<&'a str, Utf8Error> { core::str::from_utf8(self.to_bytes()) } + pub fn to_cstr(self) -> &'a core::ffi::CStr { + unsafe { core::ffi::CStr::from_ptr(self.ptr.as_ptr()) } + } pub fn to_string_lossy(self) -> Cow<'a, str> { String::from_utf8_lossy(self.to_bytes()) } diff --git a/src/macros.rs b/src/macros.rs index efe5fef5fe..a058bccd04 100644 --- a/src/macros.rs +++ b/src/macros.rs @@ -93,8 +93,6 @@ macro_rules! trace_expr { use $crate::header::errno::STR_ERROR; use $crate::platform; - log::trace!("{}", format_args!($($arg)*)); - let trace_old_errno = platform::ERRNO.get(); platform::ERRNO.set(0); diff --git a/src/platform/logger.rs b/src/platform/logger.rs index bdf4dce188..a93e512457 100644 --- a/src/platform/logger.rs +++ b/src/platform/logger.rs @@ -241,7 +241,7 @@ impl RedoxLogger { process_name: Option<&str>, writer: &mut W, ) -> fmt::Result { - let target = record.module_path().unwrap_or(record.target()); + let target = record.file().unwrap_or(""); let level = record.level(); let message = record.args(); @@ -250,7 +250,7 @@ impl RedoxLogger { let process_name = process_name.unwrap_or(""); let line = &LineFmt(line_number); - writeln!(writer, "[{process_name}@{target}{line} {level}] {message}",) + writeln!(writer, "[{level} {process_name}@{target}{line}] {message}",) } } diff --git a/src/platform/mod.rs b/src/platform/mod.rs index 5e5f4c62c8..35190a808c 100644 --- a/src/platform/mod.rs +++ b/src/platform/mod.rs @@ -386,7 +386,8 @@ pub unsafe fn init_inner(auxvs: Box<[[usize; 2]]>) { .expect("failed to move cwd fd to upper table") }), ) { - self::sys::path::set_cwd_manual(cwd_path, cwd_fd); + self::sys::path::set_cwd_manual(cwd_path, cwd_fd) + .expect("cwd_path is not valid redox path") } } diff --git a/src/platform/redox/event.rs b/src/platform/redox/event.rs index ef979920af..b662f663b6 100644 --- a/src/platform/redox/event.rs +++ b/src/platform/redox/event.rs @@ -8,6 +8,7 @@ use crate::header::{ use super::libredox::RawResult; +use redox_path::RedoxStr; use syscall::{EINVAL, Error, Result}; #[unsafe(no_mangle)] @@ -18,7 +19,7 @@ pub unsafe extern "C" fn redox_event_queue_create_v1(flags: u32) -> RawResult { } Ok(super::libredox::openat( AT_FDCWD, - "/scheme/event", + RedoxStr::new("/scheme/event").unwrap(), O_CLOEXEC | O_CREAT | O_RDWR, 0o700, )? as usize) diff --git a/src/platform/redox/libredox.rs b/src/platform/redox/libredox.rs index 4c935f0296..166f083536 100644 --- a/src/platform/redox/libredox.rs +++ b/src/platform/redox/libredox.rs @@ -2,6 +2,7 @@ use core::{mem, slice, str}; use alloc::vec::Vec; use ioslice::IoSlice; +use redox_path::RedoxStr; use redox_protocols::protocol::{ProcKillTarget, SocketCall, WaitFlags}; use redox_rt::sys::{WaitpidTarget, posix_read, posix_write, std_fs_call_ro, std_fs_call_wo}; use syscall::{ @@ -23,20 +24,20 @@ use super::Sys; pub type RawResult = usize; -pub fn openat(dirfd: c_int, path: &str, oflag: c_int, mode: mode_t) -> Result { +pub fn openat(dirfd: c_int, path: RedoxStr<'_>, oflag: c_int, mode: mode_t) -> Result { let usize_fd = super::path::openat( dirfd, path, ((oflag as usize) & 0xFFFF_0000) | ((mode as usize) & 0xFFFF), )?; - c_int::try_from(usize_fd) - .map_err(|_| { - let _ = redox_rt::sys::close(usize_fd); - Error::new(EMFILE) - }) - .map(|f| f as usize) + if let Err(_) = c_int::try_from(usize_fd) { + let _ = redox_rt::sys::close(usize_fd); + return Err(Error::new(EMFILE)); + } + Ok(usize_fd) } + pub fn fchmod(fd: usize, new_mode: u16) -> Result<()> { std_fs_call_wo( fd, @@ -226,12 +227,12 @@ pub unsafe extern "C" fn redox_open_v1( flags: u32, mode: u16, ) -> RawResult { - Error::mux(openat( - AT_FDCWD, - unsafe { str::from_utf8_unchecked(slice::from_raw_parts(path_base, path_len)) }, - flags as c_int, - mode as mode_t, - )) + let path = unsafe { str::from_utf8_unchecked(slice::from_raw_parts(path_base, path_len)) }; + let path = match RedoxStr::new(path) { + Some(path) => path, + None => return Error::mux(Err(Error::new(EINVAL))), + }; + Error::mux(openat(AT_FDCWD, path, flags as c_int, mode as mode_t)) } #[unsafe(no_mangle)] pub unsafe extern "C" fn redox_openat_v1( @@ -241,9 +242,11 @@ pub unsafe extern "C" fn redox_openat_v1( flags: u32, fcntl_flags: u32, ) -> RawResult { + // Goes straight to redox_rt, no need parsing RedoxStr + let path = unsafe { str::from_utf8_unchecked(slice::from_raw_parts(path_base, path_len)) }; Error::mux(redox_rt::sys::openat( fd, - unsafe { str::from_utf8_unchecked(slice::from_raw_parts(path_base, path_len)) }, + path, flags as usize, fcntl_flags as usize, )) diff --git a/src/platform/redox/mod.rs b/src/platform/redox/mod.rs index 6b5c95b61f..238687a2d3 100644 --- a/src/platform/redox/mod.rs +++ b/src/platform/redox/mod.rs @@ -1,3 +1,4 @@ +use alloc::borrow::Cow; use core::{ convert::TryFrom, mem::{self, size_of}, @@ -5,24 +6,25 @@ use core::{ ptr, slice, str, }; use object::bytes_of_slice_mut; +use redox_path::RedoxStr; use redox_protocols::protocol::{WaitFlags, wifstopped}; use redox_rt::{ RtTcb, sys::{Resugid, WaitpidTarget}, }; use syscall::{ - self, EILSEQ, ESRCH, Error, MODE_PERM, StdFsCallKind, StdFsCallMeta, + self, ESRCH, Error, MODE_PERM, StdFsCallKind, StdFsCallMeta, data::{Map, TimeSpec as redox_timespec}, dirent::DirentHeader, }; use self::{ exec::Executable, - path::{FileLock, canonicalize, openat2, openat2_path}, + path::{FileLock, openat2, openat2_path}, }; use super::{Pal, Read, types::*}; use crate::{ - c_str::{CStr, CString}, + c_str::CStr, error::{Errno, Result}, fs::File, header::{ @@ -185,7 +187,7 @@ impl Pal for Sys { } fn chdir(path: CStr) -> Result<()> { - let path = path.to_str().map_err(|_| Errno(EINVAL))?; + let path = RedoxStr::new_c(path.to_cstr()).ok_or_else(|| Errno(EINVAL))?; path::chdir(path)?; Ok(()) } @@ -286,14 +288,12 @@ impl Pal for Sys { if MASK & flags != 0 { return Err(Errno(EINVAL)); } - let mut path = path - .and_then(|cs| str::from_utf8(cs.to_bytes()).ok()) - .ok_or(Errno(ENOENT))?; + let mut path = path.ok_or(Errno(ENOENT))?; if path.is_empty() { if flags & AT_EMPTY_PATH == AT_EMPTY_PATH { if dirfd == AT_FDCWD { - path = "."; + path = c".".into(); } else { return Ok(libredox::fchmod(dirfd as usize, mode as u16)?); } @@ -313,11 +313,11 @@ impl Pal for Sys { if MASK & flags != 0 { return Err(Errno(EINVAL)); } - let mut path = path.to_str().map_err(|_| Errno(ENOENT))?; + let mut path = path; if path.is_empty() { if flags & AT_EMPTY_PATH == AT_EMPTY_PATH { if fildes == AT_FDCWD { - path = "."; + path = c".".into(); } else { return Ok(libredox::fchown(fildes as usize, owner as _, group as _)?); } @@ -465,13 +465,12 @@ impl Pal for Sys { fn fstatat(dirfd: c_int, path: Option, mut buf: Out, flags: c_int) -> Result<()> { // `path` should be non-null. - let path = path.ok_or(Errno(EFAULT))?; - let mut path = str::from_utf8(path.to_bytes()).ok().ok_or(Errno(EILSEQ))?; + let mut path = path.ok_or(Errno(EFAULT))?; if path.is_empty() { if flags & AT_EMPTY_PATH == AT_EMPTY_PATH { if dirfd == AT_FDCWD { - path = "."; + path = c".".into(); } else { return Ok(unsafe { libredox::fstat(dirfd as usize, buf.as_mut_ptr()) }?); } @@ -526,11 +525,11 @@ impl Pal for Sys { times: *const timespec, flag: c_int, ) -> Result<()> { - let mut path = path.to_str().map_err(|_| Errno(ENOENT))?; + let mut path = path; if path.is_empty() { if flag & AT_EMPTY_PATH == AT_EMPTY_PATH { if dirfd == AT_FDCWD { - path = "."; + path = c".".into(); } else { return Ok(unsafe { libredox::futimens(dirfd as usize, times) }?); } @@ -774,7 +773,7 @@ impl Pal for Sys { if (flags & !(AT_SYMLINK_FOLLOW)) != 0 { return Err(Errno(EINVAL)); } - let newpath = newpath.to_str().map_err(|_| Errno(EINVAL))?; + let newpath = RedoxStr::new_c(newpath.to_cstr()).ok_or_else(|| Errno(EINVAL))?; // By default, we don't follow the symlink if there is one. // We only follow it if AT_SYMLINK_FOLLOW is passed in flags. @@ -786,7 +785,7 @@ impl Pal for Sys { } let file = File::openat(fd1, oldpath, oflags)?; - let newpath = openat2_path(fd2, newpath, 0)?; + let newpath: Cow<'_, str> = openat2_path(fd2, newpath, 0)?.into(); syscall::flink(*file as usize, newpath)?; Ok(()) } @@ -954,7 +953,7 @@ impl Pal for Sys { } fn openat(dirfd: c_int, path: CStr, oflag: c_int, mode: mode_t) -> Result { - let path = path.to_str().map_err(|_| Errno(EINVAL))?; + let path = RedoxStr::new_c(path.to_cstr()).ok_or(Errno(EINVAL))?; // POSIX states that umask should affect the following: // @@ -1099,7 +1098,6 @@ impl Pal for Sys { } fn readlinkat(dirfd: c_int, path: CStr, out: &mut [u8]) -> Result { - let path = str::from_utf8(path.to_bytes()).map_err(|_| Errno(ENOENT))?; let file = openat2( dirfd, path, @@ -1121,21 +1119,24 @@ impl Pal for Sys { return Err(Errno(EOPNOTSUPP)); } - let new_path = new_path.to_str().map_err(|_| Errno(EINVAL))?; + let new_path = RedoxStr::new_c(new_path.to_cstr()).ok_or(Errno(EINVAL))?; // Fail if the target exists with RENAME_NOREPLACE. if flags & RENAME_NOREPLACE != 0 - && let Ok(fd) = - libredox::openat(new_dir, &new_path, fcntl::O_PATH | fcntl::O_CLOEXEC, 0) - .map(FdGuard::new) + && let Ok(fd) = libredox::openat( + new_dir, + new_path.clone(), + fcntl::O_PATH | fcntl::O_CLOEXEC, + 0, + ) + .map(FdGuard::new) { return Err(Errno(EEXIST)); } - let old_path = old_path.to_str().map_err(|_| Errno(EINVAL))?; // oflags are the same as Sys::rename above. let source = openat2(old_dir, old_path, 0, fcntl::O_NOFOLLOW | fcntl::O_PATH)?; - let target = openat2_path(new_dir, new_path, 0)?; + let target: Cow<'_, str> = openat2_path(new_dir, new_path, 0)?.into(); // I'm avoiding Sys::rename to avoid reallocating a CString from a String. syscall::frename(*source as usize, target) .map(|_| ()) @@ -1753,13 +1754,12 @@ impl Pal for Sys { let mut lines = BufReader::new(&mut file).lines(); let mut read_line = |mut dst: Out<[u8]>| { - // TODO: set nul byte without allocating CString - let line = match lines.next() { - Some(Ok(l)) => CString::new(l).map_err(|_| Errno(EIO))?, + let mut line = match lines.next() { + Some(Ok(l)) => l, None | Some(Err(_)) => return Err(Errno(EIO)), }; - - let line_slice: &[u8] = line.as_bytes_with_nul(); + line.push('\0'); + let line_slice: &[u8] = line.as_bytes(); if line_slice.len() > UTSLENGTH { return Err(Errno(EIO)); } @@ -1795,10 +1795,10 @@ impl Pal for Sys { if (flags & !AT_REMOVEDIR) != 0 { return Err(Errno(EINVAL)); } - let path = path.to_str().map_err(|_| Errno(EINVAL))?; + let path = RedoxStr::new_c(path.to_cstr()).ok_or(Errno(EINVAL))?; let path = openat2_path(fd, path, 0)?; - let canon = canonicalize(&path)?; - redox_rt::sys::unlink(&canon, flags.try_into().map_err(|_| Errno(EINVAL))?)?; + let path: Cow<'_, str> = path.into(); + redox_rt::sys::unlink(path, flags.try_into().map_err(|_| Errno(EINVAL))?)?; Ok(()) } diff --git a/src/platform/redox/path.rs b/src/platform/redox/path.rs index 2d94bdf6a8..b08022e68b 100644 --- a/src/platform/redox/path.rs +++ b/src/platform/redox/path.rs @@ -1,20 +1,17 @@ -use alloc::{ - collections::btree_set::BTreeSet, - ffi::CString, - string::{String, ToString}, - vec::Vec, -}; +use alloc::{borrow::Cow, string::ToString}; use arrayvec::ArrayString; use core::{ ffi::c_int, str::{self, FromStr}, }; +use redox_path::{RedoxReference, RedoxStr}; use redox_protocols::protocol::O_CLOEXEC; use redox_rt::{proc::FdGuardUpper, signal::tmp_disable_signals}; use syscall::{data::Stat, error::*, flag::*}; use super::{FdGuard, Pal, Sys, libcscheme}; use crate::{ + c_str::CStr, error::Errno, fs::File, header::{fcntl, limits, sys_file}, @@ -22,111 +19,38 @@ use crate::{ sync::rwlock::{ReadGuard, RwLock}, }; -pub use redox_path::{RedoxPath, canonicalize_using_cwd}; - -pub fn normalize_path<'a>(path: &'a str) -> Option<(bool, String)> { - let absolute = match RedoxPath::from_absolute(path) { - Some(absolute) => absolute, - None => return Some((true, partially_canonical(path)?)), - }; - let canonical = absolute.canonical()?; - Some((false, canonical.to_string())) -} - -pub fn normalize_scheme_rooted_path<'a>(path: &'a str) -> Option<(bool, String)> { - let absolute = match RedoxPath::from_absolute(path) { - Some(absolute) => absolute, - None => return Some((true, partially_canonical(path)?)), - }; - let canonical = absolute.canonical()?; - Some((false, scheme_rooted_path(&canonical.to_string()).ok()?)) -} - -fn partially_canonical(path: &str) -> Option { - let mut stack = Vec::new(); - let mut paths_to_check = BTreeSet::new(); - let mut up_counts = 0; - - for part in path.split('/') { - if part.is_empty() || part == "." { - continue; - } else if part == ".." { - if let Some(part) = stack.pop() { - paths_to_check.insert( - core::iter::repeat("..") - .take(up_counts) - .chain(stack.clone()) - .chain(core::iter::once(part)) - .collect::>() - .join("/"), - ); - } else { - up_counts += 1; - } - } else { - stack.push(part); - } - } - - for path in paths_to_check { - let _ = current_dir() - .ok()? - .as_ref() - .unwrap() - .fd - .openat(&path, O_STAT, 0) - .ok()?; - } - - Some( - core::iter::repeat("..") - .take(up_counts) - .chain(stack) - .collect::>() - .join("/"), - ) -} +pub use redox_path::RedoxPath; // POSIX states chdir is both thread-safe and signal-safe. Thus we need to synchronize access to CWD, but at the // same time forbid signal handlers from running in the meantime, to avoid reentrant deadlock. -pub fn chdir(path: &str) -> Result<()> { +pub fn chdir(path: RedoxStr<'_>) -> Result<()> { let _siglock = tmp_disable_signals(); let mut cwd_guard = CWD.write(); - let (is_relative, path) = normalize_path(path).ok_or(Error::new(ENOENT))?; - if is_relative { - let fd = cwd_guard - .as_ref() - .unwrap() - .fd - .openat_into_upper(&path, O_STAT, 0)?; - let mut stat = Stat::default(); - if fd.fstat(&mut stat).is_err() || (stat.st_mode & MODE_TYPE) != MODE_DIR { - return Err(Error::new(ENOTDIR)); + let (redox, fd) = match path { + RedoxStr::Absolute(path) => { + let path = path.to_standard_canon(); + let fd = FdGuard::open_into_upper(&path.as_reference(), O_STAT); + (path.into_owned(), fd) } - - let canon = canonicalize_using_cwd(cwd_guard.as_ref().map(|c| c.path.as_ref()), &path) - .ok_or(Error::new(ENOENT))?; - *cwd_guard = Some(Cwd { - path: to_cwd_path(&canon)?, - fd, - }); - } else { - let canon_with_scheme = scheme_rooted_path(&path)?; - - let fd = FdGuard::open(&canon_with_scheme, O_STAT)? - .to_upper() - .unwrap(); - let mut stat = Stat::default(); - if fd.fstat(&mut stat).is_err() || (stat.st_mode & MODE_TYPE) != MODE_DIR { - return Err(Error::new(ENOTDIR)); + RedoxStr::Relative(redox_reference) => { + let Some(cwd) = cwd_guard.as_ref() else { + return Err(Error::new(ENOENT)); + }; + let fd = cwd + .fd + .openat_into_upper(redox_reference.as_ref(), O_STAT, 0); + let path = cwd.redox.canonicalize_as_cwd(redox_reference.into()); + (path.into_owned(), fd) } - - *cwd_guard = Some(Cwd { - path: to_cwd_path(&path)?, - fd, - }); + }; + log::trace!("chdir({:?}): {:?}", redox.as_reference().as_ref(), fd); + let fd = fd?; + let mut stat = Stat::default(); + if fd.fstat(&mut stat).is_err() || (stat.st_mode & MODE_TYPE) != MODE_DIR { + return Err(Error::new(ENOTDIR)); } - + let path = to_cwd_path(redox.as_reference().as_ref())?; + *cwd_guard = Some(Cwd { path, redox, fd }); Ok(()) } @@ -144,7 +68,7 @@ pub fn fchdir(fd: c_int) -> Result<()> { )?) .to_upper() .unwrap(); - set_cwd_manual(buf, fd); + set_cwd_manual(buf, fd)?; Ok(()) } @@ -166,7 +90,7 @@ pub fn getcwd(mut buf: Out<[u8]>) -> Result { } // Get Cwd object -pub fn current_dir() -> Result>> { +pub fn current_dir() -> Result>>> { let _siglock = tmp_disable_signals(); let guard = CWD.read(); @@ -177,56 +101,27 @@ pub fn current_dir() -> Result>> { Ok(guard) } -fn scheme_rooted_path(path: &str) -> Result { - let standard_scheme = path == "/scheme" || path.starts_with("/scheme/"); - let legacy_scheme = path - .split("/") - .next() - .map(|c| c.contains(":")) - .unwrap_or(false); - - Ok(if standard_scheme || legacy_scheme { - path.to_string() - } else { - let mut result = format!("/scheme/file{}", path); - - // Trim trailing / to keep path canonical. - if result.as_bytes().last() == Some(&b'/') { - result.pop(); - } - - result - }) -} - -// TODO: How much of this logic should be in redox-path? -fn canonicalize_with_cwd_internal(cwd: Option<&str>, path: &str) -> Result { - let path = canonicalize_using_cwd(cwd, path).ok_or(Error::new(ENOENT))?; - scheme_rooted_path(&path) -} - -pub fn canonicalize(path: &str) -> Result { - let _siglock = tmp_disable_signals(); - let cwd_guard = CWD.read(); - canonicalize_with_cwd_internal(cwd_guard.as_ref().map(|c| c.path.as_ref()), path) -} - pub type CwdPath = ArrayString<{ limits::PATH_MAX }>; -pub struct Cwd { +pub struct Cwd<'a> { pub path: CwdPath, + pub redox: RedoxPath<'a>, pub fd: FdGuardUpper, } -static CWD: RwLock> = RwLock::new(None); +static CWD: RwLock>> = RwLock::new(None); pub fn to_cwd_path(path: &str) -> Result { ArrayString::from_str(&path).or(Err(Error::new(ENAMETOOLONG))) } -pub fn set_cwd_manual(path: CwdPath, fd: FdGuardUpper) { +pub fn set_cwd_manual(path: CwdPath, fd: FdGuardUpper) -> Result<()> { + let Some(redox) = RedoxPath::from_absolute(path.to_string()) else { + return Err(Error::new(EBADF)); + }; let _siglock = tmp_disable_signals(); - *CWD.write() = Some(Cwd { path, fd }); + *CWD.write() = Some(Cwd { path, redox, fd }); + Ok(()) } pub fn clone_cwd() -> Option { @@ -242,51 +137,56 @@ fn open_absolute(path: &str, flags: usize) -> Result { } } -fn link_target(fd: FdGuard) -> Result { - let mut resolve_buf = [0_u8; limits::PATH_MAX]; - let count = fd.read(&mut resolve_buf)?; - if count == resolve_buf.len() { - // TODO: make resolve_buf PATH_MAX + 1 bytes? - return Err(Error::new(ENAMETOOLONG)); - } - // If the symbolic link path is non-UTF8, it cannot be opened, and is thus - // considered a "dangling symbolic link". - core::str::from_utf8(&resolve_buf[..count]) - .map_err(|_| Error::new(ENOENT)) - .map(|s| s.to_string()) -} - -fn read_link_content(path: &str, is_relative: bool) -> Result { +// Read symlink content +fn read_link_content<'a, 'b>( + dirfd: Option<&FdGuard>, + path: &'a str, + is_relative: bool, +) -> Result> { let resolve_flags = O_CLOEXEC | O_SYMLINK | O_RDONLY; - - let fd = if is_relative { - current_dir()? + let fd = match (is_relative, dirfd) { + (false, _) => FdGuard::open(path, resolve_flags), + (true, None) => current_dir()? .as_ref() .unwrap() .fd - .openat(path, resolve_flags, 0)? - } else { - FdGuard::open(path, resolve_flags)? + .openat(path, resolve_flags, 0), + (true, Some(dirfd)) => dirfd.openat(path, resolve_flags, 0), }; + log::trace!( + "read_link_content ({:?} {:?} {}): {:?}", + dirfd, + &path, + is_relative, + fd + ); - link_target(fd) + let mut resolve_buf = [0_u8; limits::PATH_MAX + 1]; + let count = fd?.read(&mut resolve_buf)?; + if count == resolve_buf.len() { + return Err(Error::new(ENAMETOOLONG)); + } + + // If the symbolic link path is non-UTF8, it cannot be opened, and is thus + // considered a "dangling symbolic link". + let path = core::str::from_utf8(&resolve_buf[..count]).map_err(|_| Error::new(ENOENT))?; + RedoxStr::new(path.to_string()).ok_or(Error::new(EBADF)) } -fn calc_next_abs_path(current_abs: &str, link_target: &str) -> Result { - let parent = get_parent_path(current_abs).ok_or(Error::new(ENOENT))?; - - canonicalize_using_cwd(Some(&parent), link_target).ok_or(Error::new(ENOENT)) -} - -fn resolve_sym_links(mut current_path_string: String, flags: usize) -> Result { - // Sym reolve loop +/// Resolve symlink and open as a fd. Requires `current_path_string` as canonicalized. +fn resolve_sym_links<'a>(mut current_path_string: RedoxPath<'a>, flags: usize) -> Result { + // Sym resolve loop for _ in 0..limits::SYMLOOP_MAX { - match open_absolute(¤t_path_string, flags) { + let dirname = current_path_string.dirname(); + let cow: Cow<'_, str> = current_path_string.into(); + let initial_res = open_absolute(&cow, flags); + log::trace!("resolve_sym_links({:?}): {:?}", &cow, initial_res); + match initial_res { Ok(fd) => return Ok(fd), Err(e) if e == Error::new(EXDEV) => { - let link_target = read_link_content(¤t_path_string, false)?; - - current_path_string = calc_next_abs_path(¤t_path_string, &link_target)?; + // dirfd is None because it's canonicalized + let link_target = read_link_content(None, &cow, false)?; + current_path_string = dirname.canonicalize_as_cwd(link_target); } Err(e) => return Err(e), } @@ -296,66 +196,64 @@ fn resolve_sym_links(mut current_path_string: String, flags: usize) -> Result Result { +pub fn openat(dirfd: c_int, path: RedoxStr<'_>, flags: usize) -> Result { if path.is_empty() && flags as i32 & fcntl::AT_EMPTY_PATH != fcntl::AT_EMPTY_PATH { return Err(Error::new(ENOENT)); } - let (is_relative, path_to_open) = normalize_path(path).ok_or(Error::new(ENOENT))?; - - if !is_relative { + if !path.is_relative() { return open(path, flags); } let _siglock = tmp_disable_signals(); let fcntl_flags = flags & syscall::O_FCNTL_MASK; + let path: Cow<'_, str> = path.into(); + // First try let initial_res = if dirfd == fcntl::AT_FDCWD { current_dir()? .as_ref() .unwrap() .fd - .openat(&path_to_open, flags, fcntl_flags) + .openat(&path, flags, fcntl_flags) .map(|fd: FdGuard| fd.take()) } else { - redox_rt::sys::openat(dirfd as usize, &path_to_open, flags, fcntl_flags) + redox_rt::sys::openat(dirfd as usize, &path, flags, fcntl_flags) }; - let current_path_string = match initial_res { - Ok(fd) => return Ok(fd), + log::trace!( + "openat({dirfd:?}, {:?}, {flags:x}): {:?}", + &path, + initial_res + ); + + match initial_res { + Ok(fd) => Ok(fd), Err(e) if e == Error::new(EXDEV) => { - let resolve_flags = O_CLOEXEC | O_SYMLINK | O_RDONLY; - - let fd = if dirfd == fcntl::AT_FDCWD { - current_dir()? - .as_ref() - .unwrap() - .fd - .openat(path, resolve_flags, 0)? + let (link_path, dirfd) = if dirfd == fcntl::AT_FDCWD { + let link_path = read_link_content(None, &path, true); + (link_path, dirfd) } else { - redox_rt::sys::openat(dirfd as usize, &path_to_open, flags, fcntl_flags) - .map(FdGuard::new)? + let fd = FdGuard::new(dirfd as usize); + let link_path = read_link_content(Some(&fd), &path, true); + (link_path, fd.take() as i32) }; - - let link_target = link_target(fd)?; - - let current_abs = openat2_path(dirfd, path, 0)?; - calc_next_abs_path(¤t_abs, &link_target)? + let link_path = openat2_path(dirfd, link_path?, 0)?; + resolve_sym_links(link_path, flags) } - Err(e) => return Err(e), - }; - - resolve_sym_links(current_path_string, flags) + Err(e) => Err(e), + } } // TODO: Move to redox-rt, or maybe part of it? -pub fn open(path: &str, flags: usize) -> Result { - let _siglock = tmp_disable_signals(); - if path == "" { +pub fn open(path: RedoxStr<'_>, flags: usize) -> Result { + if path.is_empty() { return Err(Error::new(ENOENT)); } - let (is_relative, canon) = normalize_scheme_rooted_path(path).ok_or(Error::new(ENOENT))?; + let is_relative = path.is_relative(); + let _siglock = tmp_disable_signals(); + let path: Cow<'_, str> = path.into(); // First try let initial_res = if is_relative { @@ -364,78 +262,41 @@ pub fn open(path: &str, flags: usize) -> Result { .as_ref() .unwrap() .fd - .openat(&canon, flags, fcntl_flags) + .openat(&path, flags, fcntl_flags) .map(|fd: FdGuard| fd.take()) } else { - open_absolute(&canon, flags) + open_absolute(&path, flags) }; - let current_path_string = match initial_res { - Ok(fd) => return Ok(fd), + log::trace!("open({:?}, {flags:x}): {:?}", &path, initial_res); + + match initial_res { + Ok(fd) => Ok(fd), Err(e) if e == Error::new(EXDEV) => { - let link_target = read_link_content(&canon, is_relative)?; - - let cwd_guard = CWD.read(); - let current_abs = - canonicalize_using_cwd(cwd_guard.as_ref().map(|c| c.path.as_ref()), &canon) - .ok_or(Error::new(ENOENT))?; - - calc_next_abs_path(¤t_abs, &link_target)? + let link_path = read_link_content(None, &path, is_relative); + let link_path = openat2_path(fcntl::AT_FDCWD, link_path?, 0)?; + resolve_sym_links(link_path, flags) } - Err(e) => return Err(e), - }; - - // Sym reolve loop - resolve_sym_links(current_path_string, flags) -} - -fn get_parent_path(path: &str) -> Option { - let path = path.strip_suffix('/').unwrap_or(path); - fn parent_opt(path: &str) -> Option<&str> { - path.rfind('/').map(|index| { - if index == 0 { - // Path is something like "/file.txt" or the root "/". - // The parent is the root directory "/". - "/" - } else { - // Path is something like "/a/b/c.txt". - // Take the slice from the beginning up to the last '/'. - &path[..index] - } - }) - } - match RedoxPath::from_absolute(path) { - Some(path) => { - let (scheme, reference) = path.as_parts()?; - let parent_ref = parent_opt(reference.as_ref()).unwrap_or(""); - Some(format!("/scheme/{}/{}", scheme.as_ref(), parent_ref)) - } - None => parent_opt(path).map(String::from), + Err(e) => Err(e), } } -pub fn dir_path_and_fd_path(socket_path: &str) -> Result<(String, String)> { +/// Return the directory (or scheme path) and the relative socket path to the scheme +pub fn dir_path_and_fd_path( + socket_path: RedoxStr<'_>, +) -> Result<(RedoxPath<'_>, RedoxReference<'_>)> { let _siglock = tmp_disable_signals(); let cwd_guard = CWD.read(); - let cwd_path = cwd_guard.as_ref().map(|c| c.path.as_ref()); + let cwd_path = cwd_guard.as_ref().map(|c| &c.redox); + let redox_path = openat2_path(fcntl::AT_FDCWD, socket_path, 0)?; - let full_path = canonicalize_with_cwd_internal(cwd_path, socket_path)?; - - let redox_path = RedoxPath::from_absolute(&full_path).ok_or(Error::new(EINVAL))?; - let (_, ref_path) = redox_path.as_parts().ok_or(Error::new(EINVAL))?; + let (scheme, ref_path) = redox_path.as_parts().ok_or(Error::new(EINVAL))?; if ref_path.as_ref().is_empty() { return Err(Error::new(EINVAL)); } - if redox_path.is_default_scheme() { - let dir_to_open = get_parent_path(&full_path).ok_or(Error::new(EINVAL))?; - Ok((dir_to_open, ref_path.as_ref().to_string())) - } else { - let full_path = canonicalize_with_cwd_internal(cwd_path, ref_path.as_ref())?; - let redox_path = RedoxPath::from_absolute(&full_path).ok_or(Error::new(EINVAL))?; - let (_, path) = redox_path.as_parts().ok_or(Error::new(EINVAL))?; - let dir_to_open = get_parent_path(&full_path).ok_or(Error::new(EINVAL))?; - Ok((dir_to_open, path.as_ref().to_string())) - } + let ref_path = RedoxReference::new(ref_path.to_string()).unwrap(); + let dir_to_open = ref_path.clone().dirname(); + Ok((scheme.canonicalize_as_scheme(dir_to_open.into()), ref_path)) } pub struct FileLock(c_int); @@ -465,7 +326,11 @@ impl Drop for FileLock { /// Resolve `path` under `dirfd`. /// /// See [`openat2`] for more information. -pub(super) fn openat2_path(dirfd: c_int, path: &str, at_flags: c_int) -> Result { +pub(super) fn openat2_path( + dirfd: c_int, + path: RedoxStr<'_>, + at_flags: c_int, +) -> Result, Errno> { // Ideally, the function calling this fn would check AT_EMPTY_PATH and just call fstat or // whatever with the fd. if path.is_empty() && at_flags & fcntl::AT_EMPTY_PATH != fcntl::AT_EMPTY_PATH { @@ -477,16 +342,23 @@ pub(super) fn openat2_path(dirfd: c_int, path: &str, at_flags: c_int) -> Result< // isn't needed. if dirfd == fcntl::AT_FDCWD { // The special constant AT_FDCWD indicates that we should use the cwd. - let cwd_guard = CWD.read(); - canonicalize_using_cwd(cwd_guard.as_ref().map(|c| c.path.as_ref()), path) - .ok_or(Errno(EBADF)) + let redox_path = match path { + RedoxStr::Absolute(redox_path) => redox_path, + RedoxStr::Relative(redox_reference) => { + let cwd_guard = CWD.read(); + let cwd_path = cwd_guard.as_ref().map(|c| &c.redox); + let cwd_path = cwd_path.ok_or(Error::new(EINVAL))?; + cwd_path.canonicalize_as_cwd(redox_reference.into()) + } + }; + Ok(redox_path) } else { let mut buf = [0; limits::PATH_MAX]; let len = Sys::fpath(dirfd, &mut buf)?; // SAFETY: fpath checks then copies valid UTF8. let dir = unsafe { str::from_utf8_unchecked(&buf[..len]) }; - - canonicalize_using_cwd(Some(dir), path).ok_or(Errno(EBADF)) + let dir = RedoxPath::from_absolute(dir).ok_or(Errno(EBADF))?; + Ok(dir.canonicalize_as_cwd(path)) } } @@ -520,12 +392,11 @@ fn at_flags_to_open_flags(at_flags: c_int) -> c_int { /// directory. pub(super) fn openat2( dirfd: c_int, - path: &str, + path: CStr, at_flags: c_int, oflags: c_int, ) -> Result { // Translate at flags into open flags; openat will do this on its own most likely. let oflags = at_flags_to_open_flags(at_flags) | fcntl::O_CLOEXEC | oflags; - let c_path = CString::new(path).map_err(|_| Errno(EINVAL))?; - File::openat(dirfd, c_path.as_c_str().into(), oflags) + File::openat(dirfd, path, oflags) } diff --git a/src/platform/redox/socket.rs b/src/platform/redox/socket.rs index 744f1a0248..0a3c25e2d6 100644 --- a/src/platform/redox/socket.rs +++ b/src/platform/redox/socket.rs @@ -1,5 +1,6 @@ -use alloc::vec::Vec; +use alloc::{borrow::Cow, vec::Vec}; use core::{cmp, mem, ptr, slice, str}; +use redox_path::RedoxStr; use redox_protocols::protocol::{FsCall, O_CLOEXEC, SocketCall}; use redox_rt::proc::FdGuard; use syscall::{self, flag::*}; @@ -573,18 +574,20 @@ impl PalSocket for Sys { let addr = unsafe { slice::from_raw_parts(&data.sun_path as *const _ as *const u8, len) }; - let path = format!("{}", str::from_utf8(addr).unwrap()); + let path = str::from_utf8(addr).map_err(|_| Errno(EINVAL))?; log::trace!("bind(): path: {:?}", path); - let (dir_path, fd_path) = dir_path_and_fd_path(&path)?; + let path = RedoxStr::new(path).ok_or(Errno(EINVAL))?; + let (dir_path, fd_path) = dir_path_and_fd_path(path)?; redox_rt::sys::sys_call_wo( socket as usize, - fd_path.as_bytes(), + fd_path.as_ref().as_bytes(), CallFlags::empty(), &[SocketCall::Bind as u64], )?; + let dir_path: Cow<'_, str> = dir_path.into(); let fs_bind_result = (|| -> Result<()> { let dirfd = FdGuard::open( &dir_path, @@ -657,12 +660,13 @@ impl PalSocket for Sys { let addr = unsafe { slice::from_raw_parts(&data.sun_path as *const _ as *const u8, len) }; - let path = format!("{}", str::from_utf8(addr).unwrap()); - log::trace!("connect(): path: {:?}", path); + let path = str::from_utf8(addr).map_err(|_| Errno(EINVAL))?; + log::trace!("bind(): path: {:?}", path); - let (_, fd_path) = dir_path_and_fd_path(&path)?; + let path = RedoxStr::new(path).ok_or(Errno(EINVAL))?; + let (_, fd_path) = dir_path_and_fd_path(path)?; - let target_path = format!("/{fd_path}"); + let target_path = format!("/{}", fd_path.as_ref()); let socket_file_fd = FdGuard::open(&target_path, syscall::O_RDWR)?; const TOKEN_BUF_SIZE: usize = 16;