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