Skip to content

Commit

Permalink
fix(util): preserve some file permissions during write_atomic
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
stevenengler committed May 10, 2024
1 parent 1f354a6 commit 9103faf
Showing 1 changed file with 42 additions and 1 deletion.
43 changes: 42 additions & 1 deletion crates/cargo-util/src/paths.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ use anyhow::{Context, Result};
use filetime::FileTime;
use std::env;
use std::ffi::{OsStr, OsString};
use std::fs::{self, File, Metadata, OpenOptions};
use std::fs::{self, File, Metadata, OpenOptions, Permissions};
use std::io;
use std::io::prelude::*;
use std::iter;
Expand Down Expand Up @@ -185,10 +185,51 @@ pub fn write<P: AsRef<Path>, C: AsRef<[u8]>>(path: P, contents: C) -> Result<()>
/// write_atomic uses tempfile::persist to accomplish atomic writes.
pub fn write_atomic<P: AsRef<Path>, C: AsRef<[u8]>>(path: P, contents: C) -> Result<()> {
let path = path.as_ref();

// On unix platforms, get the permissions of the original file. Copy only the user/group/other
// read/write/execute permission bits. The tempfile lib defaults to an initial mode of 0o600,
// and we'll set the proper permissions after creating the file.
#[cfg(unix)]
let perms = path.metadata().ok().map(|meta| {
use std::os::unix::fs::PermissionsExt;

// these constants are u16 on macOS
let mask = u32::from(libc::S_IRWXU | libc::S_IRWXG | libc::S_IRWXO);
let mode = meta.permissions().mode() & mask;

Permissions::from_mode(mode)
});

// On non-unix platforms, get the "readonly" permission of the original file.
#[cfg(not(unix))]
let readonly = path
.metadata()
.ok()
.map(|meta| meta.permissions().readonly());

let mut tmp = TempFileBuilder::new()
.prefix(path.file_name().unwrap())
.tempfile_in(path.parent().unwrap())?;
tmp.write_all(contents.as_ref())?;

// On unix platforms, set the permissions on the newly created file. We can use fchmod (called
// by the std lib; subject to change) which ignores the umask so that the new file has the same
// permissions as the old file.
#[cfg(unix)]
if let Some(perms) = perms {
tmp.as_file().set_permissions(perms)?;
}

// On non-unix platforms, update the readonly permissions on the newly created file.
#[cfg(not(unix))]
if let Some(readonly) = readonly {
if let Ok(metadata) = tmp.as_file().metadata() {
let mut perms = metadata.permissions();
perms.set_readonly(readonly);
tmp.as_file().set_permissions(perms)?;
}
}

tmp.persist(path)?;
Ok(())
}
Expand Down

0 comments on commit 9103faf

Please sign in to comment.