1use std::io;
5use std::path::Path;
6
7use bitflags::bitflags;
8
9bitflags! {
11 pub struct AccessMode: u8 {
13 const EXISTS = 0b0001;
15 const READ = 0b0010;
17 const WRITE = 0b0100;
19 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 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#[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 fn eaccess(p: &Path, mut mode: FILE_ACCESS_RIGHTS) -> io::Result<()> {
269 let md = p.metadata()?;
270
271 if !md.is_dir() {
272 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 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 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#[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}