Skip to content

Commit 9103faf

Browse files
committed
fix(util): preserve some file permissions during write_atomic
Preseves u/g/o r/w/x permissions on unix platforms, and the "readonly" property on non-unix platforms. Fixes the two unit tests added in the previous commit.
1 parent 1f354a6 commit 9103faf

File tree

1 file changed

+42
-1
lines changed

1 file changed

+42
-1
lines changed

crates/cargo-util/src/paths.rs

+42-1
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ use anyhow::{Context, Result};
44
use filetime::FileTime;
55
use std::env;
66
use std::ffi::{OsStr, OsString};
7-
use std::fs::{self, File, Metadata, OpenOptions};
7+
use std::fs::{self, File, Metadata, OpenOptions, Permissions};
88
use std::io;
99
use std::io::prelude::*;
1010
use std::iter;
@@ -185,10 +185,51 @@ pub fn write<P: AsRef<Path>, C: AsRef<[u8]>>(path: P, contents: C) -> Result<()>
185185
/// write_atomic uses tempfile::persist to accomplish atomic writes.
186186
pub fn write_atomic<P: AsRef<Path>, C: AsRef<[u8]>>(path: P, contents: C) -> Result<()> {
187187
let path = path.as_ref();
188+
189+
// On unix platforms, get the permissions of the original file. Copy only the user/group/other
190+
// read/write/execute permission bits. The tempfile lib defaults to an initial mode of 0o600,
191+
// and we'll set the proper permissions after creating the file.
192+
#[cfg(unix)]
193+
let perms = path.metadata().ok().map(|meta| {
194+
use std::os::unix::fs::PermissionsExt;
195+
196+
// these constants are u16 on macOS
197+
let mask = u32::from(libc::S_IRWXU | libc::S_IRWXG | libc::S_IRWXO);
198+
let mode = meta.permissions().mode() & mask;
199+
200+
Permissions::from_mode(mode)
201+
});
202+
203+
// On non-unix platforms, get the "readonly" permission of the original file.
204+
#[cfg(not(unix))]
205+
let readonly = path
206+
.metadata()
207+
.ok()
208+
.map(|meta| meta.permissions().readonly());
209+
188210
let mut tmp = TempFileBuilder::new()
189211
.prefix(path.file_name().unwrap())
190212
.tempfile_in(path.parent().unwrap())?;
191213
tmp.write_all(contents.as_ref())?;
214+
215+
// On unix platforms, set the permissions on the newly created file. We can use fchmod (called
216+
// by the std lib; subject to change) which ignores the umask so that the new file has the same
217+
// permissions as the old file.
218+
#[cfg(unix)]
219+
if let Some(perms) = perms {
220+
tmp.as_file().set_permissions(perms)?;
221+
}
222+
223+
// On non-unix platforms, update the readonly permissions on the newly created file.
224+
#[cfg(not(unix))]
225+
if let Some(readonly) = readonly {
226+
if let Ok(metadata) = tmp.as_file().metadata() {
227+
let mut perms = metadata.permissions();
228+
perms.set_readonly(readonly);
229+
tmp.as_file().set_permissions(perms)?;
230+
}
231+
}
232+
192233
tmp.persist(path)?;
193234
Ok(())
194235
}

0 commit comments

Comments
 (0)