diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 0133154..1e50bb3 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -1,10 +1,7 @@ name: "CI" on: - push: - branches: - - main - pull_request: {} + push env: CI: true diff --git a/Cargo.lock b/Cargo.lock index c83d19b..6dcd824 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -10,7 +10,7 @@ checksum = "88eeeb1a5ef57ab0d40fb7a256e1b211496f97d6a8664ef52c0a665ce69a4e0f" [[package]] name = "rusl" -version = "0.1.0" +version = "0.2.0" dependencies = [ "linux-rust-bindings", "sc", @@ -31,19 +31,9 @@ dependencies = [ [[package]] name = "tiny-std" -version = "0.1.1" +version = "0.2.0" dependencies = [ "rusl", "sc", "tiny-start", - "unix-print", -] - -[[package]] -name = "unix-print" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c50e1866b3de196f1329f6a805771eee750651c83bbebd5dff159e5f033cc16f" -dependencies = [ - "sc", ] diff --git a/rusl/Cargo.toml b/rusl/Cargo.toml index 9fad76f..2446881 100644 --- a/rusl/Cargo.toml +++ b/rusl/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "rusl" -version = "0.1.0" +version = "0.2.0" edition = "2021" license = "MPL-2.0" readme = "../Readme.md" @@ -15,6 +15,7 @@ keywords = ["ffi", "bindings", "operating", "system", "linux"] [features] default = ["alloc"] alloc = [] +integration-test = [] [dependencies] linux-rust-bindings = { version = "0.1.1", features = ["all"] } diff --git a/rusl/Changelog.md b/rusl/Changelog.md index fb93be1..f963bef 100644 --- a/rusl/Changelog.md +++ b/rusl/Changelog.md @@ -3,6 +3,7 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/) and this project adheres to [Semantic Versioning](http://semver.org/). + ## [Unreleased] ### Fixed @@ -10,6 +11,14 @@ and this project adheres to [Semantic Versioning](http://semver.org/). ### Changed +## [v0.2.0] - 2023-10-01 + +### Changed +- API-breakage from removing AsRefUnixStr and replacing with +`&UnixStr`. Propagates down to all things containing `&UnixStr`. Main reason +is making allocation more explicit on the user's side, inviting opportunities for +const-evaluation of null-terminated strings. + ## [v0.1.0] - 2023-07-25 ### Added diff --git a/rusl/src/io_uring/test.rs b/rusl/src/io_uring/test.rs index 87f2b8d..a41d401 100644 --- a/rusl/src/io_uring/test.rs +++ b/rusl/src/io_uring/test.rs @@ -45,7 +45,11 @@ fn uring_register_files() { let Some(uring_fd) = setup_io_poll_uring() else { return; }; - let open_fd = open("test-files/can_open.txt\0", OpenFlags::O_RDWR).unwrap(); + let open_fd = open( + UnixStr::try_from_str("test-files/can_open.txt\0").unwrap(), + OpenFlags::O_RDWR, + ) + .unwrap(); io_uring_register_files(uring_fd, &[open_fd]).unwrap(); } @@ -103,7 +107,11 @@ fn uring_single_read() { let mut bytes = [0u8; 1024]; let buf = IoSliceMut::new(&mut bytes); let mut slices = [buf]; - let fd = open("test-files/can_open.txt\0", OpenFlags::O_RDONLY).unwrap(); + let fd = open( + UnixStr::try_from_str("test-files/can_open.txt\0").unwrap(), + OpenFlags::O_RDONLY, + ) + .unwrap(); let user_data = 15; let entry = unsafe { IoUringSubmissionQueueEntry::new_readv( @@ -193,7 +201,11 @@ fn uring_single_close() { let Some(mut uring) = setup_ignore_enosys(8, IoUringParamFlags::empty()) else { return; }; - let fd = open("test-files/can_open.txt\0", OpenFlags::O_RDONLY).unwrap(); + let fd = open( + UnixStr::try_from_str("test-files/can_open.txt\0").unwrap(), + OpenFlags::O_RDONLY, + ) + .unwrap(); let user_data = 35; let entry = IoUringSubmissionQueueEntry::new_close(fd, user_data, IoUringSQEFlags::empty()); write_await_single_entry(&mut uring, entry, user_data); @@ -317,7 +329,7 @@ fn uring_single_mkdir_at() { IoUringSubmissionQueueEntry::new_mkdirat( None, new_dir_path, - Mode::empty(), + Mode(0o755), user_data, IoUringSQEFlags::empty(), ) @@ -495,17 +507,17 @@ fn uring_read_registered_buffers_and_fds() { .unwrap(); } let fd1 = open( - "test-files/io_uring/uring_register_read1\0", + UnixStr::try_from_str("test-files/io_uring/uring_register_read1\0").unwrap(), OpenFlags::O_RDONLY, ) .unwrap(); let fd2 = open( - "test-files/io_uring/uring_register_read2\0", + UnixStr::try_from_str("test-files/io_uring/uring_register_read2\0").unwrap(), OpenFlags::O_RDONLY, ) .unwrap(); let fd3 = open( - "test-files/io_uring/uring_register_read3\0", + UnixStr::try_from_str("test-files/io_uring/uring_register_read3\0").unwrap(), OpenFlags::O_RDONLY, ) .unwrap(); @@ -594,9 +606,9 @@ fn uring_write_registered_buffers_and_fds() { ) .unwrap(); } - let path1 = "test-files/io_uring/tmp_uring_register_write1\0"; - let path2 = "test-files/io_uring/tmp_uring_register_write2\0"; - let path3 = "test-files/io_uring/tmp_uring_register_write3\0"; + let path1 = UnixStr::try_from_str("test-files/io_uring/tmp_uring_register_write1\0").unwrap(); + let path2 = UnixStr::try_from_str("test-files/io_uring/tmp_uring_register_write2\0").unwrap(); + let path3 = UnixStr::try_from_str("test-files/io_uring/tmp_uring_register_write3\0").unwrap(); let fd1 = open_mode( path1, OpenFlags::O_RDWR | OpenFlags::O_CREAT | OpenFlags::O_TRUNC, diff --git a/rusl/src/platform/compat/socket.rs b/rusl/src/platform/compat/socket.rs index 80e8ae4..5856a8e 100644 --- a/rusl/src/platform/compat/socket.rs +++ b/rusl/src/platform/compat/socket.rs @@ -1,6 +1,6 @@ //! These are not defined in the uapi which is a bit hairy, if they change, that's obviously +use crate::string::unix_str::UnixStr; /// a problem. -use crate::string::unix_str::AsUnixStr; use crate::Error; #[derive(Debug, Copy, Clone)] @@ -122,14 +122,14 @@ impl SocketAddress { self.0.sun_path } - /// Tries to construct a `SocketAddress` from an `AsUnixStr` path + /// Tries to construct a `SocketAddress` from a `UnixStr` path /// # Errors /// The path is longer than 108 bytes (null termination included) - pub fn try_from_unix(path: P) -> crate::Result { + pub fn try_from_unix(path: &UnixStr) -> crate::Result { let mut ind = 0; - let buf = path.exec_with_self_as_ptr(|ptr| unsafe { + let buf = unsafe { let mut buf = [0; 108]; - let mut ptr = ptr; + let mut ptr = path.as_ptr(); while !ptr.is_null() { buf[ind] = ptr.read() as core::ffi::c_char; if ind == 107 && buf[ind] != 0 { @@ -141,8 +141,8 @@ impl SocketAddress { ptr = ptr.add(1); ind += 1; } - Ok(buf) - })?; + buf + }; let addr = Self(linux_rust_bindings::socket::sockaddr_un { sun_family: AddressFamily::AF_UNIX.0, sun_path: buf, diff --git a/rusl/src/process/execve.rs b/rusl/src/process/execve.rs index 668a3b9..dc6040e 100644 --- a/rusl/src/process/execve.rs +++ b/rusl/src/process/execve.rs @@ -1,6 +1,6 @@ use sc::syscall; -use crate::string::unix_str::AsUnixStr; +use crate::string::unix_str::UnixStr; use crate::Error; /// Executes provided binary `bin` with arguments `arg_v` and environment `env_p`. @@ -14,16 +14,14 @@ use crate::Error; /// # Safety /// See above #[inline] -pub unsafe fn execve( - bin: B, +pub unsafe fn execve( + bin: &UnixStr, arg_v: *const *const u8, env_p: *const *const u8, ) -> Result<(), Error> { - bin.exec_with_self_as_ptr(|ptr| { - let res = syscall!(EXECVE, ptr, arg_v, env_p); - // EXECVE doesn't return on success, on err it returns an error code - // [docs](https://man7.org/linux/man-pages/man2/execve.2.html#RETURN_VALUE) - #[allow(clippy::cast_possible_truncation, clippy::cast_possible_wrap)] - Err(Error::with_code("`EXECVE` syscall failed", res as i32)) - }) + let res = syscall!(EXECVE, bin.as_ptr(), arg_v, env_p); + // EXECVE doesn't return on success, on err it returns an error code + // [docs](https://man7.org/linux/man-pages/man2/execve.2.html#RETURN_VALUE) + #[allow(clippy::cast_possible_truncation, clippy::cast_possible_wrap)] + Err(Error::with_code("`EXECVE` syscall failed", res as i32)) } diff --git a/rusl/src/select/poll.rs b/rusl/src/select/poll.rs index cc41a7a..6b8c5ef 100644 --- a/rusl/src/select/poll.rs +++ b/rusl/src/select/poll.rs @@ -37,6 +37,7 @@ mod tests { fn poll_stdout_ready() { // Stdout should essentially always be ready for output let mut poll_fds = [PollFd::new(STDOUT, PollEvents::POLLOUT)]; + println!("Dummy out"); let num_rdy = ppoll(&mut poll_fds, Some(&TimeSpec::new(1, 0)), None).unwrap(); // Get one changed revents assert_eq!(1, num_rdy); diff --git a/rusl/src/string.rs b/rusl/src/string.rs index 8fbdb2d..af4522d 100644 --- a/rusl/src/string.rs +++ b/rusl/src/string.rs @@ -1,25 +1,4 @@ -use crate::platform::NULL_BYTE; - pub mod strlen; #[cfg(test)] mod test; pub mod unix_str; - -/// Basic compare two null terminated strings -/// # Safety -/// Instant UB if these pointers are not null terminated -#[inline] -#[must_use] -pub unsafe fn null_term_ptr_cmp_up_to(a: *const u8, b: *const u8) -> usize { - let mut it = 0; - loop { - let a_val = a.add(it).read(); - let b_val = b.add(it).read(); - if a_val != b_val || a_val == NULL_BYTE { - // Not equal, or terminated - return it; - } - // Equal continue - it += 1; - } -} diff --git a/rusl/src/string/test.rs b/rusl/src/string/test.rs index 7cf61cf..86ed8b4 100644 --- a/rusl/src/string/test.rs +++ b/rusl/src/string/test.rs @@ -1,88 +1,4 @@ -use crate::platform::NULL_BYTE; use crate::string::strlen::{buf_strlen, strlen}; -use crate::string::unix_str::{AsUnixStr, UnixStr}; - -#[test] -#[cfg(not(feature = "alloc"))] -fn errs_on_not_null_without_alloc() { - let use_with_non_null_without_allocator = "Hello".exec_with_self_as_ptr(|_| Ok(())); - assert!(use_with_non_null_without_allocator.is_err()); -} - -#[test] -#[cfg(feature = "alloc")] -fn accepts_not_null_on_alloc() { - let use_with_non_null_without_allocator = "Hello".exec_with_self_as_ptr(|_| Ok(())); - assert!(use_with_non_null_without_allocator.is_ok()); -} - -#[test] -fn errs_on_many_null() { - let raw = "Hello\0oh no\0"; - let many_non_null_res = raw.exec_with_self_as_ptr(|_| Ok(())); - assert!(many_non_null_res.is_err()); -} - -#[test] -fn accepts_empty() { - let empty = "".exec_with_self_as_ptr(|ptr| { - let null_byte = unsafe { ptr.read() }; - assert_eq!(NULL_BYTE, null_byte); - Ok(()) - }); - assert!(empty.is_ok()); -} - -#[test] -#[cfg(feature = "alloc")] -fn accepts_non_null_terminated_with_allocator() { - use alloc::borrow::ToOwned; - let owned = "Hello".to_owned(); - let non_null_term_with_alloc = owned.exec_with_self_as_ptr(|_| Ok(())); - assert!(non_null_term_with_alloc.is_ok()); -} - -#[test] -#[cfg(feature = "alloc")] -fn can_convert_into_unix_string() { - use crate::string::unix_str::UnixString; - let template = UnixString::try_from("Hello!\0").unwrap(); - let owned_with_null = b"Hello!\0".to_vec(); - assert_eq!( - template, - UnixString::try_from(owned_with_null.clone()).unwrap() - ); - let owned_non_null = b"Hello!".to_vec(); - assert_eq!( - template, - UnixString::try_from(owned_non_null.clone()).unwrap() - ); - let owned_empty = b"".to_vec(); - let template_empty = UnixString::try_from("\0").unwrap(); - assert_eq!( - template_empty, - UnixString::try_from(owned_empty.clone()).unwrap() - ); - let bad_input = "Hi!\0Hello!\0"; - assert!(UnixString::try_from(bad_input).is_err()); -} - -#[test] -fn can_match_up_to() { - let haystack = "haystack\0"; - unsafe { - let needle = "\0"; - assert_eq!(0, haystack.match_up_to(needle.as_ptr())); - let needle = "h\0"; - assert_eq!(1, haystack.match_up_to(needle.as_ptr())); - let needle = "haystac\0"; - assert_eq!(7, haystack.match_up_to(needle.as_ptr())); - let needle = "haystack\0"; - assert_eq!(8, haystack.match_up_to(needle.as_ptr())); - let needle = "haystack2\0"; - assert_eq!(8, haystack.match_up_to(needle.as_ptr())); - } -} #[test] fn can_check_strlen() { @@ -107,10 +23,3 @@ fn can_check_buf_strlen() { let bad = b"10_000_000"; assert!(buf_strlen(bad).is_err()); } - -#[test] -fn can_const_instantiate_unix_str() { - // Const eval would fail here. - const MY_CONST_UNIX_STR: &UnixStr = UnixStr::from_str_checked("Hello!\0"); - assert_eq!(MY_CONST_UNIX_STR, UnixStr::from_str_checked("Hello!\0")); -} diff --git a/rusl/src/string/unix_str.rs b/rusl/src/string/unix_str.rs index 5dc4797..b7eead6 100644 --- a/rusl/src/string/unix_str.rs +++ b/rusl/src/string/unix_str.rs @@ -2,142 +2,16 @@ use crate::Error; #[cfg(feature = "alloc")] use alloc::string::String; #[cfg(feature = "alloc")] -use alloc::vec; -#[cfg(feature = "alloc")] use alloc::vec::Vec; -use core::ops::Deref; +use core::hash::Hasher; use core::str::Utf8Error; use crate::platform::NULL_BYTE; -use crate::string::null_term_ptr_cmp_up_to; use crate::string::strlen::strlen; -#[cfg(feature = "alloc")] -pub trait AsUnixStr: ToUnixString { - /// Executes a function with this null terminated entity - /// converts it to a string and pushes a null byte if not already null terminated - /// # Errors - /// Propagates the function's errors - fn exec_with_self_as_ptr(&self, func: F) -> crate::Result - where - F: FnOnce(*const u8) -> crate::Result; - - /// Checks if this `AsUnixStr` matches a null terminated pointer and returns the non null length - /// # Safety - /// Pointer is null terminated - unsafe fn match_up_to(&self, null_terminated_pointer: *const u8) -> usize; -} - -#[cfg(not(feature = "alloc"))] -pub trait AsUnixStr { - /// Executes a function with this null terminated entity - /// # Errors - /// 1. Propagates the function's errors - /// 2. Errors if the provided entity isn't null terminated as we need an allocator to modify - /// it. - fn exec_with_self_as_ptr(&self, func: F) -> crate::Result - where - F: FnOnce(*const u8) -> crate::Result; - - /// Checks if this `AsUnixStr` matches a null terminated pointer and returns the non null length - /// # Safety - /// Pointer is null terminated - unsafe fn match_up_to(&self, null_terminated_pointer: *const u8) -> usize; -} - -#[cfg(feature = "alloc")] -pub trait ToUnixString { - /// Turn this into a `UnixString` - /// # Errors - /// If this string can't be converted this will throw an error - /// The only real reasons are if you have multiple null bytes or no access to an allocator - fn to_unix_string(&self) -> crate::Result; -} - -#[cfg(feature = "alloc")] -impl ToUnixString for () { - #[inline] - fn to_unix_string(&self) -> crate::Result { - Ok(UnixString(vec![b'\0'])) - } -} - -impl AsUnixStr for () { - #[inline] - fn exec_with_self_as_ptr(&self, func: F) -> crate::Result - where - F: FnOnce(*const u8) -> crate::Result, - { - func([NULL_BYTE].as_ptr()) - } - - #[inline] - unsafe fn match_up_to(&self, _null_terminated_pointer: *const u8) -> usize { - 0 - } -} - -#[cfg(feature = "alloc")] -impl ToUnixString for &T -where - T: ToUnixString, -{ - #[inline] - fn to_unix_string(&self) -> crate::Result { - (*self).to_unix_string() - } -} - -#[cfg(feature = "alloc")] -impl ToUnixString for &mut T -where - T: ToUnixString, -{ - #[inline] - fn to_unix_string(&self) -> crate::Result { - self.deref().to_unix_string() - } -} - -impl AsUnixStr for &A -where - A: AsUnixStr, -{ - #[inline] - fn exec_with_self_as_ptr(&self, func: F) -> crate::Result - where - F: FnOnce(*const u8) -> crate::Result, - { - (*self).exec_with_self_as_ptr(func) - } - - #[inline] - unsafe fn match_up_to(&self, null_terminated_pointer: *const u8) -> usize { - (*self).match_up_to(null_terminated_pointer) - } -} - -impl AsUnixStr for &mut A -where - A: AsUnixStr, -{ - #[inline] - fn exec_with_self_as_ptr(&self, func: F) -> crate::Result - where - F: FnOnce(*const u8) -> crate::Result, - { - self.deref().exec_with_self_as_ptr(func) - } - - #[inline] - unsafe fn match_up_to(&self, null_terminated_pointer: *const u8) -> usize { - self.deref().match_up_to(null_terminated_pointer) - } -} - #[cfg(feature = "alloc")] #[repr(transparent)] -#[derive(Debug, Clone, Eq, PartialEq)] +#[derive(Debug, Clone, Eq, PartialEq, Hash, Ord, PartialOrd)] pub struct UnixString(pub(crate) Vec); #[cfg(feature = "alloc")] @@ -147,175 +21,88 @@ impl UnixString { pub fn as_ptr(&self) -> *const u8 { self.0.as_ptr() } -} - -#[cfg(feature = "alloc")] -impl TryFrom for UnixString { - type Error = crate::Error; - - #[inline] - fn try_from(value: String) -> Result { - value.into_bytes().try_into() - } -} - -#[cfg(feature = "alloc")] -impl TryFrom<&str> for UnixString { - type Error = crate::Error; - - #[inline] - fn try_from(value: &str) -> Result { - value.as_bytes().to_vec().try_into() - } -} - -#[cfg(feature = "alloc")] -impl ToUnixString for &str { - #[inline] - fn to_unix_string(&self) -> crate::Result { - self.as_bytes().to_unix_string() - } -} - -#[cfg(feature = "alloc")] -impl TryFrom> for UnixString { - type Error = crate::Error; - fn try_from(mut value: Vec) -> Result { - match null_terminated_ok(&value) { - NullTermCheckResult::NullTerminated => Ok(Self(value)), - NullTermCheckResult::NullByteOutOfPlace => null_byte_out_of_place(), - NullTermCheckResult::NotNullTerminated => { - value.push(NULL_BYTE); - Ok(Self(value)) - } - } - } -} - -#[cfg(feature = "alloc")] -impl ToUnixString for Vec { - #[inline] - fn to_unix_string(&self) -> crate::Result { - self.as_slice().to_unix_string() - } -} - -#[cfg(feature = "alloc")] -impl AsUnixStr for Vec { - #[inline] - fn exec_with_self_as_ptr(&self, func: F) -> crate::Result - where - F: FnOnce(*const u8) -> crate::Result, - { - self.as_slice().exec_with_self_as_ptr(func) - } + /// Create a `UnixString` from a `String`. + /// # Errors + /// String has nulls in other places than end. #[inline] - unsafe fn match_up_to(&self, null_terminated_pointer: *const u8) -> usize { - self.as_slice().match_up_to(null_terminated_pointer) + pub fn try_from_string(s: String) -> Result { + Self::try_from_vec(s.into_bytes()) } -} -impl AsUnixStr for &[u8] { - fn exec_with_self_as_ptr(&self, func: F) -> crate::Result - where - F: FnOnce(*const u8) -> crate::Result, - { - match null_terminated_ok(self) { - NullTermCheckResult::NullTerminated => func(self.as_ptr()), - NullTermCheckResult::NullByteOutOfPlace => null_byte_out_of_place(), - NullTermCheckResult::NotNullTerminated => { - if self.is_empty() { - return func([NULL_BYTE].as_ptr()); - } - #[cfg(feature = "alloc")] - { - let mut buf = self.to_vec(); - buf.push(NULL_BYTE); - func(buf.as_ptr()) - } - #[cfg(not(feature = "alloc"))] - Err(crate::Error::not_null_terminated()) + /// Create a `UnixString` from a `Vec`. + /// # Errors + /// Vec has nulls in other places than end. + #[inline] + pub fn try_from_vec(mut s: Vec) -> Result { + let len = s.len(); + for (ind, byte) in s.iter().enumerate() { + if *byte == NULL_BYTE { + return if ind == len - 1 { + unsafe { Ok(core::mem::transmute(s)) } + } else { + Err(Error::no_code("Tried to instantiate UnixStr from an invalid &str, a null byte was found but out of place")) + }; } } + s.push(0); + Ok(Self(s)) } - #[inline] - unsafe fn match_up_to(&self, null_terminated_pointer: *const u8) -> usize { - let this_len = self.len(); - for i in 0..this_len { - let other_byte = null_terminated_pointer.add(i).read(); - if self[i] != other_byte || other_byte == NULL_BYTE { - return i; + /// Create a `UnixString` from a `&[u8]`. + /// Will allocate and push a null byte if not null terminated + /// # Errors + /// Bytes aren't properly null terminated, several nulls contained. + pub fn try_from_bytes(s: &[u8]) -> Result { + let len = s.len(); + for (ind, byte) in s.iter().enumerate() { + if *byte == NULL_BYTE { + return if ind == len - 1 { + unsafe { Ok(core::mem::transmute(s.to_vec())) } + } else { + Err(Error::no_code("Tried to instantiate UnixStr from an invalid &str, a null byte was found but out of place")) + }; } } - this_len - 1 - } -} - -#[cfg(feature = "alloc")] -impl ToUnixString for &mut [u8] { - #[inline] - fn to_unix_string(&self) -> crate::Result { - self.deref().to_unix_string() - } -} - -impl AsUnixStr for &mut [u8] { - #[inline] - fn exec_with_self_as_ptr(&self, func: F) -> crate::Result - where - F: FnOnce(*const u8) -> crate::Result, - { - self.deref().exec_with_self_as_ptr(func) + let mut new = s.to_vec(); + new.push(0); + Ok(Self(new)) } + /// Create a `UnixString` from a `&str`. + /// Will allocate and push a null byte if not null terminated + /// # Errors + /// String isn't properly null terminated, several nulls contained. #[inline] - unsafe fn match_up_to(&self, null_terminated_pointer: *const u8) -> usize { - (&self).match_up_to(null_terminated_pointer) + pub fn try_from_str(s: &str) -> Result { + Self::try_from_bytes(s.as_bytes()) } } #[cfg(feature = "alloc")] -impl TryFrom<&[u8]> for UnixString { - type Error = crate::Error; +impl core::ops::Deref for UnixString { + type Target = UnixStr; - #[inline] - fn try_from(value: &[u8]) -> Result { - value.to_vec().try_into() + fn deref(&self) -> &Self::Target { + unsafe { &*(self.0.as_slice() as *const [u8] as *const UnixStr) } } } #[cfg(feature = "alloc")] -impl ToUnixString for &[u8] { +impl AsRef for UnixString { #[inline] - fn to_unix_string(&self) -> crate::Result { - match null_terminated_ok(self) { - NullTermCheckResult::NullTerminated => Ok(UnixString(self.to_vec())), - NullTermCheckResult::NullByteOutOfPlace => null_byte_out_of_place(), - NullTermCheckResult::NotNullTerminated => { - let mut v = self.to_vec(); - v.push(NULL_BYTE); - Ok(UnixString(v)) - } - } - } -} - -#[cfg(feature = "alloc")] -impl core::ops::Deref for UnixString { - type Target = UnixStr; - - fn deref(&self) -> &Self::Target { - unsafe { &*(self.0.as_slice() as *const [u8] as *const UnixStr) } + fn as_ref(&self) -> &UnixStr { + unsafe { UnixStr::from_bytes_unchecked(self.0.as_slice()) } } } #[repr(transparent)] -#[derive(Debug, Eq, PartialEq)] +#[derive(Debug, Eq, PartialEq, Ord, PartialOrd)] pub struct UnixStr(pub(crate) [u8]); impl UnixStr { + pub const EMPTY: &'static Self = UnixStr::from_str_checked("\0"); + /// # Safety /// `&str` needs to be null terminated or downstream UB may occur #[must_use] @@ -323,6 +110,13 @@ impl UnixStr { core::mem::transmute(s) } + /// # Safety + /// `&[u8]` needs to be null terminated or downstream UB may occur + #[must_use] + pub const unsafe fn from_bytes_unchecked(s: &[u8]) -> &Self { + core::mem::transmute(s) + } + /// Const instantiation of a `&UnixStr` from a `&str`. /// Should only be used in a `const`-context, although `rustc` does not let me enforce this. /// # Panics @@ -330,15 +124,36 @@ impl UnixStr { /// not particularly efficient. #[must_use] pub const fn from_str_checked(s: &str) -> &Self { - match const_null_term_check(s.as_bytes()) { - NullTermCheckResult::NullTerminated => unsafe { core::mem::transmute(s) }, - NullTermCheckResult::NullByteOutOfPlace => { - panic!("Tried to instantiate UnixStr from an invalid &str, a null byte was found but out of place"); - } - NullTermCheckResult::NotNullTerminated => { - panic!("Tried to instantiate UnixStr from an invalid &str, not null terminated"); + const_null_term_validate(s.as_bytes()); + unsafe { core::mem::transmute(s) } + } + + /// Create a `&UnixStr` from a `&str`. + /// # Errors + /// String isn't properly null terminated. + #[inline] + pub fn try_from_str(s: &str) -> Result<&Self, Error> { + Self::try_from_bytes(s.as_bytes()) + } + + /// Create a `&UnixStr` from a `&[u8]`. + /// # Errors + /// Slice isn't properly null terminated. + #[inline] + pub fn try_from_bytes(s: &[u8]) -> Result<&Self, Error> { + let len = s.len(); + for (ind, byte) in s.iter().enumerate() { + if *byte == NULL_BYTE { + return if ind == len - 1 { + unsafe { Ok(&*(s as *const [u8] as *const UnixStr)) } + } else { + Err(Error::no_code("Tried to instantiate UnixStr from an invalid &str, a null byte was found but out of place")) + }; } } + Err(Error::no_code( + "Tried to instantiate UnixStr from an invalid &str, not null terminated", + )) } /// # Safety @@ -372,189 +187,164 @@ impl UnixStr { pub fn len(&self) -> usize { self.0.len() } -} - -#[cfg(feature = "alloc")] -impl From<&UnixStr> for UnixString { - #[inline] - fn from(s: &UnixStr) -> Self { - UnixString(s.0.to_vec()) - } -} - -#[cfg(feature = "alloc")] -impl ToUnixString for &UnixStr { - #[inline] - fn to_unix_string(&self) -> crate::Result { - Ok(UnixString(self.0.to_vec())) - } -} - -impl AsUnixStr for &UnixStr { - #[inline] - fn exec_with_self_as_ptr(&self, func: F) -> crate::Result - where - F: FnOnce(*const u8) -> crate::Result, - { - func(self.0.as_ptr()) - } - - #[inline] - unsafe fn match_up_to(&self, null_terminated_pointer: *const u8) -> usize { - null_term_ptr_cmp_up_to(self.0.as_ptr(), null_terminated_pointer) - } -} - -impl AsUnixStr for &mut UnixStr { - #[inline] - fn exec_with_self_as_ptr(&self, func: F) -> crate::Result - where - F: FnOnce(*const u8) -> crate::Result, - { - self.deref().exec_with_self_as_ptr(func) - } - - #[inline] - unsafe fn match_up_to(&self, null_terminated_pointer: *const u8) -> usize { - null_term_ptr_cmp_up_to(self.0.as_ptr(), null_terminated_pointer) - } -} - -#[cfg(feature = "alloc")] -impl ToUnixString for &mut UnixStr { - #[inline] - fn to_unix_string(&self) -> crate::Result { - self.deref().to_unix_string() - } -} - -#[cfg(feature = "alloc")] -impl AsUnixStr for UnixString { - #[inline] - fn exec_with_self_as_ptr(&self, func: F) -> crate::Result - where - F: FnOnce(*const u8) -> crate::Result, - { - func(self.0.as_ptr()) - } - - #[inline] - unsafe fn match_up_to(&self, null_terminated_pointer: *const u8) -> usize { - self.deref().match_up_to(null_terminated_pointer) - } -} - -#[cfg(feature = "alloc")] -impl ToUnixString for UnixString { - #[inline] - fn to_unix_string(&self) -> crate::Result { - Ok(self.clone()) - } -} - -impl AsUnixStr for &str { - #[inline] - fn exec_with_self_as_ptr(&self, func: F) -> crate::Result - where - F: FnOnce(*const u8) -> crate::Result, - { - self.as_bytes().exec_with_self_as_ptr(func) - } + /// Get the length of this `&UnixStr`, including the null byte + #[must_use] #[inline] - unsafe fn match_up_to(&self, null_terminated_pointer: *const u8) -> usize { - self.as_bytes().match_up_to(null_terminated_pointer) + pub fn as_ptr(&self) -> *const u8 { + self.0.as_ptr() } -} -impl AsUnixStr for &mut str { - #[inline] - fn exec_with_self_as_ptr(&self, func: F) -> crate::Result - where - F: FnOnce(*const u8) -> crate::Result, - { - self.deref().exec_with_self_as_ptr(func) + #[must_use] + pub fn match_up_to(&self, other: &UnixStr) -> usize { + let mut it = 0; + let slf_ptr = self.as_ptr(); + let other_ptr = other.as_ptr(); + loop { + unsafe { + let a_val = slf_ptr.add(it).read(); + let b_val = other_ptr.add(it).read(); + if a_val != b_val || a_val == NULL_BYTE { + // Not equal, or terminated + return it; + } + // Equal continue + it += 1; + } + } } - #[inline] - unsafe fn match_up_to(&self, null_terminated_pointer: *const u8) -> usize { - self.as_bytes().match_up_to(null_terminated_pointer) - } -} - -#[cfg(feature = "alloc")] -impl ToUnixString for &mut str { - #[inline] - fn to_unix_string(&self) -> crate::Result { - self.deref().to_unix_string() + #[must_use] + pub fn match_up_to_str(&self, other: &str) -> usize { + let mut it = 0; + let slf_ptr = self.as_ptr(); + let other_ptr = other.as_ptr(); + let other_len = other.len(); + loop { + unsafe { + let a_val = slf_ptr.add(it).read(); + let b_val = other_ptr.add(it).read(); + if a_val != b_val || a_val == NULL_BYTE { + // Not equal, or terminated + return it; + } + // Equal continue + it += 1; + } + if it == other_len { + return it; + } + } } } -#[cfg(feature = "alloc")] -impl AsUnixStr for String { +impl<'a> core::hash::Hash for &'a UnixStr { #[inline] - fn exec_with_self_as_ptr(&self, func: F) -> crate::Result - where - F: FnOnce(*const u8) -> crate::Result, - { - self.as_bytes().exec_with_self_as_ptr(func) - } - - #[inline] - unsafe fn match_up_to(&self, null_terminated_pointer: *const u8) -> usize { - self.as_bytes().match_up_to(null_terminated_pointer) + fn hash(&self, state: &mut H) { + self.0.hash(state); } } #[cfg(feature = "alloc")] -impl ToUnixString for String { +impl From<&UnixStr> for UnixString { #[inline] - fn to_unix_string(&self) -> crate::Result { - self.as_bytes().to_unix_string() - } -} - -const fn null_byte_out_of_place() -> crate::Result { - Err(Error::no_code("Null byte out of place")) -} - -#[derive(Debug, Copy, Clone)] -enum NullTermCheckResult { - NullTerminated, - NullByteOutOfPlace, - NotNullTerminated, -} - -#[inline] -fn null_terminated_ok(s: &[u8]) -> NullTermCheckResult { - let len = s.len(); - for (ind, byte) in s.iter().enumerate() { - if *byte == NULL_BYTE { - return if ind == len - 1 { - NullTermCheckResult::NullTerminated - } else { - NullTermCheckResult::NullByteOutOfPlace - }; - } + fn from(s: &UnixStr) -> Self { + UnixString(s.0.to_vec()) } - NullTermCheckResult::NotNullTerminated } #[inline] -const fn const_null_term_check(s: &[u8]) -> NullTermCheckResult { - if s.is_empty() { - return NullTermCheckResult::NotNullTerminated; - } +const fn const_null_term_validate(s: &[u8]) { + assert!( + !s.is_empty(), + "Tried to instantiate UnixStr from an invalid &str, not null terminated" + ); let len = s.len() - 1; let mut i = len; - if s[i] != b'\0' { - return NullTermCheckResult::NotNullTerminated; - } + assert!( + s[i] == b'\0', + "Tried to instantiate UnixStr from an invalid &str, not null terminated" + ); while i > 0 { i -= 1; - if s[i] == b'\0' { - return NullTermCheckResult::NullByteOutOfPlace; - } + assert!(s[i] != b'\0', "Tried to instantiate UnixStr from an invalid &str, a null byte was found but out of place"); + } +} + +#[cfg(test)] +mod tests { + use super::*; + #[test] + fn can_match_up_to() { + let haystack = UnixStr::try_from_str("haystack\0").unwrap(); + let needle = UnixStr::EMPTY; + assert_eq!(0, haystack.match_up_to(needle)); + let needle = UnixStr::try_from_str("h\0").unwrap(); + assert_eq!(1, haystack.match_up_to(needle)); + let needle = UnixStr::try_from_str("haystac\0").unwrap(); + assert_eq!(7, haystack.match_up_to(needle)); + let needle = UnixStr::try_from_str("haystack\0").unwrap(); + assert_eq!(8, haystack.match_up_to(needle)); + let needle = UnixStr::try_from_str("haystack2\0").unwrap(); + assert_eq!(8, haystack.match_up_to(needle)); + } + #[test] + #[cfg(feature = "alloc")] + fn can_create_unix_string_happy() { + let correct = UnixString::try_from_str("abc\0").unwrap(); + let correct2 = UnixString::try_from_bytes(b"abc\0").unwrap(); + let correct3 = UnixString::try_from_string("abc\0".to_string()).unwrap(); + let correct4 = UnixString::try_from_vec(b"abc\0".to_vec()).unwrap(); + let correct5 = UnixString::try_from_str("abc").unwrap(); + let correct6 = UnixString::try_from_string(String::from("abc")).unwrap(); + assert_eq!(correct, correct2); + assert_eq!(correct2, correct3); + assert_eq!(correct3, correct4); + assert_eq!(correct4, correct5); + assert_eq!(correct5, correct6); + let compare = [b'a', b'b', b'c', 0]; + assert_eq!(correct4.as_slice(), compare); + assert_ne!(correct.as_ptr(), correct2.as_ptr()); + assert_eq!(UnixStr::try_from_str("abc\0").unwrap(), correct.as_ref()); + assert_eq!(UnixStr::try_from_str("abc\0").unwrap(), &*correct); + } + + #[test] + #[cfg(feature = "alloc")] + fn can_create_unix_string_sad() { + let acceptable = UnixString::try_from_str("abc").unwrap(); + let correct = UnixString::try_from_str("abc\0").unwrap(); + assert_eq!(correct, acceptable); + let unacceptable = UnixString::try_from_str("a\0bc"); + assert!(unacceptable.is_err()); + let unacceptable_vec = UnixString::try_from_vec(alloc::vec![b'a', b'\0', b'b', b'c']); + assert!(unacceptable_vec.is_err()); + } + + #[test] + fn can_create_unix_str() { + const CONST_CORRECT: &UnixStr = UnixStr::from_str_checked("abc\0"); + + let correct1 = UnixStr::try_from_str("abc\0").unwrap(); + let correct2 = UnixStr::try_from_bytes(b"abc\0").unwrap(); + assert_eq!(CONST_CORRECT, correct1); + assert_eq!(correct1, correct2); + } + + #[test] + fn can_create_unix_str_sad() { + let unacceptable = UnixStr::try_from_str("a\0bc"); + assert!(unacceptable.is_err()); + let unacceptable_vec = UnixStr::try_from_bytes(&[b'a', b'\0', b'b', b'c']); + assert!(unacceptable_vec.is_err()); + let unacceptable_not_null_term = UnixStr::try_from_str("abc"); + assert!(unacceptable_not_null_term.is_err()); + } + + #[test] + fn can_cmp_unix_str_and_str() { + const UNIX_STR: &UnixStr = UnixStr::from_str_checked("my-nice-str\0"); + const MY_CMP_STR: &str = "my-nice-str"; + assert_eq!(MY_CMP_STR.len(), UNIX_STR.match_up_to_str(MY_CMP_STR)); } - NullTermCheckResult::NullTerminated } diff --git a/rusl/src/unistd/chdir.rs b/rusl/src/unistd/chdir.rs index 4b9c500..555361d 100644 --- a/rusl/src/unistd/chdir.rs +++ b/rusl/src/unistd/chdir.rs @@ -1,6 +1,6 @@ use sc::syscall; -use crate::string::unix_str::AsUnixStr; +use crate::string::unix_str::UnixStr; use crate::Result; /// Changes the working directory of the current process. @@ -8,10 +8,8 @@ use crate::Result; /// # Errors /// See above docs #[inline] -pub fn chdir(path: P) -> Result<()> { - path.exec_with_self_as_ptr(|ptr| { - let res = unsafe { syscall!(CHDIR, ptr) }; - bail_on_below_zero!(res, "`CHDIR` syscall failed"); - Ok(()) - }) +pub fn chdir(path: &UnixStr) -> Result<()> { + let res = unsafe { syscall!(CHDIR, path.as_ptr()) }; + bail_on_below_zero!(res, "`CHDIR` syscall failed"); + Ok(()) } diff --git a/rusl/src/unistd/get_dents.rs b/rusl/src/unistd/get_dents.rs index edb7df7..b9fad7c 100644 --- a/rusl/src/unistd/get_dents.rs +++ b/rusl/src/unistd/get_dents.rs @@ -16,6 +16,7 @@ pub fn get_dents(fd: Fd, dir_p: &mut [u8]) -> crate::Result { mod tests { use crate::platform::{DirType, Dirent, OpenFlags}; use crate::string::strlen::buf_strlen; + use crate::string::unix_str::UnixStr; use crate::unistd::open; use super::*; @@ -45,7 +46,7 @@ mod tests { #[test] fn try_read_dir() { let dir = open( - "test-files/dents-test\0", + UnixStr::try_from_str("test-files/dents-test\0").unwrap(), OpenFlags::O_CLOEXEC | OpenFlags::O_RDONLY, ) .unwrap(); diff --git a/rusl/src/unistd/mkdir.rs b/rusl/src/unistd/mkdir.rs index ef27d6d..c738269 100644 --- a/rusl/src/unistd/mkdir.rs +++ b/rusl/src/unistd/mkdir.rs @@ -1,7 +1,7 @@ use sc::syscall; use crate::platform::{Fd, Mode, AT_FDCWD}; -use crate::string::unix_str::AsUnixStr; +use crate::string::unix_str::UnixStr; use crate::Result; /// Create a directory named `path` @@ -9,7 +9,7 @@ use crate::Result; /// # Errors /// See above #[inline] -pub fn mkdir(path: impl AsUnixStr, mode: Mode) -> Result<()> { +pub fn mkdir(path: &UnixStr, mode: Mode) -> Result<()> { do_mkdir(AT_FDCWD, path, mode) } @@ -17,16 +17,14 @@ pub fn mkdir(path: impl AsUnixStr, mode: Mode) -> Result<()> { /// See [linux documentation for details](https://man7.org/linux/man-pages/man2/mkdir.2.html) /// # Errors /// See above -pub fn mkdir_at(dir_fd: Fd, path: impl AsUnixStr, mode: Mode) -> Result<()> { +pub fn mkdir_at(dir_fd: Fd, path: &UnixStr, mode: Mode) -> Result<()> { do_mkdir(dir_fd.0, path, mode) } #[inline(always)] #[allow(clippy::inline_always)] -fn do_mkdir(dir_fd: i32, path: impl AsUnixStr, mode: Mode) -> Result<()> { - path.exec_with_self_as_ptr(|ptr| { - let res = unsafe { syscall!(MKDIRAT, dir_fd, ptr, mode.bits()) }; - bail_on_below_zero!(res, "`MKDIRAT` syscall failed"); - Ok(()) - }) +fn do_mkdir(dir_fd: i32, path: &UnixStr, mode: Mode) -> Result<()> { + let res = unsafe { syscall!(MKDIRAT, dir_fd, path.as_ptr(), mode.bits()) }; + bail_on_below_zero!(res, "`MKDIRAT` syscall failed"); + Ok(()) } diff --git a/rusl/src/unistd/mount.rs b/rusl/src/unistd/mount.rs index 1e6d7b3..2a5b944 100644 --- a/rusl/src/unistd/mount.rs +++ b/rusl/src/unistd/mount.rs @@ -1,5 +1,5 @@ use crate::platform::{FilesystemType, Mountflags}; -use crate::string::unix_str::AsUnixStr; +use crate::string::unix_str::UnixStr; use crate::Result; use sc::syscall; @@ -9,35 +9,36 @@ use sc::syscall; /// See the [linux docs for details](https://man7.org/linux/man-pages/man2/mount.2.html). /// # Errors /// See above -pub fn mount( - source: SRC, - target: TGT, +pub fn mount( + source: &UnixStr, + target: &UnixStr, fs_type: FilesystemType, flags: Mountflags, - data: Option, -) -> Result<()> -where - SRC: AsUnixStr, - TGT: AsUnixStr, - DATA: AsUnixStr, -{ + data: Option<&UnixStr>, +) -> Result<()> { unsafe { - source.exec_with_self_as_ptr(|src| { - target.exec_with_self_as_ptr(|tgt| { - if let Some(data) = data { - data.exec_with_self_as_ptr(|data| { - let res = syscall!(MOUNT, src, tgt, fs_type.0, flags.bits(), data); - bail_on_below_zero!(res, "`MOUNT` syscall failed"); - Ok(res) - }) - } else { - let res = syscall!(MOUNT, src, tgt, fs_type.0, flags.bits(), 0); - bail_on_below_zero!(res, "`MOUNT` syscall failed"); - Ok(res) - } - }) - })?; - }; + if let Some(data) = data { + let res = syscall!( + MOUNT, + source.as_ptr(), + target.as_ptr(), + fs_type.0, + flags.bits(), + data.as_ptr() + ); + bail_on_below_zero!(res, "`MOUNT` syscall failed"); + } else { + let res = syscall!( + MOUNT, + source.as_ptr(), + target.as_ptr(), + fs_type.0, + flags.bits(), + 0 + ); + bail_on_below_zero!(res, "`MOUNT` syscall failed"); + } + } Ok(()) } @@ -46,15 +47,10 @@ where /// See the [linux docs for details](https://man7.org/linux/man-pages/man2/umount.2.html). /// # Errors /// See above. -pub fn unmount(target: TGT) -> Result<()> -where - TGT: AsUnixStr, -{ - target.exec_with_self_as_ptr(|ptr| { - unsafe { - let res = syscall!(UMOUNT2, ptr, 0); - bail_on_below_zero!(res, "`UNMOUNT2` syscall failed"); - } - Ok(()) - }) +pub fn unmount(target: &UnixStr) -> Result<()> { + unsafe { + let res = syscall!(UMOUNT2, target.as_ptr(), 0); + bail_on_below_zero!(res, "`UNMOUNT2` syscall failed"); + } + Ok(()) } diff --git a/rusl/src/unistd/open.rs b/rusl/src/unistd/open.rs index 2d63acf..d4211b4 100644 --- a/rusl/src/unistd/open.rs +++ b/rusl/src/unistd/open.rs @@ -1,7 +1,7 @@ use sc::syscall; use crate::platform::{Fd, Mode, OpenFlags, AT_FDCWD}; -use crate::string::unix_str::AsUnixStr; +use crate::string::unix_str::UnixStr; /// Attempts to open the fd at the path described by the null terminated pointer supplied. /// # Errors @@ -18,11 +18,9 @@ pub unsafe fn open_raw(name_addr: usize, flags: OpenFlags) -> crate::Result /// See the [linux docs here](https://man7.org/linux/man-pages/man2/open.2.html) /// # Errors /// See above, errors are converted into an Err with the corresponding error code -pub fn open(path: impl AsUnixStr, flags: OpenFlags) -> crate::Result { - path.exec_with_self_as_ptr(|ptr| { - let res = unsafe { syscall!(OPENAT, AT_FDCWD, ptr, flags.bits().0) }; - Fd::coerce_from_register(res, "`OPENAT` syscall failed") - }) +pub fn open(path: &UnixStr, flags: OpenFlags) -> crate::Result { + let res = unsafe { syscall!(OPENAT, AT_FDCWD, path.as_ptr(), flags.bits().0) }; + Fd::coerce_from_register(res, "`OPENAT` syscall failed") } /// Attempts to open the fd at the path specified by a null terminated string, with the provided `OpenFlags` and `Mode` @@ -30,57 +28,42 @@ pub fn open(path: impl AsUnixStr, flags: OpenFlags) -> crate::Result { /// # Errors /// see above #[inline] -pub fn open_mode(path: impl AsUnixStr, flags: OpenFlags, mode: Mode) -> crate::Result { - path.exec_with_self_as_ptr(|ptr| { - let res = unsafe { syscall!(OPENAT, AT_FDCWD, ptr, flags.bits().0, mode.bits()) }; - Fd::coerce_from_register(res, "`OPENAT` syscall failed") - }) +pub fn open_mode(path: &UnixStr, flags: OpenFlags, mode: Mode) -> crate::Result { + let res = unsafe { syscall!(OPENAT, AT_FDCWD, path.as_ptr(), flags.bits().0, mode.bits()) }; + Fd::coerce_from_register(res, "`OPENAT` syscall failed") } /// Attempts to open a file at the specified path from the opened directory (`Fd`) with the specified `OpenFlags` /// See the [linux docs here](https://man7.org/linux/man-pages/man2/open.2.html) /// # Errors /// see above -pub fn open_at(dir: Fd, path: impl AsUnixStr, flags: OpenFlags) -> crate::Result { - path.exec_with_self_as_ptr(|ptr| { - let res = unsafe { syscall!(OPENAT, dir.0, ptr, flags.bits().0) }; - Fd::coerce_from_register(res, "`OPENAT` syscall failed") - }) +pub fn open_at(dir: Fd, path: &UnixStr, flags: OpenFlags) -> crate::Result { + let res = unsafe { syscall!(OPENAT, dir.0, path.as_ptr(), flags.bits().0) }; + Fd::coerce_from_register(res, "`OPENAT` syscall failed") } /// Attempts to open a file at the specified path from the opened directory (`Fd`) with the specified `OpenFlags` and `Mode` /// See the [linux docs here](https://man7.org/linux/man-pages/man2/open.2.html) /// # Errors /// see above -pub fn open_at_mode( - dir: Fd, - path: impl AsUnixStr, - flags: OpenFlags, - mode: Mode, -) -> crate::Result { - path.exec_with_self_as_ptr(|ptr| { - let res = unsafe { syscall!(OPENAT, dir.0, ptr, flags.bits().0, mode.bits()) }; - Fd::coerce_from_register(res, "`OPENAT` syscall failed") - }) +pub fn open_at_mode(dir: Fd, path: &UnixStr, flags: OpenFlags, mode: Mode) -> crate::Result { + let res = unsafe { syscall!(OPENAT, dir.0, path.as_ptr(), flags.bits().0, mode.bits()) }; + Fd::coerce_from_register(res, "`OPENAT` syscall failed") } #[cfg(test)] mod tests { + use crate::string::unix_str::UnixStr; + // Differences between tmp-file creating on `x86_64` and `aarch64`, pretty interesting // seems that we can't just name a dir on `aarch64` because it produces `EISDIR` #[test] - #[cfg(target_arch = "x86_64")] fn try_open_temp() { - // TODO: fix - #[cfg(feature = "alloc")] - let path = "test-files"; - #[cfg(not(feature = "alloc"))] - let path = "test-files\0"; + let path = UnixStr::try_from_str("test-files\0").unwrap(); try_open_temp_at_path(path).unwrap(); } - #[cfg(target_arch = "x86_64")] - fn try_open_temp_at_path(path: &str) -> crate::Result<()> { + fn try_open_temp_at_path(path: &UnixStr) -> crate::Result<()> { use super::*; let _fd = open_mode( path, diff --git a/rusl/src/unistd/pipe.rs b/rusl/src/unistd/pipe.rs index b46847f..cde79fa 100644 --- a/rusl/src/unistd/pipe.rs +++ b/rusl/src/unistd/pipe.rs @@ -14,19 +14,14 @@ pub struct Pipe { /// # Errors /// See above pub fn pipe() -> crate::Result { - let mut fds = [-1, -1]; - let res = unsafe { syscall!(PIPE2, fds.as_mut_ptr(), 0) }; - bail_on_below_zero!(res, "`PIPE2` syscall failed creating new pipe"); - Ok(Pipe { - in_pipe: Fd::try_new(fds[0]).map_err(|_e| Error::no_code("In pipe fd below zero"))?, - out_pipe: Fd::try_new(fds[1]).map_err(|_e| Error::no_code("Out pipe fd below zero"))?, - }) + pipe2(OpenFlags::empty()) } /// Creates a new set of pipes by utilizing the `PIPE2` syscall, with openflags /// See that [documentation here](https://man7.org/linux/man-pages/man2/pipe.2.html) /// # Errors /// See above +#[inline] pub fn pipe2(flags: OpenFlags) -> crate::Result { let mut fds = [-1, -1]; let res = unsafe { syscall!(PIPE2, fds.as_mut_ptr(), flags.bits().0) }; @@ -36,3 +31,19 @@ pub fn pipe2(flags: OpenFlags) -> crate::Result { out_pipe: Fd::try_new(fds[1]).map_err(|_e| Error::no_code("Out pipe fd below zero"))?, }) } + +#[cfg(test)] +mod tests { + use crate::unistd::pipe; + + #[test] + fn test_pipe() { + const PAYLOAD: &[u8; 6] = b"Hello!"; + let pipe = pipe().unwrap(); + + crate::unistd::write(pipe.out_pipe, PAYLOAD).unwrap(); + let mut buf = [0u8; 6]; + crate::unistd::read(pipe.in_pipe, &mut buf).unwrap(); + assert_eq!(PAYLOAD, &buf); + } +} diff --git a/rusl/src/unistd/rename.rs b/rusl/src/unistd/rename.rs index dc9a5af..21c6756 100644 --- a/rusl/src/unistd/rename.rs +++ b/rusl/src/unistd/rename.rs @@ -1,14 +1,14 @@ use sc::syscall; use crate::platform::{Fd, RenameFlags, AT_FDCWD}; -use crate::string::unix_str::AsUnixStr; +use crate::string::unix_str::UnixStr; /// Renames `old_path` to `new_path` overwriting any content at `new_path` /// # Errors /// See the [linux docs for details](https://man7.org/linux/man-pages/man2/rename.2.html#ERRORS) #[inline(always)] #[allow(clippy::inline_always)] -pub fn rename(old_path: impl AsUnixStr, new_path: impl AsUnixStr) -> crate::Result<()> { +pub fn rename(old_path: &UnixStr, new_path: &UnixStr) -> crate::Result<()> { do_rename_at(AT_FDCWD, old_path, AT_FDCWD, new_path, RenameFlags::empty()) } @@ -18,8 +18,8 @@ pub fn rename(old_path: impl AsUnixStr, new_path: impl AsUnixStr) -> crate::Resu #[inline(always)] #[allow(clippy::inline_always)] pub fn rename_flags( - old_path: impl AsUnixStr, - new_path: impl AsUnixStr, + old_path: &UnixStr, + new_path: &UnixStr, flags: RenameFlags, ) -> crate::Result<()> { do_rename_at(AT_FDCWD, old_path, AT_FDCWD, new_path, flags) @@ -32,9 +32,9 @@ pub fn rename_flags( #[allow(clippy::inline_always)] pub fn rename_at( old_dir_fd: Fd, - old_path: impl AsUnixStr, + old_path: &UnixStr, new_dir_fd: Fd, - new_path: impl AsUnixStr, + new_path: &UnixStr, ) -> crate::Result<()> { do_rename_at( old_dir_fd.value(), @@ -52,9 +52,9 @@ pub fn rename_at( #[allow(clippy::inline_always)] pub fn rename_at2( old_dir_fd: Fd, - old_path: impl AsUnixStr, + old_path: &UnixStr, new_dir_fd: Fd, - new_path: impl AsUnixStr, + new_path: &UnixStr, flags: RenameFlags, ) -> crate::Result<()> { do_rename_at( @@ -68,25 +68,21 @@ pub fn rename_at2( fn do_rename_at( old_dir_fd: i32, - old_path: impl AsUnixStr, + old_path: &UnixStr, new_dir_fd: i32, - new_path: impl AsUnixStr, + new_path: &UnixStr, flags: RenameFlags, ) -> crate::Result<()> { - old_path.exec_with_self_as_ptr(move |old_ptr| { - new_path.exec_with_self_as_ptr(move |new_ptr| { - let res = unsafe { - syscall!( - RENAMEAT2, - old_dir_fd, - old_ptr, - new_dir_fd, - new_ptr, - flags.bits() - ) - }; - bail_on_below_zero!(res, "`RENAMEAT2` syscall failed`"); - Ok(()) - }) - }) + let res = unsafe { + syscall!( + RENAMEAT2, + old_dir_fd, + old_path.as_ptr(), + new_dir_fd, + new_path.as_ptr(), + flags.bits() + ) + }; + bail_on_below_zero!(res, "`RENAMEAT2` syscall failed`"); + Ok(()) } diff --git a/rusl/src/unistd/stat.rs b/rusl/src/unistd/stat.rs index 5c8d34b..bd7e7c3 100644 --- a/rusl/src/unistd/stat.rs +++ b/rusl/src/unistd/stat.rs @@ -3,15 +3,15 @@ use core::mem::MaybeUninit; use sc::syscall; use crate::platform::{Fd, Stat, AT_FDCWD}; -use crate::string::unix_str::AsUnixStr; +use crate::string::unix_str::UnixStr; /// [stat](https://man7.org/linux/man-pages/man2/statx.2.html) /// Gets file status at the path pointed to by `path` /// # Errors /// See above docs #[inline] -pub fn stat(path: impl AsUnixStr) -> crate::Result { - path.exec_with_self_as_ptr(|ptr| do_statat(AT_FDCWD, ptr)) +pub fn stat(path: &UnixStr) -> crate::Result { + do_statat(AT_FDCWD, path) } /// [fstat](https://man7.org/linux/man-pages/man2/stat.2.html) @@ -19,8 +19,8 @@ pub fn stat(path: impl AsUnixStr) -> crate::Result { /// # Errors /// See above docs #[inline] -pub fn statat(dir_fd: Fd, path: impl AsUnixStr) -> crate::Result { - path.exec_with_self_as_ptr(|ptr| do_statat(dir_fd.value(), ptr)) +pub fn statat(dir_fd: Fd, path: &UnixStr) -> crate::Result { + do_statat(dir_fd.value(), path) } /// [fstat](https://man7.org/linux/man-pages/man2/stat.2.html) @@ -29,16 +29,16 @@ pub fn statat(dir_fd: Fd, path: impl AsUnixStr) -> crate::Result { /// See above docs #[inline] pub fn stat_fd(dir_fd: Fd) -> crate::Result { - do_statat(dir_fd.0, b"\0".as_ptr()) + do_statat(dir_fd.0, UnixStr::EMPTY) } -fn do_statat(fd: i32, pathname: *const u8) -> crate::Result { +fn do_statat(fd: i32, pathname: &UnixStr) -> crate::Result { let mut stat = MaybeUninit::uninit(); let res = unsafe { syscall!( NEWFSTATAT, fd, - pathname, + pathname.as_ptr(), stat.as_mut_ptr(), crate::platform::DirFlags::AT_EMPTY_PATH.bits().0 ) @@ -55,16 +55,8 @@ mod tests { #[test] fn stat_test() { - #[cfg(feature = "alloc")] - let (a, b) = { ("test-files/can_stat.txt", "") }; - #[cfg(not(feature = "alloc"))] - let (a, b) = { ("test-files/can_stat.txt\0", "\0") }; - do_stat_cwd(a, b); - } - - fn do_stat_cwd(cwd_path: &str, empty_path: &str) { - stat(cwd_path).unwrap(); - stat(empty_path).unwrap(); - stat(()).unwrap(); + let legit_path = UnixStr::try_from_str("test-files/can_stat.txt\0").unwrap(); + stat(legit_path).unwrap(); + stat(UnixStr::EMPTY).unwrap(); } } diff --git a/rusl/src/unistd/swapon.rs b/rusl/src/unistd/swapon.rs index ad87a64..9b27668 100644 --- a/rusl/src/unistd/swapon.rs +++ b/rusl/src/unistd/swapon.rs @@ -1,4 +1,4 @@ -use crate::string::unix_str::AsUnixStr; +use crate::string::unix_str::UnixStr; use crate::Result; use sc::syscall; @@ -6,12 +6,10 @@ use sc::syscall; /// See the [linux docs for details](https://man7.org/linux/man-pages/man2/swapon.2.html). /// # Errors /// See above. -pub fn swapon(path: impl AsUnixStr, flags: i32) -> Result<()> { - path.exec_with_self_as_ptr(|ptr| { - unsafe { - let res = syscall!(SWAPON, ptr, flags); - bail_on_below_zero!(res, "`SWAPON` syscall failed"); - } - Ok(()) - }) +pub fn swapon(path: &UnixStr, flags: i32) -> Result<()> { + unsafe { + let res = syscall!(SWAPON, path.as_ptr(), flags); + bail_on_below_zero!(res, "`SWAPON` syscall failed"); + } + Ok(()) } diff --git a/rusl/src/unistd/test.rs b/rusl/src/unistd/test.rs index 2b13216..90f37c4 100644 --- a/rusl/src/unistd/test.rs +++ b/rusl/src/unistd/test.rs @@ -1,10 +1,15 @@ use crate::error::Errno; use crate::platform::OpenFlags; +use crate::string::unix_str::UnixStr; use crate::unistd::{close, fcntl_get_file_status, fcntl_set_file_status, open, read, write}; #[test] fn no_write_on_read_only() { - let fd = open("test-files/can_open.txt\0", OpenFlags::O_RDONLY).unwrap(); + let fd = open( + UnixStr::try_from_str("test-files/can_open.txt\0").unwrap(), + OpenFlags::O_RDONLY, + ) + .unwrap(); let mut buf = [0; 256]; let read_bytes = read(fd, &mut buf).unwrap(); let expect = b"open"; @@ -17,7 +22,7 @@ fn no_write_on_read_only() { #[test] fn no_read_on_wr_only() { - let path = "test-files/can_open.txt\0"; + let path = UnixStr::try_from_str("test-files/can_open.txt\0").unwrap(); let fd = open(path, OpenFlags::O_WRONLY).unwrap(); let mut buf = [0; 256]; expect_errno!(Errno::EBADF, read(fd, &mut buf)); @@ -29,7 +34,7 @@ fn no_read_on_wr_only() { #[test] fn close_closes() { - let path = "test-files/can_open.txt\0"; + let path = UnixStr::try_from_str("test-files/can_open.txt\0").unwrap(); let fd = open(path, OpenFlags::O_RDONLY).unwrap(); let mut buf = [0; 128]; let read_res = read(fd, &mut buf).unwrap(); @@ -43,7 +48,7 @@ fn close_closes() { #[test] fn set_file_non_blocking() { - let path = "test-files/can_open.txt\0"; + let path = UnixStr::try_from_str("test-files/can_open.txt\0").unwrap(); let fd = open(path, OpenFlags::O_RDONLY).unwrap(); let flags = fcntl_get_file_status(fd).unwrap(); assert_eq!(OpenFlags::empty(), flags & OpenFlags::O_NONBLOCK); diff --git a/rusl/src/unistd/unlink.rs b/rusl/src/unistd/unlink.rs index 2cf4748..d374eea 100644 --- a/rusl/src/unistd/unlink.rs +++ b/rusl/src/unistd/unlink.rs @@ -1,7 +1,7 @@ use sc::syscall; use crate::platform::{Fd, AT_FDCWD, AT_REMOVEDIR}; -use crate::string::unix_str::AsUnixStr; +use crate::string::unix_str::UnixStr; #[derive(Debug, Copy, Clone)] pub struct UnlinkFlags(i32); @@ -26,7 +26,7 @@ impl UnlinkFlags { /// # Errors /// See above docs #[inline] -pub fn unlink(path: impl AsUnixStr) -> crate::Result<()> { +pub fn unlink(path: &UnixStr) -> crate::Result<()> { unlink_flags(path, UnlinkFlags::empty()) } @@ -35,7 +35,7 @@ pub fn unlink(path: impl AsUnixStr) -> crate::Result<()> { /// # Errors /// See above docs #[inline] -pub fn unlink_flags(path: impl AsUnixStr, flags: UnlinkFlags) -> crate::Result<()> { +pub fn unlink_flags(path: &UnixStr, flags: UnlinkFlags) -> crate::Result<()> { do_unlink(AT_FDCWD, path, flags) } @@ -45,18 +45,16 @@ pub fn unlink_flags(path: impl AsUnixStr, flags: UnlinkFlags) -> crate::Result<( /// # Errors /// See above docs #[inline] -pub fn unlink_at(dir_fd: Fd, path: impl AsUnixStr, flags: UnlinkFlags) -> crate::Result<()> { +pub fn unlink_at(dir_fd: Fd, path: &UnixStr, flags: UnlinkFlags) -> crate::Result<()> { do_unlink(dir_fd.0, path, flags) } #[inline(always)] #[allow(clippy::inline_always)] -fn do_unlink(dir_fd: i32, path: impl AsUnixStr, flags: UnlinkFlags) -> crate::Result<()> { - path.exec_with_self_as_ptr(|ptr| { - let res = unsafe { syscall!(UNLINKAT, dir_fd, ptr, flags.0) }; - bail_on_below_zero!(res, "`UNLINKAT` syscall failed"); - Ok(()) - }) +fn do_unlink(dir_fd: i32, path: &UnixStr, flags: UnlinkFlags) -> crate::Result<()> { + let res = unsafe { syscall!(UNLINKAT, dir_fd, path.as_ptr(), flags.0) }; + bail_on_below_zero!(res, "`UNLINKAT` syscall failed"); + Ok(()) } /// Taking the liberty of using `unlinkat` for both implementations, effectively meaning diff --git a/rusl/tests/spawn.rs b/rusl/tests/spawn.rs index 687f0d7..ba091de 100644 --- a/rusl/tests/spawn.rs +++ b/rusl/tests/spawn.rs @@ -1,13 +1,19 @@ +#[cfg(feature = "integration-test")] use std::mem::MaybeUninit; +#[cfg(feature = "integration-test")] use rusl::error::Errno; +#[cfg(feature = "integration-test")] use rusl::platform::{ Clone3Args, CloneArgs, CloneFlags, Fd, PollEvents, PollFd, SignalKind, WaitPidFlags, }; +#[cfg(feature = "integration-test")] use rusl::process::{clone, clone3, exit, wait_pid}; +#[cfg(feature = "integration-test")] use rusl::select::ppoll; #[test] +#[cfg(feature = "integration-test")] fn test_clone3_vfork() { unsafe { // Doing a vfork, that's not explicitly implemented on aarch64 but is possible @@ -35,6 +41,7 @@ fn test_clone3_vfork() { } #[test] +#[cfg(feature = "integration-test")] fn test_clone3_pidfd() { unsafe { let mut pidfd: MaybeUninit = MaybeUninit::uninit(); @@ -64,6 +71,7 @@ fn test_clone3_pidfd() { } #[test] +#[cfg(feature = "integration-test")] fn test_regular_clone_vfork() { unsafe { let flags = CloneFlags::CLONE_VFORK; diff --git a/test-runners/alloc-st-main/Cargo.lock b/test-runners/alloc-st-main/Cargo.lock index 441fb44..f208411 100644 --- a/test-runners/alloc-st-main/Cargo.lock +++ b/test-runners/alloc-st-main/Cargo.lock @@ -8,7 +8,6 @@ version = "0.1.0" dependencies = [ "test-lib", "tiny-std", - "unix-print", ] [[package]] @@ -19,7 +18,7 @@ checksum = "88eeeb1a5ef57ab0d40fb7a256e1b211496f97d6a8664ef52c0a665ce69a4e0f" [[package]] name = "rusl" -version = "0.1.0" +version = "0.2.0" dependencies = [ "linux-rust-bindings", "sc", @@ -37,7 +36,6 @@ version = "0.1.0" dependencies = [ "rusl", "tiny-std", - "unix-print", ] [[package]] @@ -49,19 +47,9 @@ dependencies = [ [[package]] name = "tiny-std" -version = "0.1.1" +version = "0.2.0" dependencies = [ "rusl", "sc", "tiny-start", - "unix-print", -] - -[[package]] -name = "unix-print" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c50e1866b3de196f1329f6a805771eee750651c83bbebd5dff159e5f033cc16f" -dependencies = [ - "sc", ] diff --git a/test-runners/alloc-st-main/Cargo.toml b/test-runners/alloc-st-main/Cargo.toml index cf60763..fb8ad8e 100644 --- a/test-runners/alloc-st-main/Cargo.toml +++ b/test-runners/alloc-st-main/Cargo.toml @@ -7,5 +7,4 @@ edition = "2021" [dependencies] test-lib = { path = "../test-lib", features = ["alloc"] } -tiny-std = { path = "../../tiny-std", features = ["executable", "alloc", "global-allocator"] } -unix-print = "0.1.0" \ No newline at end of file +tiny-std = { path = "../../tiny-std", features = ["executable", "alloc", "global-allocator"] } \ No newline at end of file diff --git a/test-runners/alloc-st-main/src/main.rs b/test-runners/alloc-st-main/src/main.rs index 9ccbf56..727289e 100644 --- a/test-runners/alloc-st-main/src/main.rs +++ b/test-runners/alloc-st-main/src/main.rs @@ -3,7 +3,7 @@ #[no_mangle] pub fn main() -> i32 { - unix_print::unix_eprintln!("Starting alloc single threaded main"); + tiny_std::eprintln!("Starting alloc single threaded main"); test_lib::run_tests(); 0 } diff --git a/test-runners/no-alloc-main/Cargo.lock b/test-runners/no-alloc-main/Cargo.lock index 1a7c67d..d4fa9a7 100644 --- a/test-runners/no-alloc-main/Cargo.lock +++ b/test-runners/no-alloc-main/Cargo.lock @@ -14,12 +14,11 @@ version = "0.1.0" dependencies = [ "test-lib", "tiny-std", - "unix-print", ] [[package]] name = "rusl" -version = "0.1.0" +version = "0.2.0" dependencies = [ "linux-rust-bindings", "sc", @@ -37,7 +36,6 @@ version = "0.1.0" dependencies = [ "rusl", "tiny-std", - "unix-print", ] [[package]] @@ -49,19 +47,9 @@ dependencies = [ [[package]] name = "tiny-std" -version = "0.1.1" +version = "0.2.0" dependencies = [ "rusl", "sc", "tiny-start", - "unix-print", -] - -[[package]] -name = "unix-print" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c50e1866b3de196f1329f6a805771eee750651c83bbebd5dff159e5f033cc16f" -dependencies = [ - "sc", ] diff --git a/test-runners/no-alloc-main/Cargo.toml b/test-runners/no-alloc-main/Cargo.toml index 2080537..85821ae 100644 --- a/test-runners/no-alloc-main/Cargo.toml +++ b/test-runners/no-alloc-main/Cargo.toml @@ -7,5 +7,4 @@ edition = "2021" [dependencies] test-lib = { path = "../test-lib" } -tiny-std = { path = "../../tiny-std", default-features = false, features = ["executable"] } -unix-print = "0.1.0" \ No newline at end of file +tiny-std = { path = "../../tiny-std", default-features = false, features = ["executable"] } \ No newline at end of file diff --git a/test-runners/no-alloc-main/src/main.rs b/test-runners/no-alloc-main/src/main.rs index ad27c66..f0696dd 100644 --- a/test-runners/no-alloc-main/src/main.rs +++ b/test-runners/no-alloc-main/src/main.rs @@ -3,7 +3,7 @@ #[no_mangle] pub fn main() -> i32 { - unix_print::unix_eprintln!("Starting minimum main"); + tiny_std::eprintln!("Starting minimum main"); test_lib::run_tests(); 0 } diff --git a/test-runners/test-lib/Cargo.toml b/test-runners/test-lib/Cargo.toml index 25f436b..c54971c 100644 --- a/test-runners/test-lib/Cargo.toml +++ b/test-runners/test-lib/Cargo.toml @@ -8,7 +8,6 @@ edition = "2021" [dependencies] tiny-std = { path = "../../tiny-std", default-features = false, features = ["aux", "start", "symbols", "vdso"] } rusl = { path = "../../rusl", default-features = false } -unix-print = "0.1.0" [features] default = [] diff --git a/test-runners/test-lib/src/lib.rs b/test-runners/test-lib/src/lib.rs index 8a69cff..e000c91 100644 --- a/test-runners/test-lib/src/lib.rs +++ b/test-runners/test-lib/src/lib.rs @@ -18,14 +18,16 @@ mod threaded; #[cfg(not(feature = "alloc"))] use no_alloc::{spawn_no_args, spawn_with_args}; +use tiny_std::UnixStr; + #[macro_export] macro_rules! run_test { ($func: expr) => {{ - unix_print::unix_print!("Running test {} ... ", stringify!($func)); + tiny_std::print!("Running test {} ... ", stringify!($func)); let __start = tiny_std::time::MonotonicInstant::now(); $func(); let __elapsed = __start.elapsed().as_secs_f32(); - unix_print::unix_println!("[OK] - {:.3} seconds", __elapsed); + tiny_std::println!("[OK] - {:.3} seconds", __elapsed); }}; } @@ -38,7 +40,7 @@ pub fn run_tests() { } fn run_minimal_feature_set() { - unix_print::unix_println!("Running minimal feature set tests"); + tiny_std::println!("Running minimal feature set tests"); run_test!(get_env); run_test!(get_args); run_test!(spawn_no_args); @@ -48,7 +50,9 @@ fn run_minimal_feature_set() { } fn get_env() { + let v_unix = tiny_std::env::var_unix(UnixStr::try_from_str("HOME\0").unwrap()).unwrap(); let v = tiny_std::env::var("HOME").unwrap(); + assert_eq!(v, v_unix); if is_ci() { assert_eq!("/home/runner", v); } else { @@ -62,6 +66,7 @@ fn get_args() { let arg = args.next().unwrap().unwrap(); assert_eq!("dummy_arg", arg); let mut os_args = tiny_std::env::args_os(); + os_args.next(); let os_arg = os_args.next().unwrap(); assert_eq!("dummy_arg", os_arg.as_str().unwrap()); } @@ -94,7 +99,7 @@ fn get_time() { fn is_ci() -> bool { !matches!( - tiny_std::env::var("CI"), + tiny_std::env::var_unix(UnixStr::try_from_str("CI\0").unwrap()), Err(tiny_std::env::VarError::Missing) ) } diff --git a/test-runners/test-lib/src/no_alloc.rs b/test-runners/test-lib/src/no_alloc.rs index 68c7d00..ebbf3e7 100644 --- a/test-runners/test-lib/src/no_alloc.rs +++ b/test-runners/test-lib/src/no_alloc.rs @@ -1,10 +1,10 @@ use tiny_std::io::Read; use tiny_std::process::{Environment, Stdio}; +use tiny_std::UnixStr; pub fn spawn_no_args() { - // - let mut proc = tiny_std::process::spawn::<0, &str, &str, ()>( - "/usr/bin/uname\0", + let mut proc = tiny_std::process::spawn::<0, ()>( + UnixStr::try_from_str("/usr/bin/uname\0").unwrap(), [], &Environment::Inherit, Some(Stdio::MakePipe), @@ -29,9 +29,13 @@ pub fn spawn_no_args() { } pub fn spawn_with_args() { - let mut proc_with_arg = tiny_std::process::spawn::<3, _, _, ()>( - "/usr/bin/uname\0", - ["/usr/bin/uname\0", "-a\0", "\0"], + let mut proc_with_arg = tiny_std::process::spawn::<3, ()>( + UnixStr::try_from_str("/usr/bin/uname\0").unwrap(), + [ + UnixStr::try_from_str("/usr/bin/uname\0").unwrap(), + UnixStr::try_from_str("-a\0").unwrap(), + UnixStr::EMPTY, + ], &Environment::Inherit, Some(Stdio::MakePipe), Some(Stdio::MakePipe), diff --git a/test-runners/test-lib/src/with_alloc.rs b/test-runners/test-lib/src/with_alloc.rs index 1073fdc..c59fc34 100644 --- a/test-runners/test-lib/src/with_alloc.rs +++ b/test-runners/test-lib/src/with_alloc.rs @@ -1,13 +1,15 @@ use alloc::string::String; use tiny_std::io::Read; use tiny_std::process::Stdio; +use tiny_std::UnixStr; pub(crate) fn spawn_no_args() { - let mut proc = tiny_std::process::Command::new("/usr/bin/uname") - .unwrap() - .stdout(Stdio::MakePipe) - .spawn() - .unwrap(); + let mut proc = + tiny_std::process::Command::new(UnixStr::try_from_str("/usr/bin/uname\0").unwrap()) + .unwrap() + .stdout(Stdio::MakePipe) + .spawn() + .unwrap(); let exit = proc.wait().unwrap(); assert_eq!(0, exit); let mut out = proc.stdout.unwrap(); @@ -17,13 +19,13 @@ pub(crate) fn spawn_no_args() { } pub(crate) fn spawn_with_args() { - let mut proc = tiny_std::process::Command::new("/usr/bin/uname") - .unwrap() - .arg("-a") - .unwrap() - .stdout(Stdio::MakePipe) - .spawn() - .unwrap(); + let mut proc = + tiny_std::process::Command::new(UnixStr::try_from_str("/usr/bin/uname\0").unwrap()) + .unwrap() + .arg(UnixStr::try_from_str("-a\0").unwrap()) + .stdout(Stdio::MakePipe) + .spawn() + .unwrap(); let exit = proc.wait().unwrap(); assert_eq!(0, exit); let mut out = proc.stdout.unwrap(); diff --git a/test-runners/threaded-main/Cargo.lock b/test-runners/threaded-main/Cargo.lock index f81f6d5..9c745cf 100644 --- a/test-runners/threaded-main/Cargo.lock +++ b/test-runners/threaded-main/Cargo.lock @@ -10,7 +10,7 @@ checksum = "88eeeb1a5ef57ab0d40fb7a256e1b211496f97d6a8664ef52c0a665ce69a4e0f" [[package]] name = "rusl" -version = "0.1.0" +version = "0.2.0" dependencies = [ "linux-rust-bindings", "sc", @@ -28,7 +28,6 @@ version = "0.1.0" dependencies = [ "rusl", "tiny-std", - "unix-print", ] [[package]] @@ -37,7 +36,6 @@ version = "0.1.0" dependencies = [ "test-lib", "tiny-std", - "unix-print", ] [[package]] @@ -49,19 +47,9 @@ dependencies = [ [[package]] name = "tiny-std" -version = "0.1.1" +version = "0.2.0" dependencies = [ "rusl", "sc", "tiny-start", - "unix-print", -] - -[[package]] -name = "unix-print" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c50e1866b3de196f1329f6a805771eee750651c83bbebd5dff159e5f033cc16f" -dependencies = [ - "sc", ] diff --git a/test-runners/threaded-main/Cargo.toml b/test-runners/threaded-main/Cargo.toml index 8014206..30c8406 100644 --- a/test-runners/threaded-main/Cargo.toml +++ b/test-runners/threaded-main/Cargo.toml @@ -8,4 +8,3 @@ edition = "2021" [dependencies] test-lib = { path = "../test-lib", features = ["threaded"] } tiny-std = { path = "../../tiny-std", features = ["executable", "threaded", "global-allocator"] } -unix-print = "0.1.0" diff --git a/test-runners/threaded-main/src/main.rs b/test-runners/threaded-main/src/main.rs index c07e8a1..aa5de0d 100644 --- a/test-runners/threaded-main/src/main.rs +++ b/test-runners/threaded-main/src/main.rs @@ -3,7 +3,7 @@ #[no_mangle] pub fn main() -> i32 { - unix_print::unix_eprintln!("Starting threaded main"); + tiny_std::eprintln!("Starting threaded main"); test_lib::run_tests(); 0 } diff --git a/tiny-std/Cargo.toml b/tiny-std/Cargo.toml index 136557c..55c0e58 100644 --- a/tiny-std/Cargo.toml +++ b/tiny-std/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "tiny-std" -version = "0.1.1" +version = "0.2.0" edition = "2021" license = "MPL-2.0" readme = "../Readme.md" @@ -43,4 +43,3 @@ threaded = ["alloc", "start"] rusl = { path = "../rusl" , default-features = false } tiny-start = { path = "../tiny-start", default-features = false, optional = true } sc = "0.2.7" -unix-print = "0.1.0" diff --git a/tiny-std/Changelog.md b/tiny-std/Changelog.md index 458e529..c3bf5b9 100644 --- a/tiny-std/Changelog.md +++ b/tiny-std/Changelog.md @@ -3,6 +3,7 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/) and this project adheres to [Semantic Versioning](http://semver.org/). + ## [Unreleased] ### Fixed @@ -10,6 +11,21 @@ and this project adheres to [Semantic Versioning](http://semver.org/). ### Changed +## [v0.2.0] - 2023-10-01 +### Fixed +- Create dir all used to error when it ended with a slash +- `args_os` started at second arg + +### Added +- More tests + +### Changed +- API-breakage from removing AsRefUnixStr and replacing with +`&UnixStr`. Propagates down to all things containing `&UnixStr`. Main reason +is making allocation more explicit on the user's side, inviting opportunities for +const-evaluation of null-terminated strings. +- Un-publicked ReadBuf, might replace entirely later + ## [v0.1.1] ### Fixed - Segfaults on binaries built by rust 1.72+ and `nightly-08-16+` [details](https://github.com/rust-lang/rust/issues/115225#issuecomment-1694183173) by diff --git a/tiny-std/src/allocator/dlmalloc.rs b/tiny-std/src/allocator/dlmalloc.rs index f62ff8a..21140db 100644 --- a/tiny-std/src/allocator/dlmalloc.rs +++ b/tiny-std/src/allocator/dlmalloc.rs @@ -2019,18 +2019,72 @@ mod tests { #[test] fn smoke() { let mut a = Dlmalloc::new(); - unsafe { - let ptr = a.malloc(1, 1); - assert!(!ptr.is_null()); - *ptr = 9; - assert_eq!(*ptr, 9); - a.free(ptr); - - let ptr = a.malloc(1, 1); - assert!(!ptr.is_null()); - *ptr = 10; - assert_eq!(*ptr, 10); - a.free(ptr); + let aligns = [1, 2, 4, 8, 16]; + for i in 0..22 { + let align = aligns[i % aligns.len()]; + unsafe { + let tiny_ptr = a.malloc(1, 1); + assert!(!tiny_ptr.is_null()); + *tiny_ptr = 9; + assert_eq!(*tiny_ptr, 9); + + let small_ptr = a.malloc(16, 2); + assert!(!small_ptr.is_null()); + *small_ptr = 10; + assert_eq!(*small_ptr, 10); + let large_ptr = a.malloc(2_u64.pow(i as u32) as usize, align); + a.free(large_ptr); + a.free(small_ptr); + a.free(tiny_ptr); + } + } + } + + #[test] + fn realloc_small_align() { + let mut a = Dlmalloc::new(); + let aligns = [1, 2, 4, 8, 16]; + for i in 0..22 { + let align = aligns[i % aligns.len()]; + unsafe { + let sz = 2_u64.pow(i as u32) as usize; + let ptr1 = a.malloc(2_u64.pow(i as u32) as usize, align); + ptr1.write_bytes(1, sz); + let new_size = 2_u64.pow(i as u32 + 1) as usize; + let ptr2 = a.realloc(ptr1, sz, align, new_size); + ptr2.add(sz).write_bytes(2, sz); + let mut sum = 0usize; + for j in 0..new_size { + sum += ptr2.add(j).read() as usize; + } + // Should have written 1 to the first half, and 2 to the second half + assert_eq!(sz * 3, sum); + a.free(ptr2); + } + } + } + + #[test] + fn realloc_big_align() { + let mut a = Dlmalloc::new(); + let aligns = [32, 64, 128, 256, 512]; + for i in 9..22 { + let align = aligns[i % aligns.len()]; + unsafe { + let sz = 2_u64.pow(i as u32) as usize; + let ptr1 = a.malloc(2_u64.pow(i as u32) as usize, align); + ptr1.write_bytes(1, sz); + let new_size = 2_u64.pow(i as u32 + 1) as usize; + let ptr2 = a.realloc(ptr1, sz, align, new_size); + ptr2.add(sz).write_bytes(2, sz); + let mut sum = 0usize; + for j in 0..new_size { + sum += ptr2.add(j).read() as usize; + } + // Should have written 1 to the first half, and 2 to the second half + assert_eq!(sz * 3, sum); + a.free(ptr2); + } } } diff --git a/tiny-std/src/env.rs b/tiny-std/src/env.rs index eac882b..74215d3 100644 --- a/tiny-std/src/env.rs +++ b/tiny-std/src/env.rs @@ -1,7 +1,6 @@ use core::str::Utf8Error; use rusl::string::strlen::strlen; -use rusl::string::unix_str::AsUnixStr; use rusl::string::unix_str::UnixStr; /// We have to mimic libc globals here sadly, we rip the environment off the first pointer of the stack @@ -28,7 +27,7 @@ pub enum VarError { /// # Errors /// 1. Value is not in the environment /// 2. Value exists but is not utf-8 -pub fn var(key: P) -> Result<&'static str, VarError> { +pub fn var_unix(key: &UnixStr) -> Result<&'static str, VarError> { let mut env_ptr = unsafe { ENV.env_p }; while !env_ptr.is_null() { unsafe { @@ -37,13 +36,13 @@ pub fn var(key: P) -> Result<&'static str, VarError> { if var_ptr.is_null() { return Err(VarError::Missing); } - let match_up_to = key.match_up_to(var_ptr); + let match_up_to = key.match_up_to(UnixStr::from_ptr(var_ptr)); if match_up_to != 0 { // Next is '=' - if var_ptr.add(match_up_to + 1).read() == b'=' { - let value_len = strlen(var_ptr.add(match_up_to + 2)); + if var_ptr.add(match_up_to).read() == b'=' { + let value_len = strlen(var_ptr.add(match_up_to + 1)); let value_slice = - core::slice::from_raw_parts(var_ptr.add(match_up_to + 2), value_len); + core::slice::from_raw_parts(var_ptr.add(match_up_to + 1), value_len); return core::str::from_utf8(value_slice).map_err(VarError::NotUnicode); } } @@ -54,15 +53,44 @@ pub fn var(key: P) -> Result<&'static str, VarError> { Err(VarError::Missing) } +/// Get a variable for this process' environment with the provided `key`. +/// # Errors +/// 1. Value is not in the environment +/// 2. Value exists but is not utf-8 +pub fn var(key: &str) -> Result<&'static str, VarError> { + let mut env_ptr = unsafe { ENV.env_p }; + while !env_ptr.is_null() { + unsafe { + let var_ptr = env_ptr.read(); + // The last ptr in the ptr of ptrs in always null + if var_ptr.is_null() { + return Err(VarError::Missing); + } + let match_up_to = UnixStr::from_ptr(var_ptr).match_up_to_str(key); + if match_up_to != 0 { + // Next is '=' + if var_ptr.add(match_up_to).read() == b'=' { + let value_len = strlen(var_ptr.add(match_up_to + 1)); + let value_slice = + core::slice::from_raw_parts(var_ptr.add(match_up_to + 1), value_len); + return core::str::from_utf8(value_slice).map_err(VarError::NotUnicode); + } + } + + env_ptr = env_ptr.add(1); + } + } + Err(VarError::Missing) +} + +#[inline] #[must_use] #[allow(clippy::cast_possible_truncation)] pub fn args() -> Args { - Args { - ind: 0, - num_args: unsafe { ENV.arg_c } as usize, - } + Args(args_os()) } +#[inline] #[must_use] #[allow(clippy::cast_possible_truncation)] pub fn args_os() -> ArgsOs { @@ -72,38 +100,20 @@ pub fn args_os() -> ArgsOs { } } -pub struct Args { - ind: usize, - num_args: usize, -} +pub struct Args(ArgsOs); impl Iterator for Args { type Item = Result<&'static str, Utf8Error>; + #[inline] fn next(&mut self) -> Option { - if self.ind < self.num_args { - unsafe { - let arg_ptr = ENV.arg_v.add(self.ind); - self.ind += 1; - if arg_ptr.is_null() { - return None; - } - let arg = arg_ptr.read(); - if arg.is_null() { - return None; - } - let len = strlen(arg); - let arg_slice = core::slice::from_raw_parts(arg, len); - return Some(core::str::from_utf8(arg_slice)); - } - } - None + self.0.next().map(UnixStr::as_str) } } impl ExactSizeIterator for Args { fn len(&self) -> usize { - self.num_args + self.0.num_args } } @@ -117,9 +127,9 @@ impl Iterator for ArgsOs { fn next(&mut self) -> Option { if self.ind < self.num_args { - self.ind += 1; unsafe { let arg_ptr = ENV.arg_v.add(self.ind); + self.ind += 1; if arg_ptr.is_null() { return None; } @@ -127,6 +137,8 @@ impl Iterator for ArgsOs { if arg.is_null() { return None; } + // # Safety: + // Trusting the OS to null terminate strings return Some(UnixStr::from_ptr(arg)); } } diff --git a/tiny-std/src/fs.rs b/tiny-std/src/fs.rs index 574b55a..3b63ce7 100644 --- a/tiny-std/src/fs.rs +++ b/tiny-std/src/fs.rs @@ -8,7 +8,7 @@ use rusl::error::Errno; pub use rusl::platform::Mode; use rusl::platform::{Dirent, OpenFlags, Stat, NULL_BYTE}; use rusl::string::strlen::{buf_strlen, strlen}; -use rusl::string::unix_str::{AsUnixStr, UnixStr}; +use rusl::string::unix_str::UnixStr; use rusl::unistd::UnlinkFlags; use crate::error::Error; @@ -26,19 +26,19 @@ impl File { /// # Errors /// Operating system errors ond finding and reading files #[inline] - pub fn open(path: impl AsUnixStr) -> Result { + pub fn open(path: &UnixStr) -> Result { Self::open_with_options(path, OpenOptions::new().read(true)) } #[inline] - fn open_with_options(path: impl AsUnixStr, opts: &OpenOptions) -> Result { + fn open_with_options(path: &UnixStr, opts: &OpenOptions) -> Result { let flags = OpenFlags::O_CLOEXEC | opts.get_access_mode()? | opts.get_creation_mode()? | opts.flags; let fd = rusl::unistd::open_mode(path, flags, opts.mode)?; Ok(File(OwnedFd(fd))) } - fn open_at(dir_fd: RawFd, path: impl AsUnixStr) -> Result { + fn open_at(dir_fd: RawFd, path: &UnixStr) -> Result { let mut opts = OpenOptions::new(); opts.read(true); let flags = @@ -81,7 +81,7 @@ impl File { /// # Errors /// Os errors relating to file access #[allow(clippy::cast_sign_loss, clippy::cast_possible_truncation)] - pub fn copy(&self, dest: P) -> Result { + pub fn copy(&self, dest: &UnixStr) -> Result { let this_metadata = self.metadata()?; let dest = OpenOptions::new() .create(true) @@ -134,7 +134,7 @@ impl Read for File { /// # Errors /// Os errors relating to file access and reading #[cfg(feature = "alloc")] -pub fn read(path: P) -> Result> { +pub fn read(path: &UnixStr) -> Result> { let mut file = File::open(path)?; let mut bytes = Vec::new(); file.read_to_end(&mut bytes)?; @@ -145,7 +145,7 @@ pub fn read(path: P) -> Result> { /// # Errors /// Os errors relating to file access and reading as well as utf8 conversion errors #[cfg(feature = "alloc")] -pub fn read_to_string(path: P) -> Result { +pub fn read_to_string(path: &UnixStr) -> Result { let mut file = File::open(path)?; let mut string = String::new(); file.read_to_string(&mut string)?; @@ -158,7 +158,7 @@ pub fn read_to_string(path: P) -> Result { /// Use `File` and open with `append` to append to a file. /// # Errors /// Os errors relating to file creation or writing, such as permissions errors. -pub fn write(path: P, buf: &[u8]) -> Result<()> { +pub fn write(path: &UnixStr, buf: &[u8]) -> Result<()> { let mut file = OpenOptions::new() .write(true) .create(true) @@ -208,7 +208,7 @@ impl Metadata { /// # Errors /// Os errors relating to file access #[inline] -pub fn metadata(path: P) -> Result { +pub fn metadata(path: &UnixStr) -> Result { let res = rusl::unistd::stat(path)?; Ok(Metadata(res)) } @@ -218,7 +218,7 @@ pub fn metadata(path: P) -> Result { /// # Errors /// Os errors relating to file access #[inline] -pub fn rename(src: impl AsUnixStr, dest: impl AsUnixStr) -> Result<()> { +pub fn rename(src: &UnixStr, dest: &UnixStr) -> Result<()> { rusl::unistd::rename(src, dest)?; Ok(()) } @@ -228,8 +228,8 @@ pub fn rename(src: impl AsUnixStr, dest: impl AsUnixStr) -> Result<()> { /// # Errors /// See [`File::copy`] #[inline] -pub fn copy_file(src: impl AsUnixStr, dest: impl AsUnixStr) -> Result { - let src_file = File::open(&src)?; +pub fn copy_file(src: &UnixStr, dest: &UnixStr) -> Result { + let src_file = File::open(src)?; src_file.copy(dest) } @@ -237,7 +237,7 @@ pub fn copy_file(src: impl AsUnixStr, dest: impl AsUnixStr) -> Result { /// Will false-negative if the path is empty. /// # Errors /// Os errors relating to file access -pub fn exists(path: P) -> Result { +pub fn exists(path: &UnixStr) -> Result { match rusl::unistd::stat(path) { Ok(_) => Ok(true), Err(e) => { @@ -253,7 +253,7 @@ pub fn exists(path: P) -> Result { /// # Errors /// OS errors relating to file access/permissions #[inline] -pub fn remove_file(path: P) -> Result<()> { +pub fn remove_file(path: &UnixStr) -> Result<()> { rusl::unistd::unlink(path)?; Ok(()) } @@ -262,7 +262,7 @@ pub fn remove_file(path: P) -> Result<()> { /// # Errors /// OS errors relating to file access/permissions #[inline] -pub fn create_dir(path: P) -> Result<()> { +pub fn create_dir(path: &UnixStr) -> Result<()> { create_dir_mode(path, Mode::from(0o755)) } @@ -270,7 +270,7 @@ pub fn create_dir(path: P) -> Result<()> { /// # Errors /// OS errors relating to file access/permissions #[inline] -pub fn create_dir_mode(path: P, mode: Mode) -> Result<()> { +pub fn create_dir_mode(path: &UnixStr, mode: Mode) -> Result<()> { rusl::unistd::mkdir(path, mode)?; Ok(()) } @@ -280,24 +280,31 @@ pub fn create_dir_mode(path: P, mode: Mode) -> Result<()> { /// OS errors relating to file access/permissions /// If working without an allocator, the maximum path length is 512 bytes #[inline] -pub fn create_dir_all(path: P) -> Result<()> { +pub fn create_dir_all(path: &UnixStr) -> Result<()> { // To make it simple we'll just stack alloc an uninit 512 array for the path. // Kind of a travesty, but needs to be like this to work without an allocator const NO_ALLOC_MAX_LEN: usize = 512; const EMPTY: [MaybeUninit; NO_ALLOC_MAX_LEN] = [MaybeUninit::uninit(); NO_ALLOC_MAX_LEN]; - path.exec_with_self_as_ptr(|ptr| unsafe { + unsafe { // Could check if we haven't got any slashes at all and just run the pointer straight through // without possibly having to add an extra indirection buffer here. + let ptr = path.as_ptr(); let len = strlen(ptr); + if len == 0 { + return Err(Error::no_code( + "Can't create a directory with an empty name", + )); + } #[cfg(feature = "alloc")] if len > NO_ALLOC_MAX_LEN { let mut owned: Vec = Vec::with_capacity(len); ptr.copy_to(owned.as_mut_ptr(), len); - return write_all_sub_paths(owned.as_mut_slice(), ptr); + write_all_sub_paths(owned.as_mut_slice(), ptr)?; + return Ok(()); } #[cfg(not(feature = "alloc"))] if len > NO_ALLOC_MAX_LEN { - return Err(rusl::Error::no_code( + return Err(Error::no_code( "Supplied path larger than 512 without an allocator present", )); } @@ -306,13 +313,16 @@ pub fn create_dir_all(path: P) -> Result<()> { // Make into slice, we know the actual slice-length is len + 1 let initialized_section = copied[..len].as_mut_ptr().cast(); let buf: &mut [u8] = core::slice::from_raw_parts_mut(initialized_section, len); - write_all_sub_paths(buf, ptr) - })?; + write_all_sub_paths(buf, ptr)?; + } Ok(()) } #[inline] -fn write_all_sub_paths(buf: &mut [u8], raw: *const u8) -> core::result::Result<(), rusl::Error> { +unsafe fn write_all_sub_paths( + buf: &mut [u8], + raw: *const u8, +) -> core::result::Result<(), rusl::Error> { let len = buf.len(); let mut it = 1; loop { @@ -321,12 +331,16 @@ fn write_all_sub_paths(buf: &mut [u8], raw: *const u8) -> core::result::Result<( if ind == 0 { break; } - // Todo, actually make sure we restore + let byte = buf[ind]; if byte == b'/' { // Swap slash for null termination to make a valid path buf[ind] = NULL_BYTE; - return match rusl::unistd::mkdir(&buf[..=ind], Mode::from(0o755)) { + + return match rusl::unistd::mkdir( + UnixStr::from_bytes_unchecked(&buf[..=ind]), + Mode::from(0o755), + ) { // Successfully wrote, traverse down Ok(_) => { // Replace the null byte to make a valid path concatenation @@ -336,14 +350,21 @@ fn write_all_sub_paths(buf: &mut [u8], raw: *const u8) -> core::result::Result<( if buf[i] == b'/' { // Swap slash for null termination to make a valid path buf[i] = NULL_BYTE; - rusl::unistd::mkdir(&buf[..=i], Mode::from(0o755))?; + rusl::unistd::mkdir( + UnixStr::from_bytes_unchecked(&buf[..=i]), + Mode::from(0o755), + )?; // Swap back to continue down buf[i] = b'/'; } } + // if we end on a slash we don't have to write the last part + if unsafe { raw.add(len - 1).read() } == b'/' { + return Ok(()); + } // We know the actual length is len + 1 and null terminated, try write full rusl::unistd::mkdir( - unsafe { core::slice::from_raw_parts(raw, len + 1) }, + UnixStr::from_bytes_unchecked(core::slice::from_raw_parts(raw, len + 1)), Mode::from(0o755), )?; Ok(()) @@ -376,13 +397,13 @@ impl Directory { /// # Errors /// OS errors relating to file access/permissions #[inline] - pub fn open(path: P) -> Result { + pub fn open(path: &UnixStr) -> Result { let fd = rusl::unistd::open(path, OpenFlags::O_CLOEXEC | OpenFlags::O_RDONLY)?; Ok(Directory(OwnedFd(fd))) } #[inline] - fn open_at(dir_fd: RawFd, path: P) -> Result { + fn open_at(dir_fd: RawFd, path: &UnixStr) -> Result { let fd = rusl::unistd::open_at(dir_fd, path, OpenFlags::O_CLOEXEC | OpenFlags::O_RDONLY)?; Ok(Directory(OwnedFd(fd))) } @@ -577,7 +598,7 @@ impl<'a> DirEntry<'a> { /// # Errors /// OS errors relating to file access/permissions #[inline] -pub fn remove_dir(path: P) -> Result<()> { +pub fn remove_dir(path: &UnixStr) -> Result<()> { rusl::unistd::unlink_flags(path, UnlinkFlags::at_removedir())?; Ok(()) } @@ -586,8 +607,8 @@ pub fn remove_dir(path: P) -> Result<()> { /// Potentially very destructive /// # Errors /// Os errors relating to file access/permissions -pub fn remove_dir_all(path: P) -> Result<()> { - let dir = Directory::open(&path)?; +pub fn remove_dir_all(path: &UnixStr) -> Result<()> { + let dir = Directory::open(path)?; dir.remove_all()?; remove_dir(path) } @@ -678,7 +699,7 @@ impl OpenOptions { /// # Errors /// See `File::open_with_options` #[inline] - pub fn open(&self, path: impl AsUnixStr) -> Result { + pub fn open(&self, path: &UnixStr) -> Result { File::open_with_options(path, self) } diff --git a/tiny-std/src/fs/test.rs b/tiny-std/src/fs/test.rs index 1efec56..d4541d2 100644 --- a/tiny-std/src/fs/test.rs +++ b/tiny-std/src/fs/test.rs @@ -14,11 +14,11 @@ const TEST_1_CONTENT: &str = "Hello world! #[test] fn can_read_null_term_path() { - let path = "test-files/fs/test1.txt\0"; + let path = UnixStr::try_from_str("test-files/fs/test1.txt\0").unwrap(); can_read_using_file_at_path(path); } -fn can_read_using_file_at_path(path: &str) { +fn can_read_using_file_at_path(path: &UnixStr) { let mut file = File::open(path).unwrap(); let mut buf = [0; 128]; let content_len = file.read(&mut buf).unwrap(); @@ -29,7 +29,7 @@ fn can_read_using_file_at_path(path: &str) { #[test] #[cfg(feature = "alloc")] fn can_read_to_vec() { - let path = "test-files/fs/test1.txt"; + let path = UnixStr::try_from_str("test-files/fs/test1.txt\0").unwrap(); let mut opts = crate::fs::OpenOptions::new(); opts.read(true); let mut file = File::open(path).unwrap(); @@ -43,7 +43,7 @@ fn can_read_to_vec() { #[test] #[cfg(feature = "alloc")] fn can_read_to_string() { - let path = "test-files/fs/test1.txt"; + let path = UnixStr::try_from_str("test-files/fs/test1.txt\0").unwrap(); let mut opts = crate::fs::OpenOptions::new(); opts.read(true); let mut file = File::open(path).unwrap(); @@ -56,12 +56,12 @@ fn can_read_to_string() { #[test] fn can_stat() { - let dir = ""; + let dir = UnixStr::EMPTY; let dir_meta = metadata(dir).unwrap(); assert!(dir_meta.is_dir()); assert!(!dir_meta.is_symlink()); assert!(!dir_meta.is_file()); - let path = "src/fs/test.rs\0"; + let path = UnixStr::try_from_str("src/fs/test.rs\0").unwrap(); let file_meta = metadata(path).unwrap(); assert!(!file_meta.is_dir()); assert!(!file_meta.is_symlink()); @@ -70,7 +70,7 @@ fn can_stat() { #[test] fn can_create_read_and_write_file() { - let tgt = "test-files/fs/test_create_read1.txt\0"; + let tgt = UnixStr::try_from_str("test-files/fs/test_create_read1.txt\0").unwrap(); let mut file = OpenOptions::new() .write(true) .create(true) @@ -91,7 +91,7 @@ fn can_create_read_and_write_file() { #[test] fn can_create_and_delete_file() { - let tgt = "test-files/fs/test_create_delete1.txt\0"; + let tgt = UnixStr::try_from_str("test-files/fs/test_create_delete1.txt\0").unwrap(); let file = OpenOptions::new() .write(true) .create(true) @@ -112,7 +112,7 @@ fn can_create_and_delete_file() { #[test] fn can_create_and_delete_dir() { - let tgt = "test-files/fs/dir-test\0"; + let tgt = UnixStr::try_from_str("test-files/fs/dir-test\0").unwrap(); if metadata(tgt).is_ok() { crate::fs::remove_dir(tgt).unwrap(); } @@ -131,7 +131,7 @@ fn can_create_and_delete_dir() { #[test] fn can_open_and_read_dir() { - let tgt = "test-files/fs/dir-test1\0"; + let tgt = UnixStr::try_from_str("test-files/fs/dir-test1\0").unwrap(); let dir = crate::fs::Directory::open(tgt).unwrap(); let it = dir.read(); for entry in it { @@ -165,7 +165,7 @@ fn can_open_and_read_dir() { #[test] fn create_read_and_delete_dir_with_a_lot_of_files() { - let tgt = "test-files/fs/dir-test2\0"; + let tgt = UnixStr::try_from_str("test-files/fs/dir-test2\0").unwrap(); if metadata(tgt).is_ok() { crate::fs::remove_dir_all(tgt).unwrap(); } else { @@ -179,7 +179,7 @@ fn create_read_and_delete_dir_with_a_lot_of_files() { let mut f = OpenOptions::new() .create_new(true) .write(true) - .open(path) + .open(UnixStr::try_from_str(path).unwrap()) .unwrap(); f.write(std::format!("Test write {i}").as_bytes()).unwrap(); } @@ -218,17 +218,37 @@ fn create_read_and_delete_dir_with_a_lot_of_files() { #[test] fn can_create_remove_dir_all() { - let base = "test-files/fs/dir-test3\0"; - if metadata(base).is_ok() { - crate::fs::remove_dir_all(base).unwrap(); + create_remove_dir_all( + UnixStr::try_from_str("test-files/fs/dir-test3\0").unwrap(), + UnixStr::try_from_str("test-files/fs/dir-test3/dir0/dir1/dir2/dir3\0").unwrap(), + ); +} + +#[test] +fn can_create_remove_dir_all_trailing_slash() { + create_remove_dir_all( + UnixStr::try_from_str("test-files/fs/dir-test4/\0").unwrap(), + UnixStr::try_from_str("test-files/fs/dir-test4/dir0/dir1/dir2/dir3/\0").unwrap(), + ); +} + +#[test] +fn cant_create_empty_dir_all() { + assert!(crate::fs::create_dir_all(UnixStr::try_from_str("\0").unwrap()).is_err()); +} + +fn create_remove_dir_all(parent: &UnixStr, full_path: &UnixStr) { + if crate::fs::exists(parent).unwrap() { + crate::fs::remove_dir_all(parent).unwrap(); } - let sub_dirs = b"test-files/fs/dir-test3/dir0/dir1/dir2/dir3\0"; - let mut sub_dir_bytes = [0u8; 44]; - sub_dir_bytes.copy_from_slice(sub_dirs); - crate::fs::create_dir_all(sub_dir_bytes.as_mut_slice()).unwrap(); - assert!(metadata(sub_dirs.as_slice()).is_ok()); - crate::fs::remove_dir_all(base).unwrap(); - assert!(metadata(base).is_err()); + assert!( + !crate::fs::exists(parent).unwrap(), + "Failed to prepare test by removing {parent:?}" + ); + crate::fs::create_dir_all(full_path).unwrap(); + assert!(metadata(full_path).is_ok()); + crate::fs::remove_dir_all(parent).unwrap(); + assert!(metadata(parent).is_err()); } #[test] @@ -237,7 +257,7 @@ fn read_after_write_needs_reseek() { .read(true) .write(true) .create(true) - .open("test-files/test-read-after-write.txt\0") + .open(UnixStr::try_from_str("test-files/test-read-after-write.txt\0").unwrap()) .unwrap(); let content = b"My content goes here!"; let wrote_bytes = file.write(content).unwrap(); @@ -249,7 +269,7 @@ fn read_after_write_needs_reseek() { #[test] fn can_create_with_write() { - const TARGET_FILE: &str = "test-files/test-write-create.txt\0"; + const TARGET_FILE: &UnixStr = UnixStr::from_str_checked("test-files/test-write-create.txt\0"); const PAYLOAD: &[u8] = b"My write create payload!\n"; if metadata(TARGET_FILE).is_ok() { crate::fs::remove_file(TARGET_FILE).unwrap(); @@ -264,7 +284,8 @@ fn can_create_with_write() { #[test] fn can_replace_with_write() { - const TARGET_FILE: &str = "test-files/test-write-overwrite.txt\0"; + const TARGET_FILE: &UnixStr = + UnixStr::from_str_checked("test-files/test-write-overwrite.txt\0"); const PRE_PAYLOAD: &[u8] = b"My write should overwrite this payload!\n"; const POST_PAYLOAD: &[u8] = b"Overwritten!\n"; let mut file = OpenOptions::new() @@ -290,7 +311,7 @@ fn can_replace_with_write() { #[test] fn file_exists_when_exists() { - let tgt = "test-files/test_exists_not_yet.txt\0"; + let tgt = UnixStr::try_from_str("test-files/test_exists_not_yet.txt\0").unwrap(); if crate::fs::exists(tgt).unwrap() { crate::fs::remove_file(tgt).unwrap(); } @@ -307,10 +328,10 @@ fn file_can_be_moved() { let mut orig_buf = [0u8; EXPECT_CONTENT.len()]; let mut src_buf = [0u8; EXPECT_CONTENT.len()]; let mut dest_buf = [0u8; EXPECT_CONTENT.len()]; - let tgt = "test-files/fs/test_move_orig.txt\0"; + let tgt = UnixStr::try_from_str("test-files/fs/test_move_orig.txt\0").unwrap(); File::open(tgt).unwrap().read_exact(&mut orig_buf).unwrap(); assert_eq!(EXPECT_CONTENT, &orig_buf); - let src = "test-files/fs/tmp_test_move_cp.txt\0"; + let src = UnixStr::try_from_str("test-files/fs/tmp_test_move_cp.txt\0").unwrap(); let md = metadata(tgt).unwrap(); #[cfg(target_arch = "x86_64")] let file = { crate::fs::copy_file(tgt, src).unwrap() }; @@ -325,7 +346,7 @@ fn file_can_be_moved() { assert_eq!(md.mode(), md2.mode()); File::open(src).unwrap().read_exact(&mut src_buf).unwrap(); assert_eq!(EXPECT_CONTENT, &src_buf); - let dest = "test-files/fs/tmp_test_move_moved.txt\0"; + let dest = UnixStr::try_from_str("test-files/fs/tmp_test_move_moved.txt\0").unwrap(); crate::fs::rename(src, dest).unwrap(); assert!(!crate::fs::exists(src).unwrap()); File::open(dest).unwrap().read_exact(&mut dest_buf).unwrap(); @@ -333,7 +354,7 @@ fn file_can_be_moved() { } #[cfg(target_arch = "aarch64")] -fn rw_copy_exact(src: &str, buf: &mut [u8], dst: &str) -> File { +fn rw_copy_exact(src: &UnixStr, buf: &mut [u8], dst: &UnixStr) -> File { let src_md = metadata(src).unwrap(); let mut src = File::open(src).unwrap(); src.read_exact(buf).unwrap(); diff --git a/tiny-std/src/io.rs b/tiny-std/src/io.rs index 239aa6c..dd3cc8b 100644 --- a/tiny-std/src/io.rs +++ b/tiny-std/src/io.rs @@ -7,9 +7,9 @@ use core::{fmt, str}; use rusl::error::Errno; use crate::error::{Error, Result}; -use crate::io::read_buf::ReadBuf; -pub mod read_buf; +#[cfg(feature = "alloc")] +pub(crate) mod read_buf; pub trait Read { /// Read into to provided buffer @@ -40,33 +40,6 @@ pub trait Read { default_read_exact(self, buf) } - /// Reads into the provided `ReadBuf` - /// # Errors - /// Eventual Errors specific to the implementation - fn read_buf(&mut self, buf: &mut ReadBuf<'_>) -> Result<()> { - default_read_buf(|b| self.read(b), buf) - } - - /// Reads exactly enough bytes to fill the `ReadBuf` - /// # Errors - /// Eventual Errors specific to the implementation - fn read_buf_exact(&mut self, buf: &mut ReadBuf<'_>) -> Result<()> { - while buf.remaining() > 0 { - let prev_filled = buf.filled().len(); - match self.read_buf(buf) { - Ok(()) => {} - Err(e) if e.matches_errno(Errno::EINTR) => continue, - Err(e) => return Err(e), - } - - if buf.filled().len() == prev_filled { - return Err(Error::no_code("Failed to fill buffer")); - } - } - - Ok(()) - } - /// Get this reader by mut ref /// # Errors /// Eventual Errors specific to the implementation @@ -95,14 +68,14 @@ pub(crate) fn default_read_to_end(r: &mut R, buf: &mut Vec buf.reserve(32); // buf is full, need more space } - let mut read_buf = ReadBuf::uninit(buf.spare_capacity_mut()); + let mut read_buf = crate::io::read_buf::ReadBuf::uninit(buf.spare_capacity_mut()); // SAFETY: These bytes were initialized but not filled in the previous loop unsafe { read_buf.assume_init(initialized); } - match r.read_buf(&mut read_buf) { + match default_read_buf(|b| r.read(b), &mut read_buf) { Ok(()) => {} Err(ref e) if e.matches_errno(Errno::EINTR) => continue, Err(e) => return Err(e), @@ -229,7 +202,8 @@ pub(crate) fn default_read_exact(this: &mut R, mut buf: &mut [ } } -pub(crate) fn default_read_buf(read: F, buf: &mut ReadBuf<'_>) -> Result<()> +#[cfg(feature = "alloc")] +pub(crate) fn default_read_buf(read: F, buf: &mut crate::io::read_buf::ReadBuf<'_>) -> Result<()> where F: FnOnce(&mut [u8]) -> Result, { diff --git a/tiny-std/src/io/read_buf.rs b/tiny-std/src/io/read_buf.rs index cbfe091..dfc02de 100644 --- a/tiny-std/src/io/read_buf.rs +++ b/tiny-std/src/io/read_buf.rs @@ -1,5 +1,4 @@ use core::cmp; -use core::fmt::{self, Debug, Formatter}; use core::mem::MaybeUninit; /// A wrapper around a byte buffer that is incrementally filled and initialized. @@ -15,28 +14,18 @@ use core::mem::MaybeUninit; /// [ filled | unfilled ] /// [ initialized | uninitialized ] /// ``` -pub struct ReadBuf<'a> { +pub(crate) struct ReadBuf<'a> { buf: &'a mut [MaybeUninit], filled: usize, initialized: usize, } -impl Debug for ReadBuf<'_> { - fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - f.debug_struct("ReadBuf") - .field("init", &self.initialized()) - .field("filled", &self.filled) - .field("capacity", &self.capacity()) - .finish() - } -} - impl<'a> ReadBuf<'a> { /// Creates a new `ReadBuf` from a fully uninitialized buffer. /// /// Use `assume_init` if part of the buffer is known to be already initialized. #[inline] - pub fn uninit(buf: &'a mut [MaybeUninit]) -> ReadBuf<'a> { + pub(crate) fn uninit(buf: &'a mut [MaybeUninit]) -> ReadBuf<'a> { ReadBuf { buf, filled: 0, @@ -47,60 +36,24 @@ impl<'a> ReadBuf<'a> { /// Returns the total capacity of the buffer. #[inline] #[must_use] - pub fn capacity(&self) -> usize { + pub(crate) fn capacity(&self) -> usize { self.buf.len() } - /// Returns a shared reference to the filled portion of the buffer. - #[inline] - #[must_use] - pub fn filled(&self) -> &[u8] { - //SAFETY: We only slice the filled part of the buffer, which is always valid - unsafe { slice_assume_init_ref(&self.buf[0..self.filled]) } - } - - /// Returns a mutable reference to the filled portion of the buffer. - #[inline] - pub fn filled_mut(&mut self) -> &mut [u8] { - //SAFETY: We only slice the filled part of the buffer, which is always valid - unsafe { slice_assume_init_mut(&mut self.buf[0..self.filled]) } - } - - /// Returns a shared reference to the initialized portion of the buffer. - /// - /// This includes the filled portion. - #[inline] - #[must_use] - pub fn initialized(&self) -> &[u8] { - //SAFETY: We only slice the initialized part of the buffer, which is always valid - unsafe { slice_assume_init_ref(&self.buf[0..self.initialized]) } - } - /// Returns a mutable reference to the initialized portion of the buffer. /// /// This includes the filled portion. #[inline] - pub fn initialized_mut(&mut self) -> &mut [u8] { + pub(crate) fn initialized_mut(&mut self) -> &mut [u8] { //SAFETY: We only slice the initialized part of the buffer, which is always valid unsafe { slice_assume_init_mut(&mut self.buf[0..self.initialized]) } } - /// Returns a mutable reference to the unfilled part of the buffer without ensuring that it has been fully - /// initialized. - /// - /// # Safety - /// - /// The caller must not de-initialize portions of the buffer that have already been initialized. - #[inline] - pub unsafe fn unfilled_mut(&mut self) -> &mut [MaybeUninit] { - &mut self.buf[self.filled..] - } - /// Returns a mutable reference to the uninitialized part of the buffer. /// /// It is safe to uninitialize any of these bytes. #[inline] - pub fn uninitialized_mut(&mut self) -> &mut [MaybeUninit] { + pub(crate) fn uninitialized_mut(&mut self) -> &mut [MaybeUninit] { &mut self.buf[self.initialized..] } @@ -109,7 +62,7 @@ impl<'a> ReadBuf<'a> { /// Since `ReadBuf` tracks the region of the buffer that has been initialized, this is effectively "free" after /// the first use. #[inline] - pub fn initialize_unfilled(&mut self) -> &mut [u8] { + pub(crate) fn initialize_unfilled(&mut self) -> &mut [u8] { // should optimize out the assertion self.initialize_unfilled_to(self.remaining()) } @@ -121,7 +74,7 @@ impl<'a> ReadBuf<'a> { /// /// Panics if `self.remaining()` is less than `n`. #[inline] - pub fn initialize_unfilled_to(&mut self, n: usize) -> &mut [u8] { + pub(crate) fn initialize_unfilled_to(&mut self, n: usize) -> &mut [u8] { assert!(self.remaining() >= n); let extra_init = self.initialized - self.filled; @@ -148,18 +101,10 @@ impl<'a> ReadBuf<'a> { /// Returns the number of bytes at the end of the slice that have not yet been filled. #[inline] #[must_use] - pub fn remaining(&self) -> usize { + pub(crate) fn remaining(&self) -> usize { self.capacity() - self.filled } - /// Clears the buffer, resetting the filled region to empty. - /// - /// The number of initialized bytes is not changed, and the contents of the buffer are not modified. - #[inline] - pub fn clear(&mut self) { - self.set_filled(0); // The assertion in `set_filled` is optimized out - } - /// Increases the size of the filled region of the buffer. /// /// The number of initialized bytes is not changed. @@ -168,7 +113,7 @@ impl<'a> ReadBuf<'a> { /// /// Panics if the filled region of the buffer would become larger than the initialized region. #[inline] - pub fn add_filled(&mut self, n: usize) { + pub(crate) fn add_filled(&mut self, n: usize) { self.set_filled(self.filled + n); } @@ -183,9 +128,8 @@ impl<'a> ReadBuf<'a> { /// /// Panics if the filled region of the buffer would become larger than the initialized region. #[inline] - pub fn set_filled(&mut self, n: usize) { + pub(crate) fn set_filled(&mut self, n: usize) { assert!(n <= self.initialized); - self.filled = n; } @@ -198,53 +142,25 @@ impl<'a> ReadBuf<'a> { /// /// The caller must ensure that the first `n` unfilled bytes of the buffer have already been initialized. #[inline] - pub unsafe fn assume_init(&mut self, n: usize) { + pub(crate) unsafe fn assume_init(&mut self, n: usize) { self.initialized = cmp::max(self.initialized, self.filled + n); } - /// Appends data to the buffer, advancing the written position and possibly also the initialized position. - /// - /// # Panics - /// - /// Panics if `self.remaining()` is less than `buf.len()`. - #[inline] - pub fn append(&mut self, buf: &[u8]) { - assert!(self.remaining() >= buf.len()); - - // SAFETY: we do not de-initialize any of the elements of the slice - unsafe { - write_slice(&mut self.unfilled_mut()[..buf.len()], buf); - } - - // SAFETY: We just added the entire contents of buf to the filled section. - unsafe { self.assume_init(buf.len()) } - self.add_filled(buf.len()); - } - /// Returns the amount of bytes that have been filled. #[inline] #[must_use] - pub fn filled_len(&self) -> usize { + pub(crate) fn filled_len(&self) -> usize { self.filled } /// Returns the amount of bytes that have been initialized. #[inline] #[must_use] - pub fn initialized_len(&self) -> usize { + pub(crate) fn initialized_len(&self) -> usize { self.initialized } } -#[inline] -pub(crate) unsafe fn slice_assume_init_ref(slice: &[MaybeUninit]) -> &[T] { - // SAFETY: casting `slice` to a `*const [T]` is safe since the caller guarantees that - // `slice` is initialized, and `MaybeUninit` is guaranteed to have the same layout as `T`. - // The pointer obtained is valid since it refers to memory owned by `slice` which is a - // reference and thus guaranteed to be valid for reads. - &*(slice as *const [MaybeUninit] as *const [T]) -} - #[inline] pub(crate) unsafe fn slice_assume_init_mut(slice: &mut [MaybeUninit]) -> &mut [T] { // SAFETY: casting `slice` to a `*const [T]` is safe since the caller guarantees that @@ -253,17 +169,3 @@ pub(crate) unsafe fn slice_assume_init_mut(slice: &mut [MaybeUninit]) -> & // reference and thus guaranteed to be valid for reads. &mut *(slice as *mut [MaybeUninit] as *mut [T]) } - -pub(crate) fn write_slice<'a, T>(this: &'a mut [MaybeUninit], src: &[T]) -> &'a mut [T] -where - T: Copy, -{ - #[allow(clippy::transmute_ptr_to_ptr)] - // SAFETY: &[T] and &[MaybeUninit] have the same layout - let uninit_src: &[MaybeUninit] = unsafe { core::mem::transmute(src) }; - - this.copy_from_slice(uninit_src); - - // SAFETY: Valid elements have just been copied into `this` so it is initialized - unsafe { slice_assume_init_mut(this) } -} diff --git a/tiny-std/src/linux/epoll.rs b/tiny-std/src/linux/epoll.rs index bd3fb5a..02cc30f 100644 --- a/tiny-std/src/linux/epoll.rs +++ b/tiny-std/src/linux/epoll.rs @@ -92,29 +92,22 @@ impl EpollDriver { #[cfg(test)] mod tests { use super::*; - use rusl::platform::STDIN; + use rusl::platform::STDOUT; #[test] fn test_epoll_driver() { let drive = EpollDriver::create(true).unwrap(); - drive.register(STDIN, 1, EpollEventMask::EPOLLOUT).unwrap(); + drive.register(STDOUT, 1, EpollEventMask::EPOLLOUT).unwrap(); + println!("Dummy out"); let mut buf = [EpollEvent::new(0, EpollEventMask::empty())]; drive .wait(&mut buf, EpollTimeout::WaitMillis(1_000)) .unwrap(); assert_eq!(1, buf[0].get_data()); - if std::env::var("CI").is_ok() { - assert!( - buf[0].get_events().contains(EpollEventMask::EPOLLHUP), - "Expected EPOLLHUP, got {:?}", - buf[0].get_events() - ); - } else { - assert!( - buf[0].get_events().contains(EpollEventMask::EPOLLOUT), - "Expected EPOLLOUT, got {:?}", - buf[0].get_events() - ); - } + assert!( + buf[0].get_events().contains(EpollEventMask::EPOLLOUT), + "Expected EPOLLOUT, got {:?}", + buf[0].get_events() + ); } } diff --git a/tiny-std/src/net.rs b/tiny-std/src/net.rs index 5934ecf..658c443 100644 --- a/tiny-std/src/net.rs +++ b/tiny-std/src/net.rs @@ -1,7 +1,7 @@ use rusl::platform::{ AddressFamily, NonNegativeI32, SocketAddress, SocketFlags, SocketOptions, SocketType, }; -use rusl::string::unix_str::AsUnixStr; +use rusl::string::unix_str::UnixStr; use crate::error::Result; use crate::io::{Read, Write}; @@ -17,7 +17,7 @@ impl UnixStream { /// Creates and connects a non-blocking `UnixStream` at the specified path /// # Errors /// Various OS errors relating to permissions, and missing paths - pub fn connect(path: P, blocking: bool) -> Result { + pub fn connect(path: &UnixStr, blocking: bool) -> Result { let block = blocking .then(SocketFlags::empty) .unwrap_or(SocketFlags::SOCK_NONBLOCK); @@ -29,7 +29,7 @@ impl UnixStream { let addr = SocketAddress::try_from_unix(path)?; rusl::network::connect(fd, &addr)?; - Ok(UnixStream(OwnedFd(fd))) + Ok(Self(OwnedFd(fd))) } } @@ -65,7 +65,7 @@ impl UnixListener { /// Use the `blocking` variable to set as blocking or non-blocking /// # Errors /// Various OS errors relating to permissions, and missing paths - pub fn bind(path: P, blocking: bool) -> Result { + pub fn bind(path: &UnixStr, blocking: bool) -> Result { let block = blocking .then(SocketFlags::empty) .unwrap_or(SocketFlags::SOCK_NONBLOCK); diff --git a/tiny-std/src/net/test.rs b/tiny-std/src/net/test.rs index 43d4978..668657b 100644 --- a/tiny-std/src/net/test.rs +++ b/tiny-std/src/net/test.rs @@ -1,16 +1,26 @@ use crate::io::{Read, Write}; use crate::net::{UnixListener, UnixStream}; +use crate::unix::fd::AsRawFd; +use rusl::platform::{PollEvents, PollFd}; +use rusl::string::unix_str::UnixStr; #[test] fn test_ping_pong() { - let sock_path = "/tmp/test-sock/sock1\0"; + let sock_path = UnixStr::try_from_str("/tmp/test-sock/sock1\0").unwrap(); let _ = crate::fs::remove_file(sock_path); - let _ = crate::fs::create_dir("/tmp/test-sock\0"); + let _ = crate::fs::create_dir(UnixStr::try_from_str("/tmp/test-sock\0").unwrap()); let listener = UnixListener::bind(sock_path, false).unwrap(); let mut client = UnixStream::connect(sock_path, false).unwrap(); let mut client_handle = listener.accept(true).unwrap(); let write_in = &[8, 8, 8, 8]; client_handle.write_all(write_in).unwrap(); + client_handle.flush().unwrap(); + rusl::select::ppoll( + &mut [PollFd::new(client.as_raw_fd(), PollEvents::POLLOUT)], + None, + None, + ) + .unwrap(); let mut dest = [0u8; 4]; client.read_exact(&mut dest).unwrap(); assert_eq!(write_in, &dest); diff --git a/tiny-std/src/process.rs b/tiny-std/src/process.rs index 401f72b..467068a 100644 --- a/tiny-std/src/process.rs +++ b/tiny-std/src/process.rs @@ -9,16 +9,16 @@ use core::hint::unreachable_unchecked; use rusl::error::Errno; use rusl::platform::{Fd, GidT, OpenFlags, PidT, UidT, WaitPidFlags}; use rusl::platform::{STDERR, STDIN, STDOUT}; +use rusl::string::unix_str::UnixStr; #[cfg(feature = "alloc")] use rusl::string::unix_str::UnixString; -use rusl::string::unix_str::{AsUnixStr, UnixStr}; use crate::error::{Error, Result}; use crate::fs::OpenOptions; use crate::io::{Read, Write}; use crate::unix::fd::{OwnedFd, RawFd}; -const DEV_NULL: &str = "/dev/null\0"; +const DEV_NULL: &UnixStr = UnixStr::from_str_checked("/dev/null\0"); /// Terminates this process #[inline] @@ -27,13 +27,13 @@ pub fn exit(code: i32) -> ! { } #[cfg(feature = "alloc")] -pub struct Command { - bin: UnixString, - args: Vec, +pub struct Command<'a> { + bin: &'a UnixStr, + args: Vec<&'a UnixStr>, argv: Argv, - closures: Vec crate::error::Result<()> + Send + Sync>>, + closures: Vec Result<()> + Send + Sync>>, env: Environment, - cwd: Option, + cwd: Option<&'a UnixStr>, uid: Option, gid: Option, stdin: Option, @@ -67,15 +67,14 @@ unsafe impl Send for Envp {} unsafe impl Sync for Envp {} #[cfg(feature = "alloc")] -impl Command { +impl<'a> Command<'a> { /// Constructs a new command, setting the first argument as the binary's name /// # Errors /// If the string is not `C string compatible` - pub fn new(bin: A) -> Result { - let bin = bin.to_unix_string()?; + pub fn new(bin: &'a UnixStr) -> Result { let bin_ptr = bin.as_ptr(); Ok(Self { - bin: bin.clone(), + bin, args: vec![bin], argv: Argv(vec![bin_ptr, core::ptr::null()]), closures: vec![], @@ -92,7 +91,7 @@ impl Command { /// # Errors /// If the string is not `C string compatible` - pub fn env(&mut self, env: A) -> Result<&mut Self> { + pub fn env(&mut self, env: UnixString) -> &mut Self { #[cfg(feature = "start")] if matches!(self.env, Environment::Inherit | Environment::None) { self.env = Environment::Provided(ProvidedEnvironment { @@ -108,42 +107,40 @@ impl Command { }); }; if let Environment::Provided(pe) = &mut self.env { - let s = env.to_unix_string()?; + let s = env; pe.envp.0[pe.vars.len()] = s.as_ptr(); pe.envp.0.push(core::ptr::null()); pe.vars.push(s); } - Ok(self) + self } /// # Errors /// If the string is not `C string compatible` - pub fn envs(&mut self, envs: Vec) -> Result<&mut Self> { + pub fn envs(&mut self, envs: impl Iterator) -> &mut Self { for env in envs { - self.env(env)?; + self.env(env); } - Ok(self) + self } /// # Errors /// If the string is not `C string compatible` - pub fn arg(&mut self, arg: A) -> Result<&mut Self> { - let unix_string = arg.to_unix_string()?; + pub fn arg(&mut self, arg: &'a UnixStr) -> &mut Self { + let unix_string = arg; self.argv.0[self.args.len()] = unix_string.as_ptr(); self.argv.0.push(core::ptr::null()); self.args.push(unix_string); - Ok(self) + self } /// # Errors /// If the string is not `C string compatible` - pub fn args(&mut self, args: &[A]) -> Result<&mut Self> { - self.args.reserve(args.len()); - self.argv.0.reserve(args.len()); + pub fn args(&mut self, args: impl Iterator) -> &mut Self { for arg in args { - self.arg(arg)?; + self.arg(arg); } - Ok(self) + self } /// A function to run after `forking` off the process but before the exec call @@ -160,9 +157,9 @@ impl Command { /// # Errors /// If the string is not `C string compatible` - pub fn cwd(&mut self, dir: A) -> Result<&mut Self> { - self.cwd = Some(dir.to_unix_string()?); - Ok(self) + pub fn cwd(&mut self, dir: &'a UnixStr) -> &mut Self { + self.cwd = Some(dir); + self } pub fn uid(&mut self, id: UidT) -> &mut Self { @@ -208,7 +205,7 @@ impl Command { }; unsafe { do_spawn( - &self.bin, + self.bin, self.argv.0.as_ptr(), envp, Stdio::Inherit, @@ -217,7 +214,7 @@ impl Command { self.stdout, self.stderr, &mut self.closures, - self.cwd.as_deref(), + self.cwd, self.uid, self.gid, self.pgroup, @@ -233,7 +230,7 @@ impl Command { Environment::None => NULL_ENV.as_ptr(), Environment::Provided(provided) => provided.envp.0.as_ptr(), }; - unsafe { do_exec(&self.bin, self.argv.0.as_ptr(), envp, &mut self.closures) } + unsafe { do_exec(self.bin, self.argv.0.as_ptr(), envp, &mut self.closures) } } } @@ -388,18 +385,18 @@ pub trait PreExec { /// Run this routing pre exec /// # Errors /// Any errors occuring, it's up to the implementor to decide - fn run(&mut self) -> crate::error::Result<()>; + fn run(&mut self) -> Result<()>; } #[cfg(feature = "alloc")] -impl PreExec for Box crate::error::Result<()> + Send + Sync> { +impl PreExec for Box Result<()> + Send + Sync> { #[inline] fn run(&mut self) -> Result<()> { (self)() } } -impl<'a> PreExec for &'a mut (dyn FnMut() -> crate::error::Result<()> + Send + Sync) { +impl<'a> PreExec for &'a mut (dyn FnMut() -> Result<()> + Send + Sync) { #[inline] fn run(&mut self) -> Result<()> { (self)() @@ -562,9 +559,9 @@ unsafe fn do_spawn( /// after if we did an allocation before that closure. #[cfg(not(feature = "alloc"))] #[allow(clippy::too_many_arguments)] -pub fn spawn( - bin: BIN, - argv: [ARG; N], +pub fn spawn( + bin: &UnixStr, + argv: [&UnixStr; N], env: &Environment, stdin: Option, stdout: Option, @@ -585,28 +582,20 @@ pub fn spawn( let mut new_args = [core::ptr::null(); N]; let arg_ptr = if argv.is_empty() { // Make sure we at least send the bin as arg - bin.exec_with_self_as_ptr(|ptr| { - no_args[0] = ptr; - Ok(()) - })?; + no_args[0] = bin.as_ptr(); no_args.as_ptr() } else { for (ind, arg) in argv.into_iter().enumerate() { - arg.exec_with_self_as_ptr(|ptr| { - new_args[ind] = ptr; - Ok(()) - })?; + new_args[ind] = arg.as_ptr(); } new_args[N - 1] = core::ptr::null(); new_args.as_ptr() }; // Only safe to do on no-alloc, since we may create a string there and the pointer will // dangle if we take it out of the closure - let bin_ptr = bin.exec_with_self_as_ptr(Ok)?; - let bin_str = unsafe { UnixStr::from_ptr(bin_ptr) }; unsafe { do_spawn( - bin_str, + bin, arg_ptr, envp, Stdio::Inherit, diff --git a/tiny-std/src/sync.rs b/tiny-std/src/sync.rs index 75fdec2..6e9138a 100644 --- a/tiny-std/src/sync.rs +++ b/tiny-std/src/sync.rs @@ -42,3 +42,29 @@ pub(crate) fn futex_wait_fast(futex: &AtomicU32, expect: u32) { } } } + +#[cfg(test)] +mod tests { + use crate::sync::futex_wait_fast; + use core::sync::atomic::AtomicU32; + use rusl::futex::futex_wake; + + #[test] + fn wait_fast_shortcircuit() { + let futex = AtomicU32::new(0); + // We would expect this to hang forever if it didn't work + futex_wait_fast(&futex, 1); + } + + #[test] + fn wait_fast_cant_short() { + let futex = std::sync::Arc::new(AtomicU32::new(0)); + let f_c = futex.clone(); + let handle = std::thread::spawn(move || { + futex_wait_fast(&f_c, 0); + }); + // Just try wake until we wake the thread + while futex_wake(&futex, 1).unwrap() == 0 {} + handle.join().unwrap(); + } +} diff --git a/tiny-std/src/sync/mutex.rs b/tiny-std/src/sync/mutex.rs index fcfed34..88fd67b 100644 --- a/tiny-std/src/sync/mutex.rs +++ b/tiny-std/src/sync/mutex.rs @@ -37,7 +37,7 @@ use core::sync::atomic::{ }; use rusl::futex::futex_wake; -pub struct InnerMutex { +struct InnerMutex { /// 0: unlocked /// 1: locked, no other threads waiting /// 2: locked, and other threads waiting (contended) @@ -46,19 +46,19 @@ pub struct InnerMutex { impl InnerMutex { #[inline] - pub const fn new() -> Self { + const fn new() -> Self { Self { futex: AtomicU32::new(0), } } #[inline] - pub fn try_lock(&self) -> bool { + fn try_lock(&self) -> bool { self.futex.compare_exchange(0, 1, Acquire, Relaxed).is_ok() } #[inline] - pub fn lock(&self) { + fn lock(&self) { if self.futex.compare_exchange(0, 1, Acquire, Relaxed).is_err() { self.lock_contended(); } @@ -114,7 +114,7 @@ impl InnerMutex { } #[inline] - pub unsafe fn unlock(&self) { + unsafe fn unlock(&self) { if self.futex.swap(0, Release) == 2 { // We only wake up one thread. When that thread locks the mutex, it // will mark the mutex as contended (2) (see lock_contended above), @@ -253,3 +253,43 @@ impl fmt::Debug for MutexGuard<'_, T> { fmt::Debug::fmt(&**self, f) } } + +#[cfg(test)] +mod tests { + use crate::sync::Mutex; + use core::time::Duration; + + #[test] + fn lock_threaded_mutex() { + let count = std::sync::Arc::new(Mutex::new(0)); + let mut handles = std::vec::Vec::new(); + for _i in 0..15 { + let count_c = count.clone(); + let handle = std::thread::spawn(move || { + // Try to create some contention + let mut guard = count_c.lock(); + std::thread::sleep(Duration::from_millis(1)); + *guard += 1; + }); + handles.push(handle); + } + for h in handles { + h.join().unwrap(); + } + assert_eq!(15, *count.lock()); + } + + #[test] + fn try_lock_threaded_mutex() { + let val = std::sync::Arc::new(Mutex::new(0)); + let val_c = val.clone(); + assert_eq!(0, *val_c.try_lock().unwrap()); + std::thread::spawn(move || { + let _guard = val_c.lock(); + std::thread::sleep(Duration::from_millis(2000)); + }); + // ... Timing + std::thread::sleep(Duration::from_millis(100)); + assert!(val.try_lock().is_none()); + } +} diff --git a/tiny-std/src/sync/rwlock.rs b/tiny-std/src/sync/rwlock.rs index 96b92cd..5dd7006 100644 --- a/tiny-std/src/sync/rwlock.rs +++ b/tiny-std/src/sync/rwlock.rs @@ -538,11 +538,12 @@ impl Drop for RwLockWriteGuard<'_, T> { #[cfg(test)] mod tests { + use crate::sync::RwLock; + use core::time::Duration; #[test] - #[cfg(feature = "alloc")] fn can_lock() { - let rw = alloc::sync::Arc::new(super::RwLock::new(0)); + let rw = std::sync::Arc::new(super::RwLock::new(0)); let rw_c = rw.clone(); let mut guard = rw.write(); let res = std::thread::spawn(move || *rw_c.read()); @@ -551,4 +552,46 @@ mod tests { let thread_res = res.join().unwrap(); assert_eq!(15, thread_res); } + + #[test] + fn can_mutex_contended() { + const NUM_THREADS: usize = 32; + let count = std::sync::Arc::new(RwLock::new(0)); + let mut handles = std::vec::Vec::new(); + for _i in 0..NUM_THREADS { + let count_c = count.clone(); + let handle = std::thread::spawn(move || { + // Try to create some contention + let mut w_guard = count_c.write(); + let orig = *w_guard; + std::thread::sleep(Duration::from_millis(1)); + *w_guard += 1; + drop(w_guard); + std::thread::sleep(Duration::from_millis(1)); + let r_guard = count_c.read(); + std::thread::sleep(Duration::from_millis(1)); + // We incremented this + assert!(*r_guard > orig); + }); + handles.push(handle); + } + for h in handles { + h.join().unwrap(); + } + assert_eq!(NUM_THREADS, *count.read()); + } + + #[test] + fn can_try_rw_single_thread_contended() { + let rw = std::sync::Arc::new(super::RwLock::new(0)); + let rw_c = rw.clone(); + assert_eq!(0, *rw_c.try_read().unwrap()); + let r_guard = rw.read(); + assert_eq!(0, *rw_c.try_read().unwrap()); + assert!(rw_c.try_write().is_none()); + drop(r_guard); + assert_eq!(0, *rw_c.try_write().unwrap()); + let _w_guard = rw.write(); + assert!(rw_c.try_read().is_none()); + } } diff --git a/tiny-std/src/thread.rs b/tiny-std/src/thread.rs index 419a451..e945266 100644 --- a/tiny-std/src/thread.rs +++ b/tiny-std/src/thread.rs @@ -34,11 +34,10 @@ mod tests { #[test] fn try_sleep() { - let sleep_dur = Duration::from_nanos(5_000_000); + let sleep_dur = Duration::from_millis(15); let now = MonotonicInstant::now(); sleep(sleep_dur).unwrap(); let elapsed = now.elapsed(); - let drift = elapsed - sleep_dur; - assert!(drift.as_millis() <= 1); + assert!(elapsed > sleep_dur); } } diff --git a/tiny-std/src/thread/spawn.rs b/tiny-std/src/thread/spawn.rs index 9f3b18a..c6dc3a9 100644 --- a/tiny-std/src/thread/spawn.rs +++ b/tiny-std/src/thread/spawn.rs @@ -9,6 +9,7 @@ use core::num::NonZeroUsize; use core::sync::atomic::{AtomicBool, AtomicU32, Ordering}; use sc::nr::MUNMAP; +use crate::eprintln; use rusl::platform::{CloneFlags, MapAdditionalFlags, MapRequiredFlag, MemoryProtection}; use rusl::unistd::mmap; @@ -626,7 +627,7 @@ pub fn on_panic(info: &core::panic::PanicInfo) -> ! { options(nostack, noreturn) ); } else { - unix_print::unix_eprintln!("Main thread panicked: {}", info); + eprintln!("Main thread panicked: {}", info); rusl::process::exit(1) } } diff --git a/tiny-std/src/time.rs b/tiny-std/src/time.rs index 393a02a..16529d4 100644 --- a/tiny-std/src/time.rs +++ b/tiny-std/src/time.rs @@ -41,7 +41,7 @@ impl MonotonicInstant { /// Some instant in time, ever increasing but able to be manipulated. /// The manipulations carries a risk of over/underflow, #[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)] -pub struct Instant(TimeSpec); +pub struct Instant(pub(crate) TimeSpec); impl Instant { #[inline] @@ -169,13 +169,14 @@ fn checked_sub_dur(timespec: TimeSpec, duration: Duration) -> Option { if total_nanos < 0 { // tv_nsec is always < `NANOS_A_SECOND`, so this won't get wonky total_nanos += NANOS_A_SECOND; - seconds = seconds.checked_sub(1)?; + seconds = seconds.checked_add(1)?; } let tv_sec = timespec.seconds().checked_sub(seconds.try_into().ok()?)?; - Some(TimeSpec::new(tv_sec.gt(&0).then_some(tv_sec)?, total_nanos)) + Some(TimeSpec::new(tv_sec.ge(&0).then_some(tv_sec)?, total_nanos)) } +/// Can panic if left is not bigger than right #[inline] #[allow(clippy::cast_sign_loss, clippy::cast_possible_truncation)] fn sub_ts_dur(lhs: TimeSpec, rhs: TimeSpec) -> Duration { @@ -242,6 +243,13 @@ fn get_real_time() -> TimeSpec { rusl::time::clock_get_real_time() } +impl AsRef for Instant { + #[inline] + fn as_ref(&self) -> &TimeSpec { + &self.0 + } +} + #[inline] #[cfg(not(feature = "vdso"))] fn get_monotonic_time() -> TimeSpec { @@ -254,16 +262,91 @@ fn get_real_time() -> TimeSpec { rusl::time::clock_get_real_time() } -impl From for Instant { - #[inline] - fn from(value: TimeSpec) -> Self { - Self(value) +#[cfg(test)] +mod tests { + use super::*; + use core::ops::Add; + #[test] + fn instant_now() { + // We're using monotonic time, Linux implementation is time since boot. + let instant = Instant::now(); + let since_start = instant + .duration_since(Instant(TimeSpec::new_zeroed())) + .unwrap(); + assert!(since_start > Duration::from_secs(1)); + assert!(instant.elapsed().unwrap().as_nanos() > 0); } -} -impl AsRef for Instant { - #[inline] - fn as_ref(&self) -> &TimeSpec { - &self.0 + #[test] + fn system_time_now() { + let system_time = SystemTime::now(); + let since_start = system_time.duration_since_unix_time(); + assert!( + since_start.as_secs() > 1_694_096_772, + "Test has failed or this machine's clock is off" + ); + assert!(system_time.elapsed().unwrap().as_nanos() > 0); + } + + #[test] + fn monotonic_to_instant() { + let now = MonotonicInstant::now(); + let instant = now.as_instant(); + assert_eq!(now.0, instant.0); + let dur = instant.duration_since(instant).unwrap(); + assert_eq!(0, dur.as_secs()); + } + + #[test] + fn instant_arithmetic() { + let now = Instant::now().add(Duration::from_millis(100)).unwrap(); + let before = (now - Duration::from_millis(10)).unwrap(); + let diff = (now - before).unwrap(); + assert_eq!(Duration::from_millis(10), diff); + let back_to_now = (before + diff).unwrap(); + assert_eq!(now, back_to_now); + } + + #[test] + fn system_time_arithmetic() { + let now = SystemTime::now(); + let before = (now - Duration::from_millis(10)).unwrap(); + let diff = (now - before).unwrap(); + assert_eq!(Duration::from_millis(10), diff); + let back_to_now = (before + diff).unwrap(); + assert_eq!(now, back_to_now); + } + + #[test] + fn nano_overflow_adds() { + let overflow_nanos = TimeSpec::new(0, 999_999_999); + let add_by = Duration::from_nanos(2); + let dur = checked_add_dur(overflow_nanos, add_by).unwrap(); + assert_eq!(TimeSpec::new(1, 1), dur); + } + + #[test] + fn nano_underflow_subs() { + let underflow_nanos = TimeSpec::new(1, 0); + let sub_by = Duration::from_nanos(1); + let dur = checked_sub_dur(underflow_nanos, sub_by).unwrap(); + assert_eq!(TimeSpec::new(0, 999_999_999), dur); + } + + #[test] + fn ts_underflow_sub() { + let left = TimeSpec::new(1, 0); + let right = TimeSpec::new(0, 999_999_999); + let res = sub_ts_dur(left, right); + assert_eq!(Duration::from_nanos(1), res); + } + + #[test] + fn ts_underflow_checked() { + let left = TimeSpec::new(1, 0); + let right = TimeSpec::new(0, 999_999_999); + let res = sub_ts_checked_dur(left, right).unwrap(); + assert_eq!(Duration::from_nanos(1), res); + assert!(sub_ts_checked_dur(right, left).is_none()); } } diff --git a/tiny-std/src/unix.rs b/tiny-std/src/unix.rs index f558389..0533752 100644 --- a/tiny-std/src/unix.rs +++ b/tiny-std/src/unix.rs @@ -2,6 +2,7 @@ pub mod fd; pub mod host_name; pub mod misc; pub mod passwd; +pub mod print; pub mod random; #[cfg(feature = "symbols")] mod symbols; diff --git a/tiny-std/src/unix/host_name.rs b/tiny-std/src/unix/host_name.rs index c1f91a6..56e52b3 100644 --- a/tiny-std/src/unix/host_name.rs +++ b/tiny-std/src/unix/host_name.rs @@ -8,3 +8,13 @@ pub fn host_name() -> Result { let raw = rusl::unistd::uname()?; Ok(raw.nodename()?.to_string()) } + +#[cfg(test)] +mod tests { + #[test] + #[cfg(feature = "alloc")] + fn get_host_name() { + let host = crate::unix::host_name::host_name().unwrap(); + assert!(!host.is_empty()); + } +} diff --git a/tiny-std/src/unix/misc/openpty.rs b/tiny-std/src/unix/misc/openpty.rs index 2831fde..f1b59a7 100644 --- a/tiny-std/src/unix/misc/openpty.rs +++ b/tiny-std/src/unix/misc/openpty.rs @@ -1,6 +1,6 @@ use rusl::ioctl::ioctl; use rusl::platform::{Fd, OpenFlags, SetAction, TermioFlags, Termios, WindowSize}; -use rusl::string::unix_str::AsUnixStr; +use rusl::string::unix_str::UnixStr; use rusl::termios::tcsetattr; use rusl::unistd::{open, open_raw}; @@ -15,14 +15,15 @@ pub struct TerminalHandle { /// Not many errors can occur assuming that (`None`, `None`, `None`) is passed and you have /// appropriate permissions. /// See the [linux docs for the exceptions](https://man7.org/linux/man-pages/man2/ioctl_tty.2.html) -pub fn openpty( - name: Option

, +pub fn openpty( + name: Option<&UnixStr>, termios: Option<&Termios>, winsize: Option<&WindowSize>, ) -> crate::error::Result { + const PTMX: &UnixStr = UnixStr::from_str_checked("/dev/ptmx\0"); let use_flags: OpenFlags = OpenFlags::O_RDWR | OpenFlags::O_NOCTTY; unsafe { - let master = open("/dev/ptmx\0", use_flags)?; + let master = open(PTMX, use_flags)?; let mut pty_num = 0; let pty_num_addr = core::ptr::addr_of_mut!(pty_num); // Todo: Maybe check if not zero and bail like musl does diff --git a/tiny-std/src/unix/passwd/getpw_r.rs b/tiny-std/src/unix/passwd/getpw_r.rs index 56db93c..baede31 100644 --- a/tiny-std/src/unix/passwd/getpw_r.rs +++ b/tiny-std/src/unix/passwd/getpw_r.rs @@ -171,6 +171,7 @@ fn find_last_newline(buf: &[u8]) -> Option { #[cfg(test)] mod tests { use rusl::platform::OpenFlags; + use rusl::string::unix_str::UnixStr; use rusl::unistd::open; use crate::unix::passwd::getpw_r::{ @@ -260,7 +261,11 @@ nvidia-persis"; #[test] fn search_pwd_normal_sized_buf() { - let fd = open("test-files/unix/passwd/pwd_test.txt\0", OpenFlags::O_RDONLY).unwrap(); + let fd = open( + UnixStr::try_from_str("test-files/unix/passwd/pwd_test.txt\0").unwrap(), + OpenFlags::O_RDONLY, + ) + .unwrap(); let mut buf = [0u8; 1024]; let pwd = search_pwd_fd(fd, 1000, &mut buf).unwrap().unwrap(); assert_eq!(pwd.name, "gramar"); @@ -274,7 +279,11 @@ nvidia-persis"; #[test] fn search_pwd_small_buf() { - let fd = open("test-files/unix/passwd/pwd_test.txt\0", OpenFlags::O_RDONLY).unwrap(); + let fd = open( + UnixStr::try_from_str("test-files/unix/passwd/pwd_test.txt\0").unwrap(), + OpenFlags::O_RDONLY, + ) + .unwrap(); let mut buf = [0u8; 256]; let pwd = search_pwd_fd(fd, 1000, &mut buf).unwrap().unwrap(); assert_eq!(pwd.name, "gramar"); @@ -288,7 +297,11 @@ nvidia-persis"; #[test] fn last_entry_tiny_buf() { - let fd = open("test-files/unix/passwd/pwd_test.txt\0", OpenFlags::O_RDONLY).unwrap(); + let fd = open( + UnixStr::try_from_str("test-files/unix/passwd/pwd_test.txt\0").unwrap(), + OpenFlags::O_RDONLY, + ) + .unwrap(); let mut buf = [0u8; 100]; let pwd = search_pwd_fd(fd, 110, &mut buf).unwrap().unwrap(); assert_eq!(pwd.name, "partimag"); diff --git a/tiny-std/src/unix/print.rs b/tiny-std/src/unix/print.rs new file mode 100644 index 0000000..013fc84 --- /dev/null +++ b/tiny-std/src/unix/print.rs @@ -0,0 +1,164 @@ +//! Synchronized printing to stdout and stderr +use crate::sync::Mutex; + +pub static __STDOUT_LOCK: Mutex<()> = Mutex::new(()); +pub static __STDERR_LOCK: Mutex<()> = Mutex::new(()); + +use rusl::platform::Fd; + +/// Corresponds to std's `print!`-macro +#[macro_export] +macro_rules! print { + ($($arg:tt)*) => { + { + let __tiny_std_unix_print_writer_guard = $crate::unix::print::__STDOUT_LOCK.lock(); + let mut __tiny_std_unix_print_writer = $crate::unix::print::__STDOUT_WRITER; + let _ = core::fmt::Write::write_fmt(&mut __tiny_std_unix_print_writer, format_args!($($arg)*)); + } + } +} + +/// Corresponds to std's `println!`-macro +#[macro_export] +macro_rules! println { + () => { + { + let __tiny_std_unix_print_writer_guard = $crate::unix::print::__STDOUT_LOCK.lock(); + let __tiny_std_unix_print_writer = $crate::unix::print::__STDOUT_WRITER; + let _ = __tiny_std_unix_print_writer.__write_newline(); + } + }; + ($($arg:tt)*) => { + { + let __tiny_std_unix_print_writer_guard = $crate::unix::print::__STDOUT_LOCK.lock(); + let mut __tiny_std_unix_print_writer = $crate::unix::print::__STDOUT_WRITER; + let _ = core::fmt::Write::write_fmt(&mut __tiny_std_unix_print_writer, format_args!($($arg)*)); + let _ = __tiny_std_unix_print_writer.__write_newline(); + } + } +} + +/// Corresponds to std's `eprint!`-macro +#[macro_export] +macro_rules! eprint { + ($($arg:tt)*) => { + { + let __tiny_std_unix_print_writer_guard = $crate::unix::print::__STDERR_LOCK.lock(); + let mut __tiny_std_unix_print_writer = $crate::unix::print::__STDERR_WRITER; + let _ = core::fmt::Write::write_fmt(&mut __tiny_std_unix_print_writer, format_args!($($arg)*)); + } + } +} + +/// Corresponds to std's `eprintln!`-macro +#[macro_export] +macro_rules! eprintln { + () => { + { + let __tiny_std_unix_print_writer_guard = $crate::unix::print::__STDERR_LOCK.lock(); + let __tiny_std_unix_print_writer = $crate::unix::print::__STDERR_WRITER; + let _ = __tiny_std_unix_print_writer.__write_newline(); + } + }; + ($($arg:tt)*) => { + { + let __tiny_std_unix_print_writer_guard = $crate::unix::print::__STDERR_LOCK.lock(); + let mut __tiny_std_unix_print_writer = $crate::unix::print::__STDERR_WRITER; + let _ = core::fmt::Write::write_fmt(&mut __tiny_std_unix_print_writer, format_args!($($arg)*)); + let _ = __tiny_std_unix_print_writer.__write_newline(); + } + } +} + +/// Corresponds to std's `dbg!`-macro +#[macro_export] +macro_rules! dbg { + () => { + $crate::eprintln!("[{}:{}]", core::file!(), core::line!()) + }; + ($val:expr $(,)?) => { + match $val { + tmp => { + $crate::eprintln!("[{}:{}] {} = {:#?}", + core::file!(), core::line!(), core::stringify!($val), &tmp); + tmp + } + } + }; + ($($val:expr),+ $(,)?) => { + ($($crate::dbg!($val)),+,) + }; +} + +#[allow(clippy::cast_sign_loss)] +fn try_print(fd: Fd, msg: &str) -> core::fmt::Result { + let buf = msg.as_bytes(); + let len = buf.len(); + let mut flushed = 0; + loop { + let res = rusl::unistd::write(fd, &buf[flushed..]).map_err(|_e| core::fmt::Error)?; + match res.cmp(&0) { + core::cmp::Ordering::Less => return Err(core::fmt::Error), + core::cmp::Ordering::Equal => return Ok(()), + core::cmp::Ordering::Greater => { + // Greater than zero + flushed += res as usize; + if flushed >= len { + return Ok(()); + } + } + } + } +} + +pub struct __UnixWriter(Fd); + +pub const __STDOUT_WRITER: __UnixWriter = __UnixWriter(rusl::platform::STDOUT); +pub const __STDERR_WRITER: __UnixWriter = __UnixWriter(rusl::platform::STDERR); +impl __UnixWriter { + /// # Errors + /// Will return an error if the underlying syscall fails + + pub fn __write_newline(&self) -> core::fmt::Result { + try_print(self.0, "\n") + } +} + +impl core::fmt::Write for __UnixWriter { + #[inline] + fn write_str(&mut self, s: &str) -> core::fmt::Result { + try_print(self.0, s) + } +} + +#[cfg(test)] +mod tests { + + #[test] + fn test_prints() { + print!("-- First"); + print!("My first"); + print!(" two messages"); + print!(" were cutoff but it's fine"); + println!(); + println!("-- Second\nHello there {}", "me"); + } + + #[test] + fn test_eprints() { + eprintln!("-- First"); + eprint!("My first"); + eprint!(" two messages"); + eprint!(" were cutoff but it's fine"); + eprintln!(); + eprintln!("-- Second\nHello there {}", "me"); + } + + #[test] + fn test_dbgs() { + dbg!(); + let val = 5; + let res = dbg!(val) - 5; + assert_eq!(0, res); + } +} diff --git a/tiny-std/src/unix/random.rs b/tiny-std/src/unix/random.rs index 0ca3103..ff08871 100644 --- a/tiny-std/src/unix/random.rs +++ b/tiny-std/src/unix/random.rs @@ -2,12 +2,15 @@ use crate::error::Result; use crate::fs::File; use crate::io::Read; use rusl::error::Errno; +use rusl::string::unix_str::UnixStr; + +const DEV_RANDOM: &UnixStr = UnixStr::from_str_checked("/dev/random\0"); /// Fills the provided buffer with random bytes from /dev/random. /// # Errors /// File permissions failure pub fn system_random(buf: &mut [u8]) -> Result<()> { - let mut file = File::open("/dev/random\0")?; + let mut file = File::open(DEV_RANDOM)?; let mut offset = 0; while offset < buf.len() { match file.read(&mut buf[offset..]) { @@ -113,4 +116,11 @@ mod tests { // Sweet determinism assert_eq!(0, count_zero); } + + #[test] + fn prng_seeded_not_same() { + let time_seeded1 = Prng::new_time_seeded(); + let time_seeded2 = Prng::new_time_seeded(); + assert_ne!(time_seeded1.seed, time_seeded2.seed); + } } diff --git a/tiny-std/src/unix/symbols.rs b/tiny-std/src/unix/symbols.rs index 10ecf96..3d10f67 100644 --- a/tiny-std/src/unix/symbols.rs +++ b/tiny-std/src/unix/symbols.rs @@ -20,6 +20,6 @@ pub unsafe extern "C" fn rust_eh_personality() {} #[panic_handler] #[cfg(not(feature = "threaded"))] pub fn on_panic(info: &core::panic::PanicInfo) -> ! { - unix_print::unix_eprintln!("Main thread panicked: {}", info); + crate::eprintln!("Main thread panicked: {}", info); rusl::process::exit(1) }