160160
161161use std:: borrow:: Cow ;
162162use std:: collections:: BTreeMap ;
163- use std:: fs :: File ;
163+ use std:: io :: Write ;
164164use std:: path:: { Path , PathBuf } ;
165165
166166use flate2:: read:: GzDecoder ;
@@ -179,6 +179,7 @@ use crate::util::to_url::ToUrl;
179179use crate :: util:: { internal, CargoResult , Config , FileLock , Filesystem } ;
180180
181181const INDEX_LOCK : & str = ".cargo-index-lock" ;
182+ const PACKAGE_SOURCE_LOCK : & str = ".cargo-ok" ;
182183pub const CRATES_IO_INDEX : & str = "https://github.com/rust-lang/crates.io-index" ;
183184pub const CRATES_IO_REGISTRY : & str = "crates-io" ;
184185const CRATE_TEMPLATE : & str = "{crate}" ;
@@ -426,23 +427,40 @@ impl<'cfg> RegistrySource<'cfg> {
426427 ///
427428 /// No action is taken if the source looks like it's already unpacked.
428429 fn unpack_package ( & self , pkg : PackageId , tarball : & FileLock ) -> CargoResult < PathBuf > {
429- let dst = self
430- . src_path
431- . join ( & format ! ( "{}-{}" , pkg. name( ) , pkg. version( ) ) ) ;
432- dst. create_dir ( ) ?;
433- // Note that we've already got the `tarball` locked above, and that
434- // implies a lock on the unpacked destination as well, so this access
435- // via `into_path_unlocked` should be ok.
436- let dst = dst. into_path_unlocked ( ) ;
437- let ok = dst. join ( ".cargo-ok" ) ;
438- if ok. exists ( ) {
439- return Ok ( dst) ;
430+ // The `.cargo-ok` file is used to track if the source is already
431+ // unpacked and to lock the directory for unpacking.
432+ let mut ok = {
433+ let package_dir = format ! ( "{}-{}" , pkg. name( ) , pkg. version( ) ) ;
434+ let dst = self
435+ . src_path
436+ . join ( & package_dir) ;
437+ dst. create_dir ( ) ?;
438+
439+ // Attempt to open a read-only copy first to avoid an exclusive write
440+ // lock and also work with read-only filesystems. If the file has
441+ // any data, assume the source is already unpacked.
442+ if let Ok ( ok) = dst. open_ro ( PACKAGE_SOURCE_LOCK , self . config , & package_dir) {
443+ let meta = ok. file ( ) . metadata ( ) ?;
444+ if meta. len ( ) > 0 {
445+ let unpack_dir = ok. parent ( ) . to_path_buf ( ) ;
446+ return Ok ( unpack_dir) ;
447+ }
448+ }
449+
450+ dst. open_rw ( PACKAGE_SOURCE_LOCK , self . config , & package_dir) ?
451+ } ;
452+ let unpack_dir = ok. parent ( ) . to_path_buf ( ) ;
453+
454+ // If the file has any data, assume the source is already unpacked.
455+ let meta = ok. file ( ) . metadata ( ) ?;
456+ if meta. len ( ) > 0 {
457+ return Ok ( unpack_dir) ;
440458 }
441459
442460 let gz = GzDecoder :: new ( tarball. file ( ) ) ;
443461 let mut tar = Archive :: new ( gz) ;
444- let prefix = dst . file_name ( ) . unwrap ( ) ;
445- let parent = dst . parent ( ) . unwrap ( ) ;
462+ let prefix = unpack_dir . file_name ( ) . unwrap ( ) ;
463+ let parent = unpack_dir . parent ( ) . unwrap ( ) ;
446464 for entry in tar. entries ( ) ? {
447465 let mut entry = entry. chain_err ( || "failed to iterate over archive" ) ?;
448466 let entry_path = entry
@@ -470,8 +488,11 @@ impl<'cfg> RegistrySource<'cfg> {
470488 . unpack_in ( parent)
471489 . chain_err ( || format ! ( "failed to unpack entry at `{}`" , entry_path. display( ) ) ) ?;
472490 }
473- File :: create ( & ok) ?;
474- Ok ( dst)
491+
492+ // Write to the lock file to indicate that unpacking was successful.
493+ write ! ( ok, "ok" ) ?;
494+
495+ Ok ( unpack_dir)
475496 }
476497
477498 fn do_update ( & mut self ) -> CargoResult < ( ) > {
0 commit comments