Files.createDirectories throws AccessDeniedException for an existing directory

ole.sh at gmx.de ole.sh at gmx.de
Wed Nov 27 11:01:58 UTC 2024


Hi Brian,

thanks for our reply!

>> I came a across a behavior of Files.createDirectories on Linux (RHEL8, JDK 17)
>> that is a litte "unexpected":
>> The call of Files.createDirectories for an existing (and readable) directory
>> on an NFS mount throws an AccessDeniedException. Technically this exception is
>> correct because our process does not have the filesystem rights to create the
>> directory, the process does have the rights to "read" the directory though.
>> Looking at the JavaDoc of Files.createDirectories my expectation was that no
>> exception would be thrown because the directory exists. I know that
>> Files.createDirectories may throw an IOException and thus an
>> AccessDeniedException but - as I said - it is a little "unexpected". What was
>> even more suprising is that for the same directory the call to
>> Files.createDirectories sometimes works and sometimes throws an
>> AccessDeniedException (using the same process, user, etc. of course).
> This sounds like correct behavior driven by whatever errno the mkdir system call
> reports. If the target is a directory, then one would not expect an exception
> for EEXISTS but would expect an AccessDeniedException for EACCES.
I didn't mean to say that the behavior isn't correct, it just seemed a bit "unexpected" (at least to me as a "user" of the API not knowing the details of the implementation of Files.createDirectories). 

>> I took a closer look at it and saw that Files.createDirectories never
>> explicitly checks for the existence of the directory but relies on the
>> createDirectory of the underlying FileSystemProvider to throw an
>> FileAlreadyExistsException. In case of Linux the underlying
>> UnixFileSystemProvider calls the mkdir system call and checks/translates its
>> return code.
> I assume that you intend the system call itself here, as the JNI interface to
> the system call does not translate the errno value.
I was referring to UnixException.translateToIOException(String, String) which is called by UnixFileSystemProvider.createDirectory. It looks like it "translates" the errno (which I guessed is the return code of the system call) to regular IOExceptions like AccessDeniedException or FileAlreadyExistsException.

>> The problem is that the mkdir system call seems to be allowed (according to
>> Posix?) to return any of the matching return codes in case multiple return
>> codes are "correct”. And that happens in our NFS case.
> Do you have a link to documentation of this behavior / specification?
It is described in section "2.3 Error Numbers" in the "System Interfaces" volume of Posix (https://pubs.opengroup.org/onlinepubs/9799919799.2024edition/functions/V2_chap02.html#tag_16_03):
"
If more than one error occurs in processing a function call, any one of the possible errors may be returned, as the order of detection is undefined.
"

>> We verified that the mkdir call sometimes returns EACCESS and sometimes EEXIST
>> (most likely due to some "caching" in the kernel NFS driver).
> That sounds like flakiness in the system itself, not in Java, perhaps an NFS
> configuration issue? It seems that Java is responding as expected to the errno
> values encontered.
I agree that it is a flakiness in the system though it seems to be "correct"/OK wrt. Posix. 

>> Although it is just an "edge case" I wonder if it makes sense to make
>> Files.createDirectories more "predictable" in such cases? A simple solution
>> might be to adapt Files.createAndCheckIsDirectory to check if the "dir" is a
>> directory not just in case of an FileAlreadyExistsException but also in case
>> of an AccessDeniedException:
>> 
>> private static void createAndCheckIsDirectory(Path dir,
>> FileAttribute<!--?-->>... attrs)
>> throws IOException
>> {
>> try {
>> createDirectory(dir, attrs);
>> } catch (FileAlreadyExistsException x) {
>> if (!isDirectory(dir))
>> throw x;
>> + } catch (AccessDeniedException x) {
>> + if (!isDirectory(dir))
>> + throw x;
> I am not sure that we want to hide the AccessDeniedException when the target is
> a directory. Subsequently executed code might encounter problems and it could be
> harder to identify the cause. Also, this code is used by all platforms, not only
Linux.
>> }
>> }
 
>> Some additional notes:
>> * A similar problem was reported and fixed in JDK-8032220 for MacOS and
>> Windows
> In that issue, filtering of EISDIR was added but this appears to be an error
specific to macOS.
What I was referring more to were the changes made in the WindowsFileSystemProvider.createDirectory for JDK-8032220 (https://hg.openjdk.org/jdk9/jdk9/jdk/rev/8ff79b0e3503): An ERROR_ACCESS_DENIED is catched and a FileAlreadyExistException is thrown instead if the directory already exists.
In the bug ticket (https://bugs.openjdk.org/browse/JDK-8032220) it is said that "It does not appear to be an issue on Linux". This is true for the special case of "/" handled in the bug ticket. But given the behavior of mkdir regarding the return code according to Posix similar problems could/do occur on Linux (as in my NFS case)

Best regards,
Ole


More information about the nio-dev mailing list