sys_traits/impls/
real.rs

1use std::borrow::Cow;
2use std::env;
3use std::fs;
4use std::io::Result;
5use std::path::Path;
6use std::path::PathBuf;
7use std::time::SystemTime;
8
9use io::IsTerminal;
10
11use super::strip_unc_prefix;
12use super::RealSys;
13
14use crate::*;
15
16// ==== Environment ====
17
18impl EnvCurrentDir for RealSys {
19  #[inline]
20  fn env_current_dir(&self) -> std::io::Result<PathBuf> {
21    env::current_dir()
22  }
23}
24
25impl BaseEnvSetCurrentDir for RealSys {
26  #[inline]
27  fn base_env_set_current_dir(&self, path: &Path) -> std::io::Result<()> {
28    env::set_current_dir(path)
29  }
30}
31
32impl BaseEnvVar for RealSys {
33  #[inline]
34  fn base_env_var_os(&self, key: &OsStr) -> Option<OsString> {
35    env::var_os(key)
36  }
37}
38
39impl BaseEnvSetVar for RealSys {
40  #[inline]
41  fn base_env_set_var(&self, key: &OsStr, value: &OsStr) {
42    env::set_var(key, value);
43  }
44}
45
46#[cfg(all(unix, feature = "libc"))]
47impl EnvUmask for RealSys {
48  fn env_umask(&self) -> std::io::Result<u32> {
49    use libc::mode_t;
50    use libc::umask;
51
52    // SAFETY: libc calls
53    unsafe {
54      // unfortuantely there's no way to get the umask without setting it
55      // temporarily... so we set the value then restore it after
56      let current_umask = umask(0o000 as mode_t);
57      umask(current_umask);
58      Ok(current_umask as u32)
59    }
60  }
61}
62
63#[cfg(not(unix))]
64impl EnvUmask for RealSys {
65  fn env_umask(&self) -> std::io::Result<u32> {
66    Err(std::io::Error::new(
67      ErrorKind::Unsupported,
68      "umask is not supported on this platform",
69    ))
70  }
71}
72
73#[cfg(all(unix, feature = "libc"))]
74impl EnvSetUmask for RealSys {
75  fn env_set_umask(&self, value: u32) -> std::io::Result<u32> {
76    // SAFETY: libc calls
77    unsafe {
78      use libc::mode_t;
79      use libc::umask;
80
81      let current_umask = umask(value as mode_t);
82      Ok(current_umask as u32)
83    }
84  }
85}
86
87#[cfg(not(unix))]
88impl EnvSetUmask for RealSys {
89  fn env_set_umask(&self, _umask: u32) -> std::io::Result<u32> {
90    Err(std::io::Error::new(
91      ErrorKind::Unsupported,
92      "umask is not supported on this platform",
93    ))
94  }
95}
96
97#[cfg(any(
98  all(target_os = "windows", feature = "winapi"),
99  all(unix, feature = "libc")
100))]
101impl EnvCacheDir for RealSys {
102  #[inline]
103  fn env_cache_dir(&self) -> Option<PathBuf> {
104    real_cache_dir_with_env(self)
105  }
106}
107
108/// Uses the provided env for environment variables and the home
109/// directory, but falls back to real sys calls.
110#[cfg(any(
111  all(target_os = "windows", feature = "winapi"),
112  all(unix, feature = "libc")
113))]
114pub fn real_cache_dir_with_env(
115  env: &(impl EnvVar + EnvHomeDir),
116) -> Option<PathBuf> {
117  #[cfg(all(target_os = "windows", feature = "winapi"))]
118  {
119    let _ = env;
120    known_folder(&windows_sys::Win32::UI::Shell::FOLDERID_LocalAppData)
121  }
122  #[cfg(all(unix, feature = "libc"))]
123  {
124    if cfg!(target_os = "macos") {
125      env.env_home_dir().map(|h| h.join("Library/Caches"))
126    } else {
127      env
128        .env_var_path("XDG_CACHE_HOME")
129        .or_else(|| env.env_home_dir().map(|home| home.join(".cache")))
130    }
131  }
132}
133
134#[cfg(any(
135  all(target_os = "windows", feature = "winapi"),
136  all(unix, feature = "libc")
137))]
138impl EnvHomeDir for RealSys {
139  fn env_home_dir(&self) -> Option<PathBuf> {
140    real_home_dir_with_env(self)
141  }
142}
143
144/// Uses the provided env for environment variables, but falls
145/// back to real sys calls.
146#[cfg(any(
147  all(target_os = "windows", feature = "winapi"),
148  all(unix, feature = "libc")
149))]
150pub fn real_home_dir_with_env(env: &impl EnvVar) -> Option<PathBuf> {
151  #[cfg(all(target_os = "windows", feature = "winapi"))]
152  {
153    env.env_var_path("USERPROFILE").or_else(|| {
154      known_folder(&windows_sys::Win32::UI::Shell::FOLDERID_Profile)
155    })
156  }
157  #[cfg(all(unix, feature = "libc"))]
158  {
159    // This piece of code was taken from the deprecated home_dir() function in Rust's standard library
160    unsafe fn fallback() -> Option<std::ffi::OsString> {
161      let amt = match libc::sysconf(libc::_SC_GETPW_R_SIZE_MAX) {
162        n if n < 0 => 512_usize,
163        n => n as usize,
164      };
165      let mut buf = Vec::with_capacity(amt);
166      let mut passwd: libc::passwd = std::mem::zeroed();
167      let mut result = std::ptr::null_mut();
168      match libc::getpwuid_r(
169        libc::getuid(),
170        &mut passwd,
171        buf.as_mut_ptr(),
172        buf.capacity(),
173        &mut result,
174      ) {
175        0 if !result.is_null() => {
176          let ptr = passwd.pw_dir as *const _;
177          let bytes = std::ffi::CStr::from_ptr(ptr).to_bytes().to_vec();
178          Some(std::os::unix::ffi::OsStringExt::from_vec(bytes))
179        }
180        _ => None,
181      }
182    }
183
184    env.env_var_path("HOME").or_else(|| {
185      // SAFETY: libc
186      unsafe { fallback().map(PathBuf::from) }
187    })
188  }
189}
190
191impl EnvTempDir for RealSys {
192  #[inline]
193  fn env_temp_dir(&self) -> std::io::Result<PathBuf> {
194    Ok(env::temp_dir())
195  }
196}
197
198// ==== File System ====
199
200impl BaseFsCanonicalize for RealSys {
201  #[inline]
202  fn base_fs_canonicalize(&self, path: &Path) -> Result<PathBuf> {
203    fs::canonicalize(path).map(strip_unc_prefix)
204  }
205}
206
207#[cfg(unix)]
208impl BaseFsChown for RealSys {
209  #[inline]
210  fn base_fs_chown(
211    &self,
212    path: &Path,
213    uid: Option<u32>,
214    gid: Option<u32>,
215  ) -> io::Result<()> {
216    std::os::unix::fs::chown(path, uid, gid)
217  }
218}
219
220#[cfg(not(unix))]
221impl BaseFsChown for RealSys {
222  #[inline]
223  fn base_fs_chown(
224    &self,
225    _path: &Path,
226    _uid: Option<u32>,
227    _gid: Option<u32>,
228  ) -> io::Result<()> {
229    Err(Error::new(
230      ErrorKind::Unsupported,
231      "chown is not supported on this platform",
232    ))
233  }
234}
235
236#[cfg(unix)]
237impl BaseFsSymlinkChown for RealSys {
238  #[inline]
239  fn base_fs_symlink_chown(
240    &self,
241    path: &Path,
242    uid: Option<u32>,
243    gid: Option<u32>,
244  ) -> io::Result<()> {
245    std::os::unix::fs::lchown(path, uid, gid)
246  }
247}
248
249#[cfg(not(unix))]
250impl BaseFsSymlinkChown for RealSys {
251  #[inline]
252  fn base_fs_symlink_chown(
253    &self,
254    _path: &Path,
255    _uid: Option<u32>,
256    _gid: Option<u32>,
257  ) -> io::Result<()> {
258    Err(Error::new(
259      ErrorKind::Unsupported,
260      "lchown is not supported on this platform",
261    ))
262  }
263}
264
265#[cfg(all(target_vendor = "apple", feature = "libc"))]
266impl BaseFsCloneFile for RealSys {
267  #[inline]
268  fn base_fs_clone_file(&self, from: &Path, to: &Path) -> std::io::Result<()> {
269    use std::os::unix::ffi::OsStrExt;
270    let from = std::ffi::CString::new(from.as_os_str().as_bytes())?;
271    let to = std::ffi::CString::new(to.as_os_str().as_bytes())?;
272    // SAFETY: `from` and `to` are valid C strings.
273    let ret = unsafe { libc::clonefile(from.as_ptr(), to.as_ptr(), 0) };
274    if ret != 0 {
275      return Err(std::io::Error::last_os_error());
276    }
277    Ok(())
278  }
279}
280
281#[cfg(not(all(target_vendor = "apple", feature = "libc")))]
282impl BaseFsCloneFile for RealSys {
283  fn base_fs_clone_file(&self, _from: &Path, _to: &Path) -> io::Result<()> {
284    Err(std::io::Error::new(
285      ErrorKind::Unsupported,
286      "clonefile is not supported on this platform or the libc feature in sys_traits is not enabled",
287    ))
288  }
289}
290
291impl BaseFsCopy for RealSys {
292  #[inline]
293  fn base_fs_copy(&self, from: &Path, to: &Path) -> std::io::Result<u64> {
294    fs::copy(from, to)
295  }
296}
297
298impl BaseFsCreateDir for RealSys {
299  fn base_fs_create_dir(
300    &self,
301    path: &Path,
302    options: &CreateDirOptions,
303  ) -> Result<()> {
304    let mut builder = fs::DirBuilder::new();
305    builder.recursive(options.recursive);
306    #[cfg(unix)]
307    {
308      use std::os::unix::fs::DirBuilderExt;
309      if let Some(mode) = options.mode {
310        builder.mode(mode);
311      }
312    }
313    builder.create(path)
314  }
315}
316
317impl BaseFsHardLink for RealSys {
318  #[inline]
319  fn base_fs_hard_link(&self, src: &Path, dst: &Path) -> Result<()> {
320    fs::hard_link(src, dst)
321  }
322}
323
324macro_rules! unix_metadata_prop {
325  ($id:ident, $type:ident) => {
326    #[inline]
327    fn $id(&self) -> Result<$type> {
328      #[cfg(unix)]
329      {
330        use std::os::unix::fs::MetadataExt;
331        Ok(self.0.$id())
332      }
333      #[cfg(not(unix))]
334      {
335        Err(Error::new(
336          ErrorKind::Unsupported,
337          concat!(stringify!($id), " is not supported on this platform"),
338        ))
339      }
340    }
341  };
342}
343
344macro_rules! unix_metadata_file_type_prop {
345  ($id:ident, $type:ident) => {
346    #[inline]
347    fn $id(&self) -> Result<$type> {
348      #[cfg(unix)]
349      {
350        use std::os::unix::fs::FileTypeExt;
351        Ok(self.0.file_type().$id())
352      }
353      #[cfg(not(unix))]
354      {
355        Err(Error::new(
356          ErrorKind::Unsupported,
357          concat!(stringify!($id), " is not supported on this platform"),
358        ))
359      }
360    }
361  };
362}
363
364/// A wrapper type is used in order to force usages to
365/// `use sys_traits::FsMetadataValue` so that the code
366/// compiles under Wasm.
367#[derive(Debug, Clone)]
368pub struct RealFsMetadata(fs::Metadata);
369
370impl FsMetadataValue for RealFsMetadata {
371  #[inline]
372  fn file_type(&self) -> FileType {
373    self.0.file_type().into()
374  }
375
376  #[inline]
377  fn len(&self) -> u64 {
378    self.0.len()
379  }
380
381  #[inline]
382  fn accessed(&self) -> Result<SystemTime> {
383    self.0.accessed()
384  }
385
386  #[inline]
387  fn changed(&self) -> Result<SystemTime> {
388    #[cfg(unix)]
389    {
390      use std::os::unix::fs::MetadataExt;
391      let changed = self.0.ctime();
392      Ok(
393        SystemTime::UNIX_EPOCH + std::time::Duration::from_secs(changed as u64),
394      )
395    }
396    #[cfg(not(unix))]
397    {
398      Err(Error::new(
399        ErrorKind::Unsupported,
400        "ctime is not supported on this platform",
401      ))
402    }
403  }
404
405  #[inline]
406  fn created(&self) -> Result<SystemTime> {
407    self.0.created()
408  }
409
410  #[inline]
411  fn modified(&self) -> Result<SystemTime> {
412    self.0.modified()
413  }
414
415  unix_metadata_prop!(dev, u64);
416  unix_metadata_prop!(ino, u64);
417  unix_metadata_prop!(mode, u32);
418  unix_metadata_prop!(nlink, u64);
419  unix_metadata_prop!(uid, u32);
420  unix_metadata_prop!(gid, u32);
421  unix_metadata_prop!(rdev, u64);
422  unix_metadata_prop!(blksize, u64);
423  unix_metadata_prop!(blocks, u64);
424  unix_metadata_file_type_prop!(is_block_device, bool);
425  unix_metadata_file_type_prop!(is_char_device, bool);
426  unix_metadata_file_type_prop!(is_fifo, bool);
427  unix_metadata_file_type_prop!(is_socket, bool);
428
429  fn file_attributes(&self) -> io::Result<u32> {
430    #[cfg(windows)]
431    {
432      use std::os::windows::prelude::MetadataExt;
433      Ok(self.0.file_attributes())
434    }
435    #[cfg(not(windows))]
436    {
437      Err(Error::new(
438        ErrorKind::Unsupported,
439        "file_attributes is not supported on this platform",
440      ))
441    }
442  }
443}
444
445impl BaseFsMetadata for RealSys {
446  type Metadata = RealFsMetadata;
447
448  #[inline]
449  fn base_fs_metadata(&self, path: &Path) -> Result<Self::Metadata> {
450    fs::metadata(path).map(RealFsMetadata)
451  }
452
453  #[inline]
454  fn base_fs_symlink_metadata(&self, path: &Path) -> Result<Self::Metadata> {
455    fs::symlink_metadata(path).map(RealFsMetadata)
456  }
457
458  #[cfg(any(all(unix, feature = "libc"), all(windows, feature = "winapi")))]
459  #[inline]
460  fn base_fs_exists_no_err(&self, path: &Path) -> bool {
461    #[cfg(unix)]
462    {
463      use libc::access;
464      use libc::F_OK;
465      use std::os::unix::ffi::OsStrExt;
466
467      let Ok(c_path) = std::ffi::CString::new(path.as_os_str().as_bytes())
468      else {
469        return false;
470      };
471
472      // Safety: `access` is a system call and we ensure `c_path` is a valid C string.
473      unsafe { access(c_path.as_ptr(), F_OK) == 0 }
474    }
475
476    #[cfg(windows)]
477    {
478      use std::os::windows::ffi::OsStrExt;
479      use windows_sys::Win32::Storage::FileSystem::GetFileAttributesW;
480      use windows_sys::Win32::Storage::FileSystem::INVALID_FILE_ATTRIBUTES;
481
482      let wide_path: Vec<u16> = path
483        .as_os_str()
484        .encode_wide()
485        .chain(std::iter::once(0))
486        .collect();
487
488      // Safety: `GetFileAttributesW` is a Windows API call, and `wide_path` is null-terminated.
489      unsafe {
490        GetFileAttributesW(wide_path.as_ptr()) != INVALID_FILE_ATTRIBUTES
491      }
492    }
493  }
494}
495
496impl BaseFsOpen for RealSys {
497  type File = RealFsFile;
498
499  fn base_fs_open(
500    &self,
501    path: &Path,
502    options: &OpenOptions,
503  ) -> std::io::Result<Self::File> {
504    let mut builder = fs::OpenOptions::new();
505    if let Some(mode) = options.mode {
506      #[cfg(unix)]
507      {
508        use std::os::unix::fs::OpenOptionsExt;
509        builder.mode(mode);
510      }
511      #[cfg(not(unix))]
512      let _ = mode;
513    }
514    if let Some(flags) = options.custom_flags {
515      #[cfg(unix)]
516      {
517        use std::os::unix::fs::OpenOptionsExt;
518        builder.custom_flags(flags);
519      }
520      #[cfg(windows)]
521      {
522        use std::os::windows::fs::OpenOptionsExt;
523        builder.custom_flags(flags);
524      }
525      #[cfg(all(not(windows), not(unix)))]
526      let _ = flags;
527    }
528    if let Some(value) = options.access_mode {
529      #[cfg(windows)]
530      {
531        use std::os::windows::fs::OpenOptionsExt;
532        builder.access_mode(value);
533      }
534      #[cfg(not(windows))]
535      let _ = value;
536    }
537    if let Some(value) = options.share_mode {
538      #[cfg(windows)]
539      {
540        use std::os::windows::fs::OpenOptionsExt;
541        builder.share_mode(value);
542      }
543      #[cfg(not(windows))]
544      let _ = value;
545    }
546    if let Some(value) = options.attributes {
547      #[cfg(windows)]
548      {
549        use std::os::windows::fs::OpenOptionsExt;
550        builder.attributes(value);
551      }
552      #[cfg(not(windows))]
553      let _ = value;
554    }
555    if let Some(value) = options.security_qos_flags {
556      #[cfg(windows)]
557      {
558        use std::os::windows::fs::OpenOptionsExt;
559        builder.security_qos_flags(value);
560      }
561      #[cfg(not(windows))]
562      let _ = value;
563    }
564    builder
565      .read(options.read)
566      .write(options.write)
567      .create(options.create)
568      .truncate(options.truncate)
569      .append(options.append)
570      .create_new(options.create_new)
571      .open(path)
572      .map(RealFsFile)
573  }
574}
575
576impl BaseFsRead for RealSys {
577  #[inline]
578  fn base_fs_read(&self, path: &Path) -> Result<Cow<'static, [u8]>> {
579    fs::read(path).map(Cow::Owned)
580  }
581}
582
583#[derive(Debug)]
584pub struct RealFsDirEntry(fs::DirEntry);
585
586impl FsDirEntry for RealFsDirEntry {
587  type Metadata = RealFsMetadata;
588
589  #[inline]
590  fn file_name(&self) -> Cow<OsStr> {
591    Cow::Owned(self.0.file_name())
592  }
593
594  #[inline]
595  fn file_type(&self) -> std::io::Result<FileType> {
596    self.0.file_type().map(FileType::from)
597  }
598
599  #[inline]
600  fn metadata(&self) -> std::io::Result<Self::Metadata> {
601    self.0.metadata().map(RealFsMetadata)
602  }
603
604  #[inline]
605  fn path(&self) -> Cow<Path> {
606    Cow::Owned(self.0.path())
607  }
608}
609
610impl BaseFsReadDir for RealSys {
611  type ReadDirEntry = RealFsDirEntry;
612
613  #[inline]
614  fn base_fs_read_dir(
615    &self,
616    path: &Path,
617  ) -> std::io::Result<
618    Box<dyn Iterator<Item = std::io::Result<Self::ReadDirEntry>> + '_>,
619  > {
620    let iterator = fs::read_dir(path)?;
621    Ok(Box::new(iterator.map(|result| result.map(RealFsDirEntry))))
622  }
623}
624
625impl BaseFsReadLink for RealSys {
626  fn base_fs_read_link(&self, path: &Path) -> io::Result<PathBuf> {
627    fs::read_link(path)
628  }
629}
630
631impl BaseFsRemoveDir for RealSys {
632  #[inline]
633  fn base_fs_remove_dir(&self, path: &Path) -> std::io::Result<()> {
634    fs::remove_dir(path)
635  }
636}
637
638impl BaseFsRemoveDirAll for RealSys {
639  #[inline]
640  fn base_fs_remove_dir_all(&self, path: &Path) -> std::io::Result<()> {
641    fs::remove_dir_all(path)
642  }
643}
644
645impl BaseFsRemoveFile for RealSys {
646  #[inline]
647  fn base_fs_remove_file(&self, path: &Path) -> std::io::Result<()> {
648    fs::remove_file(path)
649  }
650}
651
652impl BaseFsRename for RealSys {
653  #[inline]
654  fn base_fs_rename(&self, from: &Path, to: &Path) -> std::io::Result<()> {
655    fs::rename(from, to)
656  }
657}
658
659#[cfg(feature = "filetime")]
660impl BaseFsSetFileTimes for RealSys {
661  #[inline]
662  fn base_fs_set_file_times(
663    &self,
664    path: &Path,
665    atime: SystemTime,
666    mtime: SystemTime,
667  ) -> Result<()> {
668    let atime = filetime::FileTime::from_system_time(atime);
669    let mtime = filetime::FileTime::from_system_time(mtime);
670    filetime::set_file_times(path, atime, mtime)
671  }
672}
673
674#[cfg(feature = "filetime")]
675impl BaseFsSetSymlinkFileTimes for RealSys {
676  #[inline]
677  fn base_fs_set_symlink_file_times(
678    &self,
679    path: &Path,
680    atime: SystemTime,
681    mtime: SystemTime,
682  ) -> Result<()> {
683    let atime = filetime::FileTime::from_system_time(atime);
684    let mtime = filetime::FileTime::from_system_time(mtime);
685    filetime::set_symlink_file_times(path, atime, mtime)
686  }
687}
688
689#[cfg(unix)]
690impl BaseFsSetPermissions for RealSys {
691  #[inline]
692  fn base_fs_set_permissions(
693    &self,
694    path: &Path,
695    mode: u32,
696  ) -> std::io::Result<()> {
697    use std::os::unix::fs::PermissionsExt;
698    let permissions = fs::Permissions::from_mode(mode);
699    fs::set_permissions(path, permissions)
700  }
701}
702
703#[cfg(not(unix))]
704impl BaseFsSetPermissions for RealSys {
705  fn base_fs_set_permissions(
706    &self,
707    _path: &Path,
708    _mode: u32,
709  ) -> std::io::Result<()> {
710    Err(std::io::Error::new(
711      ErrorKind::Unsupported,
712      "cannot set path permissions on this platform",
713    ))
714  }
715}
716
717impl BaseFsSymlinkDir for RealSys {
718  fn base_fs_symlink_dir(
719    &self,
720    original: &Path,
721    link: &Path,
722  ) -> std::io::Result<()> {
723    #[cfg(windows)]
724    {
725      std::os::windows::fs::symlink_dir(original, link)
726    }
727    #[cfg(not(windows))]
728    {
729      std::os::unix::fs::symlink(original, link)
730    }
731  }
732}
733
734impl BaseFsSymlinkFile for RealSys {
735  fn base_fs_symlink_file(
736    &self,
737    original: &Path,
738    link: &Path,
739  ) -> std::io::Result<()> {
740    #[cfg(windows)]
741    {
742      std::os::windows::fs::symlink_file(original, link)
743    }
744    #[cfg(not(windows))]
745    {
746      std::os::unix::fs::symlink(original, link)
747    }
748  }
749}
750
751impl BaseFsWrite for RealSys {
752  #[inline]
753  fn base_fs_write(&self, path: &Path, data: &[u8]) -> std::io::Result<()> {
754    fs::write(path, data)
755  }
756}
757
758// ==== File System File ====
759
760/// A wrapper type is used in order to force usages to
761/// `use sys_traits::FsFile` so that the code
762/// compiles under Wasm.
763#[derive(Debug)]
764pub struct RealFsFile(fs::File);
765
766impl FsFile for RealFsFile {}
767
768impl FsFileAsRaw for RealFsFile {
769  #[cfg(windows)]
770  #[inline]
771  fn fs_file_as_raw_handle(&self) -> Option<std::os::windows::io::RawHandle> {
772    use std::os::windows::io::AsRawHandle;
773    Some(self.0.as_raw_handle())
774  }
775
776  #[cfg(unix)]
777  #[inline]
778  fn fs_file_as_raw_fd(&self) -> Option<std::os::fd::RawFd> {
779    use std::os::fd::AsRawFd;
780    Some(self.0.as_raw_fd())
781  }
782}
783
784impl FsFileIsTerminal for RealFsFile {
785  #[inline]
786  fn fs_file_is_terminal(&self) -> bool {
787    self.0.is_terminal()
788  }
789}
790
791impl FsFileLock for RealFsFile {
792  fn fs_file_lock(&mut self, mode: FsFileLockMode) -> io::Result<()> {
793    lock_file(&self.0, mode, false)
794  }
795
796  fn fs_file_try_lock(&mut self, mode: FsFileLockMode) -> io::Result<()> {
797    lock_file(&self.0, mode, true)
798  }
799
800  fn fs_file_unlock(&mut self) -> io::Result<()> {
801    unlock_file(&self.0)
802  }
803}
804
805#[cfg(all(unix, feature = "libc"))]
806fn lock_file(
807  file: &fs::File,
808  mode: FsFileLockMode,
809  try_lock: bool,
810) -> Result<()> {
811  let operation = match mode {
812    FsFileLockMode::Shared => libc::LOCK_SH,
813    FsFileLockMode::Exclusive => libc::LOCK_EX,
814  } | if try_lock { libc::LOCK_NB } else { 0 };
815
816  flock(file, operation)
817}
818
819#[cfg(all(unix, feature = "libc"))]
820#[inline]
821fn unlock_file(file: &fs::File) -> Result<()> {
822  flock(file, libc::LOCK_UN)
823}
824
825#[cfg(all(unix, feature = "libc"))]
826fn flock(file: &fs::File, operation: i32) -> Result<()> {
827  use std::os::unix::io::AsRawFd;
828
829  // SAFETY: libc calls
830  unsafe {
831    let fd = file.as_raw_fd();
832    let result = libc::flock(fd, operation);
833    if result < 0 {
834      Err(Error::last_os_error())
835    } else {
836      Ok(())
837    }
838  }
839}
840
841#[cfg(all(windows, feature = "winapi"))]
842fn lock_file(
843  file: &fs::File,
844  mode: FsFileLockMode,
845  try_lock: bool,
846) -> Result<()> {
847  use std::os::windows::io::AsRawHandle;
848
849  use windows_sys::Win32::Foundation::FALSE;
850  use windows_sys::Win32::Storage::FileSystem::LockFileEx;
851  use windows_sys::Win32::Storage::FileSystem::LOCKFILE_EXCLUSIVE_LOCK;
852  use windows_sys::Win32::Storage::FileSystem::LOCKFILE_FAIL_IMMEDIATELY;
853
854  let flags = match mode {
855    FsFileLockMode::Shared => 0,
856    FsFileLockMode::Exclusive => LOCKFILE_EXCLUSIVE_LOCK,
857  } | if try_lock {
858    LOCKFILE_FAIL_IMMEDIATELY
859  } else {
860    0
861  };
862
863  // SAFETY: winapi calls
864  unsafe {
865    let mut overlapped = std::mem::zeroed();
866    let success =
867      LockFileEx(file.as_raw_handle(), flags, 0, !0, !0, &mut overlapped);
868    if success == FALSE {
869      Err(Error::last_os_error())
870    } else {
871      Ok(())
872    }
873  }
874}
875
876#[cfg(all(windows, feature = "winapi"))]
877fn unlock_file(file: &fs::File) -> Result<()> {
878  use std::os::windows::io::AsRawHandle;
879
880  use windows_sys::Win32::Foundation::FALSE;
881  use windows_sys::Win32::Storage::FileSystem::UnlockFile;
882
883  // SAFETY: winapi calls
884  unsafe {
885    let success = UnlockFile(file.as_raw_handle(), 0, 0, !0, !0);
886    if success == FALSE {
887      Err(Error::last_os_error())
888    } else {
889      Ok(())
890    }
891  }
892}
893
894#[cfg(not(any(
895  all(unix, feature = "libc"),
896  all(windows, feature = "winapi")
897)))]
898fn lock_file(
899  _file: &fs::File,
900  _mode: FsFileLockMode,
901  _try_lock: bool,
902) -> Result<()> {
903  Err(Error::new(
904    ErrorKind::Unsupported,
905    "file locking is not supported on this platform or the libc/winapi feature is not enabled",
906  ))
907}
908
909#[cfg(not(any(
910  all(unix, feature = "libc"),
911  all(windows, feature = "winapi")
912)))]
913fn unlock_file(_file: &fs::File) -> Result<()> {
914  Err(Error::new(
915    ErrorKind::Unsupported,
916    "file locking is not supported on this platform or the libc/winapi feature is not enabled",
917  ))
918}
919
920impl FsFileSetLen for RealFsFile {
921  #[inline]
922  fn fs_file_set_len(&mut self, size: u64) -> std::io::Result<()> {
923    self.0.set_len(size)
924  }
925}
926
927impl FsFileSetPermissions for RealFsFile {
928  #[inline]
929  fn fs_file_set_permissions(&mut self, mode: u32) -> Result<()> {
930    #[cfg(unix)]
931    {
932      use std::os::unix::fs::PermissionsExt;
933      let permissions = fs::Permissions::from_mode(mode);
934      self.0.set_permissions(permissions)
935    }
936    #[cfg(not(unix))]
937    {
938      let _ = mode;
939      Ok(())
940    }
941  }
942}
943
944impl FsFileSetTimes for RealFsFile {
945  fn fs_file_set_times(&mut self, times: FsFileTimes) -> io::Result<()> {
946    let mut std_times = std::fs::FileTimes::new();
947    if let Some(atime) = times.accessed {
948      std_times = std_times.set_accessed(atime);
949    }
950    if let Some(mtime) = times.modified {
951      std_times = std_times.set_modified(mtime);
952    }
953    self.0.set_times(std_times)
954  }
955}
956
957impl FsFileSyncAll for RealFsFile {
958  #[inline]
959  fn fs_file_sync_all(&mut self) -> io::Result<()> {
960    self.0.sync_all()
961  }
962}
963
964impl FsFileSyncData for RealFsFile {
965  #[inline]
966  fn fs_file_sync_data(&mut self) -> io::Result<()> {
967    self.0.sync_data()
968  }
969}
970
971impl std::io::Seek for RealFsFile {
972  #[inline]
973  fn seek(&mut self, pos: std::io::SeekFrom) -> Result<u64> {
974    self.0.seek(pos)
975  }
976}
977
978impl std::io::Write for RealFsFile {
979  #[inline]
980  fn write(&mut self, buf: &[u8]) -> Result<usize> {
981    self.0.write(buf)
982  }
983
984  #[inline]
985  fn flush(&mut self) -> Result<()> {
986    self.0.flush()
987  }
988}
989
990impl std::io::Read for RealFsFile {
991  #[inline]
992  fn read(&mut self, buf: &mut [u8]) -> Result<usize> {
993    self.0.read(buf)
994  }
995}
996
997// ==== System ====
998
999impl SystemTimeNow for RealSys {
1000  #[inline]
1001  fn sys_time_now(&self) -> SystemTime {
1002    SystemTime::now()
1003  }
1004}
1005
1006#[cfg(feature = "getrandom")]
1007impl crate::SystemRandom for RealSys {
1008  #[inline]
1009  fn sys_random(&self, buf: &mut [u8]) -> Result<()> {
1010    getrandom::getrandom(buf)
1011      .map_err(|err| Error::new(ErrorKind::Other, err.to_string()))
1012  }
1013}
1014
1015impl crate::ThreadSleep for RealSys {
1016  #[inline]
1017  fn thread_sleep(&self, duration: std::time::Duration) {
1018    std::thread::sleep(duration);
1019  }
1020}
1021
1022#[cfg(all(windows, feature = "winapi"))]
1023fn known_folder(folder_id: *const windows_sys::core::GUID) -> Option<PathBuf> {
1024  use std::ffi::c_void;
1025  use std::os::windows::ffi::OsStringExt;
1026  use windows_sys::Win32::Foundation::S_OK;
1027  use windows_sys::Win32::Globalization::lstrlenW;
1028  use windows_sys::Win32::System::Com::CoTaskMemFree;
1029  use windows_sys::Win32::UI::Shell::SHGetKnownFolderPath;
1030
1031  // SAFETY: winapi calls
1032  unsafe {
1033    let mut path_ptr = std::ptr::null_mut();
1034    let result =
1035      SHGetKnownFolderPath(folder_id, 0, std::ptr::null_mut(), &mut path_ptr);
1036    if result != S_OK {
1037      return None;
1038    }
1039    let len = lstrlenW(path_ptr) as usize;
1040    let path = std::slice::from_raw_parts(path_ptr, len);
1041    let ostr: OsString = OsStringExt::from_wide(path);
1042    CoTaskMemFree(path_ptr as *mut c_void);
1043    Some(PathBuf::from(ostr))
1044  }
1045}
1046
1047#[cfg(test)]
1048mod test {
1049  use super::*;
1050
1051  #[cfg(any(feature = "winapi", feature = "libc"))]
1052  #[test]
1053  fn test_known_folders() {
1054    assert!(RealSys.env_cache_dir().is_some());
1055    assert!(RealSys.env_home_dir().is_some());
1056  }
1057
1058  #[cfg(all(unix, feature = "libc"))]
1059  #[test]
1060  fn test_umask() {
1061    let original_umask = RealSys.env_umask().unwrap();
1062    assert_eq!(RealSys.env_set_umask(0o777).unwrap(), original_umask);
1063    assert_eq!(RealSys.env_set_umask(original_umask).unwrap(), 0o777);
1064  }
1065
1066  #[cfg(target_os = "windows")]
1067  #[test]
1068  fn test_umask() {
1069    let err = RealSys.env_umask().unwrap_err();
1070    assert_eq!(err.kind(), ErrorKind::Unsupported);
1071    let err = RealSys.env_set_umask(0o000).unwrap_err();
1072    assert_eq!(err.kind(), ErrorKind::Unsupported);
1073  }
1074
1075  #[test]
1076  fn test_general() {
1077    assert!(RealSys.sys_time_now().elapsed().is_ok());
1078  }
1079
1080  #[cfg(any(feature = "winapi", feature = "libc"))]
1081  #[test]
1082  fn lock_file() {
1083    let sys = RealSys;
1084    let mut file = sys.fs_open("Cargo.toml", &OpenOptions::new_read()).unwrap();
1085    file.fs_file_lock(FsFileLockMode::Shared).unwrap();
1086    file.fs_file_unlock().unwrap();
1087    file.fs_file_try_lock(FsFileLockMode::Shared).unwrap();
1088    file.fs_file_unlock().unwrap();
1089    file.fs_file_lock(FsFileLockMode::Exclusive).unwrap();
1090    file.fs_file_unlock().unwrap();
1091    file.fs_file_try_lock(FsFileLockMode::Exclusive).unwrap();
1092    file.fs_file_unlock().unwrap();
1093  }
1094
1095  #[test]
1096  fn test_exists_no_err() {
1097    assert!(RealSys.fs_exists_no_err("Cargo.toml"));
1098    assert!(!RealSys.fs_exists_no_err("Cargo2.toml"));
1099  }
1100
1101  #[test]
1102  fn test_clone_file() {
1103    let temp_dir = tempfile::tempdir().unwrap();
1104    let path = temp_dir.path();
1105    RealSys.fs_write(path.join("file.txt"), "data").unwrap();
1106    let result =
1107      RealSys.fs_clone_file(path.join("file.txt"), path.join("cloned.txt"));
1108    if cfg!(target_vendor = "apple") {
1109      assert!(result.is_ok());
1110      assert_eq!(
1111        RealSys.fs_read_to_string(path.join("cloned.txt")).unwrap(),
1112        "data"
1113      );
1114    } else {
1115      assert_eq!(result.unwrap_err().kind(), ErrorKind::Unsupported);
1116    }
1117  }
1118}