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}