1use {
2 bzip2::bufread::BzDecoder,
3 log::*,
4 rand::{thread_rng, Rng},
5 solana_sdk::genesis_config::{GenesisConfig, DEFAULT_GENESIS_ARCHIVE, DEFAULT_GENESIS_FILE},
6 std::{
7 collections::HashMap,
8 fs::{self, File},
9 io::{BufReader, Read},
10 path::{
11 Component::{self, CurDir, Normal},
12 Path, PathBuf,
13 },
14 time::Instant,
15 },
16 tar::{
17 Archive,
18 EntryType::{Directory, GNUSparse, Regular},
19 },
20 thiserror::Error,
21};
22
23#[derive(Error, Debug)]
24pub enum UnpackError {
25 #[error("IO error: {0}")]
26 Io(#[from] std::io::Error),
27 #[error("Archive error: {0}")]
28 Archive(String),
29}
30
31pub type Result<T> = std::result::Result<T, UnpackError>;
32
33const MAX_SNAPSHOT_ARCHIVE_UNPACKED_APPARENT_SIZE: u64 = 64 * 1024 * 1024 * 1024 * 1024;
39
40const MAX_SNAPSHOT_ARCHIVE_UNPACKED_ACTUAL_SIZE: u64 = 4 * 1024 * 1024 * 1024 * 1024;
43
44const MAX_SNAPSHOT_ARCHIVE_UNPACKED_COUNT: u64 = 5_000_000;
45pub const MAX_GENESIS_ARCHIVE_UNPACKED_SIZE: u64 = 10 * 1024 * 1024; const MAX_GENESIS_ARCHIVE_UNPACKED_COUNT: u64 = 100;
47
48fn checked_total_size_sum(total_size: u64, entry_size: u64, limit_size: u64) -> Result<u64> {
49 trace!(
50 "checked_total_size_sum: {} + {} < {}",
51 total_size,
52 entry_size,
53 limit_size,
54 );
55 let total_size = total_size.saturating_add(entry_size);
56 if total_size > limit_size {
57 return Err(UnpackError::Archive(format!(
58 "too large archive: {total_size} than limit: {limit_size}",
59 )));
60 }
61 Ok(total_size)
62}
63
64fn checked_total_count_increment(total_count: u64, limit_count: u64) -> Result<u64> {
65 let total_count = total_count + 1;
66 if total_count > limit_count {
67 return Err(UnpackError::Archive(format!(
68 "too many files in snapshot: {total_count:?}"
69 )));
70 }
71 Ok(total_count)
72}
73
74fn check_unpack_result(unpack_result: bool, path: String) -> Result<()> {
75 if !unpack_result {
76 return Err(UnpackError::Archive(format!("failed to unpack: {path:?}")));
77 }
78 Ok(())
79}
80
81#[derive(Debug, PartialEq, Eq)]
82pub enum UnpackPath<'a> {
83 Valid(&'a Path),
84 Ignore,
85 Invalid,
86}
87
88fn unpack_archive<'a, A, C, D>(
89 archive: &mut Archive<A>,
90 apparent_limit_size: u64,
91 actual_limit_size: u64,
92 limit_count: u64,
93 mut entry_checker: C, entry_processor: D, ) -> Result<()>
96where
97 A: Read,
98 C: FnMut(&[&str], tar::EntryType) -> UnpackPath<'a>,
99 D: Fn(PathBuf),
100{
101 let mut apparent_total_size: u64 = 0;
102 let mut actual_total_size: u64 = 0;
103 let mut total_count: u64 = 0;
104
105 let mut total_entries = 0;
106 let mut last_log_update = Instant::now();
107 for entry in archive.entries()? {
108 let mut entry = entry?;
109 let path = entry.path()?;
110 let path_str = path.display().to_string();
111
112 let parts = path.components().map(|p| match p {
117 CurDir => Some("."),
118 Normal(c) => c.to_str(),
119 _ => None, });
121
122 let legacy_dir_entry =
124 entry.header().as_ustar().is_none() && entry.path_bytes().ends_with(b"/");
125 let kind = entry.header().entry_type();
126 let reject_legacy_dir_entry = legacy_dir_entry && (kind != Directory);
127
128 if parts.clone().any(|p| p.is_none()) || reject_legacy_dir_entry {
129 return Err(UnpackError::Archive(format!(
130 "invalid path found: {path_str:?}"
131 )));
132 }
133
134 let parts: Vec<_> = parts.map(|p| p.unwrap()).collect();
135 let account_filename =
136 (parts.len() == 2 && parts[0] == "accounts").then(|| PathBuf::from(parts[1]));
137 let unpack_dir = match entry_checker(parts.as_slice(), kind) {
138 UnpackPath::Invalid => {
139 return Err(UnpackError::Archive(format!(
140 "extra entry found: {:?} {:?}",
141 path_str,
142 entry.header().entry_type(),
143 )));
144 }
145 UnpackPath::Ignore => {
146 continue;
147 }
148 UnpackPath::Valid(unpack_dir) => unpack_dir,
149 };
150
151 apparent_total_size = checked_total_size_sum(
152 apparent_total_size,
153 entry.header().size()?,
154 apparent_limit_size,
155 )?;
156 actual_total_size = checked_total_size_sum(
157 actual_total_size,
158 entry.header().entry_size()?,
159 actual_limit_size,
160 )?;
161 total_count = checked_total_count_increment(total_count, limit_count)?;
162
163 let target = sanitize_path(&entry.path()?, unpack_dir)?; if target.is_none() {
165 continue; }
167 let target = target.unwrap();
168
169 let unpack = entry.unpack(target);
170 check_unpack_result(unpack.map(|_unpack| true)?, path_str)?;
171
172 let mode = match entry.header().entry_type() {
174 GNUSparse | Regular => 0o644,
175 _ => 0o755,
176 };
177 let entry_path_buf = unpack_dir.join(entry.path()?);
178 set_perms(&entry_path_buf, mode)?;
179
180 let entry_path = if let Some(account_filename) = account_filename {
181 let stripped_path = unpack_dir.join(account_filename); fs::rename(&entry_path_buf, &stripped_path)?;
183 stripped_path
184 } else {
185 entry_path_buf
186 };
187
188 entry_processor(entry_path);
190
191 total_entries += 1;
192 let now = Instant::now();
193 if now.duration_since(last_log_update).as_secs() >= 10 {
194 info!("unpacked {} entries so far...", total_entries);
195 last_log_update = now;
196 }
197 }
198 info!("unpacked {} entries total", total_entries);
199
200 return Ok(());
201
202 #[cfg(unix)]
203 fn set_perms(dst: &Path, mode: u32) -> std::io::Result<()> {
204 use std::os::unix::fs::PermissionsExt;
205
206 let perm = fs::Permissions::from_mode(mode as _);
207 fs::set_permissions(dst, perm)
208 }
209
210 #[cfg(windows)]
211 fn set_perms(dst: &Path, _mode: u32) -> std::io::Result<()> {
212 let mut perm = fs::metadata(dst)?.permissions();
213 perm.set_readonly(false);
214 fs::set_permissions(dst, perm)
215 }
216}
217
218fn sanitize_path(entry_path: &Path, dst: &Path) -> Result<Option<PathBuf>> {
222 let mut file_dst = dst.to_path_buf();
226 const SKIP: Result<Option<PathBuf>> = Ok(None);
227 {
228 let path = entry_path;
229 for part in path.components() {
230 match part {
231 Component::Prefix(..) | Component::RootDir | Component::CurDir => continue,
235
236 Component::ParentDir => return SKIP,
241
242 Component::Normal(part) => file_dst.push(part),
243 }
244 }
245 }
246
247 if *dst == *file_dst {
250 return SKIP;
251 }
252
253 let parent = match file_dst.parent() {
255 Some(p) => p,
256 None => return SKIP,
257 };
258
259 fs::create_dir_all(parent)?;
260
261 validate_inside_dst(dst, parent)?;
264 let target = parent.join(entry_path.file_name().unwrap());
265
266 Ok(Some(target))
267}
268
269fn validate_inside_dst(dst: &Path, file_dst: &Path) -> Result<PathBuf> {
272 let canon_parent = file_dst.canonicalize().map_err(|err| {
274 UnpackError::Archive(format!(
275 "{} while canonicalizing {}",
276 err,
277 file_dst.display()
278 ))
279 })?;
280 let canon_target = dst.canonicalize().map_err(|err| {
281 UnpackError::Archive(format!("{} while canonicalizing {}", err, dst.display()))
282 })?;
283 if !canon_parent.starts_with(&canon_target) {
284 return Err(UnpackError::Archive(format!(
285 "trying to unpack outside of destination path: {}",
286 canon_target.display()
287 )));
288 }
289 Ok(canon_target)
290}
291
292pub type UnpackedAppendVecMap = HashMap<String, PathBuf>;
294
295pub struct ParallelSelector {
297 pub index: usize,
298 pub divisions: usize,
299}
300
301impl ParallelSelector {
302 pub fn select_index(&self, index: usize) -> bool {
303 index % self.divisions == self.index
304 }
305}
306
307pub fn unpack_snapshot<A: Read>(
309 archive: &mut Archive<A>,
310 ledger_dir: &Path,
311 account_paths: &[PathBuf],
312 parallel_selector: Option<ParallelSelector>,
313) -> Result<UnpackedAppendVecMap> {
314 let mut unpacked_append_vec_map = UnpackedAppendVecMap::new();
315
316 unpack_snapshot_with_processors(
317 archive,
318 ledger_dir,
319 account_paths,
320 parallel_selector,
321 |file, path| {
322 unpacked_append_vec_map.insert(file.to_string(), path.join("accounts").join(file));
323 },
324 |_| {},
325 )
326 .map(|_| unpacked_append_vec_map)
327}
328
329pub fn streaming_unpack_snapshot<A: Read>(
331 archive: &mut Archive<A>,
332 ledger_dir: &Path,
333 account_paths: &[PathBuf],
334 parallel_selector: Option<ParallelSelector>,
335 sender: &crossbeam_channel::Sender<PathBuf>,
336) -> Result<()> {
337 unpack_snapshot_with_processors(
338 archive,
339 ledger_dir,
340 account_paths,
341 parallel_selector,
342 |_, _| {},
343 |entry_path_buf| {
344 if entry_path_buf.is_file() {
345 sender.send(entry_path_buf).unwrap();
346 }
347 },
348 )
349}
350
351fn unpack_snapshot_with_processors<A, F, G>(
352 archive: &mut Archive<A>,
353 ledger_dir: &Path,
354 account_paths: &[PathBuf],
355 parallel_selector: Option<ParallelSelector>,
356 mut accounts_path_processor: F,
357 entry_processor: G,
358) -> Result<()>
359where
360 A: Read,
361 F: FnMut(&str, &Path),
362 G: Fn(PathBuf),
363{
364 assert!(!account_paths.is_empty());
365 let mut i = 0;
366
367 unpack_archive(
368 archive,
369 MAX_SNAPSHOT_ARCHIVE_UNPACKED_APPARENT_SIZE,
370 MAX_SNAPSHOT_ARCHIVE_UNPACKED_ACTUAL_SIZE,
371 MAX_SNAPSHOT_ARCHIVE_UNPACKED_COUNT,
372 |parts, kind| {
373 if is_valid_snapshot_archive_entry(parts, kind) {
374 i += 1;
375 match ¶llel_selector {
376 Some(parallel_selector) => {
377 if !parallel_selector.select_index(i - 1) {
378 return UnpackPath::Ignore;
379 }
380 }
381 None => {}
382 };
383 if let ["accounts", file] = parts {
384 let path_index = thread_rng().gen_range(0, account_paths.len());
386 match account_paths
387 .get(path_index)
388 .map(|path_buf| path_buf.as_path())
389 {
390 Some(path) => {
391 accounts_path_processor(file, path);
392 UnpackPath::Valid(path)
393 }
394 None => UnpackPath::Invalid,
395 }
396 } else {
397 UnpackPath::Valid(ledger_dir)
398 }
399 } else {
400 UnpackPath::Invalid
401 }
402 },
403 entry_processor,
404 )
405}
406
407fn all_digits(v: &str) -> bool {
408 if v.is_empty() {
409 return false;
410 }
411 for x in v.chars() {
412 if !x.is_ascii_digit() {
413 return false;
414 }
415 }
416 true
417}
418
419fn like_storage(v: &str) -> bool {
420 let mut periods = 0;
421 let mut saw_numbers = false;
422 for x in v.chars() {
423 if !x.is_ascii_digit() {
424 if x == '.' {
425 if periods > 0 || !saw_numbers {
426 return false;
427 }
428 saw_numbers = false;
429 periods += 1;
430 } else {
431 return false;
432 }
433 } else {
434 saw_numbers = true;
435 }
436 }
437 saw_numbers && periods == 1
438}
439
440fn is_valid_snapshot_archive_entry(parts: &[&str], kind: tar::EntryType) -> bool {
441 match (parts, kind) {
442 (["version"], Regular) => true,
443 (["accounts"], Directory) => true,
444 (["accounts", file], GNUSparse) if like_storage(file) => true,
445 (["accounts", file], Regular) if like_storage(file) => true,
446 (["snapshots"], Directory) => true,
447 (["snapshots", "status_cache"], GNUSparse) => true,
448 (["snapshots", "status_cache"], Regular) => true,
449 (["snapshots", dir, file], GNUSparse) if all_digits(dir) && all_digits(file) => true,
450 (["snapshots", dir, file], Regular) if all_digits(dir) && all_digits(file) => true,
451 (["snapshots", dir], Directory) if all_digits(dir) => true,
452 _ => false,
453 }
454}
455
456pub fn open_genesis_config(
457 ledger_path: &Path,
458 max_genesis_archive_unpacked_size: u64,
459) -> GenesisConfig {
460 GenesisConfig::load(ledger_path).unwrap_or_else(|load_err| {
461 let genesis_package = ledger_path.join(DEFAULT_GENESIS_ARCHIVE);
462 unpack_genesis_archive(
463 &genesis_package,
464 ledger_path,
465 max_genesis_archive_unpacked_size,
466 )
467 .unwrap_or_else(|unpack_err| {
468 warn!(
469 "Failed to open ledger genesis_config at {:?}: {}, {}",
470 ledger_path, load_err, unpack_err,
471 );
472 std::process::exit(1);
473 });
474
475 GenesisConfig::load(ledger_path).unwrap()
477 })
478}
479
480pub fn unpack_genesis_archive(
481 archive_filename: &Path,
482 destination_dir: &Path,
483 max_genesis_archive_unpacked_size: u64,
484) -> std::result::Result<(), UnpackError> {
485 info!("Extracting {:?}...", archive_filename);
486 let extract_start = Instant::now();
487
488 fs::create_dir_all(destination_dir)?;
489 let tar_bz2 = File::open(archive_filename)?;
490 let tar = BzDecoder::new(BufReader::new(tar_bz2));
491 let mut archive = Archive::new(tar);
492 unpack_genesis(
493 &mut archive,
494 destination_dir,
495 max_genesis_archive_unpacked_size,
496 )?;
497 info!(
498 "Extracted {:?} in {:?}",
499 archive_filename,
500 Instant::now().duration_since(extract_start)
501 );
502 Ok(())
503}
504
505fn unpack_genesis<A: Read>(
506 archive: &mut Archive<A>,
507 unpack_dir: &Path,
508 max_genesis_archive_unpacked_size: u64,
509) -> Result<()> {
510 unpack_archive(
511 archive,
512 max_genesis_archive_unpacked_size,
513 max_genesis_archive_unpacked_size,
514 MAX_GENESIS_ARCHIVE_UNPACKED_COUNT,
515 |p, k| is_valid_genesis_archive_entry(unpack_dir, p, k),
516 |_| {},
517 )
518}
519
520fn is_valid_genesis_archive_entry<'a>(
521 unpack_dir: &'a Path,
522 parts: &[&str],
523 kind: tar::EntryType,
524) -> UnpackPath<'a> {
525 trace!("validating: {:?} {:?}", parts, kind);
526 #[allow(clippy::match_like_matches_macro)]
527 match (parts, kind) {
528 ([DEFAULT_GENESIS_FILE], GNUSparse) => UnpackPath::Valid(unpack_dir),
529 ([DEFAULT_GENESIS_FILE], Regular) => UnpackPath::Valid(unpack_dir),
530 (["rocksdb"], Directory) => UnpackPath::Ignore,
531 (["rocksdb", _], GNUSparse) => UnpackPath::Ignore,
532 (["rocksdb", _], Regular) => UnpackPath::Ignore,
533 (["rocksdb_fifo"], Directory) => UnpackPath::Ignore,
534 (["rocksdb_fifo", _], GNUSparse) => UnpackPath::Ignore,
535 (["rocksdb_fifo", _], Regular) => UnpackPath::Ignore,
536 _ => UnpackPath::Invalid,
537 }
538}
539
540#[cfg(test)]
541mod tests {
542 use {
543 super::*,
544 assert_matches::assert_matches,
545 tar::{Builder, Header},
546 };
547
548 #[test]
549 fn test_archive_is_valid_entry() {
550 assert!(is_valid_snapshot_archive_entry(
551 &["snapshots"],
552 tar::EntryType::Directory
553 ));
554 assert!(!is_valid_snapshot_archive_entry(
555 &["snapshots", ""],
556 tar::EntryType::Directory
557 ));
558 assert!(is_valid_snapshot_archive_entry(
559 &["snapshots", "3"],
560 tar::EntryType::Directory
561 ));
562 assert!(is_valid_snapshot_archive_entry(
563 &["snapshots", "3", "3"],
564 tar::EntryType::Regular
565 ));
566 assert!(is_valid_snapshot_archive_entry(
567 &["version"],
568 tar::EntryType::Regular
569 ));
570 assert!(is_valid_snapshot_archive_entry(
571 &["accounts"],
572 tar::EntryType::Directory
573 ));
574 assert!(!is_valid_snapshot_archive_entry(
575 &["accounts", ""],
576 tar::EntryType::Regular
577 ));
578
579 assert!(!is_valid_snapshot_archive_entry(
580 &["snapshots"],
581 tar::EntryType::Regular
582 ));
583 assert!(!is_valid_snapshot_archive_entry(
584 &["snapshots", "x0"],
585 tar::EntryType::Directory
586 ));
587 assert!(!is_valid_snapshot_archive_entry(
588 &["snapshots", "0x"],
589 tar::EntryType::Directory
590 ));
591 assert!(!is_valid_snapshot_archive_entry(
592 &["snapshots", "①"],
593 tar::EntryType::Directory
594 ));
595 assert!(!is_valid_snapshot_archive_entry(
596 &["snapshots", "0", "aa"],
597 tar::EntryType::Regular
598 ));
599 assert!(!is_valid_snapshot_archive_entry(
600 &["aaaa"],
601 tar::EntryType::Regular
602 ));
603 }
604
605 #[test]
606 fn test_valid_snapshot_accounts() {
607 solana_logger::setup();
608 assert!(is_valid_snapshot_archive_entry(
609 &["accounts", "0.0"],
610 tar::EntryType::Regular
611 ));
612 assert!(is_valid_snapshot_archive_entry(
613 &["accounts", "01829.077"],
614 tar::EntryType::Regular
615 ));
616
617 assert!(!is_valid_snapshot_archive_entry(
618 &["accounts", "1.2.34"],
619 tar::EntryType::Regular
620 ));
621 assert!(!is_valid_snapshot_archive_entry(
622 &["accounts", "12."],
623 tar::EntryType::Regular
624 ));
625 assert!(!is_valid_snapshot_archive_entry(
626 &["accounts", ".12"],
627 tar::EntryType::Regular
628 ));
629 assert!(!is_valid_snapshot_archive_entry(
630 &["accounts", "0x0"],
631 tar::EntryType::Regular
632 ));
633 assert!(!is_valid_snapshot_archive_entry(
634 &["accounts", "abc"],
635 tar::EntryType::Regular
636 ));
637 assert!(!is_valid_snapshot_archive_entry(
638 &["accounts", "232323"],
639 tar::EntryType::Regular
640 ));
641 assert!(!is_valid_snapshot_archive_entry(
642 &["accounts", "৬.¾"],
643 tar::EntryType::Regular
644 ));
645 }
646
647 #[test]
648 fn test_archive_is_valid_archive_entry() {
649 let path = Path::new("");
650 assert_eq!(
651 is_valid_genesis_archive_entry(path, &["genesis.bin"], tar::EntryType::Regular),
652 UnpackPath::Valid(path)
653 );
654 assert_eq!(
655 is_valid_genesis_archive_entry(path, &["genesis.bin"], tar::EntryType::GNUSparse,),
656 UnpackPath::Valid(path)
657 );
658 assert_eq!(
659 is_valid_genesis_archive_entry(path, &["rocksdb"], tar::EntryType::Directory),
660 UnpackPath::Ignore
661 );
662 assert_eq!(
663 is_valid_genesis_archive_entry(path, &["rocksdb", "foo"], tar::EntryType::Regular),
664 UnpackPath::Ignore
665 );
666 assert_eq!(
667 is_valid_genesis_archive_entry(path, &["rocksdb", "foo"], tar::EntryType::GNUSparse,),
668 UnpackPath::Ignore
669 );
670 assert_eq!(
671 is_valid_genesis_archive_entry(path, &["rocksdb_fifo"], tar::EntryType::Directory),
672 UnpackPath::Ignore
673 );
674 assert_eq!(
675 is_valid_genesis_archive_entry(path, &["rocksdb_fifo", "foo"], tar::EntryType::Regular),
676 UnpackPath::Ignore
677 );
678 assert_eq!(
679 is_valid_genesis_archive_entry(
680 path,
681 &["rocksdb_fifo", "foo"],
682 tar::EntryType::GNUSparse,
683 ),
684 UnpackPath::Ignore
685 );
686 assert_eq!(
687 is_valid_genesis_archive_entry(path, &["aaaa"], tar::EntryType::Regular),
688 UnpackPath::Invalid
689 );
690 assert_eq!(
691 is_valid_genesis_archive_entry(path, &["aaaa"], tar::EntryType::GNUSparse,),
692 UnpackPath::Invalid
693 );
694 assert_eq!(
695 is_valid_genesis_archive_entry(path, &["rocksdb"], tar::EntryType::Regular),
696 UnpackPath::Invalid
697 );
698 assert_eq!(
699 is_valid_genesis_archive_entry(path, &["rocksdb"], tar::EntryType::GNUSparse,),
700 UnpackPath::Invalid
701 );
702 assert_eq!(
703 is_valid_genesis_archive_entry(path, &["rocksdb", "foo"], tar::EntryType::Directory,),
704 UnpackPath::Invalid
705 );
706 assert_eq!(
707 is_valid_genesis_archive_entry(
708 path,
709 &["rocksdb", "foo", "bar"],
710 tar::EntryType::Directory,
711 ),
712 UnpackPath::Invalid
713 );
714 assert_eq!(
715 is_valid_genesis_archive_entry(
716 path,
717 &["rocksdb", "foo", "bar"],
718 tar::EntryType::Regular
719 ),
720 UnpackPath::Invalid
721 );
722 assert_eq!(
723 is_valid_genesis_archive_entry(
724 path,
725 &["rocksdb", "foo", "bar"],
726 tar::EntryType::GNUSparse
727 ),
728 UnpackPath::Invalid
729 );
730 assert_eq!(
731 is_valid_genesis_archive_entry(path, &["rocksdb_fifo"], tar::EntryType::Regular),
732 UnpackPath::Invalid
733 );
734 assert_eq!(
735 is_valid_genesis_archive_entry(path, &["rocksdb_fifo"], tar::EntryType::GNUSparse,),
736 UnpackPath::Invalid
737 );
738 assert_eq!(
739 is_valid_genesis_archive_entry(
740 path,
741 &["rocksdb_fifo", "foo"],
742 tar::EntryType::Directory,
743 ),
744 UnpackPath::Invalid
745 );
746 assert_eq!(
747 is_valid_genesis_archive_entry(
748 path,
749 &["rocksdb_fifo", "foo", "bar"],
750 tar::EntryType::Directory,
751 ),
752 UnpackPath::Invalid
753 );
754 assert_eq!(
755 is_valid_genesis_archive_entry(
756 path,
757 &["rocksdb_fifo", "foo", "bar"],
758 tar::EntryType::Regular
759 ),
760 UnpackPath::Invalid
761 );
762 assert_eq!(
763 is_valid_genesis_archive_entry(
764 path,
765 &["rocksdb_fifo", "foo", "bar"],
766 tar::EntryType::GNUSparse
767 ),
768 UnpackPath::Invalid
769 );
770 }
771
772 fn with_finalize_and_unpack<C>(archive: tar::Builder<Vec<u8>>, checker: C) -> Result<()>
773 where
774 C: Fn(&mut Archive<BufReader<&[u8]>>, &Path) -> Result<()>,
775 {
776 let data = archive.into_inner().unwrap();
777 let reader = BufReader::new(&data[..]);
778 let mut archive: Archive<std::io::BufReader<&[u8]>> = Archive::new(reader);
779 let temp_dir = tempfile::TempDir::new().unwrap();
780
781 checker(&mut archive, temp_dir.path())?;
782 let result = temp_dir.close();
784 assert_matches!(result, Ok(()));
785 Ok(())
786 }
787
788 fn finalize_and_unpack_snapshot(archive: tar::Builder<Vec<u8>>) -> Result<()> {
789 with_finalize_and_unpack(archive, |a, b| {
790 unpack_snapshot_with_processors(a, b, &[PathBuf::new()], None, |_, _| {}, |_| {})
791 })
792 }
793
794 fn finalize_and_unpack_genesis(archive: tar::Builder<Vec<u8>>) -> Result<()> {
795 with_finalize_and_unpack(archive, |a, b| {
796 unpack_genesis(a, b, MAX_GENESIS_ARCHIVE_UNPACKED_SIZE)
797 })
798 }
799
800 #[test]
801 fn test_archive_unpack_snapshot_ok() {
802 let mut header = Header::new_gnu();
803 header.set_path("version").unwrap();
804 header.set_size(4);
805 header.set_cksum();
806
807 let data: &[u8] = &[1, 2, 3, 4];
808
809 let mut archive = Builder::new(Vec::new());
810 archive.append(&header, data).unwrap();
811
812 let result = finalize_and_unpack_snapshot(archive);
813 assert_matches!(result, Ok(()));
814 }
815
816 #[test]
817 fn test_archive_unpack_genesis_ok() {
818 let mut header = Header::new_gnu();
819 header.set_path("genesis.bin").unwrap();
820 header.set_size(4);
821 header.set_cksum();
822
823 let data: &[u8] = &[1, 2, 3, 4];
824
825 let mut archive = Builder::new(Vec::new());
826 archive.append(&header, data).unwrap();
827
828 let result = finalize_and_unpack_genesis(archive);
829 assert_matches!(result, Ok(()));
830 }
831
832 #[test]
833 fn test_archive_unpack_genesis_bad_perms() {
834 let mut archive = Builder::new(Vec::new());
835
836 let mut header = Header::new_gnu();
837 header.set_path("rocksdb").unwrap();
838 header.set_entry_type(Directory);
839 header.set_size(0);
840 header.set_cksum();
841 let data: &[u8] = &[];
842 archive.append(&header, data).unwrap();
843
844 let mut header = Header::new_gnu();
845 header.set_path("rocksdb/test").unwrap();
846 header.set_size(4);
847 header.set_cksum();
848 let data: &[u8] = &[1, 2, 3, 4];
849 archive.append(&header, data).unwrap();
850
851 let mut header = Header::new_gnu();
854 header.set_path("rocksdb").unwrap();
855 header.set_entry_type(Directory);
856 header.set_mode(0o000);
857 header.set_size(0);
858 header.set_cksum();
859 let data: &[u8] = &[];
860 archive.append(&header, data).unwrap();
861
862 let result = finalize_and_unpack_genesis(archive);
863 assert_matches!(result, Ok(()));
864 }
865
866 #[test]
867 fn test_archive_unpack_genesis_bad_rocksdb_subdir() {
868 let mut archive = Builder::new(Vec::new());
869
870 let mut header = Header::new_gnu();
871 header.set_path("rocksdb").unwrap();
872 header.set_entry_type(Directory);
873 header.set_size(0);
874 header.set_cksum();
875 let data: &[u8] = &[];
876 archive.append(&header, data).unwrap();
877
878 let mut header = Header::new_gnu();
880 header.set_path("rocksdb/test/").unwrap();
881 header.set_entry_type(Regular);
882 header.set_size(0);
883 header.set_cksum();
884 let data: &[u8] = &[];
885 archive.append(&header, data).unwrap();
886
887 let result = finalize_and_unpack_genesis(archive);
888 assert_matches!(result, Err(UnpackError::Archive(ref message)) if message == "invalid path found: \"rocksdb/test/\"");
889 }
890
891 #[test]
892 fn test_archive_unpack_snapshot_invalid_path() {
893 let mut header = Header::new_gnu();
894 for (p, c) in header
896 .as_old_mut()
897 .name
898 .iter_mut()
899 .zip(b"foo/../../../dangerous".iter().chain(Some(&0)))
900 {
901 *p = *c;
902 }
903 header.set_size(4);
904 header.set_cksum();
905
906 let data: &[u8] = &[1, 2, 3, 4];
907
908 let mut archive = Builder::new(Vec::new());
909 archive.append(&header, data).unwrap();
910 let result = finalize_and_unpack_snapshot(archive);
911 assert_matches!(result, Err(UnpackError::Archive(ref message)) if message == "invalid path found: \"foo/../../../dangerous\"");
912 }
913
914 fn with_archive_unpack_snapshot_invalid_path(path: &str) -> Result<()> {
915 let mut header = Header::new_gnu();
916 for (p, c) in header
918 .as_old_mut()
919 .name
920 .iter_mut()
921 .zip(path.as_bytes().iter().chain(Some(&0)))
922 {
923 *p = *c;
924 }
925 header.set_size(4);
926 header.set_cksum();
927
928 let data: &[u8] = &[1, 2, 3, 4];
929
930 let mut archive = Builder::new(Vec::new());
931 archive.append(&header, data).unwrap();
932 with_finalize_and_unpack(archive, |unpacking_archive, path| {
933 for entry in unpacking_archive.entries()? {
934 if !entry?.unpack_in(path)? {
935 return Err(UnpackError::Archive("failed!".to_string()));
936 } else if !path.join(path).exists() {
937 return Err(UnpackError::Archive("not existing!".to_string()));
938 }
939 }
940 Ok(())
941 })
942 }
943
944 #[test]
945 fn test_archive_unpack_itself() {
946 assert_matches!(
947 with_archive_unpack_snapshot_invalid_path("ryoqun/work"),
948 Ok(())
949 );
950 assert_matches!(
952 with_archive_unpack_snapshot_invalid_path("/etc/passwd"),
953 Ok(())
954 );
955 assert_matches!(with_archive_unpack_snapshot_invalid_path("../../../dangerous"), Err(UnpackError::Archive(ref message)) if message == "failed!");
956 }
957
958 #[test]
959 fn test_archive_unpack_snapshot_invalid_entry() {
960 let mut header = Header::new_gnu();
961 header.set_path("foo").unwrap();
962 header.set_size(4);
963 header.set_cksum();
964
965 let data: &[u8] = &[1, 2, 3, 4];
966
967 let mut archive = Builder::new(Vec::new());
968 archive.append(&header, data).unwrap();
969 let result = finalize_and_unpack_snapshot(archive);
970 assert_matches!(result, Err(UnpackError::Archive(ref message)) if message == "extra entry found: \"foo\" Regular");
971 }
972
973 #[test]
974 fn test_archive_unpack_snapshot_too_large() {
975 let mut header = Header::new_gnu();
976 header.set_path("version").unwrap();
977 header.set_size(1024 * 1024 * 1024 * 1024 * 1024);
978 header.set_cksum();
979
980 let data: &[u8] = &[1, 2, 3, 4];
981
982 let mut archive = Builder::new(Vec::new());
983 archive.append(&header, data).unwrap();
984 let result = finalize_and_unpack_snapshot(archive);
985 assert_matches!(
986 result,
987 Err(UnpackError::Archive(ref message))
988 if message == &format!(
989 "too large archive: 1125899906842624 than limit: {MAX_SNAPSHOT_ARCHIVE_UNPACKED_APPARENT_SIZE}"
990 )
991 );
992 }
993
994 #[test]
995 fn test_archive_unpack_snapshot_bad_unpack() {
996 let result = check_unpack_result(false, "abc".to_string());
997 assert_matches!(result, Err(UnpackError::Archive(ref message)) if message == "failed to unpack: \"abc\"");
998 }
999
1000 #[test]
1001 fn test_archive_checked_total_size_sum() {
1002 let result = checked_total_size_sum(500, 500, MAX_SNAPSHOT_ARCHIVE_UNPACKED_ACTUAL_SIZE);
1003 assert_matches!(result, Ok(1000));
1004
1005 let result = checked_total_size_sum(
1006 u64::max_value() - 2,
1007 2,
1008 MAX_SNAPSHOT_ARCHIVE_UNPACKED_ACTUAL_SIZE,
1009 );
1010 assert_matches!(
1011 result,
1012 Err(UnpackError::Archive(ref message))
1013 if message == &format!(
1014 "too large archive: 18446744073709551615 than limit: {MAX_SNAPSHOT_ARCHIVE_UNPACKED_ACTUAL_SIZE}"
1015 )
1016 );
1017 }
1018
1019 #[test]
1020 fn test_archive_checked_total_size_count() {
1021 let result = checked_total_count_increment(101, MAX_SNAPSHOT_ARCHIVE_UNPACKED_COUNT);
1022 assert_matches!(result, Ok(102));
1023
1024 let result =
1025 checked_total_count_increment(999_999_999_999, MAX_SNAPSHOT_ARCHIVE_UNPACKED_COUNT);
1026 assert_matches!(
1027 result,
1028 Err(UnpackError::Archive(ref message))
1029 if message == "too many files in snapshot: 1000000000000"
1030 );
1031 }
1032}