use std::{fs::File, io, io::Read};
use cfg_if::cfg_if;
use rand::{distributions::Distribution, RngCore};
use tracing::instrument;
use crate::utils::FastPathBuf;
pub trait FileContentsGenerator {
fn create_file(
&mut self,
file: &mut FastPathBuf,
file_num: usize,
retryable: bool,
) -> io::Result<u64>;
fn byte_counts_pool_return(self) -> Option<Vec<u64>>;
}
pub struct NoGeneratedFileContents;
impl FileContentsGenerator for NoGeneratedFileContents {
#[inline]
fn create_file(&mut self, file: &mut FastPathBuf, _: usize, _: bool) -> io::Result<u64> {
cfg_if! {
if #[cfg(any(not(unix), miri))] {
File::create(file).map(|_| 0)
} else if #[cfg(target_os = "linux")] {
use rustix::fs::{mknodat, FileType, Mode};
let cstr = file.to_cstr_mut();
mknodat(
rustix::fs::cwd(),
&*cstr,
FileType::RegularFile,
Mode::RUSR | Mode::WUSR | Mode::RGRP | Mode::WGRP | Mode::ROTH,
0,
)
.map_err(io::Error::from)
.map(|_| 0)
} else {
use rustix::fs::{openat, OFlags, Mode};
let cstr = file.to_cstr_mut();
openat(
rustix::fs::cwd(),
&*cstr,
OFlags::CREATE,
Mode::RUSR | Mode::WUSR | Mode::RGRP | Mode::WGRP | Mode::ROTH,
)
.map_err(io::Error::from)
.map(|_| 0)
}
}
}
fn byte_counts_pool_return(self) -> Option<Vec<u64>> {
None
}
}
pub struct OnTheFlyGeneratedFileContents<D: Distribution<f64>, R: RngCore> {
pub num_bytes_distr: D,
pub random: R,
pub fill_byte: Option<u8>,
}
impl<D: Distribution<f64>, R: RngCore + 'static> FileContentsGenerator
for OnTheFlyGeneratedFileContents<D, R>
{
#[inline]
#[allow(clippy::cast_sign_loss, clippy::cast_possible_truncation)]
fn create_file(
&mut self,
file: &mut FastPathBuf,
file_num: usize,
retryable: bool,
) -> io::Result<u64> {
let num_bytes = self.num_bytes_distr.sample(&mut self.random).round() as u64;
if num_bytes > 0 || retryable {
File::create(file).and_then(|f| {
let num_bytes = if retryable {
self.num_bytes_distr.sample(&mut self.random).round() as u64
} else {
num_bytes
};
write_bytes(f, num_bytes, (self.fill_byte, &mut self.random))?;
Ok(num_bytes)
})
} else {
NoGeneratedFileContents.create_file(file, file_num, retryable)
}
}
fn byte_counts_pool_return(self) -> Option<Vec<u64>> {
None
}
}
pub struct PreDefinedGeneratedFileContents<R: RngCore> {
pub byte_counts: Vec<u64>,
pub random: R,
pub fill_byte: Option<u8>,
}
impl<R: RngCore + 'static> FileContentsGenerator for PreDefinedGeneratedFileContents<R> {
#[inline]
fn create_file(
&mut self,
file: &mut FastPathBuf,
file_num: usize,
retryable: bool,
) -> io::Result<u64> {
let num_bytes = self.byte_counts[file_num];
if num_bytes > 0 {
File::create(file)
.and_then(|f| write_bytes(f, num_bytes, (self.fill_byte, &mut self.random)))
.map(|_| num_bytes)
} else {
NoGeneratedFileContents.create_file(file, file_num, retryable)
}
}
fn byte_counts_pool_return(self) -> Option<Vec<u64>> {
Some(self.byte_counts)
}
}
enum BytesKind<'a, R> {
Random(&'a mut R),
Fixed(u8),
}
impl<'a, R> From<(Option<u8>, &'a mut R)> for BytesKind<'a, R> {
fn from((fill_byte, random): (Option<u8>, &'a mut R)) -> Self {
fill_byte.map_or(BytesKind::Random(random), |byte| BytesKind::Fixed(byte))
}
}
#[instrument(level = "trace", skip(file, kind))]
fn write_bytes<'a, R: RngCore + 'static>(
mut file: File,
num: u64,
kind: impl Into<BytesKind<'a, R>>,
) -> io::Result<()> {
let copied = match kind.into() {
BytesKind::Random(random) => {
io::copy(&mut (random as &mut dyn RngCore).take(num), &mut file)
}
BytesKind::Fixed(byte) => io::copy(&mut io::repeat(byte).take(num), &mut file),
}?;
debug_assert_eq!(num, copied);
Ok(())
}