gpt4 book ai didi

python - 在 Windows 上成功 shutil.rmtree 后,os.mkdir 可能会因 PermissionError 而失败

转载 作者:行者123 更新时间:2023-12-03 11:08:03 27 4
gpt4 key购买 nike

考虑以下用于清理目录的 python 函数:

def cleanDir(path):
shutil.rmtree(path)
os.mkdir(path)

在 Windows 上(实际使用 python 2.7.10 和 3.4.4 在 Windows7 和 Windows10 上测试),当使用 Windows Explorer 同时导航到相应目录时(或仅在左侧树 Pane 中导航到父文件夹时),可能会引发以下异常:
Traceback (most recent call last):
...
File "cleanDir.py", line ..., in cleanDir
os.mkdir(path)
PermissionError: [WinError 5] Access is denied: 'testFolder'

此问题已在此 issue 中报告过.但是没有进一步分析,使用 sleep 的给定解决方案并不令人满意。根据 Eryk 在下面的评论,对于当前的 python 版本,即 python 3.8,也可以预期相同的行为。

请注意 shutil.rmtree无一异常(exception)地返回。但是尝试立即再次创建目录可能会失败。 (重试大多数情况下是成功的,请参阅下面的完整测试代码。)请注意,您需要在 Windows 资源管理器中单击左右两侧的测试文件夹,以强制解决问题。

问题似乎出在 Windows 文件系统 API 函数中(而不是 Python os 模块中):当 Windows 资源管理器拥有相应文件夹的句柄时,已删除的文件夹似乎不会立即“转发”到所有函数。
import os, shutil
import time

def populateFolder(path):
if os.path.exists(path):
with open(os.path.join(path,'somefile.txt'), 'w') as f:
f.write('test')
#subfolderpath = os.path.join(path,'subfolder')
#os.mkdir(subfolderpath)
#with open(os.path.join(subfolderpath,'anotherfile.txt'), 'w') as f2:
# f2.write('test')

def cleanDir(path):
shutil.rmtree(path)
os.mkdir(path)


def cleanDir_safe(path):
shutil.rmtree(path)

try:
#time.sleep(0.005) # makes first try of os.mkdir successful
os.mkdir(path)
except Exception as e:
print('os.mkdir failed: %s' % e)
time.sleep(0.01)
os.mkdir(path)

assert os.path.exists(path)


FOLDER_PATH = 'testFolder'
if os.path.exists(FOLDER_PATH):
cleanDir(FOLDER_PATH)
else:
os.mkdir(FOLDER_PATH)

loopCnt = 0
while True:
populateFolder(FOLDER_PATH)
#cleanDir(FOLDER_PATH)
cleanDir_safe(FOLDER_PATH)
time.sleep(0.01)
loopCnt += 1
if loopCnt % 100 == 0:
print(loopCnt)

最佳答案

Explorer 对共享删除/重命名访问的目录有一个打开的句柄。这允许 rmdir成功,而通常打开不会共享删除/重命名访问权限,rmdir将因共享冲突而失败 (32)。然而,即使 rmdir成功后,直到资源管理器关闭其句柄,该目录才真正取消链接。它正在监视目录的更改,因此会收到目录已被删除的通知,但即使它立即关闭其句柄,脚本的 os.mkdir 也会存在竞争条件。称呼。

您应该重试 os.mkdir在一个循环中,超时时间增加。您还需要一个 onerror shutil.rmtree 的处理程序处理尝试删除不为空的目录,因为它包含“已删除”的文件或目录。

例如:

import os
import time
import errno
import shutil

def onerror(function, path, exc_info):
# Handle ENOTEMPTY for rmdir
if (function is os.rmdir
and issubclass(exc_info[0], OSError)
and exc_info[1].errno == errno.ENOTEMPTY):
timeout = 0.001
while timeout < 2:
if not os.listdir(path):
return os.rmdir(path)
time.sleep(timeout)
timeout *= 2
raise

def clean_dir_safe(path):
shutil.rmtree(path, onerror=onerror)
# rmtree didn't fail, but path may still be linked if there is or was
# a handle that shares delete access. Assume the owner of the handle
# is watching for changes and will close it ASAP. So retry creating
# the directory by using a loop with an increasing timeout.
timeout = 0.001
while True:
try:
return os.mkdir(path)
except PermissionError as e:
# Getting access denied (5) when trying to create a file or
# directory means either the caller lacks access to the
# parent directory or that a file or directory with that
# name exists but is in the deleted state. Handle both cases
# the same way. Otherwise, re-raise the exception for other
# permission errors, such as a sharing violation (32).
if e.winerror != 5 or timeout >= 2:
raise
time.sleep(timeout)
timeout *= 2

讨论

在常见情况下,这个问题是“避免的”,因为现有的打开不共享删除/重命名访问。在这种情况下,尝试删除文件或目录会因共享冲突而失败(winerror 32)。例如,如果一个目录作为进程的工作目录打开,它不共享删除/重命名访问权限。对于常规文件,大多数程序仅共享读取/执行和写入/附加访问权限。

临时文件通常使用删除/重命名访问共享打开,特别是如果它们是使用删除/重命名访问打开的(例如,使用关闭时删除标志打开)。这是仍然链接但无法访问的“已删除”文件的最常见原因。另一种情况是打开一个目录来监视更改(例如,参见 ReadDirectoryChangesW )。通常,此打开将共享删除/重命名访问权限,这就是资源管理器在此问题中的情况。

对于 Unix 开发人员来说,说文件被删除而不被取消链接可能听起来很奇怪(至少可以这么说)。在 Windows 中,删除文件(或目录)只是在其文件控制 block (FCB) 上设置删除处置。当文件系统清理文件的最后一个内核文件对象引用时,具有其删除处置集的文件将自动取消链接。文件对象通常由 CreateFileW 创建。 ,它返回对象的句柄。当文件对象的最后一个句柄关闭时,将触发文件对象的清理。由于子进程中的句柄继承或显式 DuplicateHandle 可能存在文件对象的多个句柄引用来电。

重申一下,一个文件或目录可以被多个内核文件对象引用,每个内核文件对象都可以被多个句柄引用。通常,使用经典的 Windows 删除语义,必须在文件取消链接之前关闭所有句柄。此外,设置删除处置不一定是最终的。如果任何打开的句柄具有删除/重命名权限,它实际上可以用于通过清除删除处置来恢复对文件的访问(例如,参见 SetFileInformationByHandle : FileDispositionInfo )。

在 Windows 10 中,内核还支持 POSIX 删除语义,一旦删除句柄关闭,文件或目录就会立即取消链接(参见 NTAPI FileDispositionInformationEx 的详细信息)。 NTFS 已更新以支持 POSIX 删除语义。最近WINAPI DeleteFileW (即 Python os.remove )如果文件系统支持它,则已切换到使用它,但是 RemoveDirectoryW (即 Python os.rmdir )仍然仅限于经典的 Windows 删除。

对于 NTFS,实现 POSIX 语义相对容易。它只是设置删除处置并将文件重命名为 NTFS 保留目录“\$Extend\$Deleted”,名称基于其文件 ID。实际上,该文件似乎已取消链接,同时继续允许现有文件对象访问该文件。与经典删除相比,一个显着的区别是原始名称丢失了,因此删除处置不能被具有删除/重命名访问权限的现有句柄取消设置。

关于python - 在 Windows 上成功 shutil.rmtree 后,os.mkdir 可能会因 PermissionError 而失败,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/60112795/

27 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com