mirror of
https://github.com/RGBCube/uutils-coreutils
synced 2025-07-28 03:27:44 +00:00
Merge pull request #5735 from sylvestre/install-root
install: Manages permissions when run as root
This commit is contained in:
commit
76fedf60ce
2 changed files with 85 additions and 29 deletions
|
@ -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,23 +675,31 @@ 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,
|
||||
};
|
||||
|
||||
// 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,
|
||||
};
|
||||
// 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(());
|
||||
};
|
||||
|
||||
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()),
|
||||
}
|
||||
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);
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue