diff --git a/core/src/main/java/org/dcache/nfs/util/Misc.java b/core/src/main/java/org/dcache/nfs/util/Misc.java new file mode 100644 index 00000000..86ad5dcf --- /dev/null +++ b/core/src/main/java/org/dcache/nfs/util/Misc.java @@ -0,0 +1,69 @@ +/* + * Copyright (c) 2009 - 2023 Deutsches Elektronen-Synchroton, + * Member of the Helmholtz Association, (DESY), HAMBURG, GERMANY + * + * This library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Library General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this program (see the file COPYING.LIB for more + * details); if not, write to the Free Software Foundation, Inc., + * 675 Mass Ave, Cambridge, MA 02139, USA. + */ +package org.dcache.nfs.util; + +import java.io.IOException; +import java.io.InputStream; +import java.net.URL; +import java.security.CodeSource; +import java.security.ProtectionDomain; +import java.time.Instant; +import java.time.format.DateTimeParseException; +import java.util.Optional; +import java.util.jar.Attributes; +import java.util.jar.JarInputStream; +import java.util.jar.Manifest; + +public class Misc { + + private Misc() {} + + /** + * Get package build time. This method uses {@code Build-Time} attribute in the + * jar Manifest file. + * @return optional instant of package build time. + */ + public static Optional getBuildTime() { + + try { + + ProtectionDomain pd = Misc.class.getProtectionDomain(); + CodeSource cs = pd.getCodeSource(); + URL u = cs.getLocation(); + + InputStream is = u.openStream(); + JarInputStream jis = new JarInputStream(is); + Manifest m = jis.getManifest(); + + if (m != null) { + Attributes as = m.getMainAttributes(); + String buildTime = as.getValue("Build-Time"); + if (buildTime != null) { + return Optional.of(Instant.parse(buildTime)); + } + } + + } catch (IOException | DateTimeParseException e) { + // bad luck + } + + return Optional.empty(); + } +} diff --git a/core/src/main/java/org/dcache/nfs/v4/CompoundContext.java b/core/src/main/java/org/dcache/nfs/v4/CompoundContext.java index 5b328e00..0d708020 100644 --- a/core/src/main/java/org/dcache/nfs/v4/CompoundContext.java +++ b/core/src/main/java/org/dcache/nfs/v4/CompoundContext.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009 - 2020 Deutsches Elektronen-Synchroton, + * Copyright (c) 2009 - 2023 Deutsches Elektronen-Synchroton, * Member of the Helmholtz Association, (DESY), HAMBURG, GERMANY * * This library is free software; you can redistribute it and/or modify @@ -25,7 +25,12 @@ import com.sun.security.auth.UnixNumericUserPrincipal; import org.dcache.nfs.ChimeraNFSException; import org.dcache.nfs.ExportTable; +import org.dcache.nfs.v4.xdr.nfs_impl_id4; import org.dcache.nfs.v4.xdr.nfs_resop4; +import org.dcache.nfs.v4.xdr.server_owner4; +import org.dcache.nfs.v4.xdr.stateid4; +import org.dcache.nfs.v4.xdr.uint64_t; +import org.dcache.nfs.v4.xdr.verifier4; import org.dcache.oncrpc4j.rpc.RpcCall; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -39,10 +44,6 @@ import org.dcache.nfs.status.NoFileHandleException; import org.dcache.nfs.status.RestoreFhException; import org.dcache.nfs.v4.nlm.LockManager; -import org.dcache.nfs.v4.xdr.server_owner4; -import org.dcache.nfs.v4.xdr.stateid4; -import org.dcache.nfs.v4.xdr.uint64_t; -import org.dcache.nfs.v4.xdr.verifier4; import org.dcache.nfs.vfs.VirtualFileSystem; import org.dcache.oncrpc4j.rpc.net.InetSocketAddresses; import org.dcache.oncrpc4j.rpc.RpcAuthType; @@ -96,6 +97,8 @@ public boolean equals(Object obj) { private final int _exchangeIdFlags; private final verifier4 _rebootVerifier; + private final nfs_impl_id4 _implId; + /** * Create context of COUMPOUND request. * @@ -114,6 +117,7 @@ public CompoundContext(CompoundContextBuilder builder) { _principal = principalOf(_callInfo); _exchangeIdFlags = builder.getExchangeIdFlags(); _rebootVerifier = builder.getRebootVerifier(); + _implId = builder.getImplementationId(); } public RpcCall getRpcCall() { @@ -354,4 +358,11 @@ public InetSocketAddress getLocalSocketAddress() { public verifier4 getRebootVerifier() { return _rebootVerifier; } + + /** + * Return server Implementation ID. + */ + public nfs_impl_id4 getImplementationId() { + return _implId; + } } diff --git a/core/src/main/java/org/dcache/nfs/v4/CompoundContextBuilder.java b/core/src/main/java/org/dcache/nfs/v4/CompoundContextBuilder.java index 9f95ad5f..646c4819 100644 --- a/core/src/main/java/org/dcache/nfs/v4/CompoundContextBuilder.java +++ b/core/src/main/java/org/dcache/nfs/v4/CompoundContextBuilder.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009 - 2020 Deutsches Elektronen-Synchroton, + * Copyright (c) 2009 - 2023 Deutsches Elektronen-Synchroton, * Member of the Helmholtz Association, (DESY), HAMBURG, GERMANY * * This library is free software; you can redistribute it and/or modify @@ -19,10 +19,10 @@ */ package org.dcache.nfs.v4; -import org.dcache.nfs.ExportFile; import org.dcache.nfs.ExportTable; import org.dcache.nfs.v4.nlm.LockManager; import org.dcache.nfs.v4.xdr.nfs4_prot; +import org.dcache.nfs.v4.xdr.nfs_impl_id4; import org.dcache.nfs.vfs.VirtualFileSystem; import org.dcache.nfs.v4.xdr.verifier4; import org.dcache.oncrpc4j.rpc.RpcCall; @@ -41,6 +41,8 @@ public class CompoundContextBuilder { private int exchangeIdFlags = nfs4_prot.EXCHGID4_FLAG_USE_NON_PNFS; private verifier4 rebootVerifier; + private nfs_impl_id4 implId; + public CompoundContextBuilder withCall(RpcCall call) { this.call = call; return this; @@ -76,6 +78,11 @@ public CompoundContextBuilder withLockManager(LockManager lm) { return this; } + public CompoundContextBuilder withImplementationId(nfs_impl_id4 impId) { + this.implId = impId; + return this; + } + public LockManager getLm() { return lm; } @@ -104,6 +111,10 @@ public ExportTable getExportTable() { return exportTable; } + public nfs_impl_id4 getImplementationId() { + return implId; + } + public CompoundContext build() { requireNonNull(call); diff --git a/core/src/main/java/org/dcache/nfs/v4/NFSServerV41.java b/core/src/main/java/org/dcache/nfs/v4/NFSServerV41.java index 013ad022..049827ac 100644 --- a/core/src/main/java/org/dcache/nfs/v4/NFSServerV41.java +++ b/core/src/main/java/org/dcache/nfs/v4/NFSServerV41.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009 - 2020 Deutsches Elektronen-Synchroton, + * Copyright (c) 2009 - 2023 Deutsches Elektronen-Synchroton, * Member of the Helmholtz Association, (DESY), HAMBURG, GERMANY * * This library is free software; you can redistribute it and/or modify @@ -22,21 +22,18 @@ import org.dcache.nfs.ChimeraNFSException; import org.dcache.nfs.ExportTable; import org.dcache.nfs.ExportFile; -import org.dcache.nfs.v4.xdr.COMPOUND4args; -import org.dcache.nfs.v4.xdr.COMPOUND4res; -import org.dcache.nfs.v4.xdr.nfs4_prot_NFS4_PROGRAM_ServerStub; -import org.dcache.nfs.v4.xdr.nfs_argop4; -import org.dcache.nfs.v4.xdr.nfs_resop4; +import org.dcache.nfs.v4.xdr.*; import org.dcache.nfs.nfsstat; import org.dcache.oncrpc4j.rpc.RpcCall; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.slf4j.MDC; +import java.time.Instant; import java.util.ArrayList; import java.util.Collections; import java.util.List; -import org.dcache.nfs.v4.xdr.nfs_opnum4; + import org.dcache.nfs.vfs.PseudoFs; import org.dcache.nfs.vfs.VirtualFileSystem; import org.dcache.nfs.status.MinorVersMismatchException; @@ -48,7 +45,6 @@ import org.dcache.nfs.status.TooManyOpsException; import org.dcache.nfs.v4.nlm.LockManager; import org.dcache.nfs.v4.nlm.SimpleLm; -import org.dcache.nfs.v4.xdr.verifier4; public class NFSServerV41 extends nfs4_prot_NFS4_PROGRAM_ServerStub { @@ -60,6 +56,8 @@ public class NFSServerV41 extends nfs4_prot_NFS4_PROGRAM_ServerStub { private final NFSv41DeviceManager _deviceManager; private final NFSv4StateHandler _statHandler; private final LockManager _nlm; + private final nfs_impl_id4 _implementationId; + /** * Verifier to indicate client that server is rebooted. Current currentTimeMillis * is good enough, unless server reboots within a millisecond. @@ -73,6 +71,12 @@ private NFSServerV41(Builder builder) { _operationExecutor = builder.operationExecutor; _nlm = builder.nlm == null ? new SimpleLm() : builder.nlm; _statHandler = builder.stateHandler == null ? new NFSv4StateHandler() : builder.stateHandler; + + _implementationId = new nfs_impl_id4(); + _implementationId.nii_date = new nfstime4(builder.implementationDate.toEpochMilli()); + _implementationId.nii_domain = new utf8str_cis(builder.implementationDomain); + _implementationId.nii_name = new utf8str_cs(builder.implementationName); + } @Deprecated @@ -86,6 +90,11 @@ public NFSServerV41(OperationExecutor operationExecutor, _operationExecutor = operationExecutor; _nlm = new SimpleLm(); _statHandler = new NFSv4StateHandler(); + + _implementationId = new nfs_impl_id4(); + _implementationId.nii_date = new nfstime4(NFSv4Defaults.NFS4_IMPLEMENTATION_DATE); + _implementationId.nii_domain = new utf8str_cis(NFSv4Defaults.NFS4_IMPLEMENTATION_DOMAIN); + _implementationId.nii_name = new utf8str_cs(NFSv4Defaults.NFS4_IMPLEMENTATION_ID); } @Override @@ -136,6 +145,7 @@ public COMPOUND4res NFSPROC4_COMPOUND_4(RpcCall call$, COMPOUND4args arg1) { .withLockManager(_nlm) .withExportTable(_exportTable) .withRebootVerifier(_rebootVerifier) + .withImplementationId(_implementationId) .withCall(call$); if (_deviceManager != null) { @@ -291,6 +301,9 @@ public static class Builder { private ExportTable exportTable; private LockManager nlm; private NFSv4StateHandler stateHandler; + private String implementationName = NFSv4Defaults.NFS4_IMPLEMENTATION_ID; + private String implementationDomain = NFSv4Defaults.NFS4_IMPLEMENTATION_DOMAIN; + private Instant implementationDate = NFSv4Defaults.NFS4_IMPLEMENTATION_DATE; public Builder withDeviceManager(NFSv41DeviceManager deviceManager) { this.deviceManager = deviceManager; @@ -317,6 +330,19 @@ public Builder withExportTable(ExportTable exportTable) { return this; } + public Builder withImplementationName(String implementationName) { + this.implementationName = implementationName; + return this; + } + + public Builder withImplementationDomain(String implementationDomain) { + this.implementationDomain = implementationDomain; + return this; + } + public Builder withImplementationDate(Instant implementationDate) { + this.implementationDate = implementationDate; + return this; + } /** * @deprecated Use {@link #withExportTable} */ diff --git a/core/src/main/java/org/dcache/nfs/v4/NFSv4Defaults.java b/core/src/main/java/org/dcache/nfs/v4/NFSv4Defaults.java index 550215c4..56525436 100644 --- a/core/src/main/java/org/dcache/nfs/v4/NFSv4Defaults.java +++ b/core/src/main/java/org/dcache/nfs/v4/NFSv4Defaults.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009 - 2014 Deutsches Elektronen-Synchroton, + * Copyright (c) 2009 - 2023 Deutsches Elektronen-Synchroton, * Member of the Helmholtz Association, (DESY), HAMBURG, GERMANY * * This library is free software; you can redistribute it and/or modify @@ -19,6 +19,10 @@ */ package org.dcache.nfs.v4; +import org.dcache.nfs.util.Misc; + +import java.time.Instant; + public interface NFSv4Defaults { public static final int NFS4_LEASE_TIME = 90; @@ -54,7 +58,7 @@ public interface NFSv4Defaults { /** * NFSv4.1 implementation date */ - public final static long NFS4_IMPLEMENTATION_DATE = System.currentTimeMillis(); + public final static Instant NFS4_IMPLEMENTATION_DATE = Misc.getBuildTime().orElse(Instant.now()); /** * Maximal number of operations in a compound call @@ -65,4 +69,5 @@ public interface NFSv4Defaults { * Maximal number of session slots */ public final static int NFS4_MAX_SESSION_SLOTS = 16; + } diff --git a/core/src/main/java/org/dcache/nfs/v4/OperationEXCHANGE_ID.java b/core/src/main/java/org/dcache/nfs/v4/OperationEXCHANGE_ID.java index 9d05571c..eef45ec9 100644 --- a/core/src/main/java/org/dcache/nfs/v4/OperationEXCHANGE_ID.java +++ b/core/src/main/java/org/dcache/nfs/v4/OperationEXCHANGE_ID.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009 - 2017 Deutsches Elektronen-Synchroton, + * Copyright (c) 2009 - 2023 Deutsches Elektronen-Synchroton, * Member of the Helmholtz Association, (DESY), HAMBURG, GERMANY * * This library is free software; you can redistribute it and/or modify @@ -79,40 +79,6 @@ public class OperationEXCHANGE_ID extends AbstractNFSv4Operation { | nfs4_prot.EXCHGID4_FLAG_UPD_CONFIRMED_REC_A | nfs4_prot.EXCHGID4_FLAG_CONFIRMED_R); - /** - * compile time - */ - private static long COMPILE_TIME = 0; - - static { - /* - * get 'Build-Time' attribute from jar file manifest ( if available ) - */ - try { - - ProtectionDomain pd = OperationEXCHANGE_ID.class.getProtectionDomain(); - CodeSource cs = pd.getCodeSource(); - URL u = cs.getLocation(); - - InputStream is = u.openStream(); - JarInputStream jis = new JarInputStream(is); - Manifest m = jis.getManifest(); - - if (m != null) { - Attributes as = m.getMainAttributes(); - String buildTime = as.getValue("Build-Time"); - if( buildTime != null ) { - Instant dateTime = Instant.parse(buildTime); - COMPILE_TIME = dateTime.toEpochMilli(); - } - } - - }catch(IOException | DateTimeParseException e) { - _log.warn("Failed to get compile time: {}", e.getMessage()); - // bad luck - } - } - public OperationEXCHANGE_ID(nfs_argop4 args) { super(args, nfs_opnum4.OP_EXCHANGE_ID); } @@ -264,10 +230,7 @@ public void process(CompoundContext context, nfs_resop4 result) throws ChimeraNF res.eir_resok4.eir_server_scope = serverIdProvider.getScope(); res.eir_resok4.eir_server_impl_id = new nfs_impl_id4[1]; - res.eir_resok4.eir_server_impl_id[0] = new nfs_impl_id4(); - res.eir_resok4.eir_server_impl_id[0].nii_domain = new utf8str_cis(NFS4_IMPLEMENTATION_DOMAIN); - res.eir_resok4.eir_server_impl_id[0].nii_name = new utf8str_cs(NFS4_IMPLEMENTATION_ID); - res.eir_resok4.eir_server_impl_id[0].nii_date = new nfstime4(COMPILE_TIME); + res.eir_resok4.eir_server_impl_id[0] = context.getImplementationId(); res.eir_resok4.eir_state_protect = new state_protect4_r(); res.eir_resok4.eir_state_protect.spr_how = state_protect_how4.SP4_NONE; diff --git a/core/src/main/java/org/dcache/nfs/v4/xdr/nfstime4.java b/core/src/main/java/org/dcache/nfs/v4/xdr/nfstime4.java index 2e4f32e6..3ce043d7 100644 --- a/core/src/main/java/org/dcache/nfs/v4/xdr/nfstime4.java +++ b/core/src/main/java/org/dcache/nfs/v4/xdr/nfstime4.java @@ -56,6 +56,15 @@ public nfstime4(long millis) { nseconds = (int)((millis % 1000) * 1000000); } + /** + * Create a new nfstime4 from given {@link Instant}. + * @param instant + */ + public nfstime4(Instant instant) { + seconds = instant.getEpochSecond(); + nseconds = instant.getNano(); + } + public nfstime4(XdrDecodingStream xdr) throws OncRpcException, IOException { xdrDecode(xdr); diff --git a/core/src/main/java/org/dcache/nfs/v4/xdr/utf8str_cis.java b/core/src/main/java/org/dcache/nfs/v4/xdr/utf8str_cis.java index 70d033f5..6705cacd 100644 --- a/core/src/main/java/org/dcache/nfs/v4/xdr/utf8str_cis.java +++ b/core/src/main/java/org/dcache/nfs/v4/xdr/utf8str_cis.java @@ -54,5 +54,26 @@ public void xdrDecode(XdrDecodingStream xdr) value = new utf8string(xdr); } + @Override + public boolean equals(Object obj) { + if (obj == this) { + return true; + } + if (!(obj instanceof utf8str_cis)) { + return false; + } + + return this.value.equals(((utf8str_cis) obj).value); + } + + @Override + public int hashCode() { + return value.hashCode(); + } + + @Override + public String toString() { + return value.toString(); + } } // End of utf8str_cis.java diff --git a/core/src/test/java/org/dcache/nfs/v4/OperationEXCHANGE_IDTest.java b/core/src/test/java/org/dcache/nfs/v4/OperationEXCHANGE_IDTest.java index 91b99e52..387b90d4 100644 --- a/core/src/test/java/org/dcache/nfs/v4/OperationEXCHANGE_IDTest.java +++ b/core/src/test/java/org/dcache/nfs/v4/OperationEXCHANGE_IDTest.java @@ -20,14 +20,19 @@ package org.dcache.nfs.v4; import java.time.Duration; -import org.dcache.nfs.v4.xdr.nfs_argop4; -import org.dcache.nfs.v4.xdr.state_protect_how4; -import org.dcache.nfs.v4.xdr.nfs4_prot; -import org.dcache.nfs.v4.xdr.nfs_opnum4; -import org.dcache.nfs.v4.xdr.nfs_resop4; + import java.util.UUID; import java.util.concurrent.TimeUnit; import org.dcache.nfs.nfsstat; +import org.dcache.nfs.v4.xdr.nfs4_prot; +import org.dcache.nfs.v4.xdr.nfs_argop4; +import org.dcache.nfs.v4.xdr.nfs_impl_id4; +import org.dcache.nfs.v4.xdr.nfs_opnum4; +import org.dcache.nfs.v4.xdr.nfs_resop4; +import org.dcache.nfs.v4.xdr.nfstime4; +import org.dcache.nfs.v4.xdr.state_protect_how4; +import org.dcache.nfs.v4.xdr.utf8str_cis; +import org.dcache.nfs.v4.xdr.utf8str_cs; import org.junit.After; import org.junit.Before; import org.junit.Test; @@ -341,4 +346,38 @@ public void testPnfsMDS_DSFlags() throws Exception { nfs4_prot.EXCHGID4_FLAG_USE_PNFS_MDS | nfs4_prot.EXCHGID4_FLAG_USE_PNFS_DS, result.opexchange_id.eir_resok4.eir_flags.value); } + + + @Test + public void testCustomImplementationId() throws Exception { + CompoundContext context; + nfs_resop4 result; + + nfs_argop4 exchangeid_args = new CompoundBuilder() + .withExchangeId(domain, name, clientId, 0, state_protect_how4.SP4_NONE) + .build().argarray[0]; + + nfs_impl_id4 implId = new nfs_impl_id4(); + implId.nii_date = new nfstime4(NFSv4Defaults.NFS4_IMPLEMENTATION_DATE); + implId.nii_domain = new utf8str_cis("nfs.dev"); + implId.nii_name = new utf8str_cs("junit"); + + OperationEXCHANGE_ID EXCHANGE_ID = new OperationEXCHANGE_ID(exchangeid_args); + + result = nfs_resop4.resopFor(nfs_opnum4.OP_EXCHANGE_ID); + context = new CompoundContextBuilder() + .withStateHandler(stateHandler) + .withCall(generateRpcCall()) + .withImplementationId(implId) + .build(); + + AssertNFS.assertNFS(EXCHANGE_ID, context, result, nfsstat.NFS_OK); + assertEquals("Invalid implementation domain returned", + new utf8str_cis("nfs.dev"), + result.opexchange_id.eir_resok4.eir_server_impl_id[0].nii_domain); + + assertEquals("Invalid implementation name returned", + new utf8str_cs("junit"), + result.opexchange_id.eir_resok4.eir_server_impl_id[0].nii_name); + } }