什么原因会导致针对只读文件调用close(2)失败并返回EIO?

3

我正在调查一个Android问题,其中因未能关闭文件而抛出了IOException

java.io.IOException: close failed: EIO (I/O error)
    at libcore.io.IoUtils.close(IoUtils.java:41)
    at java.io.FileInputStream.close(FileInputStream.java:121)
    at com.adamrosenfield.wordswithcrosses.io.JPZIO.convertJPZPuzzle(JPZIO.java:191)
    at com.adamrosenfield.wordswithcrosses.net.AbstractJPZDownloader.download(AbstractJPZDownloader.java:56)
    at com.adamrosenfield.wordswithcrosses.net.AbstractJPZDownloader.download(AbstractJPZDownloader.java:41)
    at com.adamrosenfield.wordswithcrosses.net.AbstractDownloader.download(AbstractDownloader.java:112)
    at com.adamrosenfield.wordswithcrosses.net.AbstractDownloader.download(AbstractDownloader.java:108)
    at com.adamrosenfield.wordswithcrosses.net.Downloaders.download(Downloaders.java:257)
    at com.adamrosenfield.wordswithcrosses.BrowseActivity.internalDownload(BrowseActivity.java:702)
    at com.adamrosenfield.wordswithcrosses.BrowseActivity.access$6(BrowseActivity.java:696)
    at com.adamrosenfield.wordswithcrosses.BrowseActivity$7.run(BrowseActivity.java:691)
    at java.lang.Thread.run(Thread.java:856)
Caused by: libcore.io.ErrnoException: close failed: EIO (I/O error)
    at libcore.io.Posix.close(Native Method)
    at libcore.io.BlockGuardOs.close(BlockGuardOs.java:75)
    at libcore.io.IoUtils.close(IoUtils.java:38)
    ... 11 more

相关代码如下:
public static void convertJPZPuzzle(File jpzFile, File destFile,
        PuzzleMetadataSetter metadataSetter) throws IOException {
    FileInputStream fis = new FileInputStream(jpzFile);
    try {
        DataOutputStream dos = new DataOutputStream(new FileOutputStream(destFile));
        try {
            if (!convertJPZPuzzle(fis, dos, metadataSetter)) {
                throw new IOException("Failed to convert JPZ file: " + jpzFile);
            }
        } finally {
            dos.close();
        }
    } finally {
        fis.close();
    }
}

完整的源代码可以在GitHub上找到。
异常是从fis.close()这一行抛出的。从我阅读Android源码得知,看起来FileInputStream.close()对本地代码中的底层文件描述符只是调用了close(2)
手册似乎没有说明什么会导致EIO错误,它们只是说“发生了I/O错误”或者“如果在关闭()期间从文件系统读取或写入时发生了I/O错误”。Mac OS X的手册说它可能发生在“以前未提交的write(2)遇到输入/输出错误。”的情况下。在这些系统中,该错误可能是由于内存问题等原因引起的。
像本例中仅打开文件进行读取的文件描述符,究竟是什么原因导致close(2)失败并显示EIO错误?显然不是未提交的write(2)。对于这个特定文件,它是使用Android的DownloadManager服务下载的,这意味着可能有残留的线程和/或进程同时访问它,但我几乎无法想象这会影响关闭文件。此外,在此代码运行之后,文件将被删除(此处),但除非Android有未记录的时间机器,否则未来的代码不应在这里产生影响。
我特别感兴趣的是Android和/或Linux的答案,但对其他操作系统的一般答案也将受到欢迎。

我无法继续下去,因为我不懂cposix系统。相关问题和答案在这里libcore.io的源代码在这里close(用于fd)的native实现在这里 - Sotirios Delimanolis
在最后一个链接中,您需要找到第429行上close()的实现。 - Sotirios Delimanolis
@SotiriosDelimanolis:是的,我看到了那个问题——讨论了EIO错误后文件描述符的状态;我对导致EIO的原因感兴趣,而不是在错误发生后该怎么做。close()的实现是Linux内核中的系统调用。 - Adam Rosenfield
似乎与坏的inode有关,但是深入内核代码需要比我想象的更长的时间... - jxh
同样的。除了特殊的文件系统,我也看不出close会返回EIO的其他方式。你所说的文件所处的文件系统类型是什么? - R.. GitHub STOP HELPING ICE
@R..:我不确定,因为这个错误发生在另一个用户的设备上,但是它是在SD卡上发生的。我的Android设备的SD卡格式为vfat(根据mount(1)),所以我怀疑用户的SD卡也可能是这种格式。 - Adam Rosenfield
2个回答

3
我猜测 EIO 来自于 fs/bad_inode.c 中的 bad_file_flush。当内核访问一个索引节点时出现任何故障,它会将打开文件描述符转换为伪打开文件,其文件操作使用 bad_inode_ops。我找不到对基于 FAT 的文件系统执行此操作的代码,但可能存在某些通用代码。
至于原因,可能是类似插入 USB 线并从连接的计算机上挂载文件系统、移除 SD 卡等。

由于我不幸无法访问我的用户设备,所以我无法确定发生了什么,但在那之前,这似乎是最有可能的解释。在浏览内核源代码一段时间后,似乎 sys_close() 只能返回 0、-EINTR-EBADF 或文件系统 flush() 实现的返回值,而 vfat 没有实现 flush() - Adam Rosenfield

-1
通常情况下,在关闭流时,您应该始终预期IOExceptions异常。代码非常简单,但是请参阅此处的Java提供的最干净的示例:

https://dev59.com/GXVC5IYBdhLWcg3w4VVz#156520

然而,在您的特定情况下,我想异常被抛出是因为看起来您正在unzipOrPassthrough(InputStream)方法中更改InputStream的值,然后尝试在稍后关闭它:

        if (entry == null) {
        is = new ByteArrayInputStream(baos.toByteArray());

当您稍后在FileInputStream类上调用close时,它可能会出现问题,因为它现在是一个ByteArrayInputStream而不再是FileInputStream。

1
这根本就没有意义。如果现在是一个 ByteArrayInputStream 对象,他就不能“稍后调用 FileInputStream 类上的 close”。他正在关闭 ByteArrayInputStream。结束了。关于“现在是 ByteArrayInputStream 而不再是 FileInputStream”的部分是无意义的。引用是类型为 InputStream 的。对象的引用无法记住它们曾经指向的对象类型。 - user207421
unzipOrPassthrough() 从传入的输入流中读取数据,但不关闭它。它重新分配了本地参数is,但这对传入的原始流没有影响。 - Adam Rosenfield

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接