From c132f9465cc6303f07376c86ac76ee3118a4c32c Mon Sep 17 00:00:00 2001 From: MarcusGrass <34198073+MarcusGrass@users.noreply.github.com> Date: Tue, 10 Oct 2023 21:56:21 +0200 Subject: [PATCH] Add string utilities (#24) --- .cargo/config.toml | 1 + rusl/Cargo.toml | 4 +- rusl/Changelog.md | 4 + rusl/src/lib.rs | 1 + rusl/src/platform/compat/socket.rs | 10 +- rusl/src/platform/compat/stat.rs | 47 +++ rusl/src/string/unix_str.rs | 526 +++++++++++++++++++++++++++-- tiny-std/Changelog.md | 1 + tiny-std/src/fs.rs | 2 +- tiny-std/src/fs/test.rs | 7 +- tiny-std/src/sync.rs | 2 +- tiny-std/src/thread.rs | 2 +- tiny-std/src/time.rs | 7 + tiny-std/src/unix/random.rs | 2 +- 14 files changed, 572 insertions(+), 44 deletions(-) diff --git a/.cargo/config.toml b/.cargo/config.toml index 603d345..63a304a 100644 --- a/.cargo/config.toml +++ b/.cargo/config.toml @@ -2,6 +2,7 @@ rustflags = [ "-Wclippy::all", "-Wclippy::pedantic", + "-Wclippy::mod_module_files", "-Aclippy::cast_lossless", "-Aclippy::cast_possible_truncation", "-Aclippy::cast_sign_loss", diff --git a/rusl/Cargo.toml b/rusl/Cargo.toml index 9debe77..08b6dd8 100644 --- a/rusl/Cargo.toml +++ b/rusl/Cargo.toml @@ -19,4 +19,6 @@ integration-test = [] [dependencies] linux-rust-bindings = { version = "0.1.1", features = ["all"] } -sc = "0.2.7" \ No newline at end of file +sc = "0.2.7" + +[dev-dependencies] \ No newline at end of file diff --git a/rusl/Changelog.md b/rusl/Changelog.md index a839372..06e8039 100644 --- a/rusl/Changelog.md +++ b/rusl/Changelog.md @@ -8,6 +8,10 @@ and this project adheres to [Semantic Versioning](http://semver.org/). ### Fixed ### Added +- Utility methods for `UnixStr` to make it easier to navigate them +as paths +- Find-method for `UnixStr` +- Accessors for some inner fields of `Statx` ### Changed diff --git a/rusl/src/lib.rs b/rusl/src/lib.rs index 4590ba9..0be81db 100644 --- a/rusl/src/lib.rs +++ b/rusl/src/lib.rs @@ -2,6 +2,7 @@ #![cfg_attr(not(test), no_std)] #![warn(clippy::pedantic)] #![allow(clippy::module_name_repetitions, clippy::similar_names)] +#![cfg_attr(test, allow(clippy::ignored_unit_patterns))] #[cfg(feature = "alloc")] extern crate alloc; diff --git a/rusl/src/platform/compat/socket.rs b/rusl/src/platform/compat/socket.rs index 5856a8e..949c818 100644 --- a/rusl/src/platform/compat/socket.rs +++ b/rusl/src/platform/compat/socket.rs @@ -124,14 +124,20 @@ impl SocketAddress { /// Tries to construct a `SocketAddress` from a `UnixStr` path /// # Errors - /// The path is longer than 108 bytes (null termination included) + /// The path is longer than 108 bytes (null termination included). + /// The path contains byte values out of the 7-bit ASCII range. pub fn try_from_unix(path: &UnixStr) -> crate::Result { let mut ind = 0; let buf = unsafe { let mut buf = [0; 108]; let mut ptr = path.as_ptr(); while !ptr.is_null() { - buf[ind] = ptr.read() as core::ffi::c_char; + let val = core::ffi::c_char::try_from(ptr.read()).map_err(|_e| { + Error::no_code( + "Socket paths need to be 7-bit ASCII, path contained value in 8-bit range", + ) + })?; + buf[ind] = val; if ind == 107 && buf[ind] != 0 { return Err(Error::no_code("Socket address too long")); } else if buf[ind] == 0 { diff --git a/rusl/src/platform/compat/stat.rs b/rusl/src/platform/compat/stat.rs index 1c77eb6..67a9ad7 100644 --- a/rusl/src/platform/compat/stat.rs +++ b/rusl/src/platform/compat/stat.rs @@ -1,4 +1,5 @@ use crate::platform::numbers::NonNegativeI32; +use crate::platform::TimeSpec; pub type Stat = linux_rust_bindings::stat::stat; @@ -6,6 +7,52 @@ pub type Stat = linux_rust_bindings::stat::stat; #[derive(Debug)] pub struct Statx(pub(crate) linux_rust_bindings::stat::statx); +impl Statx { + #[inline] + #[must_use] + pub const fn mask(&self) -> StatxMask { + StatxMask(self.0.stx_mask) + } + + #[inline] + #[must_use] + pub const fn size(&self) -> u64 { + self.0.stx_size + } + + /// Last access + #[inline] + #[must_use] + pub const fn access_time(&self) -> TimeSpec { + let ts = self.0.stx_atime; + TimeSpec::new(ts.tv_sec, ts.tv_nsec as i64) + } + + /// Creation + #[inline] + #[must_use] + pub const fn birth_time(&self) -> TimeSpec { + let ts = self.0.stx_btime; + TimeSpec::new(ts.tv_sec, ts.tv_nsec as i64) + } + + /// Content modification + #[inline] + #[must_use] + pub const fn modified_time(&self) -> TimeSpec { + let ts = self.0.stx_mtime; + TimeSpec::new(ts.tv_sec, ts.tv_nsec as i64) + } + + /// Metadata modification + #[inline] + #[must_use] + pub const fn changed_time(&self) -> TimeSpec { + let ts = self.0.stx_ctime; + TimeSpec::new(ts.tv_sec, ts.tv_nsec as i64) + } +} + transparent_bitflags! { pub struct StatxMask: u32 { const DEFAULT = 0; diff --git a/rusl/src/string/unix_str.rs b/rusl/src/string/unix_str.rs index 7eafca5..aa35b10 100644 --- a/rusl/src/string/unix_str.rs +++ b/rusl/src/string/unix_str.rs @@ -88,6 +88,31 @@ impl UnixString { pub fn try_from_str(s: &str) -> Result { Self::try_from_bytes(s.as_bytes()) } + + /// A slightly less efficient that creating a format string way of + /// creating a [`UnixString`], since it'll in all likelihood lead to two allocations. + /// Can't do much since fmt internals are feature gated in `core`. + /// Still a bit more ergonomic than creating a format-String and then creating a [`UnixString`] from that. + /// More efficient if a null byte is added to the format strings. + /// # Example + /// ``` + /// use rusl::string::unix_str::UnixString; + /// fn create_format_unix_string() { + /// let ins_with = "gramar"; + /// let good = UnixString::from_format(format_args!("/home/{ins_with}/code")); + /// assert_eq!("/home/gramar/code", good.as_str().unwrap()); + /// let great = UnixString::from_format(format_args!("/home/{ins_with}/code\0")); + /// assert_eq!("/home/gramar/code", great.as_str().unwrap()); + /// } + /// ``` + #[must_use] + pub fn from_format(args: core::fmt::Arguments<'_>) -> Self { + let mut fmt_str_buf = alloc::fmt::format(args).into_bytes(); + if !matches!(fmt_str_buf.last(), Some(&NULL_BYTE)) { + fmt_str_buf.push(NULL_BYTE); + } + UnixString(fmt_str_buf) + } } #[cfg(feature = "alloc")] @@ -247,6 +272,232 @@ impl UnixStr { } } } + + #[must_use] + pub fn find(&self, other: &Self) -> Option { + if other.len() > self.len() { + return None; + } + let this_buf = &self.0; + let other_buf = &other.0[..other.0.len() - 2]; + buf_find(this_buf, other_buf) + } + + #[must_use] + pub fn find_buf(&self, other: &[u8]) -> Option { + if other.len() > self.len() { + return None; + } + let this_buf = &self.0; + buf_find(this_buf, other) + } + + #[must_use] + pub fn ends_with(&self, other: &Self) -> bool { + if other.len() > self.len() { + return false; + } + let mut ind = 0; + while let (Some(this), Some(that)) = ( + self.0.get(self.0.len() - 1 - ind), + other.0.get(other.0.len() - 1 - ind), + ) { + if this != that { + return false; + } + if other.0.len() - 1 - ind == 0 { + return true; + } + ind += 1; + } + true + } + + /// Joins this [`UnixStr`] with some other [`UnixStr`] adding a slash if necessary. + /// Will make sure that there's at most one slash at the boundary but won't check + /// either string for "path validity" in any other case + /// # Example + /// ``` + /// use rusl::string::unix_str::UnixStr; + /// fn join_paths() { + /// // Combines slash + /// let a = UnixStr::try_from_str("path/").unwrap(); + /// let b = UnixStr::try_from_str("/ext").unwrap(); + /// let combined = a.path_join(b); + /// assert_eq!("path/ext", combined.as_str().unwrap()); + /// // Adds slash + /// let combined_other_way = b.path_join(a); + /// assert_eq!("/ext/path/", combined_other_way.as_str().unwrap()); + /// // Doesn't truncate other slashes, only works at the boundary between the two paths + /// let a = UnixStr::try_from_str("path//").unwrap(); + /// let combined_many_slashes = a.path_join(b); + /// assert_eq!("path//ext", combined_many_slashes.as_str().unwrap()); + /// } + /// ``` + #[must_use] + #[cfg(feature = "alloc")] + pub fn path_join(&self, ext: &Self) -> UnixString { + let mut as_string = self.0.to_vec(); + as_string.pop(); + let Some(last) = as_string.last().copied() else { + return UnixString::from(ext); + }; + if ext.len() == 1 { + return UnixString::from(self); + } + as_string.reserve(ext.len()); + let buf = if last == b'/' { + unsafe { + if ext.0.get_unchecked(0) == &b'/' { + as_string.extend_from_slice(ext.0.get_unchecked(1..)); + } else { + as_string.extend_from_slice(&ext.0); + } + } + as_string + } else if unsafe { ext.0.get_unchecked(0) == &b'/' } { + as_string.extend_from_slice(&ext.0); + as_string + } else { + as_string.push(b'/'); + as_string.extend_from_slice(&ext.0); + as_string + }; + UnixString(buf) + } + + /// Joins this [`UnixStr`] with some format string adding a slash if necessary. + /// Follows the same rules as [`UnixStr::path_join`]. + /// # Example + /// ``` + /// use rusl::string::unix_str::UnixStr; + /// fn join_paths() { + /// // Combines slash + /// let a = UnixStr::try_from_str("path/").unwrap(); + /// let combined = a.path_join_fmt(format_args!("ext")); + /// assert_eq!("path/ext", combined.as_str().unwrap()); + /// } + /// ``` + #[must_use] + #[cfg(feature = "alloc")] + pub fn path_join_fmt(&self, args: core::fmt::Arguments<'_>) -> UnixString { + let container = alloc::fmt::format(args); + if container.is_empty() { + return UnixString::from(self); + } + let mut container_vec = container.into_bytes(); + let mut as_string = self.0.to_vec(); + as_string.pop(); + let Some(last) = as_string.last().copied() else { + if !matches!(container_vec.last().copied(), Some(NULL_BYTE)) { + container_vec.push(NULL_BYTE); + } + return UnixString(container_vec); + }; + if last == b'/' { + as_string.reserve(container_vec.len() + 1); + let start_from = if let Some(b'/') = container_vec.first().copied() { + 1 + } else { + 0 + }; + if let Some(add_slice) = container_vec.get(start_from..) { + as_string.extend_from_slice(add_slice); + } + if !matches!(as_string.last().copied(), Some(NULL_BYTE)) { + as_string.push(NULL_BYTE); + } + } else if let Some(b'/') = container_vec.first().copied() { + as_string.extend(container_vec); + if !matches!(as_string.last().copied(), Some(NULL_BYTE)) { + as_string.push(NULL_BYTE); + } + } else { + as_string.push(b'/'); + as_string.extend(container_vec); + if !matches!(as_string.last().copied(), Some(NULL_BYTE)) { + as_string.push(NULL_BYTE); + } + } + UnixString(as_string) + } + + /// Treats this [`UnixStr`] as a path, then tries to find its parent. + /// Will treat any double slash as a path with no parent + /// # Example + /// ``` + /// use rusl::string::unix_str::UnixStr; + /// fn find_parent() { + /// let well_formed = UnixStr::try_from_str("/home/gramar/code/").unwrap(); + /// let up_one = well_formed.parent_path().unwrap(); + /// assert_eq!("/home/gramar", up_one.as_str().unwrap()); + /// let up_two = up_one.parent_path().unwrap(); + /// assert_eq!("/home", up_two.as_str().unwrap()); + /// let up_three = up_two.parent_path().unwrap(); + /// assert_eq!("/", up_three.as_str().unwrap()); + /// assert!(up_three.parent_path().is_none()); + /// let ill_formed = UnixStr::try_from_str("/home/gramar/code//").unwrap(); + /// assert!(ill_formed.parent_path().is_none()); + /// } + /// ``` + #[must_use] + #[cfg(feature = "alloc")] + pub fn parent_path(&self) -> Option { + let len = self.0.len(); + // Can't be len 0, len 1 is only a null byte, len 2 is a single char, parent becomes none + if len < 3 { + return None; + } + let last = self.0.len() - 2; + let mut next_slash_back = last; + while let Some(byte) = self.0.get(next_slash_back).copied() { + if byte == b'/' { + if next_slash_back != 0 { + if let Some(b'/') = self.0.get(next_slash_back - 1) { + return None; + } + } + break; + } + if next_slash_back == 0 { + return None; + } + next_slash_back -= 1; + } + // Found slash at root, we want to include it in output then giving only the root path + if next_slash_back == 0 { + next_slash_back += 1; + } + unsafe { + Some(UnixString( + self.0.get_unchecked(..=next_slash_back).to_vec(), + )) + } + } +} + +#[inline] +#[allow(clippy::needless_range_loop)] +fn buf_find(this_buf: &[u8], other_buf: &[u8]) -> Option { + for i in 0..this_buf.len() { + if this_buf[i] == other_buf[0] { + let mut no_match = false; + for j in 1..other_buf.len() { + if let Some(this) = this_buf.get(i + j) { + if *this != other_buf[j] { + no_match = true; + break; + } + } else { + return None; + } + } + if !no_match { + return Some(i); + } + } + } + None } impl<'a> Debug for &'a UnixStr { @@ -319,38 +570,6 @@ mod tests { 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() { @@ -378,4 +597,247 @@ mod tests { const MY_CMP_STR: &str = "my-nice-str"; assert_eq!(MY_CMP_STR.len(), UNIX_STR.match_up_to_str(MY_CMP_STR)); } + + #[test] + fn can_check_ends_with() { + const UNIX_STR: &UnixStr = UnixStr::from_str_checked("my-nice-str\0"); + assert!(UNIX_STR.ends_with(UnixStr::from_str_checked("-str\0"))); + } + + #[test] + fn can_check_ends_with_self() { + const UNIX_STR: &UnixStr = UnixStr::from_str_checked("my-nice-str\0"); + assert!(UNIX_STR.ends_with(UNIX_STR)); + } + + #[test] + fn can_check_ends_with_empty() { + const UNIX_STR: &UnixStr = UnixStr::from_str_checked("my-nice-str\0"); + assert!(UNIX_STR.ends_with(UnixStr::EMPTY)); + } + + #[test] + fn can_check_ends_with_no() { + const UNIX_STR: &UnixStr = UnixStr::from_str_checked("my-nice-str\0"); + assert!(!UNIX_STR.ends_with(UnixStr::from_str_checked("nice-\0"))); + } + + #[test] + fn can_check_ends_with_no_too_long() { + const UNIX_STR: &UnixStr = UnixStr::from_str_checked("my-nice-str\0"); + assert!(!UNIX_STR.ends_with(UnixStr::from_str_checked("other-my-nice-str\0"))); + } + + #[test] + fn can_find_at_end() { + const UNIX_STR: &UnixStr = UnixStr::from_str_checked("my-nice-str\0"); + let found_at = UNIX_STR.find(UnixStr::from_str_checked("-str\0")).unwrap(); + assert_eq!(7, found_at); + } + + #[test] + fn can_find_finds_first() { + const UNIX_STR: &UnixStr = UnixStr::from_str_checked("str-str-str\0"); + let found_at = UNIX_STR.find(UnixStr::from_str_checked("-str\0")).unwrap(); + assert_eq!(3, found_at); + let found_at = UNIX_STR.find_buf("-str".as_bytes()).unwrap(); + assert_eq!(3, found_at); + } + + #[test] + fn can_find_at_middle() { + const UNIX_STR: &UnixStr = UnixStr::from_str_checked("my-nice-str\0"); + let found_at = UNIX_STR.find(UnixStr::from_str_checked("-nice\0")).unwrap(); + assert_eq!(2, found_at); + let found_at = UNIX_STR.find_buf("-nice".as_bytes()).unwrap(); + assert_eq!(2, found_at); + } + + #[test] + fn can_find_at_start() { + const UNIX_STR: &UnixStr = UnixStr::from_str_checked("my-nice-str\0"); + let found_at = UNIX_STR.find(UnixStr::from_str_checked("my\0")).unwrap(); + assert_eq!(0, found_at); + let found_at = UNIX_STR.find_buf("my".as_bytes()).unwrap(); + assert_eq!(0, found_at); + } + + #[test] + fn can_find_no_match() { + const UNIX_STR: &UnixStr = UnixStr::from_str_checked("my-nice-str\0"); + let found_at = UNIX_STR.find(UnixStr::from_str_checked("cake\0")); + assert!(found_at.is_none()); + let found_at = UNIX_STR.find_buf("cake".as_bytes()); + assert!(found_at.is_none()); + } + + #[test] + fn can_find_too_long() { + const UNIX_STR: &UnixStr = UnixStr::from_str_checked("str\0"); + let found_at = UNIX_STR.find(UnixStr::from_str_checked("sstr\0")); + assert!(found_at.is_none()); + + let found_at = UNIX_STR.find_buf("sstr".as_bytes()); + assert!(found_at.is_none()); + } + + #[cfg(feature = "alloc")] + mod alloc_tests { + use super::*; + use alloc::string::ToString; + #[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_path_join() { + let a = UnixStr::from_str_checked("hello\0"); + let b = UnixStr::from_str_checked("there\0"); + let new = a.path_join(b); + assert_eq!("hello/there", new.as_str().unwrap()); + } + + #[test] + fn can_path_join_fmt() { + let a = UnixStr::from_str_checked("hello\0"); + let new = a.path_join_fmt(format_args!("there")); + assert_eq!("hello/there", new.as_str().unwrap()); + } + + #[test] + fn can_path_join_with_trailing_slash() { + let a = UnixStr::from_str_checked("hello/\0"); + let b = UnixStr::from_str_checked("there\0"); + let new = a.path_join(b); + assert_eq!("hello/there", new.as_str().unwrap()); + } + + #[test] + fn can_path_join_fmt_with_trailing_slash() { + let a = UnixStr::from_str_checked("hello/\0"); + let new = a.path_join_fmt(format_args!("there")); + assert_eq!("hello/there", new.as_str().unwrap()); + } + #[test] + fn can_path_join_with_leading_slash() { + let a = UnixStr::from_str_checked("hello\0"); + let b = UnixStr::from_str_checked("/there\0"); + let new = a.path_join(b); + assert_eq!("hello/there", new.as_str().unwrap()); + } + + #[test] + fn can_path_join_fmt_with_leading_slash() { + let a = UnixStr::from_str_checked("hello\0"); + let new = a.path_join_fmt(format_args!("/there")); + assert_eq!("hello/there", new.as_str().unwrap()); + } + + #[test] + fn can_path_join_empty() { + let a = UnixStr::from_str_checked("\0"); + let b = UnixStr::from_str_checked("/there\0"); + let new = a.path_join(b); + assert_eq!("/there", new.as_str().unwrap()); + let new = b.path_join(a); + assert_eq!("/there", new.as_str().unwrap()); + } + + #[test] + fn can_path_join_fmt_empty() { + let a = UnixStr::from_str_checked("\0"); + let b = UnixStr::from_str_checked("/there\0"); + let new = a.path_join_fmt(format_args!("/there")); + assert_eq!("/there", new.as_str().unwrap()); + let new = b.path_join_fmt(format_args!("")); + assert_eq!("/there", new.as_str().unwrap()); + } + + #[test] + fn can_path_join_truncates_slashes() { + let a = UnixStr::from_str_checked("hello/\0"); + let b = UnixStr::from_str_checked("/there\0"); + let new = a.path_join(b); + assert_eq!("hello/there", new.as_str().unwrap()); + } + + #[test] + fn find_parent_path_happy() { + let a = UnixStr::from_str_checked("hello/there/friend\0"); + let parent = a.parent_path().unwrap(); + assert_eq!("hello/there", parent.as_str().unwrap()); + let b = UnixStr::from_str_checked("/home/gramar/code/rust/tiny-std\0"); + let b_first_parent = b.parent_path().unwrap(); + assert_eq!("/home/gramar/code/rust", b_first_parent.as_str().unwrap()); + let b_second_parent = b_first_parent.parent_path().unwrap(); + assert_eq!("/home/gramar/code", b_second_parent.as_str().unwrap()); + let b_third_parent = b_second_parent.parent_path().unwrap(); + assert_eq!("/home/gramar", b_third_parent.as_str().unwrap()); + let b_fourth_parent = b_third_parent.parent_path().unwrap(); + assert_eq!("/home", b_fourth_parent.as_str().unwrap()); + let root = b_fourth_parent.parent_path().unwrap(); + assert_eq!("/", root.as_str().unwrap()); + } + + #[test] + fn find_parent_path_empty_no_parent() { + let a = UnixStr::EMPTY; + let parent = a.parent_path(); + assert!(parent.is_none()); + } + + #[test] + fn find_parent_path_short_no_parent() { + let a = UnixStr::from_str_checked("/\0"); + let parent = a.parent_path(); + assert!(parent.is_none()); + let b = UnixStr::from_str_checked("a\0"); + let parent = b.parent_path(); + assert!(parent.is_none()); + } + + #[test] + fn find_parent_path_short_has_parent() { + let a = UnixStr::from_str_checked("/a\0"); + let parent = a.parent_path().unwrap(); + assert_eq!("/", parent.as_str().unwrap()); + } + + #[test] + fn find_parent_path_double_slash_invalid() { + let a = UnixStr::from_str_checked("//\0"); + let parent = a.parent_path(); + assert!(parent.is_none()); + let a = UnixStr::from_str_checked("hello//\0"); + let parent = a.parent_path(); + assert!(parent.is_none()); + } + + #[test] + 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); + } + } } diff --git a/tiny-std/Changelog.md b/tiny-std/Changelog.md index d37f9b0..755e6c7 100644 --- a/tiny-std/Changelog.md +++ b/tiny-std/Changelog.md @@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/). ### Added ### Changed +- Implement `From` for `SystemTime` ## [v0.2.2] - 2023-10-01 diff --git a/tiny-std/src/fs.rs b/tiny-std/src/fs.rs index 3b63ce7..04da4e0 100644 --- a/tiny-std/src/fs.rs +++ b/tiny-std/src/fs.rs @@ -342,7 +342,7 @@ unsafe fn write_all_sub_paths( Mode::from(0o755), ) { // Successfully wrote, traverse down - Ok(_) => { + Ok(()) => { // Replace the null byte to make a valid path concatenation buf[ind] = b'/'; for i in ind + 1..len { diff --git a/tiny-std/src/fs/test.rs b/tiny-std/src/fs/test.rs index d4541d2..c458e82 100644 --- a/tiny-std/src/fs/test.rs +++ b/tiny-std/src/fs/test.rs @@ -120,7 +120,7 @@ fn can_create_and_delete_dir() { let dir_meta = metadata(tgt).unwrap(); assert!(dir_meta.is_dir()); match crate::fs::create_dir(tgt) { - Ok(_) => panic!("Could create on already existing dir"), + Ok(()) => panic!("Could create on already existing dir"), Err(e) => { assert!(e.matches_errno(Errno::EEXIST)); } @@ -188,10 +188,7 @@ fn create_read_and_delete_dir_with_a_lot_of_files() { let mut found_entries = 0; for entry in it { let entry = entry.unwrap(); - let null_term = entry.file_unix_name().unwrap(); - if null_term != unsafe { UnixStr::from_str_unchecked(".\0") } - && null_term != unsafe { UnixStr::from_str_unchecked("..\0") } - { + if !entry.is_relative_reference() { assert_eq!(FileType::RegularFile, entry.file_type()); let file_name = entry.file_name().unwrap(); let mut num = String::new(); diff --git a/tiny-std/src/sync.rs b/tiny-std/src/sync.rs index 6e9138a..6f8f1e9 100644 --- a/tiny-std/src/sync.rs +++ b/tiny-std/src/sync.rs @@ -28,7 +28,7 @@ pub(crate) fn futex_wait_fast(futex: &AtomicU32, expect: u32) { return; } match futex_wait(futex, expect, FutexFlags::PRIVATE, None) { - Ok(_) => { + Ok(()) => { return; } Err(e) => { diff --git a/tiny-std/src/thread.rs b/tiny-std/src/thread.rs index e945266..8389435 100644 --- a/tiny-std/src/thread.rs +++ b/tiny-std/src/thread.rs @@ -16,7 +16,7 @@ pub fn sleep(duration: Duration) -> Result<()> { let mut ts = duration.try_into()?; loop { match rusl::time::nanosleep_same_ptr(&mut ts) { - Ok(_) => return Ok(()), + Ok(()) => return Ok(()), Err(ref e) if e.code == Some(Errno::EINTR) => { continue; } diff --git a/tiny-std/src/time.rs b/tiny-std/src/time.rs index 16529d4..4d310fb 100644 --- a/tiny-std/src/time.rs +++ b/tiny-std/src/time.rs @@ -143,6 +143,13 @@ impl core::ops::Sub for SystemTime { } } +impl From for SystemTime { + #[inline] + fn from(value: TimeSpec) -> Self { + Self(value) + } +} + #[inline] fn checked_add_dur(timespec: TimeSpec, duration: Duration) -> Option { // tv_nsec are < `NANOS_A_SECOND`, this cannot overflow diff --git a/tiny-std/src/unix/random.rs b/tiny-std/src/unix/random.rs index ff08871..fa9aa69 100644 --- a/tiny-std/src/unix/random.rs +++ b/tiny-std/src/unix/random.rs @@ -45,7 +45,7 @@ impl Prng { const A: u128 = 25_214_903_917; const C: u128 = 11; - /// Create a new Prng-instance seeding with a `MonotonicInstant`, this is fine because + /// Create a new Prng-instance seeding with a [`crate::time::MonotonicInstant`], this is fine because /// you shouldn't need a secure seed anyway, because you should not use this for /// security-purposes at all. #[must_use]