@@ -1159,6 +1159,21 @@ impl Options {
11591159 None
11601160 } ;
11611161
1162+ // -Z/--context conflicts with explicit --preserve=context but overrides implicit (from -a)
1163+ if set_selinux_context || context. is_some ( ) {
1164+ match attributes. context {
1165+ Preserve :: Yes { required : true } => {
1166+ return Err ( CpError :: Error ( translate ! (
1167+ "cp-error-selinux-context-conflict"
1168+ ) ) ) ;
1169+ }
1170+ Preserve :: Yes { required : false } => {
1171+ attributes. context = Preserve :: No { explicit : false } ;
1172+ }
1173+ Preserve :: No { .. } => { }
1174+ }
1175+ }
1176+
11621177 let options = Self {
11631178 attributes_only : matches. get_flag ( options:: ATTRIBUTES_ONLY ) ,
11641179 copy_contents : matches. get_flag ( options:: COPY_CONTENTS ) ,
@@ -1550,7 +1565,7 @@ fn copy_source(
15501565 if options. parents {
15511566 for ( x, y) in aligned_ancestors ( source, dest. as_path ( ) ) {
15521567 if let Ok ( src) = canonicalize ( x, MissingHandling :: Normal , ResolveMode :: Physical ) {
1553- copy_attributes ( & src, y, & options. attributes , false ) ?;
1568+ copy_attributes ( & src, y, & options. attributes , false , options . set_selinux_context ) ?;
15541569 }
15551570 }
15561571 }
@@ -1671,12 +1686,27 @@ fn handle_preserve<F: Fn() -> CopyResult<()>>(p: Preserve, f: F) -> CopyResult<(
16711686 Ok ( ( ) )
16721687}
16731688
1689+ #[ cfg( all( feature = "selinux" , target_os = "linux" ) ) ]
1690+ pub ( crate ) fn set_selinux_context ( path : & Path , context : Option < & String > ) -> CopyResult < ( ) > {
1691+ if !uucore:: selinux:: is_selinux_enabled ( ) {
1692+ return Ok ( ( ) ) ;
1693+ }
1694+
1695+ match uucore:: selinux:: set_selinux_security_context ( path, context) {
1696+ Ok ( ( ) ) => Ok ( ( ) ) ,
1697+ Err ( uucore:: selinux:: SeLinuxError :: OperationNotSupported ) => Ok ( ( ) ) ,
1698+ Err ( e) => Err ( CpError :: Error (
1699+ translate ! ( "cp-error-selinux-error" , "error" => e) ,
1700+ ) ) ,
1701+ }
1702+ }
1703+
16741704/// Copies extended attributes (xattrs) from `source` to `dest`, ensuring that `dest` is temporarily
16751705/// user-writable if needed and restoring its original permissions afterward. This avoids "Operation
16761706/// not permitted" errors on read-only files. Returns an error if permission or metadata operations fail,
16771707/// or if xattr copying fails.
16781708#[ cfg( all( unix, not( target_os = "android" ) ) ) ]
1679- fn copy_extended_attrs ( source : & Path , dest : & Path ) -> CopyResult < ( ) > {
1709+ fn copy_extended_attrs ( source : & Path , dest : & Path , skip_selinux : bool ) -> CopyResult < ( ) > {
16801710 let metadata = fs:: symlink_metadata ( dest) ?;
16811711
16821712 // Check if the destination file is currently read-only for the user.
@@ -1692,7 +1722,13 @@ fn copy_extended_attrs(source: &Path, dest: &Path) -> CopyResult<()> {
16921722
16931723 // Perform the xattr copy and capture any potential error,
16941724 // so we can restore permissions before returning.
1695- let copy_xattrs_result = copy_xattrs ( source, dest) ;
1725+ let copy_xattrs_result = if skip_selinux {
1726+ // When -Z is used, skip copying security.selinux xattr so that
1727+ // the default context can be set instead of preserving from source
1728+ copy_xattrs_skip_selinux ( source, dest)
1729+ } else {
1730+ copy_xattrs ( source, dest)
1731+ } ;
16961732
16971733 // Restore read-only if we changed it.
16981734 if was_readonly {
@@ -1712,12 +1748,31 @@ fn copy_extended_attrs(source: &Path, dest: &Path) -> CopyResult<()> {
17121748 Ok ( ( ) )
17131749}
17141750
1751+ /// Copy extended attributes but skip security.selinux
1752+ #[ cfg( all( unix, not( target_os = "android" ) ) ) ]
1753+ fn copy_xattrs_skip_selinux ( source : & Path , dest : & Path ) -> std:: io:: Result < ( ) > {
1754+ for attr_name in xattr:: list ( source) ? {
1755+ // Skip security.selinux when -Z is used to set default context
1756+ if attr_name. to_string_lossy ( ) == "security.selinux" {
1757+ continue ;
1758+ }
1759+ if let Some ( value) = xattr:: get ( source, & attr_name) ? {
1760+ xattr:: set ( dest, & attr_name, & value) ?;
1761+ }
1762+ }
1763+ Ok ( ( ) )
1764+ }
1765+
17151766/// Copy the specified attributes from one path to another.
1767+ /// If `skip_selinux_xattr` is true, the security.selinux xattr will not be copied
1768+ /// (used when -Z is specified to set the default context instead).
1769+ #[ allow( unused_variables) ]
17161770pub ( crate ) fn copy_attributes (
17171771 source : & Path ,
17181772 dest : & Path ,
17191773 attributes : & Attributes ,
17201774 dest_is_freshly_created_dir : bool ,
1775+ skip_selinux_xattr : bool ,
17211776) -> CopyResult < ( ) > {
17221777 let context = & * format ! ( "{} -> {}" , source. quote( ) , dest. quote( ) ) ;
17231778 let source_metadata =
@@ -1822,9 +1877,10 @@ pub(crate) fn copy_attributes(
18221877 handle_preserve ( attributes. xattr , || -> CopyResult < ( ) > {
18231878 #[ cfg( all( unix, not( target_os = "android" ) ) ) ]
18241879 {
1825- copy_extended_attrs ( source, dest) ?;
1880+ copy_extended_attrs ( source, dest, skip_selinux_xattr ) ?;
18261881 }
18271882 #[ cfg( not( all( unix, not( target_os = "android" ) ) ) ) ]
1883+ #[ allow( unused_variables) ]
18281884 {
18291885 // The documentation for GNU cp states:
18301886 //
@@ -2576,33 +2632,44 @@ fn copy_file(
25762632 fs:: set_permissions ( dest, dest_permissions) . ok ( ) ;
25772633 }
25782634
2579- if options. dereference ( source_in_command_line) {
2635+ let copy_attributes_result = if options. dereference ( source_in_command_line) {
25802636 // Try to canonicalize, but if it fails (e.g., due to inaccessible parent directories),
25812637 // fall back to the original source path
25822638 let src_for_attrs = canonicalize ( source, MissingHandling :: Normal , ResolveMode :: Physical )
25832639 . ok ( )
25842640 . filter ( |p| p. exists ( ) )
25852641 . unwrap_or_else ( || source. to_path_buf ( ) ) ;
2586- copy_attributes ( & src_for_attrs, dest, & options. attributes , false ) ?;
2642+ copy_attributes (
2643+ & src_for_attrs,
2644+ dest,
2645+ & options. attributes ,
2646+ false ,
2647+ options. set_selinux_context ,
2648+ )
25872649 } else if source_is_stream && !source. exists ( ) {
25882650 // Some stream files may not exist after we have copied it,
25892651 // like anonymous pipes. Thus, we can't really copy its
25902652 // attributes. However, this is already handled in the stream
25912653 // copy function (see `copy_stream` under platform/linux.rs).
2654+ Ok ( ( ) )
25922655 } else {
2593- copy_attributes ( source, dest, & options. attributes , false ) ?;
2594- }
2656+ copy_attributes (
2657+ source,
2658+ dest,
2659+ & options. attributes ,
2660+ false ,
2661+ options. set_selinux_context ,
2662+ )
2663+ } ;
25952664
2596- #[ cfg( all( feature = "selinux" , any( target_os = "linux" , target_os = "android" ) ) ) ]
2597- if options. set_selinux_context && uucore:: selinux:: is_selinux_enabled ( ) {
2598- // Set the given selinux permissions on the copied file.
2599- if let Err ( e) =
2600- uucore:: selinux:: set_selinux_security_context ( dest, options. context . as_ref ( ) )
2601- {
2602- return Err ( CpError :: Error (
2603- translate ! ( "cp-error-selinux-error" , "error" => e) ,
2604- ) ) ;
2605- }
2665+ // GNU cp truncates the destination when a required attribute cannot be preserved
2666+ copy_attributes_result. inspect_err ( |_| {
2667+ fs:: File :: create ( dest) . map ( |f| f. set_len ( 0 ) ) . ok ( ) ;
2668+ } ) ?;
2669+
2670+ #[ cfg( all( feature = "selinux" , target_os = "linux" ) ) ]
2671+ if options. set_selinux_context {
2672+ set_selinux_context ( dest, options. context . as_ref ( ) ) ?;
26062673 }
26072674
26082675 // Skip tracking copied files when using --link mode since hard link
@@ -2772,7 +2839,13 @@ fn copy_link(
27722839 delete_path ( dest, options) ?;
27732840 }
27742841 symlink_file ( & link, dest, symlinked_files) ?;
2775- copy_attributes ( source, dest, & options. attributes , false )
2842+ copy_attributes (
2843+ source,
2844+ dest,
2845+ & options. attributes ,
2846+ false ,
2847+ options. set_selinux_context ,
2848+ )
27762849}
27772850
27782851/// Generate an error message if `target` is not the correct `target_type`
0 commit comments