0

我有一个基于网络的自制文件系统,允许用户以 zip 格式下载文件;但是,我在生产系统上不存在的本地盒子上进行开发时发现了一个问题。

在 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 之后添加 a + '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在 Windows 中指定时,它会在循环中循环两次并返回一些糟糕的数据(因为 python 认为它正在与字符串文件而不是二进制文件交互)。

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

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

4

1 回答 1

1

我在这里猜测一下:

您有一些其他程序不断替换您尝试读取的文件。

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

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

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

事实上,它可能比这更微妙和烦人。更高版本的 Windows 试图变得聪明。如果我在其他人锁定文件时尝试打开文件,而不是立即失败,它可能会等待一小段时间然后重试。具体如何工作的规则取决于您需要的共享和访问,并且在任何地方都没有真正记录。实际上,只要它以您想要的方式工作,就意味着您依赖于竞争条件。这对于诸如将文件从资源管理器拖动到记事本之类的交互式内容很好(最好在 99% 的时间而不是 10% 的时间成功),但对于试图可靠工作的代码(在 99% 的时间成功)显然是不可接受的只是意味着问题更难调试)。所以它可以很容易地在r和之间以不同的方式工作r+模式的原因,你永远无法完全弄清楚,如果可以的话,也不想依赖……</p>


无论如何,如果这是您的问题,您需要修复其他程序,即重写文件的程序,或者可能是两个程序合作,以正确模拟 Windows 上的原子文件替换。仅此程序无法解决此问题。 *


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

于 2013-09-06T22:24:58.110 回答