File.exists() returns false for file (directory) that actually exists

I have had a similar issue, but with a higher trouble rate, where the Anti Virus was locking FileSystem, and thus failing any requests (almost instantly)

the workaround was using java.nio.Files.exists() instead.


First of all, if you are using Android, bug reports in the Java Bugs database are not relevant. Android does not use the Sun / Oracle codebase. Android started out as a clean-room re-implementation of the Java class libraries.

So if there are bugs in File.exists() on Android the bugs would be in the Android codebase, and any reports would be in the Android issue tracker.

But when you say this:

According to this, when operating on NFS-mounted volumes, java.io.File.exists ends up performing a stat(2). If the stat fails (which it may do for several reasons), then File.exists (mistakenly) assumes that the file being stat'ed does not exist.

  1. Unless you are using NFS, that bug report is not directly relevant.
  2. It is not a mistake / bug. It is a limitation.
  3. At the file system level, it is a fact of life that Linux supports many different kinds of file system, and that many of them behave in unexpected ways ... compared to an "ordinary" file system. It is not possible for the JVM to hide all of the weird filesystem-specific edge cases at the Java API level.
  4. On the API level, File.exists cannot report any errors. The signature doesn't allow it to throw an IOException, and throwing an unchecked exception would be a breaking change. All it can say is true or false.
  5. If you want to distinguish the various reasons for a false, you should use the newer Files.exists(Path, LinkOptions...) method instead.

Could this be the source of my troubles?

Yes it could, and not just in the NFS case! See below. (With Files.exist, an NFS stat failure would most likely be an EIO, and that would raise an IOException rather than returning false.)


The File.java code in the Android codebase (version android-4.2.2_r1) is:

public boolean exists() {
    return doAccess(F_OK);
}

private boolean doAccess(int mode) {
    try {
        return Libcore.os.access(path, mode);
    } catch (ErrnoException errnoException) {
        return false;
    }
}

Note how it turns any ErrnoException into a false.

A bit more digging reveals that the os.access call is performing a native call which makes an access syscall, and throws ErrnoException if the syscall fails.

So now we need look at the documented behavior of the access syscall. Here's what man 2 access says:

  1. F_OK tests for the existence of the file.
  2. On error (at least one bit in mode asked for a permission that is denied, or mode is F_OK and the file does not exist, or some other error occurred), -1 is returned, and errno is set appropriately.
  3. access() shall fail if:

    • EACCES The requested access would be denied to the file, or search per‐ mission is denied for one of the directories in the path prefix of pathname. (See also path_resolution(7).)

    • ELOOP Too many symbolic links were encountered in resolving pathname.

    • ENAMETOOLONG pathname is too long.

    • ENOENT A component of pathname does not exist or is a dangling symbolic link.

    • ENOTDIR A component used as a directory in pathname is not, in fact, a directory.

    • EROFS Write permission was requested for a file on a read-only filesystem.

  4. access() may fail if:

    • EFAULT pathname points outside your accessible address space.

    • EINVAL mode was incorrectly specified.

    • EIO An I/O error occurred.

    • ENOMEM Insufficient kernel memory was available.

    • ETXTBSY Write access was requested to an executable which is being executed.

I have struck out the errors that I think are technically impossible or implausible, but the still leaves quite few to consider.


Another possibility is something (e.g. some other part of your application) is deleting or renaming the file or a (hypothetical) symlink, or changing file permissions ... behind your back.

But I don't think that File.exist() is broken1, or that the host OS is broken. It is theoretically possible, but you would need some clear evidence to support the theory.

1 - It is not broken in the sense that it is not behaving differently to the known behavior of the method. You could argue until the cows come home about whether the behavior is "correct", but it has been like that since Java 1.0 and it can't be changed in OpenJDK or in Android without breaking thousands of existing applications written over the last 20+ years. It won't happen.


What to do next?

Well my recommendation would be to use strace to track the syscalls that your app is making and see if you can get some clues as to why some access syscalls are giving you unexpected results; e.g. what the paths are and what the errno is. See https://source.android.com/devices/tech/debug/strace .

Tags:

Java

File

Android