打开并编写一个大型二进制文件python



我有一个自制的基于web的文件系统,允许用户将文件下载为zip;然而,我在开发生产系统中不存在的本地盒子时发现了一个问题。

在linux中,这是没有问题的(本地dev框是一个windows系统)。

我有以下代码

algo = CipherType('AES-256', 'CBC')
decrypt = DecryptCipher(algo, cur_share.key[:32], cur_share.key[-16:])
file = open(settings.STORAGE_ROOT + 'f_' + str(cur_file.id), 'rb')
temp_file = open(temp_file_path, 'wb+')
data = file.read(settings.READ_SIZE)
while data:
dec_data = decrypt.update(data)
temp_file.write(dec_data)
data = file.read(settings.READ_SIZE)
# Takes a dump right here!
# error in cipher operation (wrong final block length)
final_data = decrypt.finish()
temp_file.write(final_data)
file.close()
temp_file.close()

上面的代码打开一个文件,(使用当前文件共享的密钥)解密该文件并将其写入一个临时位置(稍后将填充到zip文件中)。

我的问题在file = open(settings.STORAGE_ROOT + 'f_' + str(cur_file.id), 'rb')线上。由于windows关心二进制文件,如果我不指定'rb',文件将不会在数据读取循环中读取到最后;然而,由于某种原因,由于我也在向temp_file写入,它从未完全读取到文件的末尾除非我在b'rb+'后面加一个+。

如果我将代码更改为file = open(settings.STORAGE_ROOT + 'f_' + str(cur_file.id), 'rb+'),一切都按要求运行,代码成功地刮取了整个二进制文件并对其进行了解密。如果我不添加加号,它将失败,无法读取整个文件。。。

代码的另一部分(用于下载单个文件)读取(无论操作系统如何,都能完美工作):

algo = CipherType('AES-256', 'CBC')
decrypt = DecryptCipher(algo, cur_share.key[:32], cur_share.key[-16:])
file = open(settings.STORAGE_ROOT + 'f_' + str(cur_file.id), 'rb')
filename = smart_str(cur_file.name, errors='replace')
response = HttpResponse(mimetype='application/octet-stream')
response['Content-Disposition'] = 'attachment; filename="' + filename + '"'
data = file.read(settings.READ_SIZE)
while data:
dec_data = decrypt.update(data)
response.write(dec_data)
data = file.read(settings.READ_SIZE)
# no dumps to be taken when finishing up the decrypt process... 
final_data = decrypt.finish()
temp_file.write(final_data)
file.close()
temp_file.close()

澄清

密码错误可能是因为没有完整读取文件。例如,我有一个500MB的文件,每次读取64*1024字节。我一直读到没有收到更多的字节,当我在窗口中没有指定b时,它会循环两次循环并返回一些糟糕的数据(因为python认为它与字符串文件而不是二进制文件交互)。

当我指定b时,完全读取文件需要10-15秒,但它成功地完成了,代码正常完成。

当我在从源文件读取时同时写入另一个文件时(如第一个示例中所示),如果我没有指定rb+,它显示的行为与甚至没有指定b的行为相同,即在关闭句柄并继续操作之前,它只从文件中读取几段,我最终会得到一个不完整的文件,解密失败。

我在这里猜测一下:

你有一些其他程序在不断地替换你试图读取的文件。

在linux上,这个其他程序通过原子替换文件(即,写入临时文件,然后将临时文件移动到路径)来工作。所以,当你打开一个文件时,你会得到8秒前的版本。几秒钟后,有人出现并将其从目录中取消链接,但这不会以任何方式影响您的文件句柄,因此您可以在空闲时read整个文件。

在Windows上,没有原子替换这回事。有多种方法可以解决这个问题,但许多人所做的只是将文件重写到位。所以,当你打开一个文件时,你会得到8秒前的版本,开始read对它进行重写……然后突然有人清空文件进行重写。这确实会影响你的文件句柄,因为他们重写了同一个文件。所以你达到了EOF。

r+模式打开文件并不能解决问题,但它添加了一个隐藏问题的新问题:您打开文件时使用的共享设置会阻止其他程序重写文件。所以,现在另一个程序失败了,这意味着没有人干扰这个程序,也意味着这个程序似乎可以工作。

事实上,它可能比这更微妙、更烦人。Windows的较新版本尝试智能化。如果我试图在其他人锁定的情况下打开文件,而不是立即失败,它可能会等待很短时间后重试。具体如何工作的规则取决于您需要的共享和访问,并且在任何地方都没有真正的文档记录。实际上,无论何时它以你想要的方式工作,都意味着你依赖于一种种族状况。这对于将文件从资源管理器拖动到记事本等交互式操作来说是可以的(最好99%的时间成功,而不是10%的时间),但对于试图可靠工作的代码来说显然是不可接受的(99%的时间失败只意味着问题更难调试)。因此,它可以很容易地在rr+模式之间以不同的方式工作,原因是你永远无法完全弄清楚,如果可以的话,你也不想依赖…


无论如何,如果这是你的问题,你需要修复另一个程序,重写文件的程序,或者可能是两个程序合作,以在Windows上正确模拟原子文件替换。你无法通过这个程序来解决它。*


*好吧,你可以做一些事情,比如乐观检查读取检查,并在modtime意外更改时重新启动,或者使用文件系统通知API,或者……但这比在正确的地方修复它要复杂得多。

最新更新