Skip to content

Commit 8752aee

Browse files
committed
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
2 parents 18d8acf + f595280 commit 8752aee

File tree

1 file changed

+35
-18
lines changed

1 file changed

+35
-18
lines changed

src/librustc_trans/back/link.rs

Lines changed: 35 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -262,19 +262,31 @@ fn link_binary_output(sess: &Session,
262262
check_file_is_writeable(obj, sess);
263263
}
264264

265-
let tmpdir = match TempDir::new("rustc") {
266-
Ok(tmpdir) => tmpdir,
267-
Err(err) => sess.fatal(&format!("couldn't create a temp dir: {}", err)),
268-
};
269-
270265
let mut out_filenames = vec![];
271266

272267
if outputs.outputs.contains_key(&OutputType::Metadata) {
273268
let out_filename = filename_for_metadata(sess, crate_name, outputs);
274-
emit_metadata(sess, trans, &out_filename);
269+
// To avoid races with another rustc process scanning the output directory,
270+
// we need to write the file somewhere else and atomically move it to its
271+
// final destination, with a `fs::rename` call. In order for the rename to
272+
// always succeed, the temporary file needs to be on the same filesystem,
273+
// which is why we create it inside the output directory specifically.
274+
let metadata_tmpdir = match TempDir::new_in(out_filename.parent().unwrap(), "rmeta") {
275+
Ok(tmpdir) => tmpdir,
276+
Err(err) => sess.fatal(&format!("couldn't create a temp dir: {}", err)),
277+
};
278+
let metadata = emit_metadata(sess, trans, &metadata_tmpdir);
279+
if let Err(e) = fs::rename(metadata, &out_filename) {
280+
sess.fatal(&format!("failed to write {}: {}", out_filename.display(), e));
281+
}
275282
out_filenames.push(out_filename);
276283
}
277284

285+
let tmpdir = match TempDir::new("rustc") {
286+
Ok(tmpdir) => tmpdir,
287+
Err(err) => sess.fatal(&format!("couldn't create a temp dir: {}", err)),
288+
};
289+
278290
if outputs.outputs.should_trans() {
279291
let out_filename = out_filename(sess, crate_type, outputs, crate_name);
280292
match crate_type {
@@ -283,10 +295,10 @@ fn link_binary_output(sess: &Session,
283295
trans,
284296
RlibFlavor::Normal,
285297
&out_filename,
286-
tmpdir.path()).build();
298+
&tmpdir).build();
287299
}
288300
config::CrateTypeStaticlib => {
289-
link_staticlib(sess, trans, &out_filename, tmpdir.path());
301+
link_staticlib(sess, trans, &out_filename, &tmpdir);
290302
}
291303
_ => {
292304
link_natively(sess, crate_type, &out_filename, trans, tmpdir.path());
@@ -321,14 +333,23 @@ fn archive_config<'a>(sess: &'a Session,
321333
}
322334
}
323335

324-
fn emit_metadata<'a>(sess: &'a Session, trans: &CrateTranslation, out_filename: &Path) {
325-
let result = fs::File::create(out_filename).and_then(|mut f| {
336+
/// We use a temp directory here to avoid races between concurrent rustc processes,
337+
/// such as builds in the same directory using the same filename for metadata while
338+
/// building an `.rlib` (stomping over one another), or writing an `.rmeta` into a
339+
/// directory being searched for `extern crate` (observing an incomplete file).
340+
/// The returned path is the temporary file containing the complete metadata.
341+
fn emit_metadata<'a>(sess: &'a Session, trans: &CrateTranslation, tmpdir: &TempDir)
342+
-> PathBuf {
343+
let out_filename = tmpdir.path().join(METADATA_FILENAME);
344+
let result = fs::File::create(&out_filename).and_then(|mut f| {
326345
f.write_all(&trans.metadata.raw_data)
327346
});
328347

329348
if let Err(e) = result {
330349
sess.fatal(&format!("failed to write {}: {}", out_filename.display(), e));
331350
}
351+
352+
out_filename
332353
}
333354

334355
enum RlibFlavor {
@@ -346,7 +367,7 @@ fn link_rlib<'a>(sess: &'a Session,
346367
trans: &CrateTranslation,
347368
flavor: RlibFlavor,
348369
out_filename: &Path,
349-
tmpdir: &Path) -> ArchiveBuilder<'a> {
370+
tmpdir: &TempDir) -> ArchiveBuilder<'a> {
350371
info!("preparing rlib to {:?}", out_filename);
351372
let mut ab = ArchiveBuilder::new(archive_config(sess, out_filename, None));
352373

@@ -408,12 +429,8 @@ fn link_rlib<'a>(sess: &'a Session,
408429
match flavor {
409430
RlibFlavor::Normal => {
410431
// Instead of putting the metadata in an object file section, rlibs
411-
// contain the metadata in a separate file. We use a temp directory
412-
// here so concurrent builds in the same directory don't try to use
413-
// the same filename for metadata (stomping over one another)
414-
let metadata = tmpdir.join(METADATA_FILENAME);
415-
emit_metadata(sess, trans, &metadata);
416-
ab.add_file(&metadata);
432+
// contain the metadata in a separate file.
433+
ab.add_file(&emit_metadata(sess, trans, tmpdir));
417434

418435
// For LTO purposes, the bytecode of this library is also inserted
419436
// into the archive.
@@ -457,7 +474,7 @@ fn link_rlib<'a>(sess: &'a Session,
457474
fn link_staticlib(sess: &Session,
458475
trans: &CrateTranslation,
459476
out_filename: &Path,
460-
tempdir: &Path) {
477+
tempdir: &TempDir) {
461478
let mut ab = link_rlib(sess,
462479
trans,
463480
RlibFlavor::StaticlibBase,

0 commit comments

Comments
 (0)