Skip to content

Commit 8ad88b0

Browse files
install: add -U (unprivileged) option
1 parent 821c674 commit 8ad88b0

File tree

4 files changed

+72
-5
lines changed

4 files changed

+72
-5
lines changed

src/uu/install/locales/en-US.ftl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ install-help-verbose = explain what is being done
1919
install-help-preserve-context = preserve security context
2020
install-help-context = set security context of files and directories
2121
install-help-default-context = set SELinux security context of destination file and each created directory to default type
22+
install-help-unprivileged = do not require elevated privileges to change the owner, the group, or the file flags of the destination
2223
2324
# Error messages
2425
install-error-dir-needs-arg = { $util_name } with -d requires at least one argument.

src/uu/install/locales/fr-FR.ftl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ install-help-verbose = expliquer ce qui est fait
1919
install-help-preserve-context = préserver le contexte de sécurité
2020
install-help-context = définir le contexte de sécurité des fichiers et répertoires
2121
install-help-default-context = définir le contexte de sécurité SELinux du fichier de destination et de chaque répertoire créé au type par défaut
22+
install-help-unprivileged = ne pas nécessiter de privilèges élevés pour changer le propriétaire, le groupe ou les attributs du fichier de destination
2223
2324
# Messages d'erreur
2425
install-error-dir-needs-arg = { $util_name } avec -d nécessite au moins un argument.

src/uu/install/src/install.rs

Lines changed: 24 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ pub struct Behavior {
6262
preserve_context: bool,
6363
context: Option<String>,
6464
default_context: bool,
65+
unprivileged: bool,
6566
}
6667

6768
#[derive(Error, Debug)]
@@ -163,6 +164,7 @@ static OPT_VERBOSE: &str = "verbose";
163164
static OPT_PRESERVE_CONTEXT: &str = "preserve-context";
164165
static OPT_CONTEXT: &str = "context";
165166
static OPT_DEFAULT_CONTEXT: &str = "default-context";
167+
static OPT_UNPRIVILEGED: &str = "unprivileged";
166168

167169
static ARG_FILES: &str = "files";
168170

@@ -317,6 +319,13 @@ pub fn uu_app() -> Command {
317319
.value_hint(clap::ValueHint::AnyPath)
318320
.value_parser(clap::value_parser!(OsString)),
319321
)
322+
.arg(
323+
Arg::new(OPT_UNPRIVILEGED)
324+
.short('U')
325+
.long(OPT_UNPRIVILEGED)
326+
.help(translate!("install-help-unprivileged"))
327+
.action(ArgAction::SetTrue),
328+
)
320329
}
321330

322331
/// Determine behavior, given command line arguments.
@@ -416,6 +425,7 @@ fn behavior(matches: &ArgMatches) -> UResult<Behavior> {
416425

417426
let context = matches.get_one::<String>(OPT_CONTEXT).cloned();
418427
let default_context = matches.get_flag(OPT_DEFAULT_CONTEXT);
428+
let unprivileged = matches.get_flag(OPT_UNPRIVILEGED);
419429

420430
Ok(Behavior {
421431
main_function,
@@ -439,6 +449,7 @@ fn behavior(matches: &ArgMatches) -> UResult<Behavior> {
439449
preserve_context: matches.get_flag(OPT_PRESERVE_CONTEXT),
440450
context,
441451
default_context,
452+
unprivileged,
442453
})
443454
}
444455

@@ -498,7 +509,9 @@ fn directory(paths: &[OsString], b: &Behavior) -> UResult<()> {
498509
continue;
499510
}
500511

501-
show_if_err!(chown_optional_user_group(path, b));
512+
if !b.unprivileged {
513+
show_if_err!(chown_optional_user_group(path, b));
514+
}
502515

503516
// Set SELinux context for directory if needed
504517
#[cfg(feature = "selinux")]
@@ -922,7 +935,9 @@ fn set_ownership_and_permissions(to: &Path, b: &Behavior) -> UResult<()> {
922935
return Err(InstallError::ChmodFailed(to.to_path_buf()).into());
923936
}
924937

925-
chown_optional_user_group(to, b)?;
938+
if !b.unprivileged {
939+
chown_optional_user_group(to, b)?;
940+
}
926941

927942
Ok(())
928943
}
@@ -1124,18 +1139,22 @@ fn need_copy(from: &Path, to: &Path, b: &Behavior) -> bool {
11241139
// TODO: if -P (#1809) and from/to contexts mismatch, return true.
11251140

11261141
// Check if the owner ID is specified and differs from the destination file's owner.
1127-
if let Some(owner_id) = b.owner_id {
1142+
if !b.unprivileged
1143+
&& let Some(owner_id) = b.owner_id
1144+
{
11281145
if owner_id != to_meta.uid() {
11291146
return true;
11301147
}
11311148
}
11321149

11331150
// Check if the group ID is specified and differs from the destination file's group.
1134-
if let Some(group_id) = b.group_id {
1151+
if !b.unprivileged
1152+
&& let Some(group_id) = b.group_id
1153+
{
11351154
if group_id != to_meta.gid() {
11361155
return true;
11371156
}
1138-
} else if needs_copy_for_ownership(to, &to_meta) {
1157+
} else if !b.unprivileged && needs_copy_for_ownership(to, &to_meta) {
11391158
return true;
11401159
}
11411160

tests/by-util/test_install.rs

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2489,3 +2489,49 @@ fn test_install_non_utf8_paths() {
24892489

24902490
ucmd.arg("-D").arg(source_file).arg(&target_path).succeeds();
24912491
}
2492+
2493+
#[test]
2494+
fn test_install_unprivileged_combined() {
2495+
let ts = TestScenario::new(util_name!());
2496+
let at = &ts.fixtures;
2497+
at.touch("a");
2498+
let uid = geteuid();
2499+
let gid = getegid();
2500+
2501+
let run_and_check =
2502+
|args: &[&str], target: &str, expected_uid: u32, expected_gid: u32, expected_mode: u32| {
2503+
if let Ok(result) = run_ucmd_as_root(&ts, args) {
2504+
result.success();
2505+
assert!(at.file_exists(target) || at.dir_exists(target));
2506+
2507+
let metadata = fs::metadata(at.plus(target)).unwrap();
2508+
assert_eq!(metadata.uid(), expected_uid);
2509+
assert_eq!(metadata.gid(), expected_gid);
2510+
println!("Expected mode: {:o}", expected_mode);
2511+
println!("Actual mode: {:o}", metadata.mode());
2512+
assert_eq!(metadata.mode() & 0o7777, expected_mode);
2513+
} else {
2514+
print!("Test skipped; requires root user");
2515+
}
2516+
};
2517+
2518+
// uid/gid should not change when run as unprivileged user
2519+
run_and_check(
2520+
&["-UCv", "-m644", "-o1", "-g1", "a", "b"],
2521+
"b",
2522+
uid,
2523+
gid,
2524+
0o644,
2525+
);
2526+
// mode changes should still apply
2527+
run_and_check(&["-Cv", "-m666", "a", "d"], "d", uid, gid, 0o666);
2528+
2529+
// Same on directories
2530+
run_and_check(
2531+
&["-UCv", "-m755", "-o1", "-g1", "-d", "dir1/dir2"],
2532+
"dir1/dir2",
2533+
uid,
2534+
gid,
2535+
0o755,
2536+
);
2537+
}

0 commit comments

Comments
 (0)