@@ -398,7 +398,7 @@ fn test_chown_symlink() {
398398// Tests the -R flag
399399#[ test]
400400fn test_chown_recursive_basic ( ) {
401- let test_dir = & format ! ( "{}/test_chown_nonexistent " , env!( "CARGO_TARGET_TMPDIR" ) ) ;
401+ let test_dir = & format ! ( "{}/test_chown_recursive_basic " , env!( "CARGO_TARGET_TMPDIR" ) ) ;
402402 let f = & format ! ( "{test_dir}/f" ) ;
403403 let f_g = & format ! ( "{test_dir}/f/g" ) ;
404404 let f_g_h = & format ! ( "{test_dir}/f/g/h" ) ;
@@ -420,15 +420,223 @@ fn test_chown_recursive_basic() {
420420 0 ,
421421 ) ;
422422
423+ // Recursively change the owner and group of f, f/g and f/g/h
423424 assert_eq ! ( target_group, fs:: metadata( f) . unwrap( ) . gid( ) ) ;
424425 assert_eq ! ( target_group, fs:: metadata( f_g) . unwrap( ) . gid( ) ) ;
425426 assert_eq ! ( target_group, fs:: metadata( f_g_h) . unwrap( ) . gid( ) ) ;
427+ if is_root ( ) {
428+ let target_owner = target_owner. unwrap ( ) ;
429+ assert_eq ! ( target_owner, fs:: metadata( f) . unwrap( ) . uid( ) ) ;
430+ assert_eq ! ( target_owner, fs:: metadata( f_g) . unwrap( ) . uid( ) ) ;
431+ assert_eq ! ( target_owner, fs:: metadata( f_g_h) . unwrap( ) . uid( ) ) ;
432+ }
433+
434+ fs:: remove_dir_all ( test_dir) . unwrap ( ) ;
435+ }
436+
437+ // Tests the -P flag
438+ #[ test]
439+ fn test_chown_recursive_follow_none ( ) {
440+ let test_dir = & format ! (
441+ "{}/test_chown_recursive_follow_none" ,
442+ env!( "CARGO_TARGET_TMPDIR" )
443+ ) ;
444+ let f = & format ! ( "{test_dir}/f" ) ;
445+ let f_g = & format ! ( "{test_dir}/f/g" ) ;
446+ let f_g_h = & format ! ( "{test_dir}/f/g/h" ) ;
447+ let g = & format ! ( "{test_dir}/g" ) ;
448+ let g_symlink = & format ! ( "{test_dir}/g/symlink" ) ;
449+
450+ fs:: create_dir ( test_dir) . unwrap ( ) ;
451+ fs:: create_dir_all ( f_g) . unwrap ( ) ;
452+ fs:: File :: create ( f_g_h) . unwrap ( ) ;
453+ fs:: create_dir_all ( g) . unwrap ( ) ;
454+ unix:: fs:: symlink ( f, g_symlink) . unwrap ( ) ;
455+
456+ let original_uid = unsafe { libc:: geteuid ( ) } ;
457+ let original_gid = unsafe { libc:: getegid ( ) } ;
458+
459+ let ( target_owner, target_group) = select_target_ownergroup ( ) ;
460+ let target_owner_str = match target_owner {
461+ Some ( uid) => uid. to_string ( ) ,
462+ None => String :: from ( "" ) ,
463+ } ;
464+
465+ // chown -RP owner:group g
466+ // Only g and g/symlink should be changed. The symlink to f should not be followed.
467+ chown_test (
468+ & [ "-RP" , & format ! ( "{target_owner_str}:{target_group}" ) , g] ,
469+ "" ,
470+ "" ,
471+ 0 ,
472+ ) ;
473+
474+ // f, f/g and f/g/h must be unchanged
475+ assert_eq ! ( original_gid, fs:: metadata( f) . unwrap( ) . gid( ) ) ;
476+ assert_eq ! ( original_gid, fs:: metadata( f_g) . unwrap( ) . gid( ) ) ;
477+ assert_eq ! ( original_gid, fs:: metadata( f_g_h) . unwrap( ) . gid( ) ) ;
478+ if is_root ( ) {
479+ assert_eq ! ( original_uid, fs:: metadata( f) . unwrap( ) . uid( ) ) ;
480+ assert_eq ! ( original_uid, fs:: metadata( f_g) . unwrap( ) . uid( ) ) ;
481+ assert_eq ! ( original_uid, fs:: metadata( f_g_h) . unwrap( ) . uid( ) ) ;
482+ }
483+
484+ // g and g/symlink should be changed
485+ assert_eq ! ( target_group, fs:: metadata( g) . unwrap( ) . gid( ) ) ;
486+ assert_eq ! ( target_group, fs:: symlink_metadata( g_symlink) . unwrap( ) . gid( ) ) ;
487+ if is_root ( ) {
488+ let target_owner = target_owner. unwrap ( ) ;
489+ assert_eq ! ( target_owner, fs:: metadata( g) . unwrap( ) . uid( ) ) ;
490+ assert_eq ! ( target_owner, fs:: symlink_metadata( g_symlink) . unwrap( ) . uid( ) ) ;
491+ }
492+
493+ fs:: remove_dir_all ( test_dir) . unwrap ( ) ;
494+ }
495+
496+ // Tests the -L flag
497+ #[ test]
498+ fn test_chown_recursive_follow_symlinks ( ) {
499+ let test_dir = & format ! (
500+ "{}/test_chown_recursive_follow_symlinks" ,
501+ env!( "CARGO_TARGET_TMPDIR" )
502+ ) ;
503+ let f = & format ! ( "{test_dir}/f" ) ;
504+ let f_g = & format ! ( "{test_dir}/f/g" ) ;
505+ let f_g_h = & format ! ( "{test_dir}/f/g/h" ) ;
506+ let g = & format ! ( "{test_dir}/g" ) ;
507+ let g_symlink = & format ! ( "{test_dir}/g/symlink" ) ;
508+
509+ fs:: create_dir ( test_dir) . unwrap ( ) ;
510+ fs:: create_dir_all ( f_g) . unwrap ( ) ;
511+ fs:: File :: create ( f_g_h) . unwrap ( ) ;
512+ fs:: create_dir_all ( g) . unwrap ( ) ;
513+ unix:: fs:: symlink ( f, g_symlink) . unwrap ( ) ;
514+
515+ let original_uid = unsafe { libc:: geteuid ( ) } ;
516+ let original_gid = unsafe { libc:: getegid ( ) } ;
517+
518+ let ( target_owner, target_group) = select_target_ownergroup ( ) ;
519+ let target_owner_str = match target_owner {
520+ Some ( uid) => uid. to_string ( ) ,
521+ None => String :: from ( "" ) ,
522+ } ;
523+
524+ // chown -RL owner:group g
525+ // Follow the symlink to f and change everything. `g/symlink` remains unchanged because f was
526+ // changed in its place.
527+ chown_test (
528+ & [ "-RL" , & format ! ( "{target_owner_str}:{target_group}" ) , g] ,
529+ "" ,
530+ "" ,
531+ 0 ,
532+ ) ;
426533
534+ // g/symlink should be unchanged
535+ assert_eq ! ( original_gid, fs:: symlink_metadata( g_symlink) . unwrap( ) . gid( ) ) ;
536+ if is_root ( ) {
537+ assert_eq ! ( original_uid, fs:: symlink_metadata( g_symlink) . unwrap( ) . uid( ) ) ;
538+ }
539+
540+ // f, g, f/g and f/g/h should be changed
541+ assert_eq ! ( target_group, fs:: metadata( f) . unwrap( ) . gid( ) ) ;
542+ assert_eq ! ( target_group, fs:: metadata( f_g) . unwrap( ) . gid( ) ) ;
543+ assert_eq ! ( target_group, fs:: metadata( f_g_h) . unwrap( ) . gid( ) ) ;
544+ assert_eq ! ( target_group, fs:: metadata( g) . unwrap( ) . gid( ) ) ;
427545 if is_root ( ) {
428546 let target_owner = target_owner. unwrap ( ) ;
429547 assert_eq ! ( target_owner, fs:: metadata( f) . unwrap( ) . uid( ) ) ;
430548 assert_eq ! ( target_owner, fs:: metadata( f_g) . unwrap( ) . uid( ) ) ;
431549 assert_eq ! ( target_owner, fs:: metadata( f_g_h) . unwrap( ) . uid( ) ) ;
550+ assert_eq ! ( target_owner, fs:: metadata( g) . unwrap( ) . uid( ) ) ;
551+ }
552+
553+ // Recreate the files
554+ fs:: remove_dir_all ( test_dir) . unwrap ( ) ;
555+ fs:: create_dir ( test_dir) . unwrap ( ) ;
556+ fs:: create_dir_all ( f_g) . unwrap ( ) ;
557+ fs:: File :: create ( f_g_h) . unwrap ( ) ;
558+ fs:: create_dir_all ( g) . unwrap ( ) ;
559+ unix:: fs:: symlink ( f, g_symlink) . unwrap ( ) ;
560+
561+ // chown -RLh owner:group g
562+ // Follow the symlink to f and change everything. `f` is the one unchanged when -h is used
563+ chown_test (
564+ & [ "-RLh" , & format ! ( "{target_owner_str}:{target_group}" ) , g] ,
565+ "" ,
566+ "" ,
567+ 0 ,
568+ ) ;
569+
570+ // f should be unchanged
571+ assert_eq ! ( original_gid, fs:: metadata( f) . unwrap( ) . gid( ) ) ;
572+ if is_root ( ) {
573+ assert_eq ! ( original_uid, fs:: metadata( f) . unwrap( ) . uid( ) ) ;
574+ }
575+
576+ // f, g, f/g and f/g/h should be changed
577+ assert_eq ! ( target_group, fs:: symlink_metadata( g_symlink) . unwrap( ) . gid( ) ) ;
578+ assert_eq ! ( target_group, fs:: metadata( f_g) . unwrap( ) . gid( ) ) ;
579+ assert_eq ! ( target_group, fs:: metadata( f_g_h) . unwrap( ) . gid( ) ) ;
580+ assert_eq ! ( target_group, fs:: metadata( g) . unwrap( ) . gid( ) ) ;
581+ if is_root ( ) {
582+ let target_owner = target_owner. unwrap ( ) ;
583+ assert_eq ! ( target_owner, fs:: symlink_metadata( g_symlink) . unwrap( ) . uid( ) ) ;
584+ assert_eq ! ( target_owner, fs:: metadata( f_g) . unwrap( ) . uid( ) ) ;
585+ assert_eq ! ( target_owner, fs:: metadata( f_g_h) . unwrap( ) . uid( ) ) ;
586+ assert_eq ! ( target_owner, fs:: metadata( g) . unwrap( ) . uid( ) ) ;
587+ }
588+
589+ fs:: remove_dir_all ( test_dir) . unwrap ( ) ;
590+ }
591+
592+ // Changing ownership should propagate through hard links
593+ #[ test]
594+ fn test_chown_hardlink ( ) {
595+ let test_dir = & format ! ( "{}/test_chown_hardlink" , env!( "CARGO_TARGET_TMPDIR" ) ) ;
596+ let f = & format ! ( "{test_dir}/f" ) ;
597+ let hardlink = & format ! ( "{test_dir}/hardlink" ) ;
598+
599+ fs:: create_dir ( test_dir) . unwrap ( ) ;
600+ fs:: File :: create ( f) . unwrap ( ) ;
601+ fs:: hard_link ( f, hardlink) . unwrap ( ) ;
602+
603+ let ( target_owner, target_group) = select_target_ownergroup ( ) ;
604+ let target_owner_str = match target_owner {
605+ Some ( uid) => uid. to_string ( ) ,
606+ None => String :: from ( "" ) ,
607+ } ;
608+
609+ chown_test (
610+ & [ & format ! ( "{target_owner_str}:{target_group}" ) , f] ,
611+ "" ,
612+ "" ,
613+ 0 ,
614+ ) ;
615+
616+ // `hardlink` should be changed
617+ assert_eq ! ( target_group, fs:: metadata( hardlink) . unwrap( ) . gid( ) ) ;
618+ if is_root ( ) {
619+ assert_eq ! ( target_owner. unwrap( ) , fs:: metadata( hardlink) . unwrap( ) . uid( ) ) ;
620+ }
621+
622+ // Recreate f and the hard link
623+ fs:: remove_file ( f) . unwrap ( ) ;
624+ fs:: remove_file ( hardlink) . unwrap ( ) ;
625+ fs:: File :: create ( f) . unwrap ( ) ;
626+ fs:: hard_link ( f, hardlink) . unwrap ( ) ;
627+
628+ // Test changing ownership using the hard link
629+ chown_test (
630+ & [ & format ! ( "{target_owner_str}:{target_group}" ) , hardlink] ,
631+ "" ,
632+ "" ,
633+ 0 ,
634+ ) ;
635+
636+ // `f` should be changed
637+ assert_eq ! ( target_group, fs:: metadata( f) . unwrap( ) . gid( ) ) ;
638+ if is_root ( ) {
639+ assert_eq ! ( target_owner. unwrap( ) , fs:: metadata( f) . unwrap( ) . uid( ) ) ;
432640 }
433641
434642 fs:: remove_dir_all ( test_dir) . unwrap ( ) ;
0 commit comments