1
Fork 0
mirror of https://github.com/RGBCube/uutils-coreutils synced 2025-07-28 11:37:44 +00:00

Merge pull request #5735 from sylvestre/install-root

install: Manages permissions when run as root
This commit is contained in:
Daniel Hofstetter 2023-12-28 17:24:59 +01:00 committed by GitHub
commit 76fedf60ce
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
2 changed files with 85 additions and 29 deletions

View file

@ -10,15 +10,6 @@ mod mode;
use clap::{crate_version, Arg, ArgAction, ArgMatches, Command};
use file_diff::diff;
use filetime::{set_file_times, FileTime};
use uucore::backup_control::{self, BackupMode};
use uucore::display::Quotable;
use uucore::entries::{grp2gid, usr2uid};
use uucore::error::{FromIo, UError, UIoError, UResult, UUsageError};
use uucore::fs::dir_strip_dot_for_creation;
use uucore::mode::get_umask;
use uucore::perms::{wrap_chown, Verbosity, VerbosityLevel};
use uucore::{format_usage, help_about, help_usage, show, show_error, show_if_err, uio_error};
use std::error::Error;
use std::fmt::{Debug, Display};
use std::fs;
@ -28,8 +19,15 @@ use std::os::unix::fs::MetadataExt;
use std::os::unix::prelude::OsStrExt;
use std::path::{Path, PathBuf, MAIN_SEPARATOR};
use std::process;
#[cfg(not(target_os = "windows"))]
use uucore::backup_control::{self, BackupMode};
use uucore::display::Quotable;
use uucore::entries::{grp2gid, usr2uid};
use uucore::error::{FromIo, UError, UIoError, UResult, UUsageError};
use uucore::fs::dir_strip_dot_for_creation;
use uucore::mode::get_umask;
use uucore::perms::{wrap_chown, Verbosity, VerbosityLevel};
use uucore::process::{getegid, geteuid};
use uucore::{format_usage, help_about, help_usage, show, show_error, show_if_err, uio_error};
const DEFAULT_MODE: u32 = 0o755;
const DEFAULT_STRIP_PROGRAM: &str = "strip";
@ -665,6 +663,7 @@ fn copy_files_into_dir(files: &[PathBuf], target_dir: &Path, b: &Behavior) -> UR
/// Handle incomplete user/group parings for chown.
///
/// Returns a Result type with the Err variant containing the error message.
/// If the user is root, revert the uid & gid
///
/// # Parameters
///
@ -676,24 +675,32 @@ fn copy_files_into_dir(files: &[PathBuf], target_dir: &Path, b: &Behavior) -> UR
/// 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) {
// Determine the owner and group IDs to be used for chown.
let (owner_id, group_id) = if b.owner_id.is_some() || b.group_id.is_some() {
(b.owner_id, b.group_id)
} else if geteuid() == 0 {
// Special case for root user.
(Some(0), Some(0))
} else {
// No chown operation needed.
return Ok(());
};
let meta = match fs::metadata(path) {
Ok(meta) => meta,
Err(e) => return Err(InstallError::MetadataFailed(e).into()),
};
match wrap_chown(path, &meta, owner_id, 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(())
}
@ -916,55 +923,74 @@ fn copy(from: &Path, to: &Path, b: &Behavior) -> UResult<()> {
/// Crashes the program if a nonexistent owner or group is specified in _b_.
///
fn need_copy(from: &Path, to: &Path, b: &Behavior) -> UResult<bool> {
// Attempt to retrieve metadata for the source file.
// If this fails, assume the file needs to be copied.
let from_meta = match fs::metadata(from) {
Ok(meta) => meta,
Err(_) => return Ok(true),
};
// Attempt to retrieve metadata for the destination file.
// If this fails, assume the file needs to be copied.
let to_meta = match fs::metadata(to) {
Ok(meta) => meta,
Err(_) => return Ok(true),
};
// setuid || setgid || sticky
// Define special file mode bits (setuid, setgid, sticky).
let extra_mode: u32 = 0o7000;
// Define all file mode bits (including permissions).
// setuid || setgid || sticky || permissions
let all_modes: u32 = 0o7777;
// Check if any special mode bits are set in the specified mode,
// source file mode, or destination file mode.
if b.specified_mode.unwrap_or(0) & extra_mode != 0
|| from_meta.mode() & extra_mode != 0
|| to_meta.mode() & extra_mode != 0
{
return Ok(true);
}
// Check if the mode of the destination file differs from the specified mode.
if b.mode() != to_meta.mode() & all_modes {
return Ok(true);
}
// Check if either the source or destination is not a file.
if !from_meta.is_file() || !to_meta.is_file() {
return Ok(true);
}
// Check if the file sizes differ.
if from_meta.len() != to_meta.len() {
return Ok(true);
}
// TODO: if -P (#1809) and from/to contexts mismatch, return true.
// Check if the owner ID is specified and differs from the destination file's owner.
if let Some(owner_id) = b.owner_id {
if owner_id != to_meta.uid() {
return Ok(true);
}
} else if let Some(group_id) = b.group_id {
}
// Check if the group ID is specified and differs from the destination file's group.
if let Some(group_id) = b.group_id {
if group_id != to_meta.gid() {
return Ok(true);
}
} else {
#[cfg(not(target_os = "windows"))]
// Check if the destination file's owner or group
// differs from the effective user/group ID of the process.
if to_meta.uid() != geteuid() || to_meta.gid() != getegid() {
return Ok(true);
}
}
// Check if the contents of the source and destination files differ.
if !diff(from.to_str().unwrap(), to.to_str().unwrap()) {
return Ok(true);
}

View file

@ -4,9 +4,10 @@
// file that was distributed with this source code.
// spell-checker:ignore (words) helloworld nodir objdump n'source
use crate::common::util::{is_ci, TestScenario};
use crate::common::util::{is_ci, run_ucmd_as_root, TestScenario};
use filetime::FileTime;
use std::os::unix::fs::PermissionsExt;
use std::fs;
use std::os::unix::fs::{MetadataExt, PermissionsExt};
#[cfg(not(any(windows, target_os = "freebsd")))]
use std::process::Command;
#[cfg(any(target_os = "linux", target_os = "android"))]
@ -1613,3 +1614,32 @@ fn test_target_file_ends_with_slash() {
.fails()
.stderr_contains("failed to access 'dir/target_file/': Not a directory");
}
#[test]
fn test_install_root_combined() {
let ts = TestScenario::new(util_name!());
let at = ts.fixtures.clone();
at.touch("a");
at.touch("c");
let run_and_check = |args: &[&str], target: &str, expected_uid: u32, expected_gid: u32| {
if let Ok(result) = run_ucmd_as_root(&ts, args) {
result.success();
assert!(at.file_exists(target));
let metadata = fs::metadata(at.plus(target)).unwrap();
assert_eq!(metadata.uid(), expected_uid);
assert_eq!(metadata.gid(), expected_gid);
} else {
print!("Test skipped; requires root user");
}
};
run_and_check(&["-Cv", "-o1", "-g1", "a", "b"], "b", 1, 1);
run_and_check(&["-Cv", "-o2", "-g1", "a", "b"], "b", 2, 1);
run_and_check(&["-Cv", "-o2", "-g2", "a", "b"], "b", 2, 2);
run_and_check(&["-Cv", "-o2", "c", "d"], "d", 2, 0);
run_and_check(&["-Cv", "c", "d"], "d", 0, 0);
run_and_check(&["-Cv", "c", "d"], "d", 0, 0);
}