diff --git a/core/src/main/java/org/dcache/nfs/util/Opaque.java b/core/src/main/java/org/dcache/nfs/util/Opaque.java index bec1ddba..41dc21f9 100644 --- a/core/src/main/java/org/dcache/nfs/util/Opaque.java +++ b/core/src/main/java/org/dcache/nfs/util/Opaque.java @@ -20,10 +20,13 @@ package org.dcache.nfs.util; import java.nio.ByteBuffer; +import java.nio.ByteOrder; import java.util.Arrays; import java.util.Base64; import java.util.Objects; +import org.dcache.oncrpc4j.util.Bytes; + /** * Describes something that can be used as a key for {@link java.util.HashMap} and that can be converted to a * {@code byte[]} and a Base64 string representation. @@ -85,6 +88,9 @@ static Opaque forBytes(ByteBuffer buf, int length) { * @see #toImmutableOpaque() */ static Opaque forMutableByteBuffer(ByteBuffer buf, int index, int length) { + if (buf.order() != ByteOrder.BIG_ENDIAN) { + buf = buf.duplicate(); + } return new OpaqueBufferImpl(buf, index, length); } @@ -174,7 +180,23 @@ default void putBytes(ByteBuffer buf) { @Override boolean equals(Object o); - class OpaqueImpl implements Opaque { + /** + * Returns the byte stored at the given position. + * + * @param byteOffset The byte offset + * @return The byte. + */ + byte byteAt(int byteOffset); + + /** + * Returns the {@code long} stored at the given position, using big-endian byte order. + * + * @param byteOffset The byte offset + * @return The long. + */ + long longAt(int byteOffset); + + public class OpaqueImpl implements Opaque { final byte[] _opaque; OpaqueImpl(byte[] opaque) { @@ -253,6 +275,16 @@ public int numBytes() { public Opaque toImmutableOpaque() { return Opaque.forBytes(_opaque); } + + @Override + public byte byteAt(int position) { + return _opaque[position]; + } + + @Override + public long longAt(int byteOffset) { + return Bytes.getLong(_opaque, byteOffset); + } } final class OpaqueImmutableImpl extends OpaqueImpl { @@ -368,5 +400,15 @@ public boolean equals(Object o) { public String toString() { return super.toString() + "[" + toBase64() + "]"; } + + @Override + public byte byteAt(int position) { + return buf.get(position); + } + + @Override + public long longAt(int byteOffset) { + return buf.getLong(byteOffset); + } } } diff --git a/core/src/main/java/org/dcache/nfs/v4/FileTracker.java b/core/src/main/java/org/dcache/nfs/v4/FileTracker.java index 1ff0b9a4..81d31558 100644 --- a/core/src/main/java/org/dcache/nfs/v4/FileTracker.java +++ b/core/src/main/java/org/dcache/nfs/v4/FileTracker.java @@ -315,9 +315,9 @@ public OpenRecord addOpen(NFS4Client client, StateOwner owner, Inode inode, int os.shareDenySeen |= 1 << ((shareDeny & nfs4_prot.OPEN4_SHARE_ACCESS_BOTH) - 1); } - os.stateid.seqid++; + os.stateid.bumpSeqid(); // we need to return copy to avoid modification by concurrent opens - var openStateid = new stateid4(os.stateid.other, os.stateid.seqid); + var openStateid = os.stateid.clone(); // yet another open from the same client. Let's check if we can delegate. if (canDelegateRead && (os.shareAccess @@ -342,10 +342,10 @@ public OpenRecord addOpen(NFS4Client client, StateOwner owner, Inode inode, int OpenState openState = new OpenState(client, owner, stateid, shareAccess, shareDeny); opens.add(openState); state.addDisposeListener(s -> removeOpen(inode, stateid)); - stateid.seqid++; + stateid.bumpSeqid(); // we need to return copy to avoid modification by concurrent opens - var openStateid = new stateid4(stateid.other, stateid.seqid); + var openStateid = stateid.clone(); // REVISIT: currently only read-delegations are supported if (canDelegateRead && (wantReadDelegation || adlHeuristic.shouldDelegate(client, inode))) { @@ -408,9 +408,9 @@ public stateid4 downgradeOpen(NFS4Client client, stateid4 stateid, Inode inode, os.shareAccess = shareAccess; os.shareDeny = shareDeny; - os.stateid.seqid++; + os.stateid.bumpSeqid(); // we need to return copy to avoid modification by concurrent opens - return new stateid4(os.stateid.other, os.stateid.seqid); + return os.stateid.clone(); } finally { lock.unlock(); } @@ -463,16 +463,21 @@ public void delegationReturn(NFS4Client client, stateid4 stateid, Inode inode) */ public int getShareAccess(NFS4Client client, Inode inode, stateid4 stateid) throws ChimeraNFSException { + Opaque stateIdOpaque = stateid.getOpaque(); + return getShareAccess(client, inode, stateIdOpaque); + } + public int getShareAccess(NFS4Client client, Inode inode, Opaque stateid) + throws ChimeraNFSException { Opaque fileId = inode.getFileIdKey(); Lock lock = filesLock.get(fileId); lock.lock(); try { - switch (stateid.other[11]) { + switch (stateid4.getType(stateid)) { case Stateids.LOCK_STATE_ID: NFS4State lockState = client.state(stateid); - stateid = lockState.getOpenState().stateid(); + stateid = lockState.getOpenState().stateid().getOpaque(); // fall through case Stateids.OPEN_STATE_ID: { final List opens = files.get(fileId); @@ -481,10 +486,10 @@ public int getShareAccess(NFS4Client client, Inode inode, stateid4 stateid) throw new BadStateidException("no matching open"); } - final stateid4 openStateid = stateid; + final Opaque openStateid = stateid; return opens.stream() .filter(s -> client.getId() == s.client.getId()) - .filter(s -> s.stateid.equals(openStateid)) + .filter(s -> s.stateid.getOpaque().equals(openStateid)) .mapToInt(OpenState::getShareAccess) .findAny() .orElseThrow(BadStateidException::new); @@ -496,11 +501,11 @@ public int getShareAccess(NFS4Client client, Inode inode, stateid4 stateid) throw new BadStateidException("no delegation found"); } - stateid4 delegationStateid = stateid; + Opaque delegationStateid = stateid; var delegation = fileDelegations.stream() .filter(d -> d.client().getId().equals(client.getId())) - .filter(d -> d.delegationStateid().stateid().equals(delegationStateid)) + .filter(d -> d.delegationStateid().stateid().getOpaque().equals(delegationStateid)) .findAny() .orElseThrow(BadStateidException::new); diff --git a/core/src/main/java/org/dcache/nfs/v4/NFS4Client.java b/core/src/main/java/org/dcache/nfs/v4/NFS4Client.java index 4afbd04b..8eff70f3 100644 --- a/core/src/main/java/org/dcache/nfs/v4/NFS4Client.java +++ b/core/src/main/java/org/dcache/nfs/v4/NFS4Client.java @@ -117,7 +117,7 @@ public class NFS4Client { */ private int _sessionSequence = 1; - private final Map _clientStates = new ConcurrentHashMap<>(); + private final Map _clientStates = new ConcurrentHashMap<>(); /** * sessions associated with the client @@ -334,18 +334,20 @@ private NFS4State createState(StateOwner stateOwner, byte type, NFS4State openSt NFS4State state = new NFS4State(openState, stateOwner, _stateHandler.createStateId(this, type, _stateIdCounter .incrementAndGet())); + stateid4 stateId = state.stateid(); + Opaque opaque = stateId.getOpaque(); if (openState != null) { openState.addDisposeListener(s -> { // remove and dispose derived states. - NFS4State nfsState = _clientStates.get(state.stateid()); + NFS4State nfsState = _clientStates.get(opaque); if (nfsState != null) { _log.debug("removing derived state {}", nfsState); nfsState.tryDispose(); } - _clientStates.remove(state.stateid()); + _clientStates.remove(opaque); }); } - _clientStates.put(state.stateid(), state); + _clientStates.put(opaque, state); return state; } @@ -410,29 +412,34 @@ public NFS4State createServerSideCopyState(StateOwner stateOwner, NFS4State open } public void releaseState(stateid4 stateid) throws ChimeraNFSException { + Opaque opaque = stateid.getOpaque(); - NFS4State state = _clientStates.get(stateid); + NFS4State state = _clientStates.get(opaque); if (state == null) { throw new BadStateidException("State not known to the client: " + stateid); } state.disposeIgnoreFailures(); - _clientStates.remove(stateid); + _clientStates.remove(opaque); } public void tryReleaseState(stateid4 stateid) throws ChimeraNFSException { - NFS4State state = _clientStates.get(stateid); + NFS4State state = _clientStates.get(stateid.getOpaque()); if (state == null) { throw new BadStateidException("State not known to the client: " + stateid); } state.tryDispose(); - _clientStates.remove(stateid); + _clientStates.remove(stateid.getOpaque()); } public NFS4State state(stateid4 stateid) throws ChimeraNFSException { - NFS4State state = _clientStates.get(stateid); + return state(stateid.getOpaque()); + } + + public NFS4State state(Opaque stateidOpaque) throws ChimeraNFSException { + NFS4State state = _clientStates.get(stateidOpaque); if (state == null) { - throw new BadStateidException("State not known to the client: " + stateid); + throw new BadStateidException("State not known to the client: " + stateidOpaque); } return state; } @@ -534,7 +541,7 @@ public boolean hasState() { * @param state to attach */ public void attachState(NFS4State state) { - _clientStates.put(state.stateid(), state); + _clientStates.put(state.stateid().getOpaque(), state); } /** @@ -543,7 +550,7 @@ public void attachState(NFS4State state) { * @param state to detach */ public void detachState(NFS4State state) { - _clientStates.remove(state.stateid()); + _clientStates.remove(state.stateid().getOpaque()); } @GuardedBy("this") diff --git a/core/src/main/java/org/dcache/nfs/v4/NFS4State.java b/core/src/main/java/org/dcache/nfs/v4/NFS4State.java index fa506e13..4a7b0c4d 100644 --- a/core/src/main/java/org/dcache/nfs/v4/NFS4State.java +++ b/core/src/main/java/org/dcache/nfs/v4/NFS4State.java @@ -68,7 +68,7 @@ public NFS4State(NFS4State openState, StateOwner owner, stateid4 stateid) { } public void bumpSeqid() { - ++_stateid.seqid; + _stateid.bumpSeqid(); } public stateid4 stateid() { 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 e1da08e6..85b6588d 100644 --- a/core/src/main/java/org/dcache/nfs/v4/NFSServerV41.java +++ b/core/src/main/java/org/dcache/nfs/v4/NFSServerV41.java @@ -134,7 +134,7 @@ public COMPOUND4res NFSPROC4_COMPOUND_4(RpcCall call$, COMPOUND4args arg1) { } res.resarray = new ArrayList<>(arg1.argarray.length); - VirtualFileSystem fs = new PseudoFs(_fs, call$, _exportTable); + VirtualFileSystem fs = new PseudoFs(_fs, call$, _exportTable, _statHandler); CompoundContextBuilder builder = new CompoundContextBuilder() .withMinorversion(arg1.minorversion.value) diff --git a/core/src/main/java/org/dcache/nfs/v4/NFSv4StateHandler.java b/core/src/main/java/org/dcache/nfs/v4/NFSv4StateHandler.java index b7d64a4d..afc62a6a 100644 --- a/core/src/main/java/org/dcache/nfs/v4/NFSv4StateHandler.java +++ b/core/src/main/java/org/dcache/nfs/v4/NFSv4StateHandler.java @@ -237,13 +237,22 @@ public NFS4Client getClient(clientid4 clientid) throws StaleClientidException { } } + public NFS4Client getClientIfExists(long clientId) { + _readLock.lock(); + try { + return _clientsByServerId.get(new clientid4(clientId)); + } finally { + _readLock.unlock(); + } + } + public NFS4Client getClientIdByStateId(stateid4 stateId) throws ChimeraNFSException { _readLock.lock(); try { checkState(_running, "NFS state handler not running"); - clientid4 clientId = new clientid4(Bytes.getLong(stateId.other, 0)); + clientid4 clientId = new clientid4(stateId.getClientId()); NFS4Client client = _clientsByServerId.get(clientId); if (client == null) { throw new BadStateidException("no client for stateid: " + stateId); @@ -439,7 +448,7 @@ public int getInstanceId() { * @return state hander id. */ public static int getInstanceId(stateid4 stateid) { - long clientid = Bytes.getLong(stateid.other, 0); + long clientid = stateid.getClientId(); return (int) (clientid >> 16) & 0xFFFF; } diff --git a/core/src/main/java/org/dcache/nfs/v4/OperationCLOSE.java b/core/src/main/java/org/dcache/nfs/v4/OperationCLOSE.java index ff4e1850..82504ed8 100644 --- a/core/src/main/java/org/dcache/nfs/v4/OperationCLOSE.java +++ b/core/src/main/java/org/dcache/nfs/v4/OperationCLOSE.java @@ -29,6 +29,7 @@ import org.dcache.nfs.v4.xdr.nfs_resop4; import org.dcache.nfs.v4.xdr.stateid4; import org.dcache.nfs.vfs.Inode; +import org.dcache.nfs.vfs.VirtualFileSystem; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -59,6 +60,11 @@ public void process(CompoundContext context, nfs_resop4 result) NFS4State nfsState = client.state(stateid); Stateids.checkStateId(nfsState.stateid(), stateid); + VirtualFileSystem fs = context.getFs(); + if (fs != null) { + fs.close(stateid.getOpaque()); + } + if (context.getMinorversion() == 0) { nfsState.getStateOwner().acceptAsNextSequence(_args.opclose.seqid); client.updateLeaseTime(); @@ -68,6 +74,5 @@ public void process(CompoundContext context, nfs_resop4 result) res.open_stateid = Stateids.invalidStateId(); res.status = nfsstat.NFS_OK; - } } diff --git a/core/src/main/java/org/dcache/nfs/v4/OperationCOMMIT.java b/core/src/main/java/org/dcache/nfs/v4/OperationCOMMIT.java index d534ab26..96b45d12 100644 --- a/core/src/main/java/org/dcache/nfs/v4/OperationCOMMIT.java +++ b/core/src/main/java/org/dcache/nfs/v4/OperationCOMMIT.java @@ -28,6 +28,7 @@ import org.dcache.nfs.v4.xdr.nfs_argop4; import org.dcache.nfs.v4.xdr.nfs_opnum4; import org.dcache.nfs.v4.xdr.nfs_resop4; +import org.dcache.nfs.v4.xdr.stateid4; import org.dcache.nfs.vfs.Inode; public class OperationCOMMIT extends AbstractNFSv4Operation { @@ -42,8 +43,11 @@ public void process(CompoundContext context, nfs_resop4 result) throws ChimeraNF final COMMIT4res res = result.opcommit; Inode inode = context.currentInode(); + stateid4 stateid = Stateids.getCurrentStateidIfNeeded(context, _args.opwrite.stateid); + _args.opcommit.offset.checkOverflow(_args.opcommit.count.value, "offset + length overflow"); - context.getFs().commit(inode, _args.opcommit.offset.value, _args.opcommit.count.value); + context.getFs().commit(stateid.getOpaque(), inode, _args.opcommit.offset.value, + _args.opcommit.count.value); res.resok4 = new COMMIT4resok(); res.resok4.writeverf = context.getRebootVerifier(); diff --git a/core/src/main/java/org/dcache/nfs/v4/OperationLOCK.java b/core/src/main/java/org/dcache/nfs/v4/OperationLOCK.java index d3b9575b..dbff6f59 100644 --- a/core/src/main/java/org/dcache/nfs/v4/OperationLOCK.java +++ b/core/src/main/java/org/dcache/nfs/v4/OperationLOCK.java @@ -24,8 +24,10 @@ import org.dcache.nfs.status.InvalException; import org.dcache.nfs.status.OpenModeException; import org.dcache.nfs.status.ServerFaultException; +import org.dcache.nfs.util.Opaque; import org.dcache.nfs.v4.nlm.LockDeniedException; import org.dcache.nfs.v4.nlm.LockException; +import org.dcache.nfs.v4.nlm.LockManager; import org.dcache.nfs.v4.nlm.NlmLock; import org.dcache.nfs.v4.xdr.LOCK4denied; import org.dcache.nfs.v4.xdr.LOCK4resok; @@ -118,12 +120,12 @@ public void process(CompoundContext context, nfs_resop4 result) throws ChimeraNF NlmLock lock = new NlmLock(lockOwner, _args.oplock.locktype, _args.oplock.offset.value, _args.oplock.length.value); - context.getLm().lock(inode.getLockKey(), lock); + Opaque lockKey = inode.getLockKey(); + LockManager lm = context.getLm(); + lm.lock(lockKey, lock); // ensure, that on close locks will be released - lock_state.addDisposeListener(s -> { - context.getLm().unlockIfExists(inode.getLockKey(), lock); - }); + lock_state.addDisposeListener(s -> lm.unlockIfExists(lockKey, lock)); // FIXME: we might run into race condition, thus updating sedid must be fenced! lock_state.bumpSeqid(); diff --git a/core/src/main/java/org/dcache/nfs/v4/OperationOPEN.java b/core/src/main/java/org/dcache/nfs/v4/OperationOPEN.java index 790f3b71..d250df4c 100644 --- a/core/src/main/java/org/dcache/nfs/v4/OperationOPEN.java +++ b/core/src/main/java/org/dcache/nfs/v4/OperationOPEN.java @@ -33,6 +33,7 @@ import org.dcache.nfs.status.NotDirException; import org.dcache.nfs.status.SymlinkException; import org.dcache.nfs.status.WrongTypeException; +import org.dcache.nfs.v4.FileTracker.OpenRecord; import org.dcache.nfs.v4.xdr.OPEN4res; import org.dcache.nfs.v4.xdr.OPEN4resok; import org.dcache.nfs.v4.xdr.aceflag4; @@ -61,6 +62,8 @@ import org.dcache.nfs.v4.xdr.why_no_delegation4; import org.dcache.nfs.vfs.Inode; import org.dcache.nfs.vfs.Stat; +import org.dcache.nfs.vfs.VirtualFileSystem; +import org.dcache.nfs.vfs.VirtualFileSystem.OpenMode; import org.dcache.oncrpc4j.rpc.OncRpcException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -101,6 +104,9 @@ public void process(CompoundContext context, nfs_resop4 result) throws ChimeraNF res.resok4.cinfo = new change_info4(); res.resok4.cinfo.atomic = true; + Inode inode; + VirtualFileSystem.OpenMode openMode; + switch (_args.opopen.claim.claim) { case open_claim_type4.CLAIM_NULL: @@ -118,7 +124,6 @@ public void process(CompoundContext context, nfs_resop4 result) throws ChimeraNF String name = NameFilter.convertName(_args.opopen.claim.file.value); _log.debug("regular open for : {}", name); - Inode inode; if (_args.opopen.openhow.opentype == opentype4.OPEN4_CREATE) { boolean exclusive = (_args.opopen.openhow.how.mode == createmode4.EXCLUSIVE4) @@ -205,13 +210,13 @@ public void process(CompoundContext context, nfs_resop4 result) throws ChimeraNF } } - + openMode = OpenMode.READ_WRITE; } else { // no changes from us, old stat info is still good enough res.resok4.cinfo.after = new changeid4(stat.getGeneration()); inode = context.getFs().lookup(context.currentInode(), name); - checkCanAccess(context, inode, _args.opopen.share_access); + openMode = checkCanAccess(context, inode, _args.opopen.share_access); } context.currentInode(inode); @@ -238,7 +243,7 @@ public void process(CompoundContext context, nfs_resop4 result) throws ChimeraNF res.resok4.cinfo.after = new changeid4(0); inode = context.currentInode(); - checkCanAccess(context, inode, _args.opopen.share_access); + openMode = checkCanAccess(context, inode, _args.opopen.share_access); break; case open_claim_type4.CLAIM_DELEGATE_CUR: case open_claim_type4.CLAIM_DELEGATE_PREV: @@ -268,15 +273,19 @@ public void process(CompoundContext context, nfs_resop4 result) throws ChimeraNF * * THis is a perfectly a valid situation as at the end file is created and only one writer is allowed. */ - var openRecord = context + OpenRecord openRecord = context .getStateHandler() .getFileTracker() .addOpen(client, owner, context.currentInode(), _args.opopen.share_access.value, _args.opopen.share_deny.value); - context.currentStateid(openRecord.openStateId()); - res.resok4.stateid = openRecord.openStateId(); + stateid4 openStateId = openRecord.openStateId(); + context.currentStateid(openStateId); + + context.getFs().open(openStateId.getOpaque(), inode, openMode); + + res.resok4.stateid = openStateId; if (openRecord.hasDelegation()) { res.resok4.delegation.delegation_type = open_delegation_type4.OPEN_DELEGATE_READ; res.resok4.delegation.read = new open_read_delegation4(); @@ -294,19 +303,24 @@ public void process(CompoundContext context, nfs_resop4 result) throws ChimeraNF } - private void checkCanAccess(CompoundContext context, Inode inode, uint32_t share_access) throws IOException { + private VirtualFileSystem.OpenMode checkCanAccess(CompoundContext context, Inode inode, uint32_t share_access) + throws IOException { int accessMode; + VirtualFileSystem.OpenMode openMode; switch (share_access.value & ~nfs4_prot.OPEN4_SHARE_ACCESS_WANT_DELEG_MASK) { case nfs4_prot.OPEN4_SHARE_ACCESS_READ: accessMode = nfs4_prot.ACCESS4_READ; + openMode = VirtualFileSystem.OpenMode.READ_ONLY; break; case nfs4_prot.OPEN4_SHARE_ACCESS_WRITE: accessMode = nfs4_prot.ACCESS4_MODIFY; + openMode = VirtualFileSystem.OpenMode.WRITE_ONLY; break; case nfs4_prot.OPEN4_SHARE_ACCESS_BOTH: accessMode = nfs4_prot.ACCESS4_READ | nfs4_prot.ACCESS4_MODIFY; + openMode = VirtualFileSystem.OpenMode.READ_WRITE; break; default: throw new InvalException("Invalid share_access mode: " + share_access.value); @@ -328,5 +342,7 @@ private void checkCanAccess(CompoundContext context, Inode inode, uint32_t share default: throw context.getMinorversion() == 0 ? new SymlinkException() : new WrongTypeException(); } + + return openMode; } } diff --git a/core/src/main/java/org/dcache/nfs/v4/OperationREAD.java b/core/src/main/java/org/dcache/nfs/v4/OperationREAD.java index 649fe457..748b8c9a 100644 --- a/core/src/main/java/org/dcache/nfs/v4/OperationREAD.java +++ b/core/src/main/java/org/dcache/nfs/v4/OperationREAD.java @@ -73,7 +73,7 @@ public void process(CompoundContext context, nfs_resop4 result) throws IOExcepti ByteBuffer buf = ByteBuffer.allocate(count); res.resok4 = new READ4resok(); - int bytesRead = context.getFs().read(inode, buf, offset, res.resok4::setEOF); + int bytesRead = context.getFs().read(stateid.getOpaque(), inode, buf, offset, res.resok4::setEOF); if (bytesRead < 0) { buf.clear(); diff --git a/core/src/main/java/org/dcache/nfs/v4/OperationTEST_STATEID.java b/core/src/main/java/org/dcache/nfs/v4/OperationTEST_STATEID.java index 09feca96..44f74da2 100644 --- a/core/src/main/java/org/dcache/nfs/v4/OperationTEST_STATEID.java +++ b/core/src/main/java/org/dcache/nfs/v4/OperationTEST_STATEID.java @@ -53,7 +53,7 @@ public void process(CompoundContext context, nfs_resop4 result) throws ChimeraNF stateid4 statid = _args.optest_stateid.ts_stateids[i]; try { NFS4State state = client.state(statid); - if (state.stateid().seqid < statid.seqid) { + if (state.stateid().getSeqId() < statid.getSeqId()) { res.tsr_resok4.tsr_status_codes[i] = nfsstat.NFSERR_OLD_STATEID; } else { res.tsr_resok4.tsr_status_codes[i] = nfsstat.NFS_OK; diff --git a/core/src/main/java/org/dcache/nfs/v4/OperationWRITE.java b/core/src/main/java/org/dcache/nfs/v4/OperationWRITE.java index 27690b02..d1b8237e 100644 --- a/core/src/main/java/org/dcache/nfs/v4/OperationWRITE.java +++ b/core/src/main/java/org/dcache/nfs/v4/OperationWRITE.java @@ -87,7 +87,8 @@ public void process(CompoundContext context, nfs_resop4 result) throws ChimeraNF } long offset = _args.opwrite.offset.value; - VirtualFileSystem.WriteResult writeResult = context.getFs().write(context.currentInode(), + VirtualFileSystem.WriteResult writeResult = context.getFs().write( + stateid.getOpaque(), context.currentInode(), _args.opwrite.data, offset, VirtualFileSystem.StabilityLevel.fromStableHow(_args.opwrite.stable)); if (writeResult.getBytesWritten() < 0) { diff --git a/core/src/main/java/org/dcache/nfs/v4/Stateids.java b/core/src/main/java/org/dcache/nfs/v4/Stateids.java index 01349320..51e8fa07 100644 --- a/core/src/main/java/org/dcache/nfs/v4/Stateids.java +++ b/core/src/main/java/org/dcache/nfs/v4/Stateids.java @@ -67,7 +67,7 @@ private Stateids() { (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff}, nfs4_prot.NFS4_UINT32_MAX); public static stateid4 uptodateOf(stateid4 stateid) { - return new stateid4(stateid.other, 0); + return new stateid4(stateid.getOpaque().toBytes(), 0); } public static stateid4 currentStateId() { @@ -91,16 +91,17 @@ public static boolean isStateLess(stateid4 stateid) { } public static void checkStateId(stateid4 expected, stateid4 stateid) throws ChimeraNFSException { - if (stateid.seqid == 0) { + int seq = stateid.getSeqId(); + if (seq == 0) { // so called 'most up-to-date seqid', see https://tools.ietf.org/html/rfc5661#section-8.2.2 return; } - if (expected.seqid > stateid.seqid) { - throw new OldStateidException(); - } + int expectedSeq = expected.getSeqId(); - if (expected.seqid < stateid.seqid) { + if (expectedSeq > seq) { + throw new OldStateidException(); + } else if (expectedSeq != seq) { throw new BadStateidException(); } } @@ -114,37 +115,37 @@ public static stateid4 getCurrentStateidIfNeeded(CompoundContext context, statei } public static void checkOpenStateid(stateid4 stateid) throws BadStateidException { - if (stateid.other[11] != OPEN_STATE_ID) { + if (stateid.getType() != OPEN_STATE_ID) { throw new BadStateidException("Not an open stateid"); } } public static void checkLockStateid(stateid4 stateid) throws BadStateidException { - if (stateid.other[11] != LOCK_STATE_ID) { + if (stateid.getType() != LOCK_STATE_ID) { throw new BadStateidException("Not a lock stateid"); } } public static void checkDelegationStateid(stateid4 stateid) throws BadStateidException { - if (stateid.other[11] != DELEGATION_STATE_ID) { + if (stateid.getType() != DELEGATION_STATE_ID) { throw new BadStateidException("Not a delegation stateid"); } } public static void checkDirDelegationStateid(stateid4 stateid) throws BadStateidException { - if (stateid.other[11] != DIR_DELEGATION_STATE_ID) { + if (stateid.getType() != DIR_DELEGATION_STATE_ID) { throw new BadStateidException("Not a directory delegation stateid"); } } public static void checkServerSiderCopyStateid(stateid4 stateid) throws BadStateidException { - if (stateid.other[11] != SSC_STATE_ID) { + if (stateid.getType() != SSC_STATE_ID) { throw new BadStateidException("Not a server-side copy stateid"); } } public static void checkLayoutStateid(stateid4 stateid) throws BadStateidException { - if (stateid.other[11] != LAYOUT_STATE_ID) { + if (stateid.getType() != LAYOUT_STATE_ID) { throw new BadStateidException("Not a layout stateid"); } } diff --git a/core/src/main/java/org/dcache/nfs/v4/xdr/stateid4.java b/core/src/main/java/org/dcache/nfs/v4/xdr/stateid4.java index 701775fe..90422848 100644 --- a/core/src/main/java/org/dcache/nfs/v4/xdr/stateid4.java +++ b/core/src/main/java/org/dcache/nfs/v4/xdr/stateid4.java @@ -20,46 +20,78 @@ package org.dcache.nfs.v4.xdr; import java.io.IOException; +import java.io.ObjectInputStream.GetField; +import java.io.ObjectOutputStream.PutField; import java.io.Serializable; -import java.util.Arrays; +import org.dcache.nfs.util.Opaque; import org.dcache.oncrpc4j.rpc.OncRpcException; import org.dcache.oncrpc4j.xdr.XdrAble; import org.dcache.oncrpc4j.xdr.XdrDecodingStream; import org.dcache.oncrpc4j.xdr.XdrEncodingStream; -import com.google.common.io.BaseEncoding; - -public class stateid4 implements XdrAble, Serializable { +public class stateid4 implements XdrAble, Serializable, Cloneable { static final long serialVersionUID = -6677150504723505919L; - public int seqid; - public byte[] other; + private int seqid; + @SuppressWarnings("unused") + private byte[] other; // only declared for Java Serialization + private transient Opaque opaque; - public stateid4() { + public stateid4(XdrDecodingStream xdr) throws OncRpcException, IOException { + this.seqid = xdr.xdrDecodeInt(); + this.opaque = Opaque.forBytes(xdr.xdrDecodeOpaque(12)); } - public stateid4(byte[] other, int seq) { - this.other = other; - seqid = seq; + public stateid4(byte[] bytes, int seqid) { + this(seqid, Opaque.forBytes(bytes)); } - public stateid4(XdrDecodingStream xdr) - throws OncRpcException, IOException { - xdrDecode(xdr); + private stateid4(int seqid, Opaque other) { + this.seqid = seqid; + this.opaque = other.toImmutableOpaque(); + } + + public Opaque getOpaque() { + return opaque; + } + + public int getSeqId() { + return seqid; + } + + public long getClientId() { + return opaque.longAt(0); + } + + public static long getClientId(Opaque stateIdOther) { + return stateIdOther.longAt(0); + } + + public int getType() { + return opaque.byteAt(11); + } + + public static int getType(Opaque stateIdOther) { + return stateIdOther.byteAt(11); + } + + @Override + public stateid4 clone() { + return new stateid4(seqid, opaque); } public void xdrEncode(XdrEncodingStream xdr) throws OncRpcException, IOException { xdr.xdrEncodeInt(seqid); - xdr.xdrEncodeOpaque(other, 12); + xdr.xdrEncodeOpaque(opaque.toBytes(), 12); } public void xdrDecode(XdrDecodingStream xdr) throws OncRpcException, IOException { seqid = xdr.xdrDecodeInt(); - other = xdr.xdrDecodeOpaque(12); + opaque = Opaque.forBytes(xdr.xdrDecodeOpaque(12)); } @Override @@ -72,7 +104,7 @@ public boolean equals(Object obj) { final stateid4 other_id = (stateid4) obj; - return Arrays.equals(this.other, other_id.other); + return this.opaque.equals(other_id.opaque); } /** @@ -87,12 +119,12 @@ public boolean equalsWithSeq(stateid4 otherState) { return true; } - return otherState.seqid == this.seqid && Arrays.equals(this.other, otherState.other); + return otherState.seqid == this.seqid && this.opaque.equals(otherState.opaque); } @Override public int hashCode() { - return Arrays.hashCode(other); + return this.opaque.hashCode(); } @Override @@ -100,10 +132,28 @@ public String toString() { StringBuilder sb = new StringBuilder(); sb.append("["); - sb.append(BaseEncoding.base16().lowerCase().encode(other)); + sb.append(opaque); sb.append(", seq: ").append(seqid).append("]"); return sb.toString(); } + public void bumpSeqid() { + ++seqid; + } + + private void writeObject(java.io.ObjectOutputStream out) + throws IOException { + PutField pf = out.putFields(); + pf.put("seqid", this.seqid); + pf.put("other", this.opaque.toBytes()); + out.writeFields(); + } + + private void readObject(java.io.ObjectInputStream in) + throws IOException, ClassNotFoundException { + GetField gf = in.readFields(); + this.seqid = gf.get("seqid", 0); + this.opaque = Opaque.forBytes((byte[]) gf.get("other", new byte[0])); + } } // End of stateid4.java diff --git a/core/src/main/java/org/dcache/nfs/vfs/ForwardingFileSystem.java b/core/src/main/java/org/dcache/nfs/vfs/ForwardingFileSystem.java index c22e1108..00c1767b 100644 --- a/core/src/main/java/org/dcache/nfs/vfs/ForwardingFileSystem.java +++ b/core/src/main/java/org/dcache/nfs/vfs/ForwardingFileSystem.java @@ -26,6 +26,7 @@ import javax.security.auth.Subject; +import org.dcache.nfs.util.Opaque; import org.dcache.nfs.v4.NfsIdMapping; import org.dcache.nfs.v4.xdr.nfsace4; import org.dcache.nfs.vfs.Stat.StatAttribute; @@ -107,8 +108,8 @@ public int read(Inode inode, ByteBuffer data, long offset) throws IOException { } @Override - public int read(Inode inode, ByteBuffer data, long offset, Runnable eofReached) throws IOException { - return delegate().read(inode, data, offset, eofReached); + public int read(Opaque stateId, Inode inode, ByteBuffer data, long offset, Runnable eofReached) throws IOException { + return delegate().read(stateId, inode, data, offset, eofReached); } @Override @@ -138,11 +139,22 @@ public WriteResult write(Inode inode, ByteBuffer data, long offset, StabilityLev return delegate().write(inode, data, offset, stabilityLevel); } + @Override + public WriteResult write(Opaque stateId, Inode inode, ByteBuffer data, long offset, StabilityLevel stabilityLevel) + throws IOException { + return delegate().write(stateId, inode, data, offset, stabilityLevel); + } + @Override public void commit(Inode inode, long offset, int count) throws IOException { delegate().commit(inode, offset, count); } + @Override + public void commit(Opaque stateId, Inode inode, long offset, int count) throws IOException { + delegate().commit(stateId, inode, offset, count); + } + @Override public Stat getattr(Inode inode) throws IOException { return delegate().getattr(inode); @@ -222,4 +234,14 @@ public void removeXattr(Inode inode, String attr) throws IOException { public CompletableFuture copyFileRange(Inode src, long srcPos, Inode dst, long dstPos, long len) { return delegate().copyFileRange(src, srcPos, dst, dstPos, len); } + + @Override + public void open(Opaque stateId, Inode inode, OpenMode openMode) { + delegate().open(stateId, inode, openMode); + } + + @Override + public void close(Opaque stateId) { + delegate().close(stateId); + } } diff --git a/core/src/main/java/org/dcache/nfs/vfs/PseudoFs.java b/core/src/main/java/org/dcache/nfs/vfs/PseudoFs.java index e5e26478..697e3f7f 100644 --- a/core/src/main/java/org/dcache/nfs/vfs/PseudoFs.java +++ b/core/src/main/java/org/dcache/nfs/vfs/PseudoFs.java @@ -71,10 +71,15 @@ import org.dcache.nfs.status.NoEntException; import org.dcache.nfs.status.PermException; import org.dcache.nfs.status.RoFsException; +import org.dcache.nfs.util.Opaque; import org.dcache.nfs.util.SubjectHolder; +import org.dcache.nfs.v4.NFS4Client; +import org.dcache.nfs.v4.NFSv4StateHandler; import org.dcache.nfs.v4.acl.Acls; import org.dcache.nfs.v4.xdr.acemask4; +import org.dcache.nfs.v4.xdr.nfs4_prot; import org.dcache.nfs.v4.xdr.nfsace4; +import org.dcache.nfs.v4.xdr.stateid4; import org.dcache.nfs.vfs.AclCheckable.Access; import org.dcache.nfs.vfs.Stat.StatAttribute; import org.dcache.oncrpc4j.rpc.RpcAuth; @@ -105,6 +110,7 @@ public class PseudoFs extends ForwardingFileSystem { private final VirtualFileSystem _inner; private final ExportTable _exportTable; private final RpcAuth _auth; + private final NFSv4StateHandler _stateHandler; private final static int ACCESS4_MASK = ACCESS4_DELETE | ACCESS4_EXECUTE | ACCESS4_EXTEND @@ -112,11 +118,16 @@ public class PseudoFs extends ForwardingFileSystem { | ACCESS4_XAREAD | ACCESS4_XAWRITE | ACCESS4_XALIST; public PseudoFs(VirtualFileSystem inner, RpcCall call, ExportTable exportTable) { + this(inner, call, exportTable, null); + } + + public PseudoFs(VirtualFileSystem inner, RpcCall call, ExportTable exportTable, NFSv4StateHandler stateHandler) { _inner = inner; _subject = call.getCredential().getSubject(); _auth = call.getCredential(); _inetAddress = call.getTransport().getRemoteSocketAddress(); _exportTable = exportTable; + _stateHandler = stateHandler; } @Override @@ -329,10 +340,29 @@ public int read(Inode inode, ByteBuffer data, long offset) throws IOException { return _inner.read(innerInode(inode), data, offset); } + private void checkAccessReadWriteData(Opaque stateId, Inode inode, boolean write) throws IOException { + if (_stateHandler != null) { + NFS4Client client = _stateHandler.getClientIfExists(stateid4.getClientId(stateId)); + if (client != null) { + int shareAccess; + try { + shareAccess = _stateHandler.getFileTracker().getShareAccess(client, inode, stateId); + if ((shareAccess & (write ? nfs4_prot.OPEN4_SHARE_ACCESS_WRITE + : nfs4_prot.OPEN4_SHARE_ACCESS_READ)) != 0) { + return; // permit + } + } catch (ChimeraNFSException e) { + // ignore; check below and throw proper exception + } + } + } + checkAccess(inode, write ? ACE4_WRITE_DATA : ACE4_READ_DATA); + } + @Override - public int read(Inode inode, ByteBuffer data, long offset, Runnable eofReached) throws IOException { - checkAccess(inode, ACE4_READ_DATA); - return _inner.read(innerInode(inode), data, offset, eofReached); + public int read(Opaque stateId, Inode inode, ByteBuffer data, long offset, Runnable eofReached) throws IOException { + checkAccessReadWriteData(stateId, inode, false); + return _inner.read(stateId, innerInode(inode), data, offset, eofReached); } @Override @@ -383,6 +413,13 @@ public WriteResult write(Inode inode, ByteBuffer data, long offset, StabilityLev return _inner.write(innerInode(inode), data, offset, stabilityLevel); } + @Override + public WriteResult write(Opaque stateId, Inode inode, ByteBuffer data, long offset, StabilityLevel stabilityLevel) + throws IOException { + checkAccessReadWriteData(stateId, inode, true); + return _inner.write(stateId, innerInode(inode), data, offset, stabilityLevel); + } + @Override public Stat getattr(Inode inode) throws IOException { checkAccess(inode, ACE4_READ_ATTRIBUTES); diff --git a/core/src/main/java/org/dcache/nfs/vfs/VirtualFileSystem.java b/core/src/main/java/org/dcache/nfs/vfs/VirtualFileSystem.java index fd1e778d..229e1880 100644 --- a/core/src/main/java/org/dcache/nfs/vfs/VirtualFileSystem.java +++ b/core/src/main/java/org/dcache/nfs/vfs/VirtualFileSystem.java @@ -29,6 +29,7 @@ import org.dcache.nfs.status.InvalException; import org.dcache.nfs.status.IsDirException; import org.dcache.nfs.status.NotSuppException; +import org.dcache.nfs.util.Opaque; import org.dcache.nfs.v4.NfsIdMapping; import org.dcache.nfs.v4.xdr.nfsace4; import org.dcache.nfs.v4.xdr.stable_how4; @@ -226,6 +227,7 @@ default int read(Inode inode, ByteBuffer data, long offset) throws IOException { * information via {@link #getattr(Inode)}), which may incur a higher, recurring cost than the inconvenience of a * single additional client roundtrip at the end of the file. * + * @param stateId The stateId as obtained via an {@code open} command via NFSv4, or {@code null}. * @param inode inode of the file to read from. * @param data byte array for writing. * @param offset file's position to read from. @@ -234,7 +236,8 @@ default int read(Inode inode, ByteBuffer data, long offset) throws IOException { * @return number of bytes read from the file, possibly zero. -1 if EOF is reached. * @throws IOException */ - default int read(Inode inode, ByteBuffer data, long offset, Runnable eofReached) throws IOException { + default int read(Opaque stateId, Inode inode, ByteBuffer data, long offset, Runnable eofReached) + throws IOException { Stat stat = getattr(inode); Stat.Type statType = stat.type(); @@ -317,6 +320,22 @@ default WriteResult write(Inode inode, ByteBuffer data, long offset, StabilityLe return write(inode, bytes, offset, count, stabilityLevel); } + /** + * Write provided {@code data} into inode with a given stability level. + * + * @param stateId The stateId as obtained via an {@code open} command via NFSv4, or {@code null}. + * @param inode inode of the file to write. + * @param data data to be written. + * @param offset the file position to begin writing at. + * @param stabilityLevel data stability level. + * @return write result. + * @throws IOException + */ + default WriteResult write(Opaque stateId, Inode inode, ByteBuffer data, long offset, StabilityLevel stabilityLevel) + throws IOException { + return write(inode, data, offset, stabilityLevel); + } + /** * Flush data in {@code dirty} state to the stable storage. Typically follows * {@link #write(Inode, ByteBuffer, long, StabilityLevel)} operation. @@ -328,6 +347,20 @@ default WriteResult write(Inode inode, ByteBuffer data, long offset, StabilityLe */ void commit(Inode inode, long offset, int count) throws IOException; + /** + * Flush data in {@code dirty} state to the stable storage. Typically follows + * {@link #write(Inode, ByteBuffer, long, StabilityLevel)} operation. + * + * @param stateId The stateId as obtained via an {@code open} command via NFSv4, or {@code null}. + * @param inode inode of the file to commit. + * @param offset the file position to start commit at. + * @param count number of bytes to commit. + * @throws IOException + */ + default void commit(Opaque stateId, Inode inode, long offset, int count) throws IOException { + commit(inode, offset, count); + } + /** * Get file system object's attributes. * @@ -444,6 +477,18 @@ public StabilityLevel getStabilityLevel() { } } + enum OpenMode { + READ_ONLY, WRITE_ONLY, READ_WRITE; + + boolean canRead() { + return this == READ_ONLY || this == READ_WRITE; + } + + boolean canWrite() { + return this == WRITE_ONLY || this == READ_WRITE; + } + } + // NOTE - stability values and ordinals are the same for nfs 3 and 4 /** * Write stability level. @@ -588,4 +633,25 @@ default CompletableFuture copyFileRange(Inode src, long srcPos, Inode dst, default Inode inodeForNfsHandle(byte[] bytes) throws IOException { return Inode.forNfsHandle(bytes); } + + /** + * Notifies the filing system about an explicit open operation to an {@link Inode}, using the given open mode and + * the specified stateId as a reference for {@link #read(Opaque, Inode, ByteBuffer, long, Runnable)}, + * {@link #write(Opaque, Inode, ByteBuffer, long, StabilityLevel)} and {@link #commit(Opaque, Inode, long, int)}. + * + * @param stateId The stateId. + * @param inode The {@link Inode}. + * @param openMode The open mode. + */ + default void open(Opaque stateId, Inode inode, OpenMode openMode) { + } + + /** + * Notifies the filing system about an explicit close operation corresponding to the given stateId. + * + * @param stateId The stateId. + * @see #open(Opaque, Inode, OpenMode) + */ + default void close(Opaque stateId) { + } } diff --git a/core/src/test/java/org/dcache/nfs/v4/OperationREADTest.java b/core/src/test/java/org/dcache/nfs/v4/OperationREADTest.java index 7d4f47fd..5c8cd101 100644 --- a/core/src/test/java/org/dcache/nfs/v4/OperationREADTest.java +++ b/core/src/test/java/org/dcache/nfs/v4/OperationREADTest.java @@ -54,7 +54,7 @@ public void testLeaseUpdateForV40Client() throws UnknownHostException, ChimeraNF FileTracker fileTracker = mock(FileTracker.class); when(stateHandler.getFileTracker()).thenReturn(fileTracker); - when(fileTracker.getShareAccess(any(), any(), any())).thenReturn(nfs4_prot.OPEN4_SHARE_ACCESS_READ); + when(fileTracker.getShareAccess(any(), any(), (stateid4) any())).thenReturn(nfs4_prot.OPEN4_SHARE_ACCESS_READ); when(stateHandler.getClientIdByStateId(any())).thenReturn(client); when(session.getClient()).thenReturn(client); when(stateHandler.getClientIdByStateId(any())).thenReturn(client); @@ -90,7 +90,7 @@ public void testNoLeaseUpdateForV41Client() throws UnknownHostException, Chimera FileTracker fileTracker = mock(FileTracker.class); when(stateHandler.getFileTracker()).thenReturn(fileTracker); - when(fileTracker.getShareAccess(any(), any(), any())).thenReturn(nfs4_prot.OPEN4_SHARE_ACCESS_READ); + when(fileTracker.getShareAccess(any(), any(), (stateid4) any())).thenReturn(nfs4_prot.OPEN4_SHARE_ACCESS_READ); when(stateHandler.getClientIdByStateId(any())).thenReturn(client); when(session.getClient()).thenReturn(client); when(stateHandler.getClientIdByStateId(any())).thenReturn(client); diff --git a/core/src/test/java/org/dcache/nfs/v4/OperationWRITETest.java b/core/src/test/java/org/dcache/nfs/v4/OperationWRITETest.java index b02bfa48..90d07b77 100644 --- a/core/src/test/java/org/dcache/nfs/v4/OperationWRITETest.java +++ b/core/src/test/java/org/dcache/nfs/v4/OperationWRITETest.java @@ -57,14 +57,14 @@ public void testLeaseUpdateForV40Client() throws UnknownHostException, ChimeraNF FileTracker fileTracker = mock(FileTracker.class); when(stateHandler.getFileTracker()).thenReturn(fileTracker); - when(fileTracker.getShareAccess(any(), any(), any())).thenReturn(nfs4_prot.OPEN4_SHARE_ACCESS_WRITE); + when(fileTracker.getShareAccess(any(), any(), (stateid4) any())).thenReturn(nfs4_prot.OPEN4_SHARE_ACCESS_WRITE); when(stateHandler.getClientIdByStateId(any())).thenReturn(client); when(session.getClient()).thenReturn(client); when(stateHandler.getClientIdByStateId(any())).thenReturn(client); when(vfs.getattr(any())).thenReturn(fileStat); when(vfs.getattr(any(), any())).thenCallRealMethod(); - when(vfs.write(any(), any(), anyLong(), any())) + when(vfs.write(any(), any(), any(), anyLong(), any())) .thenReturn(new VirtualFileSystem.WriteResult(VirtualFileSystem.StabilityLevel.UNSTABLE, 1)); COMPOUND4args writeArgs = new CompoundBuilder() @@ -94,14 +94,14 @@ public void testNoLeaseUpdateForV41Client() throws UnknownHostException, Chimera FileTracker fileTracker = mock(FileTracker.class); when(stateHandler.getFileTracker()).thenReturn(fileTracker); - when(fileTracker.getShareAccess(any(), any(), any())).thenReturn(nfs4_prot.OPEN4_SHARE_ACCESS_WRITE); + when(fileTracker.getShareAccess(any(), any(), (stateid4) any())).thenReturn(nfs4_prot.OPEN4_SHARE_ACCESS_WRITE); when(stateHandler.getClientIdByStateId(any())).thenReturn(client); when(session.getClient()).thenReturn(client); when(stateHandler.getClientIdByStateId(any())).thenReturn(client); when(vfs.getattr(any())).thenReturn(fileStat); when(vfs.getattr(any(), any())).thenCallRealMethod(); - when(vfs.write(any(), any(), anyLong(), any())) + when(vfs.write(any(), any(), any(), anyLong(), any())) .thenReturn(new VirtualFileSystem.WriteResult(VirtualFileSystem.StabilityLevel.UNSTABLE, 1)); COMPOUND4args writeArgs = new CompoundBuilder() @@ -131,7 +131,7 @@ public void testReturnWriteVerifier() throws UnknownHostException, ChimeraNFSExc FileTracker fileTracker = mock(FileTracker.class); when(stateHandler.getFileTracker()).thenReturn(fileTracker); - when(fileTracker.getShareAccess(any(), any(), any())).thenReturn(nfs4_prot.OPEN4_SHARE_ACCESS_WRITE); + when(fileTracker.getShareAccess(any(), any(), (stateid4) any())).thenReturn(nfs4_prot.OPEN4_SHARE_ACCESS_WRITE); when(stateHandler.getClientIdByStateId(any())).thenReturn(client); when(session.getClient()).thenReturn(client); when(stateHandler.getClientIdByStateId(any())).thenReturn(client); @@ -140,7 +140,7 @@ public void testReturnWriteVerifier() throws UnknownHostException, ChimeraNFSExc when(vfs.getattr(any())).thenReturn(fileStat); when(vfs.getattr(any(), any())).thenCallRealMethod(); - when(vfs.write(any(), any(), anyLong(), any())) + when(vfs.write(any(), any(), any(), anyLong(), any())) .thenReturn(new VirtualFileSystem.WriteResult(VirtualFileSystem.StabilityLevel.UNSTABLE, 1)); COMPOUND4args writeArgs = new CompoundBuilder() diff --git a/core/src/test/java/org/dcache/nfs/v4/xdr/stateid4Test.java b/core/src/test/java/org/dcache/nfs/v4/xdr/stateid4Test.java index 61e0bd05..b8989e3f 100644 --- a/core/src/test/java/org/dcache/nfs/v4/xdr/stateid4Test.java +++ b/core/src/test/java/org/dcache/nfs/v4/xdr/stateid4Test.java @@ -21,8 +21,6 @@ import static org.junit.Assert.*; -import org.dcache.nfs.v4.xdr.stateid4; -import org.dcache.nfs.v4.xdr.uint32_t; import org.junit.Test; public class stateid4Test { @@ -30,13 +28,9 @@ public class stateid4Test { @Test public void testEqualsTrue() { - stateid4 stateidA = new stateid4(); - stateidA.seqid = 1; - stateidA.other = "state".getBytes(); + stateid4 stateidA = new stateid4("state".getBytes(), 1); - stateid4 stateidB = new stateid4(); - stateidB.seqid = 1; - stateidB.other = "state".getBytes(); + stateid4 stateidB = new stateid4("state".getBytes(), 1); assertTrue("equal keys not equal", stateidA.equals(stateidB)); assertTrue("equal, but different hashCode", stateidA.hashCode() == stateidB.hashCode()); @@ -46,9 +40,7 @@ public void testEqualsTrue() { @Test public void testEqualsSame() { - stateid4 stateidA = new stateid4(); - stateidA.seqid = 1; - stateidA.other = "state".getBytes(); + stateid4 stateidA = new stateid4("state".getBytes(), 1); assertTrue("equal keys not equal", stateidA.equals(stateidA)); } @@ -56,13 +48,9 @@ public void testEqualsSame() { @Test public void testDifferSequence() { - stateid4 stateidA = new stateid4(); - stateidA.seqid = 1; - stateidA.other = "state".getBytes(); + stateid4 stateidA = new stateid4("state".getBytes(), 1); - stateid4 stateidB = new stateid4(); - stateidB.seqid = 2; - stateidB.other = "state".getBytes(); + stateid4 stateidB = new stateid4("state".getBytes(), 2); assertTrue("differ by sequence should still be equal", stateidA.equals(stateidB)); assertFalse("differ by sequence can't be equal", stateidA.equalsWithSeq(stateidB)); @@ -71,13 +59,9 @@ public void testDifferSequence() { @Test public void testDifferOther() { - stateid4 stateidA = new stateid4(); - stateidA.seqid = 1; - stateidA.other = "stateA".getBytes(); + stateid4 stateidA = new stateid4("stateA".getBytes(), 1); - stateid4 stateidB = new stateid4(); - stateidB.seqid = 1; - stateidB.other = "stateB".getBytes(); + stateid4 stateidB = new stateid4("stateB".getBytes(), 1); assertFalse("differ by other not detected", stateidA.equals(stateidB)); }