//! What differs between the systems a runner runs on (oxygenflow#83): file //! modes, file identities and links are Unix's. On Windows, where a runner //! offers the WASM tier only, modes are not kept (files read as 0644 and //! directories as 0755), each file is its own, and sizes are lengths. use std::fs::{Metadata, OpenOptions}; use std::io; use std::path::Path; /// Set `p`'s permission bits; nothing on Windows. pub fn set_mode(p: &Path, mode: u32) -> io::Result<()> { #[cfg(unix)] { use std::os::unix::fs::PermissionsExt; std::fs::set_permissions(p, std::fs::Permissions::from_mode(mode)) } #[cfg(not(unix))] { let _ = (p, mode); Ok(()) } } /// A file's permission bits (`0o7777` of its mode). pub fn mode(md: &Metadata) -> u32 { #[cfg(unix)] { use std::os::unix::fs::MetadataExt; md.mode() & 0o7777 } #[cfg(not(unix))] { let base = if md.is_dir() { 0o755 } else { 0o644 }; if md.permissions().readonly() { base & !0o222 } else { base } } } /// Open a new file readable and writable by its owner only. pub fn private(o: &mut OpenOptions) -> &mut OpenOptions { #[cfg(unix)] { use std::os::unix::fs::OpenOptionsExt; o.mode(0o600) } #[cfg(not(unix))] { o } } /// Make `p` and its parents, the last only its owner's. pub fn create_private_dir(p: &Path) -> io::Result<()> { #[cfg(unix)] { std::os::unix::fs::DirBuilderExt::mode(std::fs::DirBuilder::new().recursive(true), 0o700).create(p) } #[cfg(not(unix))] { std::fs::create_dir_all(p) } } /// The disk a file takes: its blocks on Unix, its length elsewhere. pub fn disk_bytes(md: &Metadata) -> u64 { #[cfg(unix)] { use std::os::unix::fs::MetadataExt; md.blocks() * 512 } #[cfg(not(unix))] { md.len() } } /// For a file with more than one name, what names it once (device, inode); /// `None` for a file with one name, a directory, or anywhere but Unix. pub fn shared_identity(md: &Metadata) -> Option<(u64, u64)> { #[cfg(unix)] { use std::os::unix::fs::MetadataExt; (md.nlink() > 1 && !md.is_dir()).then(|| (md.dev(), md.ino())) } #[cfg(not(unix))] { let _ = md; None } } /// Make `link` a symbolic link to `target` (a file link on Windows, which /// needs Developer Mode or the right to make them). pub fn symlink(target: &Path, link: &Path) -> io::Result<()> { #[cfg(unix)] { std::os::unix::fs::symlink(target, link) } #[cfg(windows)] { std::os::windows::fs::symlink_file(target, link) } } /// Rename `from` to `to`, which must not exist. On Windows through /// `MoveFileExW`, which renames a program that is running, trying again /// while another program (a virus scanner) holds the file. pub fn rename(from: &Path, to: &Path) -> io::Result<()> { #[cfg(windows)] { use std::os::windows::ffi::OsStrExt; let wide = |p: &Path| p.as_os_str().encode_wide().chain(Some(0)).collect::>(); let (f, t) = (wide(from), wide(to)); // A virus scanner holds a new program for a while after it is // written (a sharing violation, 32, or access denied, 5): try again // for half a minute. let mut tries = 0; loop { // SAFETY: both are NUL-terminated wide paths. if unsafe { windows_sys::Win32::Storage::FileSystem::MoveFileExW(f.as_ptr(), t.as_ptr(), 0) } != 0 { return Ok(()); } let e = io::Error::last_os_error(); tries += 1; if tries >= 30 || !matches!(e.raw_os_error(), Some(32) | Some(5)) { return Err(e); } std::thread::sleep(std::time::Duration::from_secs(1)); } } #[cfg(not(windows))] { std::fs::rename(from, to) } }