Skip to main content

dope_core/io/fs/path/
mod.rs

1mod error;
2mod ffi;
3
4use std::{os::unix, path::Path};
5
6pub use error::Error;
7use o3::{alloc, alloc::VecExt, mem::rc};
8
9use crate::io::fs;
10
11const MAX_COMPONENT_BYTES: usize = 255;
12const _: () = assert!(MAX_COMPONENT_BYTES == u8::MAX as usize);
13
14#[derive(Clone, Copy)]
15pub struct Component<'a> {
16    bytes: &'a [u8],
17}
18
19pub struct Owned {
20    bytes: Box<std::ffi::CStr>,
21}
22
23pub struct Confined {
24    pub(super) directory: fs::Dir,
25    pub(super) path: Owned,
26}
27
28#[derive(Clone, Copy, Debug, Eq, PartialEq)]
29pub enum ConfineError {
30    Allocation(alloc::Error),
31    Retain(rc::Error),
32}
33
34#[derive(Clone, Copy, Debug, Eq, PartialEq)]
35pub enum CopyError {
36    Nul { position: usize },
37    Allocation(alloc::Error),
38}
39
40/// Fallible copying of a Unix path into an owned C string.
41pub trait CStrExt: Sized {
42    fn try_copy(path: &Path) -> Result<Self, CopyError>;
43}
44
45impl CStrExt for Box<std::ffi::CStr> {
46    #[inline]
47    fn try_copy(path: &Path) -> Result<Self, CopyError> {
48        let bytes = unix::ffi::OsStrExt::as_bytes(path.as_os_str());
49        if let Ok(prefix) = std::ffi::CStr::from_bytes_until_nul(bytes) {
50            return Err(CopyError::Nul {
51                position: prefix.to_bytes().len(),
52            });
53        }
54        try_copy(bytes).map_err(CopyError::Allocation)
55    }
56}
57
58#[derive(Clone, Copy)]
59pub struct Entry<'a> {
60    directory: &'a fs::Dir,
61    name: &'a Owned,
62}
63
64#[derive(Clone, Copy)]
65pub struct Rename<'a> {
66    directory: &'a fs::Dir,
67    old: &'a Owned,
68    new: &'a Owned,
69}
70
71impl<'a> Component<'a> {
72    pub fn parse_os(value: &'a std::ffi::OsStr) -> Result<Self, Error> {
73        Self::parse_bytes(unix::ffi::OsStrExt::as_bytes(value))
74    }
75
76    fn parse_bytes(bytes: &'a [u8]) -> Result<Self, Error> {
77        if bytes.is_empty() {
78            return Err(Error::Empty);
79        }
80        if bytes == b".." {
81            return Err(Error::Parent { offset: 0 });
82        }
83        if bytes == b"." {
84            return Err(Error::NoComponent { length: 1 });
85        }
86        if let Some(offset) = bytes.iter().position(|byte| *byte == b'/') {
87            return Err(Error::Nested { offset });
88        }
89        if let Some(pos) = bytes.iter().position(|byte| *byte == 0) {
90            return Err(Error::Nul { position: pos });
91        }
92        if bytes.len() > MAX_COMPONENT_BYTES {
93            return Err(Error::Component {
94                offset: 0,
95                length: bytes.len(),
96                limit: MAX_COMPONENT_BYTES,
97            });
98        }
99        Ok(Self { bytes })
100    }
101
102    pub fn try_to_owned(self) -> Result<Owned, alloc::Error> {
103        let bytes = try_copy(self.bytes)?;
104        Ok(Owned { bytes })
105    }
106}
107
108/// Callers have rejected interior NULs before copying the admitted path bytes.
109#[inline]
110fn try_copy(base: &[u8]) -> Result<Box<std::ffi::CStr>, alloc::Error> {
111    let len = base.len().checked_add(1).ok_or(alloc::Error::Overflow)?;
112    let mut bytes: Vec<u8> = VecExt::try_new(len)?;
113    bytes.extend_from_slice(base);
114    bytes.push(0);
115    Ok(ffi::finish(bytes))
116}
117
118impl Owned {
119    pub(crate) fn as_c_str(&self) -> &std::ffi::CStr {
120        &self.bytes
121    }
122}
123
124impl Confined {
125    pub(in crate::io::fs) fn new(dir: fs::Dir, path: Owned) -> Self {
126        Self {
127            directory: dir,
128            path,
129        }
130    }
131
132    pub(crate) fn view(&self) -> Entry<'_> {
133        Entry::new(&self.directory, &self.path)
134    }
135}
136
137impl Confined {
138    pub fn into_owned(self) -> Owned {
139        self.path
140    }
141}
142
143impl<'a> Entry<'a> {
144    pub(in crate::io::fs) fn new(dir: &'a fs::Dir, name: &'a Owned) -> Self {
145        Self {
146            directory: dir,
147            name,
148        }
149    }
150
151    pub(crate) fn directory(self) -> &'a fs::Dir {
152        self.directory
153    }
154
155    pub(crate) fn name(self) -> &'a std::ffi::CStr {
156        self.name.as_c_str()
157    }
158}
159
160impl<'a> Rename<'a> {
161    pub(in crate::io::fs) fn new(dir: &'a fs::Dir, old: &'a Owned, new: &'a Owned) -> Self {
162        Self {
163            directory: dir,
164            old,
165            new,
166        }
167    }
168
169    pub(crate) fn directory(self) -> &'a fs::Dir {
170        self.directory
171    }
172
173    pub(crate) fn old(self) -> &'a std::ffi::CStr {
174        self.old.as_c_str()
175    }
176
177    pub(crate) fn new_name(self) -> &'a std::ffi::CStr {
178        self.new.as_c_str()
179    }
180}
181
182const _: () = {
183    assert!(std::mem::size_of::<Owned>() == 2 * std::mem::size_of::<usize>());
184    assert!(std::mem::size_of::<Confined>() == 3 * std::mem::size_of::<usize>());
185    assert!(std::mem::size_of::<Entry<'_>>() == 2 * std::mem::size_of::<usize>());
186    assert!(std::mem::size_of::<Rename<'_>>() == 3 * std::mem::size_of::<usize>());
187};