use super::*;
use crate::test_support::{fixture, remove};
use std::os::unix::fs::PermissionsExt;
use std::path::PathBuf;
#[test]
fn a_missing_privilege_is_counted_and_any_other_refusal_is_an_error() {
let mut refused: u64 = 0;
assert!(settle(Ok(()), "set the times of", &mut refused).is_ok());
assert_eq!(refused, 0);
for errno in [libc::EPERM, libc::EACCES] {
assert!(settle(Err(std::io::Error::from_raw_os_error(errno)), "set the times of", &mut refused).is_ok());
}
assert_eq!(refused, 2);
for errno in [libc::ENOSPC, libc::EROFS, libc::EIO] {
let failure: AttributeFailure = settle(Err(std::io::Error::from_raw_os_error(errno)), "set the permission bits of", &mut refused).expect_err("refused");
assert_eq!((failure.action, failure.error.raw_os_error()), ("set the permission bits of", Some(errno)));
}
assert_eq!(refused, 2, "an error is not counted as a tolerated loss");
}
#[test]
fn a_named_pipe_is_given_its_attributes_through_its_descriptor() {
let parent: PathBuf = fixture("attrs-fifo");
make_fifo(parent.join("original").as_path(), 0o640).expect("original pipe");
make_fifo(parent.join("copy").as_path(), 0o600).expect("copy pipe");
let metadata: Metadata = std::fs::symlink_metadata(parent.join("original")).expect("metadata");
let handle: File = open_fifo(parent.join("copy").as_path()).expect("open without blocking");
let mut losses: AttributeLosses = AttributeLosses::default();
preserve_on_descriptor(parent.join("original").as_path(), &handle, &metadata, true, &mut losses).expect("attributes");
let copied: Metadata = std::fs::symlink_metadata(parent.join("copy")).expect("copy metadata");
assert_eq!((copied.mode() & 0o7777, copied.mtime(), copied.mtime_nsec()), (metadata.mode() & 0o7777, metadata.mtime(), metadata.mtime_nsec()));
std::os::unix::fs::symlink(parent.join("original"), parent.join("link")).expect("link");
assert!(open_fifo(parent.join("link").as_path()).is_err(), "a link is not opened");
std::fs::set_permissions(parent.join("copy"), std::fs::Permissions::from_mode(0o600)).expect("mode");
remove(parent.as_path());
}
#[test]
fn a_link_is_given_its_times_without_following_it() {
let parent: PathBuf = fixture("attrs-link");
std::fs::write(parent.join("target"), b"x").expect("target");
std::os::unix::fs::symlink("target", parent.join("original")).expect("original link");
std::os::unix::fs::symlink("target", parent.join("copy")).expect("copy link");
let name = std::ffi::CString::new(parent.join("original").as_os_str().as_encoded_bytes()).expect("path");
let times: [libc::timespec; 2] = [libc::timespec { tv_sec: 1_000_000_000, tv_nsec: 5 }, libc::timespec { tv_sec: 1_000_000_001, tv_nsec: 7 }];
let set: libc::c_int = unsafe { libc::utimensat(libc::AT_FDCWD, name.as_ptr(), times.as_ptr(), libc::AT_SYMLINK_NOFOLLOW) };
assert_eq!(set, 0);
let target_before: i64 = std::fs::metadata(parent.join("target")).expect("target").mtime();
let metadata: Metadata = std::fs::symlink_metadata(parent.join("original")).expect("metadata");
let mut losses: AttributeLosses = AttributeLosses::default();
preserve_on_link(parent.join("original").as_path(), parent.join("copy").as_path(), &metadata, &mut losses).expect("attributes");
let copied: Metadata = std::fs::symlink_metadata(parent.join("copy")).expect("copy");
assert_eq!((copied.mtime(), copied.mtime_nsec()), (1_000_000_001, 7));
assert_eq!(std::fs::metadata(parent.join("target")).expect("target").mtime(), target_before);
remove(parent.as_path());
}
#[test]
fn the_owner_is_left_alone_only_when_user_and_group_both_agree() {
let (user, group): (u32, u32) = (1000, 1001);
assert!(same_owner((user, group), (user, group)));
assert!(!same_owner((user, group), (user + 1, group)), "another user");
assert!(!same_owner((user, group), (user, group + 1)), "another group");
assert!(!same_owner((user, group), (user + 1, group + 1)), "another user and group");
}
fn running_as_root() -> bool {
return unsafe { libc::geteuid() } == 0;
}
#[test]
fn an_owner_that_cannot_be_given_is_counted_and_an_equal_one_is_not_touched() {
if running_as_root() {
return;
}
let parent: PathBuf = fixture("attrs-owner-refused");
std::fs::write(parent.join("copy"), b"x").expect("copy");
let copy: File = File::open(parent.join("copy")).expect("open");
let current: Metadata = copy.metadata().expect("metadata");
let foreign: Metadata = std::fs::metadata("/").expect("the root directory is owned by root");
assert_ne!(foreign.uid(), current.uid());
let mut losses: AttributeLosses = AttributeLosses::default();
set_owner(Handle::Descriptor(©), ¤t, &foreign, &mut losses);
assert_eq!(losses.owner, 1);
assert_eq!(copy.metadata().expect("metadata").uid(), current.uid());
set_owner(Handle::Descriptor(©), ¤t, ¤t, &mut losses);
assert_eq!(losses.owner, 1, "an equal owner is not an attempt");
remove(parent.as_path());
}
#[test]
fn a_group_the_user_belongs_to_is_given_and_not_counted() {
if running_as_root() {
return;
}
let parent: PathBuf = fixture("attrs-owner-group");
std::fs::write(parent.join("original"), b"x").expect("original");
std::fs::write(parent.join("copy"), b"x").expect("copy");
let copy: File = File::open(parent.join("copy")).expect("open");
let current: Metadata = copy.metadata().expect("metadata");
let count: libc::c_int = unsafe { libc::getgroups(0, std::ptr::null_mut()) };
let mut groups: Vec<libc::gid_t> = vec![0; usize::try_from(count).unwrap_or(0)];
let filled: libc::c_int = unsafe { libc::getgroups(count, groups.as_mut_ptr()) };
groups.truncate(usize::try_from(filled).unwrap_or(0));
let Some(other) = groups.into_iter().find(|group: &libc::gid_t| { return *group != current.gid(); }) else {
eprintln!("OWNER CONTROL NOT RUN: a_group_the_user_belongs_to_is_given_and_not_counted (the user belongs to no second group)");
remove(parent.as_path());
return;
};
let name = std::ffi::CString::new(parent.join("original").as_os_str().as_encoded_bytes()).expect("path");
let set: libc::c_int = unsafe { libc::chown(name.as_ptr(), libc::uid_t::MAX, other) };
assert_eq!(set, 0, "give the original a group the user belongs to");
let original: Metadata = std::fs::metadata(parent.join("original")).expect("original metadata");
let mut losses: AttributeLosses = AttributeLosses::default();
set_owner(Handle::Descriptor(©), ¤t, &original, &mut losses);
assert_eq!(losses.owner, 0);
assert_eq!(copy.metadata().expect("metadata").gid(), other);
remove(parent.as_path());
}
fn set_user_attribute(path: &Path, name: &str, value: &[u8]) -> bool {
let path_text = std::ffi::CString::new(path.as_os_str().as_encoded_bytes()).expect("path");
let name_text = std::ffi::CString::new(name).expect("name");
return unsafe { libc::lsetxattr(path_text.as_ptr(), name_text.as_ptr(), value.as_ptr().cast(), value.len(), 0) } == 0;
}
#[test]
fn carried_attributes_are_equal_only_when_names_and_values_agree() {
let parent: PathBuf = fixture("attrs-equal");
for name in ["one", "two"] {
std::fs::write(parent.join(name), b"x").expect("file");
}
let (one, two): (PathBuf, PathBuf) = (parent.join("one"), parent.join("two"));
assert!(carried_attributes_equal(one.as_path(), two.as_path()), "no attributes on either");
if !set_user_attribute(one.as_path(), "user.cli-git-test", b"value") {
eprintln!("ATTRIBUTE CONTROL NOT RUN: carried_attributes_are_equal_only_when_names_and_values_agree (no user attributes on {})", parent.display());
assert!(!carried_attributes_equal(one.as_path(), parent.join("absent").as_path()));
remove(parent.as_path());
return;
}
assert!(!carried_attributes_equal(one.as_path(), two.as_path()), "an attribute only one carries");
assert!(set_user_attribute(two.as_path(), "user.cli-git-test", b"other"));
assert!(!carried_attributes_equal(one.as_path(), two.as_path()), "the same name with another value");
assert!(set_user_attribute(two.as_path(), "user.cli-git-test", b"value"));
assert!(carried_attributes_equal(one.as_path(), two.as_path()), "the same name and value");
assert!(!carried_attributes_equal(one.as_path(), parent.join("absent").as_path()), "a path that is not there");
remove(parent.as_path());
}