gix_features/
fs.rs

1//! Filesystem utilities
2//!
3//! These are will be parallel if the `parallel` feature is enabled, at the expense of compiling additional dependencies
4//! along with runtime costs for maintaining a global [`rayon`](https://docs.rs/rayon) thread pool.
5//!
6//! For information on how to use the [`WalkDir`] type, have a look at
7// TODO: Move all this to `gix-fs` in a breaking change.
8
9#[cfg(feature = "walkdir")]
10mod shared {
11    /// The desired level of parallelism.
12    pub enum Parallelism {
13        /// Do not parallelize at all by making a serial traversal on the current thread.
14        Serial,
15        /// Create a new thread pool for each traversal with up to 16 threads or the amount of logical cores of the machine.
16        ThreadPoolPerTraversal {
17            /// The base name of the threads we create as part of the thread-pool.
18            thread_name: &'static str,
19        },
20    }
21}
22
23#[cfg(any(feature = "walkdir", feature = "fs-read-dir"))]
24mod walkdir_precompose {
25    use std::{borrow::Cow, ffi::OsStr, path::Path};
26
27    #[derive(Debug)]
28    pub struct DirEntry<T: std::fmt::Debug> {
29        inner: T,
30        precompose_unicode: bool,
31    }
32
33    impl<T: std::fmt::Debug> DirEntry<T> {
34        /// Create a new instance.
35        pub fn new(inner: T, precompose_unicode: bool) -> Self {
36            Self {
37                inner,
38                precompose_unicode,
39            }
40        }
41    }
42
43    pub trait DirEntryApi {
44        fn path(&self) -> Cow<'_, Path>;
45        fn file_name(&self) -> Cow<'_, OsStr>;
46        fn file_type(&self) -> std::io::Result<std::fs::FileType>;
47    }
48
49    impl<T: DirEntryApi + std::fmt::Debug> DirEntry<T> {
50        /// Obtain the full path of this entry, possibly with precomposed unicode if enabled.
51        ///
52        /// Note that decomposing filesystem like those made by Apple accept both precomposed and
53        /// decomposed names, and consider them equal.
54        pub fn path(&self) -> Cow<'_, Path> {
55            let path = self.inner.path();
56            if self.precompose_unicode {
57                gix_utils::str::precompose_path(path)
58            } else {
59                path
60            }
61        }
62
63        /// Obtain filen name of this entry, possibly with precomposed unicode if enabled.
64        pub fn file_name(&self) -> Cow<'_, OsStr> {
65            let name = self.inner.file_name();
66            if self.precompose_unicode {
67                gix_utils::str::precompose_os_string(name)
68            } else {
69                name
70            }
71        }
72
73        /// Return the file type for the file that this entry points to.
74        ///
75        /// If `follow_links` was `true`, this is the file type of the item the link points to.
76        pub fn file_type(&self) -> std::io::Result<std::fs::FileType> {
77            self.inner.file_type()
78        }
79    }
80
81    /// A platform over entries in a directory, which may or may not precompose unicode after retrieving
82    /// paths from the file system.
83    #[cfg(feature = "walkdir")]
84    pub struct WalkDir<T> {
85        pub(crate) inner: Option<T>,
86        pub(crate) precompose_unicode: bool,
87    }
88
89    #[cfg(feature = "walkdir")]
90    pub struct WalkDirIter<T, I, E>
91    where
92        T: Iterator<Item = Result<I, E>>,
93        I: DirEntryApi,
94    {
95        pub(crate) inner: T,
96        pub(crate) precompose_unicode: bool,
97    }
98
99    #[cfg(feature = "walkdir")]
100    impl<T, I, E> Iterator for WalkDirIter<T, I, E>
101    where
102        T: Iterator<Item = Result<I, E>>,
103        I: DirEntryApi + std::fmt::Debug,
104    {
105        type Item = Result<DirEntry<I>, E>;
106
107        fn next(&mut self) -> Option<Self::Item> {
108            self.inner
109                .next()
110                .map(|res| res.map(|entry| DirEntry::new(entry, self.precompose_unicode)))
111        }
112    }
113}
114
115///
116#[cfg(feature = "fs-read-dir")]
117pub mod read_dir {
118    use std::{borrow::Cow, ffi::OsStr, fs::FileType, path::Path};
119
120    /// A directory entry adding precompose-unicode support to [`std::fs::DirEntry`].
121    pub type DirEntry = super::walkdir_precompose::DirEntry<std::fs::DirEntry>;
122
123    impl super::walkdir_precompose::DirEntryApi for std::fs::DirEntry {
124        fn path(&self) -> Cow<'_, Path> {
125            self.path().into()
126        }
127
128        fn file_name(&self) -> Cow<'_, OsStr> {
129            self.file_name().into()
130        }
131
132        fn file_type(&self) -> std::io::Result<FileType> {
133            self.file_type()
134        }
135    }
136}
137
138///
139#[cfg(feature = "walkdir")]
140pub mod walkdir {
141    use std::{borrow::Cow, ffi::OsStr, fs::FileType, path::Path};
142
143    pub use walkdir::Error;
144    use walkdir::{DirEntry as DirEntryImpl, WalkDir as WalkDirImpl};
145
146    /// A directory entry returned by [DirEntryIter].
147    pub type DirEntry = super::walkdir_precompose::DirEntry<DirEntryImpl>;
148    /// A platform to create a [DirEntryIter] from.
149    pub type WalkDir = super::walkdir_precompose::WalkDir<WalkDirImpl>;
150
151    pub use super::shared::Parallelism;
152
153    impl super::walkdir_precompose::DirEntryApi for DirEntryImpl {
154        fn path(&self) -> Cow<'_, Path> {
155            self.path().into()
156        }
157
158        fn file_name(&self) -> Cow<'_, OsStr> {
159            self.file_name().into()
160        }
161
162        fn file_type(&self) -> std::io::Result<FileType> {
163            Ok(self.file_type())
164        }
165    }
166
167    impl IntoIterator for WalkDir {
168        type Item = Result<DirEntry, walkdir::Error>;
169        type IntoIter = DirEntryIter;
170
171        fn into_iter(self) -> Self::IntoIter {
172            DirEntryIter {
173                inner: self.inner.expect("always set (builder fix)").into_iter(),
174                precompose_unicode: self.precompose_unicode,
175            }
176        }
177    }
178
179    impl WalkDir {
180        /// Set the minimum component depth of paths of entries.
181        pub fn min_depth(mut self, min: usize) -> Self {
182            self.inner = Some(self.inner.take().expect("always set").min_depth(min));
183            self
184        }
185        /// Set the maximum component depth of paths of entries.
186        pub fn max_depth(mut self, max: usize) -> Self {
187            self.inner = Some(self.inner.take().expect("always set").max_depth(max));
188            self
189        }
190        /// Follow symbolic links.
191        pub fn follow_links(mut self, toggle: bool) -> Self {
192            self.inner = Some(self.inner.take().expect("always set").follow_links(toggle));
193            self
194        }
195    }
196
197    /// Instantiate a new directory iterator which will not skip hidden files, with the given level of `parallelism`.
198    ///
199    /// Use `precompose_unicode` to represent the `core.precomposeUnicode` configuration option.
200    pub fn walkdir_new(root: &Path, _: Parallelism, precompose_unicode: bool) -> WalkDir {
201        WalkDir {
202            inner: WalkDirImpl::new(root).into(),
203            precompose_unicode,
204        }
205    }
206
207    /// Instantiate a new directory iterator which will not skip hidden files and is sorted, with the given level of `parallelism`.
208    ///
209    /// Use `precompose_unicode` to represent the `core.precomposeUnicode` configuration option.
210    pub fn walkdir_sorted_new(root: &Path, _: Parallelism, precompose_unicode: bool) -> WalkDir {
211        WalkDir {
212            inner: WalkDirImpl::new(root)
213                .sort_by(|a, b| {
214                    let storage_a;
215                    let storage_b;
216                    let a_name = match gix_path::os_str_into_bstr(a.file_name()) {
217                        Ok(f) => f,
218                        Err(_) => {
219                            storage_a = a.file_name().to_string_lossy();
220                            storage_a.as_ref().into()
221                        }
222                    };
223                    let b_name = match gix_path::os_str_into_bstr(b.file_name()) {
224                        Ok(f) => f,
225                        Err(_) => {
226                            storage_b = b.file_name().to_string_lossy();
227                            storage_b.as_ref().into()
228                        }
229                    };
230                    // "common." < "common/" < "common0"
231                    let common = a_name.len().min(b_name.len());
232                    a_name[..common].cmp(&b_name[..common]).then_with(|| {
233                        let a = a_name.get(common).or_else(|| a.file_type().is_dir().then_some(&b'/'));
234                        let b = b_name.get(common).or_else(|| b.file_type().is_dir().then_some(&b'/'));
235                        a.cmp(&b)
236                    })
237                })
238                .into(),
239            precompose_unicode,
240        }
241    }
242
243    /// The Iterator yielding directory items
244    pub type DirEntryIter = super::walkdir_precompose::WalkDirIter<walkdir::IntoIter, DirEntryImpl, walkdir::Error>;
245}
246
247#[cfg(feature = "walkdir")]
248pub use self::walkdir::{walkdir_new, walkdir_sorted_new, WalkDir};
249
250/// Prepare open options which won't follow symlinks when the file is opened.
251///
252/// Note: only effective on unix currently.
253pub fn open_options_no_follow() -> std::fs::OpenOptions {
254    #[cfg_attr(not(unix), allow(unused_mut))]
255    let mut options = std::fs::OpenOptions::new();
256    #[cfg(unix)]
257    {
258        /// Make sure that it's impossible to follow through to the target of symlinks.
259        /// Note that this will still follow symlinks in the path, which is what we assume
260        /// has been checked separately.
261        use std::os::unix::fs::OpenOptionsExt;
262        options.custom_flags(libc::O_NOFOLLOW);
263    }
264    options
265}