Skip to content

Commit 9c1f741

Browse files
authored
fix(compile): atomically write compile output (#24378)
Atomically write `deno compile` output file so we won't get a partially written ELF/PE file, and prevents corrupting running processes.
1 parent a555cb4 commit 9c1f741

File tree

1 file changed

+42
-11
lines changed

1 file changed

+42
-11
lines changed

cli/tools/compile.rs

Lines changed: 42 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ use deno_core::error::AnyError;
1212
use deno_core::resolve_url_or_path;
1313
use deno_graph::GraphKind;
1414
use deno_terminal::colors;
15+
use rand::Rng;
1516
use std::path::Path;
1617
use std::path::PathBuf;
1718
use std::sync::Arc;
@@ -97,8 +98,20 @@ pub async fn compile(
9798
);
9899
validate_output_path(&output_path)?;
99100

100-
let mut file = std::fs::File::create(&output_path)
101-
.with_context(|| format!("Opening file '{}'", output_path.display()))?;
101+
let mut temp_filename = output_path.file_name().unwrap().to_owned();
102+
temp_filename.push(format!(
103+
".tmp-{}",
104+
faster_hex::hex_encode(
105+
&rand::thread_rng().gen::<[u8; 8]>(),
106+
&mut [0u8; 16]
107+
)
108+
.unwrap()
109+
));
110+
let temp_path = output_path.with_file_name(temp_filename);
111+
112+
let mut file = std::fs::File::create(&temp_path).with_context(|| {
113+
format!("Opening temporary file '{}'", temp_path.display())
114+
})?;
102115
let write_result = binary_writer
103116
.write_bin(
104117
&mut file,
@@ -108,20 +121,38 @@ pub async fn compile(
108121
cli_options,
109122
)
110123
.await
111-
.with_context(|| format!("Writing {}", output_path.display()));
124+
.with_context(|| {
125+
format!("Writing temporary file '{}'", temp_path.display())
126+
});
112127
drop(file);
113-
if let Err(err) = write_result {
114-
// errored, so attempt to remove the output path
115-
let _ = std::fs::remove_file(output_path);
116-
return Err(err);
117-
}
118128

119129
// set it as executable
120130
#[cfg(unix)]
121-
{
131+
let write_result = write_result.and_then(|_| {
122132
use std::os::unix::fs::PermissionsExt;
123-
let perms = std::fs::Permissions::from_mode(0o777);
124-
std::fs::set_permissions(output_path, perms)?;
133+
let perms = std::fs::Permissions::from_mode(0o755);
134+
std::fs::set_permissions(&temp_path, perms).with_context(|| {
135+
format!(
136+
"Setting permissions on temporary file '{}'",
137+
temp_path.display()
138+
)
139+
})
140+
});
141+
142+
let write_result = write_result.and_then(|_| {
143+
std::fs::rename(&temp_path, &output_path).with_context(|| {
144+
format!(
145+
"Renaming temporary file '{}' to '{}'",
146+
temp_path.display(),
147+
output_path.display()
148+
)
149+
})
150+
});
151+
152+
if let Err(err) = write_result {
153+
// errored, so attempt to remove the temporary file
154+
let _ = std::fs::remove_file(temp_path);
155+
return Err(err);
125156
}
126157

127158
Ok(())

0 commit comments

Comments
 (0)