Skip to content

Commit d76847f

Browse files
committed
Improve metadata copying + cleanup
1 parent c9dfec1 commit d76847f

File tree

2 files changed

+48
-53
lines changed

2 files changed

+48
-53
lines changed

helix-stdx/src/faccess.rs

Lines changed: 2 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ mod imp {
2626
use super::*;
2727

2828
use rustix::fs::Access;
29-
use std::os::unix::fs::{MetadataExt, PermissionsExt};
29+
use std::os::unix::fs::MetadataExt;
3030

3131
pub fn access(p: &Path, mode: AccessMode) -> io::Result<()> {
3232
let mut imode = Access::empty();
@@ -69,34 +69,6 @@ mod imp {
6969
Ok(())
7070
}
7171

72-
pub fn copy_metadata(from: &Path, to: &Path) -> io::Result<()> {
73-
let from_meta = std::fs::metadata(from)?;
74-
let to_meta = std::fs::metadata(to)?;
75-
let from_gid = from_meta.gid();
76-
let to_gid = to_meta.gid();
77-
78-
let mut perms = from_meta.permissions();
79-
perms.set_mode(perms.mode() & 0o0777);
80-
if from_gid != to_gid && chown(to, None, Some(from_gid)).is_err() {
81-
let new_perms = (perms.mode() & 0o0707) | ((perms.mode() & 0o07) << 3);
82-
perms.set_mode(new_perms);
83-
}
84-
85-
#[cfg(target_os = "macos")]
86-
{
87-
use std::fs::{File, FileTimes};
88-
use std::os::macos::fs::FileTimesExt;
89-
90-
let to_file = File::options().write(true).open(to)?;
91-
let times = FileTimes::new().set_created(from_meta.created()?);
92-
to_file.set_times(times)?;
93-
}
94-
95-
std::fs::set_permissions(to, perms)?;
96-
97-
Ok(())
98-
}
99-
10072
pub fn hardlink_count(p: &Path) -> std::io::Result<u64> {
10173
let metadata = p.metadata()?;
10274
Ok(metadata.nlink())
@@ -604,6 +576,7 @@ pub fn readonly(p: &Path) -> bool {
604576
}
605577
}
606578

579+
#[cfg(windows)]
607580
pub fn copy_metadata(from: &Path, to: &Path) -> io::Result<()> {
608581
imp::copy_metadata(from, to)
609582
}

helix-view/src/document.rs

Lines changed: 46 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ use helix_core::syntax::config::LanguageServerFeature;
1515
use helix_core::text_annotations::{InlineAnnotation, Overlay};
1616
use helix_event::TaskController;
1717
use helix_lsp::util::lsp_pos_to_pos;
18-
use helix_stdx::faccess::{copy_metadata, readonly};
18+
use helix_stdx::faccess::readonly;
1919
use helix_vcs::{DiffHandle, DiffProviderRegistry};
2020
use once_cell::sync::OnceCell;
2121
use thiserror;
@@ -212,36 +212,56 @@ impl Backup {
212212

213213
let backup_path = if is_copy {
214214
let from_meta = std::fs::metadata(&path_).ok()?;
215-
let backup_path = builder
216-
.permissions(perms) // NEED_CHECK: Don't recall why I didn't do this in the original PR...
217-
.make_in(path_.parent()?, |backup| std::fs::copy(&path_, backup))
218-
.ok()?
219-
.into_temp_path();
215+
let (backup, backup_path) = builder.tempfile().ok()?.into_parts();
216+
std::fs::copy(&path_, &backup_path).ok()?;
220217

221218
#[cfg(unix)]
222219
{
223-
use std::os::unix::fs::{MetadataExt, PermissionsExt};
224-
225-
let mut perms = from_meta.permissions();
226-
227-
// Strip s-bit
228-
perms.set_mode(perms.mode() & 0o0777);
220+
use std::os::{
221+
fd::AsFd,
222+
unix::fs::{MetadataExt, PermissionsExt},
223+
};
229224

230225
let to_meta = std::fs::metadata(&backup_path).ok()?;
231226
let from_gid = from_meta.gid();
232227
let to_gid = to_meta.gid();
233228

229+
let mut perms = from_meta.permissions();
230+
perms.set_mode(perms.mode() & 0o0777); // Strip s-bit
231+
234232
// If chown fails, se the protection bits for the roup the same as the perm bits for others
235233
if from_gid != to_gid
236-
&& helix_stdx::faccess::chown(&backup_path, None, Some(from_gid)).is_err()
234+
&& helix_stdx::faccess::fchown(backup.as_fd(), None, Some(from_gid))
235+
.is_err()
237236
{
238237
let new_perms = (perms.mode() & 0o0707) | ((perms.mode() & 0o07) << 3);
239238
perms.set_mode(new_perms);
240239
}
240+
241+
#[cfg(target_os = "macos")]
242+
{
243+
use std::fs::{File, FileTimes};
244+
use std::os::macos::fs::FileTimesExt;
245+
246+
let to_file = File::options().write(true).open(&backup_path)?;
247+
let times = FileTimes::new().set_created(from_meta.created()?);
248+
to_file.set_times(times)?;
249+
}
250+
241251
std::fs::set_permissions(&backup_path, perms).ok()?;
242252
helix_stdx::faccess::copy_xattr(&path_, &backup_path).ok()?;
243253
}
244254

255+
#[cfg(windows)]
256+
{
257+
let backup_p_ = backup_path.clone();
258+
tokio::task::spawn_blocking(move || -> std::io::Result<()> {
259+
copy_metadata(&p, &backup_p_)?;
260+
Ok(())
261+
})
262+
.await??;
263+
}
264+
245265
let atime = FileTime::from_last_access_time(&from_meta);
246266
let mtime = FileTime::from_last_modification_time(&from_meta);
247267
filetime::set_file_times(&backup_path, atime, mtime).ok()?;
@@ -1221,16 +1241,22 @@ impl Document {
12211241
let from = path.clone();
12221242
let to = backup.path.clone();
12231243
tokio::task::spawn_blocking(move || -> Result<(), Error> {
1224-
helix_stdx::faccess::copy_ownership(&from, &to)?;
1244+
helix_stdx::faccess::copy_metadata(&from, &to)?;
12251245
Ok(())
12261246
})
12271247
.await??;
12281248
}
12291249

12301250
file
12311251
} else {
1232-
// INTEGRITY: Backup copy already exists
1233-
tokio::fs::File::create(&path).await?
1252+
// INTEGRITY: Backup copy already exists. But possible TOCTOU race w/ file being changed underneath
1253+
let mut open_opt = tokio::fs::File::options();
1254+
open_opt
1255+
.create(false)
1256+
.write(true)
1257+
.truncate(true)
1258+
.open(&path)
1259+
.await?
12341260
};
12351261
to_writer(&mut dst, encoding_with_bom_info, &text).await?;
12361262
dst.sync_all().await?;
@@ -1281,14 +1307,10 @@ impl Document {
12811307
filetime::set_file_times(&path, atime, mtime)?;
12821308
}
12831309
} else {
1284-
// copy metadata and delete backup
1285-
let _ = tokio::task::spawn_blocking(move || {
1286-
let _ = copy_metadata(&backup.path, &write_path)
1287-
.map_err(|e| log::error!("Failed to copy metadata on write: {e}"));
1288-
let _ = std::fs::remove_file(backup.path)
1289-
.map_err(|e| log::error!("Failed to remove backup file on write: {e}"));
1290-
})
1291-
.await;
1310+
// We're done; delete backup
1311+
let _ = tokio::fs::remove_file(backup.path)
1312+
.await
1313+
.map_err(|e| log::error!("Failed to remove backup file on write: {e}"));
12921314
}
12931315
}
12941316

0 commit comments

Comments
 (0)