@@ -1377,6 +1377,103 @@ def test_cleanup_with_symlink_to_a_directory(self):
13771377 "were deleted" )
13781378 d2 .cleanup ()
13791379
1380+ @support .skip_unless_symlink
1381+ def test_cleanup_with_symlink_modes (self ):
1382+ # cleanup() should not follow symlinks when fixing mode bits (#91133)
1383+ with self .do_create (recurse = 0 ) as d2 :
1384+ file1 = os .path .join (d2 , 'file1' )
1385+ open (file1 , 'wb' ).close ()
1386+ dir1 = os .path .join (d2 , 'dir1' )
1387+ os .mkdir (dir1 )
1388+ for mode in range (8 ):
1389+ mode <<= 6
1390+ with self .subTest (mode = format (mode , '03o' )):
1391+ def test (target , target_is_directory ):
1392+ d1 = self .do_create (recurse = 0 )
1393+ symlink = os .path .join (d1 .name , 'symlink' )
1394+ os .symlink (target , symlink ,
1395+ target_is_directory = target_is_directory )
1396+ try :
1397+ os .chmod (symlink , mode , follow_symlinks = False )
1398+ except NotImplementedError :
1399+ pass
1400+ try :
1401+ os .chmod (symlink , mode )
1402+ except FileNotFoundError :
1403+ pass
1404+ os .chmod (d1 .name , mode )
1405+ d1 .cleanup ()
1406+ self .assertFalse (os .path .exists (d1 .name ))
1407+
1408+ with self .subTest ('nonexisting file' ):
1409+ test ('nonexisting' , target_is_directory = False )
1410+ with self .subTest ('nonexisting dir' ):
1411+ test ('nonexisting' , target_is_directory = True )
1412+
1413+ with self .subTest ('existing file' ):
1414+ os .chmod (file1 , mode )
1415+ old_mode = os .stat (file1 ).st_mode
1416+ test (file1 , target_is_directory = False )
1417+ new_mode = os .stat (file1 ).st_mode
1418+ self .assertEqual (new_mode , old_mode ,
1419+ '%03o != %03o' % (new_mode , old_mode ))
1420+
1421+ with self .subTest ('existing dir' ):
1422+ os .chmod (dir1 , mode )
1423+ old_mode = os .stat (dir1 ).st_mode
1424+ test (dir1 , target_is_directory = True )
1425+ new_mode = os .stat (dir1 ).st_mode
1426+ self .assertEqual (new_mode , old_mode ,
1427+ '%03o != %03o' % (new_mode , old_mode ))
1428+
1429+ @unittest .skipUnless (hasattr (os , 'chflags' ), 'requires os.chflags' )
1430+ @support .skip_unless_symlink
1431+ def test_cleanup_with_symlink_flags (self ):
1432+ # cleanup() should not follow symlinks when fixing flags (#91133)
1433+ flags = stat .UF_IMMUTABLE | stat .UF_NOUNLINK
1434+ self .check_flags (flags )
1435+
1436+ with self .do_create (recurse = 0 ) as d2 :
1437+ file1 = os .path .join (d2 , 'file1' )
1438+ open (file1 , 'wb' ).close ()
1439+ dir1 = os .path .join (d2 , 'dir1' )
1440+ os .mkdir (dir1 )
1441+ def test (target , target_is_directory ):
1442+ d1 = self .do_create (recurse = 0 )
1443+ symlink = os .path .join (d1 .name , 'symlink' )
1444+ os .symlink (target , symlink ,
1445+ target_is_directory = target_is_directory )
1446+ try :
1447+ os .chflags (symlink , flags , follow_symlinks = False )
1448+ except NotImplementedError :
1449+ pass
1450+ try :
1451+ os .chflags (symlink , flags )
1452+ except FileNotFoundError :
1453+ pass
1454+ os .chflags (d1 .name , flags )
1455+ d1 .cleanup ()
1456+ self .assertFalse (os .path .exists (d1 .name ))
1457+
1458+ with self .subTest ('nonexisting file' ):
1459+ test ('nonexisting' , target_is_directory = False )
1460+ with self .subTest ('nonexisting dir' ):
1461+ test ('nonexisting' , target_is_directory = True )
1462+
1463+ with self .subTest ('existing file' ):
1464+ os .chflags (file1 , flags )
1465+ old_flags = os .stat (file1 ).st_flags
1466+ test (file1 , target_is_directory = False )
1467+ new_flags = os .stat (file1 ).st_flags
1468+ self .assertEqual (new_flags , old_flags )
1469+
1470+ with self .subTest ('existing dir' ):
1471+ os .chflags (dir1 , flags )
1472+ old_flags = os .stat (dir1 ).st_flags
1473+ test (dir1 , target_is_directory = True )
1474+ new_flags = os .stat (dir1 ).st_flags
1475+ self .assertEqual (new_flags , old_flags )
1476+
13801477 @support .cpython_only
13811478 def test_del_on_collection (self ):
13821479 # A TemporaryDirectory is deleted when garbage collected
@@ -1489,9 +1586,27 @@ def test_modes(self):
14891586 d .cleanup ()
14901587 self .assertFalse (os .path .exists (d .name ))
14911588
1492- @unittest .skipUnless (hasattr (os , 'chflags' ), 'requires os.lchflags' )
1589+ def check_flags (self , flags ):
1590+ # skip the test if these flags are not supported (ex: FreeBSD 13)
1591+ filename = support .TESTFN
1592+ try :
1593+ open (filename , "w" ).close ()
1594+ try :
1595+ os .chflags (filename , flags )
1596+ except OSError as exc :
1597+ # "OSError: [Errno 45] Operation not supported"
1598+ self .skipTest (f"chflags() doesn't support flags "
1599+ f"{ flags :#b} : { exc } " )
1600+ else :
1601+ os .chflags (filename , 0 )
1602+ finally :
1603+ support .unlink (filename )
1604+
1605+ @unittest .skipUnless (hasattr (os , 'chflags' ), 'requires os.chflags' )
14931606 def test_flags (self ):
14941607 flags = stat .UF_IMMUTABLE | stat .UF_NOUNLINK
1608+ self .check_flags (flags )
1609+
14951610 d = self .do_create (recurse = 3 , dirs = 2 , files = 2 )
14961611 with d :
14971612 # Change files and directories flags recursively.
0 commit comments