Auto merge of #45899 - eddyb:meta-race, r=alexcrichton

rustc_trans: atomically write .rmeta outputs to avoid races.

Fixes #45841 in a similar vein to how LLVM writes archives: write a temporary file and then rename it.

r? @alexcrichton
This commit is contained in:
bors 2017-11-18 00:44:58 +00:00
commit 8752aeed3a

View File

@ -262,19 +262,31 @@ fn link_binary_output(sess: &Session,
check_file_is_writeable(obj, sess); check_file_is_writeable(obj, sess);
} }
let tmpdir = match TempDir::new("rustc") {
Ok(tmpdir) => tmpdir,
Err(err) => sess.fatal(&format!("couldn't create a temp dir: {}", err)),
};
let mut out_filenames = vec![]; let mut out_filenames = vec![];
if outputs.outputs.contains_key(&OutputType::Metadata) { if outputs.outputs.contains_key(&OutputType::Metadata) {
let out_filename = filename_for_metadata(sess, crate_name, outputs); let out_filename = filename_for_metadata(sess, crate_name, outputs);
emit_metadata(sess, trans, &out_filename); // To avoid races with another rustc process scanning the output directory,
// we need to write the file somewhere else and atomically move it to its
// final destination, with a `fs::rename` call. In order for the rename to
// always succeed, the temporary file needs to be on the same filesystem,
// which is why we create it inside the output directory specifically.
let metadata_tmpdir = match TempDir::new_in(out_filename.parent().unwrap(), "rmeta") {
Ok(tmpdir) => tmpdir,
Err(err) => sess.fatal(&format!("couldn't create a temp dir: {}", err)),
};
let metadata = emit_metadata(sess, trans, &metadata_tmpdir);
if let Err(e) = fs::rename(metadata, &out_filename) {
sess.fatal(&format!("failed to write {}: {}", out_filename.display(), e));
}
out_filenames.push(out_filename); out_filenames.push(out_filename);
} }
let tmpdir = match TempDir::new("rustc") {
Ok(tmpdir) => tmpdir,
Err(err) => sess.fatal(&format!("couldn't create a temp dir: {}", err)),
};
if outputs.outputs.should_trans() { if outputs.outputs.should_trans() {
let out_filename = out_filename(sess, crate_type, outputs, crate_name); let out_filename = out_filename(sess, crate_type, outputs, crate_name);
match crate_type { match crate_type {
@ -283,10 +295,10 @@ fn link_binary_output(sess: &Session,
trans, trans,
RlibFlavor::Normal, RlibFlavor::Normal,
&out_filename, &out_filename,
tmpdir.path()).build(); &tmpdir).build();
} }
config::CrateTypeStaticlib => { config::CrateTypeStaticlib => {
link_staticlib(sess, trans, &out_filename, tmpdir.path()); link_staticlib(sess, trans, &out_filename, &tmpdir);
} }
_ => { _ => {
link_natively(sess, crate_type, &out_filename, trans, tmpdir.path()); link_natively(sess, crate_type, &out_filename, trans, tmpdir.path());
@ -321,14 +333,23 @@ fn archive_config<'a>(sess: &'a Session,
} }
} }
fn emit_metadata<'a>(sess: &'a Session, trans: &CrateTranslation, out_filename: &Path) { /// We use a temp directory here to avoid races between concurrent rustc processes,
let result = fs::File::create(out_filename).and_then(|mut f| { /// such as builds in the same directory using the same filename for metadata while
/// building an `.rlib` (stomping over one another), or writing an `.rmeta` into a
/// directory being searched for `extern crate` (observing an incomplete file).
/// The returned path is the temporary file containing the complete metadata.
fn emit_metadata<'a>(sess: &'a Session, trans: &CrateTranslation, tmpdir: &TempDir)
-> PathBuf {
let out_filename = tmpdir.path().join(METADATA_FILENAME);
let result = fs::File::create(&out_filename).and_then(|mut f| {
f.write_all(&trans.metadata.raw_data) f.write_all(&trans.metadata.raw_data)
}); });
if let Err(e) = result { if let Err(e) = result {
sess.fatal(&format!("failed to write {}: {}", out_filename.display(), e)); sess.fatal(&format!("failed to write {}: {}", out_filename.display(), e));
} }
out_filename
} }
enum RlibFlavor { enum RlibFlavor {
@ -346,7 +367,7 @@ fn link_rlib<'a>(sess: &'a Session,
trans: &CrateTranslation, trans: &CrateTranslation,
flavor: RlibFlavor, flavor: RlibFlavor,
out_filename: &Path, out_filename: &Path,
tmpdir: &Path) -> ArchiveBuilder<'a> { tmpdir: &TempDir) -> ArchiveBuilder<'a> {
info!("preparing rlib to {:?}", out_filename); info!("preparing rlib to {:?}", out_filename);
let mut ab = ArchiveBuilder::new(archive_config(sess, out_filename, None)); let mut ab = ArchiveBuilder::new(archive_config(sess, out_filename, None));
@ -408,12 +429,8 @@ fn link_rlib<'a>(sess: &'a Session,
match flavor { match flavor {
RlibFlavor::Normal => { RlibFlavor::Normal => {
// Instead of putting the metadata in an object file section, rlibs // Instead of putting the metadata in an object file section, rlibs
// contain the metadata in a separate file. We use a temp directory // contain the metadata in a separate file.
// here so concurrent builds in the same directory don't try to use ab.add_file(&emit_metadata(sess, trans, tmpdir));
// the same filename for metadata (stomping over one another)
let metadata = tmpdir.join(METADATA_FILENAME);
emit_metadata(sess, trans, &metadata);
ab.add_file(&metadata);
// For LTO purposes, the bytecode of this library is also inserted // For LTO purposes, the bytecode of this library is also inserted
// into the archive. // into the archive.
@ -457,7 +474,7 @@ fn link_rlib<'a>(sess: &'a Session,
fn link_staticlib(sess: &Session, fn link_staticlib(sess: &Session,
trans: &CrateTranslation, trans: &CrateTranslation,
out_filename: &Path, out_filename: &Path,
tempdir: &Path) { tempdir: &TempDir) {
let mut ab = link_rlib(sess, let mut ab = link_rlib(sess,
trans, trans,
RlibFlavor::StaticlibBase, RlibFlavor::StaticlibBase,