6
6
use std:: path:: Path ;
7
7
8
8
use selinux:: SecurityContext ;
9
+ use thiserror:: Error ;
9
10
10
- #[ derive( Debug ) ]
11
- pub enum Error {
11
+ #[ derive( Debug , Error ) ]
12
+ pub enum SeLinuxError {
13
+ #[ error( "SELinux is not enabled on this system" ) ]
12
14
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 ) ,
16
27
}
17
28
18
- impl From < Error > for i32 {
19
- fn from ( error : Error ) -> i32 {
29
+ impl From < SeLinuxError > for i32 {
30
+ fn from ( error : SeLinuxError ) -> i32 {
20
31
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 ,
25
37
}
26
38
}
27
39
}
@@ -76,31 +88,39 @@ pub fn is_selinux_enabled() -> bool {
76
88
/// eprintln!("Failed to set context: {}", err);
77
89
/// }
78
90
/// ```
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 > {
80
95
if !is_selinux_enabled ( ) {
81
- return Err ( "SELinux is not enabled on this system" . to_string ( ) ) ;
96
+ return Err ( SeLinuxError :: SELinuxNotEnabled ) ;
82
97
}
83
98
84
99
if let Some ( ctx_str) = context {
85
100
// 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
+ } ) ?;
88
104
89
105
// 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
+ } ) ?;
92
110
93
111
// Set the provided security context on the specified path
94
112
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
+ } ) ?,
96
116
false ,
97
117
)
98
118
. 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 ( ) ) )
100
120
} else {
101
121
// If no context provided, set the default SELinux context for the path
102
122
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 ( ) ) )
104
124
}
105
125
}
106
126
@@ -117,17 +137,18 @@ pub fn set_selinux_security_context(path: &Path, context: Option<&String>) -> Re
117
137
///
118
138
/// * `Ok(String)` - The SELinux context string if successfully retrieved. Returns an empty
119
139
/// 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.
125
146
///
126
147
/// # Examples
127
148
///
128
149
/// ```
129
150
/// use std::path::Path;
130
- /// use uucore::selinux::{get_selinux_security_context, Error };
151
+ /// use uucore::selinux::{get_selinux_security_context, SeLinuxError };
131
152
///
132
153
/// // Get the SELinux context for a file
133
154
/// 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
138
159
/// println!("SELinux context: {}", context);
139
160
/// }
140
161
/// },
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),
145
167
/// }
146
168
/// ```
147
- pub fn get_selinux_security_context ( path : & Path ) -> Result < String , Error > {
169
+ pub fn get_selinux_security_context ( path : & Path ) -> Result < String , SeLinuxError > {
148
170
if !is_selinux_enabled ( ) {
149
- return Err ( Error :: SELinuxNotEnabled ) ;
171
+ return Err ( SeLinuxError :: SELinuxNotEnabled ) ;
150
172
}
151
173
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 ( ) ) ) ?;
153
175
154
176
// Get the security context of the file
155
177
let context = match SecurityContext :: of_file ( & f, false ) {
156
178
Ok ( Some ( ctx) ) => ctx,
157
179
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 ( ) ) ) ,
159
181
} ;
160
182
161
183
let context_c_string = context
162
184
. to_c_string ( )
163
- . map_err ( |_| Error :: ContextConversionFailure ) ?;
185
+ . map_err ( |e| SeLinuxError :: ContextConversionFailure ( String :: new ( ) , e . to_string ( ) ) ) ?;
164
186
165
187
if let Some ( c_str) = context_c_string {
166
188
// Convert the C string to a Rust String
@@ -180,29 +202,50 @@ mod tests {
180
202
let tmpfile = NamedTempFile :: new ( ) . expect ( "Failed to create tempfile" ) ;
181
203
let path = tmpfile. path ( ) ;
182
204
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
+ }
184
216
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" ) ;
197
236
198
237
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( )
202
246
) ;
203
247
}
204
248
}
205
-
206
249
#[ test]
207
250
fn test_invalid_context_string_error ( ) {
208
251
let tmpfile = NamedTempFile :: new ( ) . expect ( "Failed to create tempfile" ) ;
@@ -213,10 +256,18 @@ mod tests {
213
256
let result = set_selinux_security_context ( path, Some ( & invalid_context) ) ;
214
257
215
258
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
+ }
220
271
}
221
272
222
273
#[ test]
@@ -243,17 +294,56 @@ mod tests {
243
294
let result = get_selinux_security_context ( path) ;
244
295
245
296
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
+ }
247
312
} else {
248
313
let err = result. unwrap_err ( ) ;
249
314
250
- // Valid error types
251
315
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
+ ) ;
257
347
}
258
348
}
259
349
}
@@ -266,9 +356,16 @@ mod tests {
266
356
let result = get_selinux_security_context ( path) ;
267
357
268
358
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
+ }
273
370
}
274
371
}
0 commit comments