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