posix_acl/
acl.rs

1use crate::error::{ACLError, FLAG_WRITE};
2use crate::iter::RawACLIterator;
3use crate::util::{check_pointer, check_return, path_to_cstring, AutoPtr};
4use crate::Qualifier::{GroupObj, Other, UserObj};
5use crate::{ACLEntry, Qualifier, ACL_RWX};
6use acl_sys::{
7    acl_add_perm, acl_calc_mask, acl_clear_perms, acl_create_entry, acl_delete_entry, acl_entry_t,
8    acl_get_file, acl_get_permset, acl_init, acl_permset_t, acl_set_file, acl_set_permset,
9    acl_set_qualifier, acl_set_tag_type, acl_t, acl_to_text, acl_type_t, acl_valid,
10    ACL_TYPE_ACCESS, ACL_TYPE_DEFAULT,
11};
12use libc::ssize_t;
13use std::convert::TryFrom;
14use std::os::raw::c_void;
15use std::path::Path;
16use std::ptr::{addr_of, null_mut};
17use std::slice::from_raw_parts;
18use std::str::from_utf8;
19use std::{fmt, mem};
20
21/// The ACL of a file.
22///
23/// Implements a "mapping-like" interface where key is the `Qualifier` enum and value is `u32`
24/// containing permission bits.
25/// Using methods `get(qual) -> perms`, `set(qual, perms)`, `remove(qual)`.
26#[allow(clippy::upper_case_acronyms)]
27pub struct PosixACL {
28    pub(crate) acl: acl_t,
29}
30
31/// Custom debug formatting, since output `PosixACL { acl: 0x7fd74c000ca8 }` is not very helpful.
32impl fmt::Debug for PosixACL {
33    fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
34        // Not really a tuple, but tuple formatting is compact.
35        fmt.debug_tuple("PosixACL")
36            .field(&self.compact_text())
37            .finish()
38    }
39}
40
41impl Drop for PosixACL {
42    fn drop(&mut self) {
43        AutoPtr(self.acl);
44    }
45}
46
47impl PartialEq for PosixACL {
48    fn eq(&self, other: &Self) -> bool {
49        self.entries() == other.entries()
50    }
51}
52
53impl PosixACL {
54    /// Convert a file mode ("chmod" number) into a "minimal" ACL. This is the primary constructor.
55    /// Note that modes are usually expressed in octal, e.g. `PosixACL::new(0o644)`
56    ///
57    /// This creates the minimal required entries. By the POSIX ACL spec, every valid ACL must
58    /// contain at least three entries: `UserObj`, `GroupObj` and `Other`, corresponding to file
59    /// mode bits.
60    ///
61    /// Input bits higher than 9 (e.g. SUID flag, etc) are ignored.
62    ///
63    /// ```
64    /// use posix_acl::PosixACL;
65    /// assert_eq!(
66    ///     PosixACL::new(0o751).as_text(),
67    ///     "user::rwx\ngroup::r-x\nother::--x\n"
68    /// );
69    /// ```
70    #[must_use]
71    pub fn new(file_mode: u32) -> PosixACL {
72        let mut acl = PosixACL::empty();
73        acl.set(UserObj, (file_mode >> 6) & ACL_RWX);
74        acl.set(GroupObj, (file_mode >> 3) & ACL_RWX);
75        acl.set(Other, file_mode & ACL_RWX);
76        acl
77    }
78
79    /// Create an empty ACL. NB! Empty ACLs are NOT considered valid.
80    #[must_use]
81    pub fn empty() -> PosixACL {
82        PosixACL::with_capacity(6)
83    }
84
85    /// Create an empty ACL with capacity. NB! Empty ACLs are NOT considered valid.
86    #[must_use]
87    pub fn with_capacity(capacity: usize) -> PosixACL {
88        let capacity = i32::try_from(capacity).unwrap_or(i32::MAX);
89        let acl = unsafe { acl_init(capacity) };
90        check_pointer(acl, "acl_init");
91        PosixACL { acl }
92    }
93
94    /// Read a path's access ACL and return as `PosixACL` object.
95    /// ```
96    /// use posix_acl::PosixACL;
97    /// let acl = PosixACL::read_acl("/etc/shells").unwrap();
98    /// ```
99    ///
100    /// # Errors
101    /// * `ACLError::IoError`: Filesystem errors (file not found, permission denied, etc).
102    ///
103    /// <div class="warning">
104    /// It is NOT an error if the provided path has no ACL; a minimal ACL will be returned.
105    /// </div>
106    pub fn read_acl<P: AsRef<Path>>(path: P) -> Result<PosixACL, ACLError> {
107        Self::read_acl_flags(path.as_ref(), ACL_TYPE_ACCESS)
108    }
109
110    /// Read a directory's default ACL and return as `PosixACL` object.
111    /// This will fail if `path` is not a directory.
112    ///
113    /// Default ACL determines permissions for new files and subdirectories created in the
114    /// directory.
115    /// ```
116    /// use posix_acl::PosixACL;
117    /// let acl = PosixACL::read_default_acl("/tmp").unwrap();
118    /// ```
119    ///
120    /// # Errors
121    /// * `ACLError::IoError`: Filesystem errors (file not found, permission denied, etc).
122    /// * Passing a non-directory path will fail with 'permission denied' error on Linux.
123    ///
124    /// <div class="warning">
125    /// It is NOT an error if the provided path has no ACL; an empty ACL will be returned.
126    /// </div>
127    pub fn read_default_acl<P: AsRef<Path>>(path: P) -> Result<PosixACL, ACLError> {
128        Self::read_acl_flags(path.as_ref(), ACL_TYPE_DEFAULT)
129    }
130
131    fn read_acl_flags(path: &Path, flags: acl_type_t) -> Result<PosixACL, ACLError> {
132        let c_path = path_to_cstring(path);
133        let acl: acl_t = unsafe { acl_get_file(c_path.as_ptr(), flags) };
134        if acl.is_null() {
135            Err(ACLError::last_os_error(flags))
136        } else {
137            Ok(PosixACL { acl })
138        }
139    }
140
141    /// Validate and write this ACL to a path's access ACL. Overwrites any existing access ACL.
142    ///
143    /// Note: this function takes mutable `self` because it automatically re-calculates the magic
144    /// `Mask` entry.
145    ///
146    /// # Errors
147    /// * `ACLError::IoError`: Filesystem errors (file not found, permission denied, etc).
148    /// * `ACLError::ValidationError`: The ACL failed validation. See [`PosixACL::validate()`] for
149    ///    more information.
150    pub fn write_acl<P: AsRef<Path>>(&mut self, path: P) -> Result<(), ACLError> {
151        self.write_acl_flags(path.as_ref(), ACL_TYPE_ACCESS)
152    }
153
154    /// Validate and write this ACL to a directory's default ACL. Overwrites existing default ACL.
155    /// This will fail if `path` is not a directory.
156    ///
157    /// Default ACL determines permissions for new files and subdirectories created in the
158    /// directory.
159    ///
160    /// Note: this function takes mutable `self` because it automatically re-calculates the magic
161    /// `Mask` entry.
162    ///
163    /// # Errors
164    /// * `ACLError::IoError`: Filesystem errors (file not found, permission denied, etc).
165    /// * `ACLError::ValidationError`: The ACL failed validation. See [`PosixACL::validate()`] for
166    ///    more information.
167    pub fn write_default_acl<P: AsRef<Path>>(&mut self, path: P) -> Result<(), ACLError> {
168        self.write_acl_flags(path.as_ref(), ACL_TYPE_DEFAULT)
169    }
170
171    fn write_acl_flags(&mut self, path: &Path, flags: acl_type_t) -> Result<(), ACLError> {
172        let c_path = path_to_cstring(path);
173        self.fix_mask();
174        self.validate()?;
175        let ret = unsafe { acl_set_file(c_path.as_ptr(), flags, self.acl) };
176        if ret == 0 {
177            Ok(())
178        } else {
179            Err(ACLError::last_os_error(FLAG_WRITE | flags))
180        }
181    }
182
183    /// Iterator of `acl_entry_t`, unsafe
184    pub(crate) unsafe fn raw_iter(&self) -> RawACLIterator {
185        RawACLIterator::new(self)
186    }
187
188    /// Get all `ACLEntry` items. The POSIX ACL C API does not allow multiple parallel iterators so we
189    /// return a materialized vector just to be safe.
190    #[must_use]
191    pub fn entries(&self) -> Vec<ACLEntry> {
192        unsafe { self.raw_iter() }
193            .map(ACLEntry::from_entry)
194            .collect()
195    }
196
197    /// Get the current `perm` value of `qual`, if any.
198    #[must_use]
199    pub fn get(&self, qual: Qualifier) -> Option<u32> {
200        let entry = self.raw_get_entry(&qual)?;
201
202        // XXX inefficient, no need to construct ACLEntry.
203        Some(ACLEntry::from_entry(entry).perm)
204    }
205
206    /// Set the permission of `qual` to `perm`. If this `qual` already exists, it is updated,
207    /// otherwise a new one is added.
208    ///
209    /// `perm` must be a combination of the `ACL_` constants, combined by binary OR.
210    pub fn set(&mut self, qual: Qualifier, perm: u32) {
211        let entry = match self.raw_get_entry(&qual) {
212            Some(v) => v,
213            None => self.raw_add_entry(&qual),
214        };
215
216        Self::raw_set_permset(entry, perm);
217    }
218
219    /// Remove entry with matching `qual`. If found, returns the matching `perm`, otherwise `None`
220    #[allow(clippy::must_use_candidate)]
221    pub fn remove(&self, qual: Qualifier) -> Option<u32> {
222        let entry = self.raw_get_entry(&qual)?;
223        let wrapped = ACLEntry::from_entry(entry);
224
225        unsafe {
226            acl_delete_entry(self.acl, entry);
227        }
228
229        // XXX inefficient, no need to construct ACLEntry.
230        Some(wrapped.perm)
231    }
232
233    fn raw_set_permset(entry: acl_entry_t, perm: u32) {
234        unsafe {
235            let mut permset: acl_permset_t = null_mut();
236            check_return(acl_get_permset(entry, &mut permset), "acl_get_permset");
237            check_return(acl_clear_perms(permset), "acl_clear_perms");
238            check_return(acl_add_perm(permset, perm), "acl_add_perm");
239            check_return(acl_set_permset(entry, permset), "acl_set_permset");
240        }
241    }
242
243    fn raw_get_entry(&self, qual: &Qualifier) -> Option<acl_entry_t> {
244        unsafe { self.raw_iter() }.find(
245            // XXX this is slightly inefficient, calls to get_entry_uid() could be short-circuited.
246            |&entry| Qualifier::from_entry(entry) == *qual,
247        )
248    }
249
250    fn raw_add_entry(&mut self, qual: &Qualifier) -> acl_entry_t {
251        let mut entry: acl_entry_t = null_mut();
252        unsafe {
253            check_return(
254                acl_create_entry(&mut self.acl, &mut entry),
255                "acl_create_entry",
256            );
257            check_return(acl_set_tag_type(entry, qual.tag_type()), "acl_set_tag_type");
258            if let Some(uid) = qual.uid() {
259                check_return(
260                    acl_set_qualifier(entry, addr_of!(uid).cast::<c_void>()),
261                    "acl_set_qualifier",
262                );
263            }
264        }
265        entry
266    }
267
268    /// Re-calculate the `Qualifier::Mask` entry.
269    ///
270    /// Usually there is no need to call this directly, as this is done during
271    /// `write_acl/write_default_acl()` automatically.
272    pub fn fix_mask(&mut self) {
273        unsafe {
274            check_return(acl_calc_mask(&mut self.acl), "acl_calc_mask");
275        }
276    }
277
278    /// Return the textual representation of the ACL. Individual entries are separated by newline
279    /// (`'\n'`).
280    ///
281    /// UID/GID are automatically resolved to names by the platform.
282    ///
283    /// # Panics
284    ///
285    /// When platform returns a string that is not valid UTF-8.
286    #[must_use]
287    pub fn as_text(&self) -> String {
288        let mut len: ssize_t = 0;
289        let txt = AutoPtr(unsafe { acl_to_text(self.acl, &mut len) });
290        check_pointer(txt.0, "acl_to_text");
291        let len = usize::try_from(len).expect("Length should be positive");
292        let chars = unsafe { from_raw_parts(txt.0.cast::<u8>(), len) };
293
294        from_utf8(chars).expect("Not valid UTF-8").to_string()
295    }
296
297    fn compact_text(&self) -> String {
298        self.as_text().trim_end().replace('\n', ",")
299    }
300
301    /// Call the platform's validation function.
302    ///
303    /// Usually there is no need to explicitly call this method, the `write_acl()` method validates
304    /// ACL prior to writing.
305    /// If you didn't take special care of the `Mask` entry, it may be necessary to call
306    /// `fix_mask()` prior to `validate()`.
307    ///
308    /// # Errors
309    /// * `ACLError::ValidationError`: The ACL failed validation.
310    ///
311    /// Unfortunately it is not possible to provide detailed error reasons, but mainly it can be:
312    /// * Required entries are missing (`UserObj`, `GroupObj`, `Mask` and `Other`).
313    /// * ACL contains entries that are not unique.
314    pub fn validate(&self) -> Result<(), ACLError> {
315        let ret = unsafe { acl_valid(self.acl) };
316        if ret == 0 {
317            Ok(())
318        } else {
319            Err(ACLError::validation_error())
320        }
321    }
322
323    /// Consumes the `PosixACL`, returning the wrapped `acl_t`.
324    /// This can then be used directly in FFI calls to the acl library.
325    ///
326    /// To avoid a memory leak, the `acl_t` must either:
327    ///
328    /// - Be converted back to a `PosixACL` using [`PosixACL::from_raw()`]
329    /// - Have `acl_free()` called on it
330    //
331    // Note: it's typically considered safe for Rust functions to leak resources (in this specific
332    // case, the function is analogous to the safe `Rc::into_raw` function in the standard library).
333    // For more discussion on this, see [the nomicon](https://doc.rust-lang.org/nomicon/leaking.html).
334    #[must_use]
335    pub fn into_raw(self) -> acl_t {
336        let acl = self.acl;
337        mem::forget(self);
338        acl
339    }
340
341    /// Constructs a `PosixACL` from a raw `acl_t`. You should treat the `acl_t`
342    /// as being 'consumed' by this function.
343    ///
344    /// # Safety
345    ///
346    /// The `acl_t` must be a valid ACL (not `(acl_t)NULL`) acl returned
347    /// either [`PosixACL::into_raw()`] or another ACL library function.
348    ///
349    /// Improper usage of this function may lead to memory unsafety (e.g.
350    /// calling it twice on the same acl may lead to a double free).
351    pub unsafe fn from_raw(acl: acl_t) -> Self {
352        Self { acl }
353    }
354}