Skip to main content

helix_stdx/
faccess.rs

1//! Functions for managine file metadata.
2//! From <https://github.com/Freaky/faccess>
3
4use std::io;
5use std::path::Path;
6
7use bitflags::bitflags;
8
9// Licensed under MIT from faccess
10bitflags! {
11    /// Access mode flags for `access` function to test for.
12    pub struct AccessMode: u8 {
13        /// Path exists
14        const EXISTS  = 0b0001;
15        /// Path can likely be read
16        const READ    = 0b0010;
17        /// Path can likely be written to
18        const WRITE   = 0b0100;
19        /// Path can likely be executed
20        const EXECUTE = 0b1000;
21    }
22}
23
24#[cfg(unix)]
25mod imp {
26    use super::*;
27
28    use rustix::fs::Access;
29    use std::os::unix::fs::{MetadataExt, PermissionsExt};
30
31    pub fn access(p: &Path, mode: AccessMode) -> io::Result<()> {
32        #[cfg(target_os = "linux")]
33        {
34            // If helix has ambient CAP_DAC_OVERRIDE, everything is accessible regardless of mode bits
35            use rustix::thread::{capability_is_in_ambient_set, CapabilitySet};
36            if capability_is_in_ambient_set(CapabilitySet::DAC_OVERRIDE).unwrap_or(false) {
37                return Ok(());
38            }
39        }
40
41        let mut imode = Access::empty();
42
43        if mode.contains(AccessMode::EXISTS) {
44            imode |= Access::EXISTS;
45        }
46
47        if mode.contains(AccessMode::READ) {
48            imode |= Access::READ_OK;
49        }
50
51        if mode.contains(AccessMode::WRITE) {
52            imode |= Access::WRITE_OK;
53        }
54
55        if mode.contains(AccessMode::EXECUTE) {
56            imode |= Access::EXEC_OK;
57        }
58
59        rustix::fs::access(p, imode)?;
60        Ok(())
61    }
62
63    fn chown(p: &Path, uid: Option<u32>, gid: Option<u32>) -> io::Result<()> {
64        let uid = uid.map(rustix::fs::Uid::from_raw);
65        let gid = gid.map(rustix::fs::Gid::from_raw);
66        rustix::fs::chown(p, uid, gid)?;
67        Ok(())
68    }
69
70    pub fn copy_metadata(from: &Path, to: &Path) -> io::Result<()> {
71        let from_meta = std::fs::metadata(from)?;
72        let to_meta = std::fs::metadata(to)?;
73        let from_gid = from_meta.gid();
74        let to_gid = to_meta.gid();
75
76        let mut perms = from_meta.permissions();
77        perms.set_mode(perms.mode() & 0o0777);
78        if from_gid != to_gid && chown(to, None, Some(from_gid)).is_err() {
79            let new_perms = (perms.mode() & 0o0707) | ((perms.mode() & 0o07) << 3);
80            perms.set_mode(new_perms);
81        }
82
83        #[cfg(target_os = "macos")]
84        {
85            use std::fs::{File, FileTimes};
86            use std::os::macos::fs::FileTimesExt;
87
88            let to_file = File::options().write(true).open(to)?;
89            let times = FileTimes::new().set_created(from_meta.created()?);
90            to_file.set_times(times)?;
91        }
92
93        std::fs::set_permissions(to, perms)?;
94
95        Ok(())
96    }
97
98    pub fn hardlink_count(p: &Path) -> std::io::Result<u64> {
99        let metadata = p.metadata()?;
100        Ok(metadata.nlink())
101    }
102}
103
104// Licensed under MIT from faccess except for `chown`, `copy_metadata` and `is_acl_inherited`
105#[cfg(windows)]
106mod imp {
107
108    use windows_sys::Win32::Foundation::{CloseHandle, LocalFree, ERROR_SUCCESS, HANDLE};
109    use windows_sys::Win32::Security::Authorization::{
110        GetNamedSecurityInfoW, SetNamedSecurityInfoW, SE_FILE_OBJECT,
111    };
112    use windows_sys::Win32::Security::{
113        AccessCheck, AclSizeInformation, GetAce, GetAclInformation, GetSidIdentifierAuthority,
114        ImpersonateSelf, IsValidAcl, IsValidSid, MapGenericMask, RevertToSelf,
115        SecurityImpersonation, ACCESS_ALLOWED_CALLBACK_ACE, ACL, ACL_SIZE_INFORMATION,
116        DACL_SECURITY_INFORMATION, GENERIC_MAPPING, GROUP_SECURITY_INFORMATION, INHERITED_ACE,
117        LABEL_SECURITY_INFORMATION, OBJECT_SECURITY_INFORMATION, OWNER_SECURITY_INFORMATION,
118        PRIVILEGE_SET, PROTECTED_DACL_SECURITY_INFORMATION, PSECURITY_DESCRIPTOR, PSID,
119        SID_IDENTIFIER_AUTHORITY, TOKEN_DUPLICATE, TOKEN_QUERY,
120    };
121    use windows_sys::Win32::Storage::FileSystem::{
122        GetFileInformationByHandle, BY_HANDLE_FILE_INFORMATION, FILE_ACCESS_RIGHTS,
123        FILE_ALL_ACCESS, FILE_GENERIC_EXECUTE, FILE_GENERIC_READ, FILE_GENERIC_WRITE,
124    };
125    use windows_sys::Win32::System::Threading::{GetCurrentThread, OpenThreadToken};
126
127    use super::*;
128
129    use std::ffi::c_void;
130
131    use std::os::windows::{
132        ffi::OsStrExt,
133        fs::{FileTimesExt, OpenOptionsExt},
134        io::AsRawHandle,
135    };
136
137    use std::fs::{File, FileTimes};
138
139    struct SecurityDescriptor {
140        sd: PSECURITY_DESCRIPTOR,
141        owner: PSID,
142        group: PSID,
143        dacl: *mut ACL,
144    }
145
146    impl Drop for SecurityDescriptor {
147        fn drop(&mut self) {
148            if !self.sd.is_null() {
149                unsafe {
150                    LocalFree(self.sd);
151                }
152            }
153        }
154    }
155
156    impl SecurityDescriptor {
157        fn for_path(p: &Path) -> io::Result<SecurityDescriptor> {
158            let path = std::fs::canonicalize(p)?;
159            let pathos = path.into_os_string();
160            let mut pathw: Vec<u16> = Vec::with_capacity(pathos.len() + 1);
161            pathw.extend(pathos.encode_wide());
162            pathw.push(0);
163
164            let mut sd = std::ptr::null_mut();
165            let mut owner = std::ptr::null_mut();
166            let mut group = std::ptr::null_mut();
167            let mut dacl = std::ptr::null_mut();
168
169            let err = unsafe {
170                GetNamedSecurityInfoW(
171                    pathw.as_ptr(),
172                    SE_FILE_OBJECT,
173                    OWNER_SECURITY_INFORMATION
174                        | GROUP_SECURITY_INFORMATION
175                        | DACL_SECURITY_INFORMATION
176                        | LABEL_SECURITY_INFORMATION,
177                    &mut owner,
178                    &mut group,
179                    &mut dacl,
180                    std::ptr::null_mut(),
181                    &mut sd,
182                )
183            };
184
185            if err == ERROR_SUCCESS {
186                Ok(SecurityDescriptor {
187                    sd,
188                    owner,
189                    group,
190                    dacl,
191                })
192            } else {
193                Err(io::Error::last_os_error())
194            }
195        }
196
197        fn is_acl_inherited(&self) -> bool {
198            let mut acl_info: ACL_SIZE_INFORMATION = unsafe { ::core::mem::zeroed() };
199            let acl_info_ptr: *mut c_void = &mut acl_info as *mut _ as *mut c_void;
200            let mut ace: ACCESS_ALLOWED_CALLBACK_ACE = unsafe { ::core::mem::zeroed() };
201
202            unsafe {
203                GetAclInformation(
204                    self.dacl,
205                    acl_info_ptr,
206                    std::mem::size_of_val(&acl_info) as u32,
207                    AclSizeInformation,
208                )
209            };
210
211            for i in 0..acl_info.AceCount {
212                let mut ptr = &mut ace as *mut _ as *mut c_void;
213                unsafe { GetAce(self.dacl, i, &mut ptr) };
214                if (ace.Header.AceFlags as u32 & INHERITED_ACE) != 0 {
215                    return true;
216                }
217            }
218
219            false
220        }
221
222        fn descriptor(&self) -> &PSECURITY_DESCRIPTOR {
223            &self.sd
224        }
225
226        fn owner(&self) -> &PSID {
227            &self.owner
228        }
229    }
230
231    struct ThreadToken(HANDLE);
232    impl Drop for ThreadToken {
233        fn drop(&mut self) {
234            unsafe {
235                CloseHandle(self.0);
236            }
237        }
238    }
239
240    impl ThreadToken {
241        fn new() -> io::Result<Self> {
242            unsafe {
243                if ImpersonateSelf(SecurityImpersonation) == 0 {
244                    return Err(io::Error::last_os_error());
245                }
246
247                let token: *mut HANDLE = std::ptr::null_mut();
248                let err =
249                    OpenThreadToken(GetCurrentThread(), TOKEN_DUPLICATE | TOKEN_QUERY, 0, token);
250
251                RevertToSelf();
252
253                if err == 0 {
254                    return Err(io::Error::last_os_error());
255                }
256
257                Ok(Self(*token))
258            }
259        }
260
261        fn as_handle(&self) -> &HANDLE {
262            &self.0
263        }
264    }
265
266    // Based roughly on Tcl's NativeAccess()
267    // https://github.com/tcltk/tcl/blob/2ee77587e4dc2150deb06b48f69db948b4ab0584/win/tclWinFile.c
268    fn eaccess(p: &Path, mut mode: FILE_ACCESS_RIGHTS) -> io::Result<()> {
269        let md = p.metadata()?;
270
271        if !md.is_dir() {
272            // Read Only is ignored for directories
273            if mode & FILE_GENERIC_WRITE == FILE_GENERIC_WRITE && md.permissions().readonly() {
274                return Err(io::Error::new(
275                    io::ErrorKind::PermissionDenied,
276                    "File is read only",
277                ));
278            }
279
280            // If it doesn't have the correct extension it isn't executable
281            if mode & FILE_GENERIC_EXECUTE == FILE_GENERIC_EXECUTE {
282                if let Some(ext) = p.extension().and_then(|s| s.to_str()) {
283                    match ext {
284                        "exe" | "com" | "bat" | "cmd" => (),
285                        _ => {
286                            return Err(io::Error::new(
287                                io::ErrorKind::InvalidData,
288                                "File not executable",
289                            ))
290                        }
291                    }
292                }
293            }
294
295            return std::fs::OpenOptions::new()
296                .access_mode(mode)
297                .open(p)
298                .map(|_| ());
299        }
300
301        let sd = SecurityDescriptor::for_path(p)?;
302
303        // Unmapped Samba users are assigned a top level authority of 22
304        // ACL tests are likely to be misleading
305        const SAMBA_UNMAPPED: SID_IDENTIFIER_AUTHORITY = SID_IDENTIFIER_AUTHORITY {
306            Value: [0, 0, 0, 0, 0, 22],
307        };
308        unsafe {
309            let owner = sd.owner();
310            if IsValidSid(*owner) != 0
311                && (*GetSidIdentifierAuthority(*owner)).Value == SAMBA_UNMAPPED.Value
312            {
313                return Ok(());
314            }
315        }
316
317        let token = ThreadToken::new()?;
318
319        let mut privileges: PRIVILEGE_SET = unsafe { std::mem::zeroed() };
320        let mut granted_access: u32 = 0;
321        let mut privileges_length = std::mem::size_of::<PRIVILEGE_SET>() as u32;
322        let mut result = 0;
323
324        let mapping = GENERIC_MAPPING {
325            GenericRead: FILE_GENERIC_READ,
326            GenericWrite: FILE_GENERIC_WRITE,
327            GenericExecute: FILE_GENERIC_EXECUTE,
328            GenericAll: FILE_ALL_ACCESS,
329        };
330
331        unsafe { MapGenericMask(&mut mode, &mapping) };
332
333        if unsafe {
334            AccessCheck(
335                *sd.descriptor(),
336                *token.as_handle(),
337                mode,
338                &mapping,
339                &mut privileges,
340                &mut privileges_length,
341                &mut granted_access,
342                &mut result,
343            )
344        } != 0
345        {
346            if result == 0 {
347                Err(io::Error::new(
348                    io::ErrorKind::PermissionDenied,
349                    "Permission Denied",
350                ))
351            } else {
352                Ok(())
353            }
354        } else {
355            Err(io::Error::last_os_error())
356        }
357    }
358
359    pub fn access(p: &Path, mode: AccessMode) -> io::Result<()> {
360        let mut imode = 0;
361
362        if mode.contains(AccessMode::READ) {
363            imode |= FILE_GENERIC_READ;
364        }
365
366        if mode.contains(AccessMode::WRITE) {
367            imode |= FILE_GENERIC_WRITE;
368        }
369
370        if mode.contains(AccessMode::EXECUTE) {
371            imode |= FILE_GENERIC_EXECUTE;
372        }
373
374        if imode == 0 {
375            if p.exists() {
376                Ok(())
377            } else {
378                Err(io::Error::new(io::ErrorKind::NotFound, "Not Found"))
379            }
380        } else {
381            eaccess(p, imode)
382        }
383    }
384
385    fn chown(p: &Path, sd: SecurityDescriptor) -> io::Result<()> {
386        let path = std::fs::canonicalize(p)?;
387        let pathos = path.as_os_str();
388        let mut pathw = Vec::with_capacity(pathos.len() + 1);
389        pathw.extend(pathos.encode_wide());
390        pathw.push(0);
391
392        let mut owner = std::ptr::null_mut();
393        let mut group = std::ptr::null_mut();
394        let mut dacl = std::ptr::null();
395
396        let mut si = OBJECT_SECURITY_INFORMATION::default();
397        if unsafe { IsValidSid(sd.owner) } != 0 {
398            si |= OWNER_SECURITY_INFORMATION;
399            owner = sd.owner;
400        }
401
402        if unsafe { IsValidSid(sd.group) } != 0 {
403            si |= GROUP_SECURITY_INFORMATION;
404            group = sd.group;
405        }
406
407        if unsafe { IsValidAcl(sd.dacl) } != 0 {
408            si |= DACL_SECURITY_INFORMATION;
409            if !sd.is_acl_inherited() {
410                si |= PROTECTED_DACL_SECURITY_INFORMATION;
411            }
412            dacl = sd.dacl as *const _;
413        }
414
415        let err = unsafe {
416            SetNamedSecurityInfoW(
417                pathw.as_ptr(),
418                SE_FILE_OBJECT,
419                si,
420                owner,
421                group,
422                dacl,
423                std::ptr::null(),
424            )
425        };
426
427        if err == ERROR_SUCCESS {
428            Ok(())
429        } else {
430            Err(io::Error::last_os_error())
431        }
432    }
433
434    pub fn copy_metadata(from: &Path, to: &Path) -> io::Result<()> {
435        let sd = SecurityDescriptor::for_path(from)?;
436        chown(to, sd)?;
437
438        let meta = std::fs::metadata(from)?;
439        let perms = meta.permissions();
440
441        let to_file = File::options().write(true).open(to)?;
442        let times = FileTimes::new().set_created(meta.created()?);
443        to_file.set_times(times)?;
444
445        std::fs::set_permissions(to, perms)?;
446
447        Ok(())
448    }
449
450    pub fn hardlink_count(p: &Path) -> std::io::Result<u64> {
451        let file = std::fs::File::open(p)?;
452        let handle = file.as_raw_handle();
453        let mut info: BY_HANDLE_FILE_INFORMATION = unsafe { std::mem::zeroed() };
454
455        if unsafe { GetFileInformationByHandle(handle, &mut info) } == 0 {
456            Err(std::io::Error::last_os_error())
457        } else {
458            Ok(info.nNumberOfLinks as u64)
459        }
460    }
461}
462
463// Licensed under MIT from faccess except for `copy_metadata`
464#[cfg(not(any(unix, windows)))]
465mod imp {
466    use super::*;
467
468    pub fn access(p: &Path, mode: AccessMode) -> io::Result<()> {
469        if mode.contains(AccessMode::WRITE) {
470            if std::fs::metadata(p)?.permissions().readonly() {
471                return Err(io::Error::new(
472                    io::ErrorKind::PermissionDenied,
473                    "Path is read only",
474                ));
475            } else {
476                return Ok(());
477            }
478        }
479
480        if p.exists() {
481            Ok(())
482        } else {
483            Err(io::Error::new(io::ErrorKind::NotFound, "Path not found"))
484        }
485    }
486
487    pub fn copy_metadata(from: &path, to: &Path) -> io::Result<()> {
488        let meta = std::fs::metadata(from)?;
489        let perms = meta.permissions();
490        std::fs::set_permissions(to, perms)?;
491
492        Ok(())
493    }
494}
495
496pub fn readonly(p: &Path) -> bool {
497    match imp::access(p, AccessMode::WRITE) {
498        Ok(_) => false,
499        Err(err) if err.kind() == std::io::ErrorKind::NotFound => false,
500        Err(_) => true,
501    }
502}
503
504pub fn copy_metadata(from: &Path, to: &Path) -> io::Result<()> {
505    imp::copy_metadata(from, to)
506}
507
508pub fn hardlink_count(p: &Path) -> io::Result<u64> {
509    imp::hardlink_count(p)
510}