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