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 tokio::fs;
|
||||||
use tracing::debug;
|
use tracing::debug;
|
||||||
|
|
||||||
|
|
@ -94,14 +94,24 @@ impl FileSystem {
|
||||||
/// Write file contents.
|
/// Write file contents.
|
||||||
pub async fn write_file(&self, path: &str, data: &[u8]) -> Result<(), DaemonError> {
|
pub async fn write_file(&self, path: &str, data: &[u8]) -> Result<(), DaemonError> {
|
||||||
let resolved = self.resolve(path)?;
|
let resolved = self.resolve(path)?;
|
||||||
|
let owner = resolved
|
||||||
|
.parent()
|
||||||
|
.and_then(resolve_target_ownership);
|
||||||
|
|
||||||
// Ensure parent directory exists
|
// Ensure parent directory exists
|
||||||
if let Some(parent) = resolved.parent() {
|
if let Some(parent) = resolved.parent() {
|
||||||
fs::create_dir_all(parent).await.map_err(DaemonError::Io)?;
|
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");
|
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.
|
/// 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)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct FileEntry {
|
pub struct FileEntry {
|
||||||
pub name: String,
|
pub name: String,
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue