ext4_view/
path.rs

1// Copyright 2024 Google LLC
2//
3// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
4// https://www.apache.org/licenses/LICENSE-2.0> or the MIT license
5// <LICENSE-MIT or https://opensource.org/licenses/MIT>, at your
6// option. This file may not be copied, modified, or distributed
7// except according to those terms.
8
9use crate::dir_entry::{DirEntryName, DirEntryNameError};
10use crate::format::{BytesDisplay, format_bytes_debug};
11use alloc::string::String;
12use alloc::vec::Vec;
13use core::error::Error;
14use core::fmt::{self, Debug, Display, Formatter};
15use core::str::{self, Utf8Error};
16
17/// Error returned when [`Path`] or [`PathBuf`] construction fails.
18#[derive(Clone, Debug, Eq, PartialEq)]
19#[non_exhaustive]
20pub enum PathError {
21    /// Path contains a component longer than 255 bytes.
22    ComponentTooLong,
23
24    /// Path contains a null byte.
25    ContainsNull,
26
27    /// Path cannot be created due to encoding.
28    ///
29    /// This error only occurs on non-Unix targets, where
30    /// [`std::os::unix::ffi::OsStrExt`] is not available. On non-Unix
31    /// targets, converting an [`OsStr`] or [`std::path::Path`] to
32    /// [`ext4_view::Path`] requires first converting the input to a
33    /// `&str`, which will fail if the input is not valid UTF-8.
34    ///
35    /// [`OsStr`]: std::ffi::OsStr
36    /// [`ext4_view::Path`]: Path
37    Encoding,
38}
39
40impl Display for PathError {
41    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
42        match self {
43            Self::ComponentTooLong => {
44                write!(f, "path contains a component longer than 255 bytes")
45            }
46            Self::ContainsNull => write!(f, "path contains a null byte"),
47            Self::Encoding => {
48                write!(f, "path cannot be created due to encoding")
49            }
50        }
51    }
52}
53
54impl Error for PathError {}
55
56/// Reference path type.
57///
58/// Paths are mostly arbitrary sequences of bytes, with two restrictions:
59/// * The path cannot contain any null bytes.
60/// * Each component of the path must be no longer than 255 bytes.
61#[derive(Clone, Copy, Eq, Ord, PartialOrd, Hash)]
62pub struct Path<'a>(
63    // Use `&[u8]` rather than `[u8]` so that we don't have to use any
64    // unsafe code. Unfortunately that means we can't impl `Deref` to
65    // convert from `PathBuf` to `Path`.
66    &'a [u8],
67);
68
69impl<'a> Path<'a> {
70    /// Unix path separator.
71    pub const SEPARATOR: u8 = b'/';
72
73    /// Root path, equivalent to `/`.
74    pub const ROOT: Path<'static> = Path(&[Self::SEPARATOR]);
75
76    /// Create a new `Path`.
77    ///
78    /// This panics if the input is invalid, use [`Path::try_from`] if
79    /// error handling is desired.
80    ///
81    /// # Panics
82    ///
83    /// Panics if the path contains any null bytes or if a component of
84    /// the path is longer than 255 bytes.
85    #[track_caller]
86    pub fn new<P>(p: &'a P) -> Self
87    where
88        P: AsRef<[u8]> + ?Sized,
89    {
90        Self::try_from(p.as_ref()).unwrap()
91    }
92
93    /// Get whether the path is absolute (starts with `/`).
94    #[must_use]
95    pub fn is_absolute(self) -> bool {
96        if self.0.is_empty() {
97            false
98        } else {
99            self.0[0] == Self::SEPARATOR
100        }
101    }
102
103    /// Get an object that implements [`Display`] to allow conveniently
104    /// printing paths that may or may not be valid UTF-8. Non-UTF-8
105    /// characters will be replaced with '�'.
106    ///
107    /// [`Display`]: core::fmt::Display
108    pub fn display(self) -> BytesDisplay<'a> {
109        BytesDisplay(self.0)
110    }
111
112    /// Create a new `PathBuf` joining `self` with `path`.
113    ///
114    /// This will add a separator if needed. Note that if the argument
115    /// is an absolute path, the returned value will be equal to `path`.
116    ///
117    /// # Panics
118    ///
119    /// Panics if the argument is not a valid path.
120    #[must_use]
121    pub fn join(self, path: impl AsRef<[u8]>) -> PathBuf {
122        PathBuf::from(self).join(path)
123    }
124
125    /// Get an iterator over each [`Component`] in the path.
126    #[must_use]
127    pub fn components(self) -> Components<'a> {
128        Components {
129            path: self,
130            offset: 0,
131        }
132    }
133
134    /// Convert to a `&str` if the path is valid UTF-8.
135    pub fn to_str(self) -> Result<&'a str, Utf8Error> {
136        str::from_utf8(self.0)
137    }
138}
139
140impl<'a> AsRef<[u8]> for Path<'a> {
141    fn as_ref(&self) -> &'a [u8] {
142        self.0
143    }
144}
145
146impl Debug for Path<'_> {
147    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
148        format_bytes_debug(self.0, f)
149    }
150}
151
152impl<'a> TryFrom<&'a str> for Path<'a> {
153    type Error = PathError;
154
155    fn try_from(s: &'a str) -> Result<Self, PathError> {
156        Self::try_from(s.as_bytes())
157    }
158}
159
160impl<'a> TryFrom<&'a String> for Path<'a> {
161    type Error = PathError;
162
163    fn try_from(s: &'a String) -> Result<Self, PathError> {
164        Self::try_from(s.as_bytes())
165    }
166}
167
168impl<'a> TryFrom<&'a [u8]> for Path<'a> {
169    type Error = PathError;
170
171    fn try_from(s: &'a [u8]) -> Result<Self, PathError> {
172        if s.contains(&0) {
173            return Err(PathError::ContainsNull);
174        }
175
176        for component in s.split(|b| *b == Path::SEPARATOR) {
177            if component.len() > DirEntryName::MAX_LEN {
178                return Err(PathError::ComponentTooLong);
179            }
180        }
181
182        Ok(Self(s))
183    }
184}
185
186impl<'a, const N: usize> TryFrom<&'a [u8; N]> for Path<'a> {
187    type Error = PathError;
188
189    fn try_from(a: &'a [u8; N]) -> Result<Self, PathError> {
190        Self::try_from(a.as_slice())
191    }
192}
193
194impl<'a> TryFrom<&'a PathBuf> for Path<'a> {
195    type Error = PathError;
196
197    fn try_from(p: &'a PathBuf) -> Result<Self, PathError> {
198        Ok(p.as_path())
199    }
200}
201
202#[cfg(all(feature = "std", unix))]
203impl<'a> TryFrom<&'a std::ffi::OsStr> for Path<'a> {
204    type Error = PathError;
205
206    fn try_from(p: &'a std::ffi::OsStr) -> Result<Self, PathError> {
207        use std::os::unix::ffi::OsStrExt;
208
209        Self::try_from(p.as_bytes())
210    }
211}
212
213#[cfg(all(feature = "std", not(unix)))]
214impl<'a> TryFrom<&'a std::ffi::OsStr> for Path<'a> {
215    type Error = PathError;
216
217    fn try_from(p: &'a std::ffi::OsStr) -> Result<Self, PathError> {
218        Self::try_from(p.to_str().ok_or(PathError::Encoding)?)
219    }
220}
221
222#[cfg(feature = "std")]
223impl<'a> TryFrom<&'a std::path::Path> for Path<'a> {
224    type Error = PathError;
225
226    fn try_from(p: &'a std::path::Path) -> Result<Self, PathError> {
227        Self::try_from(p.as_os_str())
228    }
229}
230
231impl<T> PartialEq<T> for Path<'_>
232where
233    T: AsRef<[u8]>,
234{
235    fn eq(&self, other: &T) -> bool {
236        self.0 == other.as_ref()
237    }
238}
239
240#[cfg(all(feature = "std", unix))]
241impl<'a> From<Path<'a>> for &'a std::path::Path {
242    fn from(p: Path<'a>) -> &'a std::path::Path {
243        use std::os::unix::ffi::OsStrExt;
244
245        let s = std::ffi::OsStr::from_bytes(p.0);
246        std::path::Path::new(s)
247    }
248}
249
250/// Owned path type.
251///
252/// Paths are mostly arbitrary sequences of bytes, with two restrictions:
253/// * The path cannot contain any null bytes.
254/// * Each component of the path must be no longer than 255 bytes.
255#[derive(Clone, Default, Eq, Ord, PartialOrd, Hash)]
256pub struct PathBuf(Vec<u8>);
257
258impl PathBuf {
259    /// Create a new `PathBuf`.
260    ///
261    /// This panics if the input is invalid, use [`Path::try_from`] if
262    /// error handling is desired.
263    ///
264    /// # Panics
265    ///
266    /// Panics if the path contains any null bytes or if a component of
267    /// the path is longer than 255 bytes.
268    #[track_caller]
269    pub fn new<P>(p: &P) -> Self
270    where
271        P: AsRef<[u8]> + ?Sized,
272    {
273        Self::try_from(p.as_ref()).unwrap()
274    }
275
276    /// Create empty `PathBuf`.
277    #[must_use]
278    pub const fn empty() -> Self {
279        Self(Vec::new())
280    }
281
282    /// Borrow as a `Path`.
283    #[must_use]
284    pub fn as_path(&self) -> Path {
285        Path(&self.0)
286    }
287
288    /// Get whether the path is absolute (starts with `/`).
289    #[must_use]
290    pub fn is_absolute(&self) -> bool {
291        self.as_path().is_absolute()
292    }
293
294    /// Get an object that implements [`Display`] to allow conveniently
295    /// printing paths that may or may not be valid UTF-8. Non-UTF-8
296    /// characters will be replaced with '�'.
297    ///
298    /// [`Display`]: core::fmt::Display
299    pub fn display(&self) -> BytesDisplay {
300        BytesDisplay(&self.0)
301    }
302
303    /// Append to the path.
304    ///
305    /// This will add a separator if needed. Note that if the argument
306    /// is an absolute path, `self` will be replaced with that path.
307    ///
308    /// # Panics
309    ///
310    /// Panics if the argument is not a valid path, or if memory cannot
311    /// be allocated for the resulting path.
312    #[track_caller]
313    pub fn push(&mut self, path: impl AsRef<[u8]>) {
314        #[track_caller]
315        fn inner(this: &mut PathBuf, p: &[u8]) {
316            // Panic if the arg is not a valid path.
317            let p = Path::try_from(p).expect("push arg must be a valid path");
318
319            // If the arg is absolute, replace `self` with the arg rather
320            // than appending.
321            if p.is_absolute() {
322                this.0.clear();
323                this.0.extend(p.0);
324                return;
325            }
326
327            let add_sep = if let Some(last) = this.0.last() {
328                *last != b'/'
329            } else {
330                false
331            };
332
333            if add_sep {
334                // OK to unwrap: docstring says panic is allowed for
335                // memory allocation failure.
336                let len = p.0.len().checked_add(1).unwrap();
337                this.0.reserve(len);
338                this.0.push(Path::SEPARATOR);
339            } else {
340                this.0.reserve(p.0.len());
341            }
342
343            this.0.extend(p.0);
344        }
345
346        inner(self, path.as_ref())
347    }
348
349    /// Create a new `PathBuf` joining `self` with `path`.
350    ///
351    /// This will add a separator if needed. Note that if the argument
352    /// is an absolute path, the returned value will be equal to `path`.
353    ///
354    /// # Panics
355    ///
356    /// Panics if the argument is not a valid path.
357    #[must_use]
358    pub fn join(&self, path: impl AsRef<[u8]>) -> Self {
359        let mut t = self.clone();
360        t.push(path);
361        t
362    }
363
364    /// Get an iterator over each [`Component`] in the path.
365    #[must_use]
366    pub fn components(&self) -> Components<'_> {
367        self.as_path().components()
368    }
369
370    /// Convert to a `&str` if the path is valid UTF-8.
371    pub fn to_str(&self) -> Result<&str, Utf8Error> {
372        self.as_path().to_str()
373    }
374}
375
376impl AsRef<[u8]> for PathBuf {
377    fn as_ref(&self) -> &[u8] {
378        &self.0
379    }
380}
381
382impl Debug for PathBuf {
383    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
384        self.as_path().fmt(f)
385    }
386}
387
388impl TryFrom<&str> for PathBuf {
389    type Error = PathError;
390
391    fn try_from(s: &str) -> Result<Self, PathError> {
392        Self::try_from(s.as_bytes().to_vec())
393    }
394}
395
396impl TryFrom<&String> for PathBuf {
397    type Error = PathError;
398
399    fn try_from(s: &String) -> Result<Self, PathError> {
400        Self::try_from(s.as_bytes().to_vec())
401    }
402}
403
404impl TryFrom<String> for PathBuf {
405    type Error = PathError;
406
407    fn try_from(s: String) -> Result<Self, PathError> {
408        Self::try_from(s.into_bytes())
409    }
410}
411
412impl TryFrom<&[u8]> for PathBuf {
413    type Error = PathError;
414
415    fn try_from(s: &[u8]) -> Result<Self, PathError> {
416        Self::try_from(s.to_vec())
417    }
418}
419
420impl<const N: usize> TryFrom<&[u8; N]> for PathBuf {
421    type Error = PathError;
422
423    fn try_from(a: &[u8; N]) -> Result<Self, PathError> {
424        Self::try_from(a.as_slice().to_vec())
425    }
426}
427
428impl TryFrom<Vec<u8>> for PathBuf {
429    type Error = PathError;
430
431    fn try_from(s: Vec<u8>) -> Result<Self, PathError> {
432        // Validate the input.
433        Path::try_from(s.as_slice())?;
434
435        Ok(Self(s))
436    }
437}
438
439#[cfg(all(feature = "std", unix))]
440impl TryFrom<std::ffi::OsString> for PathBuf {
441    type Error = PathError;
442
443    fn try_from(p: std::ffi::OsString) -> Result<Self, PathError> {
444        use std::os::unix::ffi::OsStringExt;
445
446        Self::try_from(p.into_vec())
447    }
448}
449
450#[cfg(all(feature = "std", not(unix)))]
451impl TryFrom<std::ffi::OsString> for PathBuf {
452    type Error = PathError;
453
454    fn try_from(p: std::ffi::OsString) -> Result<Self, PathError> {
455        Self::try_from(p.into_string().map_err(|_| PathError::Encoding)?)
456    }
457}
458
459#[cfg(feature = "std")]
460impl TryFrom<std::path::PathBuf> for PathBuf {
461    type Error = PathError;
462
463    fn try_from(p: std::path::PathBuf) -> Result<Self, PathError> {
464        Self::try_from(p.into_os_string())
465    }
466}
467
468impl<T> PartialEq<T> for PathBuf
469where
470    T: AsRef<[u8]>,
471{
472    fn eq(&self, other: &T) -> bool {
473        self.0 == other.as_ref()
474    }
475}
476
477impl<'a> From<Path<'a>> for PathBuf {
478    fn from(p: Path<'a>) -> Self {
479        Self(p.0.to_vec())
480    }
481}
482
483#[cfg(all(feature = "std", unix))]
484impl From<PathBuf> for std::path::PathBuf {
485    fn from(p: PathBuf) -> Self {
486        use std::os::unix::ffi::OsStringExt;
487
488        let s = std::ffi::OsString::from_vec(p.0);
489        Self::from(s)
490    }
491}
492
493/// Component of a [`Path`].
494#[derive(Clone, Copy, Eq, PartialEq, Ord, PartialOrd, Hash)]
495pub enum Component<'a> {
496    /// Root directory (`/`), used at the start of an absolute path.
497    RootDir,
498
499    /// Current directory (`.`).
500    CurDir,
501
502    /// Parent directory (`..`).
503    ParentDir,
504
505    /// Directory or file name.
506    Normal(DirEntryName<'a>),
507}
508
509impl<'a> Component<'a> {
510    /// Construct a [`Component::Normal`] from the given `name`.
511    pub fn normal<T: AsRef<[u8]> + ?Sized>(
512        name: &'a T,
513    ) -> Result<Self, DirEntryNameError> {
514        Ok(Component::Normal(DirEntryName::try_from(name.as_ref())?))
515    }
516}
517
518impl Debug for Component<'_> {
519    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
520        match self {
521            Component::RootDir => write!(f, "RootDir"),
522            Component::CurDir => write!(f, "CurDir"),
523            Component::ParentDir => write!(f, "ParentDir"),
524            Component::Normal(name) => {
525                write!(f, "Normal(")?;
526                format_bytes_debug(name.as_ref(), f)?;
527                write!(f, ")")
528            }
529        }
530    }
531}
532
533impl<T> PartialEq<T> for Component<'_>
534where
535    T: AsRef<[u8]>,
536{
537    fn eq(&self, other: &T) -> bool {
538        let other = other.as_ref();
539        match self {
540            Component::RootDir => other == b"/",
541            Component::CurDir => other == b".",
542            Component::ParentDir => other == b"..",
543            Component::Normal(c) => *c == other,
544        }
545    }
546}
547
548/// Iterator over [`Component`]s in a [`Path`].
549pub struct Components<'a> {
550    path: Path<'a>,
551    offset: usize,
552}
553
554impl<'a> Iterator for Components<'a> {
555    type Item = Component<'a>;
556
557    fn next(&mut self) -> Option<Component<'a>> {
558        let path = &self.path.0;
559
560        if self.offset >= path.len() {
561            return None;
562        }
563
564        if self.offset == 0 && path[0] == Path::SEPARATOR {
565            self.offset = 1;
566            return Some(Component::RootDir);
567        }
568
569        // Coalesce repeated separators like "a//b".
570        while self.offset < path.len() && path[self.offset] == Path::SEPARATOR {
571            // OK to unwrap: `offset` is less than `path.len()`, which
572            // is also a `usize`, so adding `1` cannot fail.
573            self.offset = self.offset.checked_add(1).unwrap();
574        }
575        if self.offset >= path.len() {
576            return None;
577        }
578
579        let end: usize = if let Some(index) = self
580            .path
581            .0
582            .iter()
583            .skip(self.offset)
584            .position(|b| *b == Path::SEPARATOR)
585        {
586            // OK to unwrap: this sum is a valid index within `path`,
587            // so it must fit in a `usize`.
588            self.offset.checked_add(index).unwrap()
589        } else {
590            path.len()
591        };
592
593        let component = &path[self.offset..end];
594        let component = if component == b"." {
595            Component::CurDir
596        } else if component == b".." {
597            Component::ParentDir
598        } else {
599            // Paths are validated at construction time to ensure each
600            // component is of a valid length, so don't need to check
601            // that here when constructing `DirEntryName`.
602            Component::Normal(DirEntryName(component))
603        };
604
605        self.offset = end;
606        Some(component)
607    }
608}