]>
Commit | Line | Data |
---|---|---|
b4d5787d WB |
1 | //! File system helper utilities. |
2 | ||
b12505da WB |
3 | use std::borrow::{Borrow, BorrowMut}; |
4 | use std::ops::{Deref, DerefMut}; | |
1db41615 | 5 | use std::os::unix::io::{AsRawFd, RawFd}; |
b4d5787d | 6 | |
23fba9d7 | 7 | use failure::*; |
b4d5787d WB |
8 | use nix::dir; |
9 | use nix::dir::Dir; | |
53e1e7ca | 10 | use regex::Regex; |
b4d5787d WB |
11 | |
12 | use crate::tools::borrow::Tied; | |
13 | ||
b12505da WB |
14 | /// This wraps nix::dir::Entry with the parent directory's file descriptor. |
15 | pub struct ReadDirEntry { | |
16 | entry: dir::Entry, | |
17 | parent_fd: RawFd, | |
18 | } | |
19 | ||
20 | impl Into<dir::Entry> for ReadDirEntry { | |
21 | fn into(self) -> dir::Entry { | |
22 | self.entry | |
23 | } | |
24 | } | |
25 | ||
26 | impl Deref for ReadDirEntry { | |
27 | type Target = dir::Entry; | |
28 | ||
29 | fn deref(&self) -> &Self::Target { | |
30 | &self.entry | |
31 | } | |
32 | } | |
33 | ||
34 | impl DerefMut for ReadDirEntry { | |
35 | fn deref_mut(&mut self) -> &mut Self::Target { | |
36 | &mut self.entry | |
37 | } | |
38 | } | |
39 | ||
40 | impl AsRef<dir::Entry> for ReadDirEntry { | |
41 | fn as_ref(&self) -> &dir::Entry { | |
42 | &self.entry | |
43 | } | |
44 | } | |
45 | ||
46 | impl AsMut<dir::Entry> for ReadDirEntry { | |
47 | fn as_mut(&mut self) -> &mut dir::Entry { | |
48 | &mut self.entry | |
49 | } | |
50 | } | |
51 | ||
52 | impl Borrow<dir::Entry> for ReadDirEntry { | |
53 | fn borrow(&self) -> &dir::Entry { | |
54 | &self.entry | |
55 | } | |
56 | } | |
57 | ||
58 | impl BorrowMut<dir::Entry> for ReadDirEntry { | |
59 | fn borrow_mut(&mut self) -> &mut dir::Entry { | |
60 | &mut self.entry | |
61 | } | |
62 | } | |
63 | ||
64 | impl ReadDirEntry { | |
65 | #[inline] | |
66 | pub fn parent_fd(&self) -> RawFd { | |
67 | self.parent_fd | |
68 | } | |
32286b03 WB |
69 | |
70 | pub unsafe fn file_name_utf8_unchecked(&self) -> &str { | |
71 | std::str::from_utf8_unchecked(self.file_name().to_bytes()) | |
72 | } | |
b12505da WB |
73 | } |
74 | ||
b4d5787d WB |
75 | // Since Tied<T, U> implements Deref to U, a Tied<Dir, Iterator> already implements Iterator. |
76 | // This is simply a wrapper with a shorter type name mapping nix::Error to failure::Error. | |
77 | /// Wrapper over a pair of `nix::dir::Dir` and `nix::dir::Iter`, returned by `read_subdir()`. | |
78 | pub struct ReadDir { | |
dd5495d6 | 79 | iter: Tied<Dir, dyn Iterator<Item = nix::Result<dir::Entry>> + Send>, |
1db41615 | 80 | dir_fd: RawFd, |
b4d5787d WB |
81 | } |
82 | ||
83 | impl Iterator for ReadDir { | |
1db41615 | 84 | type Item = Result<ReadDirEntry, Error>; |
b4d5787d WB |
85 | |
86 | fn next(&mut self) -> Option<Self::Item> { | |
1db41615 WB |
87 | self.iter.next().map(|res| { |
88 | res.map(|entry| ReadDirEntry { entry, parent_fd: self.dir_fd }) | |
fc7f0352 | 89 | .map_err(Error::from) |
1db41615 | 90 | }) |
b4d5787d WB |
91 | } |
92 | } | |
93 | ||
94 | /// Create an iterator over sub directory entries. | |
95 | /// This uses `openat` on `dirfd`, so `path` can be relative to that or an absolute path. | |
96 | pub fn read_subdir<P: ?Sized + nix::NixPath>(dirfd: RawFd, path: &P) -> Result<ReadDir, Error> { | |
97 | use nix::fcntl::OFlag; | |
98 | use nix::sys::stat::Mode; | |
99 | ||
100 | let dir = Dir::openat(dirfd, path, OFlag::O_RDONLY, Mode::empty())?; | |
1db41615 | 101 | let fd = dir.as_raw_fd(); |
b4d5787d | 102 | let iter = Tied::new(dir, |dir| { |
dd5495d6 WB |
103 | Box::new(unsafe { (*dir).iter() }) |
104 | as Box<dyn Iterator<Item = nix::Result<dir::Entry>> + Send> | |
b4d5787d | 105 | }); |
1db41615 | 106 | Ok(ReadDir { iter, dir_fd: fd }) |
b4d5787d | 107 | } |
a25f863a WB |
108 | |
109 | /// Scan through a directory with a regular expression. This is simply a shortcut filtering the | |
110 | /// results of `read_subdir`. Non-UTF8 comaptible file names are silently ignored. | |
111 | pub fn scan_subdir<'a, P: ?Sized + nix::NixPath>( | |
112 | dirfd: RawFd, | |
113 | path: &P, | |
114 | regex: &'a regex::Regex, | |
806d7a6a WB |
115 | ) -> Result<impl Iterator<Item = Result<ReadDirEntry, Error>> + 'a, Error> { |
116 | Ok(read_subdir(dirfd, path)?.filter_file_name_regex(regex)) | |
a25f863a | 117 | } |
23fba9d7 WB |
118 | |
119 | /// Helper trait to provide a combinators for directory entry iterators. | |
120 | pub trait FileIterOps<T, E> | |
121 | where | |
122 | Self: Sized + Iterator<Item = Result<T, E>>, | |
123 | T: Borrow<dir::Entry>, | |
124 | E: Into<Error> + Send + Sync, | |
125 | { | |
126 | /// Filter by file type. This is more convenient than using the `filter` method alone as this | |
127 | /// also includes error handling and handling of files without a type (via an error). | |
128 | fn filter_file_type(self, ty: dir::Type) -> FileTypeFilter<Self, T, E> { | |
129 | FileTypeFilter { inner: self, ty } | |
130 | } | |
53e1e7ca WB |
131 | |
132 | /// Filter by file name. Note that file names which aren't valid utf-8 will be treated as if | |
133 | /// they do not match the pattern. | |
134 | fn filter_file_name_regex<'a>(self, regex: &'a Regex) -> FileNameRegexFilter<'a, Self, T, E> { | |
135 | FileNameRegexFilter { inner: self, regex } | |
136 | } | |
23fba9d7 WB |
137 | } |
138 | ||
139 | impl<I, T, E> FileIterOps<T, E> for I | |
140 | where | |
141 | I: Iterator<Item = Result<T, E>>, | |
142 | T: Borrow<dir::Entry>, | |
143 | E: Into<Error> + Send + Sync, | |
144 | { | |
145 | } | |
146 | ||
147 | /// This filters files from its inner iterator by a file type. Files with no type produce an error. | |
148 | pub struct FileTypeFilter<I, T, E> | |
149 | where | |
150 | I: Iterator<Item = Result<T, E>>, | |
151 | T: Borrow<dir::Entry>, | |
152 | E: Into<Error> + Send + Sync, | |
153 | { | |
154 | inner: I, | |
155 | ty: nix::dir::Type, | |
156 | } | |
157 | ||
158 | impl<I, T, E> Iterator for FileTypeFilter<I, T, E> | |
159 | where | |
160 | I: Iterator<Item = Result<T, E>>, | |
161 | T: Borrow<dir::Entry>, | |
162 | E: Into<Error> + Send + Sync, | |
163 | { | |
164 | type Item = Result<T, Error>; | |
165 | ||
166 | fn next(&mut self) -> Option<Self::Item> { | |
167 | loop { | |
168 | let item = self.inner.next()?.map_err(|e| e.into()); | |
169 | match item { | |
170 | Ok(ref entry) => match entry.borrow().file_type() { | |
171 | Some(ty) => { | |
172 | if ty == self.ty { | |
173 | return Some(item); | |
174 | } else { | |
175 | continue; | |
176 | } | |
177 | } | |
178 | None => return Some(Err(format_err!("unable to detect file type"))), | |
179 | }, | |
180 | Err(_) => return Some(item), | |
181 | } | |
182 | } | |
183 | } | |
184 | } | |
185 | ||
53e1e7ca WB |
186 | /// This filters files by name via a Regex. Files whose file name aren't valid utf-8 are skipped |
187 | /// silently. | |
188 | pub struct FileNameRegexFilter<'a, I, T, E> | |
189 | where | |
190 | I: Iterator<Item = Result<T, E>>, | |
191 | T: Borrow<dir::Entry>, | |
192 | { | |
193 | inner: I, | |
194 | regex: &'a Regex, | |
195 | } | |
196 | ||
197 | impl<I, T, E> Iterator for FileNameRegexFilter<'_, I, T, E> | |
198 | where | |
199 | I: Iterator<Item = Result<T, E>>, | |
200 | T: Borrow<dir::Entry>, | |
201 | { | |
202 | type Item = Result<T, E>; | |
203 | ||
204 | fn next(&mut self) -> Option<Self::Item> { | |
205 | loop { | |
206 | let item = self.inner.next()?; | |
207 | match item { | |
208 | Ok(ref entry) => { | |
209 | if let Ok(name) = entry.borrow().file_name().to_str() { | |
210 | if self.regex.is_match(name) { | |
211 | return Some(item); | |
212 | } | |
213 | } | |
214 | // file did not match regex or isn't valid utf-8 | |
215 | continue; | |
216 | }, | |
217 | Err(_) => return Some(item), | |
218 | } | |
219 | } | |
220 | } | |
221 | } | |
042babe4 CE |
222 | |
223 | use nix::{convert_ioctl_res, request_code_read, request_code_write, ioc}; | |
224 | ||
225 | // /usr/include/linux/fs.h: #define FS_IOC_GETFLAGS _IOR('f', 1, long) | |
226 | // read Linux file system attributes (see man chattr) | |
227 | nix::ioctl_read!(read_attr_fd, b'f', 1, usize); | |
228 | ||
229 | // /usr/include/linux/msdos_fs.h: #define FAT_IOCTL_GET_ATTRIBUTES _IOR('r', 0x10, __u32) | |
230 | // read FAT file system attributes | |
231 | nix::ioctl_read!(read_fat_attr_fd, b'r', 0x10, u32); | |
232 | ||
233 | // From /usr/include/linux/fs.h | |
234 | // #define FS_IOC_FSGETXATTR _IOR('X', 31, struct fsxattr) | |
235 | // #define FS_IOC_FSSETXATTR _IOW('X', 32, struct fsxattr) | |
236 | nix::ioctl_read!(fs_ioc_fsgetxattr, b'X', 31, FSXAttr); | |
237 | nix::ioctl_write_ptr!(fs_ioc_fssetxattr, b'X', 32, FSXAttr); | |
238 | ||
239 | #[repr(C)] | |
240 | #[derive(Debug)] | |
241 | pub struct FSXAttr { | |
242 | pub fsx_xflags: u32, | |
243 | pub fsx_extsize: u32, | |
244 | pub fsx_nextents: u32, | |
245 | pub fsx_projid: u32, | |
246 | pub fsx_cowextsize: u32, | |
247 | pub fsx_pad: [u8; 8], | |
248 | } | |
249 | ||
250 | impl Default for FSXAttr { | |
251 | fn default() -> Self { | |
252 | FSXAttr { | |
253 | fsx_xflags: 0u32, | |
254 | fsx_extsize: 0u32, | |
255 | fsx_nextents: 0u32, | |
256 | fsx_projid: 0u32, | |
257 | fsx_cowextsize: 0u32, | |
258 | fsx_pad: [0u8; 8], | |
259 | } | |
260 | } | |
261 | } |