1 use crate::changelog::Changelog; |
1 use crate::changelog::Changelog; |
2 use crate::config::{Config, ConfigError, ConfigParseError}; |
2 use crate::config::{Config, ConfigError, ConfigParseError}; |
3 use crate::dirstate::DirstateParents; |
3 use crate::dirstate::DirstateParents; |
|
4 use crate::dirstate_tree::dirstate_map::DirstateMapWriteMode; |
4 use crate::dirstate_tree::on_disk::Docket as DirstateDocket; |
5 use crate::dirstate_tree::on_disk::Docket as DirstateDocket; |
5 use crate::dirstate_tree::owning::OwningDirstateMap; |
6 use crate::dirstate_tree::owning::OwningDirstateMap; |
6 use crate::errors::HgResultExt; |
7 use crate::errors::HgResultExt; |
7 use crate::errors::{HgError, IoResultExt}; |
8 use crate::errors::{HgError, IoResultExt}; |
8 use crate::lock::{try_with_lock_no_wait, LockError}; |
9 use crate::lock::{try_with_lock_no_wait, LockError}; |
9 use crate::manifest::{Manifest, Manifestlog}; |
10 use crate::manifest::{Manifest, Manifestlog}; |
10 use crate::revlog::filelog::Filelog; |
11 use crate::revlog::filelog::Filelog; |
11 use crate::revlog::RevlogError; |
12 use crate::revlog::RevlogError; |
|
13 use crate::utils::debug::debug_wait_for_file_or_print; |
12 use crate::utils::files::get_path_from_bytes; |
14 use crate::utils::files::get_path_from_bytes; |
13 use crate::utils::hg_path::HgPath; |
15 use crate::utils::hg_path::HgPath; |
14 use crate::utils::SliceExt; |
16 use crate::utils::SliceExt; |
15 use crate::vfs::{is_dir, is_file, Vfs}; |
17 use crate::vfs::{is_dir, is_file, Vfs}; |
16 use crate::{requirements, NodePrefix}; |
18 use crate::{requirements, NodePrefix}; |
252 .read("dirstate") |
256 .read("dirstate") |
253 .io_not_found_as_none()? |
257 .io_not_found_as_none()? |
254 .unwrap_or_default()) |
258 .unwrap_or_default()) |
255 } |
259 } |
256 |
260 |
|
261 fn dirstate_identity(&self) -> Result<Option<u64>, HgError> { |
|
262 use std::os::unix::fs::MetadataExt; |
|
263 Ok(self |
|
264 .hg_vfs() |
|
265 .symlink_metadata("dirstate") |
|
266 .io_not_found_as_none()? |
|
267 .map(|meta| meta.ino())) |
|
268 } |
|
269 |
257 pub fn dirstate_parents(&self) -> Result<DirstateParents, HgError> { |
270 pub fn dirstate_parents(&self) -> Result<DirstateParents, HgError> { |
258 Ok(*self |
271 Ok(*self |
259 .dirstate_parents |
272 .dirstate_parents |
260 .get_or_init(|| self.read_dirstate_parents())?) |
273 .get_or_init(|| self.read_dirstate_parents())?) |
261 } |
274 } |
262 |
275 |
263 fn read_dirstate_parents(&self) -> Result<DirstateParents, HgError> { |
276 fn read_dirstate_parents(&self) -> Result<DirstateParents, HgError> { |
264 let dirstate = self.dirstate_file_contents()?; |
277 let dirstate = self.dirstate_file_contents()?; |
265 let parents = if dirstate.is_empty() { |
278 let parents = if dirstate.is_empty() { |
266 if self.has_dirstate_v2() { |
|
267 self.dirstate_data_file_uuid.set(None); |
|
268 } |
|
269 DirstateParents::NULL |
279 DirstateParents::NULL |
270 } else if self.has_dirstate_v2() { |
280 } else if self.has_dirstate_v2() { |
271 let docket = |
281 let docket = |
272 crate::dirstate_tree::on_disk::read_docket(&dirstate)?; |
282 crate::dirstate_tree::on_disk::read_docket(&dirstate)?; |
273 self.dirstate_data_file_uuid |
|
274 .set(Some(docket.uuid.to_owned())); |
|
275 docket.parents() |
283 docket.parents() |
276 } else { |
284 } else { |
277 *crate::dirstate::parsers::parse_dirstate_parents(&dirstate)? |
285 *crate::dirstate::parsers::parse_dirstate_parents(&dirstate)? |
278 }; |
286 }; |
279 self.dirstate_parents.set(parents); |
287 self.dirstate_parents.set(parents); |
280 Ok(parents) |
288 Ok(parents) |
281 } |
289 } |
282 |
290 |
283 fn read_dirstate_data_file_uuid( |
291 /// Returns the information read from the dirstate docket necessary to |
284 &self, |
292 /// check if the data file has been updated/deleted by another process |
285 ) -> Result<Option<Vec<u8>>, HgError> { |
293 /// since we last read the dirstate. |
|
294 /// Namely, the inode, data file uuid and the data size. |
|
295 fn get_dirstate_data_file_integrity( |
|
296 &self, |
|
297 ) -> Result<DirstateMapIdentity, HgError> { |
286 assert!( |
298 assert!( |
287 self.has_dirstate_v2(), |
299 self.has_dirstate_v2(), |
288 "accessing dirstate data file ID without dirstate-v2" |
300 "accessing dirstate data file ID without dirstate-v2" |
289 ); |
301 ); |
|
302 // Get the identity before the contents since we could have a race |
|
303 // between the two. Having an identity that is too old is fine, but |
|
304 // one that is younger than the content change is bad. |
|
305 let identity = self.dirstate_identity()?; |
290 let dirstate = self.dirstate_file_contents()?; |
306 let dirstate = self.dirstate_file_contents()?; |
291 if dirstate.is_empty() { |
307 if dirstate.is_empty() { |
292 self.dirstate_parents.set(DirstateParents::NULL); |
308 self.dirstate_parents.set(DirstateParents::NULL); |
293 Ok(None) |
309 Ok((identity, None, 0)) |
294 } else { |
310 } else { |
295 let docket = |
311 let docket = |
296 crate::dirstate_tree::on_disk::read_docket(&dirstate)?; |
312 crate::dirstate_tree::on_disk::read_docket(&dirstate)?; |
297 self.dirstate_parents.set(docket.parents()); |
313 self.dirstate_parents.set(docket.parents()); |
298 Ok(Some(docket.uuid.to_owned())) |
314 Ok((identity, Some(docket.uuid.to_owned()), docket.data_size())) |
299 } |
315 } |
300 } |
316 } |
301 |
317 |
302 fn new_dirstate_map(&self) -> Result<OwningDirstateMap, DirstateError> { |
318 fn new_dirstate_map(&self) -> Result<OwningDirstateMap, DirstateError> { |
|
319 if self.has_dirstate_v2() { |
|
320 // The v2 dirstate is split into a docket and a data file. |
|
321 // Since we don't always take the `wlock` to read it |
|
322 // (like in `hg status`), it is susceptible to races. |
|
323 // A simple retry method should be enough since full rewrites |
|
324 // only happen when too much garbage data is present and |
|
325 // this race is unlikely. |
|
326 let mut tries = 0; |
|
327 |
|
328 while tries < V2_MAX_READ_ATTEMPTS { |
|
329 tries += 1; |
|
330 match self.read_docket_and_data_file() { |
|
331 Ok(m) => { |
|
332 return Ok(m); |
|
333 } |
|
334 Err(e) => match e { |
|
335 DirstateError::Common(HgError::RaceDetected( |
|
336 context, |
|
337 )) => { |
|
338 log::info!( |
|
339 "dirstate read race detected {} (retry {}/{})", |
|
340 context, |
|
341 tries, |
|
342 V2_MAX_READ_ATTEMPTS, |
|
343 ); |
|
344 continue; |
|
345 } |
|
346 _ => return Err(e), |
|
347 }, |
|
348 } |
|
349 } |
|
350 let error = HgError::abort( |
|
351 format!("dirstate read race happened {tries} times in a row"), |
|
352 255, |
|
353 None, |
|
354 ); |
|
355 Err(DirstateError::Common(error)) |
|
356 } else { |
|
357 debug_wait_for_file_or_print( |
|
358 self.config(), |
|
359 "dirstate.pre-read-file", |
|
360 ); |
|
361 let identity = self.dirstate_identity()?; |
|
362 let dirstate_file_contents = self.dirstate_file_contents()?; |
|
363 if dirstate_file_contents.is_empty() { |
|
364 self.dirstate_parents.set(DirstateParents::NULL); |
|
365 Ok(OwningDirstateMap::new_empty(Vec::new())) |
|
366 } else { |
|
367 let (map, parents) = OwningDirstateMap::new_v1( |
|
368 dirstate_file_contents, |
|
369 identity, |
|
370 )?; |
|
371 self.dirstate_parents.set(parents); |
|
372 Ok(map) |
|
373 } |
|
374 } |
|
375 } |
|
376 |
|
377 fn read_docket_and_data_file( |
|
378 &self, |
|
379 ) -> Result<OwningDirstateMap, DirstateError> { |
|
380 debug_wait_for_file_or_print(self.config(), "dirstate.pre-read-file"); |
303 let dirstate_file_contents = self.dirstate_file_contents()?; |
381 let dirstate_file_contents = self.dirstate_file_contents()?; |
|
382 let identity = self.dirstate_identity()?; |
304 if dirstate_file_contents.is_empty() { |
383 if dirstate_file_contents.is_empty() { |
305 self.dirstate_parents.set(DirstateParents::NULL); |
384 self.dirstate_parents.set(DirstateParents::NULL); |
306 if self.has_dirstate_v2() { |
385 return Ok(OwningDirstateMap::new_empty(Vec::new())); |
307 self.dirstate_data_file_uuid.set(None); |
386 } |
308 } |
387 let docket = crate::dirstate_tree::on_disk::read_docket( |
309 Ok(OwningDirstateMap::new_empty(Vec::new())) |
388 &dirstate_file_contents, |
310 } else if self.has_dirstate_v2() { |
389 )?; |
311 let docket = crate::dirstate_tree::on_disk::read_docket( |
390 debug_wait_for_file_or_print( |
312 &dirstate_file_contents, |
391 self.config(), |
313 )?; |
392 "dirstate.post-docket-read-file", |
314 self.dirstate_parents.set(docket.parents()); |
393 ); |
315 self.dirstate_data_file_uuid |
394 self.dirstate_parents.set(docket.parents()); |
316 .set(Some(docket.uuid.to_owned())); |
395 let uuid = docket.uuid.to_owned(); |
317 let data_size = docket.data_size(); |
396 let data_size = docket.data_size(); |
318 let metadata = docket.tree_metadata(); |
397 |
319 if let Some(data_mmap) = self |
398 let context = "between reading dirstate docket and data file"; |
|
399 let race_error = HgError::RaceDetected(context.into()); |
|
400 let metadata = docket.tree_metadata(); |
|
401 |
|
402 let mut map = if crate::vfs::is_on_nfs_mount(docket.data_filename()) { |
|
403 // Don't mmap on NFS to prevent `SIGBUS` error on deletion |
|
404 let contents = self.hg_vfs().read(docket.data_filename()); |
|
405 let contents = match contents { |
|
406 Ok(c) => c, |
|
407 Err(HgError::IoError { error, context }) => { |
|
408 match error.raw_os_error().expect("real os error") { |
|
409 // 2 = ENOENT, No such file or directory |
|
410 // 116 = ESTALE, Stale NFS file handle |
|
411 // |
|
412 // TODO match on `error.kind()` when |
|
413 // `ErrorKind::StaleNetworkFileHandle` is stable. |
|
414 2 | 116 => { |
|
415 // Race where the data file was deleted right after |
|
416 // we read the docket, try again |
|
417 return Err(race_error.into()); |
|
418 } |
|
419 _ => { |
|
420 return Err( |
|
421 HgError::IoError { error, context }.into() |
|
422 ) |
|
423 } |
|
424 } |
|
425 } |
|
426 Err(e) => return Err(e.into()), |
|
427 }; |
|
428 OwningDirstateMap::new_v2( |
|
429 contents, data_size, metadata, uuid, identity, |
|
430 ) |
|
431 } else { |
|
432 match self |
320 .hg_vfs() |
433 .hg_vfs() |
321 .mmap_open(docket.data_filename()) |
434 .mmap_open(docket.data_filename()) |
322 .io_not_found_as_none()? |
435 .io_not_found_as_none() |
323 { |
436 { |
324 OwningDirstateMap::new_v2(data_mmap, data_size, metadata) |
437 Ok(Some(data_mmap)) => OwningDirstateMap::new_v2( |
325 } else { |
438 data_mmap, data_size, metadata, uuid, identity, |
326 OwningDirstateMap::new_v2(Vec::new(), data_size, metadata) |
439 ), |
327 } |
440 Ok(None) => { |
328 } else { |
441 // Race where the data file was deleted right after we |
329 let (map, parents) = |
442 // read the docket, try again |
330 OwningDirstateMap::new_v1(dirstate_file_contents)?; |
443 return Err(race_error.into()); |
331 self.dirstate_parents.set(parents); |
444 } |
332 Ok(map) |
445 Err(e) => return Err(e.into()), |
333 } |
446 } |
|
447 }?; |
|
448 |
|
449 let write_mode_config = self |
|
450 .config() |
|
451 .get_str(b"devel", b"dirstate.v2.data_update_mode") |
|
452 .unwrap_or(Some("auto")) |
|
453 .unwrap_or("auto"); // don't bother for devel options |
|
454 let write_mode = match write_mode_config { |
|
455 "auto" => DirstateMapWriteMode::Auto, |
|
456 "force-new" => DirstateMapWriteMode::ForceNewDataFile, |
|
457 "force-append" => DirstateMapWriteMode::ForceAppend, |
|
458 _ => DirstateMapWriteMode::Auto, |
|
459 }; |
|
460 |
|
461 map.with_dmap_mut(|m| m.set_write_mode(write_mode)); |
|
462 |
|
463 Ok(map) |
334 } |
464 } |
335 |
465 |
336 pub fn dirstate_map( |
466 pub fn dirstate_map( |
337 &self, |
467 &self, |
338 ) -> Result<Ref<OwningDirstateMap>, DirstateError> { |
468 ) -> Result<Ref<OwningDirstateMap>, DirstateError> { |
419 let map = self.dirstate_map()?; |
549 let map = self.dirstate_map()?; |
420 // TODO: Maintain a `DirstateMap::dirty` flag, and return early here if |
550 // TODO: Maintain a `DirstateMap::dirty` flag, and return early here if |
421 // it’s unset |
551 // it’s unset |
422 let parents = self.dirstate_parents()?; |
552 let parents = self.dirstate_parents()?; |
423 let (packed_dirstate, old_uuid_to_remove) = if self.has_dirstate_v2() { |
553 let (packed_dirstate, old_uuid_to_remove) = if self.has_dirstate_v2() { |
424 let uuid_opt = self |
554 let (identity, uuid, data_size) = |
425 .dirstate_data_file_uuid |
555 self.get_dirstate_data_file_integrity()?; |
426 .get_or_init(|| self.read_dirstate_data_file_uuid())?; |
556 let identity_changed = identity != map.old_identity(); |
427 let uuid_opt = uuid_opt.as_ref(); |
557 let uuid_changed = uuid.as_deref() != map.old_uuid(); |
428 let can_append = uuid_opt.is_some(); |
558 let data_length_changed = data_size != map.old_data_size(); |
|
559 |
|
560 if identity_changed || uuid_changed || data_length_changed { |
|
561 // If any of identity, uuid or length have changed since |
|
562 // last disk read, don't write. |
|
563 // This is fine because either we're in a command that doesn't |
|
564 // write anything too important (like `hg status`), or we're in |
|
565 // `hg add` and we're supposed to have taken the lock before |
|
566 // reading anyway. |
|
567 // |
|
568 // TODO complain loudly if we've changed anything important |
|
569 // without taking the lock. |
|
570 // (see `hg help config.format.use-dirstate-tracked-hint`) |
|
571 log::debug!( |
|
572 "dirstate has changed since last read, not updating." |
|
573 ); |
|
574 return Ok(()); |
|
575 } |
|
576 |
|
577 let uuid_opt = map.old_uuid(); |
|
578 let write_mode = if uuid_opt.is_some() { |
|
579 DirstateMapWriteMode::Auto |
|
580 } else { |
|
581 DirstateMapWriteMode::ForceNewDataFile |
|
582 }; |
429 let (data, tree_metadata, append, old_data_size) = |
583 let (data, tree_metadata, append, old_data_size) = |
430 map.pack_v2(can_append)?; |
584 map.pack_v2(write_mode)?; |
431 |
585 |
432 // Reuse the uuid, or generate a new one, keeping the old for |
586 // Reuse the uuid, or generate a new one, keeping the old for |
433 // deletion. |
587 // deletion. |
434 let (uuid, old_uuid) = match uuid_opt { |
588 let (uuid, old_uuid) = match uuid_opt { |
435 Some(uuid) => { |
589 Some(uuid) => { |