Reputation: 340
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
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
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