Show More
@@ -1,110 +1,121 b'' | |||
|
1 | 1 | use crate::errors::{HgError, HgResultExt}; |
|
2 | use crate::requirements; | |
|
2 | 3 | use bytes_cast::{unaligned, BytesCast}; |
|
3 | 4 | use memmap::Mmap; |
|
4 | 5 | use std::path::{Path, PathBuf}; |
|
5 | 6 | |
|
6 | 7 | use super::revlog::RevlogError; |
|
7 | 8 | use crate::repo::Repo; |
|
8 | 9 | use crate::utils::strip_suffix; |
|
9 | 10 | |
|
10 | 11 | const ONDISK_VERSION: u8 = 1; |
|
11 | 12 | |
|
12 | 13 | pub(super) struct NodeMapDocket { |
|
13 | 14 | pub data_length: usize, |
|
14 | 15 | // TODO: keep here more of the data from `parse()` when we need it |
|
15 | 16 | } |
|
16 | 17 | |
|
17 | 18 | #[derive(BytesCast)] |
|
18 | 19 | #[repr(C)] |
|
19 | 20 | struct DocketHeader { |
|
20 | 21 | uid_size: u8, |
|
21 | 22 | _tip_rev: unaligned::U64Be, |
|
22 | 23 | data_length: unaligned::U64Be, |
|
23 | 24 | _data_unused: unaligned::U64Be, |
|
24 | 25 | tip_node_size: unaligned::U64Be, |
|
25 | 26 | } |
|
26 | 27 | |
|
27 | 28 | impl NodeMapDocket { |
|
28 | 29 | /// Return `Ok(None)` when the caller should proceed without a persistent |
|
29 | 30 | /// nodemap: |
|
30 | 31 | /// |
|
31 | 32 | /// * This revlog does not have a `.n` docket file (it is not generated for |
|
32 | 33 | /// small revlogs), or |
|
33 | 34 | /// * The docket has an unsupported version number (repositories created by |
|
34 | 35 | /// later hg, maybe that should be a requirement instead?), or |
|
35 | 36 | /// * The docket file points to a missing (likely deleted) data file (this |
|
36 | 37 | /// can happen in a rare race condition). |
|
37 | 38 | pub fn read_from_file( |
|
38 | 39 | repo: &Repo, |
|
39 | 40 | index_path: &Path, |
|
40 | 41 | ) -> Result<Option<(Self, Mmap)>, RevlogError> { |
|
42 | if !repo | |
|
43 | .requirements() | |
|
44 | .contains(requirements::NODEMAP_REQUIREMENT) | |
|
45 | { | |
|
46 | // If .hg/requires does not opt it, don’t try to open a nodemap | |
|
47 | return Ok(None); | |
|
48 | } | |
|
49 | ||
|
41 | 50 | let docket_path = index_path.with_extension("n"); |
|
42 | 51 | let docket_bytes = if let Some(bytes) = |
|
43 | 52 | repo.store_vfs().read(&docket_path).io_not_found_as_none()? |
|
44 | 53 | { |
|
45 | 54 | bytes |
|
46 | 55 | } else { |
|
47 | 56 | return Ok(None); |
|
48 | 57 | }; |
|
49 | 58 | |
|
50 | 59 | let input = if let Some((&ONDISK_VERSION, rest)) = |
|
51 | 60 | docket_bytes.split_first() |
|
52 | 61 | { |
|
53 | 62 | rest |
|
54 | 63 | } else { |
|
55 | 64 | return Ok(None); |
|
56 | 65 | }; |
|
57 | 66 | |
|
58 | 67 | /// Treat any error as a parse error |
|
59 | 68 | fn parse<T, E>(result: Result<T, E>) -> Result<T, RevlogError> { |
|
60 | 69 | result.map_err(|_| { |
|
61 | 70 | HgError::corrupted("nodemap docket parse error").into() |
|
62 | 71 | }) |
|
63 | 72 | } |
|
64 | 73 | |
|
65 | 74 | let (header, rest) = parse(DocketHeader::from_bytes(input))?; |
|
66 | 75 | let uid_size = header.uid_size as usize; |
|
67 | 76 | // TODO: do we care about overflow for 4 GB+ nodemap files on 32-bit |
|
68 | 77 | // systems? |
|
69 | 78 | let tip_node_size = header.tip_node_size.get() as usize; |
|
70 | 79 | let data_length = header.data_length.get() as usize; |
|
71 | 80 | let (uid, rest) = parse(u8::slice_from_bytes(rest, uid_size))?; |
|
72 | 81 | let (_tip_node, _rest) = |
|
73 | 82 | parse(u8::slice_from_bytes(rest, tip_node_size))?; |
|
74 | 83 | let uid = parse(std::str::from_utf8(uid))?; |
|
75 | 84 | let docket = NodeMapDocket { data_length }; |
|
76 | 85 | |
|
77 | 86 | let data_path = rawdata_path(&docket_path, uid); |
|
78 | 87 | // TODO: use `vfs.read()` here when the `persistent-nodemap.mmap` |
|
79 | 88 | // config is false? |
|
80 | 89 | if let Some(mmap) = repo |
|
81 | 90 | .store_vfs() |
|
82 | 91 | .mmap_open(&data_path) |
|
83 | 92 | .io_not_found_as_none()? |
|
84 | 93 | { |
|
85 | 94 | if mmap.len() >= data_length { |
|
86 | 95 | Ok(Some((docket, mmap))) |
|
87 | 96 | } else { |
|
88 | 97 | Err(HgError::corrupted("persistent nodemap too short").into()) |
|
89 | 98 | } |
|
90 | 99 | } else { |
|
100 | // Even if .hg/requires opted in, some revlogs are deemed small | |
|
101 | // enough to not need a persistent nodemap. | |
|
91 | 102 | Ok(None) |
|
92 | 103 | } |
|
93 | 104 | } |
|
94 | 105 | } |
|
95 | 106 | |
|
96 | 107 | fn rawdata_path(docket_path: &Path, uid: &str) -> PathBuf { |
|
97 | 108 | let docket_name = docket_path |
|
98 | 109 | .file_name() |
|
99 | 110 | .expect("expected a base name") |
|
100 | 111 | .to_str() |
|
101 | 112 | .expect("expected an ASCII file name in the store"); |
|
102 | 113 | let prefix = strip_suffix(docket_name, ".n.a") |
|
103 | 114 | .or_else(|| strip_suffix(docket_name, ".n")) |
|
104 | 115 | .expect("expected docket path in .n or .n.a"); |
|
105 | 116 | let name = format!("{}-{}.nd", prefix, uid); |
|
106 | 117 | docket_path |
|
107 | 118 | .parent() |
|
108 | 119 | .expect("expected a non-root path") |
|
109 | 120 | .join(name) |
|
110 | 121 | } |
General Comments 0
You need to be logged in to leave comments.
Login now