66use std:: path:: Path ;
77
88use selinux:: SecurityContext ;
9+ use thiserror:: Error ;
910
10- #[ derive( Debug ) ]
11- pub enum Error {
11+ #[ derive( Debug , Error ) ]
12+ pub enum SeLinuxError {
13+ #[ error( "SELinux is not enabled on this system" ) ]
1214 SELinuxNotEnabled ,
13- FileOpenFailure ,
14- ContextRetrievalFailure ,
15- ContextConversionFailure ,
15+
16+ #[ error( "Failed to open the file: {0}" ) ]
17+ FileOpenFailure ( String ) ,
18+
19+ #[ error( "Failed to retrieve the security context: {0}" ) ]
20+ ContextRetrievalFailure ( String ) ,
21+
22+ #[ error( "Failed to set default file creation context to '{0}': {1}" ) ]
23+ ContextSetFailure ( String , String ) ,
24+
25+ #[ error( "Failed to set default file creation context to '{0}': {1}" ) ]
26+ ContextConversionFailure ( String , String ) ,
1627}
1728
18- impl From < Error > for i32 {
19- fn from ( error : Error ) -> i32 {
29+ impl From < SeLinuxError > for i32 {
30+ fn from ( error : SeLinuxError ) -> i32 {
2031 match error {
21- Error :: SELinuxNotEnabled => 1 ,
22- Error :: FileOpenFailure => 2 ,
23- Error :: ContextRetrievalFailure => 3 ,
24- Error :: ContextConversionFailure => 4 ,
32+ SeLinuxError :: SELinuxNotEnabled => 1 ,
33+ SeLinuxError :: FileOpenFailure ( _) => 2 ,
34+ SeLinuxError :: ContextRetrievalFailure ( _) => 3 ,
35+ SeLinuxError :: ContextSetFailure ( _, _) => 4 ,
36+ SeLinuxError :: ContextConversionFailure ( _, _) => 5 ,
2537 }
2638 }
2739}
@@ -76,31 +88,39 @@ pub fn is_selinux_enabled() -> bool {
7688/// eprintln!("Failed to set context: {}", err);
7789/// }
7890/// ```
79- pub fn set_selinux_security_context ( path : & Path , context : Option < & String > ) -> Result < ( ) , String > {
91+ pub fn set_selinux_security_context (
92+ path : & Path ,
93+ context : Option < & String > ,
94+ ) -> Result < ( ) , SeLinuxError > {
8095 if !is_selinux_enabled ( ) {
81- return Err ( "SELinux is not enabled on this system" . to_string ( ) ) ;
96+ return Err ( SeLinuxError :: SELinuxNotEnabled ) ;
8297 }
8398
8499 if let Some ( ctx_str) = context {
85100 // Create a CString from the provided context string
86- let c_context = std:: ffi:: CString :: new ( ctx_str. as_str ( ) )
87- . map_err ( |_| "Invalid context string (contains null bytes)" . to_string ( ) ) ?;
101+ let c_context = std:: ffi:: CString :: new ( ctx_str. as_str ( ) ) . map_err ( |e| {
102+ SeLinuxError :: ContextConversionFailure ( ctx_str. to_string ( ) , e. to_string ( ) )
103+ } ) ?;
88104
89105 // Convert the CString into an SELinux security context
90- let security_context = selinux:: OpaqueSecurityContext :: from_c_str ( & c_context)
91- . map_err ( |e| format ! ( "Failed to create security context: {}" , e) ) ?;
106+ let security_context =
107+ selinux:: OpaqueSecurityContext :: from_c_str ( & c_context) . map_err ( |e| {
108+ SeLinuxError :: ContextConversionFailure ( ctx_str. to_string ( ) , e. to_string ( ) )
109+ } ) ?;
92110
93111 // Set the provided security context on the specified path
94112 SecurityContext :: from_c_str (
95- & security_context. to_c_string ( ) . map_err ( |e| e. to_string ( ) ) ?,
113+ & security_context. to_c_string ( ) . map_err ( |e| {
114+ SeLinuxError :: ContextConversionFailure ( ctx_str. to_string ( ) , e. to_string ( ) )
115+ } ) ?,
96116 false ,
97117 )
98118 . set_for_path ( path, false , false )
99- . map_err ( |e| format ! ( "Failed to set context: {}" , e) )
119+ . map_err ( |e| SeLinuxError :: ContextSetFailure ( ctx_str . to_string ( ) , e. to_string ( ) ) )
100120 } else {
101121 // If no context provided, set the default SELinux context for the path
102122 SecurityContext :: set_default_for_path ( path)
103- . map_err ( |e| format ! ( "Failed to set default context: {}" , e) )
123+ . map_err ( |e| SeLinuxError :: ContextSetFailure ( String :: new ( ) , e. to_string ( ) ) )
104124 }
105125}
106126
@@ -117,17 +137,18 @@ pub fn set_selinux_security_context(path: &Path, context: Option<&String>) -> Re
117137///
118138/// * `Ok(String)` - The SELinux context string if successfully retrieved. Returns an empty
119139/// string if no context was found.
120- /// * `Err(Error)` - An error variant indicating the type of failure:
121- /// - `Error::SELinuxNotEnabled` - SELinux is not enabled on the system.
122- /// - `Error::FileOpenFailure` - Failed to open the specified file.
123- /// - `Error::ContextRetrievalFailure` - Failed to retrieve the security context.
124- /// - `Error::ContextConversionFailure` - Failed to convert the security context to a string.
140+ /// * `Err(SeLinuxError)` - An error variant indicating the type of failure:
141+ /// - `SeLinuxError::SELinuxNotEnabled` - SELinux is not enabled on the system.
142+ /// - `SeLinuxError::FileOpenFailure` - Failed to open the specified file.
143+ /// - `SeLinuxError::ContextRetrievalFailure` - Failed to retrieve the security context.
144+ /// - `SeLinuxError::ContextConversionFailure` - Failed to convert the security context to a string.
145+ /// - `SeLinuxError::ContextSetFailure` - Failed to set the security context.
125146///
126147/// # Examples
127148///
128149/// ```
129150/// use std::path::Path;
130- /// use uucore::selinux::{get_selinux_security_context, Error };
151+ /// use uucore::selinux::{get_selinux_security_context, SeLinuxError };
131152///
132153/// // Get the SELinux context for a file
133154/// match get_selinux_security_context(Path::new("/path/to/file")) {
@@ -138,29 +159,30 @@ pub fn set_selinux_security_context(path: &Path, context: Option<&String>) -> Re
138159/// println!("SELinux context: {}", context);
139160/// }
140161/// },
141- /// Err(Error::SELinuxNotEnabled) => println!("SELinux is not enabled on this system"),
142- /// Err(Error::FileOpenFailure) => println!("Failed to open the file"),
143- /// Err(Error::ContextRetrievalFailure) => println!("Failed to retrieve the security context"),
144- /// Err(Error::ContextConversionFailure) => println!("Failed to convert the security context to a string"),
162+ /// Err(SeLinuxError::SELinuxNotEnabled) => println!("SELinux is not enabled on this system"),
163+ /// Err(SeLinuxError::FileOpenFailure(e)) => println!("Failed to open the file: {}", e),
164+ /// Err(SeLinuxError::ContextRetrievalFailure(e)) => println!("Failed to retrieve the security context: {}", e),
165+ /// Err(SeLinuxError::ContextConversionFailure(ctx, e)) => println!("Failed to convert context '{}': {}", ctx, e),
166+ /// Err(SeLinuxError::ContextSetFailure(ctx, e)) => println!("Failed to set context '{}': {}", ctx, e),
145167/// }
146168/// ```
147- pub fn get_selinux_security_context ( path : & Path ) -> Result < String , Error > {
169+ pub fn get_selinux_security_context ( path : & Path ) -> Result < String , SeLinuxError > {
148170 if !is_selinux_enabled ( ) {
149- return Err ( Error :: SELinuxNotEnabled ) ;
171+ return Err ( SeLinuxError :: SELinuxNotEnabled ) ;
150172 }
151173
152- let f = std:: fs:: File :: open ( path) . map_err ( |_| Error :: FileOpenFailure ) ?;
174+ let f = std:: fs:: File :: open ( path) . map_err ( |e| SeLinuxError :: FileOpenFailure ( e . to_string ( ) ) ) ?;
153175
154176 // Get the security context of the file
155177 let context = match SecurityContext :: of_file ( & f, false ) {
156178 Ok ( Some ( ctx) ) => ctx,
157179 Ok ( None ) => return Ok ( String :: new ( ) ) , // No context found, return empty string
158- Err ( _ ) => return Err ( Error :: ContextRetrievalFailure ) ,
180+ Err ( e ) => return Err ( SeLinuxError :: ContextRetrievalFailure ( e . to_string ( ) ) ) ,
159181 } ;
160182
161183 let context_c_string = context
162184 . to_c_string ( )
163- . map_err ( |_| Error :: ContextConversionFailure ) ?;
185+ . map_err ( |e| SeLinuxError :: ContextConversionFailure ( String :: new ( ) , e . to_string ( ) ) ) ?;
164186
165187 if let Some ( c_str) = context_c_string {
166188 // Convert the C string to a Rust String
@@ -180,29 +202,50 @@ mod tests {
180202 let tmpfile = NamedTempFile :: new ( ) . expect ( "Failed to create tempfile" ) ;
181203 let path = tmpfile. path ( ) ;
182204
183- let result = set_selinux_security_context ( path, None ) ;
205+ if !is_selinux_enabled ( ) {
206+ let result = set_selinux_security_context ( path, None ) ;
207+ assert ! ( result. is_err( ) , "Expected error when SELinux is disabled" ) ;
208+ match result. unwrap_err ( ) {
209+ SeLinuxError :: SELinuxNotEnabled => {
210+ // This is the expected error when SELinux is not enabled
211+ }
212+ err => panic ! ( "Expected SELinuxNotEnabled error but got: {}" , err) ,
213+ }
214+ return ;
215+ }
184216
185- if result. is_ok ( ) {
186- // SELinux enabled and successfully set default context
187- assert ! ( true , "Successfully set SELinux context" ) ;
188- } else {
189- let err = result. unwrap_err ( ) ;
190- let valid_errors = [
191- "SELinux is not enabled on this system" ,
192- & format ! (
193- "Failed to set default context: selinux_lsetfilecon_default() failed on path '{}'" ,
194- path. display( )
195- ) ,
196- ] ;
217+ let default_result = set_selinux_security_context ( path, None ) ;
218+ assert ! (
219+ default_result. is_ok( ) ,
220+ "Failed to set default context: {:?}" ,
221+ default_result. err( )
222+ ) ;
223+
224+ let context = get_selinux_security_context ( path) . expect ( "Failed to get context" ) ;
225+ assert ! (
226+ !context. is_empty( ) ,
227+ "Expected non-empty context after setting default context"
228+ ) ;
229+
230+ let test_context = String :: from ( "system_u:object_r:tmp_t:s0" ) ;
231+ let explicit_result = set_selinux_security_context ( path, Some ( & test_context) ) ;
232+
233+ if explicit_result. is_ok ( ) {
234+ let new_context = get_selinux_security_context ( path)
235+ . expect ( "Failed to get context after setting explicit context" ) ;
197236
198237 assert ! (
199- valid_errors. contains( & err. as_str( ) ) ,
200- "Unexpected error message: {}" ,
201- err
238+ new_context. contains( "tmp_t" ) ,
239+ "Expected context to contain 'tmp_t', but got: {}" ,
240+ new_context
241+ ) ;
242+ } else {
243+ println ! (
244+ "Note: Could not set explicit context {:?}" ,
245+ explicit_result. err( )
202246 ) ;
203247 }
204248 }
205-
206249 #[ test]
207250 fn test_invalid_context_string_error ( ) {
208251 let tmpfile = NamedTempFile :: new ( ) . expect ( "Failed to create tempfile" ) ;
@@ -213,10 +256,18 @@ mod tests {
213256 let result = set_selinux_security_context ( path, Some ( & invalid_context) ) ;
214257
215258 assert ! ( result. is_err( ) ) ;
216- assert_eq ! (
217- result. unwrap_err( ) ,
218- "Invalid context string (contains null bytes)"
219- ) ;
259+ if let Err ( err) = result {
260+ match err {
261+ SeLinuxError :: ContextConversionFailure ( ctx, msg) => {
262+ assert_eq ! ( ctx, "invalid\0 context" ) ;
263+ assert ! (
264+ msg. contains( "nul byte" ) ,
265+ "Error message should mention nul byte"
266+ ) ;
267+ }
268+ _ => panic ! ( "Expected ContextConversionFailure error but got: {}" , err) ,
269+ }
270+ }
220271 }
221272
222273 #[ test]
@@ -243,17 +294,56 @@ mod tests {
243294 let result = get_selinux_security_context ( path) ;
244295
245296 if result. is_ok ( ) {
246- println ! ( "Retrieved SELinux context: {}" , result. unwrap( ) ) ;
297+ let context = result. unwrap ( ) ;
298+ println ! ( "Retrieved SELinux context: {}" , context) ;
299+
300+ assert ! (
301+ is_selinux_enabled( ) ,
302+ "Got a successful context result but SELinux is not enabled"
303+ ) ;
304+
305+ if !context. is_empty ( ) {
306+ assert ! (
307+ context. contains( ':' ) ,
308+ "SELinux context '{}' doesn't match expected format" ,
309+ context
310+ ) ;
311+ }
247312 } else {
248313 let err = result. unwrap_err ( ) ;
249314
250- // Valid error types
251315 match err {
252- Error :: SELinuxNotEnabled => assert ! ( true , "SELinux not supported" ) ,
253- Error :: ContextRetrievalFailure => assert ! ( true , "Context retrieval failure" ) ,
254- Error :: ContextConversionFailure => assert ! ( true , "Context conversion failure" ) ,
255- Error :: FileOpenFailure => {
256- panic ! ( "File open failure occurred despite file being created" )
316+ SeLinuxError :: SELinuxNotEnabled => {
317+ assert ! (
318+ !is_selinux_enabled( ) ,
319+ "Got SELinuxNotEnabled error, but is_selinux_enabled() returned true"
320+ ) ;
321+ }
322+ SeLinuxError :: ContextRetrievalFailure ( e) => {
323+ assert ! (
324+ is_selinux_enabled( ) ,
325+ "Got ContextRetrievalFailure when SELinux is not enabled"
326+ ) ;
327+ assert ! ( !e. is_empty( ) , "Error message should not be empty" ) ;
328+ println ! ( "Context retrieval failure: {}" , e) ;
329+ }
330+ SeLinuxError :: ContextConversionFailure ( ctx, e) => {
331+ assert ! (
332+ is_selinux_enabled( ) ,
333+ "Got ContextConversionFailure when SELinux is not enabled"
334+ ) ;
335+ assert ! ( !e. is_empty( ) , "Error message should not be empty" ) ;
336+ println ! ( "Context conversion failure for '{}': {}" , ctx, e) ;
337+ }
338+ SeLinuxError :: ContextSetFailure ( ctx, e) => {
339+ assert ! ( false ) ;
340+ }
341+ SeLinuxError :: FileOpenFailure ( e) => {
342+ assert ! (
343+ Path :: new( path) . exists( ) ,
344+ "File open failure occurred despite file being created: {}" ,
345+ e
346+ ) ;
257347 }
258348 }
259349 }
@@ -266,9 +356,16 @@ mod tests {
266356 let result = get_selinux_security_context ( path) ;
267357
268358 assert ! ( result. is_err( ) ) ;
269- assert ! (
270- matches!( result. unwrap_err( ) , Error :: FileOpenFailure ) ,
271- "Expected file open error for nonexistent file"
272- ) ;
359+ if let Err ( err) = result {
360+ match err {
361+ SeLinuxError :: FileOpenFailure ( e) => {
362+ assert ! (
363+ e. contains( "No such file" ) ,
364+ "Error should mention file not found"
365+ ) ;
366+ }
367+ _ => panic ! ( "Expected FileOpenFailure error but got: {}" , err) ,
368+ }
369+ }
273370 }
274371}
0 commit comments