@@ -1673,6 +1673,103 @@ def test_cleanup_with_symlink_to_a_directory(self):
1673
1673
"were deleted" )
1674
1674
d2 .cleanup ()
1675
1675
1676
+ @os_helper .skip_unless_symlink
1677
+ def test_cleanup_with_symlink_modes (self ):
1678
+ # cleanup() should not follow symlinks when fixing mode bits (#91133)
1679
+ with self .do_create (recurse = 0 ) as d2 :
1680
+ file1 = os .path .join (d2 , 'file1' )
1681
+ open (file1 , 'wb' ).close ()
1682
+ dir1 = os .path .join (d2 , 'dir1' )
1683
+ os .mkdir (dir1 )
1684
+ for mode in range (8 ):
1685
+ mode <<= 6
1686
+ with self .subTest (mode = format (mode , '03o' )):
1687
+ def test (target , target_is_directory ):
1688
+ d1 = self .do_create (recurse = 0 )
1689
+ symlink = os .path .join (d1 .name , 'symlink' )
1690
+ os .symlink (target , symlink ,
1691
+ target_is_directory = target_is_directory )
1692
+ try :
1693
+ os .chmod (symlink , mode , follow_symlinks = False )
1694
+ except NotImplementedError :
1695
+ pass
1696
+ try :
1697
+ os .chmod (symlink , mode )
1698
+ except FileNotFoundError :
1699
+ pass
1700
+ os .chmod (d1 .name , mode )
1701
+ d1 .cleanup ()
1702
+ self .assertFalse (os .path .exists (d1 .name ))
1703
+
1704
+ with self .subTest ('nonexisting file' ):
1705
+ test ('nonexisting' , target_is_directory = False )
1706
+ with self .subTest ('nonexisting dir' ):
1707
+ test ('nonexisting' , target_is_directory = True )
1708
+
1709
+ with self .subTest ('existing file' ):
1710
+ os .chmod (file1 , mode )
1711
+ old_mode = os .stat (file1 ).st_mode
1712
+ test (file1 , target_is_directory = False )
1713
+ new_mode = os .stat (file1 ).st_mode
1714
+ self .assertEqual (new_mode , old_mode ,
1715
+ '%03o != %03o' % (new_mode , old_mode ))
1716
+
1717
+ with self .subTest ('existing dir' ):
1718
+ os .chmod (dir1 , mode )
1719
+ old_mode = os .stat (dir1 ).st_mode
1720
+ test (dir1 , target_is_directory = True )
1721
+ new_mode = os .stat (dir1 ).st_mode
1722
+ self .assertEqual (new_mode , old_mode ,
1723
+ '%03o != %03o' % (new_mode , old_mode ))
1724
+
1725
+ @unittest .skipUnless (hasattr (os , 'chflags' ), 'requires os.chflags' )
1726
+ @os_helper .skip_unless_symlink
1727
+ def test_cleanup_with_symlink_flags (self ):
1728
+ # cleanup() should not follow symlinks when fixing flags (#91133)
1729
+ flags = stat .UF_IMMUTABLE | stat .UF_NOUNLINK
1730
+ self .check_flags (flags )
1731
+
1732
+ with self .do_create (recurse = 0 ) as d2 :
1733
+ file1 = os .path .join (d2 , 'file1' )
1734
+ open (file1 , 'wb' ).close ()
1735
+ dir1 = os .path .join (d2 , 'dir1' )
1736
+ os .mkdir (dir1 )
1737
+ def test (target , target_is_directory ):
1738
+ d1 = self .do_create (recurse = 0 )
1739
+ symlink = os .path .join (d1 .name , 'symlink' )
1740
+ os .symlink (target , symlink ,
1741
+ target_is_directory = target_is_directory )
1742
+ try :
1743
+ os .chflags (symlink , flags , follow_symlinks = False )
1744
+ except NotImplementedError :
1745
+ pass
1746
+ try :
1747
+ os .chflags (symlink , flags )
1748
+ except FileNotFoundError :
1749
+ pass
1750
+ os .chflags (d1 .name , flags )
1751
+ d1 .cleanup ()
1752
+ self .assertFalse (os .path .exists (d1 .name ))
1753
+
1754
+ with self .subTest ('nonexisting file' ):
1755
+ test ('nonexisting' , target_is_directory = False )
1756
+ with self .subTest ('nonexisting dir' ):
1757
+ test ('nonexisting' , target_is_directory = True )
1758
+
1759
+ with self .subTest ('existing file' ):
1760
+ os .chflags (file1 , flags )
1761
+ old_flags = os .stat (file1 ).st_flags
1762
+ test (file1 , target_is_directory = False )
1763
+ new_flags = os .stat (file1 ).st_flags
1764
+ self .assertEqual (new_flags , old_flags )
1765
+
1766
+ with self .subTest ('existing dir' ):
1767
+ os .chflags (dir1 , flags )
1768
+ old_flags = os .stat (dir1 ).st_flags
1769
+ test (dir1 , target_is_directory = True )
1770
+ new_flags = os .stat (dir1 ).st_flags
1771
+ self .assertEqual (new_flags , old_flags )
1772
+
1676
1773
@support .cpython_only
1677
1774
def test_del_on_collection (self ):
1678
1775
# A TemporaryDirectory is deleted when garbage collected
@@ -1845,10 +1942,7 @@ def test_modes(self):
1845
1942
d .cleanup ()
1846
1943
self .assertFalse (os .path .exists (d .name ))
1847
1944
1848
- @unittest .skipUnless (hasattr (os , 'chflags' ), 'requires os.chflags' )
1849
- def test_flags (self ):
1850
- flags = stat .UF_IMMUTABLE | stat .UF_NOUNLINK
1851
-
1945
+ def check_flags (self , flags ):
1852
1946
# skip the test if these flags are not supported (ex: FreeBSD 13)
1853
1947
filename = os_helper .TESTFN
1854
1948
try :
@@ -1857,13 +1951,18 @@ def test_flags(self):
1857
1951
os .chflags (filename , flags )
1858
1952
except OSError as exc :
1859
1953
# "OSError: [Errno 45] Operation not supported"
1860
- self .skipTest (f"chflags() doesn't support "
1861
- f"UF_IMMUTABLE|UF_NOUNLINK : { exc } " )
1954
+ self .skipTest (f"chflags() doesn't support flags "
1955
+ f"{ flags :#b } : { exc } " )
1862
1956
else :
1863
1957
os .chflags (filename , 0 )
1864
1958
finally :
1865
1959
os_helper .unlink (filename )
1866
1960
1961
+ @unittest .skipUnless (hasattr (os , 'chflags' ), 'requires os.chflags' )
1962
+ def test_flags (self ):
1963
+ flags = stat .UF_IMMUTABLE | stat .UF_NOUNLINK
1964
+ self .check_flags (flags )
1965
+
1867
1966
d = self .do_create (recurse = 3 , dirs = 2 , files = 2 )
1868
1967
with d :
1869
1968
# Change files and directories flags recursively.
0 commit comments