@@ -1394,6 +1394,103 @@ def test_cleanup_with_symlink_to_a_directory(self):
1394
1394
"were deleted" )
1395
1395
d2 .cleanup ()
1396
1396
1397
+ @support .skip_unless_symlink
1398
+ def test_cleanup_with_symlink_modes (self ):
1399
+ # cleanup() should not follow symlinks when fixing mode bits (#91133)
1400
+ with self .do_create (recurse = 0 ) as d2 :
1401
+ file1 = os .path .join (d2 , 'file1' )
1402
+ open (file1 , 'wb' ).close ()
1403
+ dir1 = os .path .join (d2 , 'dir1' )
1404
+ os .mkdir (dir1 )
1405
+ for mode in range (8 ):
1406
+ mode <<= 6
1407
+ with self .subTest (mode = format (mode , '03o' )):
1408
+ def test (target , target_is_directory ):
1409
+ d1 = self .do_create (recurse = 0 )
1410
+ symlink = os .path .join (d1 .name , 'symlink' )
1411
+ os .symlink (target , symlink ,
1412
+ target_is_directory = target_is_directory )
1413
+ try :
1414
+ os .chmod (symlink , mode , follow_symlinks = False )
1415
+ except NotImplementedError :
1416
+ pass
1417
+ try :
1418
+ os .chmod (symlink , mode )
1419
+ except FileNotFoundError :
1420
+ pass
1421
+ os .chmod (d1 .name , mode )
1422
+ d1 .cleanup ()
1423
+ self .assertFalse (os .path .exists (d1 .name ))
1424
+
1425
+ with self .subTest ('nonexisting file' ):
1426
+ test ('nonexisting' , target_is_directory = False )
1427
+ with self .subTest ('nonexisting dir' ):
1428
+ test ('nonexisting' , target_is_directory = True )
1429
+
1430
+ with self .subTest ('existing file' ):
1431
+ os .chmod (file1 , mode )
1432
+ old_mode = os .stat (file1 ).st_mode
1433
+ test (file1 , target_is_directory = False )
1434
+ new_mode = os .stat (file1 ).st_mode
1435
+ self .assertEqual (new_mode , old_mode ,
1436
+ '%03o != %03o' % (new_mode , old_mode ))
1437
+
1438
+ with self .subTest ('existing dir' ):
1439
+ os .chmod (dir1 , mode )
1440
+ old_mode = os .stat (dir1 ).st_mode
1441
+ test (dir1 , target_is_directory = True )
1442
+ new_mode = os .stat (dir1 ).st_mode
1443
+ self .assertEqual (new_mode , old_mode ,
1444
+ '%03o != %03o' % (new_mode , old_mode ))
1445
+
1446
+ @unittest .skipUnless (hasattr (os , 'chflags' ), 'requires os.chflags' )
1447
+ @support .skip_unless_symlink
1448
+ def test_cleanup_with_symlink_flags (self ):
1449
+ # cleanup() should not follow symlinks when fixing flags (#91133)
1450
+ flags = stat .UF_IMMUTABLE | stat .UF_NOUNLINK
1451
+ self .check_flags (flags )
1452
+
1453
+ with self .do_create (recurse = 0 ) as d2 :
1454
+ file1 = os .path .join (d2 , 'file1' )
1455
+ open (file1 , 'wb' ).close ()
1456
+ dir1 = os .path .join (d2 , 'dir1' )
1457
+ os .mkdir (dir1 )
1458
+ def test (target , target_is_directory ):
1459
+ d1 = self .do_create (recurse = 0 )
1460
+ symlink = os .path .join (d1 .name , 'symlink' )
1461
+ os .symlink (target , symlink ,
1462
+ target_is_directory = target_is_directory )
1463
+ try :
1464
+ os .chflags (symlink , flags , follow_symlinks = False )
1465
+ except NotImplementedError :
1466
+ pass
1467
+ try :
1468
+ os .chflags (symlink , flags )
1469
+ except FileNotFoundError :
1470
+ pass
1471
+ os .chflags (d1 .name , flags )
1472
+ d1 .cleanup ()
1473
+ self .assertFalse (os .path .exists (d1 .name ))
1474
+
1475
+ with self .subTest ('nonexisting file' ):
1476
+ test ('nonexisting' , target_is_directory = False )
1477
+ with self .subTest ('nonexisting dir' ):
1478
+ test ('nonexisting' , target_is_directory = True )
1479
+
1480
+ with self .subTest ('existing file' ):
1481
+ os .chflags (file1 , flags )
1482
+ old_flags = os .stat (file1 ).st_flags
1483
+ test (file1 , target_is_directory = False )
1484
+ new_flags = os .stat (file1 ).st_flags
1485
+ self .assertEqual (new_flags , old_flags )
1486
+
1487
+ with self .subTest ('existing dir' ):
1488
+ os .chflags (dir1 , flags )
1489
+ old_flags = os .stat (dir1 ).st_flags
1490
+ test (dir1 , target_is_directory = True )
1491
+ new_flags = os .stat (dir1 ).st_flags
1492
+ self .assertEqual (new_flags , old_flags )
1493
+
1397
1494
@support .cpython_only
1398
1495
def test_del_on_collection (self ):
1399
1496
# A TemporaryDirectory is deleted when garbage collected
@@ -1506,9 +1603,27 @@ def test_modes(self):
1506
1603
d .cleanup ()
1507
1604
self .assertFalse (os .path .exists (d .name ))
1508
1605
1509
- @unittest .skipUnless (hasattr (os , 'chflags' ), 'requires os.lchflags' )
1606
+ def check_flags (self , flags ):
1607
+ # skip the test if these flags are not supported (ex: FreeBSD 13)
1608
+ filename = support .TESTFN
1609
+ try :
1610
+ open (filename , "w" ).close ()
1611
+ try :
1612
+ os .chflags (filename , flags )
1613
+ except OSError as exc :
1614
+ # "OSError: [Errno 45] Operation not supported"
1615
+ self .skipTest (f"chflags() doesn't support flags "
1616
+ f"{ flags :#b} : { exc } " )
1617
+ else :
1618
+ os .chflags (filename , 0 )
1619
+ finally :
1620
+ support .unlink (filename )
1621
+
1622
+ @unittest .skipUnless (hasattr (os , 'chflags' ), 'requires os.chflags' )
1510
1623
def test_flags (self ):
1511
1624
flags = stat .UF_IMMUTABLE | stat .UF_NOUNLINK
1625
+ self .check_flags (flags )
1626
+
1512
1627
d = self .do_create (recurse = 3 , dirs = 2 , files = 2 )
1513
1628
with d :
1514
1629
# Change files and directories flags recursively.
0 commit comments