我有一个自制的基于Web的文件系统,允许用户将文件下载为拉链;但是,我在生产系统上没有出现在我当地的盒子上时发现了一个问题。
在linux中,这是一个非问题(本地开发框是一个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')
行。因为如果我没有指定'rb'
,那么windows会关注二进制文件,因为文件不会读取数据读取循环结束;但是,由于某些原因,因为我也写信给temp_file
,它永远不会完全读到文件的末尾...... 除非我在b {{1}后添加+ }} 即可。
如果我将代码更改为'rb+'
,一切都按预期工作,代码成功擦除整个二进制文件并对其进行解密。如果我不添加加号则失败并且无法读取整个文件...
代码的另一部分(用于下载单个文件)读取(无论操作系统如何都能完美运行):
file = open(settings.STORAGE_ROOT + 'f_' + str(cur_file.id), 'rb+')
密码错误可能是因为文件未完整读取。例如,我有一个500MB的文件,我一次只能读取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()
个字节。我读了直到我没有收到更多的字节,当我没有在windows中指定64*1024
时,它循环遍历循环两次并返回一些蹩脚的数据(因为python认为它与字符串文件而不是二进制文件交互)。
当我指定b
时,完全读取文件需要10-15秒,但它成功完成,代码正常完成。
当我从源文件读入时同时写入另一个文件时(如第一个示例中所示)如果我没有指定b
,它会显示与甚至不指定rb+
相同的行为也就是说,它只是在关闭句柄并继续前进之前从文件读取几个段,我最终得到一个不完整的文件,解密失败。
答案 0 :(得分:1)
我打算在这里猜一下:
你还有一些其他程序会不断替换你想要阅读的文件。
在linux上,这个其他程序通过原子替换文件(即写入临时文件,然后将临时文件移动到路径)来工作。因此,当您打开文件时,您将从8秒前获得该版本。几秒钟之后,有人出现并将其与目录取消链接,但这不会以任何方式影响您的文件句柄,因此您可以随意read
整个文件。
在Windows上,没有原子替换这样的东西。有多种方法可以解决这个问题,但很多人所做的就是就地重写文件。所以,当你打开一个文件时,你从8秒前得到版本,开始read
它......然后突然有人将文件空白以重写它。 会影响您的文件句柄,因为它们已经重写了相同的文件。所以你打了一个EOF。
以r+
模式打开文件对解决问题没有任何作用,但它增加了一个隐藏它的新问题:您正在使用共享设置打开文件阻止其他程序重写文件。所以,现在其他程序失败了,这意味着没有人干扰这个程序,这意味着这个程序似乎有效。
事实上,它可能比这更微妙和恼人。 Windows的更高版本试图变得聪明。如果我试图打开一个文件,而其他人已将其锁定,而不是立即失败,它可能会等待一小段时间再试一次。确切如何工作的规则取决于您需要的共享和访问,并且在任何地方都没有真正记录。实际上,只要它按照你想要的方式工作,就意味着你依赖于竞争条件。这对于交互式的东西很好,例如将文件从资源管理器拖到记事本(最好是99%的时间而不是10%的时间成功),但显然不能接受试图可靠工作的代码(99%的时间成功)只是意味着问题更难调试)。所以它可以轻松地在r
和r+
模式之间以不同的方式工作,原因是你永远无法弄清楚,并且如果可以的话也不会依赖......
无论如何,如果你的问题有任何变化,你需要修复其他程序,重写文件,或者可能两个程序合作,以正确模拟Windows上的原子文件替换。只有这个程序才能解决它。*
*嗯,你可以做一些事情,比如乐观的检查 - 读取检查,并在modtime意外改变时重新开始,或者使用文件系统通知API,或者......但它会比将它固定在正确的位置。