close(2)
的Linux手册页指出:
注意
不检查
close()
的返回值是一个常见但严重的编程错误。很可能在最后的write(2)
上首次报告先前close()
操作的错误。关闭文件时不检查返回值可能会导致数据无声丢失。使用NFS和磁盘配额尤其可以观察到这种情况。 [...]
现在我想知道这是否真的如此:很多软件项目不检查close()
的返回值,但这是一个问题吗?我试图通过在文件中使用小ext2
文件系统并写入磁盘容量边界附近的文件来生成这样的错误,但我唯一得到的是来自{{ENOSPC
的{{1}} 1}}系统调用。
那么:有没有办法可以在write(2)
上重复触发I / O错误,其中有效的文件描述符引用文件,最好是在Linux上,但* BSD会很好同样。
答案 0 :(得分:2)
与manpage状态一样,这主要是NFS共享的一个问题。引用nfs.sourceforge.net:
因此,当应用程序打开存储在NFS中的文件时,NFS客户端会检查它是否仍然存在于服务器上,并通过发送GETATTR或ACCESS操作被允许进入开启者。当应用程序关闭文件时,NFS客户端会将所有挂起的更改写回文件,以便下一个开启者可以查看更改。这也使NFS客户端有机会通过close()的返回代码向应用程序报告任何服务器写入错误。
这就是检查close()的ret值变得重要的地方。当你关闭(fd)时,磁盘上的文件系统不会在正常情况下提供EIO。但是你可以尝试在你的程序关闭文件时拔出USB棒。
答案 1 :(得分:1)
由于NFS共享可能不像您可能要查找的那样可以重现测试用例,因此您还可以使用自定义返回EIO的FUSE文件系统。
基于python-llfuse的this example,以下是您可以这样做的方法:
# See header in original example for a copy of the MIT license
import os, sys, stat, errno, llfuse, time
class TestFs(llfuse.Operations):
now = time.time()
name = b"file"
data = b"text\n"
fino = llfuse.ROOT_INODE + 1
def getattr(self, inode, ctx=None):
entry = llfuse.EntryAttributes()
if inode == llfuse.ROOT_INODE:
entry.st_mode, entry.st_size = (stat.S_IFDIR | 0o755), 0
elif inode == self.fino:
entry.st_mode, entry.st_size = (stat.S_IFREG | 0o644), len(self.data)
else:
raise llfuse.FUSEError(errno.ENOENT)
entry.st_atime_ns = entry.st_ctime_ns = entry.st_mtime_ns = self.now
entry.st_gid, entry.st_uid = os.getgid(), os.getuid()
entry.st_ino = inode
return entry
def setattr(self, inode, attr, fields, fh, ctx):
return self.getattr(inode) # ignore it
def lookup(self, parent_inode, name, ctx=None):
if parent_inode != llfuse.ROOT_INODE or name != self.name:
raise llfuse.FUSEError(errno.ENOENT)
return self.getattr(self.fino)
def opendir(self, inode, ctx):
if inode != llfuse.ROOT_INODE:
raise llfuse.FUSEError(errno.ENOENT)
return inode
def readdir(self, fh, off):
assert fh == llfuse.ROOT_INODE
if off == 0:
yield (self.name, self.getattr(self.fino), 1)
def open(self, inode, flags, ctx):
if inode != self.fino:
raise llfuse.FUSEError(errno.ENOENT)
return inode
def read(self, fh, off, size):
return self.data[off:off+size]
def write(self, fh, off, buf):
return len(buf)
def flush(self, fh):
raise llfuse.FUSEError(errno.EIO)
if __name__ == '__main__':
testfs = TestFs()
llfuse.init(testfs, "/tmp/mp")
try:
llfuse.main(workers=1)
finally:
llfuse.close()
现在,让我们试一试:
$ mkdir /tmp/mp
$ python example.py &
$ ls /tmp/mp
file
$ cat /tmp/mp/file
text
cat: /tmp/mp/file: Input/output error
$ cat /dev/null >/tmp/mp/file
cat: write error: Input/output error
解释这个例子是如何工作的:它基本上只是一堆存根实现,除了这两行:
def flush(self, fh):
raise llfuse.FUSEError(errno.EIO)
每当发生刷新或关闭操作时都会调用 flush
- 我们只是引发EIO,这意味着,只要有人刷新或关闭此文件,系统调用就会返回-EIO。你可能想稍微调整一下这个程序,但它应该提供一种非常可重现的方法来测试程序在遇到EIO时的行为。