use crate::random_id::{is_canonical_uuid, random_uuid};
use crate::service_index_lock::IndexLock;
use crate::service_land_follow::storage_failure;
use crate::service_protocol::FailureCode;
use std::io::Write as _;
use std::os::unix::fs::{DirBuilderExt as _, OpenOptionsExt as _, PermissionsExt as _};
use std::path::{Path, PathBuf};
const COPY_PREFIX: &str = "cli-git-private-";
const MARKER_FILE: &str = "owner";
const MARKER_HEAD: &str = "cli-git-private-copy v1";
const COPY_FILE: &str = "index";
const DIRECTORY_TRIES: usize = 4;
fn hold(directory: &Path) -> Option<std::fs::File> {
let opened: std::fs::File = std::fs::File::open(directory).ok()?;
return match opened.try_lock() {
Ok(()) => Some(opened),
Err(std::fs::TryLockError::WouldBlock) => None,
Err(std::fs::TryLockError::Error(error)) => {
crate::service_log!("the private copy {} cannot be tested for an owner ({error}), so it stays", directory.display());
None
}
};
}
fn is_ours(entry: &std::fs::DirEntry) -> bool {
use std::os::unix::fs::MetadataExt as _;
let name = entry.file_name();
let Some(tail) = name.to_str().and_then(|text| { return text.strip_prefix(COPY_PREFIX); }) else {
return false;
};
let Ok(metadata) = entry.path().symlink_metadata() else {
return false;
};
let effective: u32 = unsafe { libc::geteuid() };
return is_canonical_uuid(tail) && metadata.is_dir() && metadata.uid() == effective && std::os::unix::fs::PermissionsExt::mode(&metadata.permissions()) & 0o777 == 0o700;
}
fn sweep_leftovers(directory: &Path) {
let Ok(entries) = std::fs::read_dir(directory) else {
return;
};
for entry in entries.flatten() {
if !is_ours(&entry) {
continue;
}
if let Some(_held) = hold(entry.path().as_path()) {
let _removed = std::fs::remove_dir_all(entry.path());
}
}
}
#[derive(Debug)]
pub struct PrivateCopy {
directory: PathBuf,
path: PathBuf,
_hold: Option<std::fs::File>,
flushed: std::cell::Cell<bool>,
}
impl PrivateCopy {
pub fn path(&self) -> &Path {
return self.path.as_path();
}
pub fn is_flushed(&self) -> bool {
return self.flushed.get();
}
pub fn set_flushed(&self, flushed: bool) {
self.flushed.set(flushed);
}
pub fn directory(&self) -> &Path {
return self.directory.as_path();
}
pub fn make(lock: &IndexLock, seq: u64) -> Result<PrivateCopy, FailureCode> {
let live: &Path = lock.index();
let parent: &Path = live.parent().ok_or(FailureCode::Unavailable)?;
sweep_leftovers(parent);
let (directory, held) = Self::make_directory(parent)?;
let copy: PrivateCopy = PrivateCopy { path: directory.join(COPY_FILE), directory, _hold: held, flushed: std::cell::Cell::new(false) };
let marker_text: String = format!("{MARKER_HEAD}\npid={}\nbatch={seq}\n", std::process::id());
write_new(copy.directory.join(MARKER_FILE).as_path(), marker_text.as_bytes()).map_err(|error| { return storage_failure(&error); })?;
match std::fs::File::open(live) {
Ok(mut source) => {
let live_permissions: std::fs::Permissions = source.metadata().map_err(|error| { return storage_failure(&error); })?.permissions();
let mut made: std::fs::File = std::fs::OpenOptions::new().write(true).create_new(true).mode(0o600).open(copy.path.as_path()).map_err(|error| { return storage_failure(&error); })?;
std::io::copy(&mut source, &mut made).map_err(|error| { return storage_failure(&error); })?;
std::fs::set_permissions(copy.path.as_path(), live_permissions).map_err(|error| { return storage_failure(&error); })?;
}
Err(error) if error.kind() == std::io::ErrorKind::NotFound => {}
Err(error) => return Err(storage_failure(&error)),
}
return Ok(copy);
}
fn make_directory(parent: &Path) -> Result<(PathBuf, Option<std::fs::File>), FailureCode> {
for _ in 0..DIRECTORY_TRIES {
let identifier: String = random_uuid().map_err(|_| { return FailureCode::Unavailable; })?;
let directory: PathBuf = parent.join(format!("{COPY_PREFIX}{identifier}"));
match std::fs::DirBuilder::new().mode(0o700).create(&directory) {
Ok(()) => {}
Err(error) if error.kind() == std::io::ErrorKind::AlreadyExists => continue,
Err(error) => return Err(storage_failure(&error)),
}
match std::fs::set_permissions(&directory, std::fs::Permissions::from_mode(0o700)) {
Ok(()) => {}
Err(error) if error.kind() == std::io::ErrorKind::NotFound => continue,
Err(error) => return Err(storage_failure(&error)),
}
let opened: std::fs::File = match std::fs::File::open(&directory) {
Ok(found) => found,
Err(error) if error.kind() == std::io::ErrorKind::NotFound => continue,
Err(error) => return Err(storage_failure(&error)),
};
match opened.try_lock() {
Ok(()) => return Ok((directory, Some(opened))),
Err(std::fs::TryLockError::WouldBlock) => continue,
Err(std::fs::TryLockError::Error(error)) => {
crate::service_log!("the private copy {} cannot be held with flock ({error}); a later sweep leaves it", directory.display());
return Ok((directory, None));
}
}
}
return Err(FailureCode::Unavailable);
}
}
fn write_new(path: &Path, bytes: &[u8]) -> std::io::Result<()> {
let mut file: std::fs::File = std::fs::OpenOptions::new().write(true).create_new(true).mode(0o600).open(path)?;
return file.write_all(bytes);
}
impl AsRef<Path> for PrivateCopy {
fn as_ref(&self) -> &Path {
return self.path.as_path();
}
}
impl Drop for PrivateCopy {
fn drop(&mut self) {
let _removed = std::fs::remove_dir_all(self.directory.as_path());
}
}