use {
crate::{
ArchiveFormat, ArchiveFormatDecompressor,
error::SnapshotError,
hardened_unpack::{self, UnpackError},
},
agave_fs::{FileInfo, buffered_reader, file_io::file_creator, io_setup::IoSetupState},
bzip2::bufread::BzDecoder,
crossbeam_channel::{SendError, Sender},
std::{
fs,
io::{self, BufRead, BufReader},
path::{Path, PathBuf},
sync::OnceLock,
thread::{self, Scope, ScopedJoinHandle},
time::Instant,
},
};
const MAX_SNAPSHOT_READER_BUF_SIZE: usize = 128 * 1024 * 1024;
const MAX_UNPACK_WRITE_BUF_SIZE: usize = 512 * 1024 * 1024;
pub fn streaming_unarchive_snapshot<'scope, 'env: 'scope>(
scope: &'scope Scope<'scope, 'env>,
file_sender: Sender<FileInfo>,
account_paths: Vec<PathBuf>,
ledger_dir: PathBuf,
snapshot_archive_path: PathBuf,
archive_format: ArchiveFormat,
io_setup: &'env IoSetupState,
) -> ScopedJoinHandle<'scope, Result<(), SnapshotError>> {
let do_unpack = move |archive_path: &Path| {
let first_failed_send = OnceLock::<PathBuf>::new();
let first_failed_send_ref = &first_failed_send;
let (decompressor, file_creator) = {
let archive_size = fs::metadata(archive_path)?.len() as usize;
let read_buf_size = MAX_SNAPSHOT_READER_BUF_SIZE.min(archive_size);
let write_buf_size = MAX_UNPACK_WRITE_BUF_SIZE.min(archive_size);
let decompressor =
decompressed_tar_reader(archive_format, archive_path, read_buf_size, io_setup)?;
(
decompressor,
file_creator(write_buf_size, io_setup, move |file_info| {
match file_sender.send(file_info) {
Ok(()) => None,
Err(SendError(FileInfo { file, path, .. })) => {
let _ = first_failed_send_ref.set(path);
Some(file)
}
}
})?,
)
};
hardened_unpack::streaming_unpack_snapshot(
decompressor,
file_creator,
ledger_dir.as_path(),
&account_paths,
)
.map(|()| first_failed_send.into_inner())
};
thread::Builder::new()
.name("solTarUnpack".to_string())
.spawn_scoped(scope, move || -> Result<(), SnapshotError> {
match do_unpack(&snapshot_archive_path) {
Err(err) => Err(UnpackError::Unpack(Box::new(err), snapshot_archive_path).into()),
Ok(Some(path)) => Err(SnapshotError::CrossbeamSend(SendError(path))),
Ok(None) => Ok(()),
}
})
.unwrap()
}
pub fn unpack_genesis_archive(
archive_filename: &Path,
destination_dir: &Path,
max_genesis_archive_unpacked_size: u64,
) -> Result<(), UnpackError> {
log::info!("Extracting {archive_filename:?}...");
let extract_start = Instant::now();
fs::create_dir_all(destination_dir)?;
let tar_bz2 = fs::File::open(archive_filename)?;
let tar = BzDecoder::new(BufReader::new(tar_bz2));
let file_creator = file_creator(
0,
&IoSetupState::default(),
|file_info| Some(file_info.file),
)?;
hardened_unpack::unpack_genesis(
tar,
file_creator,
destination_dir,
max_genesis_archive_unpacked_size,
)?;
log::info!(
"Extracted {:?} in {:?}",
archive_filename,
Instant::now().duration_since(extract_start)
);
Ok(())
}
fn decompressed_tar_reader(
archive_format: ArchiveFormat,
archive_path: &Path,
buf_size: usize,
io_setup: &IoSetupState,
) -> io::Result<ArchiveFormatDecompressor<impl BufRead + use<>>> {
let buf_reader = buffered_reader::large_file_buf_reader(archive_path, buf_size, io_setup)?;
ArchiveFormatDecompressor::new(archive_format, buf_reader)
}