##// END OF EJS Templates
rhg: Don’t attempt to read persistent nodemap without .hg/requires opt-in...
Simon Sapin -
r47375:842f2372 default
parent child Browse files
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