@@ -228,6 +228,106 @@ pub fn get_selinux_security_context(path: &Path) -> Result<String, SeLinuxError>
228228 }
229229}
230230
231+ /// Compares SELinux security contexts of two filesystem paths.
232+ ///
233+ /// This function retrieves and compares the SELinux security contexts of two paths.
234+ /// If the contexts differ or an error occurs during retrieval, it returns true.
235+ ///
236+ /// # Arguments
237+ ///
238+ /// * `from_path` - Source filesystem path.
239+ /// * `to_path` - Destination filesystem path.
240+ ///
241+ /// # Returns
242+ ///
243+ /// * `true` - If contexts differ, cannot be retrieved, or if SELinux is not enabled.
244+ /// * `false` - If contexts are the same.
245+ ///
246+ /// # Examples
247+ ///
248+ /// ```
249+ /// use std::path::Path;
250+ /// use uucore::selinux::contexts_differ;
251+ ///
252+ /// // Check if contexts differ between two files
253+ /// let differ = contexts_differ(Path::new("/path/to/source"), Path::new("/path/to/destination"));
254+ /// if differ {
255+ /// println!("Files have different SELinux contexts");
256+ /// } else {
257+ /// println!("Files have the same SELinux context");
258+ /// }
259+ /// ```
260+ pub fn contexts_differ ( from_path : & Path , to_path : & Path ) -> bool {
261+ if !is_selinux_enabled ( ) {
262+ return true ;
263+ }
264+
265+ // Check if SELinux contexts differ
266+ match (
267+ selinux:: SecurityContext :: of_path ( from_path, false , false ) ,
268+ selinux:: SecurityContext :: of_path ( to_path, false , false ) ,
269+ ) {
270+ ( Ok ( Some ( from_ctx) ) , Ok ( Some ( to_ctx) ) ) => {
271+ // Convert contexts to CString and compare
272+ match ( from_ctx. to_c_string ( ) , to_ctx. to_c_string ( ) ) {
273+ ( Ok ( Some ( from_c_str) ) , Ok ( Some ( to_c_str) ) ) => {
274+ from_c_str. to_string_lossy ( ) != to_c_str. to_string_lossy ( )
275+ }
276+ // If contexts couldn't be converted to CString or were None, consider them different
277+ _ => true ,
278+ }
279+ }
280+ // If either context is None or an error occurred, assume contexts differ
281+ _ => true ,
282+ }
283+ }
284+
285+ /// Preserves the SELinux security context from one filesystem path to another.
286+ ///
287+ /// This function copies the security context from the source path to the destination path.
288+ /// If SELinux is not enabled, or if the source has no context, the function returns success
289+ /// without making any changes.
290+ ///
291+ /// # Arguments
292+ ///
293+ /// * `from_path` - Source filesystem path from which to copy the SELinux context.
294+ /// * `to_path` - Destination filesystem path to which the context should be applied.
295+ ///
296+ /// # Returns
297+ ///
298+ /// * `Ok(())` - If the context was successfully preserved or if SELinux is not enabled.
299+ /// * `Err(SeLinuxError)` - If an error occurred during context retrieval or application.
300+ ///
301+ /// # Examples
302+ ///
303+ /// ```
304+ /// use std::path::Path;
305+ /// use uucore::selinux::preserve_security_context;
306+ ///
307+ /// // Preserve the SELinux context from source to destination
308+ /// match preserve_security_context(Path::new("/path/to/source"), Path::new("/path/to/destination")) {
309+ /// Ok(_) => println!("Context preserved successfully (or SELinux is not enabled)"),
310+ /// Err(err) => eprintln!("Failed to preserve context: {}", err),
311+ /// }
312+ /// ```
313+ pub fn preserve_security_context ( from_path : & Path , to_path : & Path ) -> Result < ( ) , SeLinuxError > {
314+ // If SELinux is not enabled, return success without doing anything
315+ if !is_selinux_enabled ( ) {
316+ return Err ( SeLinuxError :: SELinuxNotEnabled ) ;
317+ }
318+
319+ // Get context from the source path
320+ let context = get_selinux_security_context ( from_path) ?;
321+
322+ // If no context was found, just return success (nothing to preserve)
323+ if context. is_empty ( ) {
324+ return Ok ( ( ) ) ;
325+ }
326+
327+ // Apply the context to the destination path
328+ set_selinux_security_context ( to_path, Some ( & context) )
329+ }
330+
231331#[ cfg( test) ]
232332mod tests {
233333 use super :: * ;
@@ -295,18 +395,6 @@ mod tests {
295395 let result = set_selinux_security_context ( path, Some ( & invalid_context) ) ;
296396
297397 assert ! ( result. is_err( ) ) ;
298- if let Err ( err) = result {
299- match err {
300- SeLinuxError :: ContextConversionFailure ( ctx, msg) => {
301- assert_eq ! ( ctx, "invalid\0 context" ) ;
302- assert ! (
303- msg. contains( "nul byte" ) ,
304- "Error message should mention nul byte"
305- ) ;
306- }
307- _ => panic ! ( "Expected ContextConversionFailure error but got: {}" , err) ,
308- }
309- }
310398 }
311399
312400 #[ test]
@@ -402,15 +490,139 @@ mod tests {
402490 let result = get_selinux_security_context ( path) ;
403491
404492 assert ! ( result. is_err( ) ) ;
493+ }
494+
495+ #[ test]
496+ fn test_contexts_differ ( ) {
497+ let file1 = NamedTempFile :: new ( ) . expect ( "Failed to create first tempfile" ) ;
498+ let file2 = NamedTempFile :: new ( ) . expect ( "Failed to create second tempfile" ) ;
499+ let path1 = file1. path ( ) ;
500+ let path2 = file2. path ( ) ;
501+
502+ std:: fs:: write ( path1, b"content for file 1" ) . expect ( "Failed to write to first tempfile" ) ;
503+ std:: fs:: write ( path2, b"content for file 2" ) . expect ( "Failed to write to second tempfile" ) ;
504+
505+ if !is_selinux_enabled ( ) {
506+ assert ! (
507+ contexts_differ( path1, path2) ,
508+ "contexts_differ should return true when SELinux is not enabled"
509+ ) ;
510+ return ;
511+ }
512+
513+ let test_context = String :: from ( "system_u:object_r:tmp_t:s0" ) ;
514+ let result1 = set_selinux_security_context ( path1, Some ( & test_context) ) ;
515+ let result2 = set_selinux_security_context ( path2, Some ( & test_context) ) ;
516+
517+ if result1. is_ok ( ) && result2. is_ok ( ) {
518+ assert ! (
519+ !contexts_differ( path1, path2) ,
520+ "Contexts should not differ when the same context is set on both files"
521+ ) ;
522+
523+ let different_context = String :: from ( "system_u:object_r:user_tmp_t:s0" ) ;
524+ if set_selinux_security_context ( path2, Some ( & different_context) ) . is_ok ( ) {
525+ assert ! (
526+ contexts_differ( path1, path2) ,
527+ "Contexts should differ when different contexts are set"
528+ ) ;
529+ }
530+ } else {
531+ println ! (
532+ "Note: Couldn't set SELinux contexts to test differences. This is expected if the test doesn't have sufficient permissions."
533+ ) ;
534+ assert ! (
535+ contexts_differ( path1, path2) ,
536+ "Contexts should differ when different contexts are set"
537+ ) ;
538+ }
539+
540+ let nonexistent_path = Path :: new ( "/nonexistent/file/path" ) ;
541+ assert ! (
542+ contexts_differ( path1, nonexistent_path) ,
543+ "contexts_differ should return true when one path doesn't exist"
544+ ) ;
545+ }
546+
547+ #[ test]
548+ fn test_preserve_security_context ( ) {
549+ let source_file = NamedTempFile :: new ( ) . expect ( "Failed to create source tempfile" ) ;
550+ let dest_file = NamedTempFile :: new ( ) . expect ( "Failed to create destination tempfile" ) ;
551+ let source_path = source_file. path ( ) ;
552+ let dest_path = dest_file. path ( ) ;
553+
554+ std:: fs:: write ( source_path, b"source content" ) . expect ( "Failed to write to source tempfile" ) ;
555+ std:: fs:: write ( dest_path, b"destination content" )
556+ . expect ( "Failed to write to destination tempfile" ) ;
557+
558+ if !is_selinux_enabled ( ) {
559+ let result = preserve_security_context ( source_path, dest_path) ;
560+ assert ! (
561+ result. is_err( ) ,
562+ "preserve_security_context should fail when SELinux is not enabled"
563+ ) ;
564+ return ;
565+ }
566+
567+ let source_context = String :: from ( "system_u:object_r:tmp_t:s0" ) ;
568+ let result = set_selinux_security_context ( source_path, Some ( & source_context) ) ;
569+
570+ if result. is_ok ( ) {
571+ let preserve_result = preserve_security_context ( source_path, dest_path) ;
572+ assert ! (
573+ preserve_result. is_ok( ) ,
574+ "Failed to preserve context: {:?}" ,
575+ preserve_result. err( )
576+ ) ;
577+
578+ assert ! (
579+ !contexts_differ( source_path, dest_path) ,
580+ "Contexts should be the same after preserving"
581+ ) ;
582+ } else {
583+ println ! (
584+ "Note: Couldn't set SELinux context on source file to test preservation. This is expected if the test doesn't have sufficient permissions."
585+ ) ;
586+
587+ let preserve_result = preserve_security_context ( source_path, dest_path) ;
588+ assert ! ( preserve_result. is_err( ) ) ;
589+ }
590+
591+ let nonexistent_path = Path :: new ( "/nonexistent/file/path" ) ;
592+ let result = preserve_security_context ( nonexistent_path, dest_path) ;
593+ assert ! (
594+ result. is_err( ) ,
595+ "preserve_security_context should fail when source file doesn't exist"
596+ ) ;
597+
598+ let result = preserve_security_context ( source_path, nonexistent_path) ;
599+ assert ! (
600+ result. is_err( ) ,
601+ "preserve_security_context should fail when destination file doesn't exist"
602+ ) ;
603+ }
604+
605+ #[ test]
606+ fn test_preserve_security_context_empty_context ( ) {
607+ let source_file = NamedTempFile :: new ( ) . expect ( "Failed to create source tempfile" ) ;
608+ let dest_file = NamedTempFile :: new ( ) . expect ( "Failed to create destination tempfile" ) ;
609+ let source_path = source_file. path ( ) ;
610+ let dest_path = dest_file. path ( ) ;
611+
612+ if !is_selinux_enabled ( ) {
613+ return ;
614+ }
615+
616+ let result = preserve_security_context ( source_path, dest_path) ;
617+
405618 if let Err ( err) = result {
406619 match err {
407- SeLinuxError :: FileOpenFailure ( e ) => {
408- assert ! (
409- e . contains ( "No such file" ) ,
410- "Error should mention file not found"
411- ) ;
620+ SeLinuxError :: ContextSetFailure ( _ , _ ) => {
621+ println ! ( "Note: Could not set context due to permissions: {}" , err ) ;
622+ }
623+ unexpected => {
624+ panic ! ( "Unexpected error: {}" , unexpected ) ;
412625 }
413- _ => panic ! ( "Expected FileOpenFailure error but got: {}" , err) ,
414626 }
415627 }
416628 }
0 commit comments