88use filetime:: FileTime ;
99use std:: fs;
1010#[ cfg( target_os = "linux" ) ]
11+ use std:: fs:: File ;
12+ #[ cfg( target_os = "linux" ) ]
13+ use std:: io:: { BufRead , BufReader } ;
14+ #[ cfg( target_os = "linux" ) ]
1115use std:: os:: unix:: ffi:: OsStringExt ;
1216use std:: os:: unix:: fs:: { MetadataExt , PermissionsExt } ;
1317#[ cfg( not( windows) ) ]
@@ -19,7 +23,7 @@ use uucore::process::{getegid, geteuid};
1923use uucore:: selinux:: get_getfattr_output;
2024use uutests:: at_and_ucmd;
2125use uutests:: new_ucmd;
22- use uutests:: util:: { TestScenario , is_ci, run_ucmd_as_root} ;
26+ use uutests:: util:: { is_ci, run_ucmd_as_root, TestScenario } ;
2327use uutests:: util_name;
2428
2529#[ test]
@@ -2545,3 +2549,86 @@ fn test_install_unprivileged_option_u_skips_chown() {
25452549 assert ! ( at. file_exists( dst_ok) ) ;
25462550 assert_eq ! ( at. metadata( dst_ok) . uid( ) , geteuid( ) ) ;
25472551}
2552+
2553+ #[ test]
2554+ #[ cfg( target_os = "linux" ) ]
2555+ fn test_install_set_owner_nonexistent_uid_and_gid ( ) {
2556+ let file = File :: open ( "/etc/login.defs" ) . unwrap ( ) ;
2557+ let reader = BufReader :: new ( file) ;
2558+ let mut uid_min: u32 = 0 ;
2559+ let mut uid_max: u32 = 0 ;
2560+ let mut gid_min: u32 = 0 ;
2561+ let mut gid_max: u32 = 0 ;
2562+ for line in reader. lines ( ) {
2563+ let line = line. unwrap ( ) ;
2564+ if line. starts_with ( "UID_MIN" ) {
2565+ let tokens: Vec < & str > = line. split_whitespace ( ) . collect ( ) ;
2566+ uid_min = tokens[ 1 ] . parse ( ) . unwrap ( ) ;
2567+ }
2568+ if line. starts_with ( "UID_MAX" ) {
2569+ let tokens: Vec < & str > = line. split_whitespace ( ) . collect ( ) ;
2570+ uid_max = tokens[ 1 ] . parse ( ) . unwrap ( ) ;
2571+ }
2572+ if line. starts_with ( "GID_MIN" ) {
2573+ let tokens: Vec < & str > = line. split_whitespace ( ) . collect ( ) ;
2574+ gid_min = tokens[ 1 ] . parse ( ) . unwrap ( ) ;
2575+ }
2576+ if line. starts_with ( "GID_MAX" ) {
2577+ let tokens: Vec < & str > = line. split_whitespace ( ) . collect ( ) ;
2578+ gid_max = tokens[ 1 ] . parse ( ) . unwrap ( ) ;
2579+ }
2580+ }
2581+ let file = File :: open ( "/etc/passwd" ) . unwrap ( ) ;
2582+ let reader = BufReader :: new ( file) ;
2583+
2584+ let mut uids: Vec < u32 > = vec ! [ ] ;
2585+ let mut gids: Vec < u32 > = vec ! [ ] ;
2586+ for line in reader. lines ( ) {
2587+ let line = line. unwrap ( ) ;
2588+ let tokens: Vec < & str > = line. split ( ':' ) . collect ( ) ;
2589+ let uid: u32 = tokens[ 2 ] . parse ( ) . unwrap ( ) ;
2590+ if ( uid_min..=uid_max) . contains ( & uid) {
2591+ uids. push ( uid) ;
2592+ }
2593+ let gid: u32 = tokens[ 3 ] . parse ( ) . unwrap ( ) ;
2594+ if ( gid_min..=gid_max) . contains ( & gid) {
2595+ gids. push ( gid) ;
2596+ }
2597+ }
2598+ uids. sort_unstable ( ) ;
2599+
2600+ let next_uid = if let Some ( uid) = uids. last ( ) {
2601+ * uid + 1
2602+ } else {
2603+ uid_min
2604+ } ;
2605+
2606+ let next_gid = if let Some ( gid) = gids. last ( ) {
2607+ * gid + 1
2608+ } else {
2609+ gid_min
2610+ } ;
2611+
2612+ let ts = TestScenario :: new ( util_name ! ( ) ) ;
2613+ let at = & ts. fixtures ;
2614+ at. touch ( "a" ) ;
2615+
2616+ if let Ok ( result) = run_ucmd_as_root (
2617+ & ts,
2618+ & [
2619+ format ! ( "-o{next_uid}" ) . as_str ( ) ,
2620+ format ! ( "-g{next_gid}" ) . as_str ( ) ,
2621+ "a" ,
2622+ "b" ,
2623+ ] ,
2624+ ) {
2625+ result. success ( ) ;
2626+ assert ! ( at. file_exists( "b" ) ) ;
2627+
2628+ let metadata = fs:: metadata ( at. plus ( "b" ) ) . unwrap ( ) ;
2629+ assert_eq ! ( metadata. uid( ) , next_uid) ;
2630+ assert_eq ! ( metadata. gid( ) , next_gid) ;
2631+ } else {
2632+ println ! ( "Test skipped; requires root user" ) ;
2633+ }
2634+ }
0 commit comments