##// END OF EJS Templates
rust: fix darwin build
Dan Villiom Podlaski Christiansen -
r53207:33d8cb64 default
parent child Browse files
Show More
@@ -1,421 +1,421
1 //! Helpers for the revlog config and opening options
1 //! Helpers for the revlog config and opening options
2
2
3 use std::collections::HashSet;
3 use std::collections::HashSet;
4
4
5 use crate::{
5 use crate::{
6 config::{Config, ResourceProfileValue},
6 config::{Config, ResourceProfileValue},
7 errors::HgError,
7 errors::HgError,
8 requirements::{
8 requirements::{
9 CHANGELOGV2_REQUIREMENT, GENERALDELTA_REQUIREMENT, NARROW_REQUIREMENT,
9 CHANGELOGV2_REQUIREMENT, GENERALDELTA_REQUIREMENT, NARROW_REQUIREMENT,
10 NODEMAP_REQUIREMENT, REVLOGV1_REQUIREMENT, REVLOGV2_REQUIREMENT,
10 NODEMAP_REQUIREMENT, REVLOGV1_REQUIREMENT, REVLOGV2_REQUIREMENT,
11 SPARSEREVLOG_REQUIREMENT,
11 SPARSEREVLOG_REQUIREMENT,
12 },
12 },
13 };
13 };
14
14
15 use super::{compression::CompressionConfig, RevlogType};
15 use super::{compression::CompressionConfig, RevlogType};
16
16
17 const DEFAULT_CHUNK_CACHE_SIZE: u64 = 65536;
17 const DEFAULT_CHUNK_CACHE_SIZE: u64 = 65536;
18 const DEFAULT_SPARSE_READ_DENSITY_THRESHOLD: f64 = 0.50;
18 const DEFAULT_SPARSE_READ_DENSITY_THRESHOLD: f64 = 0.50;
19 const DEFAULT_SPARSE_READ_MIN_GAP_SIZE: u64 = 262144;
19 const DEFAULT_SPARSE_READ_MIN_GAP_SIZE: u64 = 262144;
20
20
21 /// The known revlog versions and their options
21 /// The known revlog versions and their options
22 #[derive(Debug, Copy, Clone, PartialEq)]
22 #[derive(Debug, Copy, Clone, PartialEq)]
23 pub enum RevlogVersionOptions {
23 pub enum RevlogVersionOptions {
24 V0,
24 V0,
25 V1 { general_delta: bool, inline: bool },
25 V1 { general_delta: bool, inline: bool },
26 V2,
26 V2,
27 ChangelogV2 { compute_rank: bool },
27 ChangelogV2 { compute_rank: bool },
28 }
28 }
29
29
30 /// Options to govern how a revlog should be opened, usually from the
30 /// Options to govern how a revlog should be opened, usually from the
31 /// repository configuration or requirements.
31 /// repository configuration or requirements.
32 #[derive(Debug, Copy, Clone)]
32 #[derive(Debug, Copy, Clone)]
33 pub struct RevlogOpenOptions {
33 pub struct RevlogOpenOptions {
34 /// The revlog version, along with any option specific to this version
34 /// The revlog version, along with any option specific to this version
35 pub version: RevlogVersionOptions,
35 pub version: RevlogVersionOptions,
36 /// Whether the revlog uses a persistent nodemap.
36 /// Whether the revlog uses a persistent nodemap.
37 pub use_nodemap: bool,
37 pub use_nodemap: bool,
38 pub delta_config: RevlogDeltaConfig,
38 pub delta_config: RevlogDeltaConfig,
39 pub data_config: RevlogDataConfig,
39 pub data_config: RevlogDataConfig,
40 pub feature_config: RevlogFeatureConfig,
40 pub feature_config: RevlogFeatureConfig,
41 }
41 }
42
42
43 #[cfg(test)]
43 #[cfg(test)]
44 impl Default for RevlogOpenOptions {
44 impl Default for RevlogOpenOptions {
45 fn default() -> Self {
45 fn default() -> Self {
46 Self {
46 Self {
47 version: RevlogVersionOptions::V1 {
47 version: RevlogVersionOptions::V1 {
48 general_delta: true,
48 general_delta: true,
49 inline: false,
49 inline: false,
50 },
50 },
51 use_nodemap: true,
51 use_nodemap: true,
52 data_config: Default::default(),
52 data_config: Default::default(),
53 delta_config: Default::default(),
53 delta_config: Default::default(),
54 feature_config: Default::default(),
54 feature_config: Default::default(),
55 }
55 }
56 }
56 }
57 }
57 }
58
58
59 impl RevlogOpenOptions {
59 impl RevlogOpenOptions {
60 pub fn new(
60 pub fn new(
61 inline: bool,
61 inline: bool,
62 data_config: RevlogDataConfig,
62 data_config: RevlogDataConfig,
63 delta_config: RevlogDeltaConfig,
63 delta_config: RevlogDeltaConfig,
64 feature_config: RevlogFeatureConfig,
64 feature_config: RevlogFeatureConfig,
65 ) -> Self {
65 ) -> Self {
66 Self {
66 Self {
67 version: RevlogVersionOptions::V1 {
67 version: RevlogVersionOptions::V1 {
68 general_delta: data_config.general_delta,
68 general_delta: data_config.general_delta,
69 inline,
69 inline,
70 },
70 },
71 use_nodemap: false,
71 use_nodemap: false,
72 data_config,
72 data_config,
73 delta_config,
73 delta_config,
74 feature_config,
74 feature_config,
75 }
75 }
76 }
76 }
77
77
78 pub fn index_header(&self) -> super::index::IndexHeader {
78 pub fn index_header(&self) -> super::index::IndexHeader {
79 super::index::IndexHeader {
79 super::index::IndexHeader {
80 header_bytes: match self.version {
80 header_bytes: match self.version {
81 RevlogVersionOptions::V0 => [0, 0, 0, 0],
81 RevlogVersionOptions::V0 => [0, 0, 0, 0],
82 RevlogVersionOptions::V1 {
82 RevlogVersionOptions::V1 {
83 general_delta,
83 general_delta,
84 inline,
84 inline,
85 } => [
85 } => [
86 0,
86 0,
87 if general_delta && inline {
87 if general_delta && inline {
88 3
88 3
89 } else if general_delta {
89 } else if general_delta {
90 2
90 2
91 } else {
91 } else {
92 u8::from(inline)
92 u8::from(inline)
93 },
93 },
94 0,
94 0,
95 1,
95 1,
96 ],
96 ],
97 RevlogVersionOptions::V2 => 0xDEADu32.to_be_bytes(),
97 RevlogVersionOptions::V2 => 0xDEADu32.to_be_bytes(),
98 RevlogVersionOptions::ChangelogV2 { compute_rank: _ } => {
98 RevlogVersionOptions::ChangelogV2 { compute_rank: _ } => {
99 0xD34Du32.to_be_bytes()
99 0xD34Du32.to_be_bytes()
100 }
100 }
101 },
101 },
102 }
102 }
103 }
103 }
104 }
104 }
105
105
106 /// Technically only Linux 2.5.46+ has `MAP_POPULATE` and only `2.6.23` on
106 /// Technically only Linux 2.5.46+ has `MAP_POPULATE` and only `2.6.23` on
107 /// private mappings, but if you're using such ancient Linux, you have other
107 /// private mappings, but if you're using such ancient Linux, you have other
108 /// problems.
108 /// problems.
109 #[cfg(target_os = "linux")]
109 #[cfg(target_os = "linux")]
110 const fn can_populate_mmap() -> bool {
110 const fn can_populate_mmap() -> bool {
111 true
111 true
112 }
112 }
113
113
114 /// There is a of populating mmaps for Windows, but it would need testing.
114 /// There is a of populating mmaps for Windows, but it would need testing.
115 #[cfg(not(target_os = "linux"))]
115 #[cfg(not(target_os = "linux"))]
116 const fn can_populate_mmap() {
116 const fn can_populate_mmap() -> bool {
117 false
117 false
118 }
118 }
119
119
120 #[derive(Debug, Clone, Copy, PartialEq)]
120 #[derive(Debug, Clone, Copy, PartialEq)]
121 /// Holds configuration values about how the revlog data is read
121 /// Holds configuration values about how the revlog data is read
122 pub struct RevlogDataConfig {
122 pub struct RevlogDataConfig {
123 /// Should we try to open the "pending" version of the revlog
123 /// Should we try to open the "pending" version of the revlog
124 pub try_pending: bool,
124 pub try_pending: bool,
125 /// Should we try to open the "split" version of the revlog
125 /// Should we try to open the "split" version of the revlog
126 pub try_split: bool,
126 pub try_split: bool,
127 /// When True, `indexfile` should be opened with `checkambig=True` at
127 /// When True, `indexfile` should be opened with `checkambig=True` at
128 /// writing time, to avoid file stat ambiguity
128 /// writing time, to avoid file stat ambiguity
129 pub check_ambig: bool,
129 pub check_ambig: bool,
130 /// If true, use mmap instead of reading to deal with large indexes
130 /// If true, use mmap instead of reading to deal with large indexes
131 pub mmap_large_index: bool,
131 pub mmap_large_index: bool,
132 /// How much data is considered large
132 /// How much data is considered large
133 pub mmap_index_threshold: Option<u64>,
133 pub mmap_index_threshold: Option<u64>,
134 /// How much data to read and cache into the raw revlog data cache
134 /// How much data to read and cache into the raw revlog data cache
135 pub chunk_cache_size: u64,
135 pub chunk_cache_size: u64,
136 /// The size of the uncompressed cache compared to the largest revision
136 /// The size of the uncompressed cache compared to the largest revision
137 /// seen
137 /// seen
138 pub uncompressed_cache_factor: Option<f64>,
138 pub uncompressed_cache_factor: Option<f64>,
139 /// The number of chunks cached
139 /// The number of chunks cached
140 pub uncompressed_cache_count: Option<u64>,
140 pub uncompressed_cache_count: Option<u64>,
141 /// Allow sparse reading of the revlog data
141 /// Allow sparse reading of the revlog data
142 pub with_sparse_read: bool,
142 pub with_sparse_read: bool,
143 /// Minimal density of a sparse read chunk
143 /// Minimal density of a sparse read chunk
144 pub sr_density_threshold: f64,
144 pub sr_density_threshold: f64,
145 /// Minimal size of the data we skip when performing sparse reads
145 /// Minimal size of the data we skip when performing sparse reads
146 pub sr_min_gap_size: u64,
146 pub sr_min_gap_size: u64,
147 /// Whether deltas are encoded against arbitrary bases
147 /// Whether deltas are encoded against arbitrary bases
148 pub general_delta: bool,
148 pub general_delta: bool,
149 }
149 }
150
150
151 impl RevlogDataConfig {
151 impl RevlogDataConfig {
152 pub fn new(
152 pub fn new(
153 config: &Config,
153 config: &Config,
154 requirements: &HashSet<String>,
154 requirements: &HashSet<String>,
155 ) -> Result<Self, HgError> {
155 ) -> Result<Self, HgError> {
156 let mut data_config = Self::default();
156 let mut data_config = Self::default();
157 if let Some(chunk_cache_size) =
157 if let Some(chunk_cache_size) =
158 config.get_byte_size(b"format", b"chunkcachesize")?
158 config.get_byte_size(b"format", b"chunkcachesize")?
159 {
159 {
160 data_config.chunk_cache_size = chunk_cache_size;
160 data_config.chunk_cache_size = chunk_cache_size;
161 }
161 }
162
162
163 let memory_profile = config.get_resource_profile(Some("memory"));
163 let memory_profile = config.get_resource_profile(Some("memory"));
164 if memory_profile.value >= ResourceProfileValue::Medium {
164 if memory_profile.value >= ResourceProfileValue::Medium {
165 data_config.uncompressed_cache_count = Some(10_000);
165 data_config.uncompressed_cache_count = Some(10_000);
166 data_config.uncompressed_cache_factor = Some(4.0);
166 data_config.uncompressed_cache_factor = Some(4.0);
167 if memory_profile.value >= ResourceProfileValue::High {
167 if memory_profile.value >= ResourceProfileValue::High {
168 data_config.uncompressed_cache_factor = Some(10.0)
168 data_config.uncompressed_cache_factor = Some(10.0)
169 }
169 }
170 }
170 }
171
171
172 // Use mmap if requested, or by default if we can fully populate it
172 // Use mmap if requested, or by default if we can fully populate it
173 let mmap_index = config
173 let mmap_index = config
174 .get_option_no_default(b"storage", b"revlog.mmap.index")?
174 .get_option_no_default(b"storage", b"revlog.mmap.index")?
175 .unwrap_or(can_populate_mmap());
175 .unwrap_or(can_populate_mmap());
176 if mmap_index {
176 if mmap_index {
177 if let Some(mmap_index_threshold) = config.get_byte_size(
177 if let Some(mmap_index_threshold) = config.get_byte_size(
178 b"storage",
178 b"storage",
179 b"revlog.mmap.index:size-threshold",
179 b"revlog.mmap.index:size-threshold",
180 )? {
180 )? {
181 // Only mmap if above the requested size threshold
181 // Only mmap if above the requested size threshold
182 data_config.mmap_index_threshold = Some(mmap_index_threshold);
182 data_config.mmap_index_threshold = Some(mmap_index_threshold);
183 }
183 }
184 }
184 }
185
185
186 if let Some(mmap_index_threshold) = config
186 if let Some(mmap_index_threshold) = config
187 .get_byte_size(b"storage", b"revlog.mmap.index:size-threshold")?
187 .get_byte_size(b"storage", b"revlog.mmap.index:size-threshold")?
188 {
188 {
189 data_config.mmap_index_threshold = Some(mmap_index_threshold);
189 data_config.mmap_index_threshold = Some(mmap_index_threshold);
190 }
190 }
191
191
192 let with_sparse_read =
192 let with_sparse_read =
193 config.get_bool(b"experimental", b"sparse-read")?;
193 config.get_bool(b"experimental", b"sparse-read")?;
194 if let Some(sr_density_threshold) = config
194 if let Some(sr_density_threshold) = config
195 .get_f64(b"experimental", b"sparse-read.density-threshold")?
195 .get_f64(b"experimental", b"sparse-read.density-threshold")?
196 {
196 {
197 data_config.sr_density_threshold = sr_density_threshold;
197 data_config.sr_density_threshold = sr_density_threshold;
198 }
198 }
199 data_config.with_sparse_read = with_sparse_read;
199 data_config.with_sparse_read = with_sparse_read;
200 if let Some(sr_min_gap_size) = config
200 if let Some(sr_min_gap_size) = config
201 .get_byte_size(b"experimental", b"sparse-read.min-gap-size")?
201 .get_byte_size(b"experimental", b"sparse-read.min-gap-size")?
202 {
202 {
203 data_config.sr_min_gap_size = sr_min_gap_size;
203 data_config.sr_min_gap_size = sr_min_gap_size;
204 }
204 }
205
205
206 data_config.with_sparse_read =
206 data_config.with_sparse_read =
207 requirements.contains(SPARSEREVLOG_REQUIREMENT);
207 requirements.contains(SPARSEREVLOG_REQUIREMENT);
208
208
209 Ok(data_config)
209 Ok(data_config)
210 }
210 }
211 }
211 }
212
212
213 impl Default for RevlogDataConfig {
213 impl Default for RevlogDataConfig {
214 fn default() -> Self {
214 fn default() -> Self {
215 Self {
215 Self {
216 chunk_cache_size: DEFAULT_CHUNK_CACHE_SIZE,
216 chunk_cache_size: DEFAULT_CHUNK_CACHE_SIZE,
217 sr_density_threshold: DEFAULT_SPARSE_READ_DENSITY_THRESHOLD,
217 sr_density_threshold: DEFAULT_SPARSE_READ_DENSITY_THRESHOLD,
218 sr_min_gap_size: DEFAULT_SPARSE_READ_MIN_GAP_SIZE,
218 sr_min_gap_size: DEFAULT_SPARSE_READ_MIN_GAP_SIZE,
219 try_pending: Default::default(),
219 try_pending: Default::default(),
220 try_split: Default::default(),
220 try_split: Default::default(),
221 check_ambig: Default::default(),
221 check_ambig: Default::default(),
222 mmap_large_index: Default::default(),
222 mmap_large_index: Default::default(),
223 mmap_index_threshold: Default::default(),
223 mmap_index_threshold: Default::default(),
224 uncompressed_cache_factor: Default::default(),
224 uncompressed_cache_factor: Default::default(),
225 uncompressed_cache_count: Default::default(),
225 uncompressed_cache_count: Default::default(),
226 with_sparse_read: Default::default(),
226 with_sparse_read: Default::default(),
227 general_delta: Default::default(),
227 general_delta: Default::default(),
228 }
228 }
229 }
229 }
230 }
230 }
231
231
232 #[derive(Debug, Clone, Copy, PartialEq)]
232 #[derive(Debug, Clone, Copy, PartialEq)]
233 /// Holds configuration values about how new deltas are computed.
233 /// Holds configuration values about how new deltas are computed.
234 ///
234 ///
235 /// Some attributes are duplicated from [`RevlogDataConfig`] to help having
235 /// Some attributes are duplicated from [`RevlogDataConfig`] to help having
236 /// each object self contained.
236 /// each object self contained.
237 pub struct RevlogDeltaConfig {
237 pub struct RevlogDeltaConfig {
238 /// Whether deltas can be encoded against arbitrary bases
238 /// Whether deltas can be encoded against arbitrary bases
239 pub general_delta: bool,
239 pub general_delta: bool,
240 /// Allow sparse writing of the revlog data
240 /// Allow sparse writing of the revlog data
241 pub sparse_revlog: bool,
241 pub sparse_revlog: bool,
242 /// Maximum length of a delta chain
242 /// Maximum length of a delta chain
243 pub max_chain_len: Option<u64>,
243 pub max_chain_len: Option<u64>,
244 /// Maximum distance between a delta chain's start and end
244 /// Maximum distance between a delta chain's start and end
245 pub max_deltachain_span: Option<u64>,
245 pub max_deltachain_span: Option<u64>,
246 /// If `upper_bound_comp` is not None, this is the expected maximal
246 /// If `upper_bound_comp` is not None, this is the expected maximal
247 /// gain from compression for the data content
247 /// gain from compression for the data content
248 pub upper_bound_comp: Option<f64>,
248 pub upper_bound_comp: Option<f64>,
249 /// Should we try a delta against both parents
249 /// Should we try a delta against both parents
250 pub delta_both_parents: bool,
250 pub delta_both_parents: bool,
251 /// Test delta base candidate groups by chunks of this maximal size
251 /// Test delta base candidate groups by chunks of this maximal size
252 pub candidate_group_chunk_size: u64,
252 pub candidate_group_chunk_size: u64,
253 /// Should we display debug information about delta computation
253 /// Should we display debug information about delta computation
254 pub debug_delta: bool,
254 pub debug_delta: bool,
255 /// Trust incoming deltas by default
255 /// Trust incoming deltas by default
256 pub lazy_delta: bool,
256 pub lazy_delta: bool,
257 /// Trust the base of incoming deltas by default
257 /// Trust the base of incoming deltas by default
258 pub lazy_delta_base: bool,
258 pub lazy_delta_base: bool,
259 }
259 }
260
260
261 impl RevlogDeltaConfig {
261 impl RevlogDeltaConfig {
262 pub fn new(
262 pub fn new(
263 config: &Config,
263 config: &Config,
264 requirements: &HashSet<String>,
264 requirements: &HashSet<String>,
265 revlog_type: RevlogType,
265 revlog_type: RevlogType,
266 ) -> Result<Self, HgError> {
266 ) -> Result<Self, HgError> {
267 let mut delta_config = Self {
267 let mut delta_config = Self {
268 delta_both_parents: config
268 delta_both_parents: config
269 .get_option_no_default(
269 .get_option_no_default(
270 b"storage",
270 b"storage",
271 b"revlog.optimize-delta-parent-choice",
271 b"revlog.optimize-delta-parent-choice",
272 )?
272 )?
273 .unwrap_or(true),
273 .unwrap_or(true),
274 candidate_group_chunk_size: config
274 candidate_group_chunk_size: config
275 .get_u64(
275 .get_u64(
276 b"storage",
276 b"storage",
277 b"revlog.delta-parent-search.candidate-group-chunk-size",
277 b"revlog.delta-parent-search.candidate-group-chunk-size",
278 )?
278 )?
279 .unwrap_or_default(),
279 .unwrap_or_default(),
280 ..Default::default()
280 ..Default::default()
281 };
281 };
282
282
283 delta_config.debug_delta =
283 delta_config.debug_delta =
284 config.get_bool(b"debug", b"revlog.debug-delta")?;
284 config.get_bool(b"debug", b"revlog.debug-delta")?;
285
285
286 delta_config.general_delta =
286 delta_config.general_delta =
287 requirements.contains(GENERALDELTA_REQUIREMENT);
287 requirements.contains(GENERALDELTA_REQUIREMENT);
288
288
289 let lazy_delta =
289 let lazy_delta =
290 config.get_bool(b"storage", b"revlog.reuse-external-delta")?;
290 config.get_bool(b"storage", b"revlog.reuse-external-delta")?;
291
291
292 if revlog_type == RevlogType::Manifestlog {
292 if revlog_type == RevlogType::Manifestlog {
293 // upper bound of what we expect from compression
293 // upper bound of what we expect from compression
294 // (real life value seems to be 3)
294 // (real life value seems to be 3)
295 delta_config.upper_bound_comp = Some(3.0)
295 delta_config.upper_bound_comp = Some(3.0)
296 }
296 }
297
297
298 let mut lazy_delta_base = false;
298 let mut lazy_delta_base = false;
299 if lazy_delta {
299 if lazy_delta {
300 lazy_delta_base = match config.get_option_no_default(
300 lazy_delta_base = match config.get_option_no_default(
301 b"storage",
301 b"storage",
302 b"revlog.reuse-external-delta-parent",
302 b"revlog.reuse-external-delta-parent",
303 )? {
303 )? {
304 Some(base) => base,
304 Some(base) => base,
305 None => config.get_bool(b"format", b"generaldelta")?,
305 None => config.get_bool(b"format", b"generaldelta")?,
306 };
306 };
307 }
307 }
308 delta_config.lazy_delta = lazy_delta;
308 delta_config.lazy_delta = lazy_delta;
309 delta_config.lazy_delta_base = lazy_delta_base;
309 delta_config.lazy_delta_base = lazy_delta_base;
310
310
311 delta_config.max_deltachain_span =
311 delta_config.max_deltachain_span =
312 match config.get_i64(b"experimental", b"maxdeltachainspan")? {
312 match config.get_i64(b"experimental", b"maxdeltachainspan")? {
313 Some(span) => {
313 Some(span) => {
314 if span < 0 {
314 if span < 0 {
315 None
315 None
316 } else {
316 } else {
317 Some(span as u64)
317 Some(span as u64)
318 }
318 }
319 }
319 }
320 None => None,
320 None => None,
321 };
321 };
322
322
323 delta_config.sparse_revlog =
323 delta_config.sparse_revlog =
324 requirements.contains(SPARSEREVLOG_REQUIREMENT);
324 requirements.contains(SPARSEREVLOG_REQUIREMENT);
325
325
326 delta_config.max_chain_len =
326 delta_config.max_chain_len =
327 config.get_byte_size_no_default(b"format", b"maxchainlen")?;
327 config.get_byte_size_no_default(b"format", b"maxchainlen")?;
328
328
329 Ok(delta_config)
329 Ok(delta_config)
330 }
330 }
331 }
331 }
332
332
333 impl Default for RevlogDeltaConfig {
333 impl Default for RevlogDeltaConfig {
334 fn default() -> Self {
334 fn default() -> Self {
335 Self {
335 Self {
336 delta_both_parents: true,
336 delta_both_parents: true,
337 lazy_delta: true,
337 lazy_delta: true,
338 general_delta: Default::default(),
338 general_delta: Default::default(),
339 sparse_revlog: Default::default(),
339 sparse_revlog: Default::default(),
340 max_chain_len: Default::default(),
340 max_chain_len: Default::default(),
341 max_deltachain_span: Default::default(),
341 max_deltachain_span: Default::default(),
342 upper_bound_comp: Default::default(),
342 upper_bound_comp: Default::default(),
343 candidate_group_chunk_size: Default::default(),
343 candidate_group_chunk_size: Default::default(),
344 debug_delta: Default::default(),
344 debug_delta: Default::default(),
345 lazy_delta_base: Default::default(),
345 lazy_delta_base: Default::default(),
346 }
346 }
347 }
347 }
348 }
348 }
349
349
350 #[derive(Debug, Default, Clone, Copy, PartialEq)]
350 #[derive(Debug, Default, Clone, Copy, PartialEq)]
351 /// Holds configuration values about the available revlog features
351 /// Holds configuration values about the available revlog features
352 pub struct RevlogFeatureConfig {
352 pub struct RevlogFeatureConfig {
353 /// The compression engine and its options
353 /// The compression engine and its options
354 pub compression_engine: CompressionConfig,
354 pub compression_engine: CompressionConfig,
355 /// Can we use censor on this revlog
355 /// Can we use censor on this revlog
356 pub censorable: bool,
356 pub censorable: bool,
357 /// Does this revlog use the "side data" feature
357 /// Does this revlog use the "side data" feature
358 pub has_side_data: bool,
358 pub has_side_data: bool,
359 /// Might remove this configuration once the rank computation has no
359 /// Might remove this configuration once the rank computation has no
360 /// impact
360 /// impact
361 pub compute_rank: bool,
361 pub compute_rank: bool,
362 /// Parent order is supposed to be semantically irrelevant, so we
362 /// Parent order is supposed to be semantically irrelevant, so we
363 /// normally re-sort parents to ensure that the first parent is non-null,
363 /// normally re-sort parents to ensure that the first parent is non-null,
364 /// if there is a non-null parent at all.
364 /// if there is a non-null parent at all.
365 /// filelog abuses the parent order as a flag to mark some instances of
365 /// filelog abuses the parent order as a flag to mark some instances of
366 /// meta-encoded files, so allow it to disable this behavior.
366 /// meta-encoded files, so allow it to disable this behavior.
367 pub canonical_parent_order: bool,
367 pub canonical_parent_order: bool,
368 /// Can ellipsis commit be used
368 /// Can ellipsis commit be used
369 pub enable_ellipsis: bool,
369 pub enable_ellipsis: bool,
370 }
370 }
371
371
372 impl RevlogFeatureConfig {
372 impl RevlogFeatureConfig {
373 pub fn new(
373 pub fn new(
374 config: &Config,
374 config: &Config,
375 requirements: &HashSet<String>,
375 requirements: &HashSet<String>,
376 ) -> Result<Self, HgError> {
376 ) -> Result<Self, HgError> {
377 Ok(Self {
377 Ok(Self {
378 compression_engine: CompressionConfig::new(config, requirements)?,
378 compression_engine: CompressionConfig::new(config, requirements)?,
379 enable_ellipsis: requirements.contains(NARROW_REQUIREMENT),
379 enable_ellipsis: requirements.contains(NARROW_REQUIREMENT),
380 ..Default::default()
380 ..Default::default()
381 })
381 })
382 }
382 }
383 }
383 }
384
384
385 /// Return the default options for a revlog of `revlog_type` according to the
385 /// Return the default options for a revlog of `revlog_type` according to the
386 /// current config and requirements.
386 /// current config and requirements.
387 pub fn default_revlog_options(
387 pub fn default_revlog_options(
388 config: &Config,
388 config: &Config,
389 requirements: &HashSet<String>,
389 requirements: &HashSet<String>,
390 revlog_type: RevlogType,
390 revlog_type: RevlogType,
391 ) -> Result<RevlogOpenOptions, HgError> {
391 ) -> Result<RevlogOpenOptions, HgError> {
392 let is_changelog = revlog_type == RevlogType::Changelog;
392 let is_changelog = revlog_type == RevlogType::Changelog;
393 let version =
393 let version =
394 if is_changelog && requirements.contains(CHANGELOGV2_REQUIREMENT) {
394 if is_changelog && requirements.contains(CHANGELOGV2_REQUIREMENT) {
395 let compute_rank = config
395 let compute_rank = config
396 .get_bool(b"experimental", b"changelog-v2.compute-rank")?;
396 .get_bool(b"experimental", b"changelog-v2.compute-rank")?;
397 RevlogVersionOptions::ChangelogV2 { compute_rank }
397 RevlogVersionOptions::ChangelogV2 { compute_rank }
398 } else if requirements.contains(REVLOGV2_REQUIREMENT) {
398 } else if requirements.contains(REVLOGV2_REQUIREMENT) {
399 RevlogVersionOptions::V2
399 RevlogVersionOptions::V2
400 } else if requirements.contains(REVLOGV1_REQUIREMENT) {
400 } else if requirements.contains(REVLOGV1_REQUIREMENT) {
401 RevlogVersionOptions::V1 {
401 RevlogVersionOptions::V1 {
402 general_delta: requirements.contains(GENERALDELTA_REQUIREMENT),
402 general_delta: requirements.contains(GENERALDELTA_REQUIREMENT),
403 inline: !is_changelog,
403 inline: !is_changelog,
404 }
404 }
405 } else {
405 } else {
406 RevlogVersionOptions::V0
406 RevlogVersionOptions::V0
407 };
407 };
408 Ok(RevlogOpenOptions {
408 Ok(RevlogOpenOptions {
409 version,
409 version,
410 // We don't need to dance around the slow path like in the Python
410 // We don't need to dance around the slow path like in the Python
411 // implementation since we know we have access to the fast code.
411 // implementation since we know we have access to the fast code.
412 use_nodemap: requirements.contains(NODEMAP_REQUIREMENT),
412 use_nodemap: requirements.contains(NODEMAP_REQUIREMENT),
413 delta_config: RevlogDeltaConfig::new(
413 delta_config: RevlogDeltaConfig::new(
414 config,
414 config,
415 requirements,
415 requirements,
416 revlog_type,
416 revlog_type,
417 )?,
417 )?,
418 data_config: RevlogDataConfig::new(config, requirements)?,
418 data_config: RevlogDataConfig::new(config, requirements)?,
419 feature_config: RevlogFeatureConfig::new(config, requirements)?,
419 feature_config: RevlogFeatureConfig::new(config, requirements)?,
420 })
420 })
421 }
421 }
@@ -1,1130 +1,1131
1 use crate::errors::{HgError, HgResultExt, IoErrorContext, IoResultExt};
1 use crate::errors::{HgError, HgResultExt, IoErrorContext, IoResultExt};
2 use crate::exit_codes;
2 use crate::exit_codes;
3 use crate::fncache::FnCache;
3 use crate::fncache::FnCache;
4 use crate::revlog::path_encode::path_encode;
4 use crate::revlog::path_encode::path_encode;
5 use crate::utils::files::{get_bytes_from_path, get_path_from_bytes};
5 use crate::utils::files::{get_bytes_from_path, get_path_from_bytes};
6 use dyn_clone::DynClone;
6 use dyn_clone::DynClone;
7 use format_bytes::format_bytes;
7 use format_bytes::format_bytes;
8 use memmap2::{Mmap, MmapOptions};
8 use memmap2::{Mmap, MmapOptions};
9 use rand::distributions::{Alphanumeric, DistString};
9 use rand::distributions::{Alphanumeric, DistString};
10 use std::fs::{File, Metadata, OpenOptions};
10 use std::fs::{File, Metadata, OpenOptions};
11 use std::io::{ErrorKind, Read, Seek, Write};
11 use std::io::{ErrorKind, Read, Seek, Write};
12 use std::os::fd::AsRawFd;
12 use std::os::fd::AsRawFd;
13 use std::os::unix::fs::{MetadataExt, PermissionsExt};
13 use std::os::unix::fs::{MetadataExt, PermissionsExt};
14 use std::path::{Path, PathBuf};
14 use std::path::{Path, PathBuf};
15 #[cfg(test)]
15 #[cfg(test)]
16 use std::sync::atomic::AtomicUsize;
16 use std::sync::atomic::AtomicUsize;
17 #[cfg(test)]
17 #[cfg(test)]
18 use std::sync::atomic::Ordering;
18 use std::sync::atomic::Ordering;
19 use std::sync::OnceLock;
19 use std::sync::OnceLock;
20
20
21 /// Filesystem access abstraction for the contents of a given "base" diretory
21 /// Filesystem access abstraction for the contents of a given "base" diretory
22 #[derive(Clone)]
22 #[derive(Clone)]
23 pub struct VfsImpl {
23 pub struct VfsImpl {
24 pub(crate) base: PathBuf,
24 pub(crate) base: PathBuf,
25 pub readonly: bool,
25 pub readonly: bool,
26 pub mode: Option<u32>,
26 pub mode: Option<u32>,
27 }
27 }
28
28
29 struct FileNotFound(std::io::Error, PathBuf);
29 struct FileNotFound(std::io::Error, PathBuf);
30
30
31 /// Store the umask for the whole process since it's expensive to get.
31 /// Store the umask for the whole process since it's expensive to get.
32 static UMASK: OnceLock<u32> = OnceLock::new();
32 static UMASK: OnceLock<u32> = OnceLock::new();
33
33
34 fn get_umask() -> u32 {
34 fn get_umask() -> u32 {
35 *UMASK.get_or_init(|| unsafe {
35 *UMASK.get_or_init(|| unsafe {
36 // TODO is there any way of getting the umask without temporarily
36 // TODO is there any way of getting the umask without temporarily
37 // setting it? Doesn't this affect all threads in this tiny window?
37 // setting it? Doesn't this affect all threads in this tiny window?
38 let mask = libc::umask(0);
38 let mask = libc::umask(0);
39 libc::umask(mask);
39 libc::umask(mask);
40 mask & 0o777
40 #[allow(clippy::useless_conversion)]
41 (mask & 0o777).into()
41 })
42 })
42 }
43 }
43
44
44 /// Return the (unix) mode with which we will create/fix files
45 /// Return the (unix) mode with which we will create/fix files
45 fn get_mode(base: impl AsRef<Path>) -> Option<u32> {
46 fn get_mode(base: impl AsRef<Path>) -> Option<u32> {
46 match base.as_ref().metadata() {
47 match base.as_ref().metadata() {
47 Ok(meta) => {
48 Ok(meta) => {
48 // files in .hg/ will be created using this mode
49 // files in .hg/ will be created using this mode
49 let mode = meta.mode();
50 let mode = meta.mode();
50 // avoid some useless chmods
51 // avoid some useless chmods
51 if (0o777 & !get_umask()) == (0o777 & mode) {
52 if (0o777 & !get_umask()) == (0o777 & mode) {
52 None
53 None
53 } else {
54 } else {
54 Some(mode)
55 Some(mode)
55 }
56 }
56 }
57 }
57 Err(_) => None,
58 Err(_) => None,
58 }
59 }
59 }
60 }
60
61
61 impl VfsImpl {
62 impl VfsImpl {
62 pub fn new(base: PathBuf, readonly: bool) -> Self {
63 pub fn new(base: PathBuf, readonly: bool) -> Self {
63 let mode = get_mode(&base);
64 let mode = get_mode(&base);
64 Self {
65 Self {
65 base,
66 base,
66 readonly,
67 readonly,
67 mode,
68 mode,
68 }
69 }
69 }
70 }
70
71
71 // XXX these methods are probably redundant with VFS trait?
72 // XXX these methods are probably redundant with VFS trait?
72
73
73 pub fn join(&self, relative_path: impl AsRef<Path>) -> PathBuf {
74 pub fn join(&self, relative_path: impl AsRef<Path>) -> PathBuf {
74 self.base.join(relative_path)
75 self.base.join(relative_path)
75 }
76 }
76
77
77 pub fn symlink_metadata(
78 pub fn symlink_metadata(
78 &self,
79 &self,
79 relative_path: impl AsRef<Path>,
80 relative_path: impl AsRef<Path>,
80 ) -> Result<std::fs::Metadata, HgError> {
81 ) -> Result<std::fs::Metadata, HgError> {
81 let path = self.join(relative_path);
82 let path = self.join(relative_path);
82 std::fs::symlink_metadata(&path).when_reading_file(&path)
83 std::fs::symlink_metadata(&path).when_reading_file(&path)
83 }
84 }
84
85
85 pub fn read_link(
86 pub fn read_link(
86 &self,
87 &self,
87 relative_path: impl AsRef<Path>,
88 relative_path: impl AsRef<Path>,
88 ) -> Result<PathBuf, HgError> {
89 ) -> Result<PathBuf, HgError> {
89 let path = self.join(relative_path);
90 let path = self.join(relative_path);
90 std::fs::read_link(&path).when_reading_file(&path)
91 std::fs::read_link(&path).when_reading_file(&path)
91 }
92 }
92
93
93 pub fn read(
94 pub fn read(
94 &self,
95 &self,
95 relative_path: impl AsRef<Path>,
96 relative_path: impl AsRef<Path>,
96 ) -> Result<Vec<u8>, HgError> {
97 ) -> Result<Vec<u8>, HgError> {
97 let path = self.join(relative_path);
98 let path = self.join(relative_path);
98 std::fs::read(&path).when_reading_file(&path)
99 std::fs::read(&path).when_reading_file(&path)
99 }
100 }
100
101
101 /// Returns `Ok(None)` if the file does not exist.
102 /// Returns `Ok(None)` if the file does not exist.
102 pub fn try_read(
103 pub fn try_read(
103 &self,
104 &self,
104 relative_path: impl AsRef<Path>,
105 relative_path: impl AsRef<Path>,
105 ) -> Result<Option<Vec<u8>>, HgError> {
106 ) -> Result<Option<Vec<u8>>, HgError> {
106 match self.read(relative_path) {
107 match self.read(relative_path) {
107 Err(e) => match &e {
108 Err(e) => match &e {
108 HgError::IoError { error, .. } => match error.kind() {
109 HgError::IoError { error, .. } => match error.kind() {
109 ErrorKind::NotFound => Ok(None),
110 ErrorKind::NotFound => Ok(None),
110 _ => Err(e),
111 _ => Err(e),
111 },
112 },
112 _ => Err(e),
113 _ => Err(e),
113 },
114 },
114 Ok(v) => Ok(Some(v)),
115 Ok(v) => Ok(Some(v)),
115 }
116 }
116 }
117 }
117
118
118 fn mmap_open_gen(
119 fn mmap_open_gen(
119 &self,
120 &self,
120 relative_path: impl AsRef<Path>,
121 relative_path: impl AsRef<Path>,
121 ) -> Result<Result<Mmap, FileNotFound>, HgError> {
122 ) -> Result<Result<Mmap, FileNotFound>, HgError> {
122 let path = self.join(relative_path);
123 let path = self.join(relative_path);
123 let file = match std::fs::File::open(&path) {
124 let file = match std::fs::File::open(&path) {
124 Err(err) => {
125 Err(err) => {
125 if let ErrorKind::NotFound = err.kind() {
126 if let ErrorKind::NotFound = err.kind() {
126 return Ok(Err(FileNotFound(err, path)));
127 return Ok(Err(FileNotFound(err, path)));
127 };
128 };
128 return (Err(err)).when_reading_file(&path);
129 return (Err(err)).when_reading_file(&path);
129 }
130 }
130 Ok(file) => file,
131 Ok(file) => file,
131 };
132 };
132 // Safety is "enforced" by locks and assuming other processes are
133 // Safety is "enforced" by locks and assuming other processes are
133 // well-behaved. If any misbehaving or malicious process does touch
134 // well-behaved. If any misbehaving or malicious process does touch
134 // the index, it could lead to corruption. This is inherent
135 // the index, it could lead to corruption. This is inherent
135 // to file-based `mmap`, though some platforms have some ways of
136 // to file-based `mmap`, though some platforms have some ways of
136 // mitigating.
137 // mitigating.
137 // TODO linux: set the immutable flag with `chattr(1)`?
138 // TODO linux: set the immutable flag with `chattr(1)`?
138 let mmap = unsafe { MmapOptions::new().map(&file) }
139 let mmap = unsafe { MmapOptions::new().map(&file) }
139 .when_reading_file(&path)?;
140 .when_reading_file(&path)?;
140 Ok(Ok(mmap))
141 Ok(Ok(mmap))
141 }
142 }
142
143
143 pub fn mmap_open_opt(
144 pub fn mmap_open_opt(
144 &self,
145 &self,
145 relative_path: impl AsRef<Path>,
146 relative_path: impl AsRef<Path>,
146 ) -> Result<Option<Mmap>, HgError> {
147 ) -> Result<Option<Mmap>, HgError> {
147 self.mmap_open_gen(relative_path).map(|res| res.ok())
148 self.mmap_open_gen(relative_path).map(|res| res.ok())
148 }
149 }
149
150
150 pub fn mmap_open(
151 pub fn mmap_open(
151 &self,
152 &self,
152 relative_path: impl AsRef<Path>,
153 relative_path: impl AsRef<Path>,
153 ) -> Result<Mmap, HgError> {
154 ) -> Result<Mmap, HgError> {
154 match self.mmap_open_gen(relative_path)? {
155 match self.mmap_open_gen(relative_path)? {
155 Err(FileNotFound(err, path)) => Err(err).when_reading_file(&path),
156 Err(FileNotFound(err, path)) => Err(err).when_reading_file(&path),
156 Ok(res) => Ok(res),
157 Ok(res) => Ok(res),
157 }
158 }
158 }
159 }
159
160
160 #[cfg(unix)]
161 #[cfg(unix)]
161 pub fn create_symlink(
162 pub fn create_symlink(
162 &self,
163 &self,
163 relative_link_path: impl AsRef<Path>,
164 relative_link_path: impl AsRef<Path>,
164 target_path: impl AsRef<Path>,
165 target_path: impl AsRef<Path>,
165 ) -> Result<(), HgError> {
166 ) -> Result<(), HgError> {
166 let link_path = self.join(relative_link_path);
167 let link_path = self.join(relative_link_path);
167 std::os::unix::fs::symlink(target_path, &link_path)
168 std::os::unix::fs::symlink(target_path, &link_path)
168 .when_writing_file(&link_path)
169 .when_writing_file(&link_path)
169 }
170 }
170
171
171 /// Write `contents` into a temporary file, then rename to `relative_path`.
172 /// Write `contents` into a temporary file, then rename to `relative_path`.
172 /// This makes writing to a file "atomic": a reader opening that path will
173 /// This makes writing to a file "atomic": a reader opening that path will
173 /// see either the previous contents of the file or the complete new
174 /// see either the previous contents of the file or the complete new
174 /// content, never a partial write.
175 /// content, never a partial write.
175 pub fn atomic_write(
176 pub fn atomic_write(
176 &self,
177 &self,
177 relative_path: impl AsRef<Path>,
178 relative_path: impl AsRef<Path>,
178 contents: &[u8],
179 contents: &[u8],
179 ) -> Result<(), HgError> {
180 ) -> Result<(), HgError> {
180 let mut tmp = tempfile::NamedTempFile::new_in(&self.base)
181 let mut tmp = tempfile::NamedTempFile::new_in(&self.base)
181 .when_writing_file(&self.base)?;
182 .when_writing_file(&self.base)?;
182 tmp.write_all(contents)
183 tmp.write_all(contents)
183 .and_then(|()| tmp.flush())
184 .and_then(|()| tmp.flush())
184 .when_writing_file(tmp.path())?;
185 .when_writing_file(tmp.path())?;
185 let path = self.join(relative_path);
186 let path = self.join(relative_path);
186 tmp.persist(&path)
187 tmp.persist(&path)
187 .map_err(|e| e.error)
188 .map_err(|e| e.error)
188 .when_writing_file(&path)?;
189 .when_writing_file(&path)?;
189 Ok(())
190 Ok(())
190 }
191 }
191 }
192 }
192
193
193 fn fs_metadata(
194 fn fs_metadata(
194 path: impl AsRef<Path>,
195 path: impl AsRef<Path>,
195 ) -> Result<Option<std::fs::Metadata>, HgError> {
196 ) -> Result<Option<std::fs::Metadata>, HgError> {
196 let path = path.as_ref();
197 let path = path.as_ref();
197 match path.metadata() {
198 match path.metadata() {
198 Ok(meta) => Ok(Some(meta)),
199 Ok(meta) => Ok(Some(meta)),
199 Err(error) => match error.kind() {
200 Err(error) => match error.kind() {
200 // TODO: when we require a Rust version where `NotADirectory` is
201 // TODO: when we require a Rust version where `NotADirectory` is
201 // stable, invert this logic and return None for it and `NotFound`
202 // stable, invert this logic and return None for it and `NotFound`
202 // and propagate any other error.
203 // and propagate any other error.
203 ErrorKind::PermissionDenied => Err(error).with_context(|| {
204 ErrorKind::PermissionDenied => Err(error).with_context(|| {
204 IoErrorContext::ReadingMetadata(path.to_owned())
205 IoErrorContext::ReadingMetadata(path.to_owned())
205 }),
206 }),
206 _ => Ok(None),
207 _ => Ok(None),
207 },
208 },
208 }
209 }
209 }
210 }
210
211
211 /// Abstraction over the files handled by a [`Vfs`].
212 /// Abstraction over the files handled by a [`Vfs`].
212 #[derive(Debug)]
213 #[derive(Debug)]
213 pub enum VfsFile {
214 pub enum VfsFile {
214 Atomic(AtomicFile),
215 Atomic(AtomicFile),
215
216
216 Normal {
217 Normal {
217 file: File,
218 file: File,
218 path: PathBuf,
219 path: PathBuf,
219 /// If `Some`, check (and maybe fix) this file's timestamp ambiguity.
220 /// If `Some`, check (and maybe fix) this file's timestamp ambiguity.
220 /// See [`is_filetime_ambiguous`].
221 /// See [`is_filetime_ambiguous`].
221 check_ambig: Option<Metadata>,
222 check_ambig: Option<Metadata>,
222 },
223 },
223 }
224 }
224
225
225 impl VfsFile {
226 impl VfsFile {
226 pub fn normal(file: File, path: PathBuf) -> Self {
227 pub fn normal(file: File, path: PathBuf) -> Self {
227 Self::Normal {
228 Self::Normal {
228 file,
229 file,
229 check_ambig: None,
230 check_ambig: None,
230 path,
231 path,
231 }
232 }
232 }
233 }
233 pub fn normal_check_ambig(
234 pub fn normal_check_ambig(
234 file: File,
235 file: File,
235 path: PathBuf,
236 path: PathBuf,
236 ) -> Result<Self, HgError> {
237 ) -> Result<Self, HgError> {
237 Ok(Self::Normal {
238 Ok(Self::Normal {
238 file,
239 file,
239 check_ambig: Some(path.metadata().when_reading_file(&path)?),
240 check_ambig: Some(path.metadata().when_reading_file(&path)?),
240 path,
241 path,
241 })
242 })
242 }
243 }
243 pub fn try_clone(&self) -> Result<VfsFile, HgError> {
244 pub fn try_clone(&self) -> Result<VfsFile, HgError> {
244 Ok(match self {
245 Ok(match self {
245 VfsFile::Atomic(AtomicFile {
246 VfsFile::Atomic(AtomicFile {
246 fp,
247 fp,
247 temp_path,
248 temp_path,
248 check_ambig,
249 check_ambig,
249 target_name,
250 target_name,
250 is_open,
251 is_open,
251 }) => Self::Atomic(AtomicFile {
252 }) => Self::Atomic(AtomicFile {
252 fp: fp.try_clone().when_reading_file(temp_path)?,
253 fp: fp.try_clone().when_reading_file(temp_path)?,
253 temp_path: temp_path.clone(),
254 temp_path: temp_path.clone(),
254 check_ambig: *check_ambig,
255 check_ambig: *check_ambig,
255 target_name: target_name.clone(),
256 target_name: target_name.clone(),
256 is_open: *is_open,
257 is_open: *is_open,
257 }),
258 }),
258 VfsFile::Normal {
259 VfsFile::Normal {
259 file,
260 file,
260 check_ambig,
261 check_ambig,
261 path,
262 path,
262 } => Self::Normal {
263 } => Self::Normal {
263 file: file.try_clone().when_reading_file(path)?,
264 file: file.try_clone().when_reading_file(path)?,
264 check_ambig: check_ambig.clone(),
265 check_ambig: check_ambig.clone(),
265 path: path.to_owned(),
266 path: path.to_owned(),
266 },
267 },
267 })
268 })
268 }
269 }
269 pub fn set_len(&self, len: u64) -> Result<(), std::io::Error> {
270 pub fn set_len(&self, len: u64) -> Result<(), std::io::Error> {
270 match self {
271 match self {
271 VfsFile::Atomic(atomic_file) => atomic_file.fp.set_len(len),
272 VfsFile::Atomic(atomic_file) => atomic_file.fp.set_len(len),
272 VfsFile::Normal { file, .. } => file.set_len(len),
273 VfsFile::Normal { file, .. } => file.set_len(len),
273 }
274 }
274 }
275 }
275
276
276 pub fn metadata(&self) -> Result<std::fs::Metadata, std::io::Error> {
277 pub fn metadata(&self) -> Result<std::fs::Metadata, std::io::Error> {
277 match self {
278 match self {
278 VfsFile::Atomic(atomic_file) => atomic_file.fp.metadata(),
279 VfsFile::Atomic(atomic_file) => atomic_file.fp.metadata(),
279 VfsFile::Normal { file, .. } => file.metadata(),
280 VfsFile::Normal { file, .. } => file.metadata(),
280 }
281 }
281 }
282 }
282 }
283 }
283
284
284 impl AsRawFd for VfsFile {
285 impl AsRawFd for VfsFile {
285 fn as_raw_fd(&self) -> std::os::unix::prelude::RawFd {
286 fn as_raw_fd(&self) -> std::os::unix::prelude::RawFd {
286 match self {
287 match self {
287 VfsFile::Atomic(atomic_file) => atomic_file.fp.as_raw_fd(),
288 VfsFile::Atomic(atomic_file) => atomic_file.fp.as_raw_fd(),
288 VfsFile::Normal { file, .. } => file.as_raw_fd(),
289 VfsFile::Normal { file, .. } => file.as_raw_fd(),
289 }
290 }
290 }
291 }
291 }
292 }
292
293
293 impl Seek for VfsFile {
294 impl Seek for VfsFile {
294 fn seek(&mut self, pos: std::io::SeekFrom) -> std::io::Result<u64> {
295 fn seek(&mut self, pos: std::io::SeekFrom) -> std::io::Result<u64> {
295 match self {
296 match self {
296 VfsFile::Atomic(atomic_file) => atomic_file.seek(pos),
297 VfsFile::Atomic(atomic_file) => atomic_file.seek(pos),
297 VfsFile::Normal { file, .. } => file.seek(pos),
298 VfsFile::Normal { file, .. } => file.seek(pos),
298 }
299 }
299 }
300 }
300 }
301 }
301
302
302 impl Read for VfsFile {
303 impl Read for VfsFile {
303 fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
304 fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
304 match self {
305 match self {
305 VfsFile::Atomic(atomic_file) => atomic_file.fp.read(buf),
306 VfsFile::Atomic(atomic_file) => atomic_file.fp.read(buf),
306 VfsFile::Normal { file, .. } => file.read(buf),
307 VfsFile::Normal { file, .. } => file.read(buf),
307 }
308 }
308 }
309 }
309 }
310 }
310
311
311 impl Write for VfsFile {
312 impl Write for VfsFile {
312 fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
313 fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
313 match self {
314 match self {
314 VfsFile::Atomic(atomic_file) => atomic_file.fp.write(buf),
315 VfsFile::Atomic(atomic_file) => atomic_file.fp.write(buf),
315 VfsFile::Normal { file, .. } => file.write(buf),
316 VfsFile::Normal { file, .. } => file.write(buf),
316 }
317 }
317 }
318 }
318
319
319 fn flush(&mut self) -> std::io::Result<()> {
320 fn flush(&mut self) -> std::io::Result<()> {
320 match self {
321 match self {
321 VfsFile::Atomic(atomic_file) => atomic_file.fp.flush(),
322 VfsFile::Atomic(atomic_file) => atomic_file.fp.flush(),
322 VfsFile::Normal { file, .. } => file.flush(),
323 VfsFile::Normal { file, .. } => file.flush(),
323 }
324 }
324 }
325 }
325 }
326 }
326
327
327 impl Drop for VfsFile {
328 impl Drop for VfsFile {
328 fn drop(&mut self) {
329 fn drop(&mut self) {
329 if let VfsFile::Normal {
330 if let VfsFile::Normal {
330 path,
331 path,
331 check_ambig: Some(old),
332 check_ambig: Some(old),
332 ..
333 ..
333 } = self
334 } = self
334 {
335 {
335 avoid_timestamp_ambiguity(path, old)
336 avoid_timestamp_ambiguity(path, old)
336 }
337 }
337 }
338 }
338 }
339 }
339
340
340 /// Records the number of times we've fixed a timestamp ambiguity, only
341 /// Records the number of times we've fixed a timestamp ambiguity, only
341 /// applicable for tests.
342 /// applicable for tests.
342 #[cfg(test)]
343 #[cfg(test)]
343 static TIMESTAMP_FIXES_CALLS: AtomicUsize = AtomicUsize::new(0);
344 static TIMESTAMP_FIXES_CALLS: AtomicUsize = AtomicUsize::new(0);
344
345
345 fn avoid_timestamp_ambiguity(path: &Path, old: &Metadata) {
346 fn avoid_timestamp_ambiguity(path: &Path, old: &Metadata) {
346 if let Ok(new) = path.metadata() {
347 if let Ok(new) = path.metadata() {
347 let is_ambiguous = is_filetime_ambiguous(&new, old);
348 let is_ambiguous = is_filetime_ambiguous(&new, old);
348 if is_ambiguous {
349 if is_ambiguous {
349 let advanced =
350 let advanced =
350 filetime::FileTime::from_unix_time(old.mtime() + 1, 0);
351 filetime::FileTime::from_unix_time(old.mtime() + 1, 0);
351 if filetime::set_file_times(path, advanced, advanced).is_ok() {
352 if filetime::set_file_times(path, advanced, advanced).is_ok() {
352 #[cfg(test)]
353 #[cfg(test)]
353 {
354 {
354 TIMESTAMP_FIXES_CALLS.fetch_add(1, Ordering::Relaxed);
355 TIMESTAMP_FIXES_CALLS.fetch_add(1, Ordering::Relaxed);
355 }
356 }
356 }
357 }
357 }
358 }
358 }
359 }
359 }
360 }
360
361
361 /// Examine whether new stat is ambiguous against old one
362 /// Examine whether new stat is ambiguous against old one
362 ///
363 ///
363 /// `S[N]` below means stat of a file at `N`-th change:
364 /// `S[N]` below means stat of a file at `N`-th change:
364 ///
365 ///
365 /// - `S[n-1].ctime < S[n].ctime`: can detect change of a file
366 /// - `S[n-1].ctime < S[n].ctime`: can detect change of a file
366 /// - `S[n-1].ctime == S[n].ctime`
367 /// - `S[n-1].ctime == S[n].ctime`
367 /// - `S[n-1].ctime < S[n].mtime`: means natural advancing (*1)
368 /// - `S[n-1].ctime < S[n].mtime`: means natural advancing (*1)
368 /// - `S[n-1].ctime == S[n].mtime`: is ambiguous (*2)
369 /// - `S[n-1].ctime == S[n].mtime`: is ambiguous (*2)
369 /// - `S[n-1].ctime > S[n].mtime`: never occurs naturally (don't care)
370 /// - `S[n-1].ctime > S[n].mtime`: never occurs naturally (don't care)
370 /// - `S[n-1].ctime > S[n].ctime`: never occurs naturally (don't care)
371 /// - `S[n-1].ctime > S[n].ctime`: never occurs naturally (don't care)
371 ///
372 ///
372 /// Case (*2) above means that a file was changed twice or more at
373 /// Case (*2) above means that a file was changed twice or more at
373 /// same time in sec (= `S[n-1].ctime`), and comparison of timestamp
374 /// same time in sec (= `S[n-1].ctime`), and comparison of timestamp
374 /// is ambiguous.
375 /// is ambiguous.
375 ///
376 ///
376 /// Base idea to avoid such ambiguity is "advance mtime 1 sec, if
377 /// Base idea to avoid such ambiguity is "advance mtime 1 sec, if
377 /// timestamp is ambiguous".
378 /// timestamp is ambiguous".
378 ///
379 ///
379 /// But advancing mtime only in case (*2) doesn't work as
380 /// But advancing mtime only in case (*2) doesn't work as
380 /// expected, because naturally advanced `S[n].mtime` in case (*1)
381 /// expected, because naturally advanced `S[n].mtime` in case (*1)
381 /// might be equal to manually advanced `S[n-1 or earlier].mtime`.
382 /// might be equal to manually advanced `S[n-1 or earlier].mtime`.
382 ///
383 ///
383 /// Therefore, all `S[n-1].ctime == S[n].ctime` cases should be
384 /// Therefore, all `S[n-1].ctime == S[n].ctime` cases should be
384 /// treated as ambiguous regardless of mtime, to avoid overlooking
385 /// treated as ambiguous regardless of mtime, to avoid overlooking
385 /// by confliction between such mtime.
386 /// by confliction between such mtime.
386 ///
387 ///
387 /// Advancing mtime `if isambig(new, old)` ensures `S[n-1].mtime !=
388 /// Advancing mtime `if isambig(new, old)` ensures `S[n-1].mtime !=
388 /// S[n].mtime`, even if size of a file isn't changed.
389 /// S[n].mtime`, even if size of a file isn't changed.
389 pub fn is_filetime_ambiguous(new: &Metadata, old: &Metadata) -> bool {
390 pub fn is_filetime_ambiguous(new: &Metadata, old: &Metadata) -> bool {
390 new.ctime() == old.ctime()
391 new.ctime() == old.ctime()
391 }
392 }
392
393
393 /// Writable file object that atomically updates a file
394 /// Writable file object that atomically updates a file
394 ///
395 ///
395 /// All writes will go to a temporary copy of the original file. Call
396 /// All writes will go to a temporary copy of the original file. Call
396 /// [`Self::close`] when you are done writing, and [`Self`] will rename
397 /// [`Self::close`] when you are done writing, and [`Self`] will rename
397 /// the temporary copy to the original name, making the changes
398 /// the temporary copy to the original name, making the changes
398 /// visible. If the object is destroyed without being closed, all your
399 /// visible. If the object is destroyed without being closed, all your
399 /// writes are discarded.
400 /// writes are discarded.
400 #[derive(Debug)]
401 #[derive(Debug)]
401 pub struct AtomicFile {
402 pub struct AtomicFile {
402 /// The temporary file to write to
403 /// The temporary file to write to
403 fp: std::fs::File,
404 fp: std::fs::File,
404 /// Path of the temp file
405 /// Path of the temp file
405 temp_path: PathBuf,
406 temp_path: PathBuf,
406 /// Used when stat'ing the file, is useful only if the target file is
407 /// Used when stat'ing the file, is useful only if the target file is
407 /// guarded by any lock (e.g. repo.lock or repo.wlock).
408 /// guarded by any lock (e.g. repo.lock or repo.wlock).
408 check_ambig: bool,
409 check_ambig: bool,
409 /// Path of the target file
410 /// Path of the target file
410 target_name: PathBuf,
411 target_name: PathBuf,
411 /// Whether the file is open or not
412 /// Whether the file is open or not
412 is_open: bool,
413 is_open: bool,
413 }
414 }
414
415
415 impl AtomicFile {
416 impl AtomicFile {
416 pub fn new(
417 pub fn new(
417 target_path: impl AsRef<Path>,
418 target_path: impl AsRef<Path>,
418 empty: bool,
419 empty: bool,
419 check_ambig: bool,
420 check_ambig: bool,
420 ) -> Result<Self, HgError> {
421 ) -> Result<Self, HgError> {
421 let target_path = target_path.as_ref().to_owned();
422 let target_path = target_path.as_ref().to_owned();
422
423
423 let random_id =
424 let random_id =
424 Alphanumeric.sample_string(&mut rand::thread_rng(), 12);
425 Alphanumeric.sample_string(&mut rand::thread_rng(), 12);
425 let filename =
426 let filename =
426 target_path.file_name().expect("target has no filename");
427 target_path.file_name().expect("target has no filename");
427 let filename = get_bytes_from_path(filename);
428 let filename = get_bytes_from_path(filename);
428 let temp_filename =
429 let temp_filename =
429 format_bytes!(b".{}-{}~", filename, random_id.as_bytes());
430 format_bytes!(b".{}-{}~", filename, random_id.as_bytes());
430 let temp_path =
431 let temp_path =
431 target_path.with_file_name(get_path_from_bytes(&temp_filename));
432 target_path.with_file_name(get_path_from_bytes(&temp_filename));
432
433
433 if !empty {
434 if !empty {
434 std::fs::copy(&target_path, &temp_path)
435 std::fs::copy(&target_path, &temp_path)
435 .with_context(|| IoErrorContext::CopyingFile {
436 .with_context(|| IoErrorContext::CopyingFile {
436 from: target_path.to_owned(),
437 from: target_path.to_owned(),
437 to: temp_path.to_owned(),
438 to: temp_path.to_owned(),
438 })
439 })
439 // If it doesn't exist, create it on open
440 // If it doesn't exist, create it on open
440 .io_not_found_as_none()?;
441 .io_not_found_as_none()?;
441 }
442 }
442 let fp = std::fs::OpenOptions::new()
443 let fp = std::fs::OpenOptions::new()
443 .write(true)
444 .write(true)
444 .create(true)
445 .create(true)
445 .truncate(empty)
446 .truncate(empty)
446 .open(&temp_path)
447 .open(&temp_path)
447 .when_writing_file(&temp_path)?;
448 .when_writing_file(&temp_path)?;
448
449
449 Ok(Self {
450 Ok(Self {
450 fp,
451 fp,
451 temp_path,
452 temp_path,
452 check_ambig,
453 check_ambig,
453 target_name: target_path,
454 target_name: target_path,
454 is_open: true,
455 is_open: true,
455 })
456 })
456 }
457 }
457
458
458 pub fn from_file(
459 pub fn from_file(
459 fp: std::fs::File,
460 fp: std::fs::File,
460 check_ambig: bool,
461 check_ambig: bool,
461 temp_name: PathBuf,
462 temp_name: PathBuf,
462 target_name: PathBuf,
463 target_name: PathBuf,
463 ) -> Self {
464 ) -> Self {
464 Self {
465 Self {
465 fp,
466 fp,
466 check_ambig,
467 check_ambig,
467 temp_path: temp_name,
468 temp_path: temp_name,
468 target_name,
469 target_name,
469 is_open: true,
470 is_open: true,
470 }
471 }
471 }
472 }
472
473
473 /// Write `buf` to the temporary file
474 /// Write `buf` to the temporary file
474 pub fn write_all(&mut self, buf: &[u8]) -> Result<(), std::io::Error> {
475 pub fn write_all(&mut self, buf: &[u8]) -> Result<(), std::io::Error> {
475 self.fp.write_all(buf)
476 self.fp.write_all(buf)
476 }
477 }
477
478
478 fn target(&self) -> PathBuf {
479 fn target(&self) -> PathBuf {
479 self.temp_path
480 self.temp_path
480 .parent()
481 .parent()
481 .expect("should not be at the filesystem root")
482 .expect("should not be at the filesystem root")
482 .join(&self.target_name)
483 .join(&self.target_name)
483 }
484 }
484
485
485 /// Close the temporary file and rename to the target
486 /// Close the temporary file and rename to the target
486 pub fn close(mut self) -> Result<(), std::io::Error> {
487 pub fn close(mut self) -> Result<(), std::io::Error> {
487 self.fp.flush()?;
488 self.fp.flush()?;
488 let target = self.target();
489 let target = self.target();
489 if self.check_ambig {
490 if self.check_ambig {
490 if let Ok(stat) = target.metadata() {
491 if let Ok(stat) = target.metadata() {
491 std::fs::rename(&self.temp_path, &target)?;
492 std::fs::rename(&self.temp_path, &target)?;
492 avoid_timestamp_ambiguity(&target, &stat);
493 avoid_timestamp_ambiguity(&target, &stat);
493 } else {
494 } else {
494 std::fs::rename(&self.temp_path, target)?;
495 std::fs::rename(&self.temp_path, target)?;
495 }
496 }
496 } else {
497 } else {
497 std::fs::rename(&self.temp_path, target)?;
498 std::fs::rename(&self.temp_path, target)?;
498 }
499 }
499 self.is_open = false;
500 self.is_open = false;
500 Ok(())
501 Ok(())
501 }
502 }
502 }
503 }
503
504
504 impl Seek for AtomicFile {
505 impl Seek for AtomicFile {
505 fn seek(&mut self, pos: std::io::SeekFrom) -> std::io::Result<u64> {
506 fn seek(&mut self, pos: std::io::SeekFrom) -> std::io::Result<u64> {
506 self.fp.seek(pos)
507 self.fp.seek(pos)
507 }
508 }
508 }
509 }
509
510
510 impl Drop for AtomicFile {
511 impl Drop for AtomicFile {
511 fn drop(&mut self) {
512 fn drop(&mut self) {
512 if self.is_open {
513 if self.is_open {
513 std::fs::remove_file(&self.temp_path).ok();
514 std::fs::remove_file(&self.temp_path).ok();
514 }
515 }
515 }
516 }
516 }
517 }
517
518
518 /// Abstracts over the VFS to allow for different implementations of the
519 /// Abstracts over the VFS to allow for different implementations of the
519 /// filesystem layer (like passing one from Python).
520 /// filesystem layer (like passing one from Python).
520 pub trait Vfs: Sync + Send + DynClone {
521 pub trait Vfs: Sync + Send + DynClone {
521 /// Open a [`VfsFile::Normal`] for reading the file at `filename`,
522 /// Open a [`VfsFile::Normal`] for reading the file at `filename`,
522 /// relative to this VFS's root.
523 /// relative to this VFS's root.
523 fn open(&self, filename: &Path) -> Result<VfsFile, HgError>;
524 fn open(&self, filename: &Path) -> Result<VfsFile, HgError>;
524 /// Open a [`VfsFile::Normal`] for writing and reading the file at
525 /// Open a [`VfsFile::Normal`] for writing and reading the file at
525 /// `filename`, relative to this VFS's root.
526 /// `filename`, relative to this VFS's root.
526 fn open_write(&self, filename: &Path) -> Result<VfsFile, HgError>;
527 fn open_write(&self, filename: &Path) -> Result<VfsFile, HgError>;
527 /// Open a [`VfsFile::Normal`] for reading and writing the file at
528 /// Open a [`VfsFile::Normal`] for reading and writing the file at
528 /// `filename`, relative to this VFS's root. This file will be checked
529 /// `filename`, relative to this VFS's root. This file will be checked
529 /// for an ambiguous mtime on [`drop`]. See [`is_filetime_ambiguous`].
530 /// for an ambiguous mtime on [`drop`]. See [`is_filetime_ambiguous`].
530 fn open_check_ambig(&self, filename: &Path) -> Result<VfsFile, HgError>;
531 fn open_check_ambig(&self, filename: &Path) -> Result<VfsFile, HgError>;
531 /// Create a [`VfsFile::Normal`] for reading and writing the file at
532 /// Create a [`VfsFile::Normal`] for reading and writing the file at
532 /// `filename`, relative to this VFS's root. If the file already exists,
533 /// `filename`, relative to this VFS's root. If the file already exists,
533 /// it will be truncated to 0 bytes.
534 /// it will be truncated to 0 bytes.
534 fn create(
535 fn create(
535 &self,
536 &self,
536 filename: &Path,
537 filename: &Path,
537 check_ambig: bool,
538 check_ambig: bool,
538 ) -> Result<VfsFile, HgError>;
539 ) -> Result<VfsFile, HgError>;
539 /// Create a [`VfsFile::Atomic`] for reading and writing the file at
540 /// Create a [`VfsFile::Atomic`] for reading and writing the file at
540 /// `filename`, relative to this VFS's root. If the file already exists,
541 /// `filename`, relative to this VFS's root. If the file already exists,
541 /// it will be truncated to 0 bytes.
542 /// it will be truncated to 0 bytes.
542 fn create_atomic(
543 fn create_atomic(
543 &self,
544 &self,
544 filename: &Path,
545 filename: &Path,
545 check_ambig: bool,
546 check_ambig: bool,
546 ) -> Result<VfsFile, HgError>;
547 ) -> Result<VfsFile, HgError>;
547 /// Return the total file size in bytes of the open `file`. Errors are
548 /// Return the total file size in bytes of the open `file`. Errors are
548 /// usual IO errors (invalid file handle, permissions, etc.)
549 /// usual IO errors (invalid file handle, permissions, etc.)
549 fn file_size(&self, file: &VfsFile) -> Result<u64, HgError>;
550 fn file_size(&self, file: &VfsFile) -> Result<u64, HgError>;
550 /// Return `true` if `filename` exists relative to this VFS's root. Errors
551 /// Return `true` if `filename` exists relative to this VFS's root. Errors
551 /// will coerce to `false`, to this also returns `false` if there are
552 /// will coerce to `false`, to this also returns `false` if there are
552 /// IO problems. This is fine because any operation that actually tries
553 /// IO problems. This is fine because any operation that actually tries
553 /// to do anything with this path will get the same error.
554 /// to do anything with this path will get the same error.
554 fn exists(&self, filename: &Path) -> bool;
555 fn exists(&self, filename: &Path) -> bool;
555 /// Remove the file at `filename` relative to this VFS's root. Errors
556 /// Remove the file at `filename` relative to this VFS's root. Errors
556 /// are the usual IO errors (lacking permission, file does not exist, etc.)
557 /// are the usual IO errors (lacking permission, file does not exist, etc.)
557 fn unlink(&self, filename: &Path) -> Result<(), HgError>;
558 fn unlink(&self, filename: &Path) -> Result<(), HgError>;
558 /// Rename the file `from` to `to`, both relative to this VFS's root.
559 /// Rename the file `from` to `to`, both relative to this VFS's root.
559 /// Errors are the usual IO errors (lacking permission, file does not
560 /// Errors are the usual IO errors (lacking permission, file does not
560 /// exist, etc.). If `check_ambig` is `true`, the VFS will check for an
561 /// exist, etc.). If `check_ambig` is `true`, the VFS will check for an
561 /// ambiguous mtime on rename. See [`is_filetime_ambiguous`].
562 /// ambiguous mtime on rename. See [`is_filetime_ambiguous`].
562 fn rename(
563 fn rename(
563 &self,
564 &self,
564 from: &Path,
565 from: &Path,
565 to: &Path,
566 to: &Path,
566 check_ambig: bool,
567 check_ambig: bool,
567 ) -> Result<(), HgError>;
568 ) -> Result<(), HgError>;
568 /// Rename the file `from` to `to`, both relative to this VFS's root.
569 /// Rename the file `from` to `to`, both relative to this VFS's root.
569 /// Errors are the usual IO errors (lacking permission, file does not
570 /// Errors are the usual IO errors (lacking permission, file does not
570 /// exist, etc.). If `check_ambig` is passed, the VFS will check for an
571 /// exist, etc.). If `check_ambig` is passed, the VFS will check for an
571 /// ambiguous mtime on rename. See [`is_filetime_ambiguous`].
572 /// ambiguous mtime on rename. See [`is_filetime_ambiguous`].
572 fn copy(&self, from: &Path, to: &Path) -> Result<(), HgError>;
573 fn copy(&self, from: &Path, to: &Path) -> Result<(), HgError>;
573 /// Returns the absolute root path of this VFS, relative to which all
574 /// Returns the absolute root path of this VFS, relative to which all
574 /// operations are done.
575 /// operations are done.
575 fn base(&self) -> &Path;
576 fn base(&self) -> &Path;
576 }
577 }
577
578
578 /// These methods will need to be implemented once `rhg` (and other) non-Python
579 /// These methods will need to be implemented once `rhg` (and other) non-Python
579 /// users of `hg-core` start doing more on their own, like writing to files.
580 /// users of `hg-core` start doing more on their own, like writing to files.
580 impl Vfs for VfsImpl {
581 impl Vfs for VfsImpl {
581 fn open(&self, filename: &Path) -> Result<VfsFile, HgError> {
582 fn open(&self, filename: &Path) -> Result<VfsFile, HgError> {
582 // TODO auditpath
583 // TODO auditpath
583 let path = self.base.join(filename);
584 let path = self.base.join(filename);
584 Ok(VfsFile::normal(
585 Ok(VfsFile::normal(
585 std::fs::File::open(&path).when_reading_file(&path)?,
586 std::fs::File::open(&path).when_reading_file(&path)?,
586 filename.to_owned(),
587 filename.to_owned(),
587 ))
588 ))
588 }
589 }
589
590
590 fn open_write(&self, filename: &Path) -> Result<VfsFile, HgError> {
591 fn open_write(&self, filename: &Path) -> Result<VfsFile, HgError> {
591 if self.readonly {
592 if self.readonly {
592 return Err(HgError::abort(
593 return Err(HgError::abort(
593 "write access in a readonly vfs",
594 "write access in a readonly vfs",
594 exit_codes::ABORT,
595 exit_codes::ABORT,
595 None,
596 None,
596 ));
597 ));
597 }
598 }
598 // TODO auditpath
599 // TODO auditpath
599 let path = self.base.join(filename);
600 let path = self.base.join(filename);
600 copy_in_place_if_hardlink(&path)?;
601 copy_in_place_if_hardlink(&path)?;
601
602
602 Ok(VfsFile::normal(
603 Ok(VfsFile::normal(
603 OpenOptions::new()
604 OpenOptions::new()
604 .create(false)
605 .create(false)
605 .create_new(false)
606 .create_new(false)
606 .write(true)
607 .write(true)
607 .read(true)
608 .read(true)
608 .open(&path)
609 .open(&path)
609 .when_writing_file(&path)?,
610 .when_writing_file(&path)?,
610 path.to_owned(),
611 path.to_owned(),
611 ))
612 ))
612 }
613 }
613
614
614 fn open_check_ambig(&self, filename: &Path) -> Result<VfsFile, HgError> {
615 fn open_check_ambig(&self, filename: &Path) -> Result<VfsFile, HgError> {
615 if self.readonly {
616 if self.readonly {
616 return Err(HgError::abort(
617 return Err(HgError::abort(
617 "write access in a readonly vfs",
618 "write access in a readonly vfs",
618 exit_codes::ABORT,
619 exit_codes::ABORT,
619 None,
620 None,
620 ));
621 ));
621 }
622 }
622
623
623 let path = self.base.join(filename);
624 let path = self.base.join(filename);
624 copy_in_place_if_hardlink(&path)?;
625 copy_in_place_if_hardlink(&path)?;
625
626
626 // TODO auditpath
627 // TODO auditpath
627 VfsFile::normal_check_ambig(
628 VfsFile::normal_check_ambig(
628 OpenOptions::new()
629 OpenOptions::new()
629 .write(true)
630 .write(true)
630 .read(true) // Can be used for reading to save on `open` calls
631 .read(true) // Can be used for reading to save on `open` calls
631 .create(false)
632 .create(false)
632 .open(&path)
633 .open(&path)
633 .when_reading_file(&path)?,
634 .when_reading_file(&path)?,
634 path.to_owned(),
635 path.to_owned(),
635 )
636 )
636 }
637 }
637
638
638 fn create(
639 fn create(
639 &self,
640 &self,
640 filename: &Path,
641 filename: &Path,
641 check_ambig: bool,
642 check_ambig: bool,
642 ) -> Result<VfsFile, HgError> {
643 ) -> Result<VfsFile, HgError> {
643 if self.readonly {
644 if self.readonly {
644 return Err(HgError::abort(
645 return Err(HgError::abort(
645 "write access in a readonly vfs",
646 "write access in a readonly vfs",
646 exit_codes::ABORT,
647 exit_codes::ABORT,
647 None,
648 None,
648 ));
649 ));
649 }
650 }
650 // TODO auditpath
651 // TODO auditpath
651 let path = self.base.join(filename);
652 let path = self.base.join(filename);
652 let parent = path.parent().expect("file at root");
653 let parent = path.parent().expect("file at root");
653 std::fs::create_dir_all(parent).when_writing_file(parent)?;
654 std::fs::create_dir_all(parent).when_writing_file(parent)?;
654
655
655 let file = OpenOptions::new()
656 let file = OpenOptions::new()
656 .create(true)
657 .create(true)
657 .truncate(true)
658 .truncate(true)
658 .write(true)
659 .write(true)
659 .read(true)
660 .read(true)
660 .open(&path)
661 .open(&path)
661 .when_writing_file(&path)?;
662 .when_writing_file(&path)?;
662
663
663 if let Some(mode) = self.mode {
664 if let Some(mode) = self.mode {
664 // Creating the file with the right permission (with `.mode()`)
665 // Creating the file with the right permission (with `.mode()`)
665 // may not work since umask takes effect for file creation.
666 // may not work since umask takes effect for file creation.
666 // So we need to fix the permission after creating the file.
667 // So we need to fix the permission after creating the file.
667 fix_directory_permissions(&self.base, &path, mode)?;
668 fix_directory_permissions(&self.base, &path, mode)?;
668 let perm = std::fs::Permissions::from_mode(mode & 0o666);
669 let perm = std::fs::Permissions::from_mode(mode & 0o666);
669 std::fs::set_permissions(&path, perm).when_writing_file(&path)?;
670 std::fs::set_permissions(&path, perm).when_writing_file(&path)?;
670 }
671 }
671
672
672 Ok(VfsFile::Normal {
673 Ok(VfsFile::Normal {
673 file,
674 file,
674 check_ambig: if check_ambig {
675 check_ambig: if check_ambig {
675 Some(path.metadata().when_reading_file(&path)?)
676 Some(path.metadata().when_reading_file(&path)?)
676 } else {
677 } else {
677 None
678 None
678 },
679 },
679 path: path.to_owned(),
680 path: path.to_owned(),
680 })
681 })
681 }
682 }
682
683
683 fn create_atomic(
684 fn create_atomic(
684 &self,
685 &self,
685 _filename: &Path,
686 _filename: &Path,
686 _check_ambig: bool,
687 _check_ambig: bool,
687 ) -> Result<VfsFile, HgError> {
688 ) -> Result<VfsFile, HgError> {
688 todo!()
689 todo!()
689 }
690 }
690
691
691 fn file_size(&self, file: &VfsFile) -> Result<u64, HgError> {
692 fn file_size(&self, file: &VfsFile) -> Result<u64, HgError> {
692 Ok(file
693 Ok(file
693 .metadata()
694 .metadata()
694 .map_err(|e| {
695 .map_err(|e| {
695 HgError::abort(
696 HgError::abort(
696 format!("Could not get file metadata: {}", e),
697 format!("Could not get file metadata: {}", e),
697 exit_codes::ABORT,
698 exit_codes::ABORT,
698 None,
699 None,
699 )
700 )
700 })?
701 })?
701 .size())
702 .size())
702 }
703 }
703
704
704 fn exists(&self, filename: &Path) -> bool {
705 fn exists(&self, filename: &Path) -> bool {
705 self.base.join(filename).exists()
706 self.base.join(filename).exists()
706 }
707 }
707
708
708 fn unlink(&self, filename: &Path) -> Result<(), HgError> {
709 fn unlink(&self, filename: &Path) -> Result<(), HgError> {
709 if self.readonly {
710 if self.readonly {
710 return Err(HgError::abort(
711 return Err(HgError::abort(
711 "write access in a readonly vfs",
712 "write access in a readonly vfs",
712 exit_codes::ABORT,
713 exit_codes::ABORT,
713 None,
714 None,
714 ));
715 ));
715 }
716 }
716 let path = self.base.join(filename);
717 let path = self.base.join(filename);
717 std::fs::remove_file(&path)
718 std::fs::remove_file(&path)
718 .with_context(|| IoErrorContext::RemovingFile(path))
719 .with_context(|| IoErrorContext::RemovingFile(path))
719 }
720 }
720
721
721 fn rename(
722 fn rename(
722 &self,
723 &self,
723 from: &Path,
724 from: &Path,
724 to: &Path,
725 to: &Path,
725 check_ambig: bool,
726 check_ambig: bool,
726 ) -> Result<(), HgError> {
727 ) -> Result<(), HgError> {
727 if self.readonly {
728 if self.readonly {
728 return Err(HgError::abort(
729 return Err(HgError::abort(
729 "write access in a readonly vfs",
730 "write access in a readonly vfs",
730 exit_codes::ABORT,
731 exit_codes::ABORT,
731 None,
732 None,
732 ));
733 ));
733 }
734 }
734 let old_stat = if check_ambig {
735 let old_stat = if check_ambig {
735 Some(
736 Some(
736 from.metadata()
737 from.metadata()
737 .when_reading_file(from)
738 .when_reading_file(from)
738 .io_not_found_as_none()?,
739 .io_not_found_as_none()?,
739 )
740 )
740 } else {
741 } else {
741 None
742 None
742 };
743 };
743 let from = self.base.join(from);
744 let from = self.base.join(from);
744 let to = self.base.join(to);
745 let to = self.base.join(to);
745 std::fs::rename(&from, &to).with_context(|| {
746 std::fs::rename(&from, &to).with_context(|| {
746 IoErrorContext::RenamingFile {
747 IoErrorContext::RenamingFile {
747 from,
748 from,
748 to: to.to_owned(),
749 to: to.to_owned(),
749 }
750 }
750 })?;
751 })?;
751 if let Some(Some(old)) = old_stat {
752 if let Some(Some(old)) = old_stat {
752 avoid_timestamp_ambiguity(&to, &old);
753 avoid_timestamp_ambiguity(&to, &old);
753 }
754 }
754 Ok(())
755 Ok(())
755 }
756 }
756
757
757 fn copy(&self, from: &Path, to: &Path) -> Result<(), HgError> {
758 fn copy(&self, from: &Path, to: &Path) -> Result<(), HgError> {
758 let from = self.base.join(from);
759 let from = self.base.join(from);
759 let to = self.base.join(to);
760 let to = self.base.join(to);
760 std::fs::copy(&from, &to)
761 std::fs::copy(&from, &to)
761 .with_context(|| IoErrorContext::CopyingFile { from, to })
762 .with_context(|| IoErrorContext::CopyingFile { from, to })
762 .map(|_| ())
763 .map(|_| ())
763 }
764 }
764
765
765 fn base(&self) -> &Path {
766 fn base(&self) -> &Path {
766 &self.base
767 &self.base
767 }
768 }
768 }
769 }
769
770
770 fn fix_directory_permissions(
771 fn fix_directory_permissions(
771 base: &Path,
772 base: &Path,
772 path: &Path,
773 path: &Path,
773 mode: u32,
774 mode: u32,
774 ) -> Result<(), HgError> {
775 ) -> Result<(), HgError> {
775 let mut ancestors = path.ancestors();
776 let mut ancestors = path.ancestors();
776 ancestors.next(); // yields the path itself
777 ancestors.next(); // yields the path itself
777
778
778 for ancestor in ancestors {
779 for ancestor in ancestors {
779 if ancestor == base {
780 if ancestor == base {
780 break;
781 break;
781 }
782 }
782 let perm = std::fs::Permissions::from_mode(mode);
783 let perm = std::fs::Permissions::from_mode(mode);
783 std::fs::set_permissions(ancestor, perm)
784 std::fs::set_permissions(ancestor, perm)
784 .when_writing_file(ancestor)?;
785 .when_writing_file(ancestor)?;
785 }
786 }
786 Ok(())
787 Ok(())
787 }
788 }
788
789
789 /// A VFS that understands the `fncache` store layout (file encoding), and
790 /// A VFS that understands the `fncache` store layout (file encoding), and
790 /// adds new entries to the `fncache`.
791 /// adds new entries to the `fncache`.
791 /// TODO Only works when using from Python for now.
792 /// TODO Only works when using from Python for now.
792 pub struct FnCacheVfs {
793 pub struct FnCacheVfs {
793 inner: VfsImpl,
794 inner: VfsImpl,
794 fncache: Box<dyn FnCache>,
795 fncache: Box<dyn FnCache>,
795 }
796 }
796
797
797 impl Clone for FnCacheVfs {
798 impl Clone for FnCacheVfs {
798 fn clone(&self) -> Self {
799 fn clone(&self) -> Self {
799 Self {
800 Self {
800 inner: self.inner.clone(),
801 inner: self.inner.clone(),
801 fncache: dyn_clone::clone_box(&*self.fncache),
802 fncache: dyn_clone::clone_box(&*self.fncache),
802 }
803 }
803 }
804 }
804 }
805 }
805
806
806 impl FnCacheVfs {
807 impl FnCacheVfs {
807 pub fn new(
808 pub fn new(
808 base: PathBuf,
809 base: PathBuf,
809 readonly: bool,
810 readonly: bool,
810 fncache: Box<dyn FnCache>,
811 fncache: Box<dyn FnCache>,
811 ) -> Self {
812 ) -> Self {
812 let inner = VfsImpl::new(base, readonly);
813 let inner = VfsImpl::new(base, readonly);
813 Self { inner, fncache }
814 Self { inner, fncache }
814 }
815 }
815
816
816 fn maybe_add_to_fncache(
817 fn maybe_add_to_fncache(
817 &self,
818 &self,
818 filename: &Path,
819 filename: &Path,
819 encoded_path: &Path,
820 encoded_path: &Path,
820 ) -> Result<(), HgError> {
821 ) -> Result<(), HgError> {
821 let relevant_file = (filename.starts_with("data/")
822 let relevant_file = (filename.starts_with("data/")
822 || filename.starts_with("meta/"))
823 || filename.starts_with("meta/"))
823 && is_revlog_file(filename);
824 && is_revlog_file(filename);
824 if relevant_file {
825 if relevant_file {
825 let not_load = !self.fncache.is_loaded()
826 let not_load = !self.fncache.is_loaded()
826 && (self.exists(filename)
827 && (self.exists(filename)
827 && self
828 && self
828 .inner
829 .inner
829 .join(encoded_path)
830 .join(encoded_path)
830 .metadata()
831 .metadata()
831 .when_reading_file(encoded_path)?
832 .when_reading_file(encoded_path)?
832 .size()
833 .size()
833 != 0);
834 != 0);
834 if !not_load {
835 if !not_load {
835 self.fncache.add(filename);
836 self.fncache.add(filename);
836 }
837 }
837 };
838 };
838 Ok(())
839 Ok(())
839 }
840 }
840 }
841 }
841
842
842 impl Vfs for FnCacheVfs {
843 impl Vfs for FnCacheVfs {
843 fn open(&self, filename: &Path) -> Result<VfsFile, HgError> {
844 fn open(&self, filename: &Path) -> Result<VfsFile, HgError> {
844 let encoded = path_encode(&get_bytes_from_path(filename));
845 let encoded = path_encode(&get_bytes_from_path(filename));
845 let filename = get_path_from_bytes(&encoded);
846 let filename = get_path_from_bytes(&encoded);
846 self.inner.open(filename)
847 self.inner.open(filename)
847 }
848 }
848
849
849 fn open_write(&self, filename: &Path) -> Result<VfsFile, HgError> {
850 fn open_write(&self, filename: &Path) -> Result<VfsFile, HgError> {
850 let encoded = path_encode(&get_bytes_from_path(filename));
851 let encoded = path_encode(&get_bytes_from_path(filename));
851 let encoded_path = get_path_from_bytes(&encoded);
852 let encoded_path = get_path_from_bytes(&encoded);
852 self.maybe_add_to_fncache(filename, encoded_path)?;
853 self.maybe_add_to_fncache(filename, encoded_path)?;
853 self.inner.open_write(encoded_path)
854 self.inner.open_write(encoded_path)
854 }
855 }
855
856
856 fn open_check_ambig(&self, filename: &Path) -> Result<VfsFile, HgError> {
857 fn open_check_ambig(&self, filename: &Path) -> Result<VfsFile, HgError> {
857 let encoded = path_encode(&get_bytes_from_path(filename));
858 let encoded = path_encode(&get_bytes_from_path(filename));
858 let filename = get_path_from_bytes(&encoded);
859 let filename = get_path_from_bytes(&encoded);
859 self.inner.open_check_ambig(filename)
860 self.inner.open_check_ambig(filename)
860 }
861 }
861
862
862 fn create(
863 fn create(
863 &self,
864 &self,
864 filename: &Path,
865 filename: &Path,
865 check_ambig: bool,
866 check_ambig: bool,
866 ) -> Result<VfsFile, HgError> {
867 ) -> Result<VfsFile, HgError> {
867 let encoded = path_encode(&get_bytes_from_path(filename));
868 let encoded = path_encode(&get_bytes_from_path(filename));
868 let encoded_path = get_path_from_bytes(&encoded);
869 let encoded_path = get_path_from_bytes(&encoded);
869 self.maybe_add_to_fncache(filename, encoded_path)?;
870 self.maybe_add_to_fncache(filename, encoded_path)?;
870 self.inner.create(encoded_path, check_ambig)
871 self.inner.create(encoded_path, check_ambig)
871 }
872 }
872
873
873 fn create_atomic(
874 fn create_atomic(
874 &self,
875 &self,
875 filename: &Path,
876 filename: &Path,
876 check_ambig: bool,
877 check_ambig: bool,
877 ) -> Result<VfsFile, HgError> {
878 ) -> Result<VfsFile, HgError> {
878 let encoded = path_encode(&get_bytes_from_path(filename));
879 let encoded = path_encode(&get_bytes_from_path(filename));
879 let filename = get_path_from_bytes(&encoded);
880 let filename = get_path_from_bytes(&encoded);
880 self.inner.create_atomic(filename, check_ambig)
881 self.inner.create_atomic(filename, check_ambig)
881 }
882 }
882
883
883 fn file_size(&self, file: &VfsFile) -> Result<u64, HgError> {
884 fn file_size(&self, file: &VfsFile) -> Result<u64, HgError> {
884 self.inner.file_size(file)
885 self.inner.file_size(file)
885 }
886 }
886
887
887 fn exists(&self, filename: &Path) -> bool {
888 fn exists(&self, filename: &Path) -> bool {
888 let encoded = path_encode(&get_bytes_from_path(filename));
889 let encoded = path_encode(&get_bytes_from_path(filename));
889 let filename = get_path_from_bytes(&encoded);
890 let filename = get_path_from_bytes(&encoded);
890 self.inner.exists(filename)
891 self.inner.exists(filename)
891 }
892 }
892
893
893 fn unlink(&self, filename: &Path) -> Result<(), HgError> {
894 fn unlink(&self, filename: &Path) -> Result<(), HgError> {
894 let encoded = path_encode(&get_bytes_from_path(filename));
895 let encoded = path_encode(&get_bytes_from_path(filename));
895 let filename = get_path_from_bytes(&encoded);
896 let filename = get_path_from_bytes(&encoded);
896 self.inner.unlink(filename)
897 self.inner.unlink(filename)
897 }
898 }
898
899
899 fn rename(
900 fn rename(
900 &self,
901 &self,
901 from: &Path,
902 from: &Path,
902 to: &Path,
903 to: &Path,
903 check_ambig: bool,
904 check_ambig: bool,
904 ) -> Result<(), HgError> {
905 ) -> Result<(), HgError> {
905 let encoded = path_encode(&get_bytes_from_path(from));
906 let encoded = path_encode(&get_bytes_from_path(from));
906 let from = get_path_from_bytes(&encoded);
907 let from = get_path_from_bytes(&encoded);
907 let encoded = path_encode(&get_bytes_from_path(to));
908 let encoded = path_encode(&get_bytes_from_path(to));
908 let to = get_path_from_bytes(&encoded);
909 let to = get_path_from_bytes(&encoded);
909 self.inner.rename(from, to, check_ambig)
910 self.inner.rename(from, to, check_ambig)
910 }
911 }
911
912
912 fn copy(&self, from: &Path, to: &Path) -> Result<(), HgError> {
913 fn copy(&self, from: &Path, to: &Path) -> Result<(), HgError> {
913 let encoded = path_encode(&get_bytes_from_path(from));
914 let encoded = path_encode(&get_bytes_from_path(from));
914 let from = get_path_from_bytes(&encoded);
915 let from = get_path_from_bytes(&encoded);
915 let encoded = path_encode(&get_bytes_from_path(to));
916 let encoded = path_encode(&get_bytes_from_path(to));
916 let to = get_path_from_bytes(&encoded);
917 let to = get_path_from_bytes(&encoded);
917 self.inner.copy(from, to)
918 self.inner.copy(from, to)
918 }
919 }
919 fn base(&self) -> &Path {
920 fn base(&self) -> &Path {
920 self.inner.base()
921 self.inner.base()
921 }
922 }
922 }
923 }
923
924
924 /// Detects whether `path` is a hardlink and does a tmp copy + rename erase
925 /// Detects whether `path` is a hardlink and does a tmp copy + rename erase
925 /// to turn it into its own file. Revlogs are usually hardlinked when doing
926 /// to turn it into its own file. Revlogs are usually hardlinked when doing
926 /// a local clone, and we don't want to modify the original repo.
927 /// a local clone, and we don't want to modify the original repo.
927 fn copy_in_place_if_hardlink(path: &Path) -> Result<(), HgError> {
928 fn copy_in_place_if_hardlink(path: &Path) -> Result<(), HgError> {
928 let metadata = path.metadata().when_writing_file(path)?;
929 let metadata = path.metadata().when_writing_file(path)?;
929 if metadata.nlink() > 0 {
930 if metadata.nlink() > 0 {
930 // If it's hardlinked, copy it and rename it back before changing it.
931 // If it's hardlinked, copy it and rename it back before changing it.
931 let tmpdir = path.parent().expect("file at root");
932 let tmpdir = path.parent().expect("file at root");
932 let name = Alphanumeric.sample_string(&mut rand::thread_rng(), 16);
933 let name = Alphanumeric.sample_string(&mut rand::thread_rng(), 16);
933 let tmpfile = tmpdir.join(name);
934 let tmpfile = tmpdir.join(name);
934 std::fs::create_dir_all(tmpfile.parent().expect("file at root"))
935 std::fs::create_dir_all(tmpfile.parent().expect("file at root"))
935 .with_context(|| IoErrorContext::CopyingFile {
936 .with_context(|| IoErrorContext::CopyingFile {
936 from: path.to_owned(),
937 from: path.to_owned(),
937 to: tmpfile.to_owned(),
938 to: tmpfile.to_owned(),
938 })?;
939 })?;
939 std::fs::copy(path, &tmpfile).with_context(|| {
940 std::fs::copy(path, &tmpfile).with_context(|| {
940 IoErrorContext::CopyingFile {
941 IoErrorContext::CopyingFile {
941 from: path.to_owned(),
942 from: path.to_owned(),
942 to: tmpfile.to_owned(),
943 to: tmpfile.to_owned(),
943 }
944 }
944 })?;
945 })?;
945 std::fs::rename(&tmpfile, path).with_context(|| {
946 std::fs::rename(&tmpfile, path).with_context(|| {
946 IoErrorContext::RenamingFile {
947 IoErrorContext::RenamingFile {
947 from: tmpfile,
948 from: tmpfile,
948 to: path.to_owned(),
949 to: path.to_owned(),
949 }
950 }
950 })?;
951 })?;
951 }
952 }
952 Ok(())
953 Ok(())
953 }
954 }
954
955
955 pub fn is_revlog_file(path: impl AsRef<Path>) -> bool {
956 pub fn is_revlog_file(path: impl AsRef<Path>) -> bool {
956 path.as_ref()
957 path.as_ref()
957 .extension()
958 .extension()
958 .map(|ext| {
959 .map(|ext| {
959 ["i", "idx", "d", "dat", "n", "nd", "sda"]
960 ["i", "idx", "d", "dat", "n", "nd", "sda"]
960 .contains(&ext.to_string_lossy().as_ref())
961 .contains(&ext.to_string_lossy().as_ref())
961 })
962 })
962 .unwrap_or(false)
963 .unwrap_or(false)
963 }
964 }
964
965
965 pub(crate) fn is_dir(path: impl AsRef<Path>) -> Result<bool, HgError> {
966 pub(crate) fn is_dir(path: impl AsRef<Path>) -> Result<bool, HgError> {
966 Ok(fs_metadata(path)?.map_or(false, |meta| meta.is_dir()))
967 Ok(fs_metadata(path)?.map_or(false, |meta| meta.is_dir()))
967 }
968 }
968
969
969 pub(crate) fn is_file(path: impl AsRef<Path>) -> Result<bool, HgError> {
970 pub(crate) fn is_file(path: impl AsRef<Path>) -> Result<bool, HgError> {
970 Ok(fs_metadata(path)?.map_or(false, |meta| meta.is_file()))
971 Ok(fs_metadata(path)?.map_or(false, |meta| meta.is_file()))
971 }
972 }
972
973
973 /// Returns whether the given `path` is on a network file system.
974 /// Returns whether the given `path` is on a network file system.
974 /// Taken from `cargo`'s codebase.
975 /// Taken from `cargo`'s codebase.
975 #[cfg(target_os = "linux")]
976 #[cfg(target_os = "linux")]
976 pub(crate) fn is_on_nfs_mount(path: impl AsRef<Path>) -> bool {
977 pub(crate) fn is_on_nfs_mount(path: impl AsRef<Path>) -> bool {
977 use std::ffi::CString;
978 use std::ffi::CString;
978 use std::mem;
979 use std::mem;
979 use std::os::unix::prelude::*;
980 use std::os::unix::prelude::*;
980
981
981 let path = match CString::new(path.as_ref().as_os_str().as_bytes()) {
982 let path = match CString::new(path.as_ref().as_os_str().as_bytes()) {
982 Ok(path) => path,
983 Ok(path) => path,
983 Err(_) => return false,
984 Err(_) => return false,
984 };
985 };
985
986
986 unsafe {
987 unsafe {
987 let mut buf: libc::statfs = mem::zeroed();
988 let mut buf: libc::statfs = mem::zeroed();
988 let r = libc::statfs(path.as_ptr(), &mut buf);
989 let r = libc::statfs(path.as_ptr(), &mut buf);
989
990
990 r == 0 && buf.f_type as u32 == libc::NFS_SUPER_MAGIC as u32
991 r == 0 && buf.f_type as u32 == libc::NFS_SUPER_MAGIC as u32
991 }
992 }
992 }
993 }
993
994
994 /// Similar to what Cargo does; although detecting NFS (or non-local
995 /// Similar to what Cargo does; although detecting NFS (or non-local
995 /// file systems) _should_ be possible on other operating systems,
996 /// file systems) _should_ be possible on other operating systems,
996 /// we'll just assume that mmap() works there, for now; after all,
997 /// we'll just assume that mmap() works there, for now; after all,
997 /// _some_ functionality is better than a compile error, i.e. none at
998 /// _some_ functionality is better than a compile error, i.e. none at
998 /// all
999 /// all
999 #[cfg(not(target_os = "linux"))]
1000 #[cfg(not(target_os = "linux"))]
1000 pub(crate) fn is_on_nfs_mount(_path: impl AsRef<Path>) -> bool {
1001 pub(crate) fn is_on_nfs_mount(_path: impl AsRef<Path>) -> bool {
1001 false
1002 false
1002 }
1003 }
1003
1004
1004 #[cfg(test)]
1005 #[cfg(test)]
1005 mod tests {
1006 mod tests {
1006 use super::*;
1007 use super::*;
1007
1008
1008 #[test]
1009 #[test]
1009 fn test_atomic_file() {
1010 fn test_atomic_file() {
1010 let dir = tempfile::tempdir().unwrap().into_path();
1011 let dir = tempfile::tempdir().unwrap().into_path();
1011 let target_path = dir.join("sometargetname");
1012 let target_path = dir.join("sometargetname");
1012
1013
1013 for empty in [true, false] {
1014 for empty in [true, false] {
1014 let file = AtomicFile::new(&target_path, empty, false).unwrap();
1015 let file = AtomicFile::new(&target_path, empty, false).unwrap();
1015 assert!(file.is_open);
1016 assert!(file.is_open);
1016 let filename =
1017 let filename =
1017 file.temp_path.file_name().unwrap().to_str().unwrap();
1018 file.temp_path.file_name().unwrap().to_str().unwrap();
1018 // Make sure we have a coherent temp name
1019 // Make sure we have a coherent temp name
1019 assert_eq!(filename.len(), 29, "{}", filename);
1020 assert_eq!(filename.len(), 29, "{}", filename);
1020 assert!(filename.contains("sometargetname"));
1021 assert!(filename.contains("sometargetname"));
1021
1022
1022 // Make sure the temp file is created in the same folder
1023 // Make sure the temp file is created in the same folder
1023 assert_eq!(target_path.parent(), file.temp_path.parent());
1024 assert_eq!(target_path.parent(), file.temp_path.parent());
1024 }
1025 }
1025
1026
1026 assert!(!target_path.exists());
1027 assert!(!target_path.exists());
1027 std::fs::write(&target_path, "version 1").unwrap();
1028 std::fs::write(&target_path, "version 1").unwrap();
1028 let mut file = AtomicFile::new(&target_path, false, false).unwrap();
1029 let mut file = AtomicFile::new(&target_path, false, false).unwrap();
1029 file.write_all(b"version 2!").unwrap();
1030 file.write_all(b"version 2!").unwrap();
1030 assert_eq!(
1031 assert_eq!(
1031 std::fs::read(&target_path).unwrap(),
1032 std::fs::read(&target_path).unwrap(),
1032 b"version 1".to_vec()
1033 b"version 1".to_vec()
1033 );
1034 );
1034 let temp_path = file.temp_path.to_owned();
1035 let temp_path = file.temp_path.to_owned();
1035 // test that dropping the file should discard the temp file and not
1036 // test that dropping the file should discard the temp file and not
1036 // affect the target path.
1037 // affect the target path.
1037 drop(file);
1038 drop(file);
1038 assert_eq!(
1039 assert_eq!(
1039 std::fs::read(&target_path).unwrap(),
1040 std::fs::read(&target_path).unwrap(),
1040 b"version 1".to_vec()
1041 b"version 1".to_vec()
1041 );
1042 );
1042 assert!(!temp_path.exists());
1043 assert!(!temp_path.exists());
1043
1044
1044 let mut file = AtomicFile::new(&target_path, false, false).unwrap();
1045 let mut file = AtomicFile::new(&target_path, false, false).unwrap();
1045 file.write_all(b"version 2!").unwrap();
1046 file.write_all(b"version 2!").unwrap();
1046 assert_eq!(
1047 assert_eq!(
1047 std::fs::read(&target_path).unwrap(),
1048 std::fs::read(&target_path).unwrap(),
1048 b"version 1".to_vec()
1049 b"version 1".to_vec()
1049 );
1050 );
1050 file.close().unwrap();
1051 file.close().unwrap();
1051 assert_eq!(
1052 assert_eq!(
1052 std::fs::read(&target_path).unwrap(),
1053 std::fs::read(&target_path).unwrap(),
1053 b"version 2!".to_vec(),
1054 b"version 2!".to_vec(),
1054 "{}",
1055 "{}",
1055 std::fs::read_to_string(&target_path).unwrap()
1056 std::fs::read_to_string(&target_path).unwrap()
1056 );
1057 );
1057 assert!(target_path.exists());
1058 assert!(target_path.exists());
1058 assert!(!temp_path.exists());
1059 assert!(!temp_path.exists());
1059 }
1060 }
1060
1061
1061 #[test]
1062 #[test]
1062 fn test_vfs_file_check_ambig() {
1063 fn test_vfs_file_check_ambig() {
1063 let dir = tempfile::tempdir().unwrap().into_path();
1064 let dir = tempfile::tempdir().unwrap().into_path();
1064 let file_path = dir.join("file");
1065 let file_path = dir.join("file");
1065
1066
1066 fn vfs_file_write(file_path: &Path, check_ambig: bool) {
1067 fn vfs_file_write(file_path: &Path, check_ambig: bool) {
1067 let file = std::fs::OpenOptions::new()
1068 let file = std::fs::OpenOptions::new()
1068 .write(true)
1069 .write(true)
1069 .open(file_path)
1070 .open(file_path)
1070 .unwrap();
1071 .unwrap();
1071 let old_stat = if check_ambig {
1072 let old_stat = if check_ambig {
1072 Some(file.metadata().unwrap())
1073 Some(file.metadata().unwrap())
1073 } else {
1074 } else {
1074 None
1075 None
1075 };
1076 };
1076
1077
1077 let mut vfs_file = VfsFile::Normal {
1078 let mut vfs_file = VfsFile::Normal {
1078 file,
1079 file,
1079 path: file_path.to_owned(),
1080 path: file_path.to_owned(),
1080 check_ambig: old_stat,
1081 check_ambig: old_stat,
1081 };
1082 };
1082 vfs_file.write_all(b"contents").unwrap();
1083 vfs_file.write_all(b"contents").unwrap();
1083 }
1084 }
1084
1085
1085 std::fs::OpenOptions::new()
1086 std::fs::OpenOptions::new()
1086 .write(true)
1087 .write(true)
1087 .create(true)
1088 .create(true)
1088 .truncate(false)
1089 .truncate(false)
1089 .open(&file_path)
1090 .open(&file_path)
1090 .unwrap();
1091 .unwrap();
1091
1092
1092 let number_of_writes = 3;
1093 let number_of_writes = 3;
1093
1094
1094 // Try multiple times, because reproduction of an ambiguity depends
1095 // Try multiple times, because reproduction of an ambiguity depends
1095 // on "filesystem time"
1096 // on "filesystem time"
1096 for _ in 0..5 {
1097 for _ in 0..5 {
1097 TIMESTAMP_FIXES_CALLS.store(0, Ordering::Relaxed);
1098 TIMESTAMP_FIXES_CALLS.store(0, Ordering::Relaxed);
1098 vfs_file_write(&file_path, false);
1099 vfs_file_write(&file_path, false);
1099 let old_stat = file_path.metadata().unwrap();
1100 let old_stat = file_path.metadata().unwrap();
1100 if old_stat.ctime() != old_stat.mtime() {
1101 if old_stat.ctime() != old_stat.mtime() {
1101 // subsequent changing never causes ambiguity
1102 // subsequent changing never causes ambiguity
1102 continue;
1103 continue;
1103 }
1104 }
1104
1105
1105 // Repeat atomic write with `check_ambig == true`, to examine
1106 // Repeat atomic write with `check_ambig == true`, to examine
1106 // whether the mtime is advanced multiple times as expected
1107 // whether the mtime is advanced multiple times as expected
1107 for _ in 0..number_of_writes {
1108 for _ in 0..number_of_writes {
1108 vfs_file_write(&file_path, true);
1109 vfs_file_write(&file_path, true);
1109 }
1110 }
1110 let new_stat = file_path.metadata().unwrap();
1111 let new_stat = file_path.metadata().unwrap();
1111 if !is_filetime_ambiguous(&new_stat, &old_stat) {
1112 if !is_filetime_ambiguous(&new_stat, &old_stat) {
1112 // timestamp ambiguity was naturally avoided while repetition
1113 // timestamp ambiguity was naturally avoided while repetition
1113 continue;
1114 continue;
1114 }
1115 }
1115
1116
1116 assert_eq!(
1117 assert_eq!(
1117 TIMESTAMP_FIXES_CALLS.load(Ordering::Relaxed),
1118 TIMESTAMP_FIXES_CALLS.load(Ordering::Relaxed),
1118 number_of_writes
1119 number_of_writes
1119 );
1120 );
1120 assert_eq!(
1121 assert_eq!(
1121 old_stat.mtime() + number_of_writes as i64,
1122 old_stat.mtime() + number_of_writes as i64,
1122 file_path.metadata().unwrap().mtime()
1123 file_path.metadata().unwrap().mtime()
1123 );
1124 );
1124 break;
1125 break;
1125 }
1126 }
1126 // If we've arrived here without breaking, we might not have
1127 // If we've arrived here without breaking, we might not have
1127 // tested anything because the platform is too slow. This test will
1128 // tested anything because the platform is too slow. This test will
1128 // still work on fast platforms.
1129 // still work on fast platforms.
1129 }
1130 }
1130 }
1131 }
General Comments 0
You need to be logged in to leave comments. Login now