Links in NIO.2

One of the most important features in the NIO.2 library is the introduction of mechanism to work with links directly from your Java code (this was not possible to do without the use of native code in previous IO libraries). Most of the advanced users are already familiar with concept of hard links and symbolic links. Before we start to talk about links lets review some basic characteristics of both types:

  • Hard link or simply link is a directory entry that links file with name in given file system. Single file can be linked using multiple hard links. Hard link must always have an existing  target and this target can only be a file. User does not lose access to a target file by removing a hard link. Hard link provides linked file even after the target file was removed. Hard links are always bound to a single file system.
  • Symbolic link or soft link is a specific file containing relative or absolute path to a target file. Single file can be linked using multiple symbolic links. Symbolic links do not require an existing target and their target might be file or directory. User does not lose access to a target file by removing a symbolic link. Symbolic link is not influenced by removing of the target file, since its target is not required to exist. These kind of links are called dead links or broken links. Symbolic links are not bound to a single file system.

Before we start with this topic please note that support for links is highly platform and file system provider dependent so it is always good idea to consult documentation before any link related programming.

Hard links

There is pretty convenient way to create both hard and symbolic links and it involves work with java.nio.file.Files class. Use the method createLink to create a hard link. Following example shows how easy it is to create a hard link using Files class. I used exists method to check whether any link was created.

Path target = Paths.get("c:/a.txt");
Path link = Paths.get("c:/links/b.txt");

// creates hard link
Files.createLink(link, target);

// returns true

It is always good to keep in mind that hard links require target file to exist. If you don’t check the existence of your target file you might end up with a checked exception NoSuchFileException just like method body in following example.

Path target = Paths.get("c:/b.txt");
Path link = Paths.get("c:/links/b.txt");

// returns false

// throws java.nio.file.NoSuchFileException: c:\links\b.txt -> c:\b.txt
Files.createLink(link, target);

Another example of code that results in checked exception is any attempt to create a hard link pointing to a directory (hard links can be created only for files as mentioned earlier). Following code demonstrates such a behavior.

Path target = Paths.get("c:/temp");
Path link = Paths.get("c:/links/tempLink");

// throws AccessDeniedException: c:\links\tempLink -> c:\temp
Files.createLink(link, target);

To avoid any confusion that could be caused be the exception class name it is good to check out Javadoc for AccessDeniedException class:

Checked exception thrown when a file system operation is denied, typically due to a file permission or other access check. This exception is not related to the AccessControlException or SecurityException thrown by access controllers or security managers when access to a file is denied.

Symbolic links

Symbolic link creation is very similar to the previous example and requires the use of method createSymbolicLink from Files class. Since symbolic link allows programmer to link files and directories as well as non-existing file system locations following example successfully creates three symbolic links.

Path fileTarget = Paths.get("c:/a.txt");
Path directoryTarget = Paths.get("c:/temp");
Path nonExistingTarget = Paths.get("c:/b.txt");

Path directoryLink = Paths.get("c:/links/linkTemp");
Path fileLink1 = Paths.get("c:/links/c.txt");
Path fileLink2 = Paths.get("c:/links/d.txt");

// returns true
// returns true
// returns false

// creates three symbolic links
Files.createSymbolicLink(fileLink1, fileTarget);
Files.createSymbolicLink(directoryLink, directoryTarget);
Files.createSymbolicLink(fileLink2, nonExistingTarget);

Please note that both hard and symbolic links require link path to be pointing to a non-existing location on file system otherwise you receive FileAlreadyExistsException.

One specific trait of symbolic links that differentiates  them from hard links is the fact that they are in fact files. This means that they have their own file system metadata (file attributes). One important thing to bear in mind is the fact the creation of symbolic link and population of its file attributes is an atomic operation in NIO.2, which was not possible to achieve without native code until now. Having said that I would love to show you how to do this. But since one of core duties of FileSystemProvider is to initialize file attributes, we need FileSystemProvider‘s support to do this. However neither UnixFileSystemProvider nor WindowsFileSystemProvider does not support symbolic links to be initialized with file attributes just yet. Based on what I was able to find in their sources we can expect this functionality in upcoming release of Java 8. It is possible to achieve this functionality, but it requires working with other (custom) file system / file system provider that supports this feature.

Exploring target of link

Now that we know how to create both types of links it is a good idea to be able to explore the target of these links. Since hard links must always have an existing target and the target must be file there is no need to explore this target further. However symbolic links are more flexible than that and it is good to know how to explore their target. To do this we will turn to Files class and use its readSymbolicLink method. This method has one parameter – path of a link. If we manage to pass it a path that is not a link we will end up with NotLinkException. Whole situation is described in following snippet.

Path target = Paths.get("c:/temp");
Path link = Paths.get("c:/links/tempLink");
Path path = Paths.get("c:/a.txt");

// creates hard link
Files.createSymbolicLink(link, target);

// returns c:\temp
// throws java.nio.file.NotLinkException: The file or directory is not a reparse point.

Link detection

To prevent the NotLinkException from previous example we need a way to detect whether certain path is a link or not. Files class provides such a method and it name is isSymbolicLink. Following example demonstrates the use of this method – no surprises there.

Path target = Paths.get("c:/a.txt");
Path hardLink = Paths.get("c:/links/harLink.txt");
Path symbolicLink = Paths.get("c:/links/symbolicLink.txt");

// creates test links
Files.createLink(hardLink, target);
Files.createSymbolicLink(symbolicLink, target);

// returns false
// returns false
// returns true

File system operations

Last area of links that I haven’t covered so far is the way library classes handle links when they stumble upon them while performing their duties. These ‘link aware’ methods often include working with file attributes or checking whether a path is file or a directory and validating its existence. To guide these methods NIO.2 provides java.nio.file.LinkOption enum. This enum (as stated in the Javadoc) defines the options as to how symbolic links are handled. There is only one value available so far and it is NOFOLLOW_LINKS which makes these methods treat links as plain old paths.

Lets consider an example – a method I mentioned in File Attributes post for reading file metadata, readAttributes. This method is nice example of the need to be able to differentiate whether the algorithm should work with the target of the symbolic link or the link itself. By default, symbolic links are followed. This means that without specifying LinkOption parameter, links target file attributes are read and not its own. On the other hand, if this is not the desired operation, we can direct the method on the link itself by specifying LinkOption.NOFOLLOW_LINKS. The whole situation is presented in the example below.

Path target = Paths.get("c:/a.txt");
Path symbolicLink = Paths.get("c:/links/symbolicLink.txt");

// creates test link
Files.createSymbolicLink(symbolicLink, target);

BasicFileAttributes targetAttributes = Files.readAttributes(symbolicLink, BasicFileAttributes.class);
BasicFileAttributes linkAttributes = Files.readAttributes(symbolicLink, BasicFileAttributes.class, LinkOption.NOFOLLOW_LINKS);

System.out.println("File attribute - isSymbolicLink\tTarget: " + targetAttributes.isSymbolicLink() + "\t\t\t\tLink: " + linkAttributes.isSymbolicLink());
System.out.println("File attribute - size\t\tTarget: " + targetAttributes.size() + "\t\t\t\tLink: " + linkAttributes.size());
System.out.println("File attribute - creationTime:\tTarget: " + targetAttributes.creationTime() + "\tLink: " + linkAttributes.creationTime());

With an output:

File attribute - isSymbolicLink: Target: false                        Link: true
File attribute - size:           Target: 8556                         Link: 0
File attribute - creationTime:   Target: 2013-12-08T16:43:19.55401Z   Link: 2013-12-14T16:09:17.547538Z

If you enjoyed this post, then make sure you subscribe to my Newsletter Newsletter and/or Feed Feed

Posted in NIO.2 Tagged with: , , ,

Leave a Reply