Skip to main content

e2p_fileflags/
lib.rs

1/*
2MIT License
3
4Copyright (c) 2019-2026 Michael Lass
5
6Permission is hereby granted, free of charge, to any person obtaining a copy
7of this software and associated documentation files (the "Software"), to deal
8in the Software without restriction, including without limitation the rights
9to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10copies of the Software, and to permit persons to whom the Software is
11furnished to do so, subject to the following conditions:
12
13The above copyright notice and this permission notice shall be included in all
14copies or substantial portions of the Software.
15
16THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22SOFTWARE.
23*/
24
25#![deny(warnings)]
26#![deny(deprecated_safe)]
27#![deny(future_incompatible)]
28#![deny(keyword_idents)]
29#![deny(let_underscore)]
30#![deny(nonstandard_style)]
31#![deny(refining_impl_trait)]
32#![deny(rust_2018_idioms)]
33#![deny(unused)]
34
35//! Read and set ext2/ext3/ext4/btrfs/xfs/f2fs file flags like with lsattr and chattr from e2fsprogs
36//!
37//! e2p-fileflags provides access to ext* file flags. This provides similar functionality as to the
38//! lsattr and chattr command line tools on Linux. Which flags exist, depends on the file system used.
39//! This crate uses libe2p in the background, which originates from e2fsprogs and supports flags for
40//! ext2, ext3, ext4, btrfs, xfs and f2fs file systems.
41//!
42//! # Example
43//! ```no_run
44//! use std::fs::{remove_file,File};
45//! use std::path::Path;
46//! use e2p_fileflags::{FileFlags,Flags};
47//!
48//! let f = File::create("./fileflags_testfile.txt").expect("Could not create testfile");
49//! f.set_flags(Flags::NOCOW).expect("Could not set flags");
50//! println!("New flags: {:?}", f.flags().expect("Could not read flags"));
51//!
52//! let p = Path::new("./fileflags_testfile.txt");
53//! p.set_flags(Flags::NOCOW | Flags::NOATIME).expect("Could not set flags");
54//! println!("New flags: {:?}", p.flags().expect("Could not read flags"));
55//!
56//! drop(f);
57//! let _ = remove_file(p);
58//! ```
59
60#[macro_use]
61extern crate bitflags;
62
63#[cfg(feature = "serde")]
64#[macro_use]
65extern crate serde;
66
67use e2p_sys::*;
68use std::ffi::CString;
69use std::fs::File;
70use std::io::{Error, ErrorKind};
71use std::os::unix::io::AsRawFd;
72use std::path::Path;
73
74bitflags! {
75    /// Bitflags struct representing one or multiple file flags
76    #[derive(Copy, Clone, Debug, Default, Eq, Hash, PartialEq)]
77    #[cfg_attr(feature = "serde", derive(Serialize, Deserialize), serde(transparent))]
78    pub struct Flags: u32 {
79        const SECRM = EXT2_SECRM_FL;
80        const UNRM = EXT2_UNRM_FL;
81        const COMPR = EXT2_COMPR_FL;
82        const SYNC = EXT2_SYNC_FL;
83        const IMMUTABLE = EXT2_IMMUTABLE_FL;
84        const APPEND = EXT2_APPEND_FL;
85        const NODUMP = EXT2_NODUMP_FL;
86        const NOATIME = EXT2_NOATIME_FL;
87        const DIRTY = EXT2_DIRTY_FL;
88        const COMPRBLK = EXT2_COMPRBLK_FL;
89        const NOCOMPR = EXT2_NOCOMPR_FL;
90
91        #[cfg(ENCRYPT)]
92        const ENCRYPT = EXT4_ENCRYPT_FL;
93
94        const BTREE = EXT2_BTREE_FL;
95        const INDEX = EXT2_INDEX_FL;
96        const IMAGIC = EXT2_IMAGIC_FL;
97        const JOURNAL_DATA = EXT3_JOURNAL_DATA_FL;
98        const NOTAIL = EXT2_NOTAIL_FL;
99        const DIRSYNC = EXT2_DIRSYNC_FL;
100        const TOPDIR = EXT2_TOPDIR_FL;
101        const HUGE_FILE = EXT4_HUGE_FILE_FL;
102        const EXTENTS = EXT4_EXTENTS_FL;
103
104        #[cfg(VERITY)]
105        const VERITY = EXT4_VERITY_FL;
106
107        const EA_INODE = EXT4_EA_INODE_FL;
108        const NOCOW = FS_NOCOW_FL;
109        const SNAPFILE = EXT4_SNAPFILE_FL;
110
111        #[cfg(DAX)]
112        const DAX = FS_DAX_FL;
113
114        const SNAPFILE_DELETED = EXT4_SNAPFILE_DELETED_FL;
115        const SNAPFILE_SHRUNK = EXT4_SNAPFILE_SHRUNK_FL;
116
117        #[cfg(INLINE_DATA)]
118        const INLINE_DATA = EXT4_INLINE_DATA_FL;
119
120        #[cfg(PROJINHERIT)]
121        const PROJINHERIT = EXT4_PROJINHERIT_FL;
122
123        #[cfg(CASEFOLD)]
124        const CASEFOLD = EXT4_CASEFOLD_FL;
125
126        const RESERVED = EXT2_RESERVED_FL;
127        const USER_VISIBLE = EXT2_FL_USER_VISIBLE;
128        const USER_MODIFIABLE = EXT2_FL_USER_MODIFIABLE;
129    }
130}
131
132/// Reading and setting of file flags.
133pub trait FileFlags {
134    /// Determine currently set file flags.
135    fn flags(&self) -> Result<Flags, Error>;
136
137    /// Set file flags. This will update all user-writable flags according to f, i.e.,
138    /// flags set in f will be set, flags not set in f will be unset.
139    fn set_flags(&self, f: Flags) -> Result<(), Error>;
140}
141
142impl FileFlags for Path {
143    fn flags(&self) -> Result<Flags, Error> {
144        let path_cstr = match self.to_str() {
145            Some(s) => CString::new(s)?,
146            None => {
147                return Err(Error::new(
148                    ErrorKind::InvalidInput,
149                    "Provided path is no valid Unicode",
150                ));
151            }
152        };
153        let ret: i32;
154        let mut retflags: u64 = 0;
155        let path_ptr = path_cstr.as_ptr();
156        let retflags_ptr: *mut u64 = &mut retflags;
157
158        unsafe {
159            ret = fgetflags(path_ptr, retflags_ptr);
160        }
161
162        match ret {
163            0 => match Flags::from_bits(retflags as u32) {
164                Some(f) => Ok(f),
165                None => Err(Error::new(
166                    ErrorKind::InvalidData,
167                    "Unexcpected flags encountered",
168                )),
169            },
170            _ => Err(Error::last_os_error()),
171        }
172    }
173
174    fn set_flags(&self, f: Flags) -> Result<(), Error> {
175        let path_cstr = match self.to_str() {
176            Some(s) => CString::new(s)?,
177            None => {
178                return Err(Error::new(
179                    ErrorKind::InvalidInput,
180                    "Provided path is no valid Unicode",
181                ));
182            }
183        };
184        let ret: i32;
185        let intflags: u64 = f.bits() as u64;
186        let path_ptr = path_cstr.as_ptr();
187
188        unsafe {
189            ret = fsetflags(path_ptr, intflags);
190        }
191
192        match ret {
193            0 => Ok(()),
194            _ => Err(Error::last_os_error()),
195        }
196    }
197}
198
199impl FileFlags for File {
200    fn flags(&self) -> Result<Flags, Error> {
201        let ret: i32;
202        let mut retflags: u64 = 0;
203        let retflags_ptr: *mut u64 = &mut retflags;
204
205        unsafe {
206            ret = getflags(self.as_raw_fd(), retflags_ptr);
207        }
208
209        match ret {
210            0 => match Flags::from_bits(retflags as u32) {
211                Some(f) => Ok(f),
212                None => Err(Error::new(
213                    ErrorKind::InvalidData,
214                    "Unexcpected flags encountered",
215                )),
216            },
217            _ => Err(Error::last_os_error()),
218        }
219    }
220
221    fn set_flags(&self, f: Flags) -> Result<(), Error> {
222        let ret: i32;
223        let intflags: u64 = f.bits() as u64;
224
225        unsafe {
226            ret = setflags(self.as_raw_fd(), intflags);
227        }
228
229        match ret {
230            0 => Ok(()),
231            _ => Err(Error::last_os_error()),
232        }
233    }
234}
235
236#[cfg(test)]
237mod tests {
238    use super::*;
239    use std::env;
240    use std::fs::{File, remove_file};
241
242    #[test]
243    fn unified() {
244        let mut p = env::current_dir().unwrap();
245        p.push("e2p-fileflags-testfile-voo4JooY");
246        let f = File::create(&p).unwrap();
247
248        let initial = p.flags().unwrap();
249        assert_eq!(initial, f.flags().unwrap());
250
251        p.set_flags(Flags::NOATIME | initial).unwrap();
252        assert_eq!(f.flags().unwrap(), Flags::NOATIME | initial);
253        p.set_flags(initial).unwrap();
254        assert_eq!(f.flags().unwrap(), initial);
255
256        f.set_flags(Flags::NOATIME | initial).unwrap();
257        assert_eq!(p.flags().unwrap(), Flags::NOATIME | initial);
258        f.set_flags(initial).unwrap();
259        assert_eq!(p.flags().unwrap(), initial);
260
261        drop(f);
262        drop(remove_file(p));
263    }
264}