Commit d6b3cc45 authored by Tigran Mkrtchyan's avatar Tigran Mkrtchyan
Browse files

nfsv4.1: refactor reply cache

the earlier implementation of reply cache was encapsulated
inside session. On one hand it works, but on the other hand
ugly implementation did not allow me to sleep good.

This patch make nfs server session and cache aware.
The CompoundContext knows about number of operations and
current operation position.

The operation position verification is not spec complaint.
(we set compound status but do not return operation result).
Nevertheless it protects against malicious clients.
parent 94e63fff
......@@ -37,7 +37,7 @@ public abstract class AbstractNFSv4Operation {
/**
* Process current operation.
* @return <code>true</code> if next operation may continue.
* @return result of operation
*/
public abstract boolean process(CompoundContext context);
public abstract nfs_resop4 process(CompoundContext context);
}
......@@ -44,7 +44,6 @@ public class CompoundContext {
private final int _minorversion;
private NFSv41Session _session = null;
private final List<nfs_resop4> _processedOps;
private final VirtualFileSystem _fs;
private final RpcCall _callInfo;
......@@ -54,6 +53,10 @@ public class CompoundContext {
private final AclHandler _aclHandler;
private final NFSv4StateHandler _stateHandler;
private final NfsIdMapping _idMapping;
private int _slotId;
private boolean _cacheThis;
private final int _totalOperationsCount;
private int _curretOpPosition = -1;
/**
* Create context of COUMPOUND request.
......@@ -64,12 +67,11 @@ public class CompoundContext {
* @param call RPC call
* @param exportFile list of servers exports.
*/
public CompoundContext(List<nfs_resop4> processedOps, int minorversion, VirtualFileSystem fs,
public CompoundContext(int minorversion, VirtualFileSystem fs,
NFSv4StateHandler stateHandler,
NFSv41DeviceManager deviceManager, AclHandler aclHandler, RpcCall call,
NfsIdMapping idMapping,
ExportFile exportFile) {
_processedOps = processedOps;
ExportFile exportFile, int opCount) {
_minorversion = minorversion;
_fs = fs;
_deviceManager = deviceManager;
......@@ -79,6 +81,7 @@ public class CompoundContext {
_user = NfsUser.remoteUser(_callInfo, _exportFile);
_stateHandler = stateHandler;
_idMapping = idMapping;
_totalOperationsCount = opCount;
}
public RpcCall getRpcCall() {
......@@ -202,14 +205,6 @@ public class CompoundContext {
return _session;
}
/**
* Get list of currently processed operations.
* @return list of operations.
*/
public List<nfs_resop4> processedOperations() {
return _processedOps;
}
public NFSv4StateHandler getStateHandler() {
return _stateHandler;
}
......@@ -217,4 +212,43 @@ public class CompoundContext {
public NfsIdMapping getIdMapping() {
return _idMapping;
}
public int getSlotId() {
return _slotId;
}
public void setSlotId(int slotId) {
_slotId = slotId;
}
public boolean cacheThis() {
return _cacheThis;
}
public void setCacheThis(boolean cacheThis) {
_cacheThis = cacheThis;
}
private List<nfs_resop4> _cache;
public List<nfs_resop4> getCache() {
return _cache;
}
public void setCache(List<nfs_resop4> cache) {
_cache = cache;
}
public int getOperationPosition() {
return _curretOpPosition;
}
public int getTotalOperationCount() {
return _totalOperationsCount;
}
public void nextOperation() {
assert _curretOpPosition < _totalOperationsCount;
_curretOpPosition ++;
}
}
......@@ -36,6 +36,7 @@ import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import org.dcache.chimera.nfs.v4.xdr.nfs_opnum4;
import org.dcache.chimera.nfs.vfs.VirtualFileSystem;
public class NFSServerV41 extends nfs4_prot_NFS4_PROGRAM_ServerStub {
......@@ -79,36 +80,60 @@ public class NFSServerV41 extends nfs4_prot_NFS4_PROGRAM_ServerStub {
call$.getTransport().getRemoteSocketAddress(),
new String(arg1.tag.value.value));
MDC.put(NfsMdc.TAG, new String(arg1.tag.value.value) );
/*
* here we have to checkfor utf8, but it's too much work to keep
* spec happy.
*/
String tag = new String(arg1.tag.value.value);
MDC.put(NfsMdc.TAG, tag);
MDC.put(NfsMdc.CLIENT, call$.getTransport().getRemoteSocketAddress().toString());
List<nfs_resop4> v = new ArrayList<nfs_resop4>(arg1.argarray.length);
if (arg1.minorversion.value > 1) {
int minorversion = arg1.minorversion.value;
if ( minorversion > 1) {
throw new ChimeraNFSException(nfsstat4.NFS4ERR_MINOR_VERS_MISMATCH,
String.format("Unsupported minor version [%d]",arg1.minorversion.value) );
}
CompoundContext context = new CompoundContext(v, arg1.minorversion.value,
_fs, _statHandler, _deviceManager, _aclHandler, call$, _idMapping, _exportFile);
CompoundContext context = new CompoundContext(arg1.minorversion.value,
_fs, _statHandler, _deviceManager, _aclHandler, call$, _idMapping,
_exportFile, arg1.argarray.length);
for (nfs_argop4 op : arg1.argarray) {
res.status = nfsstat4.NFS4_OK;
res.resarray = new ArrayList<nfs_resop4>(arg1.argarray.length);
res.tag = arg1.tag;
if (!_operationFactory.getOperation(op).process(context)) {
boolean retransmit = false;
for (nfs_argop4 op : arg1.argarray) {
context.nextOperation();
int position = context.getOperationPosition();
if (minorversion > 0) {
checkOpPosition(op.argop, position);
if (position == 1) {
/*
* at this point we already have to have a session
*/
List<nfs_resop4> cache = context.getCache();
if (cache != null) {
res.resarray.addAll(cache.subList(position, cache.size()));
res.status = statusOfLastOperation(cache);
retransmit = true;
break;
}
}
}
nfs_resop4 opResult = _operationFactory.getOperation(op).process(context);
res.resarray.add(opResult);
res.status = opResult.getStatus();
if (res.status != nfsstat4.NFS4_OK) {
break;
}
}
res.resarray = context.processedOperations();
// result status must be equivalent
// to the status of the last operation that
// was executed within the COMPOUND procedure
if (!v.isEmpty()) {
res.status = res.resarray.get(res.resarray.size() - 1).getStatus();
} else {
res.status = nfsstat4.NFS4_OK;
if (!retransmit && context.cacheThis()) {
context.getSession().updateSlotCache(context.getSlotId(), res.resarray);
}
res.tag = arg1.tag;
_log.debug( "OP: [{}] status: {}", res.tag, res.status);
} catch (ChimeraNFSException e) {
......@@ -137,4 +162,46 @@ public class NFSServerV41 extends nfs4_prot_NFS4_PROGRAM_ServerStub {
public List<NFS4Client> getClients() {
return _statHandler.getClients();
}
/*
*
* from NFSv4.1 spec:
*
* This operation MUST appear as the first operation of any COMPOUND in which it appears.
* The error NFS4ERR_SEQUENCE_POS will be returned when if it is found in any position in
* a COMPOUND beyond the first. Operations other than SEQUENCE, BIND_CONN_TO_SESSION,
* EXCHANGE_ID, CREATE_SESSION, and DESTROY_SESSION, may not appear as the first operation
* in a COMPOUND. Such operations will get the error NFS4ERR_OP_NOT_IN_SESSION if they do
* appear at the start of a COMPOUND.
*
*/
private static void checkOpPosition(int opCode, int position) throws ChimeraNFSException {
/*
* special case of illegal operations.
*/
if(opCode > nfs_opnum4.OP_RECLAIM_COMPLETE || opCode < nfs_opnum4.OP_ACCESS)
return;
if(position == 0 ) {
switch(opCode) {
case nfs_opnum4.OP_SEQUENCE:
case nfs_opnum4.OP_CREATE_SESSION:
case nfs_opnum4.OP_EXCHANGE_ID:
case nfs_opnum4.OP_DESTROY_SESSION:
break;
default:
throw new ChimeraNFSException(nfsstat4.NFS4ERR_OP_NOT_IN_SESSION, "not in session");
}
} else {
switch (opCode) {
case nfs_opnum4.OP_SEQUENCE:
throw new ChimeraNFSException(nfsstat4.NFS4ERR_SEQUENCE_POS, "not a first operation");
}
}
}
private static int statusOfLastOperation(List<nfs_resop4> ops) {
return ops.get(ops.size() -1).getStatus();
}
}
......@@ -68,9 +68,9 @@ public class NFSv41Session {
return _slots.length - 1;
}
public boolean updateSlot(int slot, int sequence, boolean cacheThis, List<nfs_resop4> reply)
public List<nfs_resop4> checkCacheSlot(int slot, int sequence, boolean checkCache)
throws ChimeraNFSException {
return getSlot(slot).update(sequence, reply, cacheThis);
return getSlot(slot).checkSlotSequence(sequence, checkCache);
}
/**
......@@ -109,4 +109,8 @@ public class NFSv41Session {
}
return sb.toString();
}
public void updateSlotCache(int slot, List<nfs_resop4> reply) throws ChimeraNFSException {
getSlot(slot).update(reply);
}
}
......@@ -25,6 +25,7 @@ import org.dcache.chimera.nfs.v4.xdr.nfs_opnum4;
import org.dcache.chimera.nfs.v4.xdr.ACCESS4res;
import org.dcache.chimera.nfs.v4.xdr.ACCESS4resok;
import org.dcache.chimera.nfs.ChimeraNFSException;
import org.dcache.chimera.nfs.v4.xdr.nfs_resop4;
import org.dcache.chimera.posix.AclHandler;
import org.dcache.chimera.posix.Stat;
import org.dcache.chimera.posix.UnixAcl;
......@@ -40,7 +41,7 @@ public class OperationACCESS extends AbstractNFSv4Operation {
}
@Override
public boolean process(CompoundContext context) {
public nfs_resop4 process(CompoundContext context) {
ACCESS4res res = new ACCESS4res();
......@@ -104,9 +105,7 @@ public class OperationACCESS extends AbstractNFSv4Operation {
}
_result.opaccess = res;
context.processedOperations().add(_result);
return res.status == nfsstat4.NFS4_OK;
return _result;
}
......
......@@ -23,6 +23,7 @@ import org.dcache.chimera.nfs.v4.xdr.nfs_argop4;
import org.dcache.chimera.nfs.v4.xdr.nfs_opnum4;
import org.dcache.chimera.nfs.v4.xdr.CLOSE4res;
import org.dcache.chimera.nfs.ChimeraNFSException;
import org.dcache.chimera.nfs.v4.xdr.nfs_resop4;
import org.dcache.chimera.nfs.vfs.Inode;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
......@@ -36,7 +37,7 @@ public class OperationCLOSE extends AbstractNFSv4Operation {
}
@Override
public boolean process(CompoundContext context) {
public nfs_resop4 process(CompoundContext context) {
CLOSE4res res = new CLOSE4res();
try {
......@@ -62,8 +63,6 @@ public class OperationCLOSE extends AbstractNFSv4Operation {
res.status = he.getStatus();
}
_result.opclose = res;
context.processedOperations().add(_result);
return res.status == nfsstat4.NFS4_OK;
return _result;
}
}
......@@ -21,6 +21,7 @@ import org.dcache.chimera.nfs.v4.xdr.nfsstat4;
import org.dcache.chimera.nfs.v4.xdr.nfs_argop4;
import org.dcache.chimera.nfs.v4.xdr.nfs_opnum4;
import org.dcache.chimera.nfs.v4.xdr.COMMIT4res;
import org.dcache.chimera.nfs.v4.xdr.nfs_resop4;
public class OperationCOMMIT extends AbstractNFSv4Operation {
......@@ -29,10 +30,9 @@ public class OperationCOMMIT extends AbstractNFSv4Operation {
}
@Override
public boolean process(CompoundContext context) {
public nfs_resop4 process(CompoundContext context) {
_result.opcommit = new COMMIT4res();
_result.opcommit.status = nfsstat4.NFS4ERR_NOTSUPP;
context.processedOperations().add(_result);
return false;
return _result;
}
}
......@@ -28,6 +28,7 @@ import org.dcache.chimera.nfs.v4.xdr.CREATE4res;
import org.dcache.chimera.nfs.v4.xdr.CREATE4resok;
import org.dcache.chimera.nfs.ChimeraNFSException;
import org.dcache.chimera.ChimeraFsException;
import org.dcache.chimera.nfs.v4.xdr.nfs_resop4;
import org.dcache.chimera.nfs.vfs.Inode;
import org.dcache.chimera.posix.AclHandler;
import org.dcache.chimera.posix.Stat;
......@@ -44,7 +45,7 @@ public class OperationCREATE extends AbstractNFSv4Operation {
}
@Override
public boolean process(CompoundContext context) {
public nfs_resop4 process(CompoundContext context) {
CREATE4res res = new CREATE4res();
......@@ -151,8 +152,6 @@ public class OperationCREATE extends AbstractNFSv4Operation {
}
_result.opcreate = res;
context.processedOperations().add(_result);
return res.status == nfsstat4.NFS4_OK;
return _result;
}
}
......@@ -25,6 +25,7 @@ import org.dcache.chimera.nfs.v4.xdr.nfs_opnum4;
import org.dcache.chimera.nfs.v4.xdr.CREATE_SESSION4resok;
import org.dcache.chimera.nfs.v4.xdr.CREATE_SESSION4res;
import org.dcache.chimera.nfs.ChimeraNFSException;
import org.dcache.chimera.nfs.v4.xdr.nfs_resop4;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
......@@ -38,7 +39,7 @@ public class OperationCREATE_SESSION extends AbstractNFSv4Operation {
}
@Override
public boolean process(CompoundContext context) {
public nfs_resop4 process(CompoundContext context) {
CREATE_SESSION4res res = new CREATE_SESSION4res();
Long clientId = Long.valueOf(_args.opcreate_session.csa_clientid.value.value);
......@@ -131,9 +132,7 @@ public class OperationCREATE_SESSION extends AbstractNFSv4Operation {
}
_result.opcreate_session = res;
context.processedOperations().add(_result);
return res.csr_status == nfsstat4.NFS4_OK;
return _result;
}
}
......@@ -21,6 +21,7 @@ import org.dcache.chimera.nfs.v4.xdr.nfsstat4;
import org.dcache.chimera.nfs.v4.xdr.nfs_argop4;
import org.dcache.chimera.nfs.v4.xdr.nfs_opnum4;
import org.dcache.chimera.nfs.v4.xdr.DELEGPURGE4res;
import org.dcache.chimera.nfs.v4.xdr.nfs_resop4;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
......@@ -33,10 +34,9 @@ public class OperationDELEGPURGE extends AbstractNFSv4Operation {
}
@Override
public boolean process(CompoundContext context) {
public nfs_resop4 process(CompoundContext context) {
_result.opdelegpurge = new DELEGPURGE4res();
_result.opdelegpurge.status = nfsstat4.NFS4ERR_NOTSUPP;
context.processedOperations().add(_result);
return false;
return _result;
}
}
......@@ -21,6 +21,7 @@ import org.dcache.chimera.nfs.v4.xdr.nfsstat4;
import org.dcache.chimera.nfs.v4.xdr.nfs_argop4;
import org.dcache.chimera.nfs.v4.xdr.nfs_opnum4;
import org.dcache.chimera.nfs.v4.xdr.DELEGRETURN4res;
import org.dcache.chimera.nfs.v4.xdr.nfs_resop4;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
......@@ -33,10 +34,9 @@ public class OperationDELEGRETURN extends AbstractNFSv4Operation {
}
@Override
public boolean process(CompoundContext context) {
public nfs_resop4 process(CompoundContext context) {
_result.opdelegreturn = new DELEGRETURN4res();
_result.opdelegreturn.status = nfsstat4.NFS4ERR_NOTSUPP;
context.processedOperations().add(_result);
return false;
return _result;
}
}
......@@ -21,6 +21,7 @@ import org.dcache.chimera.nfs.v4.xdr.nfs_argop4;
import org.dcache.chimera.nfs.v4.xdr.nfs_opnum4;
import org.dcache.chimera.nfs.v4.xdr.DESTROY_SESSION4res;
import org.dcache.chimera.nfs.ChimeraNFSException;
import org.dcache.chimera.nfs.v4.xdr.nfs_resop4;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
......@@ -33,7 +34,7 @@ public class OperationDESTROY_SESSION extends AbstractNFSv4Operation {
}
@Override
public boolean process(CompoundContext context) {
public nfs_resop4 process(CompoundContext context) {
DESTROY_SESSION4res res = new DESTROY_SESSION4res();
......@@ -65,7 +66,6 @@ public class OperationDESTROY_SESSION extends AbstractNFSv4Operation {
}
_result.opdestroy_session = res;
context.processedOperations().add(_result);
return res.dsr_status == nfsstat4.NFS4_OK;
return _result;
}
}
......@@ -43,6 +43,7 @@ import java.security.ProtectionDomain;
import java.util.jar.Attributes;
import java.util.jar.JarInputStream;
import java.util.jar.Manifest;
import org.dcache.chimera.nfs.v4.xdr.nfs_resop4;
import org.dcache.chimera.nfs.v4.xdr.verifier4;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
......@@ -106,7 +107,7 @@ public class OperationEXCHANGE_ID extends AbstractNFSv4Operation {
}
@Override
public boolean process(CompoundContext context) {
public nfs_resop4 process(CompoundContext context) {
EXCHANGE_ID4res res = new EXCHANGE_ID4res();
......@@ -274,8 +275,7 @@ public class OperationEXCHANGE_ID extends AbstractNFSv4Operation {
}
_result.opexchange_id = res;
context.processedOperations().add(_result);
return res.eir_status == nfsstat4.NFS4_OK;
return _result;
}
}
......@@ -95,9 +95,9 @@ import org.dcache.xdr.XdrBuffer;
import org.dcache.xdr.XdrEncodingStream;
import org.dcache.chimera.FsStat;
import org.dcache.chimera.UnixPermission;
import org.dcache.chimera.nfs.v4.xdr.nfs_resop4;
import org.dcache.chimera.nfs.vfs.Inode;
import org.dcache.chimera.nfs.vfs.VirtualFileSystem;
import org.dcache.chimera.nfs.v4.acl.Ace;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
......@@ -110,7 +110,7 @@ public class OperationGETATTR extends AbstractNFSv4Operation {
}
@Override
public boolean process(CompoundContext context) {
public nfs_resop4 process(CompoundContext context) {
GETATTR4res res = new GETATTR4res();
......@@ -131,9 +131,7 @@ public class OperationGETATTR extends AbstractNFSv4Operation {
_result.opgetattr = res;
context.processedOperations().add(_result);
return res.status == nfsstat4.NFS4_OK;
return _result;
}
......
......@@ -31,7 +31,7 @@ public class OperationGETDEVICEINFO extends AbstractNFSv4Operation {
}
@Override
public boolean process(CompoundContext context) {
public nfs_resop4 process(CompoundContext context) {
/*
* GETDEVICEINFO.
......@@ -72,9 +72,7 @@ public class OperationGETDEVICEINFO extends AbstractNFSv4Operation {
}
_result.opgetdeviceinfo = res;
context.processedOperations().add(_result);
return res.gdir_status == nfsstat4.NFS4_OK;
return _result;
}
......
......@@ -33,7 +33,7 @@ public class OperationGETDEVICELIST extends AbstractNFSv4Operation {
}
@Override
public boolean process(CompoundContext context) {
public nfs_resop4 process(CompoundContext context) {
GETDEVICELIST4res res = new GETDEVICELIST4res();
......@@ -90,9 +90,7 @@ public class OperationGETDEVICELIST extends AbstractNFSv4Operation {
}
_result.opgetdevicelist = res;
context.processedOperations().add(_result);
return res.gdlr_status == nfsstat4.NFS4_OK;
return _result;
}
}
......@@ -24,6 +24,7 @@ import org.dcache.chimera.nfs.v4.xdr.nfs_opnum4;
import org.dcache.chimera.nfs.v4.xdr.GETFH4res;
import org.dcache.chimera.nfs.v4.xdr.GETFH4resok;
import org.dcache.chimera.nfs.ChimeraNFSException;
import org.dcache.chimera.nfs.v4.xdr.nfs_resop4;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
......@@ -36,7 +37,7 @@ public class OperationGETFH extends AbstractNFSv4Operation {
}
@Override
public boolean process(CompoundContext context) {
public nfs_resop4 process(CompoundContext context) {
GETFH4res res = new GETFH4res();
......@@ -54,9 +55,7 @@ public class OperationGETFH extends AbstractNFSv4Operation {
}
_result.opgetfh = res;
context.processedOperations().add(_result);
return res.status == nfsstat4.NFS4_OK;
return _result;
}
......
......@@ -21,6 +21,7 @@ import org.dcache.chimera.nfs.v4.xdr.nfsstat4;
import org.dcache.chimera.nfs.v4.xdr.nfs_argop4;
import org.dcache.chimera.nfs.v4.xdr.nfs_opnum4;
import org.dcache.chimera.nfs.v4.xdr.ILLEGAL4res;
import org.dcache.chimera.nfs.v4.xdr.nfs_resop4;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
......@@ -33,14 +34,13 @@ public class OperationILLEGAL extends AbstractNFSv4Operation {
}
@Override
public boolean process(CompoundContext context) {
public nfs_resop4 process(CompoundContext context) {