#[cfg(any(target_os = "linux", target_os = "android"))]
use uucore::process::geteuid;
use uutests::util::{CmdResult, TestScenario, is_ci, run_ucmd_as_root};
use uutests::util_name;
use uutests::{at_and_ucmd, new_ucmd};
fn skipping_test_is_okay(result: &CmdResult, needle: &str) -> bool {
if !result.succeeded() {
println!("result.stdout = {}", result.stdout_str());
println!("result.stderr = {}", result.stderr_str());
if is_ci() && result.stderr_str().contains(needle) {
println!("test skipped:");
return true;
}
result.success();
}
false
}
#[cfg(any(target_os = "linux", target_os = "android", target_os = "windows"))]
const ROOT_GROUP: &str = "root";
#[cfg(not(any(target_os = "linux", target_os = "android", target_os = "windows")))]
const ROOT_GROUP: &str = "wheel";
#[cfg(test)]
mod test_passgrp {
use chown::entries::{gid2grp, grp2gid, uid2usr, usr2uid};
#[test]
fn test_usr2uid() {
assert_eq!(0, usr2uid("root").unwrap());
assert!(usr2uid("88_888_888").is_err());
assert!(usr2uid("auserthatdoesntexist").is_err());
}
#[test]
fn test_grp2gid() {
assert_eq!(0, grp2gid(super::ROOT_GROUP).unwrap());
assert!(grp2gid("88_888_888").is_err());
assert!(grp2gid("agroupthatdoesntexist").is_err());
}
#[test]
fn test_uid2usr() {
assert_eq!("root", uid2usr(0).unwrap());
assert!(uid2usr(88_888_888).is_err());
}
#[test]
fn test_gid2grp() {
assert_eq!(super::ROOT_GROUP, gid2grp(0).unwrap());
assert!(gid2grp(88_888_888).is_err());
}
}
#[test]
fn test_invalid_option() {
new_ucmd!().arg("-w").arg("-q").arg("/").fails();
}
#[test]
fn test_invalid_arg() {
new_ucmd!().arg("--definitely-invalid").fails_with_code(1);
}
#[test]
fn test_chown_only_owner() {
let scene = TestScenario::new(util_name!());
let at = &scene.fixtures;
let result = scene.cmd("whoami").run();
if skipping_test_is_okay(&result, "whoami: cannot find name for user ID") {
return;
}
let user_name = String::from(result.stdout_str().trim());
assert!(!user_name.is_empty());
let file1 = "test_chown_file1";
at.touch(file1);
scene
.ucmd()
.arg(user_name)
.arg("--verbose")
.arg(file1)
.succeeds()
.stderr_contains("retained as");
scene
.ucmd()
.arg("root")
.arg("--verbose")
.arg(file1)
.fails()
.stderr_contains("failed to change");
}
#[test]
fn test_chown_only_owner_colon() {
let scene = TestScenario::new(util_name!());
let at = &scene.fixtures;
let result = scene.cmd("whoami").run();
if skipping_test_is_okay(&result, "whoami: cannot find name for user ID") {
return;
}
let user_name = String::from(result.stdout_str().trim());
assert!(!user_name.is_empty());
let file1 = "test_chown_file1";
at.touch(file1);
scene
.ucmd()
.arg(format!("{user_name}:"))
.arg("--verbose")
.arg(file1)
.succeeds()
.stderr_contains("retained as");
scene
.ucmd()
.arg(format!("{user_name}."))
.arg("--verbose")
.arg(file1)
.succeeds()
.stderr_contains("retained as");
scene
.ucmd()
.arg("root:")
.arg("--verbose")
.arg(file1)
.fails()
.stderr_contains("failed to change");
}
#[test]
fn test_chown_only_colon() {
let scene = TestScenario::new(util_name!());
let at = &scene.fixtures;
let file1 = "test_chown_file1";
at.touch(file1);
let result = scene.ucmd().arg(":").arg("--verbose").arg(file1).run();
if skipping_test_is_okay(&result, "No such id") {
return;
}
result.stderr_contains("retained as");
scene
.ucmd()
.arg("::")
.arg("--verbose")
.arg(file1)
.fails()
.stderr_contains("invalid group: '::'");
scene
.ucmd()
.arg("..")
.arg("--verbose")
.arg(file1)
.fails()
.stderr_contains("invalid group: '..'");
}
#[test]
fn test_chown_failed_stdout() {
}
#[test]
#[cfg(not(target_os = "openbsd"))]
fn test_chown_owner_group() {
let scene = TestScenario::new(util_name!());
let at = &scene.fixtures;
let result = scene.cmd("whoami").run();
if skipping_test_is_okay(&result, "whoami: cannot find name for user ID") {
return;
}
let user_name = String::from(result.stdout_str().trim());
assert!(!user_name.is_empty());
let file1 = "test_chown_file1";
at.touch(file1);
let result = scene.cmd("id").arg("-gn").run();
if skipping_test_is_okay(&result, "id: cannot find name for group ID") {
return;
}
let group_name = String::from(result.stdout_str().trim());
assert!(!group_name.is_empty());
let result = scene
.ucmd()
.arg(format!("{user_name}:{group_name}"))
.arg("--verbose")
.arg(file1)
.run();
if skipping_test_is_okay(&result, "chown: invalid group:") {
return;
}
result.stderr_contains("retained as");
scene
.ucmd()
.arg("root:root:root")
.arg("--verbose")
.arg(file1)
.fails()
.stderr_contains("invalid group");
scene
.ucmd()
.arg("root.root.root")
.arg("--verbose")
.arg(file1)
.fails()
.stderr_contains("invalid group");
scene
.ucmd()
.arg(format!("root:{ROOT_GROUP}"))
.arg("--verbose")
.arg(file1)
.fails()
.stderr_contains("failed to change");
}
#[test]
#[cfg(not(target_os = "openbsd"))]
fn test_chown_various_input() {
let scene = TestScenario::new(util_name!());
let at = &scene.fixtures;
let result = scene.cmd("whoami").run();
if skipping_test_is_okay(&result, "whoami: cannot find name for user ID") {
return;
}
let user_name = String::from(result.stdout_str().trim());
assert!(!user_name.is_empty());
let file1 = "test_chown_file1";
at.touch(file1);
let result = scene.cmd("id").arg("-gn").run();
if skipping_test_is_okay(&result, "id: cannot find name for group ID") {
return;
}
let group_name = String::from(result.stdout_str().trim());
assert!(!group_name.is_empty());
let result = scene
.ucmd()
.arg(format!("{user_name}:{group_name}"))
.arg("--verbose")
.arg(file1)
.run();
if skipping_test_is_okay(&result, "chown: invalid group:") {
return;
}
result.stderr_contains("retained as");
let result = scene
.ucmd()
.arg(format!("{user_name}.{group_name}"))
.arg("--verbose")
.arg(file1)
.run();
if skipping_test_is_okay(&result, "chown: invalid group:") {
return;
}
result.stderr_contains("retained as");
scene
.ucmd()
.arg(format!("{}:{}", "user.name", "groupname"))
.arg("--verbose")
.arg(file1)
.fails()
.stderr_contains("chown: invalid user: 'user.name:groupname'");
}
#[test]
#[cfg(any(windows, all(unix, not(target_os = "openbsd"))))]
fn test_chown_only_group() {
let scene = TestScenario::new(util_name!());
let at = &scene.fixtures;
let result = scene.cmd("id").arg("-gn").run();
if skipping_test_is_okay(&result, "id: cannot find name for group ID") {
return;
}
let group_name = String::from(result.stdout_str().trim());
assert!(!group_name.is_empty());
let file1 = "test_chown_file1";
at.touch(file1);
let result = scene
.ucmd()
.arg(format!(":{group_name}"))
.arg("--verbose")
.arg(file1)
.run();
result.stderr_contains("retained as");
result.success();
if group_name != ROOT_GROUP {
scene
.ucmd()
.arg(format!(":{ROOT_GROUP}"))
.arg("--verbose")
.arg(file1)
.fails()
.stderr_contains("failed to change");
}
}
#[test]
fn test_chown_only_user_id() {
let scene = TestScenario::new(util_name!());
let at = &scene.fixtures;
let result = scene.cmd("id").arg("-u").run();
if skipping_test_is_okay(&result, "id: cannot find name for group ID") {
return;
}
let user_id = String::from(result.stdout_str().trim());
assert!(!user_id.is_empty());
let file1 = "test_chown_file1";
at.touch(file1);
let result = scene.ucmd().arg(user_id).arg("--verbose").arg(file1).run();
if skipping_test_is_okay(&result, "invalid user") {
return;
}
result.stderr_contains("retained as");
scene
.ucmd()
.arg("0")
.arg("--verbose")
.arg(file1)
.fails()
.stderr_contains("failed to change");
}
#[test]
fn test_chown_fail_id() {
let scene = TestScenario::new(util_name!());
let at = &scene.fixtures;
let result = scene.cmd("id").arg("-u").run();
if skipping_test_is_okay(&result, "id: cannot find name for group ID") {
return;
}
let user_id = String::from(result.stdout_str().trim());
assert!(!user_id.is_empty());
let file1 = "test_chown_file1";
at.touch(file1);
scene
.ucmd()
.arg(format!("{user_id}:"))
.arg(file1)
.fails()
.stderr_contains("invalid spec");
scene
.ucmd()
.arg(format!("{user_id}."))
.arg(file1)
.fails()
.stderr_contains("invalid spec");
}
#[test]
fn test_chown_only_user_id_nonexistent_user() {
let ts = TestScenario::new(util_name!());
let at = &ts.fixtures;
at.touch("f");
if let Ok(result) = run_ucmd_as_root(&ts, &["12345", "f"]) {
result.success().no_stdout().no_stderr();
} else {
print!("Test skipped; requires root user");
}
}
#[test]
#[cfg(not(target_os = "openbsd"))]
fn test_chown_only_group_id() {
let scene = TestScenario::new(util_name!());
let at = &scene.fixtures;
let result = scene.cmd("id").arg("-g").run();
if skipping_test_is_okay(&result, "id: cannot find name for group ID") {
return;
}
let group_id = String::from(result.stdout_str().trim());
assert!(!group_id.is_empty());
let file1 = "test_chown_file1";
at.touch(file1);
let result = scene
.ucmd()
.arg(format!(":{group_id}"))
.arg("--verbose")
.arg(file1)
.run();
if skipping_test_is_okay(&result, "chown: invalid group:") {
return;
}
result.stderr_contains("retained as");
#[cfg(any(windows, all(unix, not(target_os = "macos"))))]
if group_id != "0" {
scene
.ucmd()
.arg(":0")
.arg("--verbose")
.arg(file1)
.fails()
.stderr_contains("failed to change");
}
}
#[test]
fn test_chown_only_group_id_nonexistent_group() {
let ts = TestScenario::new(util_name!());
let at = &ts.fixtures;
at.touch("f");
if let Ok(result) = run_ucmd_as_root(&ts, &[":12345", "f"]) {
result.success().no_stdout().no_stderr();
} else {
print!("Test skipped; requires root user");
}
}
#[test]
#[cfg(not(target_os = "openbsd"))]
fn test_chown_owner_group_id() {
let scene = TestScenario::new(util_name!());
let at = &scene.fixtures;
let result = scene.cmd("id").arg("-u").run();
if skipping_test_is_okay(&result, "id: cannot find name for group ID") {
return;
}
let user_id = String::from(result.stdout_str().trim());
assert!(!user_id.is_empty());
let result = scene.cmd("id").arg("-g").run();
if skipping_test_is_okay(&result, "id: cannot find name for group ID") {
return;
}
let group_id = String::from(result.stdout_str().trim());
assert!(!group_id.is_empty());
let file1 = "test_chown_file1";
at.touch(file1);
let result = scene
.ucmd()
.arg(format!("{user_id}:{group_id}"))
.arg("--verbose")
.arg(file1)
.run();
if skipping_test_is_okay(&result, "invalid user") {
return;
}
result.stderr_contains("retained as");
let result = scene
.ucmd()
.arg(format!("{user_id}.{group_id}"))
.arg("--verbose")
.arg(file1)
.run();
if skipping_test_is_okay(&result, "invalid user") {
return;
}
result.stderr_contains("retained as");
scene
.ucmd()
.arg("0:0")
.arg("--verbose")
.arg(file1)
.fails()
.stderr_contains("failed to change");
}
#[test]
#[cfg(not(target_os = "openbsd"))]
fn test_chown_owner_group_mix() {
let scene = TestScenario::new(util_name!());
let at = &scene.fixtures;
let result = scene.cmd("id").arg("-u").run();
if skipping_test_is_okay(&result, "id: cannot find name for group ID") {
return;
}
let user_id = String::from(result.stdout_str().trim());
assert!(!user_id.is_empty());
let result = scene.cmd("id").arg("-gn").run();
if skipping_test_is_okay(&result, "id: cannot find name for group ID") {
return;
}
let group_name = String::from(result.stdout_str().trim());
assert!(!group_name.is_empty());
let file1 = "test_chown_file1";
at.touch(file1);
let result = scene
.ucmd()
.arg(format!("{user_id}:{group_name}"))
.arg("--verbose")
.arg(file1)
.run();
result.stderr_contains("retained as");
scene
.ucmd()
.arg(format!("0:{ROOT_GROUP}"))
.arg("--verbose")
.arg(file1)
.fails()
.stderr_contains("failed to change");
}
#[test]
fn test_chown_recursive() {
let scene = TestScenario::new(util_name!());
let at = &scene.fixtures;
let result = scene.cmd("whoami").run();
if skipping_test_is_okay(&result, "whoami: cannot find name for user ID") {
return;
}
let user_name = String::from(result.stdout_str().trim());
assert!(!user_name.is_empty());
at.mkdir_all("a/b/c");
at.mkdir("z");
at.touch(at.plus_as_string("a/a"));
at.touch(at.plus_as_string("a/b/b"));
at.touch(at.plus_as_string("a/b/c/c"));
at.touch(at.plus_as_string("z/y"));
scene
.ucmd()
.arg("-R")
.arg("--verbose")
.arg(user_name)
.arg("a")
.arg("z")
.succeeds()
.stderr_contains("ownership of 'a/a' retained as")
.stderr_contains("ownership of 'z/y' retained as");
}
#[test]
fn test_root_preserve() {
let scene = TestScenario::new(util_name!());
let result = scene.cmd("whoami").run();
if skipping_test_is_okay(&result, "whoami: cannot find name for user ID") {
return;
}
let user_name = String::from(result.stdout_str().trim());
assert!(!user_name.is_empty());
let result = scene
.ucmd()
.arg("--preserve-root")
.arg("-R")
.arg(user_name)
.arg("/")
.fails();
result.stderr_contains("chown: it is dangerous to operate recursively");
}
#[cfg(any(target_os = "linux", target_os = "android"))]
#[test]
fn test_big_p() {
if geteuid() != 0 {
new_ucmd!()
.arg("-RP")
.arg("bin")
.arg("/proc/self/cwd")
.fails()
.stderr_contains(
"chown: changing ownership of '/proc/self/cwd': ",
);
}
}
#[test]
fn test_chown_file_notexisting() {
let scene = TestScenario::new(util_name!());
let result = scene.cmd("whoami").run();
if skipping_test_is_okay(&result, "whoami: cannot find name for user ID") {
return;
}
let user_name = String::from(result.stdout_str().trim());
assert!(!user_name.is_empty());
scene
.ucmd()
.arg(&user_name)
.arg("--verbose")
.arg("not_existing")
.fails()
.stdout_contains(format!(
"failed to change ownership of 'not_existing' to {user_name}"
));
}
#[test]
fn test_chown_no_change_to_user() {
let scene = TestScenario::new(util_name!());
let at = &scene.fixtures;
let result = scene.cmd("whoami").run();
if skipping_test_is_okay(&result, "whoami: cannot find name for user ID") {
return;
}
let user_name = String::from(result.stdout_str().trim());
assert!(!user_name.is_empty());
for (i, from) in ["42", ":42", "42:42"].iter().enumerate() {
let file = i.to_string();
at.touch(&file);
scene
.ucmd()
.arg("-v")
.arg(format!("--from={from}"))
.arg("43")
.arg(&file)
.succeeds()
.stdout_only(format!("ownership of '{file}' retained as {user_name}\n"));
}
}
#[test]
#[cfg(not(target_os = "openbsd"))]
fn test_chown_no_change_to_group() {
let scene = TestScenario::new(util_name!());
let at = &scene.fixtures;
let result = scene.cmd("whoami").run();
if skipping_test_is_okay(&result, "whoami: cannot find name for user ID") {
return;
}
let user_name = String::from(result.stdout_str().trim());
assert!(!user_name.is_empty());
let result = scene.cmd("id").arg("-ng").run();
if skipping_test_is_okay(&result, "id: cannot find name for group ID") {
return;
}
let group_name = String::from(result.stdout_str().trim());
assert!(!group_name.is_empty());
for (i, from) in ["42", ":42", "42:42"].iter().enumerate() {
let file = i.to_string();
at.touch(&file);
scene
.ucmd()
.arg("-v")
.arg(format!("--from={from}"))
.arg(":43")
.arg(&file)
.succeeds()
.stdout_only(format!("ownership of '{file}' retained as {group_name}\n"));
}
}
#[test]
#[cfg(not(target_os = "openbsd"))]
fn test_chown_no_change_to_user_group() {
let scene = TestScenario::new(util_name!());
let at = &scene.fixtures;
let result = scene.cmd("whoami").run();
if skipping_test_is_okay(&result, "whoami: cannot find name for user ID") {
return;
}
let user_name = String::from(result.stdout_str().trim());
assert!(!user_name.is_empty());
let result = scene.cmd("id").arg("-ng").run();
if skipping_test_is_okay(&result, "id: cannot find name for group ID") {
return;
}
let group_name = String::from(result.stdout_str().trim());
assert!(!group_name.is_empty());
for (i, from) in ["42", ":42", "42:42"].iter().enumerate() {
let file = i.to_string();
at.touch(&file);
scene
.ucmd()
.arg("-v")
.arg(format!("--from={from}"))
.arg("43:43")
.arg(&file)
.succeeds()
.stdout_only(format!(
"ownership of '{file}' retained as {user_name}:{group_name}\n"
));
}
}
#[test]
fn test_chown_reference_file() {
let (at, mut ucmd) = at_and_ucmd!();
at.touch("a");
at.touch("b");
ucmd.arg("--verbose")
.arg("--reference")
.arg("a")
.arg("b")
.succeeds()
.stderr_contains("ownership of 'b' retained as")
.no_stdout();
}