Name: gm110360 Date: 11/18/2003
FULL PRODUCT VERSION :
java version "1.4.2"
Java(TM) 2 Runtime Environment, Standard Edition (build 1.4.2-b28)
Java HotSpot(TM) Client VM (build 1.4.2-b28, mixed mode)
FULL OS VERSION :
Linux fasthat 2.4.20-19.9 #1 Tue Jul 15 17:03:30 EDT 2003 i686 athlon i386 GNU/Linux
A DESCRIPTION OF THE PROBLEM :
File.exists() reports false for symbolic link files that DO exist,
but refer to nonexistent targets. This is a bug.
If I have a File object that is a non-canonicalized path pointing
to the file (that has an inode) which is a symbolic link,
then it exists() should report true.
This is because we need to manipulate symbolic links as files in their own right.
Note that File.delete() will work just fine on such a file (a symlink with
no existing target). So far I've only noticed that exists() messes up.
So given that the file /tmp/nonexistentfile does NOT exist,
and that I create a link as with:
ln -s /tmp/nonexistentfile /tmp/nonexistentfilesymlink
The FILE /tmp/nonexistentfilesymlink DOES exist,and exists() should return
true, but it doesn't and that's the bug.
STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
See attached Java program that reproduces the bug. I've only tested it on Linux.
EXPECTED VERSUS ACTUAL BEHAVIOR :
EXPECTED -
The program output should say that the file exists, but says the file doesn't exist.
ACTUAL -
Here's the test output on my machine:
Here's what 'ls -ld' says about the symbolic link BEFORE our attempt to create it:
/bin/ls: /tmp/nonexistentfilesymlink: No such file or directory
Here's what 'ls -ld' says about the symbolic link AFTER our attempt to create it:
lrwxrwxrwx 1 dave dave 20 Sep 13 11:46 /tmp/nonexistentfilesymlink -> /tmp/nonexistentfile
The bug reproduces on your system.
/tmp/nonexistentfilesymlink.exists() reports FALSE, despite the fact the link file is there.
Curiously, File.delete() on the symlink works which is inconsistent with exists() behavior,
but this IS the desired behavior. (PLEASE DO NOT CHANGE IT!)
Here's what 'ls -ld' says about the symbolic link as we wind up the test:
/bin/ls: /tmp/nonexistentfilesymlink: No such file or directory
REPRODUCIBILITY :
This bug can be reproduced always.
---------- BEGIN SOURCE ----------
import java.io.* ;
import java.util.* ;
/**
* File.exists() fails for symbolic links referring to nonexistent targets.
* I believe that a file specification identifying the LINK, and not the target of the link,
* should return true if the link file exists (after all, it has an inode, etc).
* I can always query the existence of the link target by calling exists on the
* canonical form of the file.
*
* This bug was tested against JDK 1.4.2 (build 1.4.2-b28) on RedHat Linux 9.0
* Linux version 2.4.20-19.9 (###@###.###) (gcc version 3.2.2 20030222 (Red Hat Linux 3.2.2-5)) #1 Tue Jul 15 17:03:30 EDT 2003
*
* @author Dave Tenny
*/
public class Bug2
{
static String linkTargetName = "/tmp/nonexistentfile" ; // will not exist
static String symlinkFileName = "/tmp/nonexistentfilesymlink" ; // will exist
static File linkTargetFile = new File(linkTargetName) ; // will not exist
static File symLinkFile = new File(symlinkFileName) ; // will exist
public static void main(String[] args) {
if (linkTargetFile.exists()) {
System.out.println("This test will not function properly if the file " + linkTargetName +
" exists. Please delete it and run the test again.") ;
return ;
}
System.out.println("Here's what 'ls -ld' says about the symbolic link " +
"BEFORE our attempt to create it:") ;
lsLink() ;
// Can't actually check for existence since that's the whole bug we're reporting.
// I.e. symLinkFile.exists() will always return false!
if (!makelink(linkTargetFile, symLinkFile))
// Note it and continue
System.out.println("Unable to create symbolic link file "
+ symLinkFile
+ ", perhaps it already existed from a prior run.") ;
System.out.println("Here's what 'ls -ld' says about the symbolic link " +
"AFTER our attempt to create it:") ;
lsLink() ;
System.out.println() ;
if (symLinkFile.exists())
System.out.println("The bug does NOT reproduce on your system.") ;
else
System.out.println("The bug reproduces on your system.\n"
+ symLinkFile +
".exists() reports FALSE, despite the fact the link file is there.") ;
System.out.println() ;
if (symLinkFile.delete())
System.out.println("Curiously, File.delete() on the symlink works " +
"which is inconsistent with exists() behavior,\n" +
"but this IS the desired behavior. (PLEASE DO NOT CHANGE IT!)") ;
else
System.out.println("symLinkFile.delete() fails, which is consistent exists(), "
+ "but is undesirable since we can't delete the link file.") ;
System.out.println() ;
System.out.println("Here's what 'ls -ld' says about the symbolic link as we wind up the test:") ;
lsLink() ;
} // main()
// Yes, I know, need stream gobblers for process output. It works for our bug-reporting purposes here.
private static boolean doSimpleCommand(String[] args) {
Runtime rt = Runtime.getRuntime() ;
try {
Process proc = rt.exec(args) ;
int result = proc.waitFor() ;
StringBuffer buffer = new StringBuffer(128) ;
// stdout
try {
InputStream stream = proc.getInputStream() ;
InputStreamReader reader = new InputStreamReader(stream) ;
int ch ;
while ((ch = reader.read()) != -1)
buffer.append((char)ch) ;
} catch (IOException e) {
buffer.append(e.toString()) ;
}
if (buffer.length() > 0)
System.out.println(buffer.toString()) ;
buffer.setLength(0) ; // reset for next use
// stderr
try {
InputStream stream = proc.getErrorStream() ;
InputStreamReader reader = new InputStreamReader(stream) ;
int ch ;
while ((ch = reader.read()) != -1)
buffer.append((char)ch) ;
} catch (IOException e) {
buffer.append(e.toString()) ;
}
if (buffer.length() > 0)
System.out.println(buffer.toString()) ;
// Done
return result == 0 ;
} catch (Exception e) {
System.out.println(args[0] + " exception: " + e.toString()) ;
return false ;
}
} // doSimpleCommand()
private static boolean makelink(File target, File source) {
// "ln -s /tmp/dir /tmp/symdir"
String[] args = {"/bin/ln", "-s", target.toString(), source.toString()} ;
return doSimpleCommand(args) ;
}
private static boolean lsLink() {
String[] args = {"/bin/ls", "-ld", symlinkFileName} ;
return doSimpleCommand(args) ;
}
} // class Bug2
---------- END SOURCE ----------
CUSTOMER SUBMITTED WORKAROUND :
I haven't implemented one yet. I suspect the workaround is to
check that getCanonicalPath() doesn't equal getAbsolutePath(),
and if so we know the file is a link.
However it isn't that simple,
we must first canonicalize all elements of the parent path of the
link file (such as higher-up symbolic links, "." and ".." references, etc), and I just reported a bug yesterday that catches File.getCanonicalPath() red handed lying about symbolic link files (whose targets DO exist), but is VERY difficult to reproduce.
This bug is hard to work around and should be fixed.
The JDK currently makes it very difficult to write applications that
manages files on a unix machine.
Another possible work around is to shell out with exec, but that adds
a lot of overhead to the call and isn't practical when dealing with thousands
or tens of thousands of files. Exec has also been very bug prone and makes
it harder to ship to customers because they have to have just the right java version.
(Incident Review ID: 207260)
======================================================================