1 //! File system helper utilities.
3 use std
::borrow
::{Borrow, BorrowMut}
;
5 use std
::io
::{self, BufRead}
;
6 use std
::ops
::{Deref, DerefMut}
;
7 use std
::os
::unix
::io
::{AsRawFd, RawFd}
;
10 use anyhow
::{bail, format_err, Error}
;
13 use nix
::fcntl
::OFlag
;
14 use nix
::sys
::stat
::Mode
;
18 use proxmox
::sys
::error
::SysError
;
20 use crate::borrow
::Tied
;
22 pub type DirLockGuard
= Dir
;
24 /// This wraps nix::dir::Entry with the parent directory's file descriptor.
25 pub struct ReadDirEntry
{
30 impl Into
<dir
::Entry
> for ReadDirEntry
{
31 fn into(self) -> dir
::Entry
{
36 impl Deref
for ReadDirEntry
{
37 type Target
= dir
::Entry
;
39 fn deref(&self) -> &Self::Target
{
44 impl DerefMut
for ReadDirEntry
{
45 fn deref_mut(&mut self) -> &mut Self::Target
{
50 impl AsRef
<dir
::Entry
> for ReadDirEntry
{
51 fn as_ref(&self) -> &dir
::Entry
{
56 impl AsMut
<dir
::Entry
> for ReadDirEntry
{
57 fn as_mut(&mut self) -> &mut dir
::Entry
{
62 impl Borrow
<dir
::Entry
> for ReadDirEntry
{
63 fn borrow(&self) -> &dir
::Entry
{
68 impl BorrowMut
<dir
::Entry
> for ReadDirEntry
{
69 fn borrow_mut(&mut self) -> &mut dir
::Entry
{
76 pub fn parent_fd(&self) -> RawFd
{
80 pub unsafe fn file_name_utf8_unchecked(&self) -> &str {
81 std
::str::from_utf8_unchecked(self.file_name().to_bytes())
85 // Since Tied<T, U> implements Deref to U, a Tied<Dir, Iterator> already implements Iterator.
86 // This is simply a wrapper with a shorter type name mapping nix::Error to anyhow::Error.
87 /// Wrapper over a pair of `nix::dir::Dir` and `nix::dir::Iter`, returned by `read_subdir()`.
89 iter
: Tied
<Dir
, dyn Iterator
<Item
= nix
::Result
<dir
::Entry
>> + Send
>,
93 impl Iterator
for ReadDir
{
94 type Item
= Result
<ReadDirEntry
, Error
>;
96 fn next(&mut self) -> Option
<Self::Item
> {
97 self.iter
.next().map(|res
| {
98 res
.map(|entry
| ReadDirEntry { entry, parent_fd: self.dir_fd }
)
104 /// Create an iterator over sub directory entries.
105 /// This uses `openat` on `dirfd`, so `path` can be relative to that or an absolute path.
106 pub fn read_subdir
<P
: ?Sized
+ nix
::NixPath
>(dirfd
: RawFd
, path
: &P
) -> nix
::Result
<ReadDir
> {
107 let dir
= Dir
::openat(dirfd
, path
, OFlag
::O_RDONLY
, Mode
::empty())?
;
108 let fd
= dir
.as_raw_fd();
109 let iter
= Tied
::new(dir
, |dir
| {
110 Box
::new(unsafe { (*dir).iter() }
)
111 as Box
<dyn Iterator
<Item
= nix
::Result
<dir
::Entry
>> + Send
>
113 Ok(ReadDir { iter, dir_fd: fd }
)
116 /// Scan through a directory with a regular expression. This is simply a shortcut filtering the
117 /// results of `read_subdir`. Non-UTF8 compatible file names are silently ignored.
118 pub fn scan_subdir
<'a
, P
: ?Sized
+ nix
::NixPath
>(
121 regex
: &'a regex
::Regex
,
122 ) -> Result
<impl Iterator
<Item
= Result
<ReadDirEntry
, Error
>> + 'a
, nix
::Error
> {
123 Ok(read_subdir(dirfd
, path
)?
.filter_file_name_regex(regex
))
126 /// Scan directory for matching file names with a callback.
128 /// Scan through all directory entries and call `callback()` function
129 /// if the entry name matches the regular expression. This function
130 /// used unix `openat()`, so you can pass absolute or relative file
131 /// names. This function simply skips non-UTF8 encoded names.
132 pub fn scandir
<P
, F
>(
135 regex
: ®ex
::Regex
,
137 ) -> Result
<(), Error
>
139 F
: FnMut(RawFd
, &str, nix
::dir
::Type
) -> Result
<(), Error
>,
140 P
: ?Sized
+ nix
::NixPath
,
142 for entry
in scan_subdir(dirfd
, path
, regex
)?
{
144 let file_type
= match entry
.file_type() {
145 Some(file_type
) => file_type
,
146 None
=> bail
!("unable to detect file type"),
151 unsafe { entry.file_name_utf8_unchecked() }
,
159 /// Helper trait to provide a combinators for directory entry iterators.
160 pub trait FileIterOps
<T
, E
>
162 Self: Sized
+ Iterator
<Item
= Result
<T
, E
>>,
163 T
: Borrow
<dir
::Entry
>,
164 E
: Into
<Error
> + Send
+ Sync
,
166 /// Filter by file type. This is more convenient than using the `filter` method alone as this
167 /// also includes error handling and handling of files without a type (via an error).
168 fn filter_file_type(self, ty
: dir
::Type
) -> FileTypeFilter
<Self, T
, E
> {
169 FileTypeFilter { inner: self, ty }
172 /// Filter by file name. Note that file names which aren't valid utf-8 will be treated as if
173 /// they do not match the pattern.
174 fn filter_file_name_regex(self, regex
: &Regex
) -> FileNameRegexFilter
<Self, T
, E
> {
175 FileNameRegexFilter { inner: self, regex }
179 impl<I
, T
, E
> FileIterOps
<T
, E
> for I
181 I
: Iterator
<Item
= Result
<T
, E
>>,
182 T
: Borrow
<dir
::Entry
>,
183 E
: Into
<Error
> + Send
+ Sync
,
187 /// This filters files from its inner iterator by a file type. Files with no type produce an error.
188 pub struct FileTypeFilter
<I
, T
, E
>
190 I
: Iterator
<Item
= Result
<T
, E
>>,
191 T
: Borrow
<dir
::Entry
>,
192 E
: Into
<Error
> + Send
+ Sync
,
198 impl<I
, T
, E
> Iterator
for FileTypeFilter
<I
, T
, E
>
200 I
: Iterator
<Item
= Result
<T
, E
>>,
201 T
: Borrow
<dir
::Entry
>,
202 E
: Into
<Error
> + Send
+ Sync
,
204 type Item
= Result
<T
, Error
>;
206 fn next(&mut self) -> Option
<Self::Item
> {
208 let item
= self.inner
.next()?
.map_err(|e
| e
.into());
210 Ok(ref entry
) => match entry
.borrow().file_type() {
218 None
=> return Some(Err(format_err
!("unable to detect file type"))),
220 Err(_
) => return Some(item
),
226 /// This filters files by name via a Regex. Files whose file name aren't valid utf-8 are skipped
228 pub struct FileNameRegexFilter
<'a
, I
, T
, E
>
230 I
: Iterator
<Item
= Result
<T
, E
>>,
231 T
: Borrow
<dir
::Entry
>,
237 impl<I
, T
, E
> Iterator
for FileNameRegexFilter
<'_
, I
, T
, E
>
239 I
: Iterator
<Item
= Result
<T
, E
>>,
240 T
: Borrow
<dir
::Entry
>,
242 type Item
= Result
<T
, E
>;
244 fn next(&mut self) -> Option
<Self::Item
> {
246 let item
= self.inner
.next()?
;
249 if let Ok(name
) = entry
.borrow().file_name().to_str() {
250 if self.regex
.is_match(name
) {
254 // file did not match regex or isn't valid utf-8
257 Err(_
) => return Some(item
),
263 // /usr/include/linux/fs.h: #define FS_IOC_GETFLAGS _IOR('f', 1, long)
264 // read Linux file system attributes (see man chattr)
265 nix
::ioctl_read
!(read_attr_fd
, b'f'
, 1, libc
::c_long
);
266 nix
::ioctl_write_ptr
!(write_attr_fd
, b'f'
, 2, libc
::c_long
);
268 // /usr/include/linux/msdos_fs.h: #define FAT_IOCTL_GET_ATTRIBUTES _IOR('r', 0x10, __u32)
269 // read FAT file system attributes
270 nix
::ioctl_read
!(read_fat_attr_fd
, b'r'
, 0x10, u32);
271 nix
::ioctl_write_ptr
!(write_fat_attr_fd
, b'r'
, 0x11, u32);
273 // From /usr/include/linux/fs.h
274 // #define FS_IOC_FSGETXATTR _IOR('X', 31, struct fsxattr)
275 // #define FS_IOC_FSSETXATTR _IOW('X', 32, struct fsxattr)
276 nix
::ioctl_read
!(fs_ioc_fsgetxattr
, b'X'
, 31, FSXAttr
);
277 nix
::ioctl_write_ptr
!(fs_ioc_fssetxattr
, b'X'
, 32, FSXAttr
);
283 pub fsx_extsize
: u32,
284 pub fsx_nextents
: u32,
286 pub fsx_cowextsize
: u32,
287 pub fsx_pad
: [u8; 8],
290 impl Default
for FSXAttr
{
291 fn default() -> Self {
297 fsx_cowextsize
: 0u32,
303 /// Attempt to acquire a shared flock on the given path, 'what' and
304 /// 'would_block_message' are used for error formatting.
305 pub fn lock_dir_noblock_shared(
306 path
: &std
::path
::Path
,
308 would_block_msg
: &str,
309 ) -> Result
<DirLockGuard
, Error
> {
310 do_lock_dir_noblock(path
, what
, would_block_msg
, false)
313 /// Attempt to acquire an exclusive flock on the given path, 'what' and
314 /// 'would_block_message' are used for error formatting.
315 pub fn lock_dir_noblock(
316 path
: &std
::path
::Path
,
318 would_block_msg
: &str,
319 ) -> Result
<DirLockGuard
, Error
> {
320 do_lock_dir_noblock(path
, what
, would_block_msg
, true)
323 fn do_lock_dir_noblock(
324 path
: &std
::path
::Path
,
326 would_block_msg
: &str,
328 ) -> Result
<DirLockGuard
, Error
> {
329 let mut handle
= Dir
::open(path
, OFlag
::O_RDONLY
, Mode
::empty())
331 format_err
!("unable to open {} directory {:?} for locking - {}", what
, path
, err
)
334 // acquire in non-blocking mode, no point in waiting here since other
335 // backups could still take a very long time
336 proxmox
::tools
::fs
::lock_file(&mut handle
, exclusive
, Some(std
::time
::Duration
::from_nanos(0)))
339 "unable to acquire lock on {} directory {:?} - {}", what
, path
,
340 if err
.would_block() {
341 String
::from(would_block_msg
)
351 /// Get an iterator over lines of a file, skipping empty lines and comments (lines starting with a
353 pub fn file_get_non_comment_lines
<P
: AsRef
<Path
>>(
355 ) -> Result
<impl Iterator
<Item
= io
::Result
<String
>>, Error
> {
356 let path
= path
.as_ref();
358 Ok(io
::BufReader
::new(
359 File
::open(path
).map_err(|err
| format_err
!("error opening {:?}: {}", path
, err
))?
,
362 .filter_map(|line
| match line
{
364 let line
= line
.trim();
365 if line
.is_empty() || line
.starts_with('
#') {
368 Some(Ok(line
.to_string()))
371 Err(err
) => Some(Err(err
)),