bpo-28564: Use os.scandir() in shutil.rmtree(). (#4085) · python/cpython@d4d79bc (original) (raw)

`@@ -362,25 +362,27 @@ def copytree(src, dst, symlinks=False, ignore=None, copy_function=copy2,

`

362

362

`# version vulnerable to race conditions

`

363

363

`def _rmtree_unsafe(path, onerror):

`

364

364

`try:

`

365

``

`-

if os.path.islink(path):

`

366

``

`-

symlinks to directories are forbidden, see bug #1669

`

367

``

`-

raise OSError("Cannot call rmtree on a symbolic link")

`

``

365

`+

with os.scandir(path) as scandir_it:

`

``

366

`+

entries = list(scandir_it)

`

368

367

`except OSError:

`

369

``

`-

onerror(os.path.islink, path, sys.exc_info())

`

370

``

`-

can't continue even if onerror hook returns

`

371

``

`-

return

`

372

``

`-

names = []

`

373

``

`-

try:

`

374

``

`-

names = os.listdir(path)

`

375

``

`-

except OSError:

`

376

``

`-

onerror(os.listdir, path, sys.exc_info())

`

377

``

`-

for name in names:

`

378

``

`-

fullname = os.path.join(path, name)

`

``

368

`+

onerror(os.scandir, path, sys.exc_info())

`

``

369

`+

entries = []

`

``

370

`+

for entry in entries:

`

``

371

`+

fullname = entry.path

`

379

372

`try:

`

380

``

`-

mode = os.lstat(fullname).st_mode

`

``

373

`+

is_dir = entry.is_dir(follow_symlinks=False)

`

381

374

`except OSError:

`

382

``

`-

mode = 0

`

383

``

`-

if stat.S_ISDIR(mode):

`

``

375

`+

is_dir = False

`

``

376

`+

if is_dir:

`

``

377

`+

try:

`

``

378

`+

if entry.is_symlink():

`

``

379

`+

This can only happen if someone replaces

`

``

380

`+

a directory with a symlink after the call to

`

``

381

`+

os.scandir or entry.is_dir above.

`

``

382

`+

raise OSError("Cannot call rmtree on a symbolic link")

`

``

383

`+

except OSError:

`

``

384

`+

onerror(os.path.islink, fullname, sys.exc_info())

`

``

385

`+

continue

`

384

386

`_rmtree_unsafe(fullname, onerror)

`

385

387

`else:

`

386

388

`try:

`

`@@ -394,37 +396,40 @@ def _rmtree_unsafe(path, onerror):

`

394

396

``

395

397

`# Version using fd-based APIs to protect against races

`

396

398

`def _rmtree_safe_fd(topfd, path, onerror):

`

397

``

`-

names = []

`

398

399

`try:

`

399

``

`-

names = os.listdir(topfd)

`

``

400

`+

with os.scandir(topfd) as scandir_it:

`

``

401

`+

entries = list(scandir_it)

`

400

402

`except OSError as err:

`

401

403

`err.filename = path

`

402

``

`-

onerror(os.listdir, path, sys.exc_info())

`

403

``

`-

for name in names:

`

404

``

`-

fullname = os.path.join(path, name)

`

``

404

`+

onerror(os.scandir, path, sys.exc_info())

`

``

405

`+

return

`

``

406

`+

for entry in entries:

`

``

407

`+

fullname = os.path.join(path, entry.name)

`

405

408

`try:

`

406

``

`-

orig_st = os.stat(name, dir_fd=topfd, follow_symlinks=False)

`

407

``

`-

mode = orig_st.st_mode

`

``

409

`+

is_dir = entry.is_dir(follow_symlinks=False)

`

``

410

`+

if is_dir:

`

``

411

`+

orig_st = entry.stat(follow_symlinks=False)

`

``

412

`+

is_dir = stat.S_ISDIR(orig_st.st_mode)

`

408

413

`except OSError:

`

409

``

`-

mode = 0

`

410

``

`-

if stat.S_ISDIR(mode):

`

``

414

`+

is_dir = False

`

``

415

`+

if is_dir:

`

411

416

`try:

`

412

``

`-

dirfd = os.open(name, os.O_RDONLY, dir_fd=topfd)

`

``

417

`+

dirfd = os.open(entry.name, os.O_RDONLY, dir_fd=topfd)

`

413

418

`except OSError:

`

414

419

`onerror(os.open, fullname, sys.exc_info())

`

415

420

`else:

`

416

421

`try:

`

417

422

`if os.path.samestat(orig_st, os.fstat(dirfd)):

`

418

423

`_rmtree_safe_fd(dirfd, fullname, onerror)

`

419

424

`try:

`

420

``

`-

os.rmdir(name, dir_fd=topfd)

`

``

425

`+

os.rmdir(entry.name, dir_fd=topfd)

`

421

426

`except OSError:

`

422

427

`onerror(os.rmdir, fullname, sys.exc_info())

`

423

428

`else:

`

424

429

`try:

`

425

430

`# This can only happen if someone replaces

`

426

431

`# a directory with a symlink after the call to

`

427

``

`-

stat.S_ISDIR above.

`

``

432

`+

os.scandir or stat.S_ISDIR above.

`

428

433

`raise OSError("Cannot call rmtree on a symbolic "

`

429

434

`"link")

`

430

435

`except OSError:

`

`@@ -433,13 +438,13 @@ def _rmtree_safe_fd(topfd, path, onerror):

`

433

438

`os.close(dirfd)

`

434

439

`else:

`

435

440

`try:

`

436

``

`-

os.unlink(name, dir_fd=topfd)

`

``

441

`+

os.unlink(entry.name, dir_fd=topfd)

`

437

442

`except OSError:

`

438

443

`onerror(os.unlink, fullname, sys.exc_info())

`

439

444

``

440

445

`_use_fd_functions = ({os.open, os.stat, os.unlink, os.rmdir} <=

`

441

446

`os.supports_dir_fd and

`

442

``

`-

os.listdir in os.supports_fd and

`

``

447

`+

os.scandir in os.supports_fd and

`

443

448

`os.stat in os.supports_follow_symlinks)

`

444

449

``

445

450

`def rmtree(path, ignore_errors=False, onerror=None):

`

`@@ -491,6 +496,14 @@ def onerror(*args):

`

491

496

`finally:

`

492

497

`os.close(fd)

`

493

498

`else:

`

``

499

`+

try:

`

``

500

`+

if os.path.islink(path):

`

``

501

`+

symlinks to directories are forbidden, see bug #1669

`

``

502

`+

raise OSError("Cannot call rmtree on a symbolic link")

`

``

503

`+

except OSError:

`

``

504

`+

onerror(os.path.islink, path, sys.exc_info())

`

``

505

`+

can't continue even if onerror hook returns

`

``

506

`+

return

`

494

507

`return _rmtree_unsafe(path, onerror)

`

495

508

``

496

509

`# Allow introspection of whether or not the hardening against symlink

`