fix(daemon): preserve file ownership on writes for cs2 addons
This commit is contained in:
parent
c7d1627e18
commit
6b463c2b1a
|
|
@ -1,4 +1,4 @@
|
|||
use std::path::PathBuf;
|
||||
use std::path::{Path, PathBuf};
|
||||
use tokio::fs;
|
||||
use tracing::debug;
|
||||
|
||||
|
|
@ -94,14 +94,24 @@ impl FileSystem {
|
|||
/// Write file contents.
|
||||
pub async fn write_file(&self, path: &str, data: &[u8]) -> Result<(), DaemonError> {
|
||||
let resolved = self.resolve(path)?;
|
||||
let owner = resolved
|
||||
.parent()
|
||||
.and_then(resolve_target_ownership);
|
||||
|
||||
// Ensure parent directory exists
|
||||
if let Some(parent) = resolved.parent() {
|
||||
fs::create_dir_all(parent).await.map_err(DaemonError::Io)?;
|
||||
if let Some(ref owner) = owner {
|
||||
apply_ownership_to_path_chain(parent, &owner)?;
|
||||
}
|
||||
}
|
||||
|
||||
debug!(path = %resolved.display(), "Writing file");
|
||||
fs::write(&resolved, data).await.map_err(DaemonError::Io)
|
||||
fs::write(&resolved, data).await.map_err(DaemonError::Io)?;
|
||||
if let Some(ref owner) = owner {
|
||||
apply_ownership(&resolved, owner.uid, owner.gid)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Delete files or directories.
|
||||
|
|
@ -119,6 +129,100 @@ impl FileSystem {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
struct OwnershipTarget {
|
||||
anchor: PathBuf,
|
||||
uid: u32,
|
||||
gid: u32,
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
fn resolve_target_ownership(start: &Path) -> Option<OwnershipTarget> {
|
||||
use std::os::unix::fs::MetadataExt;
|
||||
|
||||
let mut cursor = Some(start);
|
||||
let mut fallback: Option<OwnershipTarget> = None;
|
||||
|
||||
while let Some(path) = cursor {
|
||||
if let Ok(metadata) = std::fs::metadata(path) {
|
||||
let candidate = OwnershipTarget {
|
||||
anchor: path.to_path_buf(),
|
||||
uid: metadata.uid(),
|
||||
gid: metadata.gid(),
|
||||
};
|
||||
|
||||
if fallback.is_none() {
|
||||
fallback = Some(candidate.clone());
|
||||
}
|
||||
|
||||
if candidate.uid != 0 || candidate.gid != 0 {
|
||||
return Some(candidate);
|
||||
}
|
||||
}
|
||||
cursor = path.parent();
|
||||
}
|
||||
|
||||
fallback
|
||||
}
|
||||
|
||||
#[cfg(not(unix))]
|
||||
fn resolve_target_ownership(_start: &Path) -> Option<OwnershipTarget> {
|
||||
None
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
fn apply_ownership_to_path_chain(target: &Path, owner: &OwnershipTarget) -> Result<(), DaemonError> {
|
||||
if !target.starts_with(&owner.anchor) {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let mut current = owner.anchor.clone();
|
||||
apply_ownership(¤t, owner.uid, owner.gid)?;
|
||||
|
||||
let remainder = match target.strip_prefix(&owner.anchor) {
|
||||
Ok(path) => path,
|
||||
Err(_) => return Ok(()),
|
||||
};
|
||||
|
||||
for component in remainder.components() {
|
||||
current.push(component.as_os_str());
|
||||
apply_ownership(¤t, owner.uid, owner.gid)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(not(unix))]
|
||||
fn apply_ownership_to_path_chain(_target: &Path, _owner: &OwnershipTarget) -> Result<(), DaemonError> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
fn apply_ownership(path: &Path, uid: u32, gid: u32) -> Result<(), DaemonError> {
|
||||
use std::ffi::CString;
|
||||
use std::os::unix::ffi::OsStrExt;
|
||||
|
||||
let bytes = path.as_os_str().as_bytes();
|
||||
let c_path = CString::new(bytes).map_err(|err| {
|
||||
DaemonError::Io(std::io::Error::new(
|
||||
std::io::ErrorKind::InvalidInput,
|
||||
format!("invalid path for chown: {err}"),
|
||||
))
|
||||
})?;
|
||||
|
||||
let result = unsafe { libc::chown(c_path.as_ptr(), uid, gid) };
|
||||
if result != 0 {
|
||||
return Err(DaemonError::Io(std::io::Error::last_os_error()));
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(not(unix))]
|
||||
fn apply_ownership(_path: &Path, _uid: u32, _gid: u32) -> Result<(), DaemonError> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct FileEntry {
|
||||
pub name: String,
|
||||
|
|
|
|||
Loading…
Reference in New Issue