@@ -247,3 +247,97 @@ fn test_nohup_stderr_to_stdout() {
247247 assert ! ( content. contains( "stdout message" ) ) ;
248248 assert ! ( content. contains( "stderr message" ) ) ;
249249}
250+
251+ // Test nohup.out has 0600 permissions
252+ #[ test]
253+ #[ cfg( any(
254+ target_os = "linux" ,
255+ target_os = "android" ,
256+ target_os = "freebsd" ,
257+ target_os = "openbsd" ,
258+ target_vendor = "apple"
259+ ) ) ]
260+ fn test_nohup_output_permissions ( ) {
261+ use std:: os:: unix:: fs:: PermissionsExt ;
262+
263+ let ts = TestScenario :: new ( util_name ! ( ) ) ;
264+ let at = & ts. fixtures ;
265+
266+ ts. ucmd ( )
267+ . terminal_simulation ( true )
268+ . args ( & [ "echo" , "perms" ] )
269+ . succeeds ( ) ;
270+
271+ sleep ( std:: time:: Duration :: from_millis ( 10 ) ) ;
272+
273+ let metadata = std:: fs:: metadata ( at. plus ( "nohup.out" ) ) . unwrap ( ) ;
274+ let mode = metadata. permissions ( ) . mode ( ) ;
275+
276+ assert_eq ! (
277+ mode & 0o777 ,
278+ 0o600 ,
279+ "nohup.out should have 0600 permissions"
280+ ) ;
281+ }
282+
283+ // Test that the fallback nohup.out (in $HOME) also has 0600 permissions
284+ #[ test]
285+ #[ cfg( any(
286+ target_os = "linux" ,
287+ target_os = "android" ,
288+ target_os = "freebsd" ,
289+ target_os = "openbsd"
290+ ) ) ]
291+ fn test_nohup_fallback_output_permissions ( ) {
292+ use std:: fs;
293+ use std:: os:: unix:: fs:: PermissionsExt ;
294+
295+ // Skip if root
296+ if unsafe { libc:: geteuid ( ) } == 0 {
297+ println ! ( "Skipping test when running as root" ) ;
298+ return ;
299+ }
300+
301+ let ts = TestScenario :: new ( util_name ! ( ) ) ;
302+ let at = & ts. fixtures ;
303+
304+ // Create a fake HOME directory
305+ at. mkdir ( "home" ) ;
306+ let home_dir_str = at. plus_as_string ( "home" ) ;
307+
308+ // Create a read-only directory
309+ at. mkdir ( "readonly_dir" ) ;
310+ let readonly_path = at. plus ( "readonly_dir" ) ;
311+
312+ // Make directory read-only
313+ let mut perms = fs:: metadata ( & readonly_path) . unwrap ( ) . permissions ( ) ;
314+ perms. set_mode ( 0o555 ) ;
315+ fs:: set_permissions ( & readonly_path, perms) . unwrap ( ) ;
316+
317+ // Run nohup inside the read-only dir
318+ // This forces it to fail writing to CWD and fall back to custom HOME
319+ ts. ucmd ( )
320+ . env ( "HOME" , & home_dir_str)
321+ . current_dir ( & readonly_path)
322+ . terminal_simulation ( true )
323+ . arg ( "true" )
324+ . run ( ) ;
325+
326+ // Restore permissions so the test runner can delete the folder later!
327+ let mut perms = fs:: metadata ( & readonly_path) . unwrap ( ) . permissions ( ) ;
328+ perms. set_mode ( 0o755 ) ;
329+ fs:: set_permissions ( & readonly_path, perms) . unwrap ( ) ;
330+
331+ sleep ( std:: time:: Duration :: from_millis ( 50 ) ) ;
332+
333+ // Verify the file exists in HOME and has 0600 permissions
334+ let home_nohup = at. plus ( "home/nohup.out" ) ;
335+ let metadata = fs:: metadata ( home_nohup) . expect ( "nohup.out should have been created in HOME" ) ;
336+ let mode = metadata. permissions ( ) . mode ( ) ;
337+
338+ assert_eq ! (
339+ mode & 0o777 ,
340+ 0o600 ,
341+ "Fallback nohup.out should have 0600 permissions"
342+ ) ;
343+ }
0 commit comments