use crate::fs_mount_options::read_mount_facts;
use crate::process_identity::ProcessIdentityError;
use crate::service_index_marker::own_identity;
use crate::service_index_ownership::{Ownership, inspect_ownership};
use crate::service_index_watch::Watch;
use std::path::Path;
pub struct Platform<'a> {
pub rename: &'a dyn Fn(&Path, &Path) -> std::io::Result<()>,
pub link: &'a dyn Fn(&Path, &Path) -> std::io::Result<()>,
pub unlink: &'a dyn Fn(&Path) -> std::io::Result<()>,
pub identity: &'a dyn Fn() -> Result<String, ProcessIdentityError>,
pub watch: &'a dyn Fn(&Path) -> Option<Watch>,
pub sync: &'a dyn Fn(&std::fs::File) -> std::io::Result<()>,
pub inspect: fn(&Path, &std::fs::File, &str) -> Ownership,
pub release: fn(&Path) -> std::io::Result<()>,
}
pub const SYSTEM: Platform<'static> = Platform {
rename: &rename_noreplace,
link: &link_file,
unlink: &unlink_file,
identity: &own_identity,
watch: &Watch::on,
sync: &sync_file,
inspect: inspect_ownership,
release: unlink_file,
};
pub fn sync_file(file: &std::fs::File) -> std::io::Result<()> {
return crate::service_durable::flush_file(file, "lock-marker");
}
fn link_file(from: &Path, to: &Path) -> std::io::Result<()> {
return std::fs::hard_link(from, to);
}
fn unlink_file(path: &Path) -> std::io::Result<()> {
return std::fs::remove_file(path);
}
#[cfg(target_os = "linux")]
pub fn rename_noreplace(from: &Path, to: &Path) -> std::io::Result<()> {
use std::os::unix::ffi::OsStrExt;
let source = std::ffi::CString::new(from.as_os_str().as_bytes()).map_err(std::io::Error::other)?;
let target = std::ffi::CString::new(to.as_os_str().as_bytes()).map_err(std::io::Error::other)?;
let done: i32 = unsafe { libc::renameat2(libc::AT_FDCWD, source.as_ptr(), libc::AT_FDCWD, target.as_ptr(), libc::RENAME_NOREPLACE) };
return if done == 0 { Ok(()) } else { Err(std::io::Error::last_os_error()) };
}
#[cfg(not(target_os = "linux"))]
pub fn rename_noreplace(_from: &Path, _to: &Path) -> std::io::Result<()> {
return Err(std::io::Error::from(std::io::ErrorKind::Unsupported));
}
pub fn rename_unsupported(error: &std::io::Error) -> bool {
if error.kind() == std::io::ErrorKind::Unsupported {
return true;
}
#[cfg(unix)]
{
return matches!(error.raw_os_error(), Some(code) if code == libc::EINVAL || code == libc::ENOSYS || code == libc::EOPNOTSUPP);
}
#[cfg(not(unix))]
{
return false;
}
}
pub fn links_unsupported(error: &std::io::Error) -> bool {
if error.kind() == std::io::ErrorKind::Unsupported {
return true;
}
#[cfg(unix)]
{
return matches!(error.raw_os_error(), Some(code) if code == libc::EPERM || code == libc::EOPNOTSUPP);
}
#[cfg(not(unix))]
{
return false;
}
}
pub fn place_marked(platform: &Platform, staged: &Path, lock: &Path) -> std::io::Result<()> {
let refused_rename: std::io::Error = match (platform.rename)(staged, lock) {
Ok(()) => return Ok(()),
Err(error) if !rename_unsupported(&error) => return Err(error),
Err(error) => error,
};
match (platform.link)(staged, lock) {
Ok(()) => {
let _removed = std::fs::remove_file(staged);
return Ok(());
}
Err(error) if !links_unsupported(&error) => return Err(error),
Err(refused_link) => {
return Err(std::io::Error::new(
std::io::ErrorKind::Unsupported,
format!("renaming without replacing failed ({refused_rename}) and hard links failed ({refused_link})"),
));
}
}
}
pub fn log_placement_failure(lock: &Path, error: &std::io::Error) {
if let Some(text) = placement_failure_text(lock, error) {
crate::service_error!("{text}");
}
}
pub fn placement_failure_text(lock: &Path, error: &std::io::Error) -> Option<String> {
if error.kind() == std::io::ErrorKind::AlreadyExists {
return None;
}
if error.kind() != std::io::ErrorKind::Unsupported {
return Some(format!("cannot take the index lock {}: {error}", lock.display()));
}
let directory: &Path = lock.parent().unwrap_or(Path::new("."));
let filesystem: String = match read_mount_facts(directory) {
Ok(Some(facts)) => format!("the {} filesystem mounted at {}", facts.filesystem_type, facts.mount_point.display()),
Ok(None) => format!("the filesystem of {}", directory.display()),
Err(reason) => format!("the filesystem of {} ({reason})", directory.display()),
};
return Some(format!(
"cannot take the index lock {}: {filesystem} can neither rename a file without replacing another nor make a hard link ({error}), \
so the lock cannot be created with its marker in one step, and the service takes no lock that a kill could leave empty. \
Keep this repository's Git directory on a filesystem that supports one of the two (ext4, xfs, btrfs, tmpfs and overlayfs do), or update the kernel.",
lock.display()
));
}
pub fn reclaim(platform: &Platform, lock: &Path) -> Reclaimed {
return reclaim_if(platform, lock, &|marked: &crate::service_index_marker::Marked| { return crate::service_index_marker::holder_is_gone(marked.holder.as_ref()); });
}
pub use crate::service_index_reclaim::{Reclaimed, reclaim_if};