mirror of
https://github.com/RGBCube/uutils-coreutils
synced 2025-07-28 11:37:44 +00:00
Merge pull request #3438 from jfinkels/chown-nonexistent-user-id
chown: allow setting arbitrary numeric user ID
This commit is contained in:
commit
40095e1b50
3 changed files with 111 additions and 31 deletions
|
@ -168,17 +168,18 @@ pub fn uu_app<'a>() -> Command<'a> {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Parse the username and groupname
|
/// Parse the owner/group specifier string into a user ID and a group ID.
|
||||||
///
|
///
|
||||||
/// In theory, it should be username:groupname
|
/// The `spec` can be of the form:
|
||||||
/// but ...
|
|
||||||
/// it can user.name:groupname
|
|
||||||
/// or username.groupname
|
|
||||||
///
|
///
|
||||||
/// # Arguments
|
/// * `"owner:group"`,
|
||||||
|
/// * `"owner"`,
|
||||||
|
/// * `":group"`,
|
||||||
///
|
///
|
||||||
/// * `spec` - The input from the user
|
/// and the owner or group can be specified either as an ID or a
|
||||||
/// * `sep` - Should be ':' or '.'
|
/// name. The `sep` argument specifies which character to use as a
|
||||||
|
/// separator between the owner and group; calling code should set
|
||||||
|
/// this to `':'`.
|
||||||
fn parse_spec(spec: &str, sep: char) -> UResult<(Option<u32>, Option<u32>)> {
|
fn parse_spec(spec: &str, sep: char) -> UResult<(Option<u32>, Option<u32>)> {
|
||||||
assert!(['.', ':'].contains(&sep));
|
assert!(['.', ':'].contains(&sep));
|
||||||
let mut args = spec.splitn(2, sep);
|
let mut args = spec.splitn(2, sep);
|
||||||
|
@ -198,10 +199,17 @@ fn parse_spec(spec: &str, sep: char) -> UResult<(Option<u32>, Option<u32>)> {
|
||||||
// So, try to parse it this way
|
// So, try to parse it this way
|
||||||
return parse_spec(spec, '.');
|
return parse_spec(spec, '.');
|
||||||
} else {
|
} else {
|
||||||
return Err(USimpleError::new(
|
// It's possible that the `user` string contains a
|
||||||
1,
|
// numeric user ID, in which case, we respect that.
|
||||||
format!("invalid user: {}", spec.quote()),
|
match user.parse() {
|
||||||
));
|
Ok(uid) => uid,
|
||||||
|
Err(_) => {
|
||||||
|
return Err(USimpleError::new(
|
||||||
|
1,
|
||||||
|
format!("invalid user: {}", spec.quote()),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
@ -209,11 +217,18 @@ fn parse_spec(spec: &str, sep: char) -> UResult<(Option<u32>, Option<u32>)> {
|
||||||
None
|
None
|
||||||
};
|
};
|
||||||
let gid = if !group.is_empty() {
|
let gid = if !group.is_empty() {
|
||||||
Some(
|
Some(match Group::locate(group) {
|
||||||
Group::locate(group)
|
Ok(g) => g.gid,
|
||||||
.map_err(|_| USimpleError::new(1, format!("invalid group: {}", spec.quote())))?
|
Err(_) => match group.parse() {
|
||||||
.gid,
|
Ok(gid) => gid,
|
||||||
)
|
Err(_) => {
|
||||||
|
return Err(USimpleError::new(
|
||||||
|
1,
|
||||||
|
format!("invalid group: {}", spec.quote()),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
})
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
};
|
};
|
||||||
|
@ -232,4 +247,17 @@ mod test {
|
||||||
assert!(format!("{}", parse_spec("::", ':').err().unwrap()).starts_with("invalid group: "));
|
assert!(format!("{}", parse_spec("::", ':').err().unwrap()).starts_with("invalid group: "));
|
||||||
assert!(format!("{}", parse_spec("..", ':').err().unwrap()).starts_with("invalid group: "));
|
assert!(format!("{}", parse_spec("..", ':').err().unwrap()).starts_with("invalid group: "));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Test for parsing IDs that don't correspond to a named user or group.
|
||||||
|
#[test]
|
||||||
|
fn test_parse_spec_nameless_ids() {
|
||||||
|
// This assumes that there is no named user with ID 12345.
|
||||||
|
assert!(matches!(parse_spec("12345", ':'), Ok((Some(12345), None))));
|
||||||
|
// This assumes that there is no named group with ID 54321.
|
||||||
|
assert!(matches!(parse_spec(":54321", ':'), Ok((None, Some(54321)))));
|
||||||
|
assert!(matches!(
|
||||||
|
parse_spec("12345:54321", ':'),
|
||||||
|
Ok((Some(12345), Some(54321)))
|
||||||
|
));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -92,22 +92,25 @@ pub fn wrap_chown<P: AsRef<Path>>(
|
||||||
);
|
);
|
||||||
if level == VerbosityLevel::Verbose {
|
if level == VerbosityLevel::Verbose {
|
||||||
out = if verbosity.groups_only {
|
out = if verbosity.groups_only {
|
||||||
|
let gid = meta.gid();
|
||||||
format!(
|
format!(
|
||||||
"{}\nfailed to change group of {} from {} to {}",
|
"{}\nfailed to change group of {} from {} to {}",
|
||||||
out,
|
out,
|
||||||
path.quote(),
|
path.quote(),
|
||||||
entries::gid2grp(meta.gid()).unwrap(),
|
entries::gid2grp(gid).unwrap_or_else(|_| gid.to_string()),
|
||||||
entries::gid2grp(dest_gid).unwrap()
|
entries::gid2grp(dest_gid).unwrap_or_else(|_| dest_gid.to_string())
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
|
let uid = meta.uid();
|
||||||
|
let gid = meta.gid();
|
||||||
format!(
|
format!(
|
||||||
"{}\nfailed to change ownership of {} from {}:{} to {}:{}",
|
"{}\nfailed to change ownership of {} from {}:{} to {}:{}",
|
||||||
out,
|
out,
|
||||||
path.quote(),
|
path.quote(),
|
||||||
entries::uid2usr(meta.uid()).unwrap(),
|
entries::uid2usr(uid).unwrap_or_else(|_| uid.to_string()),
|
||||||
entries::gid2grp(meta.gid()).unwrap(),
|
entries::gid2grp(gid).unwrap_or_else(|_| gid.to_string()),
|
||||||
entries::uid2usr(dest_uid).unwrap(),
|
entries::uid2usr(dest_uid).unwrap_or_else(|_| dest_uid.to_string()),
|
||||||
entries::gid2grp(dest_gid).unwrap()
|
entries::gid2grp(dest_gid).unwrap_or_else(|_| dest_gid.to_string())
|
||||||
)
|
)
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
@ -119,21 +122,24 @@ pub fn wrap_chown<P: AsRef<Path>>(
|
||||||
if changed {
|
if changed {
|
||||||
match verbosity.level {
|
match verbosity.level {
|
||||||
VerbosityLevel::Changes | VerbosityLevel::Verbose => {
|
VerbosityLevel::Changes | VerbosityLevel::Verbose => {
|
||||||
|
let gid = meta.gid();
|
||||||
out = if verbosity.groups_only {
|
out = if verbosity.groups_only {
|
||||||
format!(
|
format!(
|
||||||
"changed group of {} from {} to {}",
|
"changed group of {} from {} to {}",
|
||||||
path.quote(),
|
path.quote(),
|
||||||
entries::gid2grp(meta.gid()).unwrap(),
|
entries::gid2grp(gid).unwrap_or_else(|_| gid.to_string()),
|
||||||
entries::gid2grp(dest_gid).unwrap()
|
entries::gid2grp(dest_gid).unwrap_or_else(|_| dest_gid.to_string())
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
|
let gid = meta.gid();
|
||||||
|
let uid = meta.uid();
|
||||||
format!(
|
format!(
|
||||||
"changed ownership of {} from {}:{} to {}:{}",
|
"changed ownership of {} from {}:{} to {}:{}",
|
||||||
path.quote(),
|
path.quote(),
|
||||||
entries::uid2usr(meta.uid()).unwrap(),
|
entries::uid2usr(uid).unwrap_or_else(|_| uid.to_string()),
|
||||||
entries::gid2grp(meta.gid()).unwrap(),
|
entries::gid2grp(gid).unwrap_or_else(|_| gid.to_string()),
|
||||||
entries::uid2usr(dest_uid).unwrap(),
|
entries::uid2usr(dest_uid).unwrap_or_else(|_| dest_uid.to_string()),
|
||||||
entries::gid2grp(dest_gid).unwrap()
|
entries::gid2grp(dest_gid).unwrap_or_else(|_| dest_gid.to_string())
|
||||||
)
|
)
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -150,8 +156,8 @@ pub fn wrap_chown<P: AsRef<Path>>(
|
||||||
format!(
|
format!(
|
||||||
"ownership of {} retained as {}:{}",
|
"ownership of {} retained as {}:{}",
|
||||||
path.quote(),
|
path.quote(),
|
||||||
entries::uid2usr(dest_uid).unwrap(),
|
entries::uid2usr(dest_uid).unwrap_or_else(|_| dest_uid.to_string()),
|
||||||
entries::gid2grp(dest_gid).unwrap()
|
entries::gid2grp(dest_gid).unwrap_or_else(|_| dest_gid.to_string())
|
||||||
)
|
)
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -418,6 +418,29 @@ fn test_chown_only_user_id() {
|
||||||
.stderr_contains(&"failed to change");
|
.stderr_contains(&"failed to change");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Test for setting the owner to a user ID for a user that does not exist.
|
||||||
|
///
|
||||||
|
/// For example:
|
||||||
|
///
|
||||||
|
/// $ touch f && chown 12345 f
|
||||||
|
///
|
||||||
|
/// succeeds with exit status 0 and outputs nothing. The owner of the
|
||||||
|
/// file is set to 12345, even though no user with that ID exists.
|
||||||
|
///
|
||||||
|
/// This test must be run as root, because only the root user can
|
||||||
|
/// transfer ownership of a file.
|
||||||
|
#[test]
|
||||||
|
fn test_chown_only_user_id_nonexistent_user() {
|
||||||
|
let ts = TestScenario::new(util_name!());
|
||||||
|
let at = ts.fixtures.clone();
|
||||||
|
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]
|
#[test]
|
||||||
// FixME: stderr = chown: ownership of 'test_chown_file1' retained as cuuser:wheel
|
// FixME: stderr = chown: ownership of 'test_chown_file1' retained as cuuser:wheel
|
||||||
#[cfg(not(target_os = "freebsd"))]
|
#[cfg(not(target_os = "freebsd"))]
|
||||||
|
@ -461,6 +484,29 @@ fn test_chown_only_group_id() {
|
||||||
.stderr_contains(&"failed to change");
|
.stderr_contains(&"failed to change");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Test for setting the group to a group ID for a group that does not exist.
|
||||||
|
///
|
||||||
|
/// For example:
|
||||||
|
///
|
||||||
|
/// $ touch f && chown :12345 f
|
||||||
|
///
|
||||||
|
/// succeeds with exit status 0 and outputs nothing. The group of the
|
||||||
|
/// file is set to 12345, even though no group with that ID exists.
|
||||||
|
///
|
||||||
|
/// This test must be run as root, because only the root user can
|
||||||
|
/// transfer ownership of a file.
|
||||||
|
#[test]
|
||||||
|
fn test_chown_only_group_id_nonexistent_group() {
|
||||||
|
let ts = TestScenario::new(util_name!());
|
||||||
|
let at = ts.fixtures.clone();
|
||||||
|
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]
|
#[test]
|
||||||
fn test_chown_owner_group_id() {
|
fn test_chown_owner_group_id() {
|
||||||
// test chown 1111:1111 file.txt
|
// test chown 1111:1111 file.txt
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue