Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 39 additions & 3 deletions crates/engine/src/local_copy/executor/special/device.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ use crate::local_copy::{
remove_source_entry_if_requested,
};
#[cfg(unix)]
use ::metadata::create_device_node;
use ::metadata::create_device_node_with_fake_super;
use ::metadata::{MetadataOptions, apply_file_metadata_with_options};

/// Copies a device node (block or character) from source to destination.
Expand Down Expand Up @@ -208,10 +208,13 @@ pub(crate) fn copy_device(
}
}

// create the actual device node
// create the actual device node, or a 0600 placeholder when --fake-super
// is active (mirrors upstream syscall.c:do_mknod()'s am_root < 0 branch).
#[cfg(unix)]
{
create_device_node(destination, metadata).map_err(map_metadata_error)?;
let fake_super = metadata_options.fake_super_enabled();
create_device_node_with_fake_super(destination, metadata, fake_super)
.map_err(map_metadata_error)?;
}
#[cfg(not(unix))]
{
Expand Down Expand Up @@ -239,6 +242,17 @@ pub(crate) fn copy_device(
#[cfg(all(unix, feature = "acl"))]
sync_acls_if_requested(preserve_acls, mode, source, destination, true)?;

// Under fake-super, capture the would-be device's mode/uid/gid/rdev in
// the rsync.%stat xattr so the destination can be restored later. This
// is the read-side complement of `apply_ownership_via_fake_super` for
// the local-copy path, where we have a full `fs::Metadata` rather than
// a wire-protocol `FileEntry`.
// upstream: xattrs.c:set_stat_xattr() under am_root < 0
#[cfg(all(unix, feature = "xattr"))]
if metadata_options.fake_super_enabled() {
store_fake_super_for_local_metadata(destination, metadata)?;
}

context.record_hard_link(metadata, destination);
context.summary_mut().record_device();

Expand All @@ -259,3 +273,25 @@ pub(crate) fn copy_device(
remove_source_entry_if_requested(context, source, record_path.as_deref(), file_type)?;
Ok(())
}

/// Stores the would-be device/special metadata in the `rsync.%stat` xattr.
///
/// Encodes mode (with `S_IFMT` bits), uid, gid, and rdev so a later
/// fake-super read can faithfully reconstruct the original node.
// upstream: xattrs.c:set_stat_xattr()
#[cfg(all(unix, feature = "xattr"))]
fn store_fake_super_for_local_metadata(
destination: &Path,
metadata: &fs::Metadata,
) -> Result<(), LocalCopyError> {
use ::metadata::{FakeSuperStat, store_fake_super};

let stat = FakeSuperStat::from_metadata(metadata);
store_fake_super(destination, &stat).map_err(|error| {
LocalCopyError::io(
"store fake-super metadata",
destination.to_path_buf(),
error,
)
})
}
39 changes: 36 additions & 3 deletions crates/engine/src/local_copy/executor/special/fifo.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ use crate::local_copy::{
remove_source_entry_if_requested,
};
#[cfg(unix)]
use ::metadata::create_fifo;
use ::metadata::create_fifo_with_fake_super;
use ::metadata::{MetadataOptions, apply_file_metadata_with_options};

/// Copies a FIFO (named pipe) from source to destination.
Expand Down Expand Up @@ -212,10 +212,13 @@ pub(crate) fn copy_fifo(
}
}

// actually create a FIFO
// actually create a FIFO, or a 0600 placeholder when --fake-super is
// active (mirrors upstream syscall.c:do_mknod()'s am_root < 0 branch).
#[cfg(unix)]
{
create_fifo(destination, metadata).map_err(map_metadata_error)?;
let fake_super = metadata_options.fake_super_enabled();
create_fifo_with_fake_super(destination, metadata, fake_super)
.map_err(map_metadata_error)?;
}
#[cfg(not(unix))]
{
Expand Down Expand Up @@ -247,6 +250,14 @@ pub(crate) fn copy_fifo(
#[cfg(all(unix, feature = "acl"))]
sync_acls_if_requested(preserve_acls, mode, source, destination, true)?;

// Under fake-super, capture the would-be FIFO/socket's mode/uid/gid in
// the rsync.%stat xattr so the destination can be restored later.
// upstream: xattrs.c:set_stat_xattr() under am_root < 0
#[cfg(all(unix, feature = "xattr"))]
if metadata_options.fake_super_enabled() {
store_fake_super_for_local_metadata(destination, metadata)?;
}

context.record_hard_link(metadata, destination);
context.summary_mut().record_fifo();

Expand All @@ -267,3 +278,25 @@ pub(crate) fn copy_fifo(
remove_source_entry_if_requested(context, source, record_path.as_deref(), file_type)?;
Ok(())
}

/// Stores the would-be FIFO/socket metadata in the `rsync.%stat` xattr.
///
/// Encodes mode (with `S_IFMT` bits), uid, and gid so a later fake-super
/// read can faithfully reconstruct the original node.
// upstream: xattrs.c:set_stat_xattr()
#[cfg(all(unix, feature = "xattr"))]
fn store_fake_super_for_local_metadata(
destination: &Path,
metadata: &fs::Metadata,
) -> Result<(), LocalCopyError> {
use ::metadata::{FakeSuperStat, store_fake_super};

let stat = FakeSuperStat::from_metadata(metadata);
store_fake_super(destination, &stat).map_err(|error| {
LocalCopyError::io(
"store fake-super metadata",
destination.to_path_buf(),
error,
)
})
}
15 changes: 13 additions & 2 deletions crates/metadata/src/apply/ownership.rs
Original file line number Diff line number Diff line change
Expand Up @@ -292,9 +292,11 @@ fn apply_ownership_via_fake_super(
uid: Option<u32>,
gid: Option<u32>,
) -> Result<(), MetadataError> {
use crate::fake_super::{FakeSuperStat, store_fake_super};
use crate::fake_super::{FakeSuperStat, load_fake_super, store_fake_super};

let mode = entry.permissions();
// upstream: xattrs.c:set_stat_xattr() encodes the full mode (S_IFMT + perms)
// so a later read can rebuild the file type, not just the permission bits.
let mode = entry.mode();
let uid = uid.unwrap_or(0);
let gid = gid.unwrap_or(0);

Expand All @@ -314,6 +316,15 @@ fn apply_ownership_via_fake_super(
rdev,
};

// upstream: xattrs.c:read_stat_xattr() consults the existing xattr so an
// unchanged stat skips the rewrite. Mirrors `set_file_attrs()`'s "no-op
// when current state already matches" fast path.
if let Ok(Some(existing)) = load_fake_super(destination)
&& existing == stat
{
return Ok(());
}

store_fake_super(destination, &stat)
.map_err(|error| MetadataError::new("store fake-super metadata", destination, error))
}
Expand Down
124 changes: 124 additions & 0 deletions crates/metadata/src/apply/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -931,3 +931,127 @@ fn attrs_flags_skip_mtime_does_not_affect_permissions() {
let dest_meta = fs::metadata(&dest).expect("dest metadata");
assert_eq!(dest_meta.permissions().mode() & 0o777, 0o755);
}

#[cfg(all(unix, feature = "xattr"))]
#[test]
fn fake_super_writes_rsync_stat_xattr_for_regular_file() {
use crate::fake_super::{FAKE_SUPER_XATTR, FakeSuperStat};
use protocol::flist::FileEntry;

let temp = tempdir().expect("tempdir");
let dest = temp.path().join("fakesuper-regular.txt");
fs::write(&dest, b"data").expect("write dest");

let mut entry = FileEntry::new_file("fakesuper-regular.txt".into(), 4, 0o100_644);
entry.set_uid(4242);
entry.set_gid(4343);

let opts = MetadataOptions::new()
.fake_super(true)
.preserve_owner(true)
.preserve_group(true);

apply_metadata_from_file_entry(&dest, &entry, &opts).expect("apply with fake-super");

let raw = match xattr::get(&dest, FAKE_SUPER_XATTR) {
Ok(Some(value)) => value,
Ok(None) => {
// Filesystem without xattr support (e.g. tmpfs without user_xattr).
return;
}
Err(_) => return,
};
let decoded =
FakeSuperStat::decode(std::str::from_utf8(&raw).expect("xattr utf-8")).expect("decode");

assert_eq!(decoded.mode, 0o100_644);
assert_eq!(decoded.uid, 4242);
assert_eq!(decoded.gid, 4343);
assert_eq!(decoded.rdev, None);
}

#[cfg(all(unix, feature = "xattr"))]
#[test]
fn fake_super_does_not_chown_destination() {
use crate::fake_super::FAKE_SUPER_XATTR;
use protocol::flist::FileEntry;
use std::os::unix::fs::MetadataExt;

let temp = tempdir().expect("tempdir");
let dest = temp.path().join("fakesuper-nochown.txt");
fs::write(&dest, b"data").expect("write dest");

let original_uid = fs::metadata(&dest).expect("metadata").uid();
let original_gid = fs::metadata(&dest).expect("metadata").gid();

let mut entry = FileEntry::new_file("fakesuper-nochown.txt".into(), 4, 0o100_644);
// Use a uid/gid the unprivileged test process cannot assume directly.
entry.set_uid(original_uid + 1000);
entry.set_gid(original_gid + 1000);

let opts = MetadataOptions::new()
.fake_super(true)
.preserve_owner(true)
.preserve_group(true);

apply_metadata_from_file_entry(&dest, &entry, &opts)
.expect("fake-super apply must not fail without root");

let after = fs::metadata(&dest).expect("metadata");
assert_eq!(
after.uid(),
original_uid,
"fake-super must not invoke chown on the inode"
);
assert_eq!(
after.gid(),
original_gid,
"fake-super must not invoke chown on the inode"
);

// Sanity: the xattr was written when the filesystem supports it.
if let Ok(Some(_)) = xattr::get(&dest, FAKE_SUPER_XATTR) {
// Nothing else to assert; existence proves the wire-up.
}
}

#[cfg(all(unix, feature = "xattr"))]
#[test]
fn fake_super_skips_rewrite_when_xattr_already_matches() {
use crate::fake_super::{FAKE_SUPER_XATTR, FakeSuperStat, store_fake_super};
use protocol::flist::FileEntry;

let temp = tempdir().expect("tempdir");
let dest = temp.path().join("fakesuper-skip.txt");
fs::write(&dest, b"data").expect("write dest");

let stat = FakeSuperStat {
mode: 0o100_640,
uid: 7777,
gid: 8888,
rdev: None,
};
if store_fake_super(&dest, &stat).is_err() {
// Filesystem without xattr support; skip silently.
return;
}
let raw_before = xattr::get(&dest, FAKE_SUPER_XATTR)
.expect("xattr get")
.expect("xattr present");

let mut entry = FileEntry::new_file("fakesuper-skip.txt".into(), 4, 0o100_640);
entry.set_uid(7777);
entry.set_gid(8888);

let opts = MetadataOptions::new()
.fake_super(true)
.preserve_owner(true)
.preserve_group(true);

apply_metadata_from_file_entry(&dest, &entry, &opts).expect("apply with fake-super");

let raw_after = xattr::get(&dest, FAKE_SUPER_XATTR)
.expect("xattr get")
.expect("xattr present");
assert_eq!(raw_before, raw_after, "xattr must remain byte-identical");
}
5 changes: 4 additions & 1 deletion crates/metadata/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -190,7 +190,10 @@ pub use mapping_win::{GroupMapping, MappingKind, MappingParseError, NameMapping,

pub use options::{AttrsFlags, MetadataOptions};

pub use special::{create_device_node, create_fifo};
pub use special::{
create_device_node, create_device_node_with_fake_super, create_fifo,
create_fifo_with_fake_super,
};

#[cfg(all(unix, feature = "xattr"))]
pub use xattr::{apply_xattrs_from_list, read_xattrs_for_wire, sync_xattrs};
Expand Down
Loading
Loading