简体   繁体   中英

What can cause close(2) to fail with EIO for a read-only file?

I'm investigating a problem on Android where an IOException is getting thrown because of a failure to close a file:

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

The relevant code:

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();
    }
}

The full source is on GitHub .

The exception is being thrown from the line fis.close() . From what I can tell from reading the Android sources, it looks like FileInputStream.close() just calls down into close(2) on the underlying file descriptor in native code.

The manual pages don't seem to specify what can cause an EIO error, they just say things like "An I/O error occurred." or "If an I/O error occurred while reading from or writing to the file system during close()". The Mac OS X man pages say it can occur when "A previously-uncommitted write(2) encountered an input/output error." on those systems.

What exactly can cause close(2) to fail with the error EIO for a file descriptor which was only opened for reading, as in this case? Clearly it's not an uncommitted write(2) . In the case of this particular file, it was downloaded using Android's DownloadManager service, which means there might be lingering threads and/or processes trying to access it simultaneously, but I can hardly see how that would affect trying to close it. Also, the file is just about to be deleted after this code runs ( here ), but unless Android has an undocumented time machine in it, future code ought not to have an affect here.

I'm interested specifically in the answer on Android and/or Linux, but a more general answer for other OSes would also be appreciated.

I'm guessing the EIO comes from bad_file_flush in fs/bad_inode.c . It seems when the kernel has any failure accessing an inode, it transforms the open file description to a pseudo-open-file of with bad_inode_ops as its file ops. I can't find the code that does this for FAT-based filesystems, but perhaps there's some generic code.

As for the reason, it's probably something like attaching a USB cable and mounting the filesystem from an attached computer, removing the SD card, etc.

In general, you should always anticipate IOExceptions when closing streams. The code is very straightforward, but see here for the cleanest example Java can afford:

https://stackoverflow.com/a/156520/1489860

However, in your specific case, I imagine an exception is being thrown because it appears you are changing the value of the InputStream in the unzipOrPassthrough(InputStream) method and then later trying to close it:

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

When you later call close on the FileInputStream class, it probably freaks out because it is now a ByteArrayInputStream and no longer a FileInputStream.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM