Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions core/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,13 @@ and this project adheres to [Semantic Versioning](http://semver.org/).

-

## [v0.1.1](https://github.com/trussed-dev/littlefs2/releases/tag/core-0.1.1) - 2025-01-16

- Make `Path` and `PathBuf` more const-friendly:
- Make `Path::as_ptr` and `PathBuf::from_buffer_unchecked` const.
- Add const `Path::const_eq`, `PathBuf::from_path`, `PathBuf::as_path` and `PathBuf::as_str` methods.
- Add `path_buf` macro to construct a `PathBuf` from a string literal.

## [v0.1.0](https://github.com/trussed-dev/littlefs2/releases/tag/core-0.1.0) - 2024-10-17

Initial release with the core types from `littlefs2`.
2 changes: 1 addition & 1 deletion core/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "littlefs2-core"
version = "0.1.0"
version = "0.1.1"
authors = ["The Trussed developers"]
description = "Core types for the littlefs2 crate"

Expand Down
44 changes: 44 additions & 0 deletions core/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -59,3 +59,47 @@ macro_rules! path {
_PATH
}};
}

/// Creates an owned path from a string without a trailing null.
///
/// Panics and causes a compiler error if the string contains null bytes or non-ascii characters.
///
/// # Examples
///
/// ```
/// use littlefs2_core::{path_buf, PathBuf};
///
/// const HOME: PathBuf = path_buf!("/home");
/// let root = path_buf!("/");
/// ```
///
/// Illegal values:
///
/// ```compile_fail
/// # use littlefs2_core::{path_buf, PathBuf};
/// const WITH_NULL: PathBuf = path_buf!("/h\0me"); // does not compile
/// ```
///
/// ```compile_fail
/// # use littlefs2_core::{path_buf, PathBuf};
/// const WITH_UTF8: PathBuf = path_buf!("/höme"); // does not compile
/// ```
///
/// The macro enforces const evaluation so that compilation fails for illegal values even if the
/// macro is not used in a const context:
///
/// ```compile_fail
/// # use littlefs2_core::path_buf;
/// let path = path_buf!("te\0st"); // does not compile
/// ```
#[macro_export]
macro_rules! path_buf {
($path:literal) => {{
const _PATH: $crate::PathBuf =
match $crate::Path::from_str_with_nul(::core::concat!($path, "\0")) {
Ok(path) => $crate::PathBuf::from_path(path),
Err(_) => panic!("invalid littlefs2 path"),
};
_PATH
}};
Comment on lines +98 to +104
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it purposefully not using an inline const block?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, I think the workaround is simple enough to avoid bumping the MSRV just for inline const blocks.

}
89 changes: 81 additions & 8 deletions core/src/path.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,40 @@ pub struct Path {
}

impl Path {
/// Checks two paths for equality.
///
/// This provides an easy way to check paths in a const context.
///
/// # Example
///
/// ```
/// # use littlefs2_core::{Path, path};
/// const fn check(path: &Path) -> bool {
/// !path.const_eq(path!("forbidden-path"))
/// }
///
/// assert!(check(path!("allowed-path")));
/// assert!(!check(path!("forbidden-path")));
/// ```
pub const fn const_eq(&self, path: &Path) -> bool {
let a = self.inner.to_bytes();
let b = path.inner.to_bytes();

if a.len() != b.len() {
return false;
}

let mut i = 0;
while i < a.len() {
if a[i] != b[i] {
return false;
}
i += 1;
}

true
}

/// Compare the path using their string representation
/// This comarison function as would be expected for a `String` type.
///
Expand Down Expand Up @@ -281,7 +315,7 @@ impl Path {
}

/// Returns the inner pointer to this C string.
pub fn as_ptr(&self) -> *const c_char {
pub const fn as_ptr(&self) -> *const c_char {
self.inner.as_ptr()
}

Expand Down Expand Up @@ -408,7 +442,7 @@ pub struct PathBuf {

/// # Safety
/// `s` must point to valid memory; `s` will be treated as a null terminated string
unsafe fn strlen(mut s: *const c_char) -> usize {
const unsafe fn strlen(mut s: *const c_char) -> usize {
let mut n = 0;
while *s != 0 {
s = s.add(1);
Expand All @@ -434,6 +468,49 @@ impl PathBuf {
}
}

/// Creates a `PathBuf` from a `Path`.
///
/// This method is a const-friendly version of the `From<&Path>` implementation. If you don’t
/// need a const method, prefer `From<&Path>` as it is more idiomatic and more efficient.
///
/// The [`path_buf`][`crate::path_buf`] macro can be used instead to construct a `PathBuf` from
/// a string literal.
///
/// # Example
///
/// ```
/// # use littlefs2_core::{path, path_buf, PathBuf};
/// const PATH: PathBuf = PathBuf::from_path(path!("test"));
/// assert_eq!(PATH, path_buf!("test"));
/// ```
pub const fn from_path(path: &Path) -> Self {
let bytes = path.inner.to_bytes();

let mut buf = [0; Self::MAX_SIZE_PLUS_ONE];
let len = bytes.len();
assert!(len < Self::MAX_SIZE_PLUS_ONE);

let mut i = 0;
while i < len {
buf[i] = bytes[i] as _;
i += 1;
}

Self { buf, len: len + 1 }
}

pub const fn as_path(&self) -> &Path {
unsafe {
let bytes = slice::from_raw_parts(self.buf.as_ptr().cast(), self.len);
let cstr = CStr::from_bytes_with_nul_unchecked(bytes);
Path::from_cstr_unchecked(cstr)
}
}

pub const fn as_str(&self) -> &str {
self.as_path().as_str()
}

pub fn clear(&mut self) {
self.buf = [0; Self::MAX_SIZE_PLUS_ONE];
self.len = 1;
Expand All @@ -444,7 +521,7 @@ impl PathBuf {
/// # Safety
///
/// The buffer must contain only ASCII characters and at least one null byte.
pub unsafe fn from_buffer_unchecked(buf: [c_char; Self::MAX_SIZE_PLUS_ONE]) -> Self {
pub const unsafe fn from_buffer_unchecked(buf: [c_char; Self::MAX_SIZE_PLUS_ONE]) -> Self {
let len = strlen(buf.as_ptr()) + 1 /* null byte */;
PathBuf { buf, len }
}
Expand Down Expand Up @@ -559,11 +636,7 @@ impl ops::Deref for PathBuf {
type Target = Path;

fn deref(&self) -> &Path {
unsafe {
let bytes = slice::from_raw_parts(self.buf.as_ptr().cast(), self.len);
let cstr = CStr::from_bytes_with_nul_unchecked(bytes);
Path::from_cstr_unchecked(cstr)
}
self.as_path()
}
}

Expand Down
Loading