#![allow(unsafe_code)]
use std::fs::File;
use std::path::Path;
use anyhow::{Context, Result};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum Durability {
Full,
Fast,
#[default]
Auto,
}
impl Durability {
pub fn resolve(self, target: &Path) -> Durability {
match self {
Durability::Auto => {
let name = target
.file_name()
.and_then(|s| s.to_str())
.unwrap_or("")
.to_ascii_lowercase();
let ext = target
.extension()
.and_then(|s| s.to_str())
.unwrap_or("")
.to_ascii_lowercase();
if matches!(
ext.as_str(),
"toml" | "json" | "yaml" | "yml" | "ini" | "conf" | "cfg" | "env"
) || name.starts_with('.')
|| name == "cargo.lock"
|| name == "dockerfile"
{
Durability::Full
} else {
Durability::Fast
}
}
other => other,
}
}
pub const fn as_str(self) -> &'static str {
match self {
Durability::Full => "full",
Durability::Fast => "fast",
Durability::Auto => "auto",
}
}
}
pub fn fsync_file(file: &File) -> Result<()> {
fsync_file_with_durability(file, Durability::Full)
}
pub fn fsync_file_with_durability(file: &File, durability: Durability) -> Result<()> {
match durability {
Durability::Fast | Durability::Auto => {
file.sync_data().context("fsync file (fast)")?;
Ok(())
}
Durability::Full => {
#[cfg(target_os = "macos")]
{
use std::os::unix::io::AsRawFd;
let fd = file.as_raw_fd();
let ret = unsafe { libc::fcntl(fd, libc::F_FULLFSYNC) };
if ret == -1 {
file.sync_data()
.context("fsync fallback after F_FULLFSYNC failure")?;
}
return Ok(());
}
#[cfg(not(target_os = "macos"))]
{
file.sync_all().context("fsync file (full)")?;
Ok(())
}
}
}
}
pub fn fsync_file_best_effort(file: &File) {
if let Err(e) = fsync_file(file) {
tracing::warn!(error = %e, "fsync_file best-effort: continuing without durability flush");
}
}
pub fn fsync_dir(dir: &Path) -> Result<()> {
#[cfg(unix)]
{
let file = File::open(dir)
.with_context(|| format!("cannot open directory {} for fsync", dir.display()))?;
file.sync_all()
.with_context(|| format!("fsync directory {}", dir.display()))?;
Ok(())
}
#[cfg(windows)]
{
let _ = dir;
Ok(())
}
#[cfg(not(any(unix, windows)))]
{
let _ = dir;
Ok(())
}
}
pub fn preserve_timestamps(
path: &Path,
mtime: filetime::FileTime,
atime: filetime::FileTime,
) -> Result<()> {
filetime::set_file_times(path, atime, mtime)
.with_context(|| format!("cannot restore timestamps on {}", path.display()))
}
pub fn platform_fsync_name() -> &'static str {
#[cfg(target_os = "macos")]
{
"F_FULLFSYNC"
}
#[cfg(not(target_os = "macos"))]
{
"sync_data"
}
}
#[cfg(windows)]
pub fn init_console() {
use windows_sys::Win32::Foundation::INVALID_HANDLE_VALUE;
use windows_sys::Win32::System::Console::*;
unsafe {
SetConsoleOutputCP(65001);
SetConsoleCP(65001);
for handle_id in [STD_OUTPUT_HANDLE, STD_ERROR_HANDLE] {
let handle = GetStdHandle(handle_id);
if !handle.is_null() && handle != INVALID_HANDLE_VALUE {
let mut mode: u32 = 0;
if GetConsoleMode(handle, &mut mode) != 0 {
let _ = SetConsoleMode(handle, mode | ENABLE_VIRTUAL_TERMINAL_PROCESSING);
}
}
}
}
}
#[cfg(not(windows))]
pub fn init_console() {}
pub fn platform_dir_fsync_name() -> &'static str {
#[cfg(unix)]
{
"sync_all"
}
#[cfg(windows)]
{
"best_effort"
}
#[cfg(not(any(unix, windows)))]
{
"none"
}
}
pub fn atomic_rename(from: &Path, to: &Path) -> Result<&'static str> {
#[cfg(target_os = "linux")]
{
use std::ffi::CString;
use std::os::unix::ffi::OsStrExt;
let from_c = CString::new(from.as_os_str().as_bytes())
.with_context(|| format!("invalid rename source path: {}", from.display()))?;
let to_c = CString::new(to.as_os_str().as_bytes())
.with_context(|| format!("invalid rename dest path: {}", to.display()))?;
let ret = unsafe {
libc::renameat2(
libc::AT_FDCWD,
from_c.as_ptr(),
libc::AT_FDCWD,
to_c.as_ptr(),
0,
)
};
if ret == 0 {
return Ok("renameat2");
}
let err = std::io::Error::last_os_error();
if err.raw_os_error() == Some(libc::ENOSYS) {
std::fs::rename(from, to)
.with_context(|| format!("rename {} -> {}", from.display(), to.display()))?;
return Ok("rename");
}
return Err(err)
.with_context(|| format!("renameat2 {} -> {}", from.display(), to.display()));
}
#[cfg(not(target_os = "linux"))]
{
std::fs::rename(from, to)
.with_context(|| format!("rename {} -> {}", from.display(), to.display()))?;
#[cfg(target_os = "macos")]
{
Ok("rename")
}
#[cfg(windows)]
{
Ok("MoveFileEx")
}
#[cfg(not(any(target_os = "macos", windows)))]
{
Ok("rename")
}
}
}
pub fn platform_rename_method() -> &'static str {
#[cfg(target_os = "linux")]
{
"renameat2"
}
#[cfg(target_os = "macos")]
{
"rename"
}
#[cfg(windows)]
{
"MoveFileEx"
}
#[cfg(not(any(target_os = "linux", target_os = "macos", windows)))]
{
"rename"
}
}