/* * $Id$ * * Copyright (C) 2003-2015 JNode.org * * This library is free software; you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License as published * by the Free Software Foundation; either version 2.1 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 Lesser General Public * License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this library; If not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ package org.jnode.net.nfs.nfs2; import java.io.IOException; import java.net.InetAddress; import java.util.ArrayList; import java.util.LinkedList; import java.util.List; import org.acplt.oncrpc.OncRpcClient; import org.acplt.oncrpc.OncRpcClientAuthUnix; import org.acplt.oncrpc.OncRpcException; import org.acplt.oncrpc.OncRpcPortmapClient; import org.acplt.oncrpc.OncRpcProtocols; import org.acplt.oncrpc.OncRpcTcpClient; import org.acplt.oncrpc.OncRpcUdpClient; import org.acplt.oncrpc.XdrAble; import org.acplt.oncrpc.XdrDecodingStream; import org.acplt.oncrpc.XdrEncodingStream; import org.acplt.oncrpc.XdrVoid; import org.apache.log4j.Logger; import org.jnode.net.nfs.Protocol; /** * This class access a NFS2 server . It implements all the method from NFS2 specification * * http://tools.ietf.org/html/rfc1094 * * @author Andrei Dore */ public class NFS2Client { /** * The maximum number of bytes of data in a READ or WRITE request. */ public static final int MAX_DATA = 8192; public static final int FILE_HANDLE_SIZE = 32; public static final int MAX_NAME_LENGTH = 255; public static final int MAX_PATH_LENGTH = 1024; public static final int COOKIE_SIZE = 4; /** * This constant it is use when we create a rpc client . The creation of the * rpc client need a buffer to store parameters or results . To create this * buffer we must know the max length of the buffer. The NFS2 specification * specify that for read write operation the maximun number of bytes it is * 8192.At this number we must add the length of the header(24), the lenght * of the auth part (400) and the length of the parameters in write * operation (16 +FILE_HANDLE_SIZE) . We chose to add the length of the * parameter in write operation because only in this case we risk a buffer * overflow. */ private static final int HEADER_DATA = 440 + FILE_HANDLE_SIZE; private static final int NFS_VERSION = 2; private static final int NFS_PROGRAM = 100003; private static final int PROCEDURE_TEST = 0; private static final int PROCEDURE_GET_ATTRIBUTE = 1; private static final int PROCEDURE_SET_ATTRIBUTE = 2; private static final int PROCEDURE_LOOKUP = 4; private static final int PROCEDURE_READ_FILE = 6; private static final int PROCEDURE_WRITE_FILE = 8; private static final int PROCEDURE_CREATE_FILE = 9; private static final int PROCEDURE_REMOVE_FILE = 10; private static final int PROCEDURE_RENAME_FILE = 11; private static final int PROCEDURE_CREATE_DIRECTORY = 14; private static final int PROCEDURE_REMOVE_DIRECTORY = 15; private static final int PROCEDURE_LIST_DIRECTORY = 16; private static final int PROCEDURE_GET_FILE_SYSTEM_ATTRIBUTE = 17; private static final Logger LOGGER = Logger.getLogger(NFS2Client.class); private List<OncRpcClient> rpcClientPool; private InetAddress host; private Protocol protocol; private int uid; private int gid; private boolean closed; /** * Constructs a <code>NFS2Client</code> client stub proxy object from * which the NFS_PROGRAM remote program can be accessed. */ public NFS2Client(InetAddress host, Protocol protocol, int uid, int gid) { this.host = host; this.protocol = protocol; this.uid = uid; this.gid = gid; rpcClientPool = new LinkedList<OncRpcClient>(); } private OncRpcClient createRpcClient() throws OncRpcException, IOException { // invoke portmap OncRpcPortmapClient portmap = new OncRpcPortmapClient(host); int port; try { port = portmap.getPort(NFS_PROGRAM, NFS_VERSION, protocol == Protocol.UDP ? OncRpcProtocols.ONCRPC_UDP : OncRpcProtocols.ONCRPC_TCP); } finally { portmap.close(); } // create the client // We create the client with a buffer with lenght equals witn MAX_DATA + // 424 ( max header length) OncRpcClient client = null; if (protocol == Protocol.UDP) { client = new OncRpcUdpClient(host, NFS_PROGRAM, NFS_VERSION, port, MAX_DATA + HEADER_DATA); } else if (protocol == Protocol.TCP) { client = new OncRpcTcpClient(host, NFS_PROGRAM, NFS_VERSION, port, MAX_DATA + HEADER_DATA); } else { // TODO Do something } client.setTimeout(10000); if (uid != -1 && gid != -1) { client.setAuth(new OncRpcClientAuthUnix("test", uid, gid)); } return client; } // TODO This lock it is not good because we wait an IO operation before we // free the lock . So the creation of the nfsclient must be outside of the // lock private synchronized OncRpcClient getRpcClient() throws OncRpcException, IOException { if (closed) { throw new IOException("The nfs client it is closed"); } if (rpcClientPool.size() == 0) { // TODO Improve this lock return createRpcClient(); } else { return rpcClientPool.remove(0); } } private synchronized void releaseRpcClient(OncRpcClient client) throws IOException { if (closed) { throw new IOException("The nfs client it is closed"); } if (client != null) { rpcClientPool.add(client); } } private void call(final int functionId, final XdrAble parameter, final XdrAble result) throws NFS2Exception, IOException { OncRpcClient client = null; int countCall = 0; while (true) { try { countCall++; client = getRpcClient(); if (result == XdrVoid.XDR_VOID) { client.call(functionId, parameter, result); } else { ResultWithCode nfsResult = new ResultWithCode(result); client.call(functionId, parameter, nfsResult); if (nfsResult.getResultCode() != ResultCode.NFS_OK) { throw new NFS2Exception(nfsResult.getResultCode()); } } break; } catch (Exception e) { if (client != null) { try { client.close(); } catch (OncRpcException e1) { // Ignore this } client = null; } if (e instanceof RuntimeException) { throw (RuntimeException) e; } if (e instanceof OncRpcException) { if (countCall > 5) { throw new NFS2Exception(e.getMessage(), e); } else { LOGGER.warn("An error occurs when nfs file system try to call the rpc method. Reason: " + e.getMessage() + " . It will try again"); continue; } } else { throw new NFS2Exception(e.getMessage(), e); } } finally { if (client != null) { try { releaseRpcClient(client); } catch (IOException e) { // ignore } } } } } public synchronized void close() throws IOException { closed = true; List<OncRpcException> exceptionList = new ArrayList<OncRpcException>(); for (OncRpcClient client : rpcClientPool) { try { client.close(); } catch (OncRpcException e) { exceptionList.add(e); } } if (exceptionList.size() != 0) { StringBuilder builder = new StringBuilder(); builder.append("An error occurs when the mount client close connections. Reason:"); for (OncRpcException anExceptionList : exceptionList) { builder.append(anExceptionList.getMessage()); builder.append('.'); } throw new IOException(builder.toString()); } } /** * Call remote procedure test. * * @throws NFS2Exception * @throws IOException */ public void test() throws NFS2Exception, IOException { call(PROCEDURE_TEST, XdrVoid.XDR_VOID, XdrVoid.XDR_VOID); } public LookupResult lookup(final byte[] fileHandle, final String entryName) throws NFS2Exception, IOException { XdrAble nfsParameter = new NFSParameter() { public void xdrEncode(XdrEncodingStream xdr) throws OncRpcException, IOException { xdr.xdrEncodeOpaque(fileHandle, NFS2Client.FILE_HANDLE_SIZE); xdr.xdrEncodeString(entryName); } }; final LookupResult result = new LookupResult(); XdrAble nfsResult = new NFSResult() { public void xdrDecode(XdrDecodingStream xdr) throws OncRpcException, IOException { result.setFileHandle(xdr.xdrDecodeOpaque(NFS2Client.FILE_HANDLE_SIZE)); FileAttribute fileAttribute = new FileAttribute(); xdrFileAttributeDecode(xdr, fileAttribute); result.setFileAttribute(fileAttribute); } }; call(PROCEDURE_LOOKUP, nfsParameter, nfsResult); return result; } public ListDirectoryResult listDirectory(final byte[] fileHandle, final byte[] cookie, final int count) throws NFS2Exception, IOException { XdrAble nfsParameter = new NFSParameter() { public void xdrEncode(XdrEncodingStream xdr) throws OncRpcException, IOException { xdr.xdrEncodeOpaque(fileHandle, FILE_HANDLE_SIZE); xdr.xdrEncodeOpaque(cookie, COOKIE_SIZE); xdr.xdrEncodeInt(count); } }; final ListDirectoryResult result = new ListDirectoryResult(); XdrAble nfsResult = new NFSResult() { public void xdrDecode(XdrDecodingStream xdr) throws OncRpcException, IOException { List<Entry> entryList = new ArrayList<Entry>(); while (xdr.xdrDecodeBoolean()) { int fileId = xdr.xdrDecodeInt(); String name = xdr.xdrDecodeString(); byte[] cookie = xdr.xdrDecodeOpaque(COOKIE_SIZE); Entry entry = new Entry(fileId, name, cookie); entryList.add(entry); } result.setEntryList(entryList); result.setEof(xdr.xdrDecodeBoolean()); } }; call(PROCEDURE_LIST_DIRECTORY, nfsParameter, nfsResult); return result; } public ReadFileResult readFile(final byte[] fileHandle, final int offset, final int count) throws NFS2Exception, IOException { if (count > MAX_DATA) { throw new IllegalArgumentException( "The number of bytes read by the nfs client can not be greater than " + MAX_DATA); } XdrAble nfsParameter = new NFSParameter() { public void xdrEncode(XdrEncodingStream xdr) throws OncRpcException, IOException { xdr.xdrEncodeOpaque(fileHandle, FILE_HANDLE_SIZE); xdr.xdrEncodeInt(offset); xdr.xdrEncodeInt(count); xdr.xdrEncodeInt(0); } }; final ReadFileResult result = new ReadFileResult(); XdrAble nfsResult = new NFSResult() { public void xdrDecode(XdrDecodingStream xdr) throws OncRpcException, IOException { FileAttribute fileAttribute = new FileAttribute(); xdrFileAttributeDecode(xdr, fileAttribute); result.setFileAttribute(fileAttribute); // TODO Optimize this result.setData(xdr.xdrDecodeDynamicOpaque()); } }; call(PROCEDURE_READ_FILE, nfsParameter, nfsResult); return result; } public void removeDirectory(final byte[] fileHandle, final String name) throws NFS2Exception, IOException { NFSParameter nfsParameter = new NFSParameter() { public void xdrEncode(XdrEncodingStream xdr) throws OncRpcException, IOException { xdr.xdrEncodeOpaque(fileHandle, FILE_HANDLE_SIZE); xdr.xdrEncodeString(name); } }; XdrAble nfsResult = new NFSResult() { public void xdrDecode(XdrDecodingStream xdr) throws OncRpcException, IOException { } }; call(PROCEDURE_REMOVE_DIRECTORY, nfsParameter, nfsResult); } public void removeFile(final byte[] parentFileHandle, final String name) throws NFS2Exception, IOException { NFSParameter nfsParameter = new NFSParameter() { public void xdrEncode(XdrEncodingStream xdr) throws OncRpcException, IOException { xdr.xdrEncodeOpaque(parentFileHandle, FILE_HANDLE_SIZE); xdr.xdrEncodeString(name); } }; XdrAble nfsResult = new NFSResult() { public void xdrDecode(XdrDecodingStream xdr) throws OncRpcException, IOException { } }; call(PROCEDURE_REMOVE_FILE, nfsParameter, nfsResult); } public void renameFile(final byte[] fromParentFileHandle, final String fromName, final byte[] toParentFileHandle, final String toName) throws NFS2Exception, IOException { NFSParameter nfsParameter = new NFSParameter() { public void xdrEncode(XdrEncodingStream xdr) throws OncRpcException, IOException { xdr.xdrEncodeOpaque(fromParentFileHandle, FILE_HANDLE_SIZE); xdr.xdrEncodeString(fromName); xdr.xdrEncodeOpaque(toParentFileHandle, FILE_HANDLE_SIZE); xdr.xdrEncodeString(toName); } }; XdrAble nfsResult = new NFSResult() { public void xdrDecode(XdrDecodingStream xdr) throws OncRpcException, IOException { } }; call(PROCEDURE_RENAME_FILE, nfsParameter, nfsResult); } public CreateDirectoryResult createDirectory(final byte[] parentFileHandle, final String name, final boolean[] permission, final int uid, final int gid, final int size, final Time lastAccessed, final Time lastModified) throws NFS2Exception, IOException { if (name.length() > MAX_NAME_LENGTH) { throw new NFS2Exception("The name is too long.The maximun length is " + MAX_NAME_LENGTH); } final int mode = createMode(permission) | 0x4000; NFSParameter nfsParameter = new NFSParameter() { public void xdrEncode(XdrEncodingStream xdr) throws OncRpcException, IOException { xdr.xdrEncodeOpaque(parentFileHandle, FILE_HANDLE_SIZE); xdr.xdrEncodeString(name); xdr.xdrEncodeInt(mode); xdr.xdrEncodeInt(uid); xdr.xdrEncodeInt(gid); xdr.xdrEncodeInt(size); xdrEncodeTime(xdr, lastAccessed); xdrEncodeTime(xdr, lastModified); } }; final CreateDirectoryResult result = new CreateDirectoryResult(); XdrAble nfsResult = new NFSResult() { public void xdrDecode(XdrDecodingStream xdr) throws OncRpcException, IOException { result.setFileHandle(xdr.xdrDecodeOpaque(NFS2Client.FILE_HANDLE_SIZE)); FileAttribute fileAttribute = new FileAttribute(); xdrFileAttributeDecode(xdr, fileAttribute); result.setFileAttribute(fileAttribute); } }; call(PROCEDURE_CREATE_DIRECTORY, nfsParameter, nfsResult); return result; } public CreateFileResult createFile(final byte[] parentFileHandle, final String name, final boolean[] permission, final int uid, final int gid, final int size, final Time lastAccessed, final Time lastModified) throws NFS2Exception, IOException { if (name.length() > MAX_NAME_LENGTH) { throw new NFS2Exception("The name is too long.The maximun length is " + MAX_NAME_LENGTH); } final int mode = createMode(permission) | 0x8000; NFSParameter nfsParameter = new NFSParameter() { public void xdrEncode(XdrEncodingStream xdr) throws OncRpcException, IOException { xdr.xdrEncodeOpaque(parentFileHandle, FILE_HANDLE_SIZE); xdr.xdrEncodeString(name); xdr.xdrEncodeInt(mode); xdr.xdrEncodeInt(uid); xdr.xdrEncodeInt(gid); xdr.xdrEncodeInt(size); xdrEncodeTime(xdr, lastAccessed); xdrEncodeTime(xdr, lastModified); } }; final CreateFileResult result = new CreateFileResult(); XdrAble nfsResult = new NFSResult() { public void xdrDecode(XdrDecodingStream xdr) throws OncRpcException, IOException { result.setFileHandle(xdr.xdrDecodeOpaque(NFS2Client.FILE_HANDLE_SIZE)); FileAttribute fileAttribute = new FileAttribute(); xdrFileAttributeDecode(xdr, fileAttribute); result.setFileAttribute(fileAttribute); } }; call(PROCEDURE_CREATE_FILE, nfsParameter, nfsResult); return result; } public FileAttribute writeFile(final byte[] fileHandle, final int offset, final byte[] buffer) throws NFS2Exception, IOException { return writeFile(fileHandle, offset, buffer, 0, buffer.length); } public FileAttribute writeFile(final byte[] fileHandle, final int offset, final byte[] buffer, final int bufferIndex, final int bufferCount) throws NFS2Exception, IOException { NFSParameter nfsParameter = new NFSParameter() { public void xdrEncode(XdrEncodingStream xdr) throws OncRpcException, IOException { xdr.xdrEncodeOpaque(fileHandle, FILE_HANDLE_SIZE); xdr.xdrEncodeInt(0); xdr.xdrEncodeInt(offset); xdr.xdrEncodeInt(0); // encode an array of bytes xdr.xdrEncodeInt(bufferCount); xdr.xdrEncodeOpaque(buffer, bufferIndex, bufferCount); } }; final FileAttribute fileAttribute = new FileAttribute(); XdrAble nfsResult = new NFSResult() { public void xdrDecode(XdrDecodingStream xdr) throws OncRpcException, IOException { xdrFileAttributeDecode(xdr, fileAttribute); } }; call(PROCEDURE_WRITE_FILE, nfsParameter, nfsResult); return fileAttribute; } public FileAttribute getAttribute(final byte[] fileHandle) throws NFS2Exception, IOException { XdrAble nfsParameter = new NFSParameter() { public void xdrEncode(XdrEncodingStream xdr) throws OncRpcException, IOException { xdr.xdrEncodeOpaque(fileHandle, FILE_HANDLE_SIZE); } }; final FileAttribute fileAttribute = new FileAttribute(); XdrAble nfsResult = new NFSResult() { public void xdrDecode(XdrDecodingStream xdr) throws OncRpcException, IOException { xdrFileAttributeDecode(xdr, fileAttribute); } }; call(PROCEDURE_GET_ATTRIBUTE, nfsParameter, nfsResult); return fileAttribute; } /** * Set the attributes for file. * * @param fileHandle file handle. * @param mode mode. * @param uid * @param gid * @param size * @param lastAccessed * @param lastModified * @return the FileAttribute set * @throws NFS2Exception * @throws IOException */ public FileAttribute setAttribute(final byte[] fileHandle, final int mode, final int uid, final int gid, final int size, final Time lastAccessed, final Time lastModified) throws NFS2Exception, IOException { XdrAble nfsParameter = new NFSParameter() { public void xdrEncode(XdrEncodingStream xdr) throws OncRpcException, IOException { xdr.xdrEncodeOpaque(fileHandle, FILE_HANDLE_SIZE); xdr.xdrEncodeInt(mode); xdr.xdrEncodeInt(uid); xdr.xdrEncodeInt(gid); xdr.xdrEncodeInt(size); xdrEncodeTime(xdr, lastAccessed); xdrEncodeTime(xdr, lastModified); } }; final FileAttribute fileAttribute = new FileAttribute(); XdrAble nfsResult = new NFSResult() { public void xdrDecode(XdrDecodingStream xdr) throws OncRpcException, IOException { xdrFileAttributeDecode(xdr, fileAttribute); } }; call(PROCEDURE_SET_ATTRIBUTE, nfsParameter, nfsResult); return fileAttribute; } public FileSystemAttribute getFileSystemAttribute(final byte[] fileHandle) throws NFS2Exception, IOException { XdrAble nfsParameter = new NFSParameter() { public void xdrEncode(XdrEncodingStream xdr) throws OncRpcException, IOException { xdr.xdrEncodeOpaque(fileHandle, FILE_HANDLE_SIZE); } }; final FileSystemAttribute fileSystemAttribute = new FileSystemAttribute(); XdrAble nfsResult = new NFSResult() { public void xdrDecode(XdrDecodingStream xdr) throws OncRpcException, IOException { fileSystemAttribute.setTransferSize(xdrDecodeUnsignedInt(xdr)); fileSystemAttribute.setBlockSize(xdrDecodeUnsignedInt(xdr)); fileSystemAttribute.setBlockCount(xdrDecodeUnsignedInt(xdr)); fileSystemAttribute.setFreeBlockCount(xdrDecodeUnsignedInt(xdr)); fileSystemAttribute.setAvailableBlockCount(xdrDecodeUnsignedInt(xdr)); } }; call(PROCEDURE_GET_FILE_SYSTEM_ATTRIBUTE, nfsParameter, nfsResult); return fileSystemAttribute; } private int createMode(boolean[] data) { int mode = 0; // owner if (data[0]) { mode |= 0x100; } if (data[1]) { mode |= 0x80; } if (data[2]) { mode |= 0x40; } // group if (data[3]) { mode |= 0x20; } if (data[4]) { mode |= 0x10; } if (data[5]) { mode |= 0x8; } // other if (data[6]) { mode |= 0x4; } if (data[7]) { mode |= 0x2; } if (data[8]) { mode |= 0x1; } return mode; } private void xdrEncodeTime(XdrEncodingStream xdrEncodingStream, Time time) throws OncRpcException, IOException { xdrEncodingStream.xdrEncodeInt(time.getSeconds()); xdrEncodingStream.xdrEncodeInt(time.getMicroSeconds()); } private void xdrDecodeTime(XdrDecodingStream xdrDecodingStream, Time time) throws OncRpcException, IOException { time.setSeconds(xdrDecodingStream.xdrDecodeInt()); time.setMicroSeconds(xdrDecodingStream.xdrDecodeInt()); } private void xdrFileAttributeDecode(XdrDecodingStream xdr, FileAttribute fileAttribute) throws OncRpcException, IOException { fileAttribute.setType(xdr.xdrDecodeInt()); fileAttribute.setMode(xdr.xdrDecodeInt()); fileAttribute.setNlink(xdr.xdrDecodeInt()); fileAttribute.setUid(xdr.xdrDecodeInt()); fileAttribute.setGid(xdr.xdrDecodeInt()); fileAttribute.setSize(xdr.xdrDecodeInt()); fileAttribute.setBlocksize(xdr.xdrDecodeInt()); fileAttribute.setRdev(xdr.xdrDecodeInt()); fileAttribute.setBlocks(xdr.xdrDecodeInt()); fileAttribute.setFsid(xdr.xdrDecodeInt()); fileAttribute.setFileId(xdr.xdrDecodeInt()); Time lastAccessedTime = new Time(); xdrDecodeTime(xdr, lastAccessedTime); fileAttribute.setLastAccessed(lastAccessedTime); Time lastModifiedTime = new Time(); xdrDecodeTime(xdr, lastModifiedTime); fileAttribute.setLastModified(lastModifiedTime); Time lastStatusChangedTime = new Time(); xdrDecodeTime(xdr, lastStatusChangedTime); fileAttribute.setLastStatusChanged(lastStatusChangedTime); } private long xdrDecodeUnsignedInt(XdrDecodingStream xdr) throws OncRpcException, IOException { byte[] buffer = new byte[4]; xdr.xdrDecodeOpaque(buffer); return ((buffer[0] & 0xFF) << 24 | (buffer[1] & 0xFF) << 16 | (buffer[2] & 0xFF) << 8 | (buffer[3] & 0xFF)); } private abstract class NFSParameter implements XdrAble { public void xdrDecode(XdrDecodingStream arg0) throws OncRpcException, IOException { } } private abstract class NFSResult implements XdrAble { public void xdrEncode(XdrEncodingStream arg0) throws OncRpcException, IOException { } } private class ResultWithCode implements XdrAble { private ResultCode resultCode; private XdrAble xdrAble; public ResultWithCode(XdrAble xdrAble) { this.xdrAble = xdrAble; } public void xdrEncode(XdrEncodingStream xdr) throws OncRpcException, IOException { } public void xdrDecode(XdrDecodingStream xdr) throws OncRpcException, IOException { resultCode = ResultCode.getResultCode(xdr.xdrDecodeInt()); if (resultCode == ResultCode.NFS_OK) { xdrAble.xdrDecode(xdr); } } public ResultCode getResultCode() { return resultCode; } } }