paul allen
paul allen

Reputation: 340

Symlink lastModifiedTime in Java 1.7

I'm trying to change the lastModifiedTime value for a symlink that has NO TARGET.

for example: foo --> nothing

I can access the lastModifiedTime value using...

String fooPath = "/Users/me/test/foo"; 
Path path = new File(fooPath).toPath(); 
FileTime t = Files.getLastModifiedTime(path, LinkOption.NOFOLLOW_LINKS);

However, I am unable to set the same symlink using the snippet below; this gives me a java.nio.file.NoSuchFileException for the target...

String fooPath = "/Users/me/test/foo";
Path path = new File(fooPath).toPath();
FileTime t = FileTime.fromMillis(date.getTime());
Files.setLastModifiedTime(path, t);

I even tried to set the attribute by hand, but that gives me a java.nio.file.FileSystemException 'Too many levels of symbolic links or unable to access attributes of symbolic link' error:

Files.setAttribute(path, "lastModifiedTime", t, LinkOption.NOFOLLOW_LINKS);

I don't want to go the system call route as I need cross platform support.

Upvotes: 2

Views: 1066

Answers (2)

kfox
kfox

Reputation: 1186

Here is the JNA code for utimensat using FileTime as input.

POM dependency:

<dependency>
    <groupId>net.java.dev.jna</groupId>
    <artifactId>jna</artifactId>
    <version>4.2.2</version>
</dependency>

Java

import java.nio.file.Path;
import java.nio.file.attribute.FileTime;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.TimeUnit;

import com.sun.jna.Library;
import com.sun.jna.Native;
import com.sun.jna.NativeLong;
import com.sun.jna.Structure;

public class FileUtil {

    public static final int AT_SYMLINK_NOFOLLOW = 0x100;

    public interface CLibrary extends Library {
        CLibrary INSTANCE = (CLibrary) Native.loadLibrary("c", CLibrary.class);

        int utimensat(int dirfd, String filename, timespec times, int flags);
    }

    public static class timespec extends Structure {

        public static class ByReference extends timespec implements Structure.ByReference {}

        public NativeLong tv_sec; // seconds

        public NativeLong tv_nsec; // nanoseconds

        public timespec() {
        }

        @Override
        protected List<String> getFieldOrder() {
            return Arrays.asList(new String[] { "tv_sec", "tv_nsec" });
        }
    }

    public static boolean changeFileTime(Path file, FileTime atime, FileTime mtime) {
        timespec times = new timespec.ByReference();
        timespec[] vals = (timespec[])times.toArray(2);
        setTime(vals[0], atime);
        setTime(vals[1], mtime);
        int rtn = CLibrary.INSTANCE.utimensat(0, file.toString(), times, AT_SYMLINK_NOFOLLOW);
        return (rtn == 0);
    }

    private static void setTime(timespec val, FileTime time) {
        val.tv_sec = new NativeLong(time.to(TimeUnit.SECONDS));
        val.tv_nsec = new NativeLong(time.toInstant().getNano());
    }
}

Upvotes: 0

Simon Kissane
Simon Kissane

Reputation: 5268

This is arguably a bug or limitation in the JDK, at least on Linux and Solaris (I haven't tried Windows). Say you create your BasicFileAttributeView without LinkOption.NOFOLLOW_LINKS. The problem is that sun.nio.fs.UnixFileAttributeViews$Basic.setTimes() calls sun.nio.fs.UnixPath.openForAttributeAccess(), which in turn calls open/open64 on the symbolic link. Now, if the symbolic link has a target, this will succeed and return an fd pointing to the target. setTimes() then calls futimesat on the fd to update the access and modification times. However, this will update the modification time of the link target, not the link itself, which is not what you want, and will not work if the link is broken.

So you would think the answer would be to pass LinkOption.NOFOLLOW_LINKS when you request the BasicFileAttributeView. However, in that scenario, openForAttributeAccess() passes O_NOFOLLOW to open, which is specified to return an ELOOP error when a symlink occurs, which results in the error message you mentioned. In any event, since there is no way to get an fd for a symlink, the strategy the JDK uses won't work. It would need to forgo the fd, and use say utimensat or lutimes instead.

Unfortunately, it looks like using a system call (say using JNA) is the only option here. utimensat is the POSIX 2008 standard way of doing this, but it is new enough that many Unix-like operating systems don't have it yet or only do in their latest version. lutimes exists on Linux and BSD, but is non-standard.

Upvotes: 5

Related Questions