From 1329572d8df134a4e4eb2cbd5d65d74691921ff8 Mon Sep 17 00:00:00 2001 From: kirawi <67773714+kirawi@users.noreply.github.com> Date: Sun, 17 Nov 2024 00:27:52 -0500 Subject: [PATCH] copy xattr --- helix-loader/src/lib.rs | 2 +- helix-stdx/src/faccess.rs | 81 +++++++++++++++++++++++++++++++------- helix-view/src/document.rs | 34 ++++++++++++++-- 3 files changed, 98 insertions(+), 19 deletions(-) diff --git a/helix-loader/src/lib.rs b/helix-loader/src/lib.rs index 3d754cb89..ad715a112 100644 --- a/helix-loader/src/lib.rs +++ b/helix-loader/src/lib.rs @@ -136,7 +136,7 @@ pub fn state_dir() -> PathBuf { #[cfg(unix)] { let strategy = choose_base_strategy().expect("Unable to find the state directory!"); - let mut path = strategy.state_dir(); + let mut path = strategy.state_dir().unwrap(); path.push("helix"); path } diff --git a/helix-stdx/src/faccess.rs b/helix-stdx/src/faccess.rs index 1b3057679..c3be68ac1 100644 --- a/helix-stdx/src/faccess.rs +++ b/helix-stdx/src/faccess.rs @@ -54,20 +54,6 @@ mod imp { Ok(()) } - pub fn chown(p: &Path, uid: Option, gid: Option) -> io::Result<()> { - let uid = uid.map(|n| unsafe { rustix::fs::Uid::from_raw(n) }); - let gid = gid.map(|n| unsafe { rustix::fs::Gid::from_raw(n) }); - rustix::fs::chown(p, uid, gid)?; - Ok(()) - } - - pub fn fchown(fd: impl AsFd, uid: Option, gid: Option) -> io::Result<()> { - let uid = uid.map(|n| unsafe { rustix::fs::Uid::from_raw(n) }); - let gid = gid.map(|n| unsafe { rustix::fs::Gid::from_raw(n) }); - rustix::fs::fchown(fd, uid, gid)?; - Ok(()) - } - pub fn copy_metadata(from: &Path, to: &Path) -> io::Result<()> { let from_meta = std::fs::metadata(from)?; let to_meta = std::fs::metadata(to)?; @@ -547,6 +533,73 @@ pub fn copy_ownership(from: &Path, to: &Path) -> io::Result<()> { imp::copy_ownership(from, to) } +#[cfg(unix)] +pub fn chown(p: &Path, uid: Option, gid: Option) -> io::Result<()> { + let uid = uid.map(|n| unsafe { rustix::fs::Uid::from_raw(n) }); + let gid = gid.map(|n| unsafe { rustix::fs::Gid::from_raw(n) }); + rustix::fs::chown(p, uid, gid)?; + Ok(()) +} + +#[cfg(unix)] +pub fn fchown(fd: impl std::os::fd::AsFd, uid: Option, gid: Option) -> io::Result<()> { + let uid = uid.map(|n| unsafe { rustix::fs::Uid::from_raw(n) }); + let gid = gid.map(|n| unsafe { rustix::fs::Gid::from_raw(n) }); + rustix::fs::fchown(fd, uid, gid)?; + Ok(()) +} + +#[cfg(unix)] +pub fn copy_xattr(from: &Path, to: &Path) -> io::Result<()> { + let size = match rustix::fs::listxattr(from, &mut [])? { + 0 => return Ok(()), // No attributes + len => len, + }; + + let mut buf = vec![0i8; size]; + let read = rustix::fs::listxattr(from, &mut buf)?; + + fn i8_to_u8_slice(input: &[i8]) -> &[u8] { + // SAFETY: Simply reinterprets bytes + unsafe { std::slice::from_raw_parts(input.as_ptr() as *const u8, input.len()) } + } + + // Iterate over null-terminated C-style strings + // Two loops to avoid multiple allocations + // Find max-size for attributes + let mut max_attr_len = 0; + for attr_byte in buf.split(|&b| b == 0) { + let name = std::str::from_utf8(i8_to_u8_slice(attr_byte)) + .map_err(|_| std::io::Error::from(std::io::ErrorKind::InvalidData))?; + let attr_len = rustix::fs::getxattr(from, name, &mut [])?; + max_attr_len = max_attr_len.max(attr_len); + } + + let mut attr_buf = vec![0u8; max_attr_len]; + for attr_byte in buf.split(|&b| b == 0) { + let name = std::str::from_utf8(i8_to_u8_slice(attr_byte)) + .map_err(|_| std::io::Error::from(std::io::ErrorKind::InvalidData))?; + let read = rustix::fs::getxattr(from, name, &mut attr_buf)?; + + // If we can't set xattr because it already exists, try to replace it + if read != 0 { + match rustix::fs::setxattr(to, name, &attr_buf[..read], rustix::fs::XattrFlags::CREATE) + { + Err(rustix::io::Errno::EXIST) => rustix::fs::setxattr( + to, + name, + &attr_buf[..read], + rustix::fs::XattrFlags::REPLACE, + )?, + Err(e) => return Err(e.into()), + _ => {} + } + } + } + + Ok(()) +} + /* Neovim backup path function: - If a backup is desired (would be disabled by a user if using a file watcher): diff --git a/helix-view/src/document.rs b/helix-view/src/document.rs index 1b6771f71..3cd2b3264 100644 --- a/helix-view/src/document.rs +++ b/helix-view/src/document.rs @@ -23,6 +23,7 @@ use std::collections::HashMap; use std::fmt::Display; use std::future::Future; use std::io; +use std::os::unix::fs::MetadataExt; use std::path::{Path, PathBuf}; use std::str::FromStr; use std::sync::{Arc, Weak}; @@ -169,6 +170,27 @@ impl Backup { // builder.permissions() if let Ok(f) = builder.tempfile() { // Check if we have perms to set perms + #[cfg(unix)] + { + use std::os::{fd::AsFd, unix::fs::MetadataExt}; + + let from_meta = tokio::fs::metadata(&p).await?; + let to_meta = tokio::fs::metadata(&f.path()).await?; + let _ = fchown( + f.as_file().as_fd(), + Some(from_meta.uid()), + Some(from_meta.gid()), + ); + + if from_meta.uid() != to_meta.uid() + || from_meta.gid() != to_meta.gid() + || from_meta.permissions() != to_meta.permissions() + { + copy = true; + } + } + + #[cfg(not(unix))] if copy_metadata(&p, f.path()).is_err() { copy = true; } @@ -209,23 +231,26 @@ impl Backup { #[cfg(unix)] { - let mut meta = tokio::fs::metadata(&p).await?; - let mut perms = meta.permissions(); + use std::os::unix::fs::{MetadataExt, PermissionsExt}; + + let mut from_meta = tokio::fs::metadata(&p).await?; + let mut perms = from_meta.permissions(); // Strip s-bit perms.set_mode(perms.mode() & 0o0777); + let to_meta = tokio::fs::metadata(&backup).await?; let from_gid = from_meta.gid(); let to_gid = to_meta.gid(); // If chown fails, se the protection bits for the roup the same as the perm bits for others - if from_gid != to_gid && chown(to, None, Some(from_gid)).is_err() { + if from_gid != to_gid && chown(&backup, None, Some(from_gid)).is_err() { let new_perms = (perms.mode() & 0o0707) | ((perms.mode() & 0o07) << 3); perms.set_mode(new_perms); } std::fs::set_permissions(&backup, perms)?; // TODO: Set time - // TODO: set xattr via rustix + copy_xattr(&p, &backup)?; } #[cfg(windows)] @@ -1080,6 +1105,7 @@ impl Document { #[cfg(unix)] { + use std::os::unix::fs::PermissionsExt; let mode = from_meta.permissions().mode(); open_opt.mode(mode); }