mirror of
https://github.com/uutils/coreutils
synced 2024-12-13 14:52:41 +00:00
Merge pull request #4365 from kylemanna/km/fix-install-directory-chown
install: add missing directory chown
This commit is contained in:
commit
41018859bb
2 changed files with 176 additions and 86 deletions
|
@ -41,8 +41,8 @@ pub struct Behavior {
|
|||
specified_mode: Option<u32>,
|
||||
backup_mode: BackupMode,
|
||||
suffix: String,
|
||||
owner: String,
|
||||
group: String,
|
||||
owner_id: Option<u32>,
|
||||
group_id: Option<u32>,
|
||||
verbose: bool,
|
||||
preserve_timestamps: bool,
|
||||
compare: bool,
|
||||
|
@ -58,14 +58,15 @@ enum InstallError {
|
|||
DirNeedsArg(),
|
||||
CreateDirFailed(PathBuf, std::io::Error),
|
||||
ChmodFailed(PathBuf),
|
||||
ChownFailed(PathBuf, String),
|
||||
InvalidTarget(PathBuf),
|
||||
TargetDirIsntDir(PathBuf),
|
||||
BackupFailed(PathBuf, PathBuf, std::io::Error),
|
||||
InstallFailed(PathBuf, PathBuf, std::io::Error),
|
||||
StripProgramFailed(String),
|
||||
MetadataFailed(std::io::Error),
|
||||
NoSuchUser(String),
|
||||
NoSuchGroup(String),
|
||||
InvalidUser(String),
|
||||
InvalidGroup(String),
|
||||
OmittingDirectory(PathBuf),
|
||||
}
|
||||
|
||||
|
@ -99,6 +100,7 @@ impl Display for InstallError {
|
|||
Display::fmt(&uio_error!(e, "failed to create {}", dir.quote()), f)
|
||||
}
|
||||
Self::ChmodFailed(file) => write!(f, "failed to chmod {}", file.quote()),
|
||||
Self::ChownFailed(file, msg) => write!(f, "failed to chown {}: {}", file.quote(), msg),
|
||||
Self::InvalidTarget(target) => write!(
|
||||
f,
|
||||
"invalid target {}: No such file or directory",
|
||||
|
@ -117,8 +119,8 @@ impl Display for InstallError {
|
|||
),
|
||||
Self::StripProgramFailed(msg) => write!(f, "strip program failed: {msg}"),
|
||||
Self::MetadataFailed(e) => Display::fmt(&uio_error!(e, ""), f),
|
||||
Self::NoSuchUser(user) => write!(f, "no such user: {}", user.maybe_quote()),
|
||||
Self::NoSuchGroup(group) => write!(f, "no such group: {}", group.maybe_quote()),
|
||||
Self::InvalidUser(user) => write!(f, "invalid user: {}", user.quote()),
|
||||
Self::InvalidGroup(group) => write!(f, "invalid group: {}", group.quote()),
|
||||
Self::OmittingDirectory(dir) => write!(f, "omitting directory {}", dir.quote()),
|
||||
}
|
||||
}
|
||||
|
@ -391,21 +393,44 @@ fn behavior(matches: &ArgMatches) -> UResult<Behavior> {
|
|||
show_error!("Options --compare and --strip are mutually exclusive");
|
||||
return Err(1.into());
|
||||
}
|
||||
|
||||
let owner = matches
|
||||
.get_one::<String>(OPT_OWNER)
|
||||
.map(|s| s.as_str())
|
||||
.unwrap_or("")
|
||||
.to_string();
|
||||
|
||||
let owner_id = if !owner.is_empty() {
|
||||
match usr2uid(&owner) {
|
||||
Ok(u) => Some(u),
|
||||
Err(_) => return Err(InstallError::InvalidUser(owner.clone()).into()),
|
||||
}
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let group = matches
|
||||
.get_one::<String>(OPT_GROUP)
|
||||
.map(|s| s.as_str())
|
||||
.unwrap_or("")
|
||||
.to_string();
|
||||
|
||||
let group_id = if !group.is_empty() {
|
||||
match grp2gid(&group) {
|
||||
Ok(g) => Some(g),
|
||||
Err(_) => return Err(InstallError::InvalidGroup(group.clone()).into()),
|
||||
}
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
Ok(Behavior {
|
||||
main_function,
|
||||
specified_mode,
|
||||
backup_mode,
|
||||
suffix: backup_control::determine_backup_suffix(matches),
|
||||
owner: matches
|
||||
.get_one::<String>(OPT_OWNER)
|
||||
.map(|s| s.as_str())
|
||||
.unwrap_or("")
|
||||
.to_string(),
|
||||
group: matches
|
||||
.get_one::<String>(OPT_GROUP)
|
||||
.map(|s| s.as_str())
|
||||
.unwrap_or("")
|
||||
.to_string(),
|
||||
owner_id,
|
||||
group_id,
|
||||
verbose: matches.get_flag(OPT_VERBOSE),
|
||||
preserve_timestamps,
|
||||
compare,
|
||||
|
@ -466,6 +491,8 @@ fn directory(paths: &[String], b: &Behavior) -> UResult<()> {
|
|||
uucore::error::set_exit_code(1);
|
||||
continue;
|
||||
}
|
||||
|
||||
show_if_err!(chown_optional_user_group(path, b));
|
||||
}
|
||||
// If the exit code was set, or show! has been called at least once
|
||||
// (which sets the exit code as well), function execution will end after
|
||||
|
@ -620,6 +647,42 @@ fn copy_files_into_dir(files: &[PathBuf], target_dir: &Path, b: &Behavior) -> UR
|
|||
Ok(())
|
||||
}
|
||||
|
||||
/// Handle incomplete user/group parings for chown.
|
||||
///
|
||||
/// Returns a Result type with the Err variant containing the error message.
|
||||
///
|
||||
/// # Parameters
|
||||
///
|
||||
/// _path_ must exist.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// If the owner or group are invalid or copy system call fails, we print a verbose error and
|
||||
/// return an empty error value.
|
||||
///
|
||||
fn chown_optional_user_group(path: &Path, b: &Behavior) -> UResult<()> {
|
||||
if b.owner_id.is_some() || b.group_id.is_some() {
|
||||
let meta = match fs::metadata(path) {
|
||||
Ok(meta) => meta,
|
||||
Err(e) => return Err(InstallError::MetadataFailed(e).into()),
|
||||
};
|
||||
|
||||
// GNU coreutils doesn't print chown operations during install with verbose flag.
|
||||
let verbosity = Verbosity {
|
||||
groups_only: b.owner_id.is_none(),
|
||||
level: VerbosityLevel::Normal,
|
||||
};
|
||||
|
||||
match wrap_chown(path, &meta, b.owner_id, b.group_id, false, verbosity) {
|
||||
Ok(msg) if b.verbose && !msg.is_empty() => println!("chown: {msg}"),
|
||||
Ok(_) => {}
|
||||
Err(e) => return Err(InstallError::ChownFailed(path.to_path_buf(), e).into()),
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Copy one file to a new location, changing metadata.
|
||||
///
|
||||
/// Returns a Result type with the Err variant containing the error message.
|
||||
|
@ -702,66 +765,7 @@ fn copy(from: &Path, to: &Path, b: &Behavior) -> UResult<()> {
|
|||
return Err(InstallError::ChmodFailed(to.to_path_buf()).into());
|
||||
}
|
||||
|
||||
if !b.owner.is_empty() {
|
||||
let meta = match fs::metadata(to) {
|
||||
Ok(meta) => meta,
|
||||
Err(e) => return Err(InstallError::MetadataFailed(e).into()),
|
||||
};
|
||||
|
||||
let owner_id = match usr2uid(&b.owner) {
|
||||
Ok(g) => g,
|
||||
_ => return Err(InstallError::NoSuchUser(b.owner.clone()).into()),
|
||||
};
|
||||
let gid = meta.gid();
|
||||
match wrap_chown(
|
||||
to,
|
||||
&meta,
|
||||
Some(owner_id),
|
||||
Some(gid),
|
||||
false,
|
||||
Verbosity {
|
||||
groups_only: false,
|
||||
level: VerbosityLevel::Normal,
|
||||
},
|
||||
) {
|
||||
Ok(n) => {
|
||||
if !n.is_empty() {
|
||||
show_error!("{}", n);
|
||||
}
|
||||
}
|
||||
Err(e) => show_error!("{}", e),
|
||||
}
|
||||
}
|
||||
|
||||
if !b.group.is_empty() {
|
||||
let meta = match fs::metadata(to) {
|
||||
Ok(meta) => meta,
|
||||
Err(e) => return Err(InstallError::MetadataFailed(e).into()),
|
||||
};
|
||||
|
||||
let group_id = match grp2gid(&b.group) {
|
||||
Ok(g) => g,
|
||||
_ => return Err(InstallError::NoSuchGroup(b.group.clone()).into()),
|
||||
};
|
||||
match wrap_chown(
|
||||
to,
|
||||
&meta,
|
||||
Some(group_id),
|
||||
None,
|
||||
false,
|
||||
Verbosity {
|
||||
groups_only: true,
|
||||
level: VerbosityLevel::Normal,
|
||||
},
|
||||
) {
|
||||
Ok(n) => {
|
||||
if !n.is_empty() {
|
||||
show_error!("{}", n);
|
||||
}
|
||||
}
|
||||
Err(e) => show_error!("{}", e),
|
||||
}
|
||||
}
|
||||
chown_optional_user_group(to, b)?;
|
||||
|
||||
if b.preserve_timestamps {
|
||||
let meta = match fs::metadata(from) {
|
||||
|
@ -841,19 +845,11 @@ fn need_copy(from: &Path, to: &Path, b: &Behavior) -> UResult<bool> {
|
|||
|
||||
// TODO: if -P (#1809) and from/to contexts mismatch, return true.
|
||||
|
||||
if !b.owner.is_empty() {
|
||||
let owner_id = match usr2uid(&b.owner) {
|
||||
Ok(id) => id,
|
||||
_ => return Err(InstallError::NoSuchUser(b.owner.clone()).into()),
|
||||
};
|
||||
if let Some(owner_id) = b.owner_id {
|
||||
if owner_id != to_meta.uid() {
|
||||
return Ok(true);
|
||||
}
|
||||
} else if !b.group.is_empty() {
|
||||
let group_id = match grp2gid(&b.group) {
|
||||
Ok(id) => id,
|
||||
_ => return Err(InstallError::NoSuchGroup(b.group.clone()).into()),
|
||||
};
|
||||
} else if let Some(group_id) = b.group_id {
|
||||
if group_id != to_meta.gid() {
|
||||
return Ok(true);
|
||||
}
|
||||
|
|
|
@ -1400,6 +1400,100 @@ fn test_install_dir_req_verbose() {
|
|||
.stdout_contains("install: creating directory 'sub5/a'\ninstall: creating directory 'sub5/a/b'\ninstall: creating directory 'sub5/a/b/c'\n'source_file1' -> 'sub5/a/b/c/file'");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_install_chown_file_invalid() {
|
||||
let scene = TestScenario::new(util_name!());
|
||||
let at = &scene.fixtures;
|
||||
|
||||
let file_1 = "source_file1";
|
||||
at.touch(file_1);
|
||||
|
||||
scene
|
||||
.ucmd()
|
||||
.arg("-o")
|
||||
.arg("test_invalid_user")
|
||||
.arg(file_1)
|
||||
.arg("target_file1")
|
||||
.fails()
|
||||
.stderr_contains("install: invalid user: 'test_invalid_user'");
|
||||
|
||||
scene
|
||||
.ucmd()
|
||||
.arg("-g")
|
||||
.arg("test_invalid_group")
|
||||
.arg(file_1)
|
||||
.arg("target_file1")
|
||||
.fails()
|
||||
.stderr_contains("install: invalid group: 'test_invalid_group'");
|
||||
|
||||
scene
|
||||
.ucmd()
|
||||
.arg("-o")
|
||||
.arg("test_invalid_user")
|
||||
.arg("-g")
|
||||
.arg("test_invalid_group")
|
||||
.arg(file_1)
|
||||
.arg("target_file1")
|
||||
.fails()
|
||||
.stderr_contains("install: invalid user: 'test_invalid_user'");
|
||||
|
||||
scene
|
||||
.ucmd()
|
||||
.arg("-g")
|
||||
.arg("test_invalid_group")
|
||||
.arg("-o")
|
||||
.arg("test_invalid_user")
|
||||
.arg(file_1)
|
||||
.arg("target_file1")
|
||||
.fails()
|
||||
.stderr_contains("install: invalid user: 'test_invalid_user'");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_install_chown_directory_invalid() {
|
||||
let scene = TestScenario::new(util_name!());
|
||||
|
||||
scene
|
||||
.ucmd()
|
||||
.arg("-o")
|
||||
.arg("test_invalid_user")
|
||||
.arg("-d")
|
||||
.arg("dir1/dir2")
|
||||
.fails()
|
||||
.stderr_contains("install: invalid user: 'test_invalid_user'");
|
||||
|
||||
scene
|
||||
.ucmd()
|
||||
.arg("-g")
|
||||
.arg("test_invalid_group")
|
||||
.arg("-d")
|
||||
.arg("dir1/dir2")
|
||||
.fails()
|
||||
.stderr_contains("install: invalid group: 'test_invalid_group'");
|
||||
|
||||
scene
|
||||
.ucmd()
|
||||
.arg("-o")
|
||||
.arg("test_invalid_user")
|
||||
.arg("-g")
|
||||
.arg("test_invalid_group")
|
||||
.arg("-d")
|
||||
.arg("dir1/dir2")
|
||||
.fails()
|
||||
.stderr_contains("install: invalid user: 'test_invalid_user'");
|
||||
|
||||
scene
|
||||
.ucmd()
|
||||
.arg("-g")
|
||||
.arg("test_invalid_group")
|
||||
.arg("-o")
|
||||
.arg("test_invalid_user")
|
||||
.arg("-d")
|
||||
.arg("dir1/dir2")
|
||||
.fails()
|
||||
.stderr_contains("install: invalid user: 'test_invalid_user'");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_install_compare_option() {
|
||||
let scene = TestScenario::new(util_name!());
|
||||
|
|
Loading…
Reference in a new issue