e2p_fileflags/
lib.rs

1/*
2MIT License
3
4Copyright (c) 2019-2023 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#![warn(rust_2018_idioms)]
26
27//! Read and set ext2/ext3/ext4/btrfs/xfs/f2fs file flags like with lsattr and chattr from e2fsprogs
28//!
29//! e2p-fileflags provides access to ext* file flags. This provides similar functionality as to the
30//! lsattr and chattr command line tools on Linux. Which flags exist, depends on the file system used.
31//! This crate uses libe2p in the background, which originates from e2fsprogs and supports flags for
32//! ext2, ext3, ext4, btrfs, xfs and f2fs file systems.
33//!
34//! # Example
35//! ```no_run
36//! use std::fs::{remove_file,File};
37//! use std::path::Path;
38//! use e2p_fileflags::{FileFlags,Flags};
39//!
40//! let f = File::create("./fileflags_testfile.txt").expect("Could not create testfile");
41//! f.set_flags(Flags::NOCOW).expect("Could not set flags");
42//! println!("New flags: {:?}", f.flags().expect("Could not read flags"));
43//!
44//! let p = Path::new("./fileflags_testfile.txt");
45//! p.set_flags(Flags::NOCOW | Flags::NOATIME).expect("Could not set flags");
46//! println!("New flags: {:?}", p.flags().expect("Could not read flags"));
47//!
48//! drop(f);
49//! let _ = remove_file(p);
50//! ```
51
52#[macro_use]
53extern crate bitflags;
54
55#[cfg(feature = "serde")]
56#[macro_use]
57extern crate serde;
58
59use e2p_sys::*;
60use std::ffi::CString;
61use std::fs::File;
62use std::io::{Error, ErrorKind};
63use std::os::unix::io::AsRawFd;
64use std::path::Path;
65
66bitflags! {
67    /// Bitflags struct representing one or multiple file flags
68    #[derive(Copy, Clone, Debug, Default, Eq, Hash, PartialEq)]
69    #[cfg_attr(feature = "serde", derive(Serialize, Deserialize), serde(transparent))]
70    pub struct Flags: u32 {
71        const SECRM = EXT2_SECRM_FL;
72        const UNRM = EXT2_UNRM_FL;
73        const COMPR = EXT2_COMPR_FL;
74        const SYNC = EXT2_SYNC_FL;
75        const IMMUTABLE = EXT2_IMMUTABLE_FL;
76        const APPEND = EXT2_APPEND_FL;
77        const NODUMP = EXT2_NODUMP_FL;
78        const NOATIME = EXT2_NOATIME_FL;
79        const DIRTY = EXT2_DIRTY_FL;
80        const COMPRBLK = EXT2_COMPRBLK_FL;
81        const NOCOMPR = EXT2_NOCOMPR_FL;
82
83        #[cfg(ENCRYPT)]
84        const ENCRYPT = EXT4_ENCRYPT_FL;
85
86        const BTREE = EXT2_BTREE_FL;
87        const INDEX = EXT2_INDEX_FL;
88        const IMAGIC = EXT2_IMAGIC_FL;
89        const JOURNAL_DATA = EXT3_JOURNAL_DATA_FL;
90        const NOTAIL = EXT2_NOTAIL_FL;
91        const DIRSYNC = EXT2_DIRSYNC_FL;
92        const TOPDIR = EXT2_TOPDIR_FL;
93        const HUGE_FILE = EXT4_HUGE_FILE_FL;
94        const EXTENTS = EXT4_EXTENTS_FL;
95
96        #[cfg(VERITY)]
97        const VERITY = EXT4_VERITY_FL;
98
99        const EA_INODE = EXT4_EA_INODE_FL;
100        const NOCOW = FS_NOCOW_FL;
101        const SNAPFILE = EXT4_SNAPFILE_FL;
102
103        #[cfg(DAX)]
104        const DAX = FS_DAX_FL;
105
106        const SNAPFILE_DELETED = EXT4_SNAPFILE_DELETED_FL;
107        const SNAPFILE_SHRUNK = EXT4_SNAPFILE_SHRUNK_FL;
108
109        #[cfg(INLINE_DATA)]
110        const INLINE_DATA = EXT4_INLINE_DATA_FL;
111
112        #[cfg(PROJINHERIT)]
113        const PROJINHERIT = EXT4_PROJINHERIT_FL;
114
115        #[cfg(CASEFOLD)]
116        const CASEFOLD = EXT4_CASEFOLD_FL;
117
118        const RESERVED = EXT2_RESERVED_FL;
119        const USER_VISIBLE = EXT2_FL_USER_VISIBLE;
120        const USER_MODIFIABLE = EXT2_FL_USER_MODIFIABLE;
121    }
122}
123
124/// Reading and setting of file flags.
125pub trait FileFlags {
126    /// Determine currently set file flags.
127    fn flags(&self) -> Result<Flags, Error>;
128
129    /// Set file flags. This will update all user-writable flags according to f, i.e.,
130    /// flags set in f will be set, flags not set in f will be unset.
131    fn set_flags(&self, f: Flags) -> Result<(), Error>;
132}
133
134
135impl FileFlags for Path {
136    fn flags(&self) -> Result<Flags, Error> {
137        let path_cstr = match self.to_str() {
138            Some(s) => CString::new(s)?,
139            None => {
140                return Err(Error::new(
141                    ErrorKind::InvalidInput,
142                    "Provided path is no valid Unicode",
143                ));
144            }
145        };
146        let ret: i32;
147        let mut retflags: u64 = 0;
148        let path_ptr = path_cstr.as_ptr();
149        let retflags_ptr: *mut u64 = &mut retflags;
150
151        unsafe {
152            ret = fgetflags(path_ptr, retflags_ptr);
153        }
154
155        match ret {
156            0 => match Flags::from_bits(retflags as u32) {
157                Some(f) => Ok(f),
158                None => Err(Error::new(
159                    ErrorKind::InvalidData,
160                    "Unexcpected flags encountered",
161                )),
162            },
163            _ => Err(Error::last_os_error()),
164        }
165    }
166
167    fn set_flags(&self, f: Flags) -> Result<(), Error> {
168        let path_cstr = match self.to_str() {
169            Some(s) => CString::new(s)?,
170            None => {
171                return Err(Error::new(
172                    ErrorKind::InvalidInput,
173                    "Provided path is no valid Unicode",
174                ));
175            }
176        };
177        let ret: i32;
178        let intflags: u64 = f.bits() as u64;
179        let path_ptr = path_cstr.as_ptr();
180
181        unsafe {
182            ret = fsetflags(path_ptr, intflags);
183        }
184
185        match ret {
186            0 => Ok(()),
187            _ => Err(Error::last_os_error()),
188        }
189    }
190}
191
192impl FileFlags for File {
193    fn flags(&self) -> Result<Flags, Error> {
194        let ret: i32;
195        let mut retflags: u64 = 0;
196        let retflags_ptr: *mut u64 = &mut retflags;
197
198        unsafe {
199            ret = getflags(self.as_raw_fd(), retflags_ptr);
200        }
201
202        match ret {
203            0 => match Flags::from_bits(retflags as u32) {
204                Some(f) => Ok(f),
205                None => Err(Error::new(
206                    ErrorKind::InvalidData,
207                    "Unexcpected flags encountered",
208                )),
209            },
210            _ => Err(Error::last_os_error()),
211        }
212    }
213
214    fn set_flags(&self, f: Flags) -> Result<(), Error> {
215        let ret: i32;
216        let intflags: u64 = f.bits() as u64;
217
218        unsafe {
219            ret = setflags(self.as_raw_fd(), intflags);
220        }
221
222        match ret {
223            0 => Ok(()),
224            _ => Err(Error::last_os_error()),
225        }
226    }
227}
228
229#[cfg(test)]
230mod tests {
231    use super::*;
232    use std::env;
233    use std::fs::{remove_file, File};
234
235    #[test]
236    fn unified() {
237        let mut p = env::current_dir().unwrap();
238        p.push("e2p-fileflags-testfile-voo4JooY");
239        let f = File::create(&p).unwrap();
240
241        let initial = p.flags().unwrap();
242        assert_eq!(initial, f.flags().unwrap());
243
244        p.set_flags(Flags::NOATIME | initial).unwrap();
245        assert_eq!(f.flags().unwrap(), Flags::NOATIME | initial);
246        p.set_flags(initial).unwrap();
247        assert_eq!(f.flags().unwrap(), initial);
248
249        f.set_flags(Flags::NOATIME | initial).unwrap();
250        assert_eq!(p.flags().unwrap(), Flags::NOATIME | initial);
251        f.set_flags(initial).unwrap();
252        assert_eq!(p.flags().unwrap(), initial);
253
254        drop(f);
255        let _ = remove_file(p);
256    }
257}