From c637deb8a662e6d7d9ca9f1e01ee5bb7bb0aa3b6 Mon Sep 17 00:00:00 2001 From: Yves Langisch Date: Fri, 28 Aug 2026 12:19:48 +0200 Subject: [PATCH] nfs4: apply time_create as birth time in SETATTR Motivation: The macOS NFS client sends a SETATTR with time_create after an exclusive create. OperationSETATTR maps FATTR4_TIME_CREATE to Stat.CTIME, although rfc7530/rfc8881 define time_create as the creation (birth) time, explicitly unrelated to the POSIX change time, and OperationGETATTR already serves time_create from Stat.BTIME. LocalFileSystem then tries to set the non-settable "unix:ctime" attribute, which throws IllegalArgumentException and turns the compound into NFS4ERR_SERVERFAULT. macOS maps SERVERFAULT to EBADRPC, so open(O_CREAT|O_EXCL) fails with errno 72 while the file is created on the server. Modification: map FATTR4_TIME_CREATE to Stat.BTIME in OperationSETATTR, matching the GETATTR side. In LocalFileSystem apply BTIME via "basic:creationTime", drop the "unix:ctime" branch, and fix the ATIME branch that used getCTime(). Result: exclusive create from macOS clients works over NFS 4.0 and 4.1. Fixes: #174 Target: master Signed-off-by: Yves Langisch --- .../java/org/dcache/nfs4j/server/LocalFileSystem.java | 10 +++++----- .../main/java/org/dcache/nfs/v4/OperationSETATTR.java | 6 +++--- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/basic-server/src/main/java/org/dcache/nfs4j/server/LocalFileSystem.java b/basic-server/src/main/java/org/dcache/nfs4j/server/LocalFileSystem.java index 56295faa..8493de2b 100644 --- a/basic-server/src/main/java/org/dcache/nfs4j/server/LocalFileSystem.java +++ b/basic-server/src/main/java/org/dcache/nfs4j/server/LocalFileSystem.java @@ -584,7 +584,7 @@ public void setattr(Inode inode, Stat stat) throws IOException { } if (stat.isDefined(Stat.StatAttribute.ATIME)) { try { - FileTime time = FileTime.fromMillis(stat.getCTime()); + FileTime time = FileTime.fromMillis(stat.getATime()); Files.setAttribute(path, "unix:lastAccessTime", time, NOFOLLOW_LINKS); } catch (IOException e) { throw new UnsupportedOperationException("set atime failed: " + e.getMessage(), e); @@ -598,12 +598,12 @@ public void setattr(Inode inode, Stat stat) throws IOException { throw new UnsupportedOperationException("set mtime failed: " + e.getMessage(), e); } } - if (stat.isDefined(Stat.StatAttribute.CTIME)) { + if (stat.isDefined(Stat.StatAttribute.BTIME)) { try { - FileTime time = FileTime.fromMillis(stat.getCTime()); - Files.setAttribute(path, "unix:ctime", time, NOFOLLOW_LINKS); + FileTime time = FileTime.fromMillis(stat.getBTime()); + Files.setAttribute(path, "basic:creationTime", time, NOFOLLOW_LINKS); } catch (IOException e) { - throw new UnsupportedOperationException("set ctime failed: " + e.getMessage(), e); + throw new UnsupportedOperationException("set btime failed: " + e.getMessage(), e); } } } diff --git a/core/src/main/java/org/dcache/nfs/v4/OperationSETATTR.java b/core/src/main/java/org/dcache/nfs/v4/OperationSETATTR.java index 1cf50bd5..83993c34 100644 --- a/core/src/main/java/org/dcache/nfs/v4/OperationSETATTR.java +++ b/core/src/main/java/org/dcache/nfs/v4/OperationSETATTR.java @@ -185,9 +185,9 @@ static void xdr2fattr(int fattr, Stat stat, Inode inode, CompoundContext context stat.setATime(realAtime); break; case nfs4_prot.FATTR4_TIME_CREATE: - nfstime4 ctime = new nfstime4(); - ctime.xdrDecode(xdr); - stat.setCTime(ctime.toMillis()); + nfstime4 btime = new nfstime4(); + btime.xdrDecode(xdr); + stat.setBTime(btime.toMillis()); break; case nfs4_prot.FATTR4_TIME_MODIFY_SET: settime4 setMtime = new settime4();