1use crate::traverse::{EntryData, Traversal, TreeIndex};
4use anyhow::{Context, Result, anyhow, bail};
5use std::{
6 borrow::Cow,
7 collections::HashSet,
8 io::{self, BufRead, BufReader, BufWriter, Read, Seek, SeekFrom, Write},
9 path::Path,
10 time::{Duration, SystemTime, UNIX_EPOCH},
11};
12
13const MAGIC: &[u8; 8] = b"DUASNAP\0";
14const VERSION: u16 = 1;
15const HEADER_LEN: usize = 12;
16const MAX_NAME_LEN: usize = 1024 * 1024;
17const MAX_RECORD_LEN: usize = 2 * 1024 * 1024;
18const DIGEST_LEN: usize = 32;
19
20#[cfg(unix)]
22const PATH_ENCODING: u8 = 0;
23#[cfg(windows)]
25const PATH_ENCODING: u8 = 1;
26
27const FLAG_DIRECTORY: u8 = 0x01;
28const FLAG_METADATA_IO_ERROR: u8 = 0x02;
29const FLAG_ENTRY_COUNT: u8 = 0x04;
30const KNOWN_FLAGS: u8 = FLAG_DIRECTORY | FLAG_METADATA_IO_ERROR | FLAG_ENTRY_COUNT;
31
32#[derive(Debug)]
34pub struct Snapshot {
35 pub traversal: Traversal,
37 pub roots: Vec<TreeIndex>,
39}
40
41pub(crate) struct DecodedEntry<'a> {
42 pub(crate) depth: usize,
44 pub(crate) data: EntryData,
45 pub(crate) native_name: &'a [u8],
47 pub(crate) sibling_ordinal: u64,
48}
49
50impl<'a> DecodedEntry<'a> {
51 pub(crate) fn name(&self) -> Cow<'a, Path> {
52 native_name_from_bytes(self.native_name)
53 }
54}
55
56struct DecodeSummary {
57 total_size: u128,
58 total_entries: u64,
59 digest: [u8; DIGEST_LEN],
60}
61
62struct OpenNode {
63 id: u64,
64 is_dir: bool,
65 has_child: bool,
66 last_child_ordinal: u64,
67}
68
69enum SnapshotReader<R> {
70 Raw(BufReader<R>),
71 Zlib {
72 reader: BufReader<R>,
73 decompressor: gix::zlib::Decompress,
74 finished: bool,
77 },
78}
79
80struct Decoder<R> {
81 reader: HashingReader<BufReader<SnapshotReader<R>>>,
82 open_nodes: Vec<OpenNode>,
83 sibling_names: Vec<Vec<u8>>,
84 record: Vec<u8>,
85 node_count: u64,
86 total_size: u128,
87 total_entries: u64,
88 summary: Option<DecodeSummary>,
89}
90
91impl<R: Read> SnapshotReader<R> {
92 fn new(reader: R) -> Result<Self> {
93 let mut reader = BufReader::new(reader);
94 if is_zlib_header(reader.fill_buf()?) {
95 Ok(Self::Zlib {
96 reader,
97 decompressor: gix::zlib::Decompress::new(),
98 finished: false,
99 })
100 } else {
101 Ok(Self::Raw(reader))
102 }
103 }
104}
105
106impl<R: Read> Read for SnapshotReader<R> {
107 fn read(&mut self, buffer: &mut [u8]) -> io::Result<usize> {
108 match self {
109 Self::Raw(reader) => reader.read(buffer),
110 Self::Zlib {
111 reader,
112 decompressor,
113 finished,
114 } => {
115 if buffer.is_empty() {
116 return Ok(0);
117 }
118 if *finished {
119 return if reader.fill_buf()?.is_empty() {
120 Ok(0)
121 } else {
122 Err(invalid_data("compressed snapshot has trailing data"))
123 };
124 }
125 loop {
126 let (status, consumed, written, eof) = {
127 let input = reader.fill_buf()?;
128 let eof = input.is_empty();
129 let input_before = decompressor.total_in();
130 let output_before = decompressor.total_out();
131 let flush = if eof {
132 gix::zlib::FlushDecompress::Finish
133 } else {
134 gix::zlib::FlushDecompress::None
135 };
136 let status =
137 decompressor
138 .decompress(input, buffer, flush)
139 .map_err(|err| {
140 io::Error::new(
141 io::ErrorKind::InvalidData,
142 format!("could not decompress snapshot: {err}"),
143 )
144 })?;
145 (
146 status,
147 (decompressor.total_in() - input_before) as usize,
148 (decompressor.total_out() - output_before) as usize,
149 eof,
150 )
151 };
152 reader.consume(consumed);
153
154 match status {
155 gix::zlib::Status::StreamEnd => {
156 *finished = true;
157 if written == 0 && !reader.fill_buf()?.is_empty() {
158 return Err(invalid_data("compressed snapshot has trailing data"));
159 }
160 return Ok(written);
161 }
162 gix::zlib::Status::Ok | gix::zlib::Status::BufError => {
163 if written != 0 {
164 return Ok(written);
165 }
166 if eof {
167 return Err(invalid_data("compressed snapshot is truncated"));
168 }
169 if consumed == 0 {
170 return Err(invalid_data(
171 "compressed snapshot decoder made no progress",
172 ));
173 }
174 }
175 }
176 }
177 }
178 }
179 }
180}
181
182fn is_zlib_header(bytes: &[u8]) -> bool {
183 let [compression, flags, ..] = bytes else {
184 return false;
185 };
186 compression & 0x0f == 8
188 && compression >> 4 <= 7
190 && u16::from_be_bytes([*compression, *flags]) % 31 == 0
192 && flags & 0x20 == 0
194}
195
196fn verify_checksum(reader: impl Read) -> Result<[u8; DIGEST_LEN]> {
197 let mut reader = SnapshotReader::new(reader)?;
198 let mut hash = gix::hash::hasher(gix::hash::Kind::Sha256);
199 let mut buffer = vec![0; 64 * 1024];
200 let mut tail = Vec::with_capacity(DIGEST_LEN + buffer.len());
201 loop {
202 let read = reader.read(&mut buffer)?;
203 if read == 0 {
204 break;
205 }
206 tail.extend_from_slice(&buffer[..read]);
207 if tail.len() > DIGEST_LEN {
208 let hashed = tail.len() - DIGEST_LEN;
209 hash.update(&tail[..hashed]);
210 tail.copy_within(hashed.., 0);
211 tail.truncate(DIGEST_LEN);
212 }
213 }
214 if tail.len() != DIGEST_LEN {
215 bail!("snapshot checksum is truncated");
216 }
217 let actual = hash.try_finalize()?;
218 if actual.as_slice() != tail {
219 bail!("snapshot checksum mismatch");
220 }
221 let mut digest = [0; DIGEST_LEN];
222 digest.copy_from_slice(actual.as_slice());
223 Ok(digest)
224}
225
226pub struct Replay<R> {
232 reader: R,
233 start: u64,
234 digest: [u8; DIGEST_LEN],
235}
236
237pub(crate) struct ReplayEntries<'a, R> {
238 decoder: Decoder<&'a mut R>,
239 expected_digest: [u8; DIGEST_LEN],
240}
241
242impl<R: Read> ReplayEntries<'_, R> {
243 pub(crate) fn next_entry(&mut self) -> Result<Option<DecodedEntry<'_>>> {
244 self.decoder.next_entry(Some(&self.expected_digest))
245 }
246}
247
248impl<R: Read + Seek> Replay<R> {
249 pub fn new(mut reader: R) -> Result<Self> {
251 let start = reader
252 .stream_position()
253 .context("could not determine snapshot stream position")?;
254 let digest = verify_checksum(&mut reader)?;
255 reader
257 .seek(SeekFrom::Start(start))
258 .context("could not rewind snapshot")?;
259 Ok(Self {
260 reader,
261 start,
262 digest,
263 })
264 }
265
266 pub(crate) fn for_each_entry(
267 &mut self,
268 mut on_entry: impl for<'entry> FnMut(DecodedEntry<'entry>) -> Result<()>,
269 ) -> Result<()> {
270 let mut entries = self.entries()?;
271 while let Some(entry) = entries.next_entry()? {
272 on_entry(entry)?;
273 }
274 Ok(())
275 }
276
277 pub(crate) fn entries(&mut self) -> Result<ReplayEntries<'_, R>> {
278 self.reader
279 .seek(SeekFrom::Start(self.start))
280 .context("could not rewind snapshot")?;
281 Ok(ReplayEntries {
282 decoder: Decoder::new(&mut self.reader)?,
283 expected_digest: self.digest,
284 })
285 }
286}
287
288pub fn write(
292 writer: impl Write,
293 traversal: &Traversal,
294 roots: &[TreeIndex],
295 compression_level: Option<i32>,
296) -> Result<()> {
297 let Some(level) = compression_level else {
298 return write_raw(writer, traversal, roots);
299 };
300 let compression = gix::zlib::Compression::new(level)
301 .with_context(|| format!("snapshot compression level {level} is outside 0..=9"))?;
302 let mut writer = gix::zlib::stream::deflate::Write::new(writer, compression);
303 write_raw(&mut writer, traversal, roots)?;
304 Ok(())
305}
306
307fn write_raw(writer: impl Write, traversal: &Traversal, roots: &[TreeIndex]) -> Result<()> {
308 let mut writer = gix::hash::io::Write::new(BufWriter::new(writer), gix::hash::Kind::Sha256);
309 let mut header = [0; HEADER_LEN];
310 header[..MAGIC.len()].copy_from_slice(MAGIC);
311 header[8..10].copy_from_slice(&VERSION.to_le_bytes());
312 header[10] = PATH_ENCODING;
313 writer.write_all(&header)?;
314
315 let mut seen_roots = HashSet::with_capacity(roots.len());
316 for &root in roots {
317 if !seen_roots.insert(root) {
318 bail!("snapshot roots contain duplicate node {root:?}");
319 }
320 }
321 drop(seen_roots);
322
323 let mut stack = Vec::new();
324 let mut record = Vec::new();
325 let mut node_count = 0u64;
326 for &root in roots {
327 stack.push((root, traversal.root_index, 0));
328 while let Some((index, expected_parent, parent_id)) = stack.pop() {
329 if index == traversal.root_index {
330 bail!("snapshot traversal contains a cycle or shared node at {index:?}");
331 }
332 let entry = traversal
333 .tree
334 .entry(index)
335 .ok_or_else(|| anyhow!("snapshot node {index:?} does not exist"))?;
336 if traversal.tree.parent(index) != Some(expected_parent) {
337 bail!("snapshot traversal contains a cycle or shared node at {index:?}");
338 }
339 let name = traversal
340 .tree
341 .native_name(index)
342 .expect("existing tree entry has a name");
343 validate_name(name, parent_id == 0)?;
344 let node_id = node_count
345 .checked_add(1)
346 .context("snapshot contains too many nodes")?;
347 let parent_distance = node_id
348 .checked_sub(parent_id)
349 .filter(|distance| *distance != 0)
350 .context("snapshot parent was not written before its child")?;
351
352 record.clear();
353 push_uleb128(&mut record, u128::from(parent_distance));
354 let mut flags = (u8::from(entry.is_dir) * FLAG_DIRECTORY)
355 | (u8::from(entry.metadata_io_error) * FLAG_METADATA_IO_ERROR);
356 if entry.entry_count.is_some() {
357 flags |= FLAG_ENTRY_COUNT;
358 }
359 record.push(flags);
360 push_uleb128(
361 &mut record,
362 u128::try_from(name.len()).context("snapshot name is too long")?,
363 );
364 record.extend_from_slice(name);
365 push_uleb128(&mut record, entry.size);
366 let (seconds, nanos) = split_time(entry.mtime)?;
367 push_uleb128(&mut record, u128::from(zigzag_encode(seconds)));
368 push_uleb128(&mut record, u128::from(nanos));
369 if let Some(count) = entry.entry_count {
370 push_uleb128(&mut record, u128::from(count));
371 }
372 if record.len() > MAX_RECORD_LEN {
373 bail!("snapshot record exceeds the {MAX_RECORD_LEN}-byte limit");
374 }
375 write_uleb128(&mut writer, record.len() as u128)?;
376 writer.write_all(&record)?;
377
378 let mut children = traversal.tree.children(index).collect::<Vec<_>>();
379 for &child in &children {
380 validate_name(
381 traversal
382 .tree
383 .native_name(child)
384 .ok_or_else(|| anyhow!("snapshot child {child:?} does not exist"))?,
385 false,
386 )?;
387 }
388 if !children.is_empty() && !entry.is_dir {
389 bail!("snapshot file node {index:?} has children");
390 }
391 children.sort_by(|left, right| {
392 traversal
393 .tree
394 .native_name(*left)
395 .cmp(&traversal.tree.native_name(*right))
396 .then_with(|| left.index().cmp(&right.index()))
397 });
398 stack.extend(
399 children
400 .into_iter()
401 .rev()
402 .map(|child| (child, index, node_id)),
403 );
404 node_count = node_id;
405 }
406 }
407
408 writer.write_all(&[0])?;
409 write_uleb128(&mut writer, u128::from(node_count))?;
410 let gix::hash::io::Write { mut inner, hash } = writer;
411 let digest = hash.try_finalize()?;
412 inner.write_all(digest.as_slice())?;
413 inner.flush()?;
414 Ok(())
415}
416
417pub fn read(reader: impl Read) -> Result<Snapshot> {
419 let mut traversal = Traversal::new();
420 traversal.cost = Some(Duration::ZERO);
421 let mut parents = Vec::new();
422 let mut roots = Vec::new();
423 let summary = decode(reader, |entry| {
424 parents.truncate(entry.depth);
425 let parent = parents.last().copied().unwrap_or(traversal.root_index);
426 let node = traversal
427 .tree
428 .try_add_child_native(parent, entry.native_name, entry.data)
429 .map_err(|err| anyhow!("could not add snapshot entry: {err}"))?;
430 if entry.depth == 0 {
431 roots
432 .try_reserve(1)
433 .context("could not grow snapshot root table")?;
434 roots.push(node);
435 }
436 parents
437 .try_reserve(1)
438 .context("could not grow snapshot ancestor stack")?;
439 parents.push(node);
440 Ok(())
441 })?;
442
443 traversal
444 .tree
445 .update(traversal.root_index, |synthetic_root| {
446 synthetic_root.size = summary.total_size;
447 synthetic_root.entry_count = (!roots.is_empty()).then_some(summary.total_entries);
448 });
449
450 Ok(Snapshot { traversal, roots })
451}
452
453fn decode(
454 reader: impl Read,
455 mut on_entry: impl for<'entry> FnMut(DecodedEntry<'entry>) -> Result<()>,
456) -> Result<DecodeSummary> {
457 let mut decoder = Decoder::new(reader)?;
458 while let Some(entry) = decoder.next_entry(None)? {
459 on_entry(entry)?;
460 }
461 Ok(decoder.summary.expect("end of snapshot stores its summary"))
462}
463
464impl<R: Read> Decoder<R> {
465 fn new(reader: R) -> Result<Self> {
466 let mut reader = HashingReader::new(BufReader::new(SnapshotReader::new(reader)?));
467 let mut header = [0; HEADER_LEN];
468 read_exact(&mut reader, &mut header)?;
469 if &header[..MAGIC.len()] != MAGIC {
470 bail!("invalid snapshot at byte 0: bad magic");
471 }
472 let version = u16::from_le_bytes([header[8], header[9]]);
473 if version != VERSION {
474 bail!("invalid snapshot at byte 8: unsupported version {version}");
475 }
476 if header[10] != PATH_ENCODING {
477 bail!(
478 "invalid snapshot at byte 10: path encoding {} is incompatible with this host",
479 header[10]
480 );
481 }
482 if header[11] != 0 {
483 bail!("invalid snapshot at byte 11: unknown header flags");
484 }
485
486 Ok(Self {
487 reader,
488 open_nodes: Vec::new(),
489 sibling_names: Vec::new(),
490 record: Vec::new(),
491 node_count: 0,
492 total_size: 0,
493 total_entries: 0,
494 summary: None,
495 })
496 }
497
498 fn next_entry(
499 &mut self,
500 expected_digest: Option<&[u8; DIGEST_LEN]>,
501 ) -> Result<Option<DecodedEntry<'_>>> {
502 if self.summary.is_some() {
503 return Ok(None);
504 }
505
506 let length_offset = self.reader.offset;
507 let record_len = read_u64(&mut self.reader)
508 .map_err(|err| anyhow!("invalid snapshot integer at byte {length_offset}: {err}"))?;
509 if record_len == 0 {
510 self.finish()?;
511 if expected_digest.is_some_and(|expected| {
512 self.summary
513 .as_ref()
514 .expect("end of snapshot stores its summary")
515 .digest
516 != *expected
517 }) {
518 bail!("snapshot changed since it was verified");
519 }
520 return Ok(None);
521 }
522 let record_len = usize::try_from(record_len)
523 .context("snapshot record length exceeds this address space")?;
524 if record_len > MAX_RECORD_LEN {
525 bail!(
526 "invalid snapshot at byte {length_offset}: record exceeds the {MAX_RECORD_LEN}-byte limit"
527 );
528 }
529
530 self.record.clear();
531 if self.record.capacity() < record_len {
532 self.record
533 .try_reserve(record_len)
534 .context("could not allocate snapshot record")?;
535 }
536 self.record.resize(record_len, 0);
537 let record_offset = self.reader.offset;
538 read_exact(&mut self.reader, &mut self.record)?;
539
540 let node_id = self
541 .node_count
542 .checked_add(1)
543 .context("snapshot contains too many nodes")?;
544 let (parent_id, native_name, data) = parse_record(&self.record, node_id)
545 .map_err(|err| anyhow!("invalid snapshot record at byte {record_offset}: {err}"))?;
546 if node_id >= u64::from(u32::MAX) {
547 bail!("snapshot exceeds the tree node-index limit");
548 }
549
550 let sibling_ordinal = if parent_id == 0 {
551 self.open_nodes.clear();
552 0
553 } else {
554 while self
555 .open_nodes
556 .last()
557 .is_some_and(|node| node.id != parent_id)
558 {
559 self.open_nodes.pop();
560 }
561 let parent_depth = self.open_nodes.len().saturating_sub(1);
562 let parent = self.open_nodes.last_mut().with_context(|| {
563 format!(
564 "invalid snapshot record at byte {record_offset}: parent is outside the current depth-first subtree"
565 )
566 })?;
567 if !parent.is_dir {
568 bail!("invalid snapshot record at byte {record_offset}: parent is not a directory");
569 }
570 let previous = &mut self.sibling_names[parent_depth];
571 let ordinal = if parent.has_child && previous.as_slice() > native_name {
572 bail!(
573 "invalid snapshot record at byte {record_offset}: sibling names are not in canonical order"
574 );
575 } else if parent.has_child && previous.as_slice() == native_name {
576 parent
577 .last_child_ordinal
578 .checked_add(1)
579 .context("snapshot contains too many duplicate sibling names")?
580 } else {
581 0
582 };
583 previous.clear();
584 previous.extend_from_slice(native_name);
585 parent.has_child = true;
586 parent.last_child_ordinal = ordinal;
587 ordinal
588 };
589
590 let depth = self.open_nodes.len();
591 if depth == 0 {
592 self.total_size = self
593 .total_size
594 .checked_add(data.size)
595 .context("snapshot root size total overflows u128")?;
596 self.total_entries = self
597 .total_entries
598 .checked_add(data.entry_count.unwrap_or(1))
599 .context("snapshot root entry count total overflows u64")?;
600 }
601
602 self.open_nodes
603 .try_reserve(1)
604 .context("could not grow snapshot ancestor stack")?;
605 if self.sibling_names.len() <= depth {
606 self.sibling_names
607 .try_reserve(1)
608 .context("could not grow snapshot sibling buffers")?;
609 self.sibling_names.push(Vec::new());
610 } else {
611 self.sibling_names[depth].clear();
612 }
613 self.open_nodes.push(OpenNode {
614 id: node_id,
615 is_dir: data.is_dir,
616 has_child: false,
617 last_child_ordinal: 0,
618 });
619 self.node_count = node_id;
620 Ok(Some(DecodedEntry {
621 depth,
622 data,
623 native_name,
624 sibling_ordinal,
625 }))
626 }
627
628 fn finish(&mut self) -> Result<()> {
629 let count_offset = self.reader.offset;
630 let footer_count = read_u64(&mut self.reader)
631 .map_err(|err| anyhow!("invalid snapshot node count at byte {count_offset}: {err}"))?;
632 if footer_count != self.node_count {
633 bail!(
634 "invalid snapshot at byte {count_offset}: footer names {footer_count} nodes but read {}",
635 self.node_count
636 );
637 }
638
639 let actual = self.reader.hash.clone().try_finalize()?;
640 let mut expected = [0; DIGEST_LEN];
641 self.reader
642 .inner
643 .read_exact(&mut expected)
644 .context("snapshot checksum is truncated")?;
645 if actual.as_slice() != expected {
646 bail!("snapshot checksum mismatch");
647 }
648 let mut trailing = [0];
649 if self.reader.inner.read(&mut trailing)? != 0 {
650 bail!("snapshot has trailing data");
651 }
652
653 let mut digest = [0; DIGEST_LEN];
654 digest.copy_from_slice(actual.as_slice());
655 self.summary = Some(DecodeSummary {
656 total_size: self.total_size,
657 total_entries: self.total_entries,
658 digest,
659 });
660 Ok(())
661 }
662}
663
664fn parse_record(record: &[u8], node_id: u64) -> Result<(u64, &[u8], EntryData)> {
665 let mut cursor = io::Cursor::new(record);
666 let parent_distance = read_u64(&mut cursor)?;
667 let parent_id = node_id
668 .checked_sub(parent_distance)
669 .filter(|_| parent_distance != 0)
670 .context("parent distance is zero or points forward")?;
671
672 let mut flags = [0];
673 cursor.read_exact(&mut flags)?;
674 let flags = flags[0];
675 if flags & !KNOWN_FLAGS != 0 {
676 bail!("unknown node flags {flags:#04x}");
677 }
678
679 let name_len = usize::try_from(read_u64(&mut cursor)?)
680 .context("snapshot name length exceeds this address space")?;
681 if name_len > MAX_NAME_LEN {
682 bail!("name exceeds the {MAX_NAME_LEN}-byte limit");
683 }
684 let name_start = usize::try_from(cursor.position()).context("record offset is too large")?;
685 let name_end = name_start
686 .checked_add(name_len)
687 .filter(|end| *end <= record.len())
688 .context("record ends within its name")?;
689 let name = &record[name_start..name_end];
690 cursor.set_position(name_end as u64);
691 validate_name(name, parent_id == 0)?;
692
693 let size = read_uleb128(&mut cursor, 128)?;
694 let seconds = zigzag_decode(read_u64(&mut cursor)?);
695 let nanos = u32::try_from(read_u64(&mut cursor)?)
696 .ok()
697 .filter(|nanos| *nanos < 1_000_000_000)
698 .context("modification time has invalid nanoseconds")?;
699 let mtime = join_time(seconds, nanos)?;
700 let entry_count = if flags & FLAG_ENTRY_COUNT != 0 {
701 Some(read_u64(&mut cursor)?)
702 } else {
703 None
704 };
705 if cursor.position() != record.len() as u64 {
706 bail!("record length does not match its fields");
707 }
708
709 Ok((
710 parent_id,
711 name,
712 EntryData {
713 size,
714 mtime,
715 entry_count,
716 metadata_io_error: flags & FLAG_METADATA_IO_ERROR != 0,
717 is_dir: flags & FLAG_DIRECTORY != 0,
718 },
719 ))
720}
721
722fn split_time(time: SystemTime) -> Result<(i64, u32)> {
723 match time.duration_since(UNIX_EPOCH) {
724 Ok(duration) => Ok((
725 i64::try_from(duration.as_secs())
726 .context("modification time is outside the snapshot range")?,
727 duration.subsec_nanos(),
728 )),
729 Err(error) => {
730 let duration = error.duration();
731 let nanos = duration.subsec_nanos();
732 let seconds = if nanos == 0 {
733 -i128::from(duration.as_secs())
734 } else {
735 -i128::from(duration.as_secs()) - 1
736 };
737 Ok((
738 i64::try_from(seconds)
739 .context("modification time is outside the snapshot range")?,
740 if nanos == 0 { 0 } else { 1_000_000_000 - nanos },
741 ))
742 }
743 }
744}
745
746fn join_time(seconds: i64, nanos: u32) -> Result<SystemTime> {
747 let time = if seconds >= 0 {
748 UNIX_EPOCH.checked_add(Duration::new(seconds.cast_unsigned(), nanos))
749 } else if nanos == 0 {
750 UNIX_EPOCH.checked_sub(Duration::new(seconds.unsigned_abs(), 0))
751 } else {
752 UNIX_EPOCH.checked_sub(Duration::new(
753 seconds.unsigned_abs() - 1,
754 1_000_000_000 - nanos,
755 ))
756 };
757 let time = time.context("modification time is not representable on this host")?;
758 if split_time(time)? != (seconds, nanos) {
759 bail!("modification time loses precision on this host");
760 }
761 Ok(time)
762}
763
764fn zigzag_encode(value: i64) -> u64 {
765 (value.cast_unsigned() << 1) ^ (value >> 63).cast_unsigned()
766}
767
768fn zigzag_decode(value: u64) -> i64 {
769 (value >> 1).cast_signed() ^ -(value & 1).cast_signed()
770}
771
772fn push_uleb128(out: &mut Vec<u8>, mut value: u128) {
773 loop {
774 let byte = (value & 0x7f) as u8;
775 value >>= 7;
776 out.push(byte | (u8::from(value != 0) * 0x80));
777 if value == 0 {
778 return;
779 }
780 }
781}
782
783fn write_uleb128(out: &mut impl Write, value: u128) -> io::Result<()> {
784 let mut encoded = [0; 19];
785 let mut len = 0;
786 let mut value = value;
787 loop {
788 let byte = (value & 0x7f) as u8;
789 value >>= 7;
790 encoded[len] = byte | (u8::from(value != 0) * 0x80);
791 len += 1;
792 if value == 0 {
793 return out.write_all(&encoded[..len]);
794 }
795 }
796}
797
798fn read_u64(input: &mut impl Read) -> io::Result<u64> {
799 u64::try_from(read_uleb128(input, 64)?).map_err(|_| invalid_data("ULEB128 value overflows u64"))
800}
801
802fn read_uleb128(input: &mut impl Read, bits: u32) -> io::Result<u128> {
803 let groups = bits.div_ceil(7);
804 let mut value = 0u128;
805 for group in 0..groups {
806 let mut byte = [0];
807 input.read_exact(&mut byte)?;
808 let payload = u128::from(byte[0] & 0x7f);
809 let shift = group * 7;
810 let remaining = bits - shift;
811 if remaining < 7 && payload >= 1u128 << remaining {
812 return Err(invalid_data("ULEB128 value overflows its field"));
813 }
814 value |= payload << shift;
815 if byte[0] & 0x80 == 0 {
816 if group != 0 && payload == 0 {
817 return Err(invalid_data("ULEB128 value is not canonical"));
818 }
819 return Ok(value);
820 }
821 }
822 Err(invalid_data("ULEB128 value is overlong"))
823}
824
825#[cfg(unix)]
826fn native_name_from_bytes(name: &[u8]) -> Cow<'_, Path> {
827 use std::os::unix::ffi::OsStrExt as _;
828 Cow::Borrowed(Path::new(std::ffi::OsStr::from_bytes(name)))
829}
830
831#[cfg(all(test, unix))]
832fn native_name_bytes(path: &Path) -> Vec<u8> {
833 use std::os::unix::ffi::OsStrExt as _;
834 path.as_os_str().as_bytes().to_vec()
835}
836
837#[cfg(all(test, windows))]
838fn native_name_bytes(path: &Path) -> Vec<u8> {
839 use std::os::windows::ffi::OsStrExt as _;
840 path.as_os_str()
841 .encode_wide()
842 .flat_map(u16::to_le_bytes)
843 .collect()
844}
845
846#[cfg(windows)]
847fn native_name_from_bytes(name: &[u8]) -> Cow<'_, Path> {
848 use std::os::windows::ffi::OsStringExt as _;
849 debug_assert_eq!(name.len() % 2, 0);
850 let wide = name
851 .chunks_exact(2)
852 .map(|bytes| u16::from_le_bytes([bytes[0], bytes[1]]))
853 .collect::<Vec<_>>();
854 Cow::Owned(std::path::PathBuf::from(std::ffi::OsString::from_wide(
855 &wide,
856 )))
857}
858
859#[cfg(unix)]
860fn validate_name(name: &[u8], is_root: bool) -> Result<()> {
861 if name.contains(&0) {
862 bail!("path contains NUL");
863 }
864 if !is_root && (name.is_empty() || name.contains(&b'/') || matches!(name, b"." | b"..")) {
865 bail!("child name is not one native path component");
866 }
867 if name.len() > MAX_NAME_LEN {
868 bail!("name exceeds the {MAX_NAME_LEN}-byte limit");
869 }
870 Ok(())
871}
872
873#[cfg(windows)]
874fn validate_name(name: &[u8], is_root: bool) -> Result<()> {
875 if name.len() % 2 != 0 {
876 bail!("Windows path has an odd byte length");
877 }
878 let mut wide = name
879 .chunks_exact(2)
880 .map(|bytes| u16::from_le_bytes([bytes[0], bytes[1]]));
881 if wide.clone().any(|unit| unit == 0) {
882 bail!("path contains NUL");
883 }
884 if !is_root {
885 let first = wide.next();
886 let second = wide.next();
887 if first.is_none()
888 || name.chunks_exact(2).any(|bytes| {
889 let unit = u16::from_le_bytes([bytes[0], bytes[1]]);
890 unit == u16::from(b'/') || unit == u16::from(b'\\')
891 })
892 || first == Some(u16::from(b'.')) && second.is_none()
893 || first == Some(u16::from(b'.'))
894 && second == Some(u16::from(b'.'))
895 && wide.next().is_none()
896 {
897 bail!("child name is not one native path component");
898 }
899 }
900 if name.len() > MAX_NAME_LEN {
901 bail!("name exceeds the {MAX_NAME_LEN}-byte limit");
902 }
903 Ok(())
904}
905
906struct HashingReader<R> {
907 inner: R,
908 hash: gix::hash::Hasher,
909 offset: u64,
910}
911
912impl<R> HashingReader<R> {
913 fn new(inner: R) -> Self {
914 Self {
915 inner,
916 hash: gix::hash::hasher(gix::hash::Kind::Sha256),
917 offset: 0,
918 }
919 }
920}
921
922impl<R: Read> Read for HashingReader<R> {
923 fn read(&mut self, buffer: &mut [u8]) -> io::Result<usize> {
924 let read = self.inner.read(buffer)?;
925 self.hash.update(&buffer[..read]);
926 self.offset = self
927 .offset
928 .checked_add(read as u64)
929 .ok_or_else(|| invalid_data("snapshot byte offset overflow"))?;
930 Ok(read)
931 }
932}
933
934fn read_exact<R: Read>(reader: &mut HashingReader<R>, buffer: &mut [u8]) -> Result<()> {
935 reader
936 .read_exact(buffer)
937 .with_context(|| format!("snapshot is truncated at byte {}", reader.offset))
938}
939
940fn invalid_data(message: &'static str) -> io::Error {
941 io::Error::new(io::ErrorKind::InvalidData, message)
942}
943
944#[cfg(test)]
945mod tests;