use anyhow::Result;
use std::{
fs::{File, OpenOptions},
io::{Read, Seek, Write},
os::unix::fs::PermissionsExt,
path::Path,
};
#[allow(clippy::redundant_pub_crate)]
pub(crate) struct ReadOnlyGuard {
is_readonly: bool,
file: File,
}
impl Read for ReadOnlyGuard {
fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
self.file.read(buf)
}
}
impl Write for ReadOnlyGuard {
fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
self.file.write(buf)
}
fn flush(&mut self) -> std::io::Result<()> {
self.file.flush()
}
}
impl Seek for ReadOnlyGuard {
fn seek(&mut self, pos: std::io::SeekFrom) -> std::io::Result<u64> {
self.file.seek(pos)
}
}
impl ReadOnlyGuard {
pub(crate) fn new(path: impl AsRef<Path>, truncate: bool) -> Result<Self> {
let path = path.as_ref();
let is_readonly = if path.exists() {
let file = File::open(path)?;
let mut permissions = file.metadata()?.permissions();
let is_readonly = permissions.readonly();
permissions.set_mode(permissions.mode() | 0o600);
file.set_permissions(permissions)?;
is_readonly
} else {
false
};
let file = OpenOptions::new()
.truncate(truncate)
.read(true)
.write(true)
.create(true)
.open(path)?;
Ok(Self { is_readonly, file })
}
}
impl Drop for ReadOnlyGuard {
fn drop(&mut self) {
if !self.is_readonly {
return;
}
let Ok(metadata) = self.file.metadata() else {
return;
};
let mut permissions = metadata.permissions();
permissions.set_readonly(true);
self.file.set_permissions(permissions).ok();
}
}