From 225f69de01053d86a1c24e5ab12976fcfdeb1db1 Mon Sep 17 00:00:00 2001 From: Gary Spencer Date: Fri, 5 Jan 2007 15:07:18 +0000 Subject: [PATCH] NFS server support added to filesystem server, includes mount and portmapper services plus an NFS v3 server. Not enabled or wired in yet. git-svn-id: https://svn.alfresco.com/repos/alfresco-enterprise/alfresco/HEAD/root@4742 c4b6b30b-aa2e-2d43-bbcb-ca4b014f7261 --- .../java/org/alfresco/filesys/NFSServer.java | 280 + .../server/DatagramSessionHandler.java | 414 ++ .../server/PacketHandlerInterface.java | 73 + .../filesys/server/PacketHandlerList.java | 98 + .../server/SessionHandlerInterface.java | 51 + .../filesys/server/SessionHandlerList.java | 164 + .../filesys/server/SocketPacketHandler.java | 155 + .../filesys/server/SocketSessionHandler.java | 329 ++ .../alfresco/filesys/server/SrvSession.java | 99 +- .../server/auth/AlfrescoRpcAuthenticator.java | 380 ++ .../server/config/ServerConfiguration.java | 37 +- .../server/filesys/FilesysTransaction.java | 112 + .../server/filesys/SymbolicLinkInterface.java | 39 + .../filesys/server/oncrpc/AuthType.java | 48 + .../oncrpc/DefaultRpcAuthenticator.java | 207 + .../MultiThreadedTcpRpcPacketHandler.java | 118 + .../MultiThreadedTcpRpcSessionHandler.java | 227 + .../MultiThreadedUdpRpcDatagramHandler.java | 420 ++ .../filesys/server/oncrpc/PortMapping.java | 145 + .../alfresco/filesys/server/oncrpc/Rpc.java | 104 + .../oncrpc/RpcAuthenticationException.java | 65 + .../server/oncrpc/RpcAuthenticator.java | 86 + .../filesys/server/oncrpc/RpcClient.java | 146 + .../server/oncrpc/RpcNetworkServer.java | 203 + .../filesys/server/oncrpc/RpcPacket.java | 1319 +++++ .../server/oncrpc/RpcPacketHandler.java | 38 + .../filesys/server/oncrpc/RpcPacketPool.java | 439 ++ .../filesys/server/oncrpc/RpcProcessor.java | 37 + .../server/oncrpc/RpcRequestQueue.java | 117 + .../server/oncrpc/RpcRequestThreadPool.java | 255 + .../filesys/server/oncrpc/TcpRpcClient.java | 97 + .../server/oncrpc/TcpRpcPacketHandler.java | 450 ++ .../server/oncrpc/TcpRpcSessionHandler.java | 206 + .../server/oncrpc/UdpRpcDatagramHandler.java | 134 + .../filesys/server/oncrpc/mount/Mount.java | 88 + .../server/oncrpc/mount/MountEntry.java | 81 + .../server/oncrpc/mount/MountEntryList.java | 154 + .../server/oncrpc/mount/MountServer.java | 885 +++ .../server/oncrpc/nfs/BadCookieException.java | 45 + .../server/oncrpc/nfs/BadHandleException.java | 45 + .../server/oncrpc/nfs/FileIdCache.java | 69 + .../filesys/server/oncrpc/nfs/NFS.java | 267 + .../filesys/server/oncrpc/nfs/NFSHandle.java | 417 ++ .../filesys/server/oncrpc/nfs/NFSServer.java | 5104 +++++++++++++++++ .../server/oncrpc/nfs/NFSSessionTable.java | 110 + .../server/oncrpc/nfs/NFSSrvSession.java | 471 ++ .../server/oncrpc/nfs/NetworkFileCache.java | 444 ++ .../server/oncrpc/nfs/SearchCache.java | 366 ++ .../server/oncrpc/nfs/ShareDetails.java | 94 + .../server/oncrpc/nfs/ShareDetailsHash.java | 99 + .../oncrpc/nfs/StaleHandleException.java | 47 + .../server/oncrpc/portmap/PortMapper.java | 61 + .../oncrpc/portmap/PortMapperServer.java | 551 ++ 53 files changed, 16444 insertions(+), 46 deletions(-) create mode 100644 source/java/org/alfresco/filesys/NFSServer.java create mode 100644 source/java/org/alfresco/filesys/server/DatagramSessionHandler.java create mode 100644 source/java/org/alfresco/filesys/server/PacketHandlerInterface.java create mode 100644 source/java/org/alfresco/filesys/server/PacketHandlerList.java create mode 100644 source/java/org/alfresco/filesys/server/SessionHandlerInterface.java create mode 100644 source/java/org/alfresco/filesys/server/SessionHandlerList.java create mode 100644 source/java/org/alfresco/filesys/server/SocketPacketHandler.java create mode 100644 source/java/org/alfresco/filesys/server/SocketSessionHandler.java create mode 100644 source/java/org/alfresco/filesys/server/auth/AlfrescoRpcAuthenticator.java create mode 100644 source/java/org/alfresco/filesys/server/filesys/FilesysTransaction.java create mode 100644 source/java/org/alfresco/filesys/server/filesys/SymbolicLinkInterface.java create mode 100644 source/java/org/alfresco/filesys/server/oncrpc/AuthType.java create mode 100644 source/java/org/alfresco/filesys/server/oncrpc/DefaultRpcAuthenticator.java create mode 100644 source/java/org/alfresco/filesys/server/oncrpc/MultiThreadedTcpRpcPacketHandler.java create mode 100644 source/java/org/alfresco/filesys/server/oncrpc/MultiThreadedTcpRpcSessionHandler.java create mode 100644 source/java/org/alfresco/filesys/server/oncrpc/MultiThreadedUdpRpcDatagramHandler.java create mode 100644 source/java/org/alfresco/filesys/server/oncrpc/PortMapping.java create mode 100644 source/java/org/alfresco/filesys/server/oncrpc/Rpc.java create mode 100644 source/java/org/alfresco/filesys/server/oncrpc/RpcAuthenticationException.java create mode 100644 source/java/org/alfresco/filesys/server/oncrpc/RpcAuthenticator.java create mode 100644 source/java/org/alfresco/filesys/server/oncrpc/RpcClient.java create mode 100644 source/java/org/alfresco/filesys/server/oncrpc/RpcNetworkServer.java create mode 100644 source/java/org/alfresco/filesys/server/oncrpc/RpcPacket.java create mode 100644 source/java/org/alfresco/filesys/server/oncrpc/RpcPacketHandler.java create mode 100644 source/java/org/alfresco/filesys/server/oncrpc/RpcPacketPool.java create mode 100644 source/java/org/alfresco/filesys/server/oncrpc/RpcProcessor.java create mode 100644 source/java/org/alfresco/filesys/server/oncrpc/RpcRequestQueue.java create mode 100644 source/java/org/alfresco/filesys/server/oncrpc/RpcRequestThreadPool.java create mode 100644 source/java/org/alfresco/filesys/server/oncrpc/TcpRpcClient.java create mode 100644 source/java/org/alfresco/filesys/server/oncrpc/TcpRpcPacketHandler.java create mode 100644 source/java/org/alfresco/filesys/server/oncrpc/TcpRpcSessionHandler.java create mode 100644 source/java/org/alfresco/filesys/server/oncrpc/UdpRpcDatagramHandler.java create mode 100644 source/java/org/alfresco/filesys/server/oncrpc/mount/Mount.java create mode 100644 source/java/org/alfresco/filesys/server/oncrpc/mount/MountEntry.java create mode 100644 source/java/org/alfresco/filesys/server/oncrpc/mount/MountEntryList.java create mode 100644 source/java/org/alfresco/filesys/server/oncrpc/mount/MountServer.java create mode 100644 source/java/org/alfresco/filesys/server/oncrpc/nfs/BadCookieException.java create mode 100644 source/java/org/alfresco/filesys/server/oncrpc/nfs/BadHandleException.java create mode 100644 source/java/org/alfresco/filesys/server/oncrpc/nfs/FileIdCache.java create mode 100644 source/java/org/alfresco/filesys/server/oncrpc/nfs/NFS.java create mode 100644 source/java/org/alfresco/filesys/server/oncrpc/nfs/NFSHandle.java create mode 100644 source/java/org/alfresco/filesys/server/oncrpc/nfs/NFSServer.java create mode 100644 source/java/org/alfresco/filesys/server/oncrpc/nfs/NFSSessionTable.java create mode 100644 source/java/org/alfresco/filesys/server/oncrpc/nfs/NFSSrvSession.java create mode 100644 source/java/org/alfresco/filesys/server/oncrpc/nfs/NetworkFileCache.java create mode 100644 source/java/org/alfresco/filesys/server/oncrpc/nfs/SearchCache.java create mode 100644 source/java/org/alfresco/filesys/server/oncrpc/nfs/ShareDetails.java create mode 100644 source/java/org/alfresco/filesys/server/oncrpc/nfs/ShareDetailsHash.java create mode 100644 source/java/org/alfresco/filesys/server/oncrpc/nfs/StaleHandleException.java create mode 100644 source/java/org/alfresco/filesys/server/oncrpc/portmap/PortMapper.java create mode 100644 source/java/org/alfresco/filesys/server/oncrpc/portmap/PortMapperServer.java diff --git a/source/java/org/alfresco/filesys/NFSServer.java b/source/java/org/alfresco/filesys/NFSServer.java new file mode 100644 index 0000000000..286aa3d0d9 --- /dev/null +++ b/source/java/org/alfresco/filesys/NFSServer.java @@ -0,0 +1,280 @@ +/* + * Copyright (C) 2006 Alfresco, Inc. + * + * Licensed under the Mozilla Public License version 1.1 + * with a permitted attribution clause. You may obtain a + * copy of the License at + * + * http://www.alfresco.org/legal/license.txt + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the + * License. + */ + +package org.alfresco.filesys; + +import java.io.IOException; +import java.io.PrintStream; +import java.net.SocketException; +import java.util.Vector; + +import org.alfresco.error.AlfrescoRuntimeException; +import org.alfresco.filesys.server.NetworkServer; +import org.alfresco.filesys.server.config.ServerConfiguration; +import org.alfresco.filesys.server.oncrpc.mount.MountServer; +import org.alfresco.filesys.server.oncrpc.portmap.PortMapperServer; +import org.alfresco.util.AbstractLifecycleBean; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.springframework.context.ApplicationContext; +import org.springframework.context.ApplicationEvent; +import org.springframework.context.support.ClassPathXmlApplicationContext; + +/** + * NFS Server Class + * + *

Create and start the various server components required to run the NFS server. + * + * @author GKSpencer + */ +public class NFSServer extends AbstractLifecycleBean +{ + // Debug logging + + private static final Log logger = LogFactory.getLog("org.alfresco.nfs.server"); + + // Server configuration + + private ServerConfiguration m_filesysConfig; + + // List of NFS server components + + private Vector m_serverList = new Vector(); + + /** + * Class constructor + * + * @param serverConfig ServerConfiguration + */ + public NFSServer(ServerConfiguration serverConfig) + { + m_filesysConfig = serverConfig; + } + + /** + * Return the server configuration + * + * @return ServerConfiguration + */ + public final ServerConfiguration getConfiguration() + { + return m_filesysConfig; + } + + /** + * Check if the server is started/enabled + * + * @return Returns true if the server started up without any errors + */ + public boolean isStarted() + { + return (m_filesysConfig != null && m_filesysConfig.isNFSServerEnabled()); + } + + /** + * Start the NFS server components + * + * @exception SocketException If a network error occurs + * @exception IOException If an I/O error occurs + */ + public final void startServer() throws SocketException, IOException + { + try + { + // Create the NFS, mount and portmapper servers, if enabled + + if (m_filesysConfig.isNFSServerEnabled()) + { + // Create the portmapper server, if enabled + + if (m_filesysConfig.hasNFSPortMapper()) + m_serverList.add(new PortMapperServer(m_filesysConfig)); + + // Create the mount and main NFS servers + + m_serverList.add(new MountServer(m_filesysConfig)); + m_serverList.add(new org.alfresco.filesys.server.oncrpc.nfs.NFSServer(m_filesysConfig)); + + // Add the servers to the configuration + + for (NetworkServer server : m_serverList) + { + m_filesysConfig.addServer(server); + } + } + + // Start the server(s) + + for (NetworkServer server : m_serverList) + { + if (logger.isInfoEnabled()) + logger.info("Starting server " + server.getProtocolName() + " ..."); + + // Start the server + + server.startServer(); + } + } + catch (Throwable e) + { + m_filesysConfig = null; + throw new AlfrescoRuntimeException("Failed to start NFS Server", e); + } + } + + /** + * Stop the NFS server components + */ + public final void stopServer() + { + if (m_filesysConfig == null) + { + // initialisation failed + return; + } + + // Shutdown the NFS server components, in reverse order + + for ( int i = m_serverList.size() - 1; i >= 0; i--) + { + // Get the current server from the list + + NetworkServer server = m_serverList.get( i); + if (logger.isInfoEnabled()) + logger.info("Shutting server " + server.getProtocolName() + " ..."); + + // Stop the server + + server.shutdownServer(false); + + // Remove the server from the global list + + getConfiguration().removeServer(server.getProtocolName()); + } + + // Clear the server list and configuration + + m_serverList.clear(); + m_filesysConfig = null; + } + + /** + * Runs the NFS server directly + * + * @param args String[] + */ + public static void main(String[] args) + { + PrintStream out = System.out; + + out.println("NFS Server Test"); + out.println("----------------"); + + try + { + // Create the configuration service in the same way that Spring creates it + + ApplicationContext ctx = new ClassPathXmlApplicationContext("alfresco/application-context.xml"); + + // Get the NFS server bean + + NFSServer server = (NFSServer) ctx.getBean("nfsServer"); + if (server == null) + { + throw new AlfrescoRuntimeException("Server bean 'nfsServer' not defined"); + } + + // Stop the FTP server, if running + + server.getConfiguration().setFTPServerEnabled(false); + + NetworkServer srv = server.getConfiguration().findServer("FTP"); + if ( srv != null) + srv.shutdownServer(true); + + // Stop the CIFS server, if running + + server.getConfiguration().setSMBServerEnabled(false); + + srv = server.getConfiguration().findServer("SMB"); + if ( srv != null) + srv.shutdownServer(true); + + // Only wait for shutdown if the NFS server is enabled + + if ( server.getConfiguration().isNFSServerEnabled()) + { + + // NFS server should have automatically started + // Wait for shutdown via the console + + out.println("Enter 'x' to shutdown ..."); + boolean shutdown = false; + + // Wait while the server runs, user may stop the server by typing a key + + while (shutdown == false) + { + + // Wait for the user to enter the shutdown key + + int ch = System.in.read(); + + if (ch == 'x' || ch == 'X') + shutdown = true; + + synchronized (server) + { + server.wait(20); + } + } + + // Stop the server + + server.stopServer(); + } + } + catch (Exception ex) + { + ex.printStackTrace(); + } + System.exit(1); + } + + @Override + protected void onBootstrap(ApplicationEvent event) + { + try + { + startServer(); + } + catch (SocketException e) + { + throw new AlfrescoRuntimeException("Failed to start NFS server", e); + } + catch (IOException e) + { + throw new AlfrescoRuntimeException("Failed to start NFS server", e); + } + } + + @Override + protected void onShutdown(ApplicationEvent event) + { + stopServer(); + } +} diff --git a/source/java/org/alfresco/filesys/server/DatagramSessionHandler.java b/source/java/org/alfresco/filesys/server/DatagramSessionHandler.java new file mode 100644 index 0000000000..3f2c412b99 --- /dev/null +++ b/source/java/org/alfresco/filesys/server/DatagramSessionHandler.java @@ -0,0 +1,414 @@ +/* + * Copyright (C) 2005 Alfresco, Inc. + * + * Licensed under the Mozilla Public License version 1.1 + * with a permitted attribution clause. You may obtain a + * copy of the License at + * + * http://www.alfresco.org/legal/license.txt + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the + * License. + */ +package org.alfresco.filesys.server; + +import java.io.*; +import java.net.*; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +/** + * Datagram Session Handler Class + * + *

Implementation of a session handler that uses a Java datagram socket to listen for incoming requests. + * + * @author GKSpencer + */ +public abstract class DatagramSessionHandler implements SessionHandlerInterface, Runnable { + + // Debug logging + + protected static final Log logger = LogFactory.getLog(DatagramSessionHandler.class); + + // Server that the handler is associated with + + private NetworkServer m_server; + + // Address/port to use + + private int m_port; + + private InetAddress m_bindAddr; + + // Datagram socket to listen for incoming requests + + private DatagramSocket m_srvSock; + + // Maximum datagram size + + private int m_maxDgramSize; + + // Session id + + private int m_sessId; + + // Session handler name, protocol name + + private String m_name; + + private String m_protocol; + + // Shutdown request flag + + private boolean m_shutdown; + + /** + * Class constructor + * + * @param name String + * @param protocol String + * @param server NetworkServer + * @param addr InetAddress + * @param port int + */ + protected DatagramSessionHandler(String name, String protocol, NetworkServer server, InetAddress addr, int port) + { + m_name = name; + m_protocol = protocol; + m_server = server; + + m_bindAddr = addr; + m_port = port; + } + + /** + * Return the maximum datagram size allowed + * + * @return int + */ + public final int getMaximumDatagramSize() + { + return m_maxDgramSize; + } + + /** + * Return the session handler name + * + * @return String + */ + public final String getHandlerName() + { + return m_name; + } + + /** + * Return the short protocol name + * + * @return String + */ + public final String getProtocolName() + { + return m_protocol; + } + + /** + * Check if the server should bind to a specific network address + * + * @return boolean + */ + public final boolean hasBindAddress() + { + return m_bindAddr != null ? true : false; + } + + /** + * Return the network address that the server should bind to + * + * @return InetAddress + */ + public final InetAddress getBindAddres() + { + return m_bindAddr; + } + + /** + * Return the port that the server should bind to + * + * @return int + */ + public final int getPort() + { + return m_port; + } + + /** + * Clear the shutdown flag + */ + protected final void clearShutdown() + { + m_shutdown = false; + } + + /** + * Determine if the shutdown flag has been set + * + * @return boolean + */ + protected final boolean hasShutdown() + { + return m_shutdown; + } + + /** + * Get the next available session id + * + * @return int + */ + protected synchronized int getNextSessionId() + { + return m_sessId++; + } + + /** + * Set the local port that the datagram handler is using + * + * @param port int + */ + protected final void setPort(int port) + { + m_port = port; + } + + /** + * Return the datagrma socket + * + * @return DatagramSocket + */ + protected final DatagramSocket getDatagramSocket() + { + return m_srvSock; + } + + /** + * Initialize the session handler + * + * @param server NetworkServer + */ + public void initializeSessionHandler(NetworkServer server) + throws IOException + { + + // Open the server socket + + if (hasBindAddress()) + m_srvSock = new DatagramSocket(getPort(), getBindAddres()); + else + m_srvSock = new DatagramSocket(getPort()); + + // Set the datagram receive buffer size + + if (m_srvSock.getReceiveBufferSize() < getMaximumDatagramSize()) + m_srvSock.setReceiveBufferSize(getMaximumDatagramSize()); + + // Set the allocated port + + if (getPort() == 0) + setPort(m_srvSock.getLocalPort()); + + // DEBUG + + if (logger.isDebugEnabled()) + { + String bindAddr = hasBindAddress() ? getBindAddres().getHostAddress() : "ALL"; + logger.debug("[" + getProtocolName() + "] Binding " + getHandlerName() + " session handler to address : " + bindAddr); + } + } + + /** + * Close the session handler + * + * @param server NetworkServer + */ + public void closeSessionHandler(NetworkServer server) + { + + // Request the main listener thread shutdown + + m_shutdown = true; + + // Close the server socket to release any pending listen + + if (m_srvSock != null) + m_srvSock.close(); + } + + /** + * Set the maximum datagram size + * + * @param maxSize int + */ + protected final void setMaximumDatagramSize(int maxSize) + { + m_maxDgramSize = maxSize; + } + + /** + * Process a received datagram packet + * + * @param pkt DatagramPacket + * @return boolean Return true to reuse the DatagramPacket, else false to allocate a new packet + * @exception IOException + */ + protected abstract boolean processDatagram(DatagramPacket pkt) + throws IOException; + + /** + * Allocate a buffer for the datagram receive + * + * @param bufSize int + * @return byte[] + */ + protected byte[] allocateBuffer(int bufSize) + { + + // Allocate a buffer for the datagram + + return new byte[bufSize]; + } + + /** + * Send a datagram + * + * @param pkt DatagramPacket + * @exception IOException + */ + protected void sendDatagram(DatagramPacket pkt) + throws IOException + { + + // Check if the datagram socket is valid + + if (m_srvSock == null) + throw new IOException("Datagram socket is null"); + + // Default implementation sends the datagram immediately via the datagram socket + + m_srvSock.send(pkt); + } + + /** + * Socket listener thread + */ + public void run() + { + + try + { + + // Set the thread name + + Thread.currentThread().setName(getProtocolName() + "_" + getHandlerName()); + + // Clear the shutdown flag + + clearShutdown(); + + // Debug + + if (logger.isDebugEnabled()) + logger.debug("[" + getProtocolName() + "] Waiting for datagrams ..."); + + // Wait for incoming connection requests + + byte[] buf = null; + DatagramPacket pkt = null; + boolean reusePkt = false; + + while (hasShutdown() == false) + { + + // Allocate the datagram buffer and packet + + if (reusePkt == false) + { + + // Allocate a new datagram packet and buffer + + buf = allocateBuffer(getMaximumDatagramSize()); + if (pkt == null) + { + + // Allocate the datagram packet + + pkt = new DatagramPacket(buf, buf.length); + } else + { + + // Re-use the existing datagram packet + + pkt.setData(buf, 0, buf.length); + } + } else + { + + // Re-use the existing datagram packet and buffer. + // + // Reset to use our buffer as the datagram packet may have been reused to send a response. + + pkt.setData(buf, 0, buf.length); + } + + // Wait for an incoming datagram + + m_srvSock.receive(pkt); + + try + { + + // Process the datagram packet + + reusePkt = processDatagram(pkt); + } catch (Exception ex) + { + + // Debug + + if (logger.isDebugEnabled()) + logger.debug("[" + getProtocolName() + "] Error processing datagram, " + ex.toString()); + } + } + } catch (SocketException ex) + { + + // Do not report an error if the server has shutdown, closing the server socket + // causes an exception to be thrown. + + if (hasShutdown() == false) + { + logger.debug("[" + getProtocolName() + "] Socket error : " + ex.toString()); + logger.debug(ex); + } + } catch (Exception ex) + { + + // Do not report an error if the server has shutdown, closing the server socket + // causes an exception to be thrown. + + if (hasShutdown() == false) + { + logger.debug("[" + getProtocolName() + "] Server error : " + ex.toString()); + logger.debug(ex); + } + } + + // Debug + + if (logger.isDebugEnabled()) + logger.debug("[" + getProtocolName() + "] " + getHandlerName() + " session handler closed"); + } +} diff --git a/source/java/org/alfresco/filesys/server/PacketHandlerInterface.java b/source/java/org/alfresco/filesys/server/PacketHandlerInterface.java new file mode 100644 index 0000000000..4250b9275c --- /dev/null +++ b/source/java/org/alfresco/filesys/server/PacketHandlerInterface.java @@ -0,0 +1,73 @@ +/* + * Copyright (C) 2005 Alfresco, Inc. + * + * Licensed under the Mozilla Public License version 1.1 + * with a permitted attribution clause. You may obtain a + * copy of the License at + * + * http://www.alfresco.org/legal/license.txt + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the + * License. + */ +package org.alfresco.filesys.server; + +import java.io.*; + +/** + * Packet Handler Interface + * + *

Implemented by classes that read/write request packets to a network connection. + * + * @author GKSpencer + */ +public interface PacketHandlerInterface { + + /** + * Return the protocol name + * + * @return String + */ + public String getProtocolName(); + + /** + * Return the number of bytes available for reading without blocking + * + * @return int + * @exception IOException + */ + public int availableBytes() + throws IOException; + + /** + * Read a packet of data + * + * @param pkt byte[] + * @param offset int + * @param maxLen int + * @return int + * @exception IOException + */ + public int readPacket(byte[] pkt, int offset, int maxLen) + throws IOException; + + /** + * Write a packet of data + * + * @param pkt byte[] + * @param offset int + * @param len int + * @exception IOException + */ + public void writePacket(byte[] pkt, int offset, int len) + throws IOException; + + /** + * Close the packet handler + */ + public void closePacketHandler(); +} diff --git a/source/java/org/alfresco/filesys/server/PacketHandlerList.java b/source/java/org/alfresco/filesys/server/PacketHandlerList.java new file mode 100644 index 0000000000..c9bbddbec8 --- /dev/null +++ b/source/java/org/alfresco/filesys/server/PacketHandlerList.java @@ -0,0 +1,98 @@ +/* + * Copyright (C) 2005 Alfresco, Inc. + * + * Licensed under the Mozilla Public License version 1.1 + * with a permitted attribution clause. You may obtain a + * copy of the License at + * + * http://www.alfresco.org/legal/license.txt + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the + * License. + */ +package org.alfresco.filesys.server; + +import java.util.*; + +/** + * Packet Handler List Class + * + * @author GKSpencer + */ +public class PacketHandlerList { + + // List of session handlers + + private Vector m_handlers; + + /** + * Default constructor + */ + public PacketHandlerList() { + m_handlers = new Vector(); + } + + /** + * Add a handler to the list + * + * @param handler PacketHandlerInterface + */ + public final void addHandler(PacketHandlerInterface handler) { + m_handlers.addElement(handler); + } + + /** + * Return the number of handlers in the list + * + * @return int + */ + public final int numberOfHandlers() { + return m_handlers.size(); + } + + /** + * Return the specified handler + * + * @param idx int + * @return PacketHandlerInterface + */ + public final PacketHandlerInterface getHandlerAt(int idx) { + + // Range check the index + + if (idx < 0 || idx >= m_handlers.size()) + return null; + return (PacketHandlerInterface) m_handlers.elementAt(idx); + } + + /** + * Remove a handler from the list + * + * @param idx int + * @return PacketHandlerInterface + */ + public final PacketHandlerInterface remoteHandler(int idx) { + + // Range check the index + + if (idx < 0 || idx >= m_handlers.size()) + return null; + + // Remove the handler, and return it + + PacketHandlerInterface handler = (PacketHandlerInterface) m_handlers.elementAt(idx); + m_handlers.removeElementAt(idx); + return handler; + } + + /** + * Remove all handlers from the list + */ + public final void removeAllHandlers() { + m_handlers.removeAllElements(); + } +} diff --git a/source/java/org/alfresco/filesys/server/SessionHandlerInterface.java b/source/java/org/alfresco/filesys/server/SessionHandlerInterface.java new file mode 100644 index 0000000000..ebaf8f9764 --- /dev/null +++ b/source/java/org/alfresco/filesys/server/SessionHandlerInterface.java @@ -0,0 +1,51 @@ +/* + * Copyright (C) 2005 Alfresco, Inc. + * + * Licensed under the Mozilla Public License version 1.1 + * with a permitted attribution clause. You may obtain a + * copy of the License at + * + * http://www.alfresco.org/legal/license.txt + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the + * License. + */ +package org.alfresco.filesys.server; + +import java.io.*; + +/** + * Session Handler Interface + * + *

Implemented by classes that wait for an incoming session request. + * + * @author GKSpencer + */ +public interface SessionHandlerInterface +{ + /** + * Return the protocol name + * + * @return String + */ + public String getHandlerName(); + + /** + * Initialize the session handler + * + * @param server + * NetworkServer + * @exception IOException + */ + public void initializeSessionHandler(NetworkServer server) + throws IOException; + + /** + * Close the session handler + */ + public void closeSessionHandler(NetworkServer server); +} diff --git a/source/java/org/alfresco/filesys/server/SessionHandlerList.java b/source/java/org/alfresco/filesys/server/SessionHandlerList.java new file mode 100644 index 0000000000..f5810528b3 --- /dev/null +++ b/source/java/org/alfresco/filesys/server/SessionHandlerList.java @@ -0,0 +1,164 @@ +/* + * Copyright (C) 2005 Alfresco, Inc. + * + * Licensed under the Mozilla Public License version 1.1 + * with a permitted attribution clause. You may obtain a + * copy of the License at + * + * http://www.alfresco.org/legal/license.txt + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the + * License. + */ +package org.alfresco.filesys.server; + +import java.util.*; + +/** + * Session Handler List Class + * + * @author GKSpencer + */ +public class SessionHandlerList { + + // List of session handlers + + private Vector m_handlers; + + /** + * Default constructor + */ + public SessionHandlerList() + { + m_handlers = new Vector(); + } + + /** + * Add a handler to the list + * + * @param handler SessionHandlerInterface + */ + public final void addHandler(SessionHandlerInterface handler) + { + m_handlers.addElement(handler); + } + + /** + * Return the number of handlers in the list + * + * @return int + */ + public final int numberOfHandlers() + { + return m_handlers.size(); + } + + /** + * Return the specified handler + * + * @param idx int + * @return SessionHandlerInterface + */ + public final SessionHandlerInterface getHandlerAt(int idx) + { + + // Range check the index + + if (idx < 0 || idx >= m_handlers.size()) + return null; + return (SessionHandlerInterface) m_handlers.elementAt(idx); + } + + /** + * Find the required handler by name + * + * @param name String + * @return SessionHandlerInterface + */ + public final SessionHandlerInterface findHandler(String name) + { + + // Search for the required handler + + for (int i = 0; i < m_handlers.size(); i++) + { + + // Get the current handler + + SessionHandlerInterface handler = (SessionHandlerInterface) m_handlers.elementAt(i); + + if (handler.getHandlerName().equals(name)) + return handler; + } + + // Handler not found + + return null; + } + + /** + * Remove a handler from the list + * + * @param idx int + * @return SessionHandlerInterface + */ + public final SessionHandlerInterface remoteHandler(int idx) + { + + // Range check the index + + if (idx < 0 || idx >= m_handlers.size()) + return null; + + // Remove the handler, and return it + + SessionHandlerInterface handler = (SessionHandlerInterface) m_handlers.elementAt(idx); + m_handlers.removeElementAt(idx); + return handler; + } + + /** + * Remove a handler from the list + * + * @param name String + * @return SessionHandlerInterface + */ + public final SessionHandlerInterface remoteHandler(String name) + { + + // Search for the required handler + + for (int i = 0; i < m_handlers.size(); i++) + { + + // Get the current handler + + SessionHandlerInterface handler = (SessionHandlerInterface) m_handlers.elementAt(i); + + if (handler.getHandlerName().equals(name)) + { + + // Remove the handler from the list + + m_handlers.removeElementAt(i); + return handler; + } + } + + // Handler not found + + return null; + } + + /** + * Remove all handlers from the list + */ + public final void removeAllHandlers() + { + m_handlers.removeAllElements(); + } +} diff --git a/source/java/org/alfresco/filesys/server/SocketPacketHandler.java b/source/java/org/alfresco/filesys/server/SocketPacketHandler.java new file mode 100644 index 0000000000..8dd18e57a6 --- /dev/null +++ b/source/java/org/alfresco/filesys/server/SocketPacketHandler.java @@ -0,0 +1,155 @@ +/* + * Copyright (C) 2005 Alfresco, Inc. + * + * Licensed under the Mozilla Public License version 1.1 + * with a permitted attribution clause. You may obtain a + * copy of the License at + * + * http://www.alfresco.org/legal/license.txt + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the + * License. + */ +package org.alfresco.filesys.server; + +import java.io.*; +import java.net.*; + +/** + * Java Socket Based Packet Handler Class + * + * @author GKSpencer + */ +public abstract class SocketPacketHandler implements PacketHandlerInterface { + + // Socket to read/write to/from + + private Socket m_socket; + + // Input/output streams for receiving/sending data + + private DataInputStream m_in; + private DataOutputStream m_out; + + /** + * Class constructor + * + * @param socket Socket + * @param protocol String + * @exception IOException + */ + protected SocketPacketHandler(Socket socket) throws IOException { + m_socket = socket; + + // Open the input/output streams + + m_in = new DataInputStream(m_socket.getInputStream()); + m_out = new DataOutputStream(m_socket.getOutputStream()); + } + + /** + * Return the protocol name + * + * @return String + */ + public abstract String getProtocolName(); + + /** + * Return the number of bytes available for reading without blocking + * + * @return int + * @exception IOException + */ + public int availableBytes() throws IOException { + if (m_in != null) + return m_in.available(); + return 0; + } + + /** + * Read a packet of data + * + * @param pkt byte[] + * @param offset int + * @param maxLen int + * @return int + * @exception IOException + */ + public int readPacket(byte[] pkt, int offset, int maxLen) throws IOException { + + // Read a packet of data + + if (m_in != null) + return m_in.read(pkt, offset, maxLen); + return 0; + } + + /** + * Write a packet of data + * + * @param pkt byte[] + * @param offset int + * @param len int + * @exception IOException + */ + public void writePacket(byte[] pkt, int offset, int len) throws IOException { + + // Output the raw packet + + if (m_out != null) { + + synchronized (m_out) { + m_out.write(pkt, offset, len); + } + } + } + + /** + * Close the packet handler + */ + public void closePacketHandler() { + + // Close the socket + + if (m_socket != null) { + try { + m_socket.close(); + } catch (Exception ex) { + } + m_socket = null; + } + + // Close the input stream + + if (m_in != null) { + try { + m_in.close(); + } catch (Exception ex) { + } + m_in = null; + } + + // Close the output stream + + if (m_out != null) { + try { + m_out.close(); + } catch (Exception ex) { + } + m_out = null; + } + } + + /** + * Return the socket + * + * @return Socket + */ + protected final Socket getSocket() { + return m_socket; + } +} diff --git a/source/java/org/alfresco/filesys/server/SocketSessionHandler.java b/source/java/org/alfresco/filesys/server/SocketSessionHandler.java new file mode 100644 index 0000000000..edaeb8dba1 --- /dev/null +++ b/source/java/org/alfresco/filesys/server/SocketSessionHandler.java @@ -0,0 +1,329 @@ +/* + * Copyright (C) 2005 Alfresco, Inc. + * + * Licensed under the Mozilla Public License version 1.1 + * with a permitted attribution clause. You may obtain a + * copy of the License at + * + * http://www.alfresco.org/legal/license.txt + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the + * License. + */ +package org.alfresco.filesys.server; + +import java.io.*; +import java.net.*; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +/** + * Socket Session Handler Class + * + *

Implementation of a session handler that uses a Java socket to listen for incoming session requests. + * + * @author GKSpencer + */ +public abstract class SocketSessionHandler implements SessionHandlerInterface, Runnable { + + // Debug logging + + protected static final Log logger = LogFactory.getLog(SocketSessionHandler.class); + + // Constants + // + // Default socket listen back log limit + + public static final int ListenBacklog = 10; + + // Server that the handler is associated with + + private NetworkServer m_server; + + // Address/port to use + + private int m_port; + private InetAddress m_bindAddr; + + // Socket listen back log limit + + private int m_backLog = ListenBacklog; + + // Server socket to listen for incoming connections + + private ServerSocket m_srvSock; + + // Session id + + private int m_sessId; + + // Session handler name, protocol name + + private String m_name; + private String m_protocol; + + // Shutdown request flag + + private boolean m_shutdown; + + // Debug enable + + private boolean m_debug; + + /** + * Class constructor + * + * @param name String + * @param protocol String + * @param server NetworkServer + * @param addr InetAddress + * @param port int + */ + public SocketSessionHandler(String name, String protocol, NetworkServer server, InetAddress addr, int port) { + m_name = name; + m_protocol = protocol; + m_server = server; + + m_bindAddr = addr; + m_port = port; + } + + /** + * Return the session handler name + * + * @return String + */ + public final String getHandlerName() { + return m_name; + } + + /** + * Return the short protocol name + * + * @return String + */ + public final String getProtocolName() { + return m_protocol; + } + + /** + * Check if the server should bind to a specific network address + * + * @return boolean + */ + public final boolean hasBindAddress() { + return m_bindAddr != null ? true : false; + } + + /** + * Return the network address that the server should bind to + * + * @return InetAddress + */ + public final InetAddress getBindAddress() { + return m_bindAddr; + } + + /** + * Return the port that the server should bind to + * + * @return int + */ + public final int getPort() { + return m_port; + } + + /** + * Return the socket listen backlog limit + * + * @return int + */ + public final int getListenBacklog() { + return m_backLog; + } + + /** + * Determine if debug output is enabled + * + * @return boolean + */ + public final boolean hasDebug() { + return m_debug; + } + + /** + * Clear the shutdown flag + */ + protected final void clearShutdown() { + m_shutdown = false; + } + + /** + * Determine if the shutdown flag has been set + * + * @return boolean + */ + protected final boolean hasShutdown() { + return m_shutdown; + } + + /** + * Get the next available session id + * + * @return int + */ + protected synchronized int getNextSessionId() { + return m_sessId++; + } + + /** + * Enable/disable debug output + * + * @param dbg boolean + */ + public final void setDebug(boolean dbg) { + m_debug = dbg; + } + + /** + * Set the local port that the session handler is using + * + * @param port int + */ + protected final void setPort(int port) { + m_port = port; + } + + /** + * Initialize the session handler + * + * @param server NetworkServer + */ + public void initializeSessionHandler(NetworkServer server) throws IOException { + + // Open the server socket + + if (hasBindAddress()) + m_srvSock = new ServerSocket(getPort(), getListenBacklog(), getBindAddress()); + else + m_srvSock = new ServerSocket(getPort(), getListenBacklog()); + + // Set the allocated port + + if (getPort() == 0) + setPort(m_srvSock.getLocalPort()); + + // DEBUG + + if (logger.isDebugEnabled()) { + String bindAddr = hasBindAddress() ? getBindAddress().getHostAddress() : "ALL"; + logger.debug("[" + getProtocolName() + "] Binding " + getHandlerName() + " session handler to address : " + bindAddr); + } + } + + /** + * Close the session handler + * + * @param server NetworkServer + */ + public void closeSessionHandler(NetworkServer server) { + + // Request the main listener thread shutdown + + m_shutdown = true; + + try { + + // Close the server socket to release any pending listen + + if (m_srvSock != null) + m_srvSock.close(); + } catch (SocketException ex) { + } catch (Exception ex) { + } + } + + /** + * Accept a new connection on the specified socket + * + * @param sock Socket + */ + protected abstract void acceptConnection(Socket sock); + + /** + * Socket listener thread + */ + public void run() { + + try { + + // Clear the shutdown flag + + clearShutdown(); + + // Wait for incoming connection requests + + while (hasShutdown() == false) { + + // Debug + + if (logger.isDebugEnabled()) + logger.debug("[" + getProtocolName() + "] Waiting for session request ..."); + + // Wait for a connection + + Socket sessSock = m_srvSock.accept(); + + // Debug + + if (logger.isDebugEnabled()) + logger.debug("[" + getProtocolName() + "] Session request received from " + + sessSock.getInetAddress().getHostAddress()); + + try { + + // Process the new connection request + + acceptConnection(sessSock); + } + catch (Exception ex) { + + // Debug + + if (logger.isDebugEnabled()) + logger.debug("[" + getProtocolName() + "] Failed to create session, " + ex.toString()); + } + } + } + catch (SocketException ex) { + + // Do not report an error if the server has shutdown, closing the server socket + // causes an exception to be thrown. + + if (hasShutdown() == false) { + logger.debug("[" + getProtocolName() + "] Socket error : " + ex.toString()); + logger.debug(ex); + } + } + catch (Exception ex) { + + // Do not report an error if the server has shutdown, closing the server socket + // causes an exception to be thrown. + + if (hasShutdown() == false) { + logger.debug("[" + getProtocolName() + "] Server error : " + ex.toString()); + logger.debug(ex); + } + } + + // Debug + + if (logger.isDebugEnabled()) + logger.debug("[" + getProtocolName() + "] " + getHandlerName() + " session handler closed"); + } +} diff --git a/source/java/org/alfresco/filesys/server/SrvSession.java b/source/java/org/alfresco/filesys/server/SrvSession.java index 662da219b3..c20d316104 100644 --- a/source/java/org/alfresco/filesys/server/SrvSession.java +++ b/source/java/org/alfresco/filesys/server/SrvSession.java @@ -27,6 +27,7 @@ import org.alfresco.filesys.server.auth.AuthContext; import org.alfresco.filesys.server.auth.ClientInfo; import org.alfresco.filesys.server.core.SharedDevice; import org.alfresco.filesys.server.core.SharedDeviceList; +import org.alfresco.filesys.server.filesys.FilesysTransaction; import org.alfresco.service.transaction.TransactionService; /** @@ -64,7 +65,6 @@ public abstract class SrvSession // Debug flags for this session private int m_debug; - private String m_dbgPrefix; // Session shutdown flag @@ -92,8 +92,10 @@ public abstract class SrvSession // Active transaction and read/write flag - private UserTransaction m_transaction; - private boolean m_readOnlyTrans; + private ThreadLocal m_tx = new ThreadLocal(); + +// UserTransaction m_transaction; +// private boolean m_readOnlyTrans; // Request and transaction counts @@ -373,16 +375,6 @@ public abstract class SrvSession m_debug = flgs; } - /** - * Set the debug output prefix for this session - * - * @param prefix String - */ - public final void setDebugPrefix(String prefix) - { - m_dbgPrefix = prefix; - } - /** * Set the logged on/validated status for the session * @@ -507,22 +499,34 @@ public abstract class SrvSession { boolean created = false; + // Get the filesystem transaction + + FilesysTransaction filesysTx = m_tx.get(); + if ( filesysTx == null) + { + filesysTx = new FilesysTransaction(); + m_tx.set( filesysTx); + } + // If there is an active transaction check that it is the required type - if ( m_transaction != null) + if ( filesysTx.hasTransaction()) { + // Get the active transaction + + UserTransaction tx = filesysTx.getTransaction(); + // Check if the current transaction is marked for rollback try { - - if ( m_transaction.getStatus() == Status.STATUS_MARKED_ROLLBACK || - m_transaction.getStatus() == Status.STATUS_ROLLEDBACK || - m_transaction.getStatus() == Status.STATUS_ROLLING_BACK) + if ( tx.getStatus() == Status.STATUS_MARKED_ROLLBACK || + tx.getStatus() == Status.STATUS_ROLLEDBACK || + tx.getStatus() == Status.STATUS_ROLLING_BACK) { // Rollback the current transaction - m_transaction.rollback(); + tx.rollback(); } } catch ( SystemException ex) @@ -531,13 +535,13 @@ public abstract class SrvSession // Check if the transaction is a write transaction, if write has been requested - if ( readOnly == false && m_readOnlyTrans == true) + if ( readOnly == false && filesysTx.isReadOnly() == true) { // Commit the read-only transaction try { - m_transaction.commit(); + tx.commit(); m_transConvCount++; } catch ( Exception ex) @@ -548,24 +552,25 @@ public abstract class SrvSession { // Clear the active transaction - m_transaction = null; + filesysTx.clearTransaction(); } } } // Create the transaction - if ( m_transaction == null) + if ( filesysTx.hasTransaction() == false) { try { - m_transaction = transService.getUserTransaction(readOnly); - m_transaction.begin(); + UserTransaction userTrans = transService.getUserTransaction(readOnly); + userTrans.begin(); created = true; + + // Store the transaction - m_readOnlyTrans = readOnly; - + filesysTx.setTransaction( userTrans, readOnly); m_transCount++; } catch (Exception ex) @@ -585,25 +590,33 @@ public abstract class SrvSession public final void endTransaction() throws AlfrescoRuntimeException { + // Get the filesystem transaction + + FilesysTransaction filesysTx = m_tx.get(); + // Check if there is an active transaction - if ( m_transaction != null) + if ( filesysTx != null && filesysTx.hasTransaction()) { + // Get the active transaction + + UserTransaction tx = filesysTx.getTransaction(); + try { // Commit or rollback the transaction - if ( m_transaction.getStatus() == Status.STATUS_MARKED_ROLLBACK) + if ( tx.getStatus() == Status.STATUS_MARKED_ROLLBACK) { // Transaction is marked for rollback - m_transaction.rollback(); + tx.rollback(); } else { // Commit the transaction - m_transaction.commit(); + tx.commit(); } } catch ( Exception ex) @@ -614,7 +627,7 @@ public abstract class SrvSession { // Clear the current transaction - m_transaction = null; + filesysTx.clearTransaction(); } } @@ -626,7 +639,12 @@ public abstract class SrvSession */ public final boolean hasUserTransaction() { - return m_transaction != null ? true : false; + // Get the filesystem transaction + + FilesysTransaction filesysTx = m_tx.get(); + if ( filesysTx != null) + return filesysTx.hasTransaction(); + return false; } /** @@ -636,8 +654,17 @@ public abstract class SrvSession */ public final UserTransaction getUserTransaction() { - UserTransaction trans = m_transaction; - m_transaction = null; - return trans; + // Get the filesystem transaction + + UserTransaction userTrans = null; + FilesysTransaction filesysTx = m_tx.get(); + + if ( filesysTx != null) + { + userTrans = filesysTx.getTransaction(); + filesysTx.clearTransaction(); + } + + return userTrans; } } diff --git a/source/java/org/alfresco/filesys/server/auth/AlfrescoRpcAuthenticator.java b/source/java/org/alfresco/filesys/server/auth/AlfrescoRpcAuthenticator.java new file mode 100644 index 0000000000..b06ae9ea97 --- /dev/null +++ b/source/java/org/alfresco/filesys/server/auth/AlfrescoRpcAuthenticator.java @@ -0,0 +1,380 @@ +/* + * Copyright (C) 2005-2006 Alfresco, Inc. + * + * Licensed under the Mozilla Public License version 1.1 + * with a permitted attribution clause. You may obtain a + * copy of the License at + * + * http://www.alfresco.org/legal/license.txt + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the + * License. + */ +package org.alfresco.filesys.server.auth; + +import java.util.HashMap; +import java.util.List; + +import org.alfresco.config.ConfigElement; +import org.alfresco.filesys.server.SrvSession; +import org.alfresco.filesys.server.config.InvalidConfigurationException; +import org.alfresco.filesys.server.config.ServerConfiguration; +import org.alfresco.filesys.server.oncrpc.AuthType; +import org.alfresco.filesys.server.oncrpc.Rpc; +import org.alfresco.filesys.server.oncrpc.RpcAuthenticationException; +import org.alfresco.filesys.server.oncrpc.RpcAuthenticator; +import org.alfresco.filesys.server.oncrpc.RpcPacket; +import org.alfresco.repo.security.authentication.AuthenticationComponent; +import org.alfresco.service.transaction.TransactionService; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +/** + * Alfresco RPC Authenticator Class + * + *

Provides authentication support for the NFS server. + * + * @author gkspencer + */ +public class AlfrescoRpcAuthenticator implements RpcAuthenticator { + + // Debug logging + + private static final Log logger = LogFactory.getLog("org.alfresco.nfs.protocol.auth"); + + // Authentication types aupported by this implementation + + private int[] _authTypes = { AuthType.Null, AuthType.Unix }; + + // UID/GID to username conversions + + private HashMap m_idMap; + + // Authentication component and services + + private AuthenticationComponent m_authComponent; + private TransactionService m_transService; + + /** + * Authenticate an RPC client and create a unique session id key. + * + * @param authType int + * @param rpc RpcPacket + * @return Object + * @throws RpcAuthenticationException + */ + public Object authenticateRpcClient(int authType, RpcPacket rpc) + throws RpcAuthenticationException { + + // Create a unique session key depending on the authentication type + + Object sessKey = null; + + if (authType == AuthType.Unix) { + + // Get the gid and uid from the credentials data in the request + + rpc.positionAtCredentialsData(); + rpc.skipBytes(4); + int nameLen = rpc.unpackInt(); + rpc.skipBytes(nameLen); + + int gid = rpc.unpackInt(); + int uid = rpc.unpackInt(); + + // DEBUG + + if ( logger.isDebugEnabled()) + logger.debug( "RpcAuth: Type=Unix uid=" + uid + ", gid=" + gid); + + // Check if the Unix authentication session table is valid + + sessKey = new Long((((long) rpc.getClientAddress().hashCode()) << 32) + (gid << 16) + uid); + } + else if ( authType == AuthType.Null) + { + // Set the session key for the null authentication + + sessKey = new Integer(rpc.getClientAddress().hashCode()); + + // DEBUG + + if ( logger.isDebugEnabled()) + logger.debug( "RpcAuth: Type=Null client=" + rpc.getClientAddress()); + } + + // Check if the session key is valid, if not then the authentication + // type is unsupported + + if (sessKey == null) + throw new RpcAuthenticationException(Rpc.AuthBadCred, "Unsupported auth type, " + authType); + + // DEBUG + + if (logger.isDebugEnabled()) + logger.debug("RpcAuth: RPC from " + rpc.getClientDetails() + + ", authType=" + AuthType.getTypeAsString(authType) + + ", sessKey=" + sessKey); + + // Return the session key + + return sessKey; + } + + /** + * Return the authentication types that are supported by this + * implementation. + * + * @return int[] + */ + public int[] getRpcAuthenticationTypes() { + return _authTypes; + } + + /** + * Return the client information for the specified RPC request + * + * @param sessKey Object + * @param rpc RpcPacket + * @return ClientInfo + */ + public ClientInfo getRpcClientInformation(Object sessKey, RpcPacket rpc) { + + // Create a client information object to hold the client details + + ClientInfo cInfo = null; + + // Get the authentication type + + int authType = rpc.getCredentialsType(); + + // Unpack the client details from the RPC request + + if ( authType == AuthType.Unix) { + + // Unpack the credentials data + + rpc.positionAtCredentialsData(); + rpc.skipBytes(4); // stamp id + + String clientAddr = rpc.unpackString(); + int uid = rpc.unpackInt(); + int gid = rpc.unpackInt(); + + // Check for an additional groups list + + int grpLen = rpc.unpackInt(); + int[] groups = null; + + if (grpLen > 0) { + groups = new int[grpLen]; + rpc.unpackIntArray(groups); + } + + // Get the user name mapping for the uid/gid and authenticate + + Integer idKey = new Integer((gid << 16) + uid); + String userName = m_idMap.get( idKey); + + // DEBUG + + if ( logger.isDebugEnabled()) + logger.debug( "RpcClientInfo: username=" + userName + ", uid=" + uid + ", gid=" + gid); + + // Create the client information if there is a valid mapping + + if ( userName != null) + { + // Create the client information and fill in relevant fields + + cInfo = new ClientInfo( userName, null); + + cInfo.setNFSAuthenticationType( authType); + cInfo.setClientAddress( clientAddr); + cInfo.setUid( uid); + cInfo.setGid( gid); + + cInfo.setGroupsList(groups); + } + + // DEBUG + + if (logger.isDebugEnabled()) + logger.debug("RpcAuth: Client info, type=" + AuthType.getTypeAsString(authType) + ", name=" + + clientAddr + ", uid=" + uid + ", gid=" + gid + ", groups=" + grpLen); + } + else if ( authType == AuthType.Null) + { + // Create the client information + + cInfo = new ClientInfo( "", null); + cInfo.setClientAddress(rpc.getClientAddress().getHostAddress()); + + // DEBUG + + if (logger.isDebugEnabled()) + logger.debug("RpcAuth: Client info, type=" + AuthType.getTypeAsString(authType) + ", addr=" + + rpc.getClientAddress().getHostAddress()); + } + + // Return the client information + + return cInfo; + } + + /** + * Set the current authenticated user context for this thread + * + * @param sess SrvSession + * @param client ClientInfo + */ + public void setCurrentUser( SrvSession sess, ClientInfo client) + { + // Start a transaction + + sess.beginReadTransaction( m_transService); + + // Check the account type and setup the authentication context + + if ( client == null || client.isNullSession() || client.hasAuthenticationToken() == false) + { + // Clear the authentication, null user should not be allowed to do any service calls + + m_authComponent.clearCurrentSecurityContext(); + } + else if ( client.isGuest() == false) + { + // Check if the authentication token has been set for the client + + if ( client.hasAuthenticationToken() == false) + { + // Set the current user and retrieve the authentication token + + m_authComponent.setCurrentUser( client.getUserName()); + client.setAuthenticationToken( m_authComponent.getCurrentAuthentication()); + } + else + { + // Set the authentication context for the request + + m_authComponent.setCurrentAuthentication( client.getAuthenticationToken()); + } + } + else + { + // Enable guest access for the request + + m_authComponent.setGuestUserAsCurrentUser(); + } + } + + /** + * Initialize the RPC authenticator + * + * @param config ServerConfiguration + * @param params NameValueList + * @throws InvalidConfigurationException + */ + public void initialize(ServerConfiguration config, ConfigElement params) + throws InvalidConfigurationException { + + // Get the configured authentication component and transaction service + + m_authComponent = config.getAuthenticationComponent(); + m_transService = config.getTransactionService(); + + // Check for the user mappings + + ConfigElement userMappings = params.getChild("userMappings"); + if ( userMappings != null) + { + // Allocate the id mappings table + + m_idMap = new HashMap(); + + // Get the user map elements + + List userMaps = userMappings.getChildren(); + + // Process the user list + + for ( ConfigElement userElem : userMaps) + { + // Validate the element type + + if ( userElem.getName().equalsIgnoreCase( "user")) + { + // Get the user name, user id and group id + + String userName = userElem.getAttribute("name"); + String uidStr = userElem.getAttribute("uid"); + String gidStr = userElem.getAttribute("gid"); + + if ( userName == null || userName.length() == 0) + throw new InvalidConfigurationException("Empty user name, or name not specified"); + + if ( uidStr == null || uidStr.length() == 0) + throw new InvalidConfigurationException("Invalid uid, or uid not specified, for user " + userName); + + if ( gidStr == null || gidStr.length() == 0) + throw new InvalidConfigurationException("Invalid gid, or gid not specified, for user " + userName); + + // Parse the uid/gid + + int uid = -1; + int gid = -1; + + try + { + uid = Integer.parseInt( uidStr); + } + catch ( NumberFormatException ex) + { + throw new InvalidConfigurationException("Invalid uid value, " + uidStr + " for user " + userName); + } + + try + { + gid = Integer.parseInt( gidStr); + } + catch ( NumberFormatException ex) + { + throw new InvalidConfigurationException("Invalid gid value, " + gidStr + " for user " + userName); + } + + // Check if the mapping already exists + + Integer idKey = new Integer(( gid << 16) + uid); + if ( m_idMap.containsKey( idKey) == false) + { + // Add the username uid/gid mapping + + m_idMap.put( idKey, userName); + + // DEBUG + + if ( logger.isDebugEnabled()) + logger.debug("Added RPC user mapping for user " + userName + " uid=" + uid + ", gid=" + gid); + } + else if ( logger.isDebugEnabled()) + { + // DEBUG + + logger.debug("Ignored duplicate mapping for uid=" + uid + ", gid=" + gid); + } + } + else + throw new InvalidConfigurationException( "Invalid user mapping, " + userElem.getName()); + } + } + + // Make sure there are some user mappings + + if ( m_idMap == null || m_idMap.size() == 0) + throw new InvalidConfigurationException("No user mappings for RPC authenticator"); + } +} diff --git a/source/java/org/alfresco/filesys/server/config/ServerConfiguration.java b/source/java/org/alfresco/filesys/server/config/ServerConfiguration.java index e78e98cb3c..34f14524a6 100644 --- a/source/java/org/alfresco/filesys/server/config/ServerConfiguration.java +++ b/source/java/org/alfresco/filesys/server/config/ServerConfiguration.java @@ -58,6 +58,7 @@ import org.alfresco.filesys.netbios.RFCNetBIOSProtocol; import org.alfresco.filesys.netbios.win32.Win32NetBIOS; import org.alfresco.filesys.server.NetworkServer; import org.alfresco.filesys.server.NetworkServerList; +import org.alfresco.filesys.server.auth.AlfrescoRpcAuthenticator; import org.alfresco.filesys.server.auth.CifsAuthenticator; import org.alfresco.filesys.server.auth.acl.ACLParseException; import org.alfresco.filesys.server.auth.acl.AccessControl; @@ -75,8 +76,8 @@ import org.alfresco.filesys.server.core.SharedDeviceList; import org.alfresco.filesys.server.filesys.DefaultShareMapper; import org.alfresco.filesys.server.filesys.DiskInterface; import org.alfresco.filesys.server.filesys.DiskSharedDevice; -//import org.alfresco.filesys.server.oncrpc.DefaultRpcAuthenticator; -//import org.alfresco.filesys.server.oncrpc.RpcAuthenticator; +import org.alfresco.filesys.server.oncrpc.DefaultRpcAuthenticator; +import org.alfresco.filesys.server.oncrpc.RpcAuthenticator; import org.alfresco.filesys.smb.ServerType; import org.alfresco.filesys.smb.TcpipSMB; import org.alfresco.filesys.smb.server.repo.ContentContext; @@ -357,7 +358,7 @@ public class ServerConfiguration extends AbstractLifecycleBean // RPC authenticator implementation -// private RpcAuthenticator m_rpcAuthenticator; + private RpcAuthenticator m_rpcAuthenticator; // -------------------------------------------------------------------------------- // Global server configuration @@ -1699,7 +1700,6 @@ public class ServerConfiguration extends AbstractLifecycleBean */ private final void processNFSServerConfig(Config config) { -/** // If the configuration section is not valid then NFS is disabled if ( config == null) @@ -1856,8 +1856,26 @@ public class ServerConfiguration extends AbstractLifecycleBean // Create the RPC authenticator - m_rpcAuthenticator = new DefaultRpcAuthenticator(); -**/ + elem = config.getConfigElement("rpcAuthenticator"); + if ( elem != null) + { + // Create the RPC authenticator + + m_rpcAuthenticator = new AlfrescoRpcAuthenticator(); + + try + { + // Initialize the RPC authenticator + + m_rpcAuthenticator.initialize( this, elem); + } + catch (InvalidConfigurationException ex) + { + throw new AlfrescoRuntimeException( ex.getMessage()); + } + } + else + throw new AlfrescoRuntimeException("RPC authenticator configuration missing, require user mappings"); } /** @@ -2181,8 +2199,8 @@ public class ServerConfiguration extends AbstractLifecycleBean { // Check if the appropriate authentication component type is configured - if ( ntlmMode != NTLMMode.NONE) - throw new AlfrescoRuntimeException("Wrong authentication setup for passthru authenticator (can only be used with LDAP/JAAS auth component)"); +// if ( ntlmMode != NTLMMode.NONE) +// throw new AlfrescoRuntimeException("Wrong authentication setup for passthru authenticator (can only be used with LDAP/JAAS auth component)"); // Load the passthru authenticator dynamically @@ -3845,12 +3863,11 @@ public class ServerConfiguration extends AbstractLifecycleBean * * @return RpcAuthenticator */ -/** public final RpcAuthenticator getRpcAuthenticator() { return m_rpcAuthenticator; } -**/ + /** * Close the server configuration, used to close various components that are shared between protocol * handlers. diff --git a/source/java/org/alfresco/filesys/server/filesys/FilesysTransaction.java b/source/java/org/alfresco/filesys/server/filesys/FilesysTransaction.java new file mode 100644 index 0000000000..3258aa7068 --- /dev/null +++ b/source/java/org/alfresco/filesys/server/filesys/FilesysTransaction.java @@ -0,0 +1,112 @@ +/* + * Copyright (C) 2005-2006 Alfresco, Inc. + * + * Licensed under the Mozilla Public License version 1.1 + * with a permitted attribution clause. You may obtain a + * copy of the License at + * + * http://www.alfresco.org/legal/license.txt + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the + * License. + */ +package org.alfresco.filesys.server.filesys; + +import javax.transaction.UserTransaction; + +/** + * Filesystem Transaction Class + * + *

Holds the details of a transaction used during a batch of filesystem driver requests. + * + * @author gkspencer + */ +public class FilesysTransaction { + + // Transaction + + private UserTransaction m_transaction; + + // Flag to indicate read-only or writeable transaction + + private boolean m_readOnly; + + /** + * Default constructor + */ + public FilesysTransaction() + { + } + + /** + * Check if the transaction is valid + * + * @return boolean + */ + public final boolean hasTransaction() + { + return m_transaction != null ? true : false; + } + + /** + * Check if the transaction is read-only + * + * @return boolean + */ + public final boolean isReadOnly() + { + return m_readOnly; + } + + /** + * Return the active transaction + * + * @return UserTransaction + */ + public final UserTransaction getTransaction() + { + return m_transaction; + } + + /** + * Set the transaction + * + * @param trans UserTransaction + * @param readOnly boolean + */ + public final void setTransaction( UserTransaction trans, boolean readOnly) + { + m_transaction = trans; + m_readOnly = readOnly; + } + + /** + * Clear the transaction + */ + public final void clearTransaction() + { + m_transaction = null; + m_readOnly = true; + } + + /** + * Return the transaction details as a string + * + * @return String + */ + public String toString() + { + StringBuilder str = new StringBuilder(); + + str.append( "["); + str.append( m_transaction); + str.append( isReadOnly() ? ",Read" : ",Write"); + str.append( "]"); + + return str.toString(); + } +} diff --git a/source/java/org/alfresco/filesys/server/filesys/SymbolicLinkInterface.java b/source/java/org/alfresco/filesys/server/filesys/SymbolicLinkInterface.java new file mode 100644 index 0000000000..f526e907e7 --- /dev/null +++ b/source/java/org/alfresco/filesys/server/filesys/SymbolicLinkInterface.java @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2005 Alfresco, Inc. + * + * Licensed under the Mozilla Public License version 1.1 + * with a permitted attribution clause. You may obtain a + * copy of the License at + * + * http://www.alfresco.org/legal/license.txt + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the + * License. + */ +package org.alfresco.filesys.server.filesys; + +import org.alfresco.filesys.server.SrvSession; + +/** + * Symbolic Link Interface + * + *

Optional interface that a filesystem driver can implement to indicate that symbolic links are supported. + */ +public interface SymbolicLinkInterface { + + /** + * Read the link data for a symbolic link + * + * @param sess SrvSession + * @param tree TreeConnection + * @param path String + * @return String + * @exception AccessDeniedException + */ + public String readSymbolicLink( SrvSession sess, TreeConnection tree, String path) + throws AccessDeniedException; +} diff --git a/source/java/org/alfresco/filesys/server/oncrpc/AuthType.java b/source/java/org/alfresco/filesys/server/oncrpc/AuthType.java new file mode 100644 index 0000000000..f09bc44963 --- /dev/null +++ b/source/java/org/alfresco/filesys/server/oncrpc/AuthType.java @@ -0,0 +1,48 @@ +/* + * Copyright (C) 2005 Alfresco, Inc. + * + * Licensed under the Mozilla Public License version 1.1 + * with a permitted attribution clause. You may obtain a + * copy of the License at + * + * http://www.alfresco.org/legal/license.txt + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the + * License. + */ +package org.alfresco.filesys.server.oncrpc; + +/** + * Authentication Types Class + * + * @author GKSpencer + */ +public final class AuthType { + + // Authentication type contants + + public static final int Null = 0; + public static final int Unix = 1; + public static final int Short = 2; + public static final int DES = 3; + + // Authentication type strings + + private static final String[] _authTypes = { "Null", "Unix", "Short", "DES" }; + + /** + * Return the authentication type as string + * + * @param type int + * @return String + */ + public static final String getTypeAsString(int type) { + if ( type < 0 || type >= _authTypes.length) + return "" + type; + return _authTypes[type]; + } +} diff --git a/source/java/org/alfresco/filesys/server/oncrpc/DefaultRpcAuthenticator.java b/source/java/org/alfresco/filesys/server/oncrpc/DefaultRpcAuthenticator.java new file mode 100644 index 0000000000..098ac01a62 --- /dev/null +++ b/source/java/org/alfresco/filesys/server/oncrpc/DefaultRpcAuthenticator.java @@ -0,0 +1,207 @@ +/* + * Copyright (C) 2005 Alfresco, Inc. + * + * Licensed under the Mozilla Public License version 1.1 + * with a permitted attribution clause. You may obtain a + * copy of the License at + * + * http://www.alfresco.org/legal/license.txt + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the + * License. + */ +package org.alfresco.filesys.server.oncrpc; + +import org.alfresco.config.ConfigElement; +import org.alfresco.filesys.server.SrvSession; +import org.alfresco.filesys.server.auth.ClientInfo; +import org.alfresco.filesys.server.config.InvalidConfigurationException; +import org.alfresco.filesys.server.config.ServerConfiguration; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +/** + * Default RPC Authenticator Class + * + *

RPC authenticator implementation that allows any client to access the RPC servers. + * + * @author GKSpencer + */ +public class DefaultRpcAuthenticator implements RpcAuthenticator { + + // Debug logging + + private static final Log logger = LogFactory.getLog("org.alfresco.nfs.protocol.auth"); + + // Authentication types aupported by this implementation + + private int[] _authTypes = { AuthType.Null, AuthType.Unix }; + + /** + * Authenticate an RPC client and create a unique session id key. + * + * @param authType int + * @param rpc RpcPacket + * @return Object + * @throws RpcAuthenticationException + */ + public Object authenticateRpcClient(int authType, RpcPacket rpc) + throws RpcAuthenticationException { + + // Create a unique session key depending on the authentication type + + Object sessKey = null; + + switch (authType) { + + // Null authentication + + case AuthType.Null: + sessKey = new Integer(rpc.getClientAddress().hashCode()); + break; + + // Unix authentication + + case AuthType.Unix: + + // Get the gid and uid from the credentials data in the request + + rpc.positionAtCredentialsData(); + rpc.skipBytes(4); + int nameLen = rpc.unpackInt(); + rpc.skipBytes(nameLen); + + int gid = rpc.unpackInt(); + int uid = rpc.unpackInt(); + + // Check if the Unix authentication session table is valid + + sessKey = new Long((((long) rpc.getClientAddress().hashCode()) << 32) + (gid << 16) + uid); + break; + } + + // Check if the session key is valid, if not then the authentication + // type is unsupported + + if (sessKey == null) + throw new RpcAuthenticationException(Rpc.AuthBadCred, "Unsupported auth type, " + authType); + + // DEBUG + + if (logger.isDebugEnabled()) + logger.debug("RpcAuth: RPC from " + rpc.getClientDetails() + + ", authType=" + AuthType.getTypeAsString(authType) + + ", sessKey=" + sessKey); + + // Return the session key + + return sessKey; + } + + /** + * Return the authentication types that are supported by this + * implementation. + * + * @return int[] + */ + public int[] getRpcAuthenticationTypes() { + return _authTypes; + } + + /** + * Return the client information for the specified RPC request + * + * @param sessKey + * Object + * @param rpc + * RpcPacket + * @return ClientInfo + */ + public ClientInfo getRpcClientInformation(Object sessKey, RpcPacket rpc) { + + // Create a client information object to hold the client details + + ClientInfo cInfo = new ClientInfo("", null); + + // Get the authentication type + + int authType = rpc.getCredentialsType(); + cInfo.setNFSAuthenticationType(authType); + + // Unpack the client details from the RPC request + + switch (authType) { + + // Null authentication + + case AuthType.Null: + cInfo.setClientAddress(rpc.getClientAddress().getHostAddress()); + + // DEBUG + + if (logger.isDebugEnabled()) + logger.debug("RpcAuth: Client info, type=" + AuthType.getTypeAsString(authType) + ", addr=" + + rpc.getClientAddress().getHostAddress()); + break; + + // Unix authentication + + case AuthType.Unix: + + // Unpack the credentials data + + rpc.positionAtCredentialsData(); + rpc.skipBytes(4); // stamp id + + cInfo.setClientAddress(rpc.unpackString()); + cInfo.setUid(rpc.unpackInt()); + cInfo.setGid(rpc.unpackInt()); + + // Check for an additional groups list + + int grpLen = rpc.unpackInt(); + if (grpLen > 0) { + int[] groups = new int[grpLen]; + rpc.unpackIntArray(groups); + + cInfo.setGroupsList(groups); + } + + // DEBUG + + if (logger.isDebugEnabled()) + logger.debug("RpcAuth: Client info, type=" + AuthType.getTypeAsString(authType) + ", name=" + + cInfo.getClientAddress() + ", uid=" + cInfo.getUid() + ", gid=" + cInfo.getGid() + ", groups=" + grpLen); + break; + } + + // Return the client information + + return cInfo; + } + + /** + * Initialize the RPC authenticator + * + * @param config ServerConfiguration + * @param params NameValueList + * @throws InvalidConfigurationException + */ + public void initialize(ServerConfiguration config, ConfigElement params) + throws InvalidConfigurationException { + } + + /** + * Set the current authenticated user context for this thread + * + * @param sess SrvSession + * @param client ClientInfo + */ + public void setCurrentUser( SrvSession sess, ClientInfo client) + { + } +} diff --git a/source/java/org/alfresco/filesys/server/oncrpc/MultiThreadedTcpRpcPacketHandler.java b/source/java/org/alfresco/filesys/server/oncrpc/MultiThreadedTcpRpcPacketHandler.java new file mode 100644 index 0000000000..0ae4562ed2 --- /dev/null +++ b/source/java/org/alfresco/filesys/server/oncrpc/MultiThreadedTcpRpcPacketHandler.java @@ -0,0 +1,118 @@ +/* + * Copyright (C) 2005 Alfresco, Inc. + * + * Licensed under the Mozilla Public License version 1.1 + * with a permitted attribution clause. You may obtain a + * copy of the License at + * + * http://www.alfresco.org/legal/license.txt + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the + * License. + */ +package org.alfresco.filesys.server.oncrpc; + +import java.io.*; +import java.net.*; + +/** + * Multi-Threaded Tcp Rpc Packet Handler Class + * + *

Adds multi-threaded processing of RPC requests to the standard TCP RPC handler. + * + * @author GKSpencer + */ +public class MultiThreadedTcpRpcPacketHandler extends TcpRpcPacketHandler implements RpcPacketHandler { + + /** + * Class constructor to create a TCP RPC handler for a server. + * + * @param handler TcpRpcSessionHandler + * @param sessId int + * @param server RpcProcessor + * @param socket Socket + * @param maxRpcSize int + * @throws IOException + */ + public MultiThreadedTcpRpcPacketHandler(TcpRpcSessionHandler handler, int sessId, RpcProcessor server, + Socket socket, int maxRpcSize) throws IOException + { + super(handler, sessId, server, socket, maxRpcSize); + } + + /** + * Return the multi-threaded RPC session handler + * + * @return MultiThreadedTcpRpcSessionHandler + */ + protected final MultiThreadedTcpRpcSessionHandler getSessionHandler() + { + return (MultiThreadedTcpRpcSessionHandler) getHandler(); + } + + /** + * Allocate an RPC packet from the packet pool + * + * @param maxSize int + * @return RpcPacket + */ + protected RpcPacket allocateRpcPacket(int maxSize) + { + + // Use the session handler to allocate the RPC packet + + return getSessionHandler().allocateRpcPacket(maxSize); + } + + /** + * Deallocate an RPC packet, return the packet to the pool. + * + * @param pkt RpcPacket + */ + protected void deallocateRpcPacket(RpcPacket pkt) + { + + // Return the packet to the pool + + if (pkt.isAllocatedFromPool()) + pkt.getOwnerPacketPool().releasePacket(pkt); + } + + /** + * Process an RPC request by passing the request to a pool of worker threads. + * + * @param rpc RpcPacket + * @throws IOException + */ + protected void processRpc(RpcPacket rpc) + throws IOException + { + + // Link the RPC request to this handler + + rpc.setPacketHandler(this); + + // Queue the RPC request to the session handlers thread pool for processing + + getSessionHandler().queueRpcRequest(rpc); + } + + /** + * Send an RPC response using the TCP socket connection + * + * @param rpc RpcPacket + * @throws IOException + */ + public void sendRpcResponse(RpcPacket rpc) + throws IOException + { + + // Send the RPC response + + sendRpc(rpc); + } +} diff --git a/source/java/org/alfresco/filesys/server/oncrpc/MultiThreadedTcpRpcSessionHandler.java b/source/java/org/alfresco/filesys/server/oncrpc/MultiThreadedTcpRpcSessionHandler.java new file mode 100644 index 0000000000..f345a53f42 --- /dev/null +++ b/source/java/org/alfresco/filesys/server/oncrpc/MultiThreadedTcpRpcSessionHandler.java @@ -0,0 +1,227 @@ +/* + * Copyright (C) 2005 Alfresco, Inc. + * + * Licensed under the Mozilla Public License version 1.1 + * with a permitted attribution clause. You may obtain a + * copy of the License at + * + * http://www.alfresco.org/legal/license.txt + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the + * License. + */ +package org.alfresco.filesys.server.oncrpc; + +import java.io.*; +import java.net.*; + +import org.alfresco.filesys.server.NetworkServer; + +/** + * Multi-threaded TCP RPC Session Handler Class + * + *

Extend the basic TCP RPC handler class to process RPC requests using a thread pool. + * + * @author GKSpencer + */ +public class MultiThreadedTcpRpcSessionHandler extends TcpRpcSessionHandler { + + // Constants + // + // Default packet pool size + + public static final int DefaultPacketPoolSize = 50; + public static final int DefaultSmallPacketSize = 512; + + // RPC packet pool + + private RpcPacketPool m_packetPool; + + // Request handler thread pool + + private RpcRequestThreadPool m_threadPool; + + /** + * Class constructor + * + * @param name String + * @param protocol String + * @param rpcServer RpcProcessor + * @param server NetworkServer + * @param addr InetAddress + * @param port int + * @param maxSize int + */ + public MultiThreadedTcpRpcSessionHandler(String name, String protocol, RpcProcessor rpcServer, + NetworkServer server, InetAddress addr, int port, int maxSize) + { + super(name, protocol, rpcServer, server, addr, port, maxSize); + } + + /** + * Initialize the session socket handler + * + * @param server + * @throws IOException + */ + public void initializeSessionHandler(NetworkServer server) + throws IOException + { + + // If the packet pool has not been created, create a default packet pool + + if (m_packetPool == null) + m_packetPool = new RpcPacketPool(DefaultSmallPacketSize, DefaultPacketPoolSize, getMaximumRpcSize(), + DefaultPacketPoolSize); + + // Create the RPC request handling thread pool, if not already created + + if (m_threadPool == null) + m_threadPool = new RpcRequestThreadPool(getHandlerName(), getRpcProcessor()); + + // Call the base class initialization + + super.initializeSessionHandler(server); + } + + /** + * Allocate an RPC packet from the packet pool + * + * @param size int + * @return RpcPacket + */ + protected final RpcPacket allocateRpcPacket(int size) + { + + // Allocate an RPC packet from the packet pool + + return m_packetPool.allocatePacket(size); + } + + /** + * Queue an RPC request to the thread pool for processing + * + * @param rpc RpcPacket + */ + protected final void queueRpcRequest(RpcPacket rpc) + { + + // DEBUG + + // Debug.println("MTRpcSessHandler Queue rpc=" + rpc.toString()); + + // Queue the RPC request to the thread pool for processing + + m_threadPool.queueRpcRequest(rpc); + } + + /** + * Create a multi-threaded packet handler for the new session + * + * @param sessId int + * @param sock Socket + * @return TcpRpcPacketHandler + * @throws IOException + */ + protected TcpRpcPacketHandler createPacketHandler(int sessId, Socket sock) + throws IOException + { + + // Create a multi-threaded packet handler to use the session handlers thread pool to + // process the RPC requests + + return new MultiThreadedTcpRpcPacketHandler(this, sessId, getRpcProcessor(), sock, getMaximumRpcSize()); + } + + /** + * Set the packet pool size + * + * @param smallSize int + * @param smallPool int + * @param largeSize int + * @param largePool int + */ + public final void setPacketPool(int smallSize, int smallPool, int largeSize, int largePool) + { + + // Create the packet pool, if not already initialized + + if (m_packetPool == null) + { + + // Create the packet pool + + m_packetPool = new RpcPacketPool(smallSize, smallPool, largeSize, largePool); + } + } + + /** + * Set the packet pool size + * + * @param poolSize int + */ + public final void setPacketPool(int poolSize) + { + + // Create the packet pool, if not already initialized + + if (m_packetPool == null) + { + + // Create the packet pool + + m_packetPool = new RpcPacketPool(DefaultSmallPacketSize, poolSize, getMaximumRpcSize(), poolSize); + } + } + + /** + * Set the packet pool + * + * @param pktPool RpcPacketPool + */ + public final void setPacketPool(RpcPacketPool pktPool) + { + + // Set the packet pool, if not already initialized + + if (m_packetPool == null) + m_packetPool = pktPool; + } + + /** + * Set the thread pool size + * + * @param numThreads int + */ + public final void setThreadPool(int numThreads) + { + + // Create the thread pool, if not already initialized + + if (m_threadPool == null) + { + + // Create the thread pool + + m_threadPool = new RpcRequestThreadPool(getHandlerName(), numThreads, getRpcProcessor()); + } + } + + /** + * Set the thread pool + * + * @param threadPool RpcRequestThreadPool + */ + public final void setThreadPool(RpcRequestThreadPool threadPool) + { + + // Set the thread pool, if not already initialized + + if (m_threadPool == null) + m_threadPool = threadPool; + } +} diff --git a/source/java/org/alfresco/filesys/server/oncrpc/MultiThreadedUdpRpcDatagramHandler.java b/source/java/org/alfresco/filesys/server/oncrpc/MultiThreadedUdpRpcDatagramHandler.java new file mode 100644 index 0000000000..d5391b96f0 --- /dev/null +++ b/source/java/org/alfresco/filesys/server/oncrpc/MultiThreadedUdpRpcDatagramHandler.java @@ -0,0 +1,420 @@ +/* + * Copyright (C) 2005 Alfresco, Inc. + * + * Licensed under the Mozilla Public License version 1.1 + * with a permitted attribution clause. You may obtain a + * copy of the License at + * + * http://www.alfresco.org/legal/license.txt + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the + * License. + */ +package org.alfresco.filesys.server.oncrpc; + +import java.io.*; +import java.net.*; + +import org.alfresco.filesys.server.NetworkServer; + +/** + * Multi-Threaded UDP RPC Datagram Handler Class + * + *

Extend the basic UDP RPC handler class to process RPC requests using a thread pool. + * + * @author GKSpencer + */ +public class MultiThreadedUdpRpcDatagramHandler extends UdpRpcDatagramHandler implements RpcPacketHandler { + + // Constants + // + // Default packet pool size + + public static final int DefaultPacketPoolSize = 50; + public static final int DefaultSmallPacketSize = 512; + + // RPC packet pool + + private RpcPacketPool m_packetPool; + + // Request handler thread pool + + private RpcRequestThreadPool m_threadPool; + + // RPC response queue + + private RpcRequestQueue m_txQueue; + + // Datagram sender thread + + private DatagramSender m_txThread; + + // Current receive RPC packet + + private RpcPacket m_rxPkt; + + /** + * Datagram Sender Thread Inner Class + */ + protected class DatagramSender implements Runnable + { + + // Worker thread + + private Thread mi_thread; + + // RPC sender thread datagram packet + + private DatagramPacket mi_txPkt; + + // Shutdown flag + + private boolean mi_shutdown = false; + + /** + * Class constructor + * + * @param name String + */ + public DatagramSender(String name) + { + + // Create the worker thread + + mi_thread = new Thread(this); + mi_thread.setName(name); + mi_thread.setDaemon(true); + mi_thread.start(); + } + + /** + * Request the worker thread to shutdown + */ + public final void shutdownRequest() + { + mi_shutdown = true; + try + { + mi_thread.interrupt(); + } catch (Exception ex) + { + } + } + + /** + * Run the thread + */ + public void run() + { + + // Allocate the datagram packet for sending the RPC responses + + mi_txPkt = new DatagramPacket(new byte[4], 4); + + // Loop until shutdown + + RpcPacket rpc = null; + + while (mi_shutdown == false) + { + + try + { + + // Wait for an RPC response to be queued + + rpc = m_txQueue.removeRequest(); + } catch (InterruptedException ex) + { + + // Check for shutdown + + if (mi_shutdown == true) + break; + } + + // If the request is valid process it + + if (rpc != null) + { + + try + { + + // Initialize the datagram packet for this response + + mi_txPkt.setAddress(rpc.getClientAddress()); + mi_txPkt.setPort(rpc.getClientPort()); + mi_txPkt.setData(rpc.getBuffer(), rpc.getOffset(), rpc.getLength()); + + // Send the RPC response + + getDatagramSocket().send(mi_txPkt); + } catch (Throwable ex) + { + + // Do not display errors if shutting down + + if (mi_shutdown == false) + { + logger.debug("DatagramSender " + Thread.currentThread().getName() + ":"); + logger.debug(ex); + } + } finally + { + + // Release the RPC packet back to the packet pool + + if (rpc.isAllocatedFromPool()) + rpc.getOwnerPacketPool().releasePacket(rpc); + } + } + } + } + }; + + /** + * Class constructor + * + * @param name String + * @param protocol String + * @param rpcServer RpcProcessor + * @param server NetworkServer + * @param addr InetAddress + * @param port int + * @param maxSize int + */ + public MultiThreadedUdpRpcDatagramHandler(String name, String protocol, RpcProcessor rpcServer, + NetworkServer server, InetAddress addr, int port, int maxSize) + { + super(name, protocol, rpcServer, server, addr, port, maxSize); + } + + /** + * Initialize the session handler + * + * @param server NetworkServer + * @throws IOException + */ + public void initializeSessionHandler(NetworkServer server) + throws IOException + { + + // Create the RPC response queue + + m_txQueue = new RpcRequestQueue(); + + // Create the datagram sender thread + + m_txThread = new DatagramSender("UDP_Tx_" + getProtocolName()); + + // If the packet pool has not been created, create a default packet pool + + if (m_packetPool == null) + m_packetPool = new RpcPacketPool(DefaultSmallPacketSize, DefaultPacketPoolSize, getMaximumDatagramSize(), + DefaultPacketPoolSize); + + // Create the RPC request handling thread pool, if not already created + + if (m_threadPool == null) + m_threadPool = new RpcRequestThreadPool(getHandlerName(), getRpcProcessor()); + + // Call the base class initialization + + super.initializeSessionHandler(server); + } + + /** + * Process the RPC request + * + * @param pkt DatagramPacket + * @return boolean + * @throws IOException + */ + protected boolean processDatagram(DatagramPacket pkt) + throws IOException + { + + // Make sure that the received data is using the same buffer that we allocated in the + // allocateBuffer() method, if not the buffer did not come from the packet pool. + + if (pkt.getData() != m_rxPkt.getBuffer()) + throw new IOException("Received datagram is not in expected buffer"); + + // Update the RPC packet details + + m_rxPkt.setBuffer(pkt.getData(), 0, pkt.getLength()); + + // Set the client details + + m_rxPkt.setClientDetails(pkt.getAddress(), pkt.getPort(), Rpc.UDP); + + // Set the packet handler interface to be used to send the RPC reply + + m_rxPkt.setPacketHandler(this); + + // Queue the request to the thread pool for processing + + queueRpcRequest(m_rxPkt); + + // Indicate that the datagram buffer cannot be re-used, the main datagram receiving thread must + // allocate a new buffer for the next request. + + return false; + } + + /** + * Queue an RPC request to the thread pool for processing + * + * @param rpc RpcPacket + */ + protected final void queueRpcRequest(RpcPacket rpc) + { + + // Queue the RPC request to the thread pool for processing + + m_threadPool.queueRpcRequest(rpc); + } + + /** + * Allocate a buffer for the next datagram + * + * @param bufSize int + * @return byte[] + */ + protected byte[] allocateBuffer(int bufSize) + { + + // Allocate an RPC packet from the packet pool + + m_rxPkt = m_packetPool.allocatePacket(bufSize); + + // Return the buffer from the RPC packet + + return m_rxPkt.getBuffer(); + } + + /** + * Send an RPC response using the datagram socket + * + * @param rpc RpcPacket + * @throws IOException + */ + public void sendRpcResponse(RpcPacket rpc) + throws IOException + { + + // Queue the RPC response to the datagram sender thread + + m_txQueue.addRequest(rpc); + } + + /** + * Set the packet pool size + * + * @param smallSize int + * @param smallPool int + * @param largeSize int + * @param largePool int + */ + public final void setPacketPool(int smallSize, int smallPool, int largeSize, int largePool) + { + + // Create the packet pool, if not already initialized + + if (m_packetPool == null) + { + + // Create the packet pool + + m_packetPool = new RpcPacketPool(smallSize, smallPool, largeSize, largePool); + } + } + + /** + * Set the packet pool size + * + * @param poolSize int + */ + public final void setPacketPool(int poolSize) + { + + // Create the packet pool, if not already initialized + + if (m_packetPool == null) + { + + // Create the packet pool + + m_packetPool = new RpcPacketPool(DefaultSmallPacketSize, poolSize, getMaximumDatagramSize(), poolSize); + } + } + + /** + * Set the packet pool + * + * @param pktPool RpcPacketPool + */ + public final void setPacketPool(RpcPacketPool pktPool) + { + + // Set the packet pool, if not already initialized + + if (m_packetPool == null) + m_packetPool = pktPool; + } + + /** + * Set the thread pool size + * + * @param numThreads int + */ + public final void setThreadPool(int numThreads) + { + + // Create the thread pool, if not already initialized + + if (m_threadPool == null) + { + + // Create the thread pool + + m_threadPool = new RpcRequestThreadPool(getHandlerName(), numThreads, getRpcProcessor()); + } + } + + /** + * Set the thread pool + * + * @param threadPool RpcRequestThreadPool + */ + public final void setThreadPool(RpcRequestThreadPool threadPool) + { + + // Set the thread pool, if not already initialized + + if (m_threadPool == null) + m_threadPool = threadPool; + } + + /** + * Close the session handler + * + * @param server NetworkServer + */ + public void closeSessionHandler(NetworkServer server) + { + + // Shutdown the datagram sender thread + + m_txThread.shutdownRequest(); + + // Call the base class + + super.closeSessionHandler(server); + } +} diff --git a/source/java/org/alfresco/filesys/server/oncrpc/PortMapping.java b/source/java/org/alfresco/filesys/server/oncrpc/PortMapping.java new file mode 100644 index 0000000000..6b658b792c --- /dev/null +++ b/source/java/org/alfresco/filesys/server/oncrpc/PortMapping.java @@ -0,0 +1,145 @@ +/* + * Copyright (C) 2005 Alfresco, Inc. + * + * Licensed under the Mozilla Public License version 1.1 + * with a permitted attribution clause. You may obtain a + * copy of the License at + * + * http://www.alfresco.org/legal/license.txt + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the + * License. + */ +package org.alfresco.filesys.server.oncrpc; + +/** + * Port Details Class + * + *

Contains the details of an RPC service registered with the PortMapper service. + * + * @author GKSpencer + */ +public class PortMapping { + + // Program id and version + + private int m_programId; + private int m_versionId; + + // Protocol type (UDP or TCP) + + private int m_protocol; + + // Port + + private int m_port; + + /** + * Class constructor + * + * @param progId int + * @param verId int + * @param protocol int + * @param port int + */ + public PortMapping(int progId, int verId, int protocol, int port) + { + m_programId = progId; + m_versionId = verId; + m_protocol = protocol; + m_port = port; + } + + /** + * Return the program id + * + * @return int + */ + public final int getProgramId() + { + return m_programId; + } + + /** + * Return the version id + * + * @return int + */ + public final int getVersionId() + { + return m_versionId; + } + + /** + * Return the protocol type + * + * @return int + */ + public final int getProtocol() + { + return m_protocol; + } + + /** + * Return the port number + * + * @return int + */ + public final int getPort() + { + return m_port; + } + + /** + * Return a hash code for the port mapping + * + * @return int + */ + public int hashCode() + { + + // Create a hash code from the program id + version + protocol + + return generateHashCode(m_programId, m_versionId, m_protocol); + } + + /** + * Generate a hash code for the specified program, version and protocol + * + * @param progId int + * @param verId int + * @param proto int + * @return int + */ + public final static int generateHashCode(int progId, int verId, int proto) + { + + // Create a hash code from the program id + version + protocol + + return (progId << 16) + (verId << 8) + proto; + } + + /** + * Return the port details as a string + * + * @return String + */ + public String toString() + { + StringBuffer str = new StringBuffer(64); + + str.append("["); + str.append(getProgramId()); + str.append(":"); + str.append(getVersionId()); + str.append(getProtocol() == Rpc.TCP ? ",TCP," : ",UDP,"); + str.append(getPort()); + str.append("]"); + + return str.toString(); + } +} diff --git a/source/java/org/alfresco/filesys/server/oncrpc/Rpc.java b/source/java/org/alfresco/filesys/server/oncrpc/Rpc.java new file mode 100644 index 0000000000..f172fc4aed --- /dev/null +++ b/source/java/org/alfresco/filesys/server/oncrpc/Rpc.java @@ -0,0 +1,104 @@ +/* + * Copyright (C) 2005 Alfresco, Inc. + * + * Licensed under the Mozilla Public License version 1.1 + * with a permitted attribution clause. You may obtain a + * copy of the License at + * + * http://www.alfresco.org/legal/license.txt + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the + * License. + */ +package org.alfresco.filesys.server.oncrpc; + +/** + * ONC/RPC Constants Class + * + * @author GKSpencer + */ +public class Rpc { + + // RPC length/flags + + public static final int LastFragment = 0x80000000; + public static final int LengthMask = 0x7FFFFFFF; + + // RPC message types + + public static final int Call = 0; + public static final int Reply = 1; + + // Call status + + public static final int CallAccepted = 0; + public static final int CallDenied = 1; + + // Required RPC version + + public static final int RpcVersion = 2; + + // Call accepted status codes + + public static final int StsSuccess = 0; // RPC executed successfully + public static final int StsProgUnavail = 1; // program not available + public static final int StsProgMismatch = 2; // program version mismatch + public static final int StsProcUnavail = 3; // program does not support procedure + public static final int StsBadArgs = 4; // bad arguments in request + + // Call rejected status codes + + public static final int StsRpcMismatch = 0; // RPC version number does not equal 2 + public static final int StsAuthError = 1; // authentication error + + // Authentication failure status codes + + public static final int AuthBadCred = 1; // bad credentials + public static final int AuthRejectCred = 2; // client must begin new session + public static final int AuthBadVerf = 3; // bad verifier + public static final int AuthRejectedVerf = 4; // verifier rejected or replayed + public static final int AuthTooWeak = 5; // rejected for security reasons + + // True/false values + + public static final int True = 1; + public static final int False = 0; + + // Protocol ids + + public static final int TCP = 6; + public static final int UDP = 17; + + /** + * Return a program id as a service name + * + * @param progId int + * @return String + */ + public final static String getServiceName(int progId) + { + String svcName = null; + + switch (progId) + { + case 100005: + svcName = "Mount"; + break; + case 100003: + svcName = "NFS"; + break; + case 100000: + svcName = "Portmap"; + break; + default: + svcName = "" + progId; + break; + } + + return svcName; + } +} diff --git a/source/java/org/alfresco/filesys/server/oncrpc/RpcAuthenticationException.java b/source/java/org/alfresco/filesys/server/oncrpc/RpcAuthenticationException.java new file mode 100644 index 0000000000..1882e80b34 --- /dev/null +++ b/source/java/org/alfresco/filesys/server/oncrpc/RpcAuthenticationException.java @@ -0,0 +1,65 @@ +/* + * Copyright (C) 2005 Alfresco, Inc. + * + * Licensed under the Mozilla Public License version 1.1 + * with a permitted attribution clause. You may obtain a + * copy of the License at + * + * http://www.alfresco.org/legal/license.txt + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the + * License. + */ +package org.alfresco.filesys.server.oncrpc; + +/** + * RPC Authentication Exception Class + * + * @author GKSpencer + */ +public class RpcAuthenticationException extends Exception { + + // Object version id + + private static final long serialVersionUID = 7599358351809146330L; + + // Authentication failure error code + + private int m_authError; + + /** + * Class constructor + * + * @param authError int + */ + public RpcAuthenticationException(int authError) + { + m_authError = authError; + } + + /** + * Class constructor + * + * @param authError int + * @param msg String + */ + public RpcAuthenticationException(int authError, String msg) + { + super(msg); + m_authError = authError; + } + + /** + * Get the authentication error code + * + * @return int + */ + public final int getAuthenticationErrorCode() + { + return m_authError; + } +} diff --git a/source/java/org/alfresco/filesys/server/oncrpc/RpcAuthenticator.java b/source/java/org/alfresco/filesys/server/oncrpc/RpcAuthenticator.java new file mode 100644 index 0000000000..8b6f6cce73 --- /dev/null +++ b/source/java/org/alfresco/filesys/server/oncrpc/RpcAuthenticator.java @@ -0,0 +1,86 @@ +/* + * Copyright (C) 2005 Alfresco, Inc. + * + * Licensed under the Mozilla Public License version 1.1 + * with a permitted attribution clause. You may obtain a + * copy of the License at + * + * http://www.alfresco.org/legal/license.txt + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the + * License. + */ +package org.alfresco.filesys.server.oncrpc; + +import org.alfresco.config.ConfigElement; +import org.alfresco.filesys.server.SrvSession; +import org.alfresco.filesys.server.auth.ClientInfo; +import org.alfresco.filesys.server.config.InvalidConfigurationException; +import org.alfresco.filesys.server.config.ServerConfiguration; + +/** + * RPC Authenticator Interface + * + *

Provides authentication support for ONC/RPC requests. + * + * @author GKSpencer + */ +public interface RpcAuthenticator +{ + /** + * Initialize the RPC authenticator + * + * @param config ServerConfiguration + * @param params NameValueList + * @exception InvalidConfigurationException + */ + public void initialize(ServerConfiguration config, ConfigElement params) + throws InvalidConfigurationException; + + /** + * Authenticate an RPC client using the credentials within the RPC request. + * The object that is returned is used as the key to find the associated + * session object. + * + * @param authType int + * @param rpc RpcPacket + * @return Object + * @exception RpcAuthenticationException + */ + public Object authenticateRpcClient(int authType, RpcPacket rpc) + throws RpcAuthenticationException; + + /** + * Get RPC client information from the RPC request. + * + *

+ * This method is called when a new session object is created by an RPC + * server. + * + * @param sessKey Object + * @param rpc RpcPacket + * @return ClientInfo + */ + public ClientInfo getRpcClientInformation(Object sessKey, RpcPacket rpc); + + /** + * Return a list of the authentication types that the RPC authenticator + * implementation supports. The authentication types are specified in the + * AuthType class. + * + * @return int[] + */ + public int[] getRpcAuthenticationTypes(); + + /** + * Set the current authenticated user context for this thread + * + * @param sess SrvSession + * @param client ClientInfo + */ + public void setCurrentUser( SrvSession sess, ClientInfo client); +} diff --git a/source/java/org/alfresco/filesys/server/oncrpc/RpcClient.java b/source/java/org/alfresco/filesys/server/oncrpc/RpcClient.java new file mode 100644 index 0000000000..cc3b1a0f98 --- /dev/null +++ b/source/java/org/alfresco/filesys/server/oncrpc/RpcClient.java @@ -0,0 +1,146 @@ +/* + * Copyright (C) 2005 Alfresco, Inc. + * + * Licensed under the Mozilla Public License version 1.1 + * with a permitted attribution clause. You may obtain a + * copy of the License at + * + * http://www.alfresco.org/legal/license.txt + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the + * License. + */ +package org.alfresco.filesys.server.oncrpc; + +import java.io.*; +import java.net.*; + +/** + * RPC Client Class + * + *

Provides either a socket or datagram connection to an RPC server. + * + * @author GKSpencer + */ +public abstract class RpcClient { + + // Network address and port to connect to on the remote RPC server + + private InetAddress m_server; + private int m_port; + + // Protocol type + + private int m_protocol; + + // Maximum RPC size to send/receive + + private int m_maxRpcSize; + + /** + * Class constructor + * + * @param addr InetAddress + * @param port int + * @param proto int + * @param maxRpcSize int + * @throws IOException + * @throws SocketException + */ + protected RpcClient(InetAddress addr, int port, int proto, int maxRpcSize) throws IOException, SocketException + { + + // Save the server address, port and the protocol type + + m_server = addr; + m_port = port; + + m_protocol = proto; + + // Set the maximum RPC size to send/recieve + + m_maxRpcSize = maxRpcSize; + } + + /** + * Return the maximum RPC size + * + * @return int + */ + public final int getMaximumRpcSize() + { + return m_maxRpcSize; + } + + /** + * Return the server address + * + * @return InetAddress + */ + public final InetAddress getServerAddress() + { + return m_server; + } + + /** + * Return the server port + * + * @return int + */ + public final int getServerPort() + { + return m_port; + } + + /** + * Return the protocol type + * + * @return int + */ + public final int isProtocol() + { + return m_protocol; + } + + /** + * Send an RPC request to the server + * + * @param rpc RpcPacket + * @param rxRpc RpcPacket + * @return RpcPacket + * @throws IOException + */ + public abstract RpcPacket sendRPC(RpcPacket rpc, RpcPacket rxRpc) + throws IOException; + + /** + * Close the connection to the remote RPC server + */ + public abstract void closeConnection(); + + /** + * Return the RPC connection details as a string + * + * @return String + */ + public String toString() + { + StringBuffer str = new StringBuffer(); + + str.append("["); + str.append(isProtocol() == Rpc.TCP ? "TCP:" : "UDP:"); + str.append(getServerAddress().getHostAddress()); + str.append(":"); + str.append(getServerPort()); + + str.append(","); + str.append(getMaximumRpcSize()); + str.append("]"); + + return str.toString(); + } +} diff --git a/source/java/org/alfresco/filesys/server/oncrpc/RpcNetworkServer.java b/source/java/org/alfresco/filesys/server/oncrpc/RpcNetworkServer.java new file mode 100644 index 0000000000..de38833e8d --- /dev/null +++ b/source/java/org/alfresco/filesys/server/oncrpc/RpcNetworkServer.java @@ -0,0 +1,203 @@ +/* + * Copyright (C) 2005 Alfresco, Inc. + * + * Licensed under the Mozilla Public License version 1.1 + * with a permitted attribution clause. You may obtain a + * copy of the License at + * + * http://www.alfresco.org/legal/license.txt + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the + * License. + */ +package org.alfresco.filesys.server.oncrpc; + +import java.io.*; +import java.net.*; + +import org.alfresco.filesys.server.NetworkServer; +import org.alfresco.filesys.server.config.ServerConfiguration; +import org.alfresco.filesys.server.oncrpc.portmap.PortMapper; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +/** + * RPC Network Server Abstract Class + * + *

Provides the base class for RPC servers (such as mount and NFS). + * + * @author GKSpencer + */ +public abstract class RpcNetworkServer extends NetworkServer implements RpcProcessor { + + // Debug logging + + protected static final Log logger = LogFactory.getLog("org.alfresco.nfs.protocol"); + + /** + * Class constructor + * + * @param name String + * @param config ServerConfiguration + */ + public RpcNetworkServer(String name, ServerConfiguration config) + { + super(name, config); + } + + /** + * Register a port/protocol for the RPC server + * + * @param mapping PortMapping + * @throws IOException + */ + protected final void registerRPCServer(PortMapping mapping) + throws IOException + { + + // Call the main registration method + + PortMapping[] mappings = new PortMapping[1]; + mappings[0] = mapping; + + registerRPCServer(mappings); + } + + /** + * Register a set of ports/protocols for the RPC server + * + * @param mappings PortMapping[] + * @throws IOException + */ + protected final void registerRPCServer(PortMapping[] mappings) + throws IOException + { + + // Connect to the local portmapper service to register the RPC service + + InetAddress localHost = InetAddress.getByName("127.0.0.1"); + + TcpRpcClient rpcClient = new TcpRpcClient(localHost, PortMapper.DefaultPort, 512); + + // Allocate RPC request and response packets + + RpcPacket setPortRpc = new RpcPacket(512); + RpcPacket rxRpc = new RpcPacket(512); + + // Loop through the port mappings and register each port with the portmapper service + + for (int i = 0; i < mappings.length; i++) + { + + // Build the RPC request header + + setPortRpc.buildRequestHeader(PortMapper.ProgramId, PortMapper.VersionId, PortMapper.ProcSet, 0, null, 0, + null); + + // Pack the request parameters and set the request length + + setPortRpc.packPortMapping(mappings[i]); + setPortRpc.setLength(); + + // Send the RPC request and receive a response + + rxRpc = rpcClient.sendRPC(setPortRpc, rxRpc); + } + } + + /** + * Unregister a port/protocol for the RPC server + * + * @param mapping PortMapping + * @throws IOException + */ + protected final void unregisterRPCServer(PortMapping mapping) + throws IOException + { + + // Call the main unregister ports method + + PortMapping[] mappings = new PortMapping[1]; + mappings[0] = mapping; + + unregisterRPCServer(mappings); + } + + /** + * Unregister a set of ports/protocols for the RPC server + * + * @param mappings PortMapping[] + * @throws IOException + */ + protected final void unregisterRPCServer(PortMapping[] mappings) + throws IOException + { + + // Connect to the local portmapper service to unregister the RPC service + + InetAddress localHost = InetAddress.getByName("127.0.0.1"); + + TcpRpcClient rpcClient = new TcpRpcClient(localHost, PortMapper.DefaultPort, 512); + + // Allocate RPC request and response packets + + RpcPacket setPortRpc = new RpcPacket(512); + RpcPacket rxRpc = new RpcPacket(512); + + // Loop through the port mappings and unregister each port with the portmapper service + + for (int i = 0; i < mappings.length; i++) + { + + // Build the RPC request header + + setPortRpc.buildRequestHeader(PortMapper.ProgramId, PortMapper.VersionId, PortMapper.ProcUnSet, 0, null, 0, + null); + + // Pack the request parameters and set the request length + + setPortRpc.packPortMapping(mappings[i]); + setPortRpc.setLength(); + + // DEBUG + + if (logger.isDebugEnabled()) + logger.debug("[" + getProtocolName() + "] UnRegister server RPC " + setPortRpc.toString()); + + // Send the RPC request and receive a response + + rxRpc = rpcClient.sendRPC(setPortRpc, rxRpc); + + // DEBUG + + if (logger.isDebugEnabled()) + logger.debug("[" + getProtocolName() + "] UnRegister response " + rxRpc.toString()); + } + } + + /** + * Start the RPC server + */ + public abstract void startServer(); + + /** + * Shutdown the RPC server + * + * @param immediate boolean + */ + public abstract void shutdownServer(boolean immediate); + + /** + * Process an RPC request + * + * @param rpc RpcPacket + * @return RpcPacket + * @throws IOException + */ + public abstract RpcPacket processRpc(RpcPacket rpc) + throws IOException; +} diff --git a/source/java/org/alfresco/filesys/server/oncrpc/RpcPacket.java b/source/java/org/alfresco/filesys/server/oncrpc/RpcPacket.java new file mode 100644 index 0000000000..2edf6ded2b --- /dev/null +++ b/source/java/org/alfresco/filesys/server/oncrpc/RpcPacket.java @@ -0,0 +1,1319 @@ +/* + * Copyright (C) 2005 Alfresco, Inc. + * + * Licensed under the Mozilla Public License version 1.1 + * with a permitted attribution clause. You may obtain a + * copy of the License at + * + * http://www.alfresco.org/legal/license.txt + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the + * License. + */ +package org.alfresco.filesys.server.oncrpc; + +import java.net.*; + +import org.alfresco.filesys.util.DataPacker; + +/** + * ONC/RPC Request/Response Packet Class + * + * @author GKSpencer + */ +public class RpcPacket { + + // Constants + // + // Default buffer size to allocate + + private static final int DefaultBufferSize = 8192; + + // Fragment header length + + public static final int FragHeaderLen = 4; + + // Fixed packet lengths + + public static final int ResponseMismatchLen = 24; + public static final int ResponseAuthFailLen = 20; + + // RPC data buffer + + private byte[] m_buffer; + private int m_offset; + + // Current buffer pack/unpack position and end of buffer position + + private int m_pos; + private int m_endPos; + + // Callers address, port and protocol + + private InetAddress m_clientAddr; + private int m_clientPort; + private int m_protocol; + + // RPC packet handler interface used to send an RPC response + + private RpcPacketHandler m_pktHandler; + + // Packet pool that owns this packet, if allocated from a pool + + private RpcPacketPool m_ownerPool; + + /** + * Default constructor + */ + public RpcPacket() + { + // Allocate the RPC buffer + + m_buffer = new byte[DefaultBufferSize]; + m_offset = FragHeaderLen; + + m_pos = FragHeaderLen; + m_endPos = m_buffer.length; + } + + /** + * Class constructor + * + * @param len int + */ + public RpcPacket(int len) + { + // Allocate the RPC buffer + + m_buffer = new byte[len + FragHeaderLen]; + m_offset = FragHeaderLen; + + m_pos = FragHeaderLen; + m_endPos = m_buffer.length; + } + + /** + * Class constructor + * + * @param len int + * @param owner RpcPacketPool + */ + protected RpcPacket(int len, RpcPacketPool owner) + { + this(len); + + // Set the owner + + setOwnerPacketPool(owner); + } + + /** + * Class constructor + * + * @param buf byte[] + */ + public RpcPacket(byte[] buf) + { + m_buffer = buf; + m_offset = FragHeaderLen; + m_pos = FragHeaderLen; + m_endPos = buf.length; + } + + /** + * Class constructor + * + * @param buf byte[] + * @param offset int + * @param len int + */ + public RpcPacket(byte[] buf, int offset, int len) + { + m_buffer = buf; + m_offset = offset; + m_pos = offset; + m_endPos = offset + len; + } + + /** + * Determine if the packet handler is valid + * + * @return boolean + */ + public final boolean hasPacketHandler() + { + return m_pktHandler != null ? true : false; + } + + /** + * Return the packet handler interface used to send/receive a packet + * + * @return RpcPacketHandler + */ + public final RpcPacketHandler getPacketHandler() + { + return m_pktHandler; + } + + /** + * Detemrine if the packet is allocated from a packet pool + * + * @return boolean + */ + public final boolean isAllocatedFromPool() + { + return m_ownerPool != null ? true : false; + } + + /** + * Return the packet pool that owns this packet + * + * @return RpcPacketPool + */ + public final RpcPacketPool getOwnerPacketPool() + { + return m_ownerPool; + } + + /** + * Determine if the client address has been set + * + * @return boolean + */ + public final boolean hasClientAddress() + { + return m_clientAddr != null ? true : false; + } + + /** + * Return the client network address + * + * @return InetAddress + */ + public final InetAddress getClientAddress() + { + return m_clientAddr; + } + + /** + * Return the client port + * + * @return int + */ + public final int getClientPort() + { + return m_clientPort; + } + + /** + * Return the client protocol + * + * @return int + */ + public final int getClientProtocol() + { + return m_protocol; + } + + /** + * Return the client details as a string + * + * @return String + */ + public final String getClientDetails() + { + if (hasClientAddress() == false) + return ""; + + StringBuffer str = new StringBuffer(32); + str.append(getClientProtocol() == Rpc.TCP ? "T" : "U"); + str.append(getClientAddress().getHostAddress()); + str.append(":"); + str.append(getClientPort()); + + return str.toString(); + } + + /** + * Return the current buffer position + * + * @return int + */ + public final int getPosition() + { + return m_pos; + } + + /** + * Return the buffer + * + * @return byte[] + */ + public final byte[] getBuffer() + { + return m_buffer; + } + + /** + * Return the available buffer size + * + * @return int + */ + public final int getAvailableLength() + { + return m_buffer.length - m_pos; + } + + /** + * Return the used buffer length + * + * @return int + */ + public final int getLength() + { + return m_endPos - m_offset; + } + + /** + * Return the RPC + fragment header length + * + * @return int + */ + public final int getTxLength() + { + if (m_offset == 0) + return m_endPos; + else + return (m_endPos - m_offset) + FragHeaderLen; + } + + /** + * Return the start of data offset + * + * @return int + */ + public final int getOffset() + { + return m_offset; + } + + /** + * Return the message type + * + * @return int + */ + public final int getMessageType() + { + return DataPacker.getInt(m_buffer, m_offset + 4); + } + + /** + * Return the RPC version + * + * @return int + */ + public final int getRpcVersion() + { + return DataPacker.getInt(m_buffer, m_offset + 8); + } + + /** + * Return the program id + * + * @return int + */ + public final int getProgramId() + { + return DataPacker.getInt(m_buffer, m_offset + 12); + } + + /** + * Return the program version + * + * @return int + */ + public final int getProgramVersion() + { + return DataPacker.getInt(m_buffer, m_offset + 16); + } + + /** + * Return the procedure id + * + * @return int + */ + public final int getProcedureId() + { + return DataPacker.getInt(m_buffer, m_offset + 20); + } + + /** + * Return the credentials type + * + * @return int + */ + public final int getCredentialsType() + { + return DataPacker.getInt(m_buffer, m_offset + 24); + } + + /** + * Return the credentials length + * + * @return int + */ + public final int getCredentialsLength() + { + return DataPacker.getInt(m_buffer, m_offset + 28); + } + + /** + * Return the verifier type + * + * @return int + */ + public final int getVerifierType() + { + return DataPacker.getInt(m_buffer, m_offset + getCredentialsLength() + 32); + } + + /** + * Return the verifier length + * + * @return int + */ + public final int getVerifierLength() + { + return DataPacker.getInt(m_buffer, m_offset + getCredentialsLength() + 36); + } + + /** + * Return the buffer offset to the verifier + * + * @return int + */ + public final int getVerifierOffset() + { + return m_offset + getCredentialsLength() + 40; + } + + /** + * Return the procedure specific parameters offset + * + * @return int + */ + public final int getProcedureParameterOffset() + { + return m_offset + getCredentialsLength() + getVerifierLength() + 40; + } + + /** + * Return the procedure parameters length + * + * @return int + */ + public final int getProcedureParameterLength() + { + return m_endPos - getProcedureParameterOffset(); + } + + /** + * Return the XID + * + * @return int + */ + public final int getXID() + { + return DataPacker.getInt(m_buffer, m_offset); + } + + /** + * Check if the response has a success status + * + * @return boolean + */ + public final boolean hasSuccessStatus() + { + return getAcceptStatus() == Rpc.StsSuccess ? true : false; + } + + /** + * Return the reply state + * + * @return int + */ + public final int getReplyState() + { + return DataPacker.getInt(m_buffer, 8); + } + + /** + * Return the reject reply status + * + * @return int + */ + public final int getRejectStatus() + { + return DataPacker.getInt(m_buffer, 12); + } + + /** + * Return the version mismatch low version + * + * @return int + */ + public final int getMismatchVersionLow() + { + return DataPacker.getInt(m_buffer, 16); + } + + /** + * Return the version mismatch high version + * + * @return int + */ + public final int getMismatchVersionHigh() + { + return DataPacker.getInt(m_buffer, 20); + } + + /** + * Return the authentication failure status + * + * @return int + */ + public final int getAuthFailStatus() + { + return DataPacker.getInt(m_buffer, 16); + } + + /** + * Return the accept status for the RPC response + * + * @return int + */ + public final int getAcceptStatus() + { + int pos = DataPacker.getInt(m_buffer, 16) + 20; + return DataPacker.getInt(m_buffer, pos); + } + + /** + * Align the buffer position on a longword/32bit boundary + * + * @param ival + */ + protected final void alignPosition() + { + + // Align the buffer position on the required boundary + + m_pos = (m_pos + 3) & 0xFFFFFFFC; + } + + /** + * Pack a byte value + * + * @param bval int + */ + public final void packByte(int bval) + { + m_buffer[m_pos++] = (byte) (bval & 0xFF); + } + + /** + * Pack nulls + * + * @param len int + */ + public final void packNulls(int len) + { + for (int i = 0; i < len; i++) + m_buffer[m_pos++] = (byte) 0; + } + + /** + * Pack an integer value + * + * @param ival int + */ + public final void packInt(int ival) + { + DataPacker.putInt(ival, m_buffer, m_pos); + m_pos += 4; + } + + /** + * Pack a long value + * + * @param lval long + */ + public final void packLong(long lval) + { + DataPacker.putLong(lval, m_buffer, m_pos); + m_pos += 8; + } + + /** + * Pack a byte array with a length + * + * @param buf byte[] + */ + public final void packByteArrayWithLength(byte[] buf) + { + DataPacker.putInt(buf.length, m_buffer, m_pos); + m_pos += 4; + System.arraycopy(buf, 0, m_buffer, m_pos, buf.length); + m_pos += buf.length; + alignPosition(); + } + + /** + * Pack a byte array + * + * @param buf byte[] + */ + public final void packByteArray(byte[] buf) + { + System.arraycopy(buf, 0, m_buffer, m_pos, buf.length); + m_pos += buf.length; + alignPosition(); + } + + /** + * Pack an integer array + * + * @param iarray int[] + */ + public final void packIntArrayWithLength(int[] iarray) + { + DataPacker.putInt(iarray.length, m_buffer, m_pos); + m_pos += 4; + for (int i = 0; i < iarray.length; i++) + { + DataPacker.putInt(iarray[i], m_buffer, m_pos); + m_pos += 4; + } + } + + /** + * Pack a string + * + * @param str String + */ + public final void packString(String str) + { + DataPacker.putInt(str != null ? str.length() : 0, m_buffer, m_pos); + m_pos += 4; + if (str != null) + { + m_pos = DataPacker.putString(str, m_buffer, m_pos, false); + alignPosition(); + } + } + + /** + * Pack a port mapping structure + * + * @param portMap PortMapping + */ + public final void packPortMapping(PortMapping portMap) + { + DataPacker.putInt(portMap.getProgramId(), m_buffer, m_pos); + DataPacker.putInt(portMap.getVersionId(), m_buffer, m_pos + 4); + DataPacker.putInt(portMap.getProtocol(), m_buffer, m_pos + 8); + DataPacker.putInt(portMap.getPort(), m_buffer, m_pos + 12); + + m_pos += 16; + } + + /** + * Unpack an integer value + * + * @return int + */ + public final int unpackInt() + { + int val = DataPacker.getInt(m_buffer, m_pos); + m_pos += 4; + return val; + } + + /** + * Unpack a long value + * + * @return long + */ + public final long unpackLong() + { + long val = DataPacker.getLong(m_buffer, m_pos); + m_pos += 8; + return val; + } + + /** + * Unpack a string + * + * @return String + */ + public final String unpackString() + { + int len = unpackInt(); + + String str = ""; + if (len > 0) + { + str = DataPacker.getString(m_buffer, m_pos, len); + m_pos += len; + alignPosition(); + } + + return str; + } + + /** + * Unpack a byte array with a length + * + * @param buf byte[] + */ + public final void unpackByteArrayWithLength(byte[] buf) + { + int len = DataPacker.getInt(m_buffer, m_pos); + m_pos += 4; + if (len > 0) + { + System.arraycopy(m_buffer, m_pos, buf, 0, len); + m_pos += len; + } + alignPosition(); + } + + /** + * Unpack a byte array, using the buffer length + * + * @param buf byte[] + */ + public final void unpackByteArray(byte[] buf) + { + System.arraycopy(m_buffer, m_pos, buf, 0, buf.length); + m_pos += buf.length; + alignPosition(); + } + + /** + * Unpack an integer array, using the buffer length + * + * @param buf int[] + */ + public final void unpackIntArray(int[] buf) + { + for (int i = 0; i < buf.length; i++) + buf[i] = unpackInt(); + } + + /** + * Position the read pointer at the credentials data + */ + public final void positionAtCredentialsData() + { + m_pos = m_offset + 32; + } + + /** + * Position the read pointer at the verifier data + */ + public final void positionAtVerifierData() + { + m_pos = getVerifierOffset(); + } + + /** + * Position the read pointer at the procedure specific parameters + */ + public final void positionAtParameters() + { + m_pos = getProcedureParameterOffset(); + } + + /** + * Skip a number of bytes in the buffer, rounded to the next int boundary + * + * @param cnt int + */ + public final void skipBytes(int cnt) + { + m_pos += (cnt + 3) & 0xFFFC; + } + + /** + * Set the client details + * + * @param addr InetAddress + * @param port int + * @param protocol int + */ + public final void setClientDetails(InetAddress addr, int port, int protocol) + { + m_clientAddr = addr; + m_clientPort = port; + m_protocol = protocol; + } + + /** + * Reset the buffer details + * + * @param buf byte[] + * @param offset int + * @param len int + */ + public final void setBuffer(byte[] buf, int offset, int len) + { + m_buffer = buf; + m_offset = offset; + m_pos = offset; + m_endPos = offset + len; + } + + /** + * Reset the buffer details + * + * @param offset int + * @param len int + */ + public final void setBuffer(int offset, int len) + { + m_offset = offset; + m_pos = offset; + m_endPos = offset + len; + } + + /** + * Set the used buffer length + * + * @param len int + */ + public final void setLength(int len) + { + m_endPos = len + m_offset; + + // Set the fragment header, if the offset is non-zero + + if (m_offset == FragHeaderLen) + DataPacker.putInt(getLength() + Rpc.LastFragment, m_buffer, 0); + } + + /** + * Set the used buffer length + */ + public final void setLength() + { + m_endPos = m_pos; + + // Set the fragment header, if the offset is non-zero + + if (m_offset == FragHeaderLen) + DataPacker.putInt(getLength() + Rpc.LastFragment, m_buffer, 0); + } + + /** + * Set the buffer position + * + * @param pos int + */ + public final void setPosition(int pos) + { + m_pos = pos; + } + + /** + * Set the message type + * + * @param msgType int + */ + public final void setMessageType(int msgType) + { + DataPacker.putInt(msgType, m_buffer, m_offset + 4); + } + + /** + * Set the RPC version + * + * @param rpcVer int + */ + public final void setRpcVersion(int rpcVer) + { + DataPacker.putInt(rpcVer, m_buffer, m_offset + 8); + } + + /** + * Set the program id + * + * @param progId int + */ + public final void setProgramId(int progId) + { + DataPacker.putInt(progId, m_buffer, m_offset + 12); + } + + /** + * Set the program version + * + * @param progVer int + */ + public final void setProgramVersion(int progVer) + { + DataPacker.putInt(progVer, m_buffer, m_offset + 16); + } + + /** + * Set the procedure id + * + * @param procId int + */ + public final void setProcedureId(int procId) + { + DataPacker.putInt(procId, m_buffer, m_offset + 20); + } + + /** + * Set the credentials type + * + * @param credtype int + */ + public final void setCredentialsType(int credtype) + { + DataPacker.putInt(credtype, m_buffer, m_offset + 24); + } + + /** + * Set the credentials length + * + * @param credlen int + */ + public final void setCredentialsLength(int credlen) + { + DataPacker.putInt(credlen, m_buffer, m_offset + 28); + } + + /** + * Set the reply state + * + * @param replySts int + */ + public final void setReplyState(int replySts) + { + DataPacker.putInt(replySts, m_buffer, m_offset + 8); + } + + /** + * Set the reject status + * + * @param rejSts int + */ + public final void setRejectStatus(int rejSts) + { + DataPacker.putInt(rejSts, m_buffer, m_offset + 8); + } + + /** + * Set the RPC mismatch values + * + * @param rpcLow int + * @param rpcHigh int + */ + public final void setRpcMismatch(int rpcLow, int rpcHigh) + { + DataPacker.putInt(rpcLow, m_buffer, m_offset + 12); + DataPacker.putInt(rpcHigh, m_buffer, m_offset + 16); + } + + /** + * Set the authentication failure status + * + * @param authSts int + */ + public final void setAuthFailStatus(int authSts) + { + DataPacker.putInt(authSts, m_buffer, m_offset + 8); + } + + /** + * Set the verifier type + * + * @param verftype int + */ + public final void setVerifierType(int verftype) + { + DataPacker.putInt(verftype, m_buffer, m_offset + getCredentialsLength() + 32); + } + + /** + * Set the verifier length + * + * @param verflen int + */ + public final void setVerifierLength(int verflen) + { + DataPacker.putInt(verflen, m_buffer, m_offset + getCredentialsLength() + 36); + } + + /** + * Set the associated packet handler interface for the packet + * + * @param pktHandler RpcPacketHandler + */ + public final void setPacketHandler(RpcPacketHandler pktHandler) + { + m_pktHandler = pktHandler; + } + + /** + * Set the XID + * + * @param xid int + */ + public final void setXID(int xid) + { + DataPacker.putInt(xid, m_buffer, m_offset); + } + + /** + * Set the owner packet pool, if the packet was allocated from a pool + * + * @param pool RpcPacketPool + */ + protected final void setOwnerPacketPool(RpcPacketPool pool) + { + m_ownerPool = pool; + } + + /** + * Build an RPC request header, and set the buffer pointer ready to stream data into the parameter + * area of the request + * + * @param progId int + * @param verId int + * @param procId int + * @param credType int + * @param cred byte[] + * @param verfType int + * @param verf byte[] + */ + public final void buildRequestHeader(int progId, int verId, int procId, int credType, byte[] cred, int verfType, + byte[] verf) + { + + // Generate an id for the request + + setXID((int) (System.currentTimeMillis() & 0xFFFFFFFFL)); + + // Set the message type and RPC version (always version 2) + + setMessageType(Rpc.Call); + setRpcVersion(Rpc.RpcVersion); + + // Set the request details + + setProgramId(progId); + setProgramVersion(verId); + setProcedureId(procId); + + // Set the credentials type, length and value + + setCredentialsType(credType); + setCredentialsLength(cred != null ? cred.length : 0); + if (cred != null) + System.arraycopy(cred, 0, m_buffer, m_offset + 32, cred.length); + + // Set the verifier type, length and value + + setVerifierType(verfType); + setVerifierLength(verf != null ? verf.length : 0); + if (verf != null) + { + int pos = getVerifierOffset(); + System.arraycopy(verf, 0, m_buffer, pos, verf.length); + } + + // Position the buffer pointer at the request parameter area + + positionAtParameters(); + } + + /** + * Build a response header for a valid RPC response and set the buffer pointer ready to stream data + * into the parameter area of the response. + */ + public final void buildResponseHeader() + { + setMessageType(Rpc.Reply); + setReplyState(Rpc.CallAccepted); + + // Copy the verifier from the request + + DataPacker.putInt(getVerifierType(), m_buffer, m_offset + 12); + + int verfLen = getVerifierLength(); + DataPacker.putInt(verfLen, m_buffer, m_offset + 16); + + if (verfLen > 0) + System.arraycopy(m_buffer, getVerifierOffset(), m_buffer, m_offset + 20, verfLen); + + // Indicate a success status + + DataPacker.putInt(Rpc.StsSuccess, m_buffer, m_offset + 20 + verfLen); + + // Set the buffer pointer for streaming the response parameters + + m_pos = m_offset + 24 + verfLen; + setLength(); + } + + /** + * Build an error response packet where the RPC has been accepted but returns a status code in the parameter area. + * + * @param stsCode int + */ + public final void buildErrorResponse(int stsCode) + { + + // Check if the RPC is a request or reply + + boolean isReply = getMessageType() == Rpc.Reply; + + // Set the reply header + + setMessageType(Rpc.Reply); + setReplyState(Rpc.CallAccepted); + + // Copy the verifier from the request + + int verfLen = 0; + + if (isReply == false) + { + DataPacker.putInt(getVerifierType(), m_buffer, m_offset + 12); + + verfLen = getVerifierLength(); + DataPacker.putInt(verfLen, m_buffer, m_offset + 16); + + if (verfLen > 0) + System.arraycopy(m_buffer, getVerifierOffset(), m_buffer, m_offset + 20, verfLen); + } else + { + + // Get the verifier length from the reply + + verfLen = DataPacker.getInt(m_buffer, m_offset + 16); + } + + // Indicate a success status + + DataPacker.putInt(Rpc.StsSuccess, m_buffer, m_offset + 20 + verfLen); + + // Set the buffer pointer for streaming the response parameters + + m_pos = m_offset + 24 + verfLen; + + // Pack the service status code + + DataPacker.putInt(stsCode, m_buffer, m_pos); + m_pos += 4; + setLength(); + } + + /** + * Build an RPC version mismatch response + */ + public final void buildRpcMismatchResponse() + { + setMessageType(Rpc.Reply); + setReplyState(Rpc.CallDenied); + setRejectStatus(Rpc.StsRpcMismatch); + setRpcMismatch(Rpc.RpcVersion, Rpc.RpcVersion); + + setLength(ResponseMismatchLen); + } + + /** + * Build an RPC authentication failure response + * + * @param stsCode int + */ + public final void buildAuthFailResponse(int stsCode) + { + setMessageType(Rpc.Reply); + setReplyState(Rpc.CallDenied); + setRejectStatus(Rpc.StsAuthError); + setAuthFailStatus(stsCode); + + setLength(ResponseAuthFailLen); + } + + /** + * Build an RPC accept error response + * + * @param stsCode int + */ + public final void buildAcceptErrorResponse(int stsCode) + { + setMessageType(Rpc.Reply); + setReplyState(Rpc.CallAccepted); + + // Copy the verifier from the request + + DataPacker.putInt(getVerifierType(), m_buffer, m_offset + 12); + + int verfLen = getVerifierLength(); + DataPacker.putInt(verfLen, m_buffer, m_offset + 16); + + if (verfLen > 0) + System.arraycopy(m_buffer, getVerifierOffset(), m_buffer, m_offset + 20, verfLen); + + // Pack the status code + + DataPacker.putInt(stsCode, m_buffer, m_offset + 20 + verfLen); + + // Set the response length + + setLength(m_offset + 24 + verfLen); + } + + /** + * Build a program mismatch error response + * + * @param verLow int + * @param verHigh int + */ + public final void buildProgramMismatchResponse(int verLow, int verHigh) + { + setMessageType(Rpc.Reply); + setReplyState(Rpc.CallAccepted); + + // Copy the verifier from the request + + DataPacker.putInt(getVerifierType(), m_buffer, m_offset + 12); + + int verfLen = getVerifierLength(); + DataPacker.putInt(verfLen, m_buffer, m_offset + 16); + + if (verfLen > 0) + System.arraycopy(m_buffer, getVerifierOffset(), m_buffer, m_offset + 20, verfLen); + + // Pack the status code, and low/high version numbers + + int pos = m_offset + 20 + verfLen; + DataPacker.putInt(Rpc.StsProgMismatch, m_buffer, pos); + DataPacker.putInt(verLow, m_buffer, pos + 4); + DataPacker.putInt(verHigh, m_buffer, pos + 8); + + // Set the response length + + setLength(pos + 12); + } + + /** + * Return the RPC packet as a string + * + * @return String + */ + public String toString() + { + StringBuffer str = new StringBuffer(128); + + // Dump the client details + + str.append("["); + if (hasClientAddress()) + { + str.append(getClientProtocol() == Rpc.TCP ? "T" : "U"); + str.append(getClientAddress().getHostAddress()); + str.append(":"); + str.append(getClientPort()); + } else + str.append(""); + + // Dump the call/response header + + if (getMessageType() == Rpc.Call) + { + + // Request packet + + str.append("-Call,XID=0x"); + str.append(Integer.toHexString(getXID())); + + str.append(",RpcVer="); + str.append(getRpcVersion()); + + str.append(",ProgId="); + str.append(getProgramId()); + str.append(",ProgVer="); + str.append(getProgramVersion()); + + str.append(",Proc="); + str.append(getProcedureId()); + + str.append(",CredType="); + str.append(getCredentialsType()); + str.append(",CredLen="); + str.append(getCredentialsLength()); + + str.append(",VerfType"); + str.append(getVerifierType()); + str.append(",VerfLen="); + str.append(getVerifierLength()); + + str.append(",ParamLen="); + str.append(getProcedureParameterLength()); + } else + { + + // Response packet + + str.append("-Reply,XID=0x"); + str.append(Integer.toHexString(getXID())); + + if (getReplyState() == Rpc.CallAccepted) + { + + // Request accepted response + + str.append(",Accepted"); + } else + { + + // Request denied response + + str.append(",Denied"); + + if (getRejectStatus() == Rpc.StsRpcMismatch) + { + str.append(",RpcMismatch, Low="); + str.append(getMismatchVersionLow()); + str.append("/High="); + str.append(getMismatchVersionHigh()); + } else + { + str.append(",AuthError, Status="); + ; + str.append(getAuthFailStatus()); + } + } + } + + // Check if the packet is allocated from a pool + + if (isAllocatedFromPool()) + str.append(",Pool"); + str.append("]"); + + // Return the string + + return str.toString(); + } +} diff --git a/source/java/org/alfresco/filesys/server/oncrpc/RpcPacketHandler.java b/source/java/org/alfresco/filesys/server/oncrpc/RpcPacketHandler.java new file mode 100644 index 0000000000..440dc7051d --- /dev/null +++ b/source/java/org/alfresco/filesys/server/oncrpc/RpcPacketHandler.java @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2005 Alfresco, Inc. + * + * Licensed under the Mozilla Public License version 1.1 + * with a permitted attribution clause. You may obtain a + * copy of the License at + * + * http://www.alfresco.org/legal/license.txt + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the + * License. + */ +package org.alfresco.filesys.server.oncrpc; + +import java.io.*; + +/** + * RPC Packet Handler Interface + * + *

Interface used by an RpcPacket to send a response RPC via either TCP or UDP. + * + * @author GKSpencer + */ +public interface RpcPacketHandler { + + /** + * Send an RPC response + * + * @param rpc RpcPacket + * @exception IOException + */ + public void sendRpcResponse(RpcPacket rpc) + throws IOException; +} diff --git a/source/java/org/alfresco/filesys/server/oncrpc/RpcPacketPool.java b/source/java/org/alfresco/filesys/server/oncrpc/RpcPacketPool.java new file mode 100644 index 0000000000..97a3a20d31 --- /dev/null +++ b/source/java/org/alfresco/filesys/server/oncrpc/RpcPacketPool.java @@ -0,0 +1,439 @@ +/* + * Copyright (C) 2005 Alfresco, Inc. + * + * Licensed under the Mozilla Public License version 1.1 + * with a permitted attribution clause. You may obtain a + * copy of the License at + * + * http://www.alfresco.org/legal/license.txt + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the + * License. + */ +package org.alfresco.filesys.server.oncrpc; + +import java.util.*; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +/** + * Rpc Packet Pool Class + * + *

Contains a pool of small and large RpcPacket objects for use by multi-threaded RPC servers. + * + * @author GKSpencer + */ +public class RpcPacketPool { + + // Debug logging + + private static final Log logger = LogFactory.getLog(RpcPacketPool.class); + + // Constants + // + // Default small/large packet sizes + + public static final int DefaultSmallSize = 512; + public static final int DefaultLargeSize = 32768; + public static final int DefaultSmallLimit = -1; // no allocation limit + public static final int DefaultLargeLimit = -1; // " " " + + // Small/large packet lists + + private Vector m_smallPackets; + private Vector m_largePackets; + + // Small packet size and maximum allowed packets + + private int m_smallPktSize; + private int m_smallPktLimit; + + // Large packet size and maximum allowed packets + + private int m_largePktSize; + private int m_largePktLimit; + + // Count of allocated small/large packets + + private int m_smallPktCount; + private int m_largePktCount; + + /** + * Default constructor + */ + public RpcPacketPool() + { + + // Create the small/large packet lists + + m_smallPackets = new Vector(); + m_largePackets = new Vector(); + + // Set the packet sizes/limits + + m_smallPktSize = DefaultSmallSize; + m_smallPktLimit = DefaultSmallLimit; + + m_largePktSize = DefaultLargeSize; + m_largePktLimit = DefaultLargeLimit; + + } + + /** + * Class constructor + * + * @param smallSize int + * @param smallLimit int + * @param largeSize int + * @param largeLimit int + */ + public RpcPacketPool(int smallSize, int smallLimit, int largeSize, int largeLimit) + { + + // Create the small/large packet lists + + m_smallPackets = new Vector(); + m_largePackets = new Vector(); + + // Save the packet sizes/limits + + m_smallPktSize = smallSize; + m_smallPktLimit = smallLimit; + + m_largePktSize = largeSize; + m_largePktLimit = largeLimit; + } + + /** + * Class constructor + * + * @param largeSize int + * @param largeLimit int + */ + public RpcPacketPool(int largeSize, int largeLimit) + { + + // Create the small/large packet lists + + m_smallPackets = new Vector(); + m_largePackets = new Vector(); + + // Save the packet sizes/limits + + m_smallPktSize = DefaultSmallSize; + m_smallPktLimit = largeLimit; + + m_largePktSize = largeSize; + m_largePktLimit = largeLimit; + } + + /** + * Return the small packet size + * + * @return int + */ + public final int getSmallPacketSize() + { + return m_smallPktSize; + } + + /** + * Return the count of allocated small packets + * + * @return int + */ + public final int getSmallPacketCount() + { + return m_smallPktCount; + } + + /** + * Return the small packet allocation limit + * + * @return int + */ + public final int getSmallPacketAllocationLimit() + { + return m_smallPktLimit; + } + + /** + * Return the count of available large packets + * + * @return int + */ + public final int availableLargePackets() + { + return m_largePackets.size(); + } + + /** + * Return the large packet size + * + * @return int + */ + public final int getLargePacketSize() + { + return m_largePktSize; + } + + /** + * Return the count of allocated large packets + * + * @return int + */ + public final int getLargePacketCount() + { + return m_largePktCount; + } + + /** + * Return the large packet allocation limit + * + * @return int + */ + public final int getLargePacketAllocationLimit() + { + return m_largePktLimit; + } + + /** + * Return the count of available small packets + * + * @return int + */ + public final int availableSmallPackets() + { + return m_smallPackets.size(); + } + + /** + * Allocate a packet from the packet pool + * + * @param reqSize int + * @return RpcPacket + */ + public final RpcPacket allocatePacket(int reqSize) + { + + // Check if the packet should come from the small or large packet list + + RpcPacket pkt = null; + + if (reqSize <= m_smallPktSize) + { + + // Allocate a packet from the small packet list + + pkt = allocateSmallPacket(); + + // DEBUG + + if (logger.isDebugEnabled()) + logger.debug("RpcPacketPool Allocated (small) " + pkt.getBuffer() + ", len=" + pkt.getBuffer().length + + ", list=" + m_smallPackets.size() + "/" + m_smallPktLimit); + } else + { + + // Allocate a packet from the large packet list + + pkt = allocateLargePacket(); + + // DEBUG + + if (logger.isDebugEnabled()) + logger.debug("RpcPacketPool Allocated (large) " + pkt.getBuffer() + ", len=" + pkt.getBuffer().length + + ", list=" + m_largePackets.size() + "/" + m_largePktLimit); + } + + // Return the allocated packet + + return pkt; + } + + /** + * Release an RPC packet back to the pool + * + * @param pkt RpcPacket + */ + public final void releasePacket(RpcPacket pkt) + { + + // Check if the packet should be released to the small or large list + + if (pkt.getBuffer().length >= m_largePktSize) + { + + // Release the packet to the large packet list + + synchronized (m_largePackets) + { + + // Add the packet back to the free list + + m_largePackets.addElement(pkt); + + // Signal any waiting threads that there are packets available + + m_largePackets.notify(); + + // DEBUG + + if (logger.isDebugEnabled()) + logger.debug("RpcPacketPool Released (large) " + pkt.getBuffer() + ", len=" + + pkt.getBuffer().length + ", list=" + m_largePackets.size()); + } + } else + { + + // Release the packet to the small packet list + + synchronized (m_smallPackets) + { + + // Add the packet back to the free list + + m_smallPackets.addElement(pkt); + + // Signal any waiting threads that there are packets available + + m_smallPackets.notify(); + + // DEBUG + + if (logger.isDebugEnabled()) + logger.debug("RpcPacketPool Released (small) " + pkt.getBuffer() + ", len=" + + pkt.getBuffer().length); + } + } + } + + /** + * Allocate, or create, a small RPC packet + * + * @return RpcPacket + */ + private final RpcPacket allocateSmallPacket() + { + + RpcPacket pkt = null; + + synchronized (m_smallPackets) + { + + // Check if there is a packet available from the small packet list + + if (m_smallPackets.size() > 0) + { + + // Remove a packet from the head of the free list + + pkt = (RpcPacket) m_smallPackets.elementAt(0); + m_smallPackets.removeElementAt(0); + } else if (m_smallPktLimit == -1 || m_smallPktCount < m_smallPktLimit) + { + + // Allocate a new packet + + pkt = new RpcPacket(m_smallPktSize, this); + m_smallPktCount++; + } else + { + + // Wait for a packet to be released to the small packet list + + try + { + + // Wait for a packet + + m_smallPackets.wait(); + + // Try to get the packet from the small packet list again + + if (m_smallPackets.size() > 0) + { + + // Remove a packet from the head of the free list + + pkt = (RpcPacket) m_smallPackets.elementAt(0); + m_smallPackets.removeElementAt(0); + } + } catch (InterruptedException ex) + { + } + } + } + + // Return the allocated packet + + return pkt; + } + + /** + * Allocate, or create, a large RPC packet + * + * @return RpcPacket + */ + private final RpcPacket allocateLargePacket() + { + + RpcPacket pkt = null; + + synchronized (m_largePackets) + { + + // Check if there is a packet available from the large packet list + + if (m_largePackets.size() > 0) + { + + // Remove a packet from the head of the free list + + pkt = (RpcPacket) m_largePackets.elementAt(0); + m_largePackets.removeElementAt(0); + } else if (m_largePktLimit == -1 || m_largePktCount < m_largePktLimit) + { + + // Allocate a new packet + + pkt = new RpcPacket(m_largePktSize, this); + m_largePktCount++; + } else + { + + // Wait for a packet to be released to the large packet list + + try + { + + // Wait for a packet + + m_largePackets.wait(); + + // Try to get the packet from the large packet list again + + if (m_largePackets.size() > 0) + { + + // Remove a packet from the head of the free list + + pkt = (RpcPacket) m_largePackets.elementAt(0); + m_largePackets.removeElementAt(0); + } + } catch (InterruptedException ex) + { + } + } + } + + // Return the allocated packet + + return pkt; + } +} diff --git a/source/java/org/alfresco/filesys/server/oncrpc/RpcProcessor.java b/source/java/org/alfresco/filesys/server/oncrpc/RpcProcessor.java new file mode 100644 index 0000000000..6848445ab6 --- /dev/null +++ b/source/java/org/alfresco/filesys/server/oncrpc/RpcProcessor.java @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2005 Alfresco, Inc. + * + * Licensed under the Mozilla Public License version 1.1 + * with a permitted attribution clause. You may obtain a + * copy of the License at + * + * http://www.alfresco.org/legal/license.txt + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the + * License. + */ +package org.alfresco.filesys.server.oncrpc; + +import java.io.*; + +/** + * RPC Processor Interface + * + * @author GKSpencer + */ +public interface RpcProcessor { + + /** + * Process an RPC request + * + * @param rpc RpcPacket + * @return RpcPacket + * @throws IOException + */ + public RpcPacket processRpc(RpcPacket rpc) + throws IOException; +} diff --git a/source/java/org/alfresco/filesys/server/oncrpc/RpcRequestQueue.java b/source/java/org/alfresco/filesys/server/oncrpc/RpcRequestQueue.java new file mode 100644 index 0000000000..89119b63ff --- /dev/null +++ b/source/java/org/alfresco/filesys/server/oncrpc/RpcRequestQueue.java @@ -0,0 +1,117 @@ +/* + * Copyright (C) 2005 Alfresco, Inc. + * + * Licensed under the Mozilla Public License version 1.1 + * with a permitted attribution clause. You may obtain a + * copy of the License at + * + * http://www.alfresco.org/legal/license.txt + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the + * License. + */ +package org.alfresco.filesys.server.oncrpc; + +import java.util.*; + +/** + * RPC Request Queue Class + * + *

Provides a request queue for a thread pool of worker threads. + * + * @author GKSpencer + */ +public class RpcRequestQueue { + + // List of RPC requests + + private LinkedList m_queue; + + /** + * Class constructor + */ + public RpcRequestQueue() + { + m_queue = new LinkedList(); + } + + /** + * Return the number of requests in the queue + * + * @return int + */ + public final synchronized int numberOfRequests() + { + return m_queue.size(); + } + + /** + * Add a request to the queue + * + * @param req RpcPacket + */ + public final synchronized void addRequest(RpcPacket req) + { + + // Add the request to the queue + + m_queue.add(req); + + // Notify workers that there is a request to process + + notifyAll(); + } + + /** + * Remove a request from the head of the queue + * + * @return RpcPacket + * @exception InterruptedException + */ + public final synchronized RpcPacket removeRequest() + throws InterruptedException + { + + // Wait until there is a request + + waitWhileEmpty(); + + // Get the request from the head of the queue + + return (RpcPacket) m_queue.removeFirst(); + } + + /** + * Wait for a request to be added to the queue + * + * @exception InterruptedException + */ + public final synchronized void waitWhileEmpty() + throws InterruptedException + { + + // Wait until some work arrives on the queue + + while (m_queue.size() == 0) + wait(); + } + + /** + * Wait for the request queue to be emptied + * + * @exception InterruptedException + */ + public final synchronized void waitUntilEmpty() + throws InterruptedException + { + + // Wait until the request queue is empty + + while (m_queue.size() != 0) + wait(); + } +} diff --git a/source/java/org/alfresco/filesys/server/oncrpc/RpcRequestThreadPool.java b/source/java/org/alfresco/filesys/server/oncrpc/RpcRequestThreadPool.java new file mode 100644 index 0000000000..71e1dcf15f --- /dev/null +++ b/source/java/org/alfresco/filesys/server/oncrpc/RpcRequestThreadPool.java @@ -0,0 +1,255 @@ +/* + * Copyright (C) 2005 Alfresco, Inc. + * + * Licensed under the Mozilla Public License version 1.1 + * with a permitted attribution clause. You may obtain a + * copy of the License at + * + * http://www.alfresco.org/legal/license.txt + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the + * License. + */ +package org.alfresco.filesys.server.oncrpc; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +/** + * ONC/RPC Request Thread Pool Class + * + *

Processes RPC requests using a pool of worker threads. + * + * @author GKSpencer + */ +public class RpcRequestThreadPool { + + // Debug logging + + private static final Log logger = LogFactory.getLog(RpcRequestThreadPool.class); + + // Default/minimum/maximum number of worker threads to use + + public static final int DefaultWorkerThreads = 8; + public static final int MinimumWorkerThreads = 4; + public static final int MaximumWorkerThreads = 50; + + // Queue of RPC requests + + private RpcRequestQueue m_queue; + + // Worker threads + + private ThreadWorker[] m_workers; + + // RPC dispatcher + + private RpcProcessor m_rpcProcessor; + + /** + * Thread Worker Inner Class + */ + protected class ThreadWorker implements Runnable + { + + // Worker thread + + private Thread mi_thread; + + // Worker unique id + + private int mi_id; + + // Shutdown flag + + private boolean mi_shutdown = false; + + /** + * Class constructor + * + * @param name String + * @param id int + */ + public ThreadWorker(String name, int id) + { + + // Save the thread id + + mi_id = id; + + // Create the worker thread + + mi_thread = new Thread(this); + mi_thread.setName(name); + mi_thread.setDaemon(true); + mi_thread.start(); + } + + /** + * Request the worker thread to shutdown + */ + public final void shutdownRequest() + { + mi_shutdown = true; + try + { + mi_thread.interrupt(); + } catch (Exception ex) + { + } + } + + /** + * Run the thread + */ + public void run() + { + + // Loop until shutdown + + RpcPacket rpc = null; + RpcPacket response = null; + + while (mi_shutdown == false) + { + + try + { + + // Wait for an RPC request to be queued + + rpc = m_queue.removeRequest(); + } catch (InterruptedException ex) + { + + // Check for shutdown + + if (mi_shutdown == true) + break; + } + + // If the request is valid process it + + if (rpc != null) + { + + try + { + + // Process the request + + response = m_rpcProcessor.processRpc(rpc); + if (response != null) + response.getPacketHandler().sendRpcResponse(response); + } catch (Throwable ex) + { + + // Do not display errors if shutting down + + if (mi_shutdown == false) + { + if ( logger.isDebugEnabled()) { + logger.debug("Worker " + Thread.currentThread().getName() + ":"); + logger.debug(ex); + } + } + } finally + { + + // Release the RPC packet(s) back to the packet pool + + if (rpc.getClientProtocol() == Rpc.TCP && rpc.isAllocatedFromPool()) + rpc.getOwnerPacketPool().releasePacket(rpc); + + if (response != null && response.getClientProtocol() == Rpc.TCP + && response.getBuffer() != rpc.getBuffer() && response.isAllocatedFromPool()) + response.getOwnerPacketPool().releasePacket(response); + + } + } + } + } + }; + + /** + * Class constructor + * + * @param threadName String + * @param rpcServer RpcProcessor + * @param pktHandler PacketHandlerInterface + */ + public RpcRequestThreadPool(String threadName, RpcProcessor rpcServer) + { + this(threadName, DefaultWorkerThreads, rpcServer); + } + + /** + * Class constructor + * + * @param threadName String + * @param poolSize int + * @param rpcServer RpcProcessor + */ + public RpcRequestThreadPool(String threadName, int poolSize, RpcProcessor rpcServer) + { + + // Save the RPC handler + + m_rpcProcessor = rpcServer; + + // Create the request queue + + m_queue = new RpcRequestQueue(); + + // Check that we have at least minimum worker threads + + if (poolSize < MinimumWorkerThreads) + poolSize = MinimumWorkerThreads; + + // Create the worker threads + + m_workers = new ThreadWorker[poolSize]; + + for (int i = 0; i < m_workers.length; i++) + m_workers[i] = new ThreadWorker(threadName + (i + 1), i); + } + + /** + * Return the number of requests in the queue + * + * @return int + */ + public final int getNumberOfRequests() + { + return m_queue.numberOfRequests(); + } + + /** + * Queue an RPC request to the thread pool for processing + * + * @param rpc RpcPacket + */ + public final void queueRpcRequest(RpcPacket pkt) + { + m_queue.addRequest(pkt); + } + + /** + * Shutdown the thread pool and release all resources + */ + public void shutdownThreadPool() + { + + // Shutdown the worker threads + + if (m_workers != null) + { + for (int i = 0; i < m_workers.length; i++) + m_workers[i].shutdownRequest(); + } + } +} diff --git a/source/java/org/alfresco/filesys/server/oncrpc/TcpRpcClient.java b/source/java/org/alfresco/filesys/server/oncrpc/TcpRpcClient.java new file mode 100644 index 0000000000..97ff3322c5 --- /dev/null +++ b/source/java/org/alfresco/filesys/server/oncrpc/TcpRpcClient.java @@ -0,0 +1,97 @@ +/* + * Copyright (C) 2005 Alfresco, Inc. + * + * Licensed under the Mozilla Public License version 1.1 + * with a permitted attribution clause. You may obtain a + * copy of the License at + * + * http://www.alfresco.org/legal/license.txt + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the + * License. + */ +package org.alfresco.filesys.server.oncrpc; + +import java.io.*; +import java.net.*; + +/** + * TCP RPC Client Connection Class + * + * @author GKSpencer + */ +public class TcpRpcClient extends RpcClient { + + // TCP RPC client connection + + private TcpRpcPacketHandler m_client; + + /** + * Class constructor + * + * @param addr InetAddress + * @param port int + * @param maxRpcSize int + * @throws IOException + */ + public TcpRpcClient(InetAddress addr, int port, int maxRpcSize) throws IOException + { + super(addr, port, Rpc.TCP, maxRpcSize); + + // Connect a socket to the remote server + + Socket sock = new Socket(getServerAddress(), getServerPort()); + + // Create the TCP RPC packet handler for the client connection + + m_client = new TcpRpcPacketHandler(sock, maxRpcSize); + } + + /** + * Send an RPC request using the socket connection, and receive a response + * + * @param rpc RpcPacket + * @param rxRpc RpcPacket + * @return RpcPacket + * @throws IOException + */ + public RpcPacket sendRPC(RpcPacket rpc, RpcPacket rxRpc) + throws IOException + { + + // Use the TCP packet handler to send the RPC + + m_client.sendRpc(rpc); + + // Receive a response RPC + + RpcPacket rxPkt = rxRpc; + if (rxPkt == null) + rxPkt = new RpcPacket(getMaximumRpcSize()); + + m_client.receiveRpc(rxPkt); + + // Return the RPC response + + return rxPkt; + } + + /** + * Close the connection to the remote RPC server + */ + public void closeConnection() + { + + // Close the packet handler + + if (m_client != null) + { + m_client.closePacketHandler(); + m_client = null; + } + } +} diff --git a/source/java/org/alfresco/filesys/server/oncrpc/TcpRpcPacketHandler.java b/source/java/org/alfresco/filesys/server/oncrpc/TcpRpcPacketHandler.java new file mode 100644 index 0000000000..680f7ac68d --- /dev/null +++ b/source/java/org/alfresco/filesys/server/oncrpc/TcpRpcPacketHandler.java @@ -0,0 +1,450 @@ +/* + * Copyright (C) 2005 Alfresco, Inc. + * + * Licensed under the Mozilla Public License version 1.1 + * with a permitted attribution clause. You may obtain a + * copy of the License at + * + * http://www.alfresco.org/legal/license.txt + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the + * License. + */ +package org.alfresco.filesys.server.oncrpc; + +import java.io.*; +import java.net.*; + +import org.alfresco.filesys.server.SocketPacketHandler; +import org.alfresco.filesys.util.DataPacker; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +/** + * TCP RPC Packet Handler Class + * + *

Processes RPC requests received via TCP session. + * + * @author GKSpencer + */ +public class TcpRpcPacketHandler extends SocketPacketHandler implements Runnable { + + // Debug logging + + private static final Log logger = LogFactory.getLog(TcpRpcPacketHandler.class); + + // Session handler that owns this session + + private TcpRpcSessionHandler m_handler; + + // RPC server implementation used to process the requests + + private RpcProcessor m_rpcProcessor; + + // Session id + + private int m_sessId; + + // RPC processing thread shutdown flag + + private boolean m_shutdown; + + // Maximum RPC size accepted + + private int m_maxRpcSize; + + // Packet buffer for receiving incoming RPC requests + + private RpcPacket m_rxPkt; + + // Fragment header buffer + + private byte[] m_fragBuf; + + /** + * Class constructor to create a TCP RPC handler for a server. + * + * @param handler TcpRpcSessionHandler + * @param sessId int + * @param server RpcProcessor + * @param socket Socket + * @param maxRpcSize int + * @throws IOException + */ + public TcpRpcPacketHandler(TcpRpcSessionHandler handler, int sessId, RpcProcessor server, Socket socket, + int maxRpcSize) throws IOException + { + super(socket); + + // Set the session handler that owns this session + + m_handler = handler; + + // set the session id + + m_sessId = sessId; + + // Set the RPC server to be used to process requests + + m_rpcProcessor = server; + + // Set the maximum RPC size accepted + + m_maxRpcSize = maxRpcSize; + + // Allocate the RPC fragment header buffer + + m_fragBuf = new byte[4]; + + // Create a thread to run the RPC processing for this session + + Thread th = new Thread(this); + th.setName(handler.getProtocolName() + "_" + getSessionId()); + th.start(); + } + + /** + * Class constructor to create a TCP RPC handler for a client. + * + * @param socket Socket + * @param maxRpcSize int + * @throws IOException + */ + public TcpRpcPacketHandler(Socket socket, int maxRpcSize) throws IOException + { + super(socket); + + // Allocate the RPC fragment header buffer + + m_maxRpcSize = maxRpcSize; + m_fragBuf = new byte[4]; + } + + /** + * Return the protocol name + * + * @return String + */ + public String getProtocolName() + { + return "TCP RPC"; + } + + /** + * Return the session id + * + * @return int + */ + public final int getSessionId() + { + return m_sessId; + } + + /** + * Return the maximum RPC size accepted + * + * @return int + */ + public final int getMaximumRpcSize() + { + return m_maxRpcSize; + } + + /** + * Return the associated session handler + * + * @return TcpRpcSessionHandler + */ + protected final TcpRpcSessionHandler getHandler() + { + return m_handler; + } + + /** + * Thread to read and process the RPC requests for this session + */ + public void run() + { + + // Loop until shutdown + + int rxLen = 0; + RpcPacket rpcPkt = null; + + while (m_shutdown == false) + { + + try + { + + // allocate an RPC packet to receive an incoming request + + rpcPkt = allocateRpcPacket(getMaximumRpcSize()); + + // Read an RPC request + + rxLen = receiveRpc(rpcPkt); + + if (rxLen == -1) + { + + // Release the packet + + deallocateRpcPacket(rpcPkt); + + // Receive error, client has closed the socket + + m_handler.closeSession(getSessionId()); + break; + } + } catch (SocketException ex) + { + + // Release the packet + + if (rpcPkt != null) + deallocateRpcPacket(rpcPkt); + + // Socket error, close the session + + m_handler.closeSession(getSessionId()); + break; + } catch (IOException ex) + { + + // Only dump errors if not shutting down + + if (m_shutdown == false) + logger.debug(ex); + } + + // Process the RPC request + + try + { + + // Validate the RPC header + + if (rpcPkt.getRpcVersion() != Rpc.RpcVersion) + { + + // Build/send an error response + + rpcPkt.buildRpcMismatchResponse(); + sendRpc(rpcPkt); + } else + { + + // Process the RPC request + + processRpc(rpcPkt); + } + } catch (IOException ex) + { + + // Only dump errors if not shutting down + + if (m_shutdown == false) + logger.debug(ex); + } + } + } + + /** + * Close the session + */ + public void closePacketHandler() + { + + // Request the RPC processing thread to shutdown + + m_shutdown = true; + + // Close the input/output streams and socket + + super.closePacketHandler(); + } + + /** + * Send an RPC request/response packet + * + * @param rpc RpcPacket + * @exception IOException + */ + protected final void sendRpc(RpcPacket rpc) + throws IOException + { + + // Write the RPC response, this includes the fragment header + // + // If the fragment header is written seperately to the main RPC response packet trace tools + // such as Ethereal will not display the details properly. + + writePacket(rpc.getBuffer(), 0, rpc.getTxLength()); + } + + /** + * Read an RPC request/response + * + * @param rpc RpcPacket + * @return int + * @throws IOException + */ + protected final int receiveRpc(RpcPacket rpc) + throws IOException + { + + // Use the main receive method + + int rxLen = receiveRpc(rpc.getBuffer(), RpcPacket.FragHeaderLen, rpc.getBuffer().length + - RpcPacket.FragHeaderLen); + if (rxLen > 0) + { + + // Set the received length + + rpc.setBuffer(RpcPacket.FragHeaderLen, rxLen + RpcPacket.FragHeaderLen); + + // Set the client details + + rpc.setClientDetails(getSocket().getInetAddress(), getSocket().getPort(), Rpc.TCP); + } + + // Return the received data length + + return rxLen; + } + + /** + * Read an RPC request/response + * + * @param buffer byte[] + * @param offset int + * @param maxLen int + * @return int + * @throws IOException + */ + protected final int receiveRpc(byte[] buffer, int offset, int maxLen) + throws IOException + { + + // Fill the buffer until the last fragment is received + + int rxLen = 0; + int totLen = 0; + int rxOffset = offset; + int fragLen = 0; + boolean lastFrag = false; + + while (lastFrag == false) + { + + // Read in a header to get the fragment length + + rxLen = readPacket(m_fragBuf, 0, 4); + if (rxLen == -1) + return rxLen; + + // Check if we received the last fragment + + fragLen = DataPacker.getInt(m_fragBuf, 0); + + if ((fragLen & Rpc.LastFragment) != 0) + { + lastFrag = true; + fragLen = fragLen & Rpc.LengthMask; + } + + // Check if the buffer is large enough to receive the request + + if (fragLen > (buffer.length - rxOffset)) + throw new IOException("Receive RPC buffer overflow, fragment len = " + fragLen); + + // Read the data part of the packet into the users buffer, this may take + // several reads + + while (fragLen > 0) + { + + // Read the data + + rxLen = readPacket(buffer, offset, fragLen); + + // Check if the connection has been closed + + if (rxLen == -1) + return -1; + + // Update the received length and remaining data length + + totLen += rxLen; + fragLen -= rxLen; + + // Update the user buffer offset as more reads will be required + // to complete the data read + + offset += rxLen; + + } // end while reading data + + } // end while fragments + + // Return the total length read + + return totLen; + } + + /** + * Allocate an RPC packet for receiving an incoming request. This method must be overridden for + * multi-threaded implementations. + * + * @param maxSize int + * @return RpcPacket + */ + protected RpcPacket allocateRpcPacket(int maxSize) + { + + // Check if the receive packet has been allocated + + if (m_rxPkt == null) + m_rxPkt = new RpcPacket(maxSize); + + // Return the RPC receive packet + + return m_rxPkt; + } + + /** + * Deallocate an RPC packet, default method does nothing but a pooled implementation may + * return the packet to the pool. + * + * @param pkt RpcPacket + */ + protected void deallocateRpcPacket(RpcPacket pkt) + { + } + + /** + * Process an RPC request. This method must be overridden for multi-threaded implementations. + * + * @param rpc RpcPacket + * @exception IOException + */ + protected void processRpc(RpcPacket rpc) + throws IOException + { + + // Process the RPC request in the current thread + + RpcPacket response = m_rpcProcessor.processRpc(rpc); + + // Send the RPC response + + if (response != null) + sendRpc(response); + } +} diff --git a/source/java/org/alfresco/filesys/server/oncrpc/TcpRpcSessionHandler.java b/source/java/org/alfresco/filesys/server/oncrpc/TcpRpcSessionHandler.java new file mode 100644 index 0000000000..b70d3b6d23 --- /dev/null +++ b/source/java/org/alfresco/filesys/server/oncrpc/TcpRpcSessionHandler.java @@ -0,0 +1,206 @@ +/* + * Copyright (C) 2005 Alfresco, Inc. + * + * Licensed under the Mozilla Public License version 1.1 + * with a permitted attribution clause. You may obtain a + * copy of the License at + * + * http://www.alfresco.org/legal/license.txt + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the + * License. + */ +package org.alfresco.filesys.server.oncrpc; + +import java.io.*; +import java.net.*; +import java.util.*; + +import org.alfresco.filesys.server.NetworkServer; +import org.alfresco.filesys.server.PacketHandlerInterface; +import org.alfresco.filesys.server.SocketSessionHandler; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +/** + * TCP RPC Session Handler Class + * + *

Receives session requests via a TCP socketRPC requests via a datagram and passes the request to the registered RPC server. + * + * @author GKSpencer + */ +public class TcpRpcSessionHandler extends SocketSessionHandler { + + // Debug logging + + private static final Log logger = LogFactory.getLog(TcpRpcSessionHandler.class); + + // RPC server implementation that handles the RPC processing + + private RpcProcessor m_rpcProcessor; + + // Maximum request size allowed + + private int m_maxRpcSize; + + // List of active sessions + + private Hashtable m_sessions; + + /** + * Class constructor + * + * @param name String + * @param protocol String + * @param rpcServer RpcProcessor + * @param server NetworkServer + * @param addr InetAddress + * @param port int + * @param maxSize int + */ + public TcpRpcSessionHandler(String name, String protocol, RpcProcessor rpcServer, NetworkServer server, + InetAddress addr, int port, int maxSize) + { + super(name, protocol, server, addr, port); + + // Set the RPC server implementation that will handle the actual requests + + m_rpcProcessor = rpcServer; + + // Set the maximum RPC request size allowed + + m_maxRpcSize = maxSize; + + // Create the active session list + + m_sessions = new Hashtable(); + } + + /** + * Return the maximum RPC size allowed + * + * @return int + */ + protected int getMaximumRpcSize() + { + return m_maxRpcSize; + } + + /** + * Return the RPC server used to process the requests + * + * @return RpcProcessor + */ + protected final RpcProcessor getRpcProcessor() + { + return m_rpcProcessor; + } + + /** + * Accept an incoming session request + * + * @param sock Socket + */ + protected void acceptConnection(Socket sock) + { + + try + { + + // Set the socket for no delay + + sock.setTcpNoDelay(true); + + // Create a packet handler for the new session and add to the active session list + + int sessId = getNextSessionId(); + TcpRpcPacketHandler pktHandler = createPacketHandler(sessId, sock); + + // Add the packet handler to the active session table + + m_sessions.put(new Integer(sessId), pktHandler); + + // DEBUG + + if (logger.isDebugEnabled()) + logger.debug("[" + getProtocolName() + "] Created new session id = " + sessId + ", from = " + + sock.getInetAddress().getHostAddress() + ":" + sock.getPort()); + } catch (IOException ex) + { + } + } + + /** + * Remove a session from the active session list + * + * @param sessId int + */ + protected final void closeSession(int sessId) + { + + // Remove the specified session from the active session table + + PacketHandlerInterface pktHandler = (PacketHandlerInterface) m_sessions.remove(new Integer(sessId)); + if (pktHandler != null) + { + + // Close the session + + pktHandler.closePacketHandler(); + } + } + + /** + * Close the session handler, close all active sessions. + * + * @param server NetworkServer + */ + public void closeSessionHandler(NetworkServer server) + { + super.closeSessionHandler(server); + + // Close all active sessions + + if (m_sessions.size() > 0) + { + + // Enumerate the sessions + + Enumeration enm = m_sessions.elements(); + + while (enm.hasMoreElements()) + { + + // Get the current packet handler + + PacketHandlerInterface handler = (PacketHandlerInterface) enm.nextElement(); + handler.closePacketHandler(); + } + + // Clear the session list + + m_sessions.clear(); + } + } + + /** + * Create a packet handler for a new session + * + * @param sessId int + * @param sock Socket + * @return TcpRpcPacketHandler + * @exception IOException + */ + protected TcpRpcPacketHandler createPacketHandler(int sessId, Socket sock) + throws IOException + { + + // Create a single threaded TCP RPC packet handler + + return new TcpRpcPacketHandler(this, sessId, m_rpcProcessor, sock, getMaximumRpcSize()); + } +} diff --git a/source/java/org/alfresco/filesys/server/oncrpc/UdpRpcDatagramHandler.java b/source/java/org/alfresco/filesys/server/oncrpc/UdpRpcDatagramHandler.java new file mode 100644 index 0000000000..376f82735e --- /dev/null +++ b/source/java/org/alfresco/filesys/server/oncrpc/UdpRpcDatagramHandler.java @@ -0,0 +1,134 @@ +/* + * Copyright (C) 2005 Alfresco, Inc. + * + * Licensed under the Mozilla Public License version 1.1 + * with a permitted attribution clause. You may obtain a + * copy of the License at + * + * http://www.alfresco.org/legal/license.txt + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the + * License. + */ +package org.alfresco.filesys.server.oncrpc; + +import java.io.*; +import java.net.*; + +import org.alfresco.filesys.server.DatagramSessionHandler; +import org.alfresco.filesys.server.NetworkServer; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +/** + * UDP RPC Datagram Handler Class + * + *

Receives RPC requests via a datagram and passes the request to the registered RPC server. + * + * @author GKSpencer + */ +public class UdpRpcDatagramHandler extends DatagramSessionHandler +{ + // Debug logging + + protected static final Log logger = LogFactory.getLog(UdpRpcDatagramHandler.class); + + // RPC server implementation that handles the RPC processing + + private RpcProcessor m_rpcProcessor; + + /** + * Class constructor + * + * @param name String + * @param protocol String + * @param rpcServer RpcProcessor + * @param server NetworkServer + * @param addr InetAddress + * @param port int + * @param maxSize int + */ + public UdpRpcDatagramHandler(String name, String protocol, RpcProcessor rpcServer, NetworkServer server, + InetAddress addr, int port, int maxSize) + { + super(name, protocol, server, addr, port); + + // Set the RPC server implementation that will handle the actual requests + + m_rpcProcessor = rpcServer; + + // Set the maximum RPC request size allowed + + setMaximumDatagramSize(maxSize); + } + + /** + * Return the RPC server used to process the requests + * + * @return RpcProcessor + */ + protected final RpcProcessor getRpcProcessor() + { + return m_rpcProcessor; + } + + /** + * Process the RPC datagram + * + * @param pkt DatagramPacket + * @return boolean + * @throws IOException + */ + protected boolean processDatagram(DatagramPacket pkt) + throws IOException + { + + // The default implementation processes the RPC immediately then returns to the main datagram handler + // to wait for the next datagram to be received. In this case the datagram packet can be re-used as + // processing is done sequentially. + + // Wrap the datagram data up as an RPC request + + RpcPacket rpcPkt = new RpcPacket(pkt.getData(), 0, pkt.getLength()); + + // Set the client details + + rpcPkt.setClientDetails(pkt.getAddress(), pkt.getPort(), Rpc.UDP); + + // Validate the RPC header + + if (rpcPkt.getRpcVersion() != Rpc.RpcVersion) + { + + // Build/send an error response + + rpcPkt.buildRpcMismatchResponse(); + pkt.setData(rpcPkt.getBuffer(), rpcPkt.getOffset(), RpcPacket.ResponseMismatchLen); + + sendDatagram(pkt); + } + else + { + + // Pass the request to the registered RPC server to process + + RpcPacket response = m_rpcProcessor.processRpc(rpcPkt); + + // Send the RPC response + + if (response != null) + { + pkt.setData(response.getBuffer(), response.getOffset(), response.getLength()); + sendDatagram(pkt); + } + } + + // Indicate that the existing datagram packet can be re-used for the next request + + return true; + } +} diff --git a/source/java/org/alfresco/filesys/server/oncrpc/mount/Mount.java b/source/java/org/alfresco/filesys/server/oncrpc/mount/Mount.java new file mode 100644 index 0000000000..798d1c7c10 --- /dev/null +++ b/source/java/org/alfresco/filesys/server/oncrpc/mount/Mount.java @@ -0,0 +1,88 @@ +/* + * Copyright (C) 2005 Alfresco, Inc. + * + * Licensed under the Mozilla Public License version 1.1 + * with a permitted attribution clause. You may obtain a + * copy of the License at + * + * http://www.alfresco.org/legal/license.txt + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the + * License. + */ +package org.alfresco.filesys.server.oncrpc.mount; + +/** + * Mount Server Constants Class + * + * @author GKSpencer + */ +public final class Mount { + + // Program and version id + + public static final int ProgramId = 100005; + public static final int VersionId1 = 1; + public static final int VersionId3 = 3; + + // RPC procedure ids (version 1) + + public static final int ProcNull1 = 0; + public static final int ProcMnt1 = 1; + public static final int ProcDump1 = 2; + public static final int ProcUMnt1 = 3; + public static final int ProcUMntAll1 = 4; + public static final int ProcExport1 = 5; + public static final int ProcExportAll1 = 6; + public static final int ProcMax1 = 6; + + // RPC procedure ids (version 3) + + public static final int ProcNull3 = 0; + public static final int ProcMnt3 = 1; + public static final int ProcDump3 = 2; + public static final int ProcUMnt3 = 3; + public static final int ProcUMntAll3 = 4; + public static final int ProcExport3 = 5; + public static final int ProcMax3 = 5; + + // Mount server status codes + + public static final int StsSuccess = 0; + public static final int StsPerm = 1; + public static final int StsNoEnt = 2; + public static final int StsIO = 5; + public static final int StsAccess = 13; + public static final int StsNotDir = 20; + public static final int StsInval = 22; + public static final int StsNameTooLong = 63; + public static final int StsNotSupp = 10004; + public static final int StsServerFault = 10006; + + // Data structure limits + + public static final int FileHandleSize1 = 32; + public static final int FileHandleSize3 = 32; // can be 64 for v3 + + // RPC procedure names + + private static final String[] _procNames = { "Null", "Mount", "Dump", + "UnMount", "UnMountAll", "Export", "ExportAll" }; + + /** + * Return a procedure id as a name + * + * @param id + * int + * @return String + */ + public final static String getProcedureName(int id) { + if (id < 0 || id > ProcMax1) + return null; + return _procNames[id]; + } +} diff --git a/source/java/org/alfresco/filesys/server/oncrpc/mount/MountEntry.java b/source/java/org/alfresco/filesys/server/oncrpc/mount/MountEntry.java new file mode 100644 index 0000000000..3635488dcc --- /dev/null +++ b/source/java/org/alfresco/filesys/server/oncrpc/mount/MountEntry.java @@ -0,0 +1,81 @@ +/* + * Copyright (C) 2005 Alfresco, Inc. + * + * Licensed under the Mozilla Public License version 1.1 + * with a permitted attribution clause. You may obtain a + * copy of the License at + * + * http://www.alfresco.org/legal/license.txt + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the + * License. + */ +package org.alfresco.filesys.server.oncrpc.mount; + +/** + * Mount Entry Class + * + *

Contains the details of an active NFS mount. + * + * @author GKSpencer + */ +public class MountEntry { + + // Remote host name/address + + private String m_host; + + // Mount path + + private String m_path; + + /** + * Class constructor + * + * @param host String + * @param path String + */ + public MountEntry(String host, String path) { + m_host = host; + m_path = path; + } + + /** + * Return the host name/address + * + * @return String + */ + public final String getHost() { + return m_host; + } + + /** + * Return the mount path + * + * @return String + */ + public final String getPath() { + return m_path; + } + + /** + * Return the mount entry as a string + * + * @return String + */ + public String toString() { + StringBuffer str = new StringBuffer(); + + str.append("["); + str.append(getHost()); + str.append(":"); + str.append(getPath()); + str.append("]"); + + return str.toString(); + } +} diff --git a/source/java/org/alfresco/filesys/server/oncrpc/mount/MountEntryList.java b/source/java/org/alfresco/filesys/server/oncrpc/mount/MountEntryList.java new file mode 100644 index 0000000000..38d2cb2152 --- /dev/null +++ b/source/java/org/alfresco/filesys/server/oncrpc/mount/MountEntryList.java @@ -0,0 +1,154 @@ +/* + * Copyright (C) 2005 Alfresco, Inc. + * + * Licensed under the Mozilla Public License version 1.1 + * with a permitted attribution clause. You may obtain a + * copy of the License at + * + * http://www.alfresco.org/legal/license.txt + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the + * License. + */ +package org.alfresco.filesys.server.oncrpc.mount; + +import java.util.*; + +/** + * Mount Entry List Class + * + *

Contains a list of active mount entries. + * + * @author GKSpencer + */ +public class MountEntryList { + + // Mount entry list + + private Vector m_mounts; + + /** + * Default constructor + */ + public MountEntryList() { + m_mounts = new Vector(); + } + + /** + * Ad an entry to the list + * + * @param entry MountEntry + */ + public synchronized final void addEntry(MountEntry entry) { + m_mounts.addElement(entry); + } + + /** + * Return the number of entries in the list + * + * @return int + */ + public synchronized final int numberOfEntries() { + return m_mounts.size(); + } + + /** + * Return the specified entry + * + * @param idx + * @return MountEntry + */ + public synchronized final MountEntry getEntryAt(int idx) { + if ( idx < 0 || idx >= m_mounts.size()) + return null; + return (MountEntry) m_mounts.elementAt(idx); + } + + /** + * Find an entry in the list + * + * @param path String + * @param host String + * @return MountEntry + */ + public synchronized final MountEntry findEntry(String path, String host) { + for ( int i = 0; i < m_mounts.size(); i++) { + MountEntry entry = (MountEntry) m_mounts.elementAt(i); + + if ( host.compareTo(entry.getHost()) == 0 && path.compareTo(entry.getPath()) == 0) + return entry; + } + return null; + } + + /** + * Remove an entry from the list + * + * @param path String + * @param host String + * @return MountEntry + */ + public synchronized final MountEntry removeEntry(String path, String host) { + for ( int i = 0; i < m_mounts.size(); i++) { + MountEntry entry = (MountEntry) m_mounts.elementAt(i); + + if ( host.compareTo(entry.getHost()) == 0 && path.compareTo(entry.getPath()) == 0) { + m_mounts.removeElementAt(i); + return entry; + } + } + return null; + } + + /** + * Remove all entries from the list for the specified host + * + * @param host String + */ + public synchronized final void removeHostEntries(String host) { + for ( int i = 0; i < m_mounts.size(); i++) { + MountEntry entry = (MountEntry) m_mounts.elementAt(i); + + if ( host.compareTo(entry.getHost()) == 0) + m_mounts.removeElementAt(i); + } + } + + /** + * Find all items for the specified host and return as a new list + * + * @param host String + * @return MountEntryList + */ + public synchronized final MountEntryList findSessionEntries(String host) { + + // Allocate the list to hold the matching entries + + MountEntryList list = new MountEntryList(); + + // Find the matching entries + + for ( int i = 0; i < m_mounts.size(); i++) { + MountEntry entry = (MountEntry) m_mounts.elementAt(i); + if ( host.compareTo(entry.getHost()) == 0) + list.addEntry(entry); + } + + // Check if the list is empty, return the list + + if ( list.numberOfEntries() == 0) + list = null; + return list; + } + + /** + * Remote all entries from the list + */ + public synchronized final void removeAllItems() { + m_mounts.removeAllElements(); + } +} diff --git a/source/java/org/alfresco/filesys/server/oncrpc/mount/MountServer.java b/source/java/org/alfresco/filesys/server/oncrpc/mount/MountServer.java new file mode 100644 index 0000000000..8b85e0c04d --- /dev/null +++ b/source/java/org/alfresco/filesys/server/oncrpc/mount/MountServer.java @@ -0,0 +1,885 @@ +/* + * Copyright (C) 2005 Alfresco, Inc. + * + * Licensed under the Mozilla Public License version 1.1 + * with a permitted attribution clause. You may obtain a + * copy of the License at + * + * http://www.alfresco.org/legal/license.txt + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the + * License. + */ +package org.alfresco.filesys.server.oncrpc.mount; + +import java.io.*; +import java.util.*; + +import org.alfresco.filesys.server.ServerListener; +import org.alfresco.filesys.server.auth.acl.AccessControl; +import org.alfresco.filesys.server.auth.acl.AccessControlManager; +import org.alfresco.filesys.server.config.ServerConfiguration; +import org.alfresco.filesys.server.core.ShareType; +import org.alfresco.filesys.server.core.SharedDevice; +import org.alfresco.filesys.server.core.SharedDeviceList; +import org.alfresco.filesys.server.filesys.DiskInterface; +import org.alfresco.filesys.server.filesys.FileInfo; +import org.alfresco.filesys.server.filesys.TreeConnection; +import org.alfresco.filesys.server.filesys.TreeConnectionHash; +import org.alfresco.filesys.server.oncrpc.PortMapping; +import org.alfresco.filesys.server.oncrpc.Rpc; +import org.alfresco.filesys.server.oncrpc.RpcAuthenticationException; +import org.alfresco.filesys.server.oncrpc.RpcAuthenticator; +import org.alfresco.filesys.server.oncrpc.RpcNetworkServer; +import org.alfresco.filesys.server.oncrpc.RpcPacket; +import org.alfresco.filesys.server.oncrpc.RpcProcessor; +import org.alfresco.filesys.server.oncrpc.TcpRpcSessionHandler; +import org.alfresco.filesys.server.oncrpc.UdpRpcDatagramHandler; +import org.alfresco.filesys.server.oncrpc.nfs.NFSHandle; +import org.alfresco.filesys.server.oncrpc.nfs.NFSSrvSession; + +/** + * Mount Server Class + * + *

Contains the NFS mount server. + * + * @author GKSpencer + */ +public class MountServer extends RpcNetworkServer implements RpcProcessor { + + // Constants + // + // Maximum request size to accept + + public final static int MaxRequestSize = 8192; + + // Unix path seperator + + public static final String UNIX_SEPERATOR = "/"; + public static final char UNIX_SEPERATOR_CHAR = '/'; + public static final String DOS_SEPERATOR = "\\"; + public static final char DOS_SEPERATOR_CHAR = '\\'; + + // Incoming datagram handler for UDP requests + + private UdpRpcDatagramHandler m_udpHandler; + + // Incoming session handler for TCP requests + + private TcpRpcSessionHandler m_tcpHandler; + + // Tree connection hash + + private TreeConnectionHash m_connections; + + // List of active mounts + + private MountEntryList m_mounts; + + // Port number to listen on (UDP and TCP) + + private int m_port; + + /** + * Class constructor + * + * @param config + * ServerConfiguration + */ + public MountServer(ServerConfiguration config) { + super("Mount", config); + + // Enable/disable debug output + + setDebug(config.hasMountServerDebug()); + + // Set the port to bind the server to + + setPort(config.getMountServerPort()); + } + + /** + * Return the port to bind to + * + * @return int + */ + public final int getPort() { + return m_port; + } + + /** + * Set the port to use + * + * @param port + * int + */ + public final void setPort(int port) { + m_port = port; + } + + /** + * Start the mount server + */ + public void startServer() { + + try { + + // Create the UDP handler for accepting incoming requests + + m_udpHandler = new UdpRpcDatagramHandler("Mountd", "Mnt", this, this, null, getPort(), MaxRequestSize); + m_udpHandler.initializeSessionHandler(this); + + // Start the UDP request listener is a seperate thread + + Thread udpThread = new Thread(m_udpHandler); + udpThread.setName("Mountd_UDP"); + udpThread.start(); + + // Create the TCP handler for accepting incoming requests + + m_tcpHandler = new TcpRpcSessionHandler("Mountd", "Mnt", this, this, null, getPort(), MaxRequestSize); + m_tcpHandler.initializeSessionHandler(this); + + // Start the UDP request listener is a seperate thread + + Thread tcpThread = new Thread(m_tcpHandler); + tcpThread.setName("Mountd_TCP"); + tcpThread.start(); + + // Register the mount server with the portmapper + + PortMapping[] mappings = new PortMapping[4]; + mappings[0] = new PortMapping(Mount.ProgramId, Mount.VersionId1, Rpc.UDP, m_udpHandler.getPort()); + mappings[1] = new PortMapping(Mount.ProgramId, Mount.VersionId3, Rpc.UDP, m_udpHandler.getPort()); + mappings[2] = new PortMapping(Mount.ProgramId, Mount.VersionId1, Rpc.TCP, m_tcpHandler.getPort()); + mappings[3] = new PortMapping(Mount.ProgramId, Mount.VersionId3, Rpc.TCP, m_tcpHandler.getPort()); + + registerRPCServer(mappings); + } + catch (Exception ex) { + logger.error(ex); + } + + // Allocate the tree connection hash list and populate with the + // available share names + + m_connections = new TreeConnectionHash(); + + SharedDeviceList shareList = getConfiguration().getShareMapper() + .getShareList(getConfiguration().getServerName(), null, false); + Enumeration shares = shareList.enumerateShares(); + + while (shares.hasMoreElements()) { + + // Get the shared device + + SharedDevice share = (SharedDevice) shares.nextElement(); + + // Check if it is a disk type shared device, if so then add a + // connection to the tree connection hash + + if (share != null && share.getType() == ShareType.DISK) + m_connections.addConnection(new TreeConnection(share)); + } + + // Allocate the active mount list + + m_mounts = new MountEntryList(); + } + + /** + * Shutdown the mount server + * + * @param immediate + * boolean + */ + public void shutdownServer(boolean immediate) { + + // Unregister the mount server with the portmapper + + try { + PortMapping[] mappings = new PortMapping[4]; + mappings[0] = new PortMapping(Mount.ProgramId, Mount.VersionId1, + Rpc.UDP, m_udpHandler.getPort()); + mappings[1] = new PortMapping(Mount.ProgramId, Mount.VersionId3, + Rpc.UDP, m_udpHandler.getPort()); + mappings[2] = new PortMapping(Mount.ProgramId, Mount.VersionId1, + Rpc.TCP, m_tcpHandler.getPort()); + mappings[3] = new PortMapping(Mount.ProgramId, Mount.VersionId3, + Rpc.TCP, m_tcpHandler.getPort()); + + unregisterRPCServer(mappings); + } + catch (IOException ex) { + logger.debug(ex); + } + + // Stop the RPC handlers + + if (m_udpHandler != null) { + m_udpHandler.closeSessionHandler(this); + m_udpHandler = null; + } + + if (m_tcpHandler != null) { + m_tcpHandler.closeSessionHandler(this); + m_tcpHandler = null; + } + + // Fire a shutdown notification event + + fireServerEvent(ServerListener.ServerShutdown); + } + + /** + * Process an RPC request + * + * @param rpc + * RpcPacket + * @return RpcPacket + * @throws IOException + */ + public RpcPacket processRpc(RpcPacket rpc) throws IOException { + + // Validate the request + + int version = rpc.getProgramVersion(); + + if (rpc.getProgramId() != Mount.ProgramId) { + + // Request is not for us + + rpc.buildAcceptErrorResponse(Rpc.StsProgUnavail); + return rpc; + } + else if (version != Mount.VersionId1 && version != Mount.VersionId3) { + + // Request is not for this version of mount + + rpc.buildProgramMismatchResponse(Mount.VersionId1, Mount.VersionId3); + return rpc; + } + + // Authenticate the request + + NFSSrvSession sess = null; + + try { + + // Create a temporary session for the request + + sess = createTemporarySession(rpc); + } + catch (RpcAuthenticationException ex) { + + // Failed to authenticate the RPC client + + rpc.buildAuthFailResponse(ex.getAuthenticationErrorCode()); + return rpc; + } + + // Position the RPC buffer pointer at the start of the call parameters + + rpc.positionAtParameters(); + + // Process the RPC request + + RpcPacket response = null; + + if (version == Mount.VersionId1) { + + // Version 1 requests + + switch (rpc.getProcedureId()) { + + // Null request + + case Mount.ProcNull1: + response = procNull(rpc); + break; + + // Mount request + + case Mount.ProcMnt1: + response = procMount(sess, rpc, version); + break; + + // Dump request + + case Mount.ProcDump1: + response = procDump(sess, rpc, version); + break; + + // Unmount request + + case Mount.ProcUMnt1: + response = procUnMount(sess, rpc, version); + break; + + // Unmount all request + + case Mount.ProcUMntAll1: + response = procUnMountAll(sess, rpc, version); + break; + + // Export request + + case Mount.ProcExport1: + response = procExport(sess, rpc, version); + break; + + // Export all request + + case Mount.ProcExportAll1: + response = procExportAll(sess, rpc); + break; + } + } else if (version == Mount.VersionId3) { + + // Version 1 requests + + switch (rpc.getProcedureId()) { + + // Null request + + case Mount.ProcNull3: + response = procNull(rpc); + break; + + // Mount request + + case Mount.ProcMnt3: + response = procMount(sess, rpc, version); + break; + + // Dump request + + case Mount.ProcDump3: + response = procDump(sess, rpc, version); + break; + + // Unmount request + + case Mount.ProcUMnt3: + response = procUnMount(sess, rpc, version); + break; + + // Unmount all request + + case Mount.ProcUMntAll3: + response = procUnMountAll(sess, rpc, version); + break; + + // Export request + + case Mount.ProcExport3: + response = procExport(sess, rpc, version); + break; + } + } + + // Return the RPC response + + return response; + } + + /** + * Process the null request + * + * @param rpc + * RpcPacket + * @return RpcPacket + */ + private final RpcPacket procNull(RpcPacket rpc) { + + // Build the response + + rpc.buildResponseHeader(); + return rpc; + } + + /** + * Process the mount request + * + * @param sess + * NFSSrvSession + * @param rpc + * RpcPacket + * @param version + * int + * @return RpcPacket + */ + private final RpcPacket procMount(NFSSrvSession sess, RpcPacket rpc, + int version) { + + // Get the request parameters + + String mountPath = rpc.unpackString(); + + // DEBUG + + if (logger.isDebugEnabled()) + logger.debug("[Mount] Mount request from " + rpc.getClientDetails() + " path=" + mountPath); + + // Allocate the file handle + + byte[] handle = allocateFileHandle(version); + + // Mount the path + + int sts = mountPath(sess, mountPath, handle); + + // Pack mount the response + + rpc.buildResponseHeader(); + + // Pack the file handle status structure, version 1 format + + if (version == 1) { + + // Version 1 response format + + rpc.packInt(sts); + if (sts == Mount.StsSuccess) + rpc.packByteArray(handle); + } + else if (version == 3) { + + // Version 3 response format + + rpc.packInt(sts); + if (sts == Mount.StsSuccess) + rpc.packByteArrayWithLength(handle); + + // Create an authentication flavours array + + rpc.packIntArrayWithLength(getConfiguration().getRpcAuthenticator() + .getRpcAuthenticationTypes()); + } + + // Return the mount response + + rpc.setLength(); + return rpc; + } + + /** + * Process the dump request, return the list of active mounts + * + * @param sess + * NFSSrvSession + * @param rpc + * RpcPacket + * @param version + * int + * @return RpcPacket + */ + private final RpcPacket procDump(NFSSrvSession sess, RpcPacket rpc, + int version) { + + // DEBUG + + if (logger.isDebugEnabled()) + logger.debug("[Mount] Dump request from " + rpc.getClientDetails()); + + // Take a snapshot of the active mount list + + MountEntryList activeList = null; + + synchronized (m_mounts) { + + // Check if there are active mounts, if so then copy the mount list + + if (m_mounts.numberOfEntries() > 0) { + activeList = new MountEntryList(); + for (int i = 0; i < m_mounts.numberOfEntries(); i++) + activeList.addEntry(m_mounts.getEntryAt(i)); + } + } + + // Build the response header + + rpc.buildResponseHeader(); + + // Pack the mount list structures into the response + + if (activeList != null) { + + // Pack the active mount entry details + + for (int i = 0; i < activeList.numberOfEntries(); i++) { + + // Get the current entry + + MountEntry mntEntry = activeList.getEntryAt(i); + + rpc.packInt(Rpc.True); + rpc.packString(mntEntry.getPath()); + rpc.packString(mntEntry.getHost()); + } + } + + // Mark the end of the mount list and set the response length + + rpc.packInt(Rpc.False); + rpc.setLength(); + + // Return the RPC response + + return rpc; + } + + /** + * Process the unmount request + * + * @param sess + * NFSSrvSession + * @param rpc + * RpcPacket + * @param version + * int + * @return RpcPacket + */ + private final RpcPacket procUnMount(NFSSrvSession sess, RpcPacket rpc, + int version) { + + // Get the request parameters + + String mountPath = rpc.unpackString(); + + // DEBUG + + if (logger.isDebugEnabled()) + logger.debug("[Mount] UnMount request from " + rpc.getClientDetails() + " path=" + mountPath); + + // Remove the mount details from the active mount list + + m_mounts.removeEntry(mountPath, sess.getRemoteName()); + + // Build the RPC response + + rpc.buildResponseHeader(); + + // Return the RPC response + + return rpc; + } + + /** + * Process the unmoount all request + * + * @param sess + * NFSSrvSession + * @param rpc + * RpcPacket + * @param version + * int + * @return RpcPacket + */ + private final RpcPacket procUnMountAll(NFSSrvSession sess, RpcPacket rpc, + int version) { + + // DEBUG + + if (logger.isDebugEnabled()) + logger.debug("[Mount] UnMountAll request from " + rpc.getClientDetails()); + + // Remove all the mount details for the specified host + + m_mounts.removeHostEntries(sess.getRemoteName()); + + // Build the RPC response + + rpc.buildResponseHeader(); + + // Return the RPC response + + return rpc; + } + + /** + * Process the export request + * + * @param sess + * NFSSrvSession + * @param rpc + * RpcPacket + * @param version + * int + * @return RpcPacket + */ + private final RpcPacket procExport(NFSSrvSession sess, RpcPacket rpc, + int version) { + + // DEBUG + + if (logger.isDebugEnabled()) + logger.debug("[Mount] Export request from " + rpc.getClientDetails()); + + // Get the share list from the server + + SharedDeviceList shareList = sess.getServer().getShareMapper() + .getShareList(getConfiguration().getServerName(), sess, false); + + // Check if there is an access control manager configured + + if (sess.getServer().hasAccessControlManager()) { + + // Filter the list of available shares by applying any access + // control rules + + AccessControlManager aclMgr = sess.getServer().getAccessControlManager(); + + shareList = aclMgr.filterShareList(sess, shareList); + } + + // Build the response header + + rpc.buildResponseHeader(); + + // Add the visible shares to the export list + + Enumeration enm = shareList.enumerateShares(); + + while (enm.hasMoreElements()) { + + // Get the current share + + SharedDevice share = (SharedDevice) enm.nextElement(); + + // Add to the list of exports if it is a disk type share + + if (share.getType() == ShareType.DISK) { + + // Pack the share details + + rpc.packInt(Rpc.True); + rpc.packString("/" + share.getName()); + + // No group information + + rpc.packInt(Rpc.False); + } + } + + // Mark the end of the list + + rpc.packInt(Rpc.False); + rpc.setLength(); + + // Return the response + + return rpc; + } + + /** + * Process the export all request + * + * @param sess + * NFSSrvSession + * @param rpc + * RpcPacket + * @return RpcPacket + */ + private final RpcPacket procExportAll(NFSSrvSession sess, RpcPacket rpc) { + return null; + } + + /** + * Create a temporary session for a request. There is no need to cache + * sessions in the mount server as usually only single requests are made. + * + * @param rpc + * RpcPacket + * @return NFSSrvSession + * @exception RpcAuthenticationException + */ + private final NFSSrvSession createTemporarySession(RpcPacket rpc) + throws RpcAuthenticationException { + + // Authenticate the request + + RpcAuthenticator rpcAuth = getConfiguration().getRpcAuthenticator(); + Object sessKey = rpcAuth.authenticateRpcClient( rpc.getCredentialsType(), rpc); + + // Create an NFS session for the request + + NFSSrvSession nfsSess = new NFSSrvSession(this, rpc.getClientAddress(), rpc.getClientPort(), rpc.getClientProtocol()); + + // Set the client information for the request + + nfsSess.setClientInformation(rpcAuth.getRpcClientInformation(sessKey, rpc)); + + // Return the session + + return nfsSess; + } + + /** + * Mount a path. Used by the mount server to validate a path and initialize + * any NFS resources + * + * @param sess + * NFSSrvSession + * @param path + * String + * @param handle + * byte[] + * @return int + */ + protected final int mountPath(NFSSrvSession sess, String path, byte[] handle) { + + // Debug + + if (logger.isDebugEnabled()) + logger.debug("MountPath path=" + path); + + // Parse the path into share and additional path components + + if (path.startsWith(UNIX_SEPERATOR) && path.length() >= 2) { + + // Split the path into share and any additional path + + String shareName = null; + String extraPath = null; + int shareId = -1; + + int pos = path.indexOf(UNIX_SEPERATOR, 1); + if (pos != -1) { + shareName = path.substring(1, pos); + extraPath = path.substring(pos); + } else { + shareName = path.substring(1); + } + + // Search for a share with the specified name + + SharedDevice share = null; + + try { + share = getConfiguration().getShareMapper().findShare( + getConfiguration().getServerName(), shareName, + ShareType.DISK, sess, false); + } + catch (Exception ex) { + } + + // Check if the share exists + + if (share != null) { + + // Check if there is an access control manager configured + + if (getConfiguration().hasAccessControlManager()) { + + // Check the access control to the shared filesystem + + AccessControlManager aclMgr = getConfiguration().getAccessControlManager(); + + if (aclMgr.checkAccessControl(sess, share) == AccessControl.NoAccess) { + + // DEBUG + + if (logger.isDebugEnabled()) + logger.debug("Failed to mount path=" + path + ", access denied"); + + // Return a does not exist type error + + return Mount.StsNoEnt; + } + } + + // The share id is the hash of the share name + + shareId = shareName.hashCode(); + + // Check if there is an extra path to validate + + if (extraPath != null) { + + // Convert the path to an SMB share relative path + + extraPath = extraPath.replace(UNIX_SEPERATOR_CHAR, DOS_SEPERATOR_CHAR); + if (extraPath.endsWith(DOS_SEPERATOR)) + extraPath = extraPath.substring(0, extraPath.length() - 2); + + try { + + // Get the disk shared device + + TreeConnection conn = m_connections.findConnection(shareId); + DiskInterface disk = (DiskInterface) conn.getSharedDevice().getInterface(); + + // Validate the path + + FileInfo finfo = disk.getFileInformation(sess, conn, extraPath); + + if (finfo == null) + return Mount.StsNoEnt; + else if (finfo.isDirectory() == false) + return Mount.StsNotDir; + + // Fill in the handle for the directory + + NFSHandle.packDirectoryHandle(shareId, finfo.getFileId(), handle); + } + catch (Exception ex) { + } + } + else { + + // Fill in the handle using a share type handle + + NFSHandle.packShareHandle(share.getName(), handle); + } + + // Add a new entry to the active mount list + + m_mounts.addEntry(new MountEntry(sess.getRemoteName(), path)); + + // DEBUG + + if (logger.isDebugEnabled()) + logger.debug("Mounted path=" + path + ", handle=" + NFSHandle.asString(handle)); + + // Return a success status + + return Mount.StsSuccess; + } else { + + // DEBUG + + if (logger.isDebugEnabled()) + logger.debug("Failed to mount path=" + path); + + // Indicate that the share does not exist + + return Mount.StsNoEnt; + } + } + + // Return an invalid path error + + return Mount.StsNoEnt; + } + + /** + * Allocate a buffer for a file handle, the size depends on the RPC version + * + * @param version + * int + * @return byte[] + */ + private final byte[] allocateFileHandle(int version) { + byte[] handle = null; + if (version == 1) + handle = new byte[Mount.FileHandleSize1]; + else if (version == 3) + handle = new byte[Mount.FileHandleSize3]; + return handle; + } +} diff --git a/source/java/org/alfresco/filesys/server/oncrpc/nfs/BadCookieException.java b/source/java/org/alfresco/filesys/server/oncrpc/nfs/BadCookieException.java new file mode 100644 index 0000000000..f6d3f0dadb --- /dev/null +++ b/source/java/org/alfresco/filesys/server/oncrpc/nfs/BadCookieException.java @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2005 Alfresco, Inc. + * + * Licensed under the Mozilla Public License version 1.1 + * with a permitted attribution clause. You may obtain a + * copy of the License at + * + * http://www.alfresco.org/legal/license.txt + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the + * License. + */ +package org.alfresco.filesys.server.oncrpc.nfs; + +/** + * Bad Cookie Exception Class + * + * @author GKSpencer + */ +public class BadCookieException extends Exception { + + // Object version id + + private static final long serialVersionUID = -6689748925525944869L; + + /** + * Default constructor + */ + public BadCookieException() { + super(); + } + + /** + * Class constructor + * + * @param msg String + */ + public BadCookieException(String msg) { + super(msg); + } +} diff --git a/source/java/org/alfresco/filesys/server/oncrpc/nfs/BadHandleException.java b/source/java/org/alfresco/filesys/server/oncrpc/nfs/BadHandleException.java new file mode 100644 index 0000000000..be00afd65d --- /dev/null +++ b/source/java/org/alfresco/filesys/server/oncrpc/nfs/BadHandleException.java @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2005 Alfresco, Inc. + * + * Licensed under the Mozilla Public License version 1.1 + * with a permitted attribution clause. You may obtain a + * copy of the License at + * + * http://www.alfresco.org/legal/license.txt + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the + * License. + */ +package org.alfresco.filesys.server.oncrpc.nfs; + +/** + * Bad Handle Exception Class + * + * @author GKSpencer + */ +public class BadHandleException extends Exception { + + // Object version id + + private static final long serialVersionUID = 5928520475130958599L; + + /** + * Default constructor + */ + public BadHandleException() { + super(); + } + + /** + * Class constructor + * + * @param msg String + */ + public BadHandleException(String msg) { + super(msg); + } +} diff --git a/source/java/org/alfresco/filesys/server/oncrpc/nfs/FileIdCache.java b/source/java/org/alfresco/filesys/server/oncrpc/nfs/FileIdCache.java new file mode 100644 index 0000000000..a377d9416a --- /dev/null +++ b/source/java/org/alfresco/filesys/server/oncrpc/nfs/FileIdCache.java @@ -0,0 +1,69 @@ +/* + * Copyright (C) 2005 Alfresco, Inc. + * + * Licensed under the Mozilla Public License version 1.1 + * with a permitted attribution clause. You may obtain a + * copy of the License at + * + * http://www.alfresco.org/legal/license.txt + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the + * License. + */ +package org.alfresco.filesys.server.oncrpc.nfs; + +import java.util.*; + +/** + * File Id Cache Class + * + *

Converts a file/directory id to a share relative path. + * + * @author GKSpencer + */ +public class FileIdCache { + + // File id to path cache + + private Hashtable m_idCache; + + /** + * Default constructor + */ + public FileIdCache() { + m_idCache = new Hashtable(); + } + + /** + * Add an entry to the cache + * + * @param fid int + * @param path String + */ + public final void addPath(int fid, String path) { + m_idCache.put(new Integer(fid), path); + } + + /** + * Convert a file id to a path + * + * @param fid int + * @return String + */ + public final String findPath(int fid) { + return (String) m_idCache.get(new Integer(fid)); + } + + /** + * Delete an entry from the cache + * + * @param fid int + */ + public final void deletePath(int fid) { + m_idCache.remove(new Integer(fid)); + } +} diff --git a/source/java/org/alfresco/filesys/server/oncrpc/nfs/NFS.java b/source/java/org/alfresco/filesys/server/oncrpc/nfs/NFS.java new file mode 100644 index 0000000000..9aebf21619 --- /dev/null +++ b/source/java/org/alfresco/filesys/server/oncrpc/nfs/NFS.java @@ -0,0 +1,267 @@ +/* + * Copyright (C) 2005 Alfresco, Inc. + * + * Licensed under the Mozilla Public License version 1.1 + * with a permitted attribution clause. You may obtain a + * copy of the License at + * + * http://www.alfresco.org/legal/license.txt + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the + * License. + */ +package org.alfresco.filesys.server.oncrpc.nfs; + +/** + * NFS Server Constants Class + * + * @author GKSpencer + */ +public final class NFS { + + // Default NFS server port + + public static final int DefaultPort = 2049; + + // Program and version id + + public static final int ProgramId = 100003; + public static final int VersionId = 3; + + // RPC procedure ids + + public static final int ProcNull = 0; + public static final int ProcGetAttr = 1; + public static final int ProcSetAttr = 2; + public static final int ProcLookup = 3; + public static final int ProcAccess = 4; + public static final int ProcReadLink = 5; + public static final int ProcRead = 6; + public static final int ProcWrite = 7; + public static final int ProcCreate = 8; + public static final int ProcMkDir = 9; + public static final int ProcSymLink = 10; + public static final int ProcMkNode = 11; + public static final int ProcRemove = 12; + public static final int ProcRmDir = 13; + public static final int ProcRename = 14; + public static final int ProcLink = 15; + public static final int ProcReadDir = 16; + public static final int ProcReadDirPlus = 17; + public static final int ProcFsStat = 18; + public static final int ProcFsInfo = 19; + public static final int ProcPathConf = 20; + public static final int ProcCommit = 21; + public static final int ProcMax = 21; + + // NFS server status codes + + public static final int StsSuccess = 0; + public static final int StsPerm = 1; + public static final int StsNoEnt = 2; + public static final int StsIO = 5; + public static final int StsNxIO = 6; + public static final int StsAccess = 13; + public static final int StsExist = 17; + public static final int StsXDev = 18; + public static final int StsNoDev = 19; + public static final int StsNotDir = 20; + public static final int StsIsDir = 21; + public static final int StsInVal = 22; + public static final int StsFBig = 27; + public static final int StsNoSpc = 28; + public static final int StsROFS = 30; + public static final int StsMLink = 31; + public static final int StsNameTooLong = 63; + public static final int StsNotEmpty = 66; + public static final int StsDQuot = 69; + public static final int StsStale = 70; + public static final int StsRemote = 71; + public static final int StsBadHandle = 10001; + public static final int StsNotSync = 10002; + public static final int StsBadCookie = 10003; + public static final int StsNotSupp = 10004; + public static final int StsTooSmall = 10005; + public static final int StsServerFault = 10006; + public static final int StsBadType = 10007; + public static final int StsJukeBox = 10008; + + // Data structure limits + + public static final int FileHandleSize = 32; // can be 64 for NFS v3 + public static final int WriteVerfSize = 8; + public static final int CreateVerfSize = 8; + public static final int CookieVerfSize = 8; + + // File types + + public static final int FileTypeReg = 1; + public static final int FileTypeDir = 2; + public static final int FileTypeBlk = 3; + public static final int FileTypeChr = 4; + public static final int FileTypeLnk = 5; + public static final int FileTypeSock = 6; + public static final int FileTypeFifo = 7; + + // Filesystem properties + + public static final int FileSysLink = 0x0001; // supports hard links + public static final int FileSysSymLink = 0x0002; // supports symbolic links + public static final int FileSysHomogeneuos = 0x0004; // PATHCONF valid for all files + public static final int FileSysCanSetTime = 0x0008; // can set time on server side + + // Access mask + + public static final int AccessRead = 0x0001; + public static final int AccessLookup = 0x0002; + public static final int AccessModify = 0x0004; + public static final int AccessExtend = 0x0008; + public static final int AccessDelete = 0x0010; + public static final int AccessExecute = 0x0020; + public static final int AccessAll = 0x003F; + + // Create mode values + + public static final int CreateUnchecked = 1; + public static final int CreateGuarded = 2; + public static final int CreateExclusive = 3; + + // Write request stable values + + public static final int WriteUnstable = 0; + public static final int WriteDataSync = 1; + public static final int WriteFileSync = 2; + + // Set attributes file timestamp settings + + public static final int DoNotSetTime = 0; + public static final int SetTimeServer = 1; + public static final int SetTimeClient = 2; + + // RPC procedure names + + private static final String[] _procNames = { "Null", "GetAttr", "SetAttr", + "Lookup", "Access", "ReadLink", "Read", "Write", "Create", "MkDir", + "SymLink", "MkNode", "Remove", "RmDir", "Rename", "Link", + "ReadDir", "ReadDirPlus", "FsStat", "FsInfo", "PathConf", "Commit" }; + + /** + * Return a procedure id as a name + * + * @param id + * int + * @return String + */ + public final static String getProcedureName(int id) { + if (id < 0 || id > ProcMax) + return null; + return _procNames[id]; + } + + /** + * Return an error status string for the specified status code + * + * @param sts + * int + * @return String + */ + public static final String getStatusString(int sts) { + String str = null; + + switch (sts) { + case StsSuccess: + str = "Success status"; + break; + case StsAccess: + str = "Access denied"; + break; + case StsBadCookie: + str = "Bad cookie"; + break; + case StsBadHandle: + str = "Bad handle"; + break; + case StsBadType: + str = "Bad type"; + break; + case StsDQuot: + str = "Quota exceeded"; + break; + case StsPerm: + str = "No permission"; + break; + case StsExist: + str = "Already exists"; + break; + case StsFBig: + str = "File too large"; + break; + case StsInVal: + str = "Invalid argument"; + break; + case StsIO: + str = "I/O error"; + break; + case StsIsDir: + str = "Is directory"; + break; + case StsJukeBox: + str = "Jukebox"; + break; + case StsMLink: + str = "Too many hard links"; + break; + case StsNameTooLong: + str = "Name too long"; + break; + case StsNoDev: + str = "No such device"; + break; + case StsNoEnt: + str = "No entity"; + break; + case StsNoSpc: + str = "No space left on device"; + break; + case StsNotSync: + str = "Update synchronization mismatch"; + break; + case StsNotDir: + str = "Not directory"; + break; + case StsNotEmpty: + str = "Not empty"; + break; + case StsNotSupp: + str = "Not supported"; + break; + case StsNxIO: + str = "Nxio"; + break; + case StsRemote: + str = "Too many levels of remote in path"; + break; + case StsROFS: + str = "Readonly filesystem"; + break; + case StsServerFault: + str = "Server fault"; + break; + case StsStale: + str = "Stale"; + break; + case StsTooSmall: + str = "Too small"; + break; + case StsXDev: + str = "Cross device hard link attempted"; + break; + } + + return str; + } +} diff --git a/source/java/org/alfresco/filesys/server/oncrpc/nfs/NFSHandle.java b/source/java/org/alfresco/filesys/server/oncrpc/nfs/NFSHandle.java new file mode 100644 index 0000000000..ccb7a11f6f --- /dev/null +++ b/source/java/org/alfresco/filesys/server/oncrpc/nfs/NFSHandle.java @@ -0,0 +1,417 @@ +/* + * Copyright (C) 2005 Alfresco, Inc. + * + * Licensed under the Mozilla Public License version 1.1 + * with a permitted attribution clause. You may obtain a + * copy of the License at + * + * http://www.alfresco.org/legal/license.txt + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the + * License. + */ +package org.alfresco.filesys.server.oncrpc.nfs; + +import org.alfresco.filesys.server.oncrpc.RpcPacket; +import org.alfresco.filesys.util.DataPacker; + +/** + * NFS Handle Class + * + *

Contains constants and static methods used with NFS handles. + * + * @author GKSpencer + */ +public class NFSHandle { + + // Version + + public static final byte VERSION = 1; + public static final byte MIN_VERSION = 1; + public static final byte MAX_VERSION = 1; + + // Handle types + + public static final byte TYPE_SHARE = 1; + public static final byte TYPE_DIR = 2; + public static final byte TYPE_FILE = 3; + + // Offsets to fields within the handle + + private static final int VERSION_OFFSET = 0; + private static final int TYPE_OFFSET = 1; + private static final int SHARE_OFFSET = 2; + private static final int DIR_OFFSET = 6; + private static final int FILE_OFFSET = 10; + private static final int NAME_OFFSET = 14; + + /** + * Return the handle version + * + * @param handle byte[] + */ + public static final int isVersion(byte[] handle) + { + return (int) handle[0]; + } + + /** + * Return the handle type + * + * @param handle byte[] + * @return int + */ + public static final int isType(byte[] handle) + { + return (int) handle[1]; + } + + /** + * Check if the handle is a share type handle + * + * @param handle byte[] + * @return boolean + */ + public static final boolean isShareHandle(byte[] handle) + { + if (handle[1] == TYPE_SHARE) + return true; + return false; + } + + /** + * Check if the handle is a directory type handle + * + * @param handle byte[] + * @return boolean + */ + public static final boolean isDirectoryHandle(byte[] handle) + { + if (handle[1] == TYPE_DIR) + return true; + return false; + } + + /** + * Check if the handle is a file type handle + * + * @param handle byte[] + * @return boolean + */ + public static final boolean isFileHandle(byte[] handle) + { + if (handle[1] == TYPE_FILE) + return true; + return false; + } + + /** + * Pack a share handle + * + * @param name String + * @param handle byte[] + */ + public static final void packShareHandle(String name, byte[] handle) + { + + // Pack a share handle + + handle[0] = VERSION; + handle[1] = TYPE_SHARE; + + // Pack the hash code of the share name + + DataPacker.putInt(name.hashCode(), handle, SHARE_OFFSET); + + // Null pad the handle + + int pos = SHARE_OFFSET + 4; + + while (pos < handle.length) + handle[pos++] = 0; + } + + /** + * Pack a share handle + * + * @param name String + * @param rpc RpcPacket + * @param hlen int + */ + public static final void packShareHandle(String name, RpcPacket rpc, int hlen) + { + + // Pack a share handle + + rpc.packInt(hlen); + + rpc.packByte(VERSION); + rpc.packByte(TYPE_SHARE); + + // Pack the hash code of the share name + + rpc.packInt(name.hashCode()); + + // Null pad the handle + + rpc.packNulls(hlen - 6); + } + + /** + * Pack a directory handle + * + * @param shareId int + * @param dirId int + * @param handle byte[] + */ + public static final void packDirectoryHandle(int shareId, int dirId, byte[] handle) + { + + // Pack a directory handle + + handle[0] = VERSION; + handle[1] = TYPE_DIR; + + DataPacker.putInt(shareId, handle, 2); + DataPacker.putInt(dirId, handle, 6); + + // Null pad the handle + + for (int i = 10; i < handle.length; i++) + handle[i] = 0; + } + + /** + * Pack a directory handle + * + * @param shareId int + * @param dirId int + * @param rpc RpcPacket + * @param hlen int + */ + public static final void packDirectoryHandle(int shareId, int dirId, RpcPacket rpc, int hlen) + { + + // Pack a directory handle + + rpc.packInt(hlen); + + rpc.packByte(VERSION); + rpc.packByte(TYPE_DIR); + + rpc.packInt(shareId); + rpc.packInt(dirId); + + // Null pad the handle + + rpc.packNulls(hlen - 10); + } + + /** + * Pack a file handle + * + * @param shareId int + * @param dirId int + * @param fileId int + * @param handle byte[] + */ + public static final void packFileHandle(int shareId, int dirId, int fileId, byte[] handle) + { + + // Pack a directory handle + + handle[0] = VERSION; + handle[1] = TYPE_FILE; + + DataPacker.putInt(shareId, handle, 2); + DataPacker.putInt(dirId, handle, 6); + DataPacker.putInt(fileId, handle, 10); + + // Null pad the handle + + for (int i = 14; i < handle.length; i++) + handle[i] = 0; + } + + /** + * Pack a file handle + * + * @param shareId int + * @param dirId int + * @param fileId int + * @param rpc RpcPacket + * @param hlen int + */ + public static final void packFileHandle(int shareId, int dirId, int fileId, RpcPacket rpc, int hlen) + { + + // Pack a directory handle + + rpc.packInt(hlen); + + rpc.packByte(VERSION); + rpc.packByte(TYPE_FILE); + + rpc.packInt(shareId); + rpc.packInt(dirId); + rpc.packInt(fileId); + + // Null pad the handle + + rpc.packNulls(hlen - 14); + } + + /** + * Unpack a share id from a handle + * + * @param handle byte[] + * @return int + */ + public static final int unpackShareId(byte[] handle) + { + + // Check if the handle is a share type handle + + int shareId = -1; + + if (handle[1] == TYPE_SHARE || handle[1] == TYPE_DIR || handle[1] == TYPE_FILE) + { + + // Unpack the share id + + shareId = DataPacker.getInt(handle, 2); + } + + // Return the share id, or -1 if wrong handle type + + return shareId; + } + + /** + * Unpack a directory id from a handle + * + * @param handle byte[] + * @return int + */ + public static final int unpackDirectoryId(byte[] handle) + { + + // Check if the handle is a directory or file type handle + + int dirId = -1; + + if (handle[1] == TYPE_DIR || handle[1] == TYPE_FILE) + { + + // Unpack the directory id + + dirId = DataPacker.getInt(handle, 6); + } + + // Return the directory id, or -1 if wrong handle type + + return dirId; + } + + /** + * Unpack a file id from a handle + * + * @param handle byte[] + * @return int + */ + public static final int unpackFileId(byte[] handle) + { + + // Check if the handle is a file type handle + + int fileId = -1; + + if (handle[1] == TYPE_FILE) + { + + // Unpack the file id + + fileId = DataPacker.getInt(handle, 10); + } + + // Return the file id, or -1 if wrong handle type + + return fileId; + } + + /** + * Return an NFS handle as a string + * + * @param handle byte[] + * @return String + */ + public static final String asString(byte[] handle) + { + + // Check if the handle is a valid type + + StringBuffer str = new StringBuffer(); + str.append("["); + + switch (handle[1]) + { + + // Share/mountpoint type handle + + case TYPE_SHARE: + str.append("Share:0x"); + str.append(Integer.toHexString(DataPacker.getInt(handle, 2))); + break; + + // Directory handle + + case TYPE_DIR: + str.append("Dir:share=0x"); + str.append(Integer.toHexString(DataPacker.getInt(handle, 2))); + str.append(",dir=0x"); + str.append(Integer.toHexString(DataPacker.getInt(handle, 6))); + break; + + // File handle + + case TYPE_FILE: + str.append("File:share=0x"); + str.append(Integer.toHexString(DataPacker.getInt(handle, 2))); + str.append(",dir=0x"); + str.append(Integer.toHexString(DataPacker.getInt(handle, 6))); + str.append(",file=0x"); + str.append(Integer.toHexString(DataPacker.getInt(handle, 10))); + break; + } + + // Return the handle string + + str.append("]"); + return str.toString(); + } + + /** + * Check if a handle is valid + * + * @param handle byte[] + * @return boolean + */ + public static final boolean isValid(byte[] handle) + { + + // Check if the version is valid + + if (handle[0] < MIN_VERSION || handle[0] > MAX_VERSION) + return false; + + // Check if the handle type is valid + + if (handle[1] == TYPE_SHARE || handle[1] == TYPE_DIR || handle[1] == TYPE_FILE) + return true; + return false; + } +} diff --git a/source/java/org/alfresco/filesys/server/oncrpc/nfs/NFSServer.java b/source/java/org/alfresco/filesys/server/oncrpc/nfs/NFSServer.java new file mode 100644 index 0000000000..e7ac903aec --- /dev/null +++ b/source/java/org/alfresco/filesys/server/oncrpc/nfs/NFSServer.java @@ -0,0 +1,5104 @@ +/* + * Copyright (C) 2005 Alfresco, Inc. + * + * Licensed under the Mozilla Public License version 1.1 + * with a permitted attribution clause. You may obtain a + * copy of the License at + * + * http://www.alfresco.org/legal/license.txt + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the + * License. + */ +package org.alfresco.filesys.server.oncrpc.nfs; + +import java.io.*; +import java.util.*; + +import org.alfresco.filesys.server.ServerListener; +import org.alfresco.filesys.server.SrvSession; +import org.alfresco.filesys.server.auth.acl.AccessControl; +import org.alfresco.filesys.server.auth.acl.AccessControlManager; +import org.alfresco.filesys.server.config.ServerConfiguration; +import org.alfresco.filesys.server.core.InvalidDeviceInterfaceException; +import org.alfresco.filesys.server.core.ShareType; +import org.alfresco.filesys.server.core.SharedDevice; +import org.alfresco.filesys.server.core.SharedDeviceList; +import org.alfresco.filesys.server.filesys.AccessDeniedException; +import org.alfresco.filesys.server.filesys.AccessMode; +import org.alfresco.filesys.server.filesys.DiskDeviceContext; +import org.alfresco.filesys.server.filesys.DiskFullException; +import org.alfresco.filesys.server.filesys.DiskInterface; +import org.alfresco.filesys.server.filesys.DiskSizeInterface; +import org.alfresco.filesys.server.filesys.FileAction; +import org.alfresco.filesys.server.filesys.FileAttribute; +import org.alfresco.filesys.server.filesys.FileExistsException; +import org.alfresco.filesys.server.filesys.FileIdInterface; +import org.alfresco.filesys.server.filesys.FileInfo; +import org.alfresco.filesys.server.filesys.FileName; +import org.alfresco.filesys.server.filesys.FileOpenParams; +import org.alfresco.filesys.server.filesys.FileStatus; +import org.alfresco.filesys.server.filesys.FileType; +import org.alfresco.filesys.server.filesys.NetworkFile; +import org.alfresco.filesys.server.filesys.NotifyChange; +import org.alfresco.filesys.server.filesys.SearchContext; +import org.alfresco.filesys.server.filesys.SrvDiskInfo; +import org.alfresco.filesys.server.filesys.SymbolicLinkInterface; +import org.alfresco.filesys.server.filesys.TreeConnection; +import org.alfresco.filesys.server.filesys.TreeConnectionHash; +import org.alfresco.filesys.server.oncrpc.AuthType; +import org.alfresco.filesys.server.oncrpc.MultiThreadedTcpRpcSessionHandler; +import org.alfresco.filesys.server.oncrpc.MultiThreadedUdpRpcDatagramHandler; +import org.alfresco.filesys.server.oncrpc.PortMapping; +import org.alfresco.filesys.server.oncrpc.Rpc; +import org.alfresco.filesys.server.oncrpc.RpcAuthenticationException; +import org.alfresco.filesys.server.oncrpc.RpcAuthenticator; +import org.alfresco.filesys.server.oncrpc.RpcNetworkServer; +import org.alfresco.filesys.server.oncrpc.RpcPacket; +import org.alfresco.filesys.server.oncrpc.RpcPacketPool; +import org.alfresco.filesys.server.oncrpc.RpcProcessor; +import org.alfresco.filesys.server.oncrpc.RpcRequestThreadPool; + +/** + * NFS Server Class + * + *

Contains the main NFS server. + * + * @author GKSpencer + */ +public class NFSServer extends RpcNetworkServer implements RpcProcessor { + + // Constants + // + // Debug flags + + public static final int DBG_RXDATA = 0x00000001; // Received data + public static final int DBG_TXDATA = 0x00000002; // Transmit data + public static final int DBG_DUMPDATA = 0x00000004; // Dump data packets + public static final int DBG_SEARCH = 0x00000008; // File/directory search + public static final int DBG_INFO = 0x00000010; // Information requests + public static final int DBG_FILE = 0x00000020; // File open/close/info + public static final int DBG_FILEIO = 0x00000040; // File read/write + public static final int DBG_ERROR = 0x00000080; // Errors + public static final int DBG_TIMING = 0x00000100; // Time packet processing + public static final int DBG_DIRECTORY = 0x00000200; // Directory commands + public static final int DBG_SESSION = 0x00000400; // Session creation/deletion + + // Unix path seperator + + public static final String UNIX_SEPERATOR = "/"; + public static final char UNIX_SEPERATOR_CHAR = '/'; + public static final String DOS_SEPERATOR = "\\"; + public static final char DOS_SEPERATOR_CHAR = '\\'; + + // Unix file modes + + public static final int MODE_STFILE = 0100000; + public static final int MODE_STDIR = 0040000; + public static final int MODE_STREAD = 0000555; + public static final int MODE_STWRITE = 0000333; + public static final int MODE_DIR_DEFAULT = MODE_STDIR + (MODE_STREAD | MODE_STWRITE); + public static final int MODE_FILE_DEFAULT = MODE_STFILE + (MODE_STREAD | MODE_STWRITE); + + // Readdir/Readdirplus cookie masks/shift + // + // 32bit cookies (required by Solaris) + + public static final long COOKIE_RESUMEID_MASK = 0x00FFFFFFL; + public static final long COOKIE_SEARCHID_MASK = 0xFF000000L; + public static final int COOKIE_SEARCHID_SHIFT = 24; + + // Cookie ids for . and .. directory entries + + public static final long COOKIE_DOT_DIRECTORY = 0x00FFFFFFL; + public static final long COOKIE_DOTDOT_DIRECTORY = 0x00FFFFFEL; + + // ReadDir and ReadDirPlus reply header and per file fixed structure + // lengths. + // + // Add file name length rounded to 4 byte boundary to the per file structure + // length to get the actual length. + + public final static int READDIRPLUS_HEADER_LENGTH = 108; + public final static int READDIRPLUS_ENTRY_LENGTH = 200; + public final static int READDIR_HEADER_LENGTH = 108; + public final static int READDIR_ENTRY_LENGTH = 24; + + // File id offset + + public static final long FILE_ID_OFFSET = 2L; + + // Maximum request size to accept + + public final static int MaxRequestSize = 0xFFFF; + + // Filesystem limits + + public static final int MaxReadSize = MaxRequestSize; + public static final int PrefReadSize = MaxRequestSize; + public static final int MultReadSize = 4096; + public static final int MaxWriteSize = MaxRequestSize; + public static final int PrefWriteSize = MaxRequestSize; + public static final int MultWriteSize = 4096; + public static final int PrefReadDirSize = 8192; + public static final long MaxFileSize = 0x01FFFFFFF000L; + + // Thread pool and packet pool defaults + + private static final int DefaultThreadPoolSize = 8; + private static final int DefaultPacketPoolSize = 50; + + // Incoming datagram handler for UDP requests + + private MultiThreadedUdpRpcDatagramHandler m_udpHandler; + + // Incoming session handler for TCP requests + + private MultiThreadedTcpRpcSessionHandler m_tcpHandler; + + // Share details hash + + private ShareDetailsHash m_shareDetails; + + // Tree connection hash + + private TreeConnectionHash m_connections; + + // Session tables for the various authentication types + + private NFSSessionTable m_sessAuthNull; + + private NFSSessionTable m_sessAuthUnix; + + // Session id generator + + private int m_sessId = 1; + + // Port to bind the NFS server to (UDP and TCP) + + private int m_port; + + // Shared thread pool, used by TCP and UDP request handlers + + private RpcRequestThreadPool m_threadPool; + + // Shared packet pool, usd by TCP and UDP request handlers + + private RpcPacketPool m_packetPool; + + // RPC authenticator, from the main server configuration + + private RpcAuthenticator m_rpcAuthenticator; + + // Write verifier, generated from the server start time + + private long m_writeVerifier; + + /** + * Class constructor + * + * @param config + * ServerConfiguration + */ + public NFSServer(ServerConfiguration config) { + super("NFS", config); + + // Set the debug flags + + setDebugFlags(config.getNFSDebug()); + + // Set the port to bind the server to + + if (config.getNFSServerPort() != 0) + setPort(config.getNFSServerPort()); + else + setPort(NFS.DefaultPort); + + // Set the RPC authenticator + + m_rpcAuthenticator = config.getRpcAuthenticator(); + + // Generate the write verifier + + m_writeVerifier = System.currentTimeMillis(); + } + + /** + * Return the port to bind to + * + * @return int + */ + public final int getPort() { + return m_port; + } + + /** + * Set the port to use + * + * @param port + * int + */ + public final void setPort(int port) { + m_port = port; + } + + /** + * Start the NFS server + */ + public void startServer() { + + try { + + // Allocate the share detail hash list and tree connection list, and + // populate with the available share details + + m_shareDetails = new ShareDetailsHash(); + m_connections = new TreeConnectionHash(); + + checkForNewShares(); + + // Get the thread pool and packet pool sizes + + int threadPoolSize = DefaultThreadPoolSize; + + if (getConfiguration().getNFSThreadPoolSize() > 0) + threadPoolSize = getConfiguration().getNFSThreadPoolSize(); + + int packetPoolSize = DefaultPacketPoolSize; + + if (getConfiguration().getNFSPacketPoolSize() > 0) + packetPoolSize = getConfiguration().getNFSPacketPoolSize(); + + // Create the share thread pool for RPC processing + + m_threadPool = new RpcRequestThreadPool("NFS", threadPoolSize, this); + + // Create the shared packet pool + + m_packetPool = new RpcPacketPool(MaxRequestSize, packetPoolSize); + + // Create the UDP handler for accepting incoming requests + + m_udpHandler = new MultiThreadedUdpRpcDatagramHandler("Nfsd", "Nfs", this, this, null, getPort(), MaxRequestSize); + + // Use the shared thread pool and packet pool + + m_udpHandler.setThreadPool(m_threadPool); + m_udpHandler.setPacketPool(m_packetPool); + + m_udpHandler.initializeSessionHandler(this); + + // Start the UDP request listener is a seperate thread + + Thread udpThread = new Thread(m_udpHandler); + udpThread.setName("NFS_UDP"); + udpThread.start(); + + // Create the TCP handler for accepting incoming requests + + m_tcpHandler = new MultiThreadedTcpRpcSessionHandler("Nfsd", "Nfs", + this, this, null, getPort(), MaxRequestSize); + + // Use the shared thread pool and packet pool + + m_tcpHandler.setThreadPool(m_threadPool); + m_tcpHandler.setPacketPool(m_packetPool); + + m_tcpHandler.initializeSessionHandler(this); + + // Start the UDP request listener is a seperate thread + + Thread tcpThread = new Thread(m_tcpHandler); + tcpThread.setName("NFS_TCP"); + tcpThread.start(); + + // Register the NFS server with the portmapper + + PortMapping[] mappings = new PortMapping[2]; + mappings[0] = new PortMapping(NFS.ProgramId, NFS.VersionId, Rpc.UDP, m_udpHandler.getPort()); + mappings[1] = new PortMapping(NFS.ProgramId, NFS.VersionId, Rpc.TCP, m_tcpHandler.getPort()); + + registerRPCServer(mappings); + } + catch (Exception ex) { + logger.error(ex); + } + } + + /** + * Shutdown the NFS server + * + * @param immediate + * boolean + */ + public void shutdownServer(boolean immediate) { + + // Unregister the NFS server with the portmapper + + try { + PortMapping[] mappings = new PortMapping[2]; + mappings[0] = new PortMapping(NFS.ProgramId, NFS.VersionId, Rpc.UDP, m_udpHandler.getPort()); + mappings[1] = new PortMapping(NFS.ProgramId, NFS.VersionId, Rpc.TCP, m_tcpHandler.getPort()); + + unregisterRPCServer(mappings); + } + catch (IOException ex) { + logger.error(ex); + } + + // Stop the RPC handlers + + if (m_udpHandler != null) { + m_udpHandler.closeSessionHandler(this); + m_udpHandler = null; + } + + if (m_tcpHandler != null) { + m_tcpHandler.closeSessionHandler(this); + m_tcpHandler = null; + } + + // Stop the thread pool + + m_threadPool.shutdownThreadPool(); + + // Fire a shutdown notification event + + fireServerEvent(ServerListener.ServerShutdown); + } + + /** + * Process an RPC request to the NFS or mount server + * + * @param rpc + * RpcPacket + * @return RpcPacket + * @throws IOException + */ + public RpcPacket processRpc(RpcPacket rpc) throws IOException { + + // Dump the request data + + if (logger.isDebugEnabled() && hasDebugFlag(DBG_DUMPDATA)) + logger.debug("NFS Req=" + rpc.toString()); + + // Validate the request + + int version = rpc.getProgramVersion(); + + if (rpc.getProgramId() != NFS.ProgramId) { + + // Request is not for us + + rpc.buildAcceptErrorResponse(Rpc.StsProgUnavail); + return rpc; + } + else if (version != NFS.VersionId) { + + // Request is not for this version of NFS + + rpc.buildProgramMismatchResponse(NFS.VersionId, NFS.VersionId); + return rpc; + } + + // Find the associated session object for the request, or create a new + // session + + NFSSrvSession nfsSess = null; + + try { + + // Find the associated session, or create a new session + + nfsSess = findSessionForRequest(rpc); + } + catch (RpcAuthenticationException ex) { + + // Failed to authenticate the RPC client + + rpc.buildAuthFailResponse(ex.getAuthenticationErrorCode()); + return rpc; + } + + // Position the RPC buffer pointer at the start of the call parameters + + rpc.positionAtParameters(); + + // Process the RPC request + + RpcPacket response = null; + + try + { + switch (rpc.getProcedureId()) { + + // Null request + + case NFS.ProcNull: + response = procNull(nfsSess, rpc); + break; + + // Get attributes request + + case NFS.ProcGetAttr: + response = procGetAttr(nfsSess, rpc); + break; + + // Set attributes request + + case NFS.ProcSetAttr: + response = procSetAttr(nfsSess, rpc); + break; + + // Lookup request + + case NFS.ProcLookup: + response = procLookup(nfsSess, rpc); + break; + + // Access request + + case NFS.ProcAccess: + response = procAccess(nfsSess, rpc); + break; + + // Read symbolic link request + + case NFS.ProcReadLink: + response = procReadLink(nfsSess, rpc); + break; + + // Read file request + + case NFS.ProcRead: + response = procRead(nfsSess, rpc); + break; + + // Write file request + + case NFS.ProcWrite: + response = procWrite(nfsSess, rpc); + break; + + // Create file request + + case NFS.ProcCreate: + response = procCreate(nfsSess, rpc); + break; + + // Create directory request + + case NFS.ProcMkDir: + response = procMkDir(nfsSess, rpc); + break; + + // Create symbolic link request + + case NFS.ProcSymLink: + response = procSymLink(nfsSess, rpc); + break; + + // Create special device request + + case NFS.ProcMkNode: + response = procMkNode(nfsSess, rpc); + break; + + // Delete file request + + case NFS.ProcRemove: + response = procRemove(nfsSess, rpc); + break; + + // Delete directory request + + case NFS.ProcRmDir: + response = procRmDir(nfsSess, rpc); + break; + + // Rename request + + case NFS.ProcRename: + response = procRename(nfsSess, rpc); + break; + + // Create hard link request + + case NFS.ProcLink: + response = procLink(nfsSess, rpc); + break; + + // Read directory request + + case NFS.ProcReadDir: + response = procReadDir(nfsSess, rpc); + break; + + // Read directory plus request + + case NFS.ProcReadDirPlus: + response = procReadDirPlus(nfsSess, rpc); + break; + + // Filesystem status request + + case NFS.ProcFsStat: + response = procFsStat(nfsSess, rpc); + break; + + // Filesystem information request + + case NFS.ProcFsInfo: + response = procFsInfo(nfsSess, rpc); + break; + + // Retrieve POSIX information request + + case NFS.ProcPathConf: + response = procPathConf(nfsSess, rpc); + break; + + // Commit request + + case NFS.ProcCommit: + response = procCommit(nfsSess, rpc); + break; + } + + // Dump the response + + if (logger.isDebugEnabled() && hasDebugFlag(DBG_DUMPDATA)) + logger.debug("NFS Resp=" + (rpc != null ? rpc.toString() : "")); + + // Commit, or rollback, any active user transaction + + try + { + // Commit or rollback the transaction + + nfsSess.endTransaction(); + } + catch ( Exception ex) + { + // Debug + + if ( logger.isDebugEnabled()) + logger.debug("Error committing transaction", ex); + } + } + finally + { + // If there is an active transaction then roll it back + + if ( nfsSess.hasUserTransaction()) + { + try + { + nfsSess.getUserTransaction().rollback(); + } + catch (Exception ex) + { + logger.warn("Failed to rollback transaction", ex); + } + } + } + + // Return the RPC response + + return response; + } + + /** + * Process the null request + * + * @param sess + * NFSSrvSession + * @param rpc + * RpcPacket + * @return RpcPacket + */ + private final RpcPacket procNull(NFSSrvSession sess, RpcPacket rpc) { + + // Build the response + + rpc.buildResponseHeader(); + return rpc; + } + + /** + * Process the get attributes request + * + * @param sess + * NFSSrvSession + * @param rpc + * RpcPacket + * @return RpcPacket + */ + private final RpcPacket procGetAttr(NFSSrvSession sess, RpcPacket rpc) { + + // Get the handle from the request + + byte[] handle = new byte[NFS.FileHandleSize]; + rpc.unpackByteArrayWithLength(handle); + + // DEBUG + + if (logger.isDebugEnabled() && hasDebugFlag(DBG_INFO)) + logger.debug("GetAttr request from " + rpc.getClientDetails() + + ", handle=" + NFSHandle.asString(handle)); + + // Check if the handle is valid + + if (NFSHandle.isValid(handle) == false) { + + // Return an error status + + rpc.buildErrorResponse(NFS.StsBadHandle); + return rpc; + } + + // Build the response header + + rpc.buildResponseHeader(); + + // Check if this is a share handle + + int shareId = -1; + String path = null; + int errorSts = NFS.StsSuccess; + + // Call the disk share driver to get the file information for the path + + try { + + // Get the share id and path + + shareId = getShareIdFromHandle(handle); + TreeConnection conn = getTreeConnection(sess, shareId); + + // Check if the session has the required access to the shared + // filesystem + + if (conn.hasReadAccess() == false) + throw new AccessDeniedException(); + + // Get the path from the handle + + path = getPathForHandle(sess, handle, conn); + + // Check if the session has the required access to the shared + // filesystem + + if (conn.hasReadAccess() == false) + throw new AccessDeniedException(); + + // Get the disk interface from the disk driver + + DiskInterface disk = (DiskInterface) conn.getSharedDevice() + .getInterface(); + + // Get the file information for the specified path + + FileInfo finfo = disk.getFileInformation(sess, conn, path); + if (finfo != null) { + + // Pack the file information into the NFS attributes structure + + rpc.packInt(NFS.StsSuccess); + packAttributes3(rpc, finfo, shareId); + + // DEBUG + + if (logger.isDebugEnabled() && hasDebugFlag(DBG_INFO)) + logger.debug("GetAttr path=" + path + ", info=" + + finfo); + } + } catch (BadHandleException ex) { + errorSts = NFS.StsBadHandle; + } catch (StaleHandleException ex) { + errorSts = NFS.StsStale; + } catch (AccessDeniedException ex) { + errorSts = NFS.StsAccess; + } catch (Exception ex) { + errorSts = NFS.StsServerFault; + + // DEBUG + + if (logger.isDebugEnabled() && hasDebugFlag(DBG_ERROR)) + logger.debug("GetAttr Exception: " + ex.toString()); + logger.debug(ex); + } + + // Error status + + if (errorSts != NFS.StsSuccess) { + + // Pack the error response + + rpc.buildErrorResponse(errorSts); + + // DEBUG + + if (logger.isDebugEnabled() && hasDebugFlag(DBG_ERROR)) + logger.debug("GetAttr error=" + + NFS.getStatusString(errorSts)); + } + + // Return the attributes + + rpc.setLength(); + return rpc; + } + + /** + * Process the set attributes request + * + * @param sess + * NFSSrvSession + * @param rpc + * RpcPacket + * @return RpcPacket + */ + private final RpcPacket procSetAttr(NFSSrvSession sess, RpcPacket rpc) { + + // Unpack the set attributes parameters + + byte[] handle = new byte[NFS.FileHandleSize]; + rpc.unpackByteArrayWithLength(handle); + + // DEBUG + + if (logger.isDebugEnabled() && hasDebugFlag(DBG_INFO)) + logger.debug("SetAttr request from " + rpc.getClientDetails()); + + // Check if the handle is valid + + if (NFSHandle.isValid(handle) == false) { + rpc.buildErrorResponse(NFS.StsBadHandle); + return rpc; + } + + // Check if this is a share handle + + int shareId = -1; + String path = null; + int errorSts = NFS.StsSuccess; + + // Call the disk share driver to get the file information for the path + + try { + + // Get the share id and path + + shareId = getShareIdFromHandle(handle); + TreeConnection conn = getTreeConnection(sess, shareId); + + // Check if the session has the required access to the shared + // filesystem + + if (conn.hasWriteAccess() == false) + throw new AccessDeniedException(); + + // Get the path from the handle + + path = getPathForHandle(sess, handle, conn); + + // Get the disk interface from the disk driver + + DiskInterface disk = (DiskInterface) conn.getSharedDevice() + .getInterface(); + + // Get the current file information + + FileInfo oldInfo = disk.getFileInformation(sess, conn, path); + + // Get the values to be set for the file/folder + + int setFlags = 0; + int gid = -1; + int uid = -1; + int mode = -1; + long fsize = -1L; + long atime = -1L; + long mtime = -1L; + + // Check if the file mode has been specified + + if (rpc.unpackInt() == Rpc.True) { + mode = rpc.unpackInt(); + setFlags += FileInfo.SetMode; + } + + // Check if the file owner uid has been specified + + if (rpc.unpackInt() == Rpc.True) { + uid = rpc.unpackInt(); + setFlags += FileInfo.SetUid; + } + + // Check if the file group gid has been specified + + if (rpc.unpackInt() == Rpc.True) { + gid = rpc.unpackInt(); + setFlags += FileInfo.SetGid; + } + + // Check if a new file size has been specified + + if (rpc.unpackInt() == Rpc.True) { + fsize = rpc.unpackLong(); + setFlags += FileInfo.SetFileSize; + } + + // Check if the access date/time should be set. It may be set to a + // client specified time + // or using the server time + + int setTime = rpc.unpackInt(); + + if (setTime == NFS.SetTimeClient) { + atime = (long) rpc.unpackInt(); + atime *= 1000L; + rpc.skipBytes(4); // nanoseconds + setFlags += FileInfo.SetAccessDate; + } else if (setTime == NFS.SetTimeServer) { + atime = System.currentTimeMillis(); + setFlags += FileInfo.SetAccessDate; + } + + // Check if the modify date/time should be set. It may be set to a + // client specified time + // or using the server time + + setTime = rpc.unpackInt(); + + if (setTime == NFS.SetTimeClient) { + mtime = (long) rpc.unpackInt(); + mtime *= 1000L; + rpc.skipBytes(4); // nanoseconds + setFlags += FileInfo.SetModifyDate; + } else if (setTime == NFS.SetTimeServer) { + mtime = System.currentTimeMillis(); + setFlags += FileInfo.SetModifyDate; + } + + // Check if any of the file times should be updated + + if (setFlags != 0) { + + // Set the file access/modify date/times + + FileInfo finfo = new FileInfo(); + finfo.setFileInformationFlags(setFlags); + + if (atime != -1L) + finfo.setAccessDateTime(atime); + + if (mtime != -1L) + finfo.setModifyDateTime(mtime); + + // Check if the group id should be set + + if (gid != -1) { + + // Set the group id in the file information + + finfo.setGid(gid); + } + + // Check if the user id should be set + + if (uid != -1) { + + // Set the user id in the file information + + finfo.setUid(uid); + } + + // Check if the mode should be set + + if (mode != -1) { + + // Set the mode in the file information + + finfo.setMode(mode); + } + + // Set the file information + + disk.setFileInformation(sess, conn, path, finfo); + + // DEBUG + + if (logger.isDebugEnabled() && hasDebugFlag(DBG_INFO)) + logger.debug("SetAttr handle=" + + NFSHandle.asString(handle) + ", accessTime=" + + finfo.getAccessDateTime() + ", modifyTime=" + + finfo.getModifyDateTime() + ", mode=" + mode + + ", gid/uid=" + gid + "/" + uid); + } + + // Check if the file size should be updated + + if (fsize != -1L) { + + // Open the file, may be cached + + NetworkFile netFile = getNetworkFileForHandle(sess, handle, + conn, false); + + synchronized (netFile) { + + // Open the network file + + netFile.openFile(false); + + // Change the file size + + disk.truncateFile(sess, conn, netFile, fsize); + + // Close the file + + netFile.close(); + } + + // DEBUG + + if (logger.isDebugEnabled() && hasDebugFlag(DBG_INFO)) + logger.debug("SetAttr handle=" + NFSHandle.asString(handle) + ", newSize=" + fsize); + } + + // Get the updated file information + + FileInfo newInfo = disk.getFileInformation(sess, conn, path); + + // Pack the response + + rpc.buildResponseHeader(); + rpc.packInt(NFS.StsSuccess); + + packWccData(rpc, oldInfo); + packPostOpAttr(sess, newInfo, shareId, rpc); + } catch (BadHandleException ex) { + errorSts = NFS.StsBadHandle; + } catch (StaleHandleException ex) { + errorSts = NFS.StsStale; + } catch (AccessDeniedException ex) { + errorSts = NFS.StsAccess; + } catch (DiskFullException ex) { + errorSts = NFS.StsDQuot; + } catch (Exception ex) { + errorSts = NFS.StsServerFault; + + // DEBUG + + if (logger.isDebugEnabled() && hasDebugFlag(DBG_ERROR)) + logger.debug("SetAttr Exception: " + ex.toString()); + } + + // Check for a failure status + + if (errorSts != NFS.StsSuccess) { + + // Pack the error response + + rpc.buildErrorResponse(errorSts); + packWccData(rpc, null); + + // DEBUG + + if (logger.isDebugEnabled() && hasDebugFlag(DBG_ERROR)) + logger.debug("SetAttr error=" + + NFS.getStatusString(errorSts)); + } + + // Return a the set status + + rpc.setLength(); + return rpc; + } + + /** + * Process the lookup request + * + * @param sess + * NFSSrvSession + * @param rpc + * RpcPacket + * @return RpcPacket + */ + private final RpcPacket procLookup(NFSSrvSession sess, RpcPacket rpc) { + + // Unpack the lookup arguments + + byte[] handle = new byte[NFS.FileHandleSize]; + rpc.unpackByteArrayWithLength(handle); + + String fileName = rpc.unpackString(); + + // DEBUG + + if (logger.isDebugEnabled() && hasDebugFlag(DBG_SEARCH)) + logger.debug("Lookup request from " + rpc.getClientDetails() + ", handle=" + NFSHandle.asString(handle) + ", name=" + fileName); + + // Check if the handle is valid + + if (NFSHandle.isValid(handle) == false) { + rpc.buildErrorResponse(NFS.StsBadHandle); + return rpc; + } + + // Call the disk share driver to get the file information for the path + + int shareId = -1; + String path = null; + int errorSts = NFS.StsSuccess; + + try { + + // Get the share id and path + + shareId = getShareIdFromHandle(handle); + TreeConnection conn = getTreeConnection(sess, shareId); + + // Check if the session has the required access to the shared + // filesystem + + if (conn.hasReadAccess() == false) + throw new AccessDeniedException(); + + // Get the path from the handle + + path = getPathForHandle(sess, handle, conn); + + // Get the disk interface from the disk driver + + DiskInterface disk = (DiskInterface) conn.getSharedDevice().getInterface(); + + // Build the full path string + + String lookupPath = generatePath(path, fileName); + + // Check if the file/directory exists + + if (disk.fileExists(sess, conn, lookupPath) != FileStatus.NotExist) { + + // Get file information for the path + + FileInfo finfo = disk + .getFileInformation(sess, conn, lookupPath); + + if (finfo != null) { + + // Pack the response + + rpc.buildResponseHeader(); + rpc.packInt(NFS.StsSuccess); + + // Pack the file handle + + if (finfo.isDirectory()) + NFSHandle.packDirectoryHandle(shareId, finfo.getFileId(), rpc, NFS.FileHandleSize); + else + NFSHandle.packFileHandle(shareId, getFileIdForHandle(handle), finfo.getFileId(), rpc, NFS.FileHandleSize); + + // Pack the file attributes + + packPostOpAttr(sess, finfo, shareId, rpc); + + // Add a cache entry for the path + + ShareDetails details = m_shareDetails.findDetails(shareId); + + details.getFileIdCache().addPath(finfo.getFileId(), + lookupPath); + + // Check if the file path is a file name only, if so then + // get the parent directory details + + if (pathHasDirectories(fileName) == false + || fileName.equals("..")) { + + // Get the parent directory file information + + FileInfo dirInfo = disk.getFileInformation(sess, conn, + path); + packPostOpAttr(sess, dirInfo, shareId, rpc); + + // Add the path to the file id cache, if the filesystem + // does not support id lookups + + if (details.hasFileIdSupport() == false) + details.getFileIdCache().addPath(dirInfo.getFileId(), path); + } + + // DEBUG + + if (logger.isDebugEnabled() && hasDebugFlag(DBG_SEARCH)) + logger.debug("Lookup path=" + lookupPath + ", finfo=" + finfo.toString()); + } + } else { + + // File does not exist + + errorSts = NFS.StsNoEnt; + } + } + catch (BadHandleException ex) { + errorSts = NFS.StsBadHandle; + } + catch (StaleHandleException ex) { + errorSts = NFS.StsStale; + } + catch (AccessDeniedException ex) { + errorSts = NFS.StsAccess; + } + catch (Exception ex) { + errorSts = NFS.StsServerFault; + + // DEBUG + + if (logger.isDebugEnabled() && hasDebugFlag(DBG_ERROR)) + logger.debug("Lookup Exception: " + ex.toString()); + } + + // Check if an error is being returned + + if (errorSts != NFS.StsSuccess) { + + // Pack the response + + rpc.buildErrorResponse(errorSts); + packPostOpAttr(sess, null, shareId, rpc); + + // DEBUG + + if (logger.isDebugEnabled() && hasDebugFlag(DBG_ERROR)) + logger.debug("Lookup error=" + NFS.getStatusString(errorSts)); + } + + // Return the response + + rpc.setLength(); + return rpc; + } + + /** + * Process the access request + * + * @param sess + * NFSSrvSession + * @param rpc + * RpcPacket + * @return RpcPacket + */ + private final RpcPacket procAccess(NFSSrvSession sess, RpcPacket rpc) { + + // Get the parameters from the request + + byte[] handle = new byte[NFS.FileHandleSize]; + rpc.unpackByteArrayWithLength(handle); + + int accessMode = rpc.unpackInt(); + + // DEBUG + + if (logger.isDebugEnabled() && hasDebugFlag(DBG_INFO)) + logger.debug("Access request from " + rpc.getClientDetails() + + ", handle=" + NFSHandle.asString(handle) + ", access=0x" + + Integer.toHexString(accessMode)); + + // Check if the handle is valid + + if (NFSHandle.isValid(handle) == false) { + + // Return an error status + + rpc.buildErrorResponse(NFS.StsBadHandle); + return rpc; + } + + // Check if this is a share handle + + int shareId = -1; + String path = null; + int errorSts = NFS.StsSuccess; + + // Call the disk share driver to get the file information for the path + + try { + + // Get the share id and path + + shareId = getShareIdFromHandle(handle); + TreeConnection conn = getTreeConnection(sess, shareId); + path = getPathForHandle(sess, handle, conn); + + // Get the disk interface from the disk driver + + DiskInterface disk = (DiskInterface) conn.getSharedDevice().getInterface(); + + // Get the file information for the specified path + + FileInfo finfo = disk.getFileInformation(sess, conn, path); + if (finfo != null) { + + // Check the access that the session has to the filesystem + + int mask = 0; + + if (conn.hasWriteAccess()) { + + // Set the mask to allow all operations + + mask = NFS.AccessAll; + } + else if (conn.hasReadAccess()) { + + // Set the mask for read-only operations + + mask = NFS.AccessRead + NFS.AccessLookup + NFS.AccessExecute; + } + + // Pack the response + + rpc.buildResponseHeader(); + rpc.packInt(NFS.StsSuccess); + + packPostOpAttr(sess, finfo, shareId, rpc); + rpc.packInt(accessMode & mask); + + // DEBUG + + if (logger.isDebugEnabled() && hasDebugFlag(DBG_INFO)) + logger.debug("Access path=" + path + ", info=" + finfo); + } else { + + // Return an error status + + errorSts = NFS.StsNoEnt; + } + } catch (BadHandleException ex) { + errorSts = NFS.StsBadHandle; + } catch (StaleHandleException ex) { + errorSts = NFS.StsStale; + } catch (Exception ex) { + errorSts = NFS.StsServerFault; + + // DEBUG + + if (logger.isDebugEnabled() && hasDebugFlag(DBG_ERROR)) + logger.debug("Access3 Exception: " + ex.toString()); + } + + // Check for an error status + + if (errorSts != NFS.StsSuccess) { + rpc.buildErrorResponse(errorSts); + packPostOpAttr(sess, null, shareId, rpc); + + // DEBUG + + if (logger.isDebugEnabled() && hasDebugFlag(DBG_ERROR)) + logger.debug("Access error=" + NFS.getStatusString(errorSts)); + } + + // Return the response + + rpc.setLength(); + return rpc; + } + + /** + * Process the read link request + * + * @param sess + * NFSSrvSession + * @param rpc + * RpcPacket + * @return RpcPacket + */ + private final RpcPacket procReadLink(NFSSrvSession sess, RpcPacket rpc) { + + // Unpack the read link arguments + + byte[] handle = new byte[NFS.FileHandleSize]; + rpc.unpackByteArrayWithLength(handle); + + // Check if the handle is valid + + if (NFSHandle.isValid(handle) == false) { + rpc.buildErrorResponse(NFS.StsBadHandle); + return rpc; + } + + // Build the response header + + rpc.buildResponseHeader(); + + // Call the disk share driver to read the symbolic link data + + int shareId = -1; + String path = null; + int errorSts = NFS.StsSuccess; + + try { + + // Get the share id and path + + shareId = getShareIdFromHandle(handle); + + TreeConnection conn = getTreeConnection(sess, shareId); + path = getPathForHandle(sess, handle, conn); + + // Check if the filesystem supports symbolic links + + if ((conn.getInterface() instanceof SymbolicLinkInterface) == false) { + + // Symbolic links not supported on this filesystem + + rpc.buildErrorResponse(NFS.StsNotSupp); + packPostOpAttr(sess, null, 0, rpc); + packWccData(rpc, null); + + rpc.setLength(); + return rpc; + } + + // Get the disk interface from the disk driver + + DiskInterface disk = (DiskInterface) conn.getSharedDevice() + .getInterface(); + + // Get the file information for the symbolic link + + FileInfo finfo = disk.getFileInformation(sess, conn, path); + if (finfo != null && finfo.isFileType() == FileType.SymbolicLink) { + + // Get the symbolic link data + + SymbolicLinkInterface symLinkInterface = (SymbolicLinkInterface) disk; + String linkData = symLinkInterface.readSymbolicLink(sess, conn, + path); + + // Pack the read link response + + rpc.packInt(NFS.StsSuccess); + packPostOpAttr(sess, finfo, shareId, rpc); + rpc.packString(linkData); + } else { + + // Return an error status, not a symbolic link + + errorSts = NFS.StsInVal; + } + + } catch (BadHandleException ex) { + errorSts = NFS.StsBadHandle; + } catch (StaleHandleException ex) { + errorSts = NFS.StsStale; + } catch (AccessDeniedException ex) { + errorSts = NFS.StsAccess; + } catch (Exception ex) { + errorSts = NFS.StsServerFault; + + // DEBUG + + if (logger.isDebugEnabled() && hasDebugFlag(DBG_ERROR)) + logger.debug("ReadLink Exception: " + ex.toString()); + } + + // Error status + + if (errorSts != NFS.StsSuccess) { + + // Pack the error response + + rpc.buildErrorResponse(errorSts); + + // DEBUG + + if (logger.isDebugEnabled() && hasDebugFlag(DBG_ERROR)) + logger.debug("ReadLink error=" + + NFS.getStatusString(errorSts)); + } + + // Return the response + + rpc.setLength(); + return rpc; + } + + /** + * Process the read file request + * + * @param sess + * NFSSrvSession + * @param rpc + * RpcPacket + * @return RpcPacket + */ + private final RpcPacket procRead(NFSSrvSession sess, RpcPacket rpc) { + + // Unpack the read parameters + + byte[] handle = new byte[NFS.FileHandleSize]; + rpc.unpackByteArrayWithLength(handle); + + long offset = rpc.unpackLong(); + int count = rpc.unpackInt(); + + // DEBUG + + if (logger.isDebugEnabled() && hasDebugFlag(DBG_FILEIO)) + logger.debug("[NFS] Read request " + rpc.getClientDetails() + + ", count=" + count + ", pos=" + offset); + + // Call the disk share driver to read the file + + int shareId = -1; + NetworkFile netFile = null; + int errorSts = NFS.StsSuccess; + + try { + + // Get the share id and associated shared device + + shareId = getShareIdFromHandle(handle); + TreeConnection conn = getTreeConnection(sess, shareId); + + // Check if the session has the required access to the shared + // filesystem + + if (conn.hasReadAccess() == false) + throw new AccessDeniedException(); + + // Get the network file, it may be cached + + netFile = getNetworkFileForHandle(sess, handle, conn, true); + + // Get the disk interface from the disk driver + + DiskInterface disk = (DiskInterface) conn.getSharedDevice() + .getInterface(); + + // Pack the start of the response + + rpc.buildResponseHeader(); + rpc.packInt(NFS.StsSuccess); + + // Get file information for the path and pack into the reply + + FileInfo finfo = disk.getFileInformation(sess, conn, netFile.getFullName()); + packPostOpAttr(sess, finfo, shareId, rpc); + + // Save the current position in the response buffer to fill in the + // length and end of file flag after + // the read. + + int bufPos = rpc.getPosition(); + + // Read the network file + + int rdlen = -1; + + synchronized (netFile) { + + // Make sure the network file is open + + if (netFile.isClosed()) + netFile.openFile(false); + + // Read a block of data from the file + + rdlen = disk.readFile(sess, conn, netFile, rpc.getBuffer(), + bufPos + 12, count, offset); + } + + // Set the read length and end of file flag + + rpc.packInt(rdlen); + rpc.packInt(rdlen < count ? Rpc.True : Rpc.False); + rpc.packInt(rdlen); + + // Set the response length + + rpc.setLength(bufPos + 12 + ((rdlen + 3) & 0xFFFFFFFC)); + + // DEBUG + + if (logger.isDebugEnabled() && hasDebugFlag(DBG_FILEIO)) + logger.debug("Read fid=" + netFile.getFileId() + ", name=" + netFile.getName() + ", rdlen=" + rdlen); + } + catch (BadHandleException ex) { + errorSts = NFS.StsBadHandle; + } + catch (StaleHandleException ex) { + errorSts = NFS.StsStale; + } + catch (AccessDeniedException ex) { + errorSts = NFS.StsAccess; + } + catch (Exception ex) { + errorSts = NFS.StsServerFault; + + // DEBUG + + if (logger.isDebugEnabled() && hasDebugFlag(DBG_ERROR)) { + logger.debug("Read Exception: netFile=" + netFile + ", cache=" + sess.getFileCache().numberOfEntries()); + logger.debug(ex); + } + } + + // Check for an error status + + if (errorSts != NFS.StsSuccess) { + + // Pack the error response + + rpc.buildErrorResponse(errorSts); + packPostOpAttr(sess, null, shareId, rpc); + + // DEBUG + + if (logger.isDebugEnabled() && hasDebugFlag(DBG_ERROR)) + logger.debug("Read error=" + NFS.getStatusString(errorSts)); + } + + // Return the response + + return rpc; + } + + /** + * Process the write file request + * + * @param sess + * NFSSrvSession + * @param rpc + * RpcPacket + * @return RpcPacket + */ + private final RpcPacket procWrite(NFSSrvSession sess, RpcPacket rpc) { + + // Unpack the read parameters + + byte[] handle = new byte[NFS.FileHandleSize]; + rpc.unpackByteArrayWithLength(handle); + + long offset = rpc.unpackLong(); + int count = rpc.unpackInt(); + int stable = rpc.unpackInt(); + + // Skip the second write length, position at the start of the data to + // write + + rpc.skipBytes(4); + + // DEBUG + + if (logger.isDebugEnabled() && hasDebugFlag(DBG_FILEIO)) + logger.debug("Write request from " + rpc.getClientDetails() + " , count=" + count + ", offset=" + offset); + + // Call the disk share driver to write to the file + + int shareId = -1; + String path = null; + NetworkFile netFile = null; + int errorSts = NFS.StsSuccess; + + try { + + // Get the share id and associated shared device + + shareId = getShareIdFromHandle(handle); + TreeConnection conn = getTreeConnection(sess, shareId); + + // Check if the session has the required access to the shared + // filesystem + + if (conn.hasWriteAccess() == false) + throw new AccessDeniedException(); + + // Get the network file, it may be cached + + netFile = getNetworkFileForHandle(sess, handle, conn, false); + + // Get the file path + + path = getPathForHandle(sess, handle, conn); + + // Get the disk interface from the disk driver + + DiskInterface disk = (DiskInterface) conn.getSharedDevice() + .getInterface(); + + // Check if threaded writes should be used + + FileInfo preInfo = null; + + synchronized (netFile) { + + // Make sure the network file is open + + if (netFile.isClosed()) + netFile.openFile(false); + + // Get the pre-operation file details + + preInfo = disk.getFileInformation(sess, conn, path); + + // Write to the network file + + disk.writeFile(sess, conn, netFile, rpc.getBuffer(), rpc + .getPosition(), count, offset); + } + + // Get file information for the path and pack the response + + FileInfo finfo = disk.getFileInformation(sess, conn, path); + + rpc.buildResponseHeader(); + rpc.packInt(NFS.StsSuccess); + + packPreOpAttr(sess, preInfo, rpc); + packPostOpAttr(sess, finfo, shareId, rpc); + + rpc.packInt(count); + rpc.packInt(stable); + rpc.packLong(m_writeVerifier); // verifier + + // DEBUG + + if (logger.isDebugEnabled() && hasDebugFlag(DBG_FILEIO)) + logger.debug("Write fid=" + netFile.getFileId() + ", name=" + netFile.getName() + ", wrlen=" + count); + } + catch (BadHandleException ex) { + errorSts = NFS.StsBadHandle; + } + catch (StaleHandleException ex) { + errorSts = NFS.StsStale; + } + catch (AccessDeniedException ex) { + errorSts = NFS.StsAccess; + } + catch (DiskFullException ex) { + errorSts = NFS.StsNoSpc; + } + catch (Exception ex) { + errorSts = NFS.StsServerFault; + + // DEBUG + + if (logger.isDebugEnabled() && hasDebugFlag(DBG_ERROR)) { + logger.debug("Write Exception: netFile=" + netFile + ", cache=" + sess.getFileCache().numberOfEntries()); + logger.debug(ex); + } + } + + // Check for a failure status + + if (errorSts != NFS.StsSuccess) { + + // Pack the error response + + rpc.buildErrorResponse(errorSts); + packWccData(rpc, null); // before attributes + packWccData(rpc, null); // after attributes + + // DEBUG + + if (logger.isDebugEnabled() && hasDebugFlag(DBG_ERROR)) + logger.debug("Write error=" + + NFS.getStatusString(errorSts)); + } + + // Return the write response + + rpc.setLength(); + return rpc; + } + + /** + * Process the create file request + * + * @param sess + * NFSSrvSession + * @param rpc + * RpcPacket + * @return RpcPacket + */ + private final RpcPacket procCreate(NFSSrvSession sess, RpcPacket rpc) { + + // Unpack the create arguments + + byte[] handle = new byte[NFS.FileHandleSize]; + rpc.unpackByteArrayWithLength(handle); + + String fileName = rpc.unpackString(); + + int createMode = rpc.unpackInt(); + + // DEBUG + + if (logger.isDebugEnabled() && hasDebugFlag(DBG_FILE)) + logger.debug("Create request from " + rpc.getClientDetails() + ", name=" + fileName); + + // Check if the handle is valid + + if (NFSHandle.isValid(handle) == false) { + rpc.buildErrorResponse(NFS.StsBadHandle); + return rpc; + } + + // Call the disk share driver to create the new file + + int shareId = -1; + String path = null; + int errorSts = NFS.StsSuccess; + + try { + + // Get the share id and path + + shareId = getShareIdFromHandle(handle); + + TreeConnection conn = getTreeConnection(sess, shareId); + path = getPathForHandle(sess, handle, conn); + + // Check if the session has the required access to the shared + // filesystem + + if (conn.hasWriteAccess() == false) + throw new AccessDeniedException(); + + // Get the disk interface from the disk driver + + DiskInterface disk = (DiskInterface) conn.getSharedDevice() + .getInterface(); + + // Get the pre-operation state for the parent directory + + FileInfo preInfo = disk.getFileInformation(sess, conn, path); + + // Build the full path string + + StringBuffer str = new StringBuffer(); + str.append(path); + + if (path.endsWith("\\") == false) + str.append("\\"); + str.append(fileName); + + String filePath = str.toString(); + + // Check if the file exists + + int existSts = disk.fileExists(sess, conn, filePath); + if (existSts == FileStatus.FileExists) { + errorSts = NFS.StsExist; + } + else if (existSts == FileStatus.DirectoryExists) { + errorSts = NFS.StsIsDir; + } + else { + + // Get the file permissions + + int gid = -1; + int uid = -1; + int mode = -1; + + if (rpc.unpackInt() == Rpc.True) + mode = rpc.unpackInt(); + + if (rpc.unpackInt() == Rpc.True) + uid = rpc.unpackInt(); + + if (rpc.unpackInt() == Rpc.True) + gid = rpc.unpackInt(); + + // Create a new file + + FileOpenParams params = new FileOpenParams(filePath, FileAction.CreateNotExist, AccessMode.ReadWrite, 0, gid, uid, mode); + NetworkFile netFile = disk.createFile(sess, conn, params); + + // DEBUG + + if (logger.isDebugEnabled() && hasDebugFlag(DBG_FILE)) + logger.debug(" Create file params=" + params); + + // Get file information for the path + + FileInfo finfo = disk.getFileInformation(sess, conn, filePath); + + if (finfo != null) { + + // Pack the response + + rpc.buildResponseHeader(); + rpc.packInt(NFS.StsSuccess); + + if (finfo.isDirectory()) + packDirectoryHandle(shareId, finfo.getFileId(), rpc); + else + packFileHandle(shareId, getFileIdForHandle(handle), + finfo.getFileId(), rpc); + + // Pack the file attributes + + packPostOpAttr(sess, finfo, shareId, rpc); + + // Add a cache entry for the path + + ShareDetails details = m_shareDetails.findDetails(shareId); + details.getFileIdCache().addPath(finfo.getFileId(), + filePath); + + // Add a cache entry for the network file + + sess.getFileCache().addFile(netFile, conn); + + // Pack the wcc data structure for the directory + + packPreOpAttr(sess, preInfo, rpc); + + FileInfo postInfo = disk.getFileInformation(sess, conn, + path); + packPostOpAttr(sess, postInfo, shareId, rpc); + + // DEBUG + + if (logger.isDebugEnabled() && hasDebugFlag(DBG_FILE)) + logger.debug("Create path=" + filePath + + ", finfo=" + finfo.toString()); + + // Notify change listeners that a new file has been created + + DiskDeviceContext diskCtx = (DiskDeviceContext) conn + .getContext(); + + if (diskCtx.hasChangeHandler()) + diskCtx.getChangeHandler().notifyFileChanged( NotifyChange.ActionAdded, filePath); + } + } + } + catch (BadHandleException ex) { + errorSts = NFS.StsBadHandle; + } + catch (StaleHandleException ex) { + errorSts = NFS.StsStale; + } + catch (AccessDeniedException ex) { + errorSts = NFS.StsAccess; + } + catch (Exception ex) { + errorSts = NFS.StsServerFault; + + // DEBUG + + if (logger.isDebugEnabled() && hasDebugFlag(DBG_ERROR)) + logger.debug("Create Exception: " + ex.toString()); + } + + // Check for a failure status + + if (errorSts != NFS.StsSuccess) { + + // Pack the error response + + rpc.buildErrorResponse(errorSts); + packWccData(rpc, null); // before attributes + packWccData(rpc, null); // after attributes + + // DEBUG + + if (logger.isDebugEnabled() && hasDebugFlag(DBG_ERROR)) + logger.debug("Create error=" + + NFS.getStatusString(errorSts)); + } + + // Return the response + + rpc.setLength(); + return rpc; + } + + /** + * Process the create directory request + * + * @param sess + * NFSSrvSession + * @param rpc + * RpcPacket + * @return RpcPacket + */ + private final RpcPacket procMkDir(NFSSrvSession sess, RpcPacket rpc) { + + // Unpack the mkdir arguments + + byte[] handle = new byte[NFS.FileHandleSize]; + rpc.unpackByteArrayWithLength(handle); + + String dirName = rpc.unpackString(); + + // DEBUG + + if (logger.isDebugEnabled() && hasDebugFlag(DBG_DIRECTORY)) + logger.debug("MkDir request from " + rpc.getClientDetails() + ", name=" + dirName); + + // Check if the handle is valid + + if (NFSHandle.isValid(handle) == false) { + rpc.buildErrorResponse(NFS.StsBadHandle); + return rpc; + } + + // Call the disk share driver to create the new directory + + int shareId = -1; + String path = null; + int errorSts = NFS.StsSuccess; + + try { + + // Get the share id and path + + shareId = getShareIdFromHandle(handle); + TreeConnection conn = getTreeConnection(sess, shareId); + path = getPathForHandle(sess, handle, conn); + + // Check if the session has the required access to the shared + // filesystem + + if (conn.hasWriteAccess() == false) + throw new AccessDeniedException(); + + // Get the disk interface from the disk driver + + DiskInterface disk = (DiskInterface) conn.getSharedDevice() + .getInterface(); + + // Get the pre-operation state for the parent directory + + FileInfo preInfo = disk.getFileInformation(sess, conn, path); + + // Build the full path string + + StringBuffer str = new StringBuffer(); + str.append(path); + if (path.endsWith("\\") == false) + str.append("\\"); + str.append(dirName); + String dirPath = str.toString(); + + // Check if the file exists + + int existSts = disk.fileExists(sess, conn, dirPath); + if (existSts != FileStatus.NotExist) { + errorSts = NFS.StsExist; + } else { + + // Get the user id, group id and mode for the new directory + + int gid = -1; + int uid = -1; + int mode = -1; + + if (rpc.unpackInt() == Rpc.True) + mode = rpc.unpackInt(); + + if (rpc.unpackInt() == Rpc.True) + uid = rpc.unpackInt(); + + if (rpc.unpackInt() == Rpc.True) + gid = rpc.unpackInt(); + + // Directory creation parameters + + FileOpenParams params = new FileOpenParams(dirPath, FileAction.CreateNotExist, AccessMode.ReadWrite, + FileAttribute.NTDirectory, gid, uid, mode); + + // Create a new directory + + disk.createDirectory(sess, conn, params); + + // Get file information for the new directory + + FileInfo finfo = disk.getFileInformation(sess, conn, dirPath); + + if (finfo != null) { + + // Pack the response + + rpc.buildResponseHeader(); + rpc.packInt(NFS.StsSuccess); + + packDirectoryHandle(shareId, finfo.getFileId(), rpc); + + // Pack the file attributes + + packPostOpAttr(sess, finfo, shareId, rpc); + + // Add a cache entry for the path + + ShareDetails details = m_shareDetails.findDetails(shareId); + + details.getFileIdCache().addPath(finfo.getFileId(), dirPath); + + // Pack the post operation details for the parent directory + + packWccData(rpc, preInfo); + packPostOpAttr(sess, conn, handle, rpc); + + // Notify change listeners that a new directory has been + // created + + DiskDeviceContext diskCtx = (DiskDeviceContext) conn + .getContext(); + + if (diskCtx.hasChangeHandler()) + diskCtx.getChangeHandler().notifyFileChanged( NotifyChange.ActionAdded, dirPath); + + // DEBUG + + if (logger.isDebugEnabled() && hasDebugFlag(DBG_DIRECTORY)) + logger.debug("Mkdir path=" + dirPath + ", finfo=" + finfo.toString()); + } + } + } + catch (BadHandleException ex) { + errorSts = NFS.StsBadHandle; + } + catch (StaleHandleException ex) { + errorSts = NFS.StsStale; + } + catch (AccessDeniedException ex) { + errorSts = NFS.StsAccess; + } + catch (Exception ex) { + errorSts = NFS.StsServerFault; + + // DEBUG + + if (logger.isDebugEnabled() && hasDebugFlag(DBG_ERROR)) + logger.debug("Mkdir Exception: " + ex.toString()); + } + + // Check for an error status + + if (errorSts != NFS.StsSuccess) { + + // Pack the error response + + rpc.buildErrorResponse(errorSts); + packWccData(rpc, null); + packWccData(rpc, null); + + // DEBUG + + if (logger.isDebugEnabled() && hasDebugFlag(DBG_ERROR)) + logger.debug("Mkdir error=" + NFS.getStatusString(errorSts)); + } + + // Return the response + + rpc.setLength(); + return rpc; + } + + /** + * Process the create symbolic link request + * + * @param sess + * NFSSrvSession + * @param rpc + * RpcPacket + * @return RpcPacket + */ + private final RpcPacket procSymLink(NFSSrvSession sess, RpcPacket rpc) { + + // Unpack the create symbolic link arguments + + byte[] handle = new byte[NFS.FileHandleSize]; + rpc.unpackByteArrayWithLength(handle); + + String fileName = rpc.unpackString(); + + // Check if the handle is valid + + if (NFSHandle.isValid(handle) == false) { + rpc.buildErrorResponse(NFS.StsBadHandle); + return rpc; + } + + // Call the disk share driver to create the symbolic link + + int shareId = -1; + String path = null; + int errorSts = NFS.StsSuccess; + + try { + + // Get the share id and path + + shareId = getShareIdFromHandle(handle); + + TreeConnection conn = getTreeConnection(sess, shareId); + path = getPathForHandle(sess, handle, conn); + + // Check if the filesystem supports symbolic links + + if ((conn.getInterface() instanceof SymbolicLinkInterface) == false) { + + // Symbolic links not supported on this filesystem + + rpc.buildErrorResponse(NFS.StsNotSupp); + packPostOpAttr(sess, null, 0, rpc); + packWccData(rpc, null); + + rpc.setLength(); + return rpc; + } + + // Check if the session has the required access to the shared + // filesystem + + if (conn.hasWriteAccess() == false) + throw new AccessDeniedException(); + + // Get the symbolic link attributes + + int setFlags = 0; + int gid = -1; + int uid = -1; + int mode = -1; + long fsize = -1L; + long atime = -1L; + long mtime = -1L; + + // Check if the file mode has been specified + + if (rpc.unpackInt() == Rpc.True) { + mode = rpc.unpackInt(); + setFlags += FileInfo.SetMode; + } + + // Check if the file owner uid has been specified + + if (rpc.unpackInt() == Rpc.True) { + uid = rpc.unpackInt(); + setFlags += FileInfo.SetUid; + } + + // Check if the file group gid has been specified + + if (rpc.unpackInt() == Rpc.True) { + gid = rpc.unpackInt(); + setFlags += FileInfo.SetGid; + } + + // Check if a new file size has been specified + + if (rpc.unpackInt() == Rpc.True) { + fsize = rpc.unpackLong(); + setFlags += FileInfo.SetFileSize; + } + + // Check if the access date/time should be set. It may be set to a + // client specified time + // or using the server time + + int setTime = rpc.unpackInt(); + + if (setTime == NFS.SetTimeClient) { + atime = (long) rpc.unpackInt(); + atime *= 1000L; + rpc.skipBytes(4); // nanoseconds + setFlags += FileInfo.SetAccessDate; + } + else if (setTime == NFS.SetTimeServer) { + atime = System.currentTimeMillis(); + setFlags += FileInfo.SetAccessDate; + } + + // Check if the modify date/time should be set. It may be set to a + // client specified time + // or using the server time + + setTime = rpc.unpackInt(); + + if (setTime == NFS.SetTimeClient) { + mtime = (long) rpc.unpackInt(); + mtime *= 1000L; + rpc.skipBytes(4); // nanoseconds + setFlags += FileInfo.SetModifyDate; + } + else if (setTime == NFS.SetTimeServer) { + mtime = System.currentTimeMillis(); + setFlags += FileInfo.SetModifyDate; + } + + // Get the symbolic link name + + String linkName = rpc.unpackString(); + + // DEBUG + + if (logger.isDebugEnabled() && hasDebugFlag(DBG_FILE)) + logger.debug("Symbolic link request from " + rpc.getClientDetails() + ", name=" + fileName + ", link=" + linkName); + + // Get the disk interface from the disk driver + + DiskInterface disk = (DiskInterface) conn.getSharedDevice() + .getInterface(); + + // Get the pre-operation state for the parent directory + + FileInfo preInfo = disk.getFileInformation(sess, conn, path); + + // Build the full path string + + StringBuffer str = new StringBuffer(); + str.append(path); + + if (path.endsWith("\\") == false) + str.append("\\"); + str.append(fileName); + + String filePath = str.toString(); + + // Check if the file exists + + int existSts = disk.fileExists(sess, conn, filePath); + if (existSts == FileStatus.FileExists) { + errorSts = NFS.StsExist; + } + else if (existSts == FileStatus.DirectoryExists) { + errorSts = NFS.StsIsDir; + } + else { + + // Create a new symbolic + + FileOpenParams params = new FileOpenParams(filePath, FileAction.CreateNotExist, AccessMode.ReadWrite, 0, gid, uid, mode); + params.setSymbolicLink(linkName); + + NetworkFile netFile = disk.createFile(sess, conn, params); + + // DEBUG + + if (logger.isDebugEnabled() && hasDebugFlag(DBG_FILE)) + logger.debug(" Symbolic link params=" + params); + + // Get file information for the path + + FileInfo finfo = disk.getFileInformation(sess, conn, filePath); + + if (finfo != null) { + + // Pack the response + + rpc.buildResponseHeader(); + rpc.packInt(NFS.StsSuccess); + + packFileHandle(shareId, getFileIdForHandle(handle), finfo + .getFileId(), rpc); + + // Pack the file attributes + + packPostOpAttr(sess, finfo, shareId, rpc); + + // Add a cache entry for the path + + ShareDetails details = m_shareDetails.findDetails(shareId); + details.getFileIdCache().addPath(finfo.getFileId(), filePath); + + // Add a cache entry for the network file + + sess.getFileCache().addFile(netFile, conn); + + // Pack the wcc data structure for the directory + + packPreOpAttr(sess, preInfo, rpc); + + FileInfo postInfo = disk.getFileInformation(sess, conn, + path); + packPostOpAttr(sess, postInfo, shareId, rpc); + + // DEBUG + + if (logger.isDebugEnabled() && hasDebugFlag(DBG_FILE)) + logger.debug("Symbolic link path=" + filePath + ", finfo=" + finfo.toString()); + } + } + } + catch (BadHandleException ex) { + errorSts = NFS.StsBadHandle; + } + catch (StaleHandleException ex) { + errorSts = NFS.StsStale; + } + catch (AccessDeniedException ex) { + errorSts = NFS.StsAccess; + } + catch (Exception ex) { + errorSts = NFS.StsServerFault; + + // DEBUG + + if (logger.isDebugEnabled() && hasDebugFlag(DBG_ERROR)) + logger.debug("SymbolicLink Exception: " + ex.toString()); + } + + // Error status + + if (errorSts != NFS.StsSuccess) { + + // Pack the error response + + rpc.buildErrorResponse(errorSts); + + // DEBUG + + if (logger.isDebugEnabled() && hasDebugFlag(DBG_ERROR)) + logger.debug("SymLink error=" + + NFS.getStatusString(errorSts)); + } + + // Return the response + + rpc.setLength(); + return rpc; + } + + /** + * Process the make special device request + * + * @param sess + * NFSSrvSession + * @param rpc + * RpcPacket + * @return RpcPacket + */ + private final RpcPacket procMkNode(NFSSrvSession sess, RpcPacket rpc) { + + // DEBUG + + if (logger.isDebugEnabled() && hasDebugFlag(DBG_DIRECTORY)) + logger.debug("MkNode request from " + rpc.getClientDetails()); + + // Return an error status + + rpc.buildErrorResponse(NFS.StsNotSupp); + packPostOpAttr(sess, null, 0, rpc); + packWccData(rpc, null); + + rpc.setLength(); + return rpc; + } + + /** + * Process the delete file request + * + * @param sess + * NFSSrvSession + * @param rpc + * RpcPacket + * @return RpcPacket + */ + private final RpcPacket procRemove(NFSSrvSession sess, RpcPacket rpc) { + + // Unpack the remove arguments + + byte[] handle = new byte[NFS.FileHandleSize]; + rpc.unpackByteArrayWithLength(handle); + + String fileName = rpc.unpackString(); + + // DEBUG + + if (logger.isDebugEnabled() && hasDebugFlag(DBG_FILE)) + logger.debug("Remove request from " + rpc.getClientDetails() + ", name=" + fileName); + + // Call the disk share driver to delete the file + + int shareId = -1; + String path = null; + int errorSts = NFS.StsSuccess; + + try { + + // Get the share id and path + + shareId = getShareIdFromHandle(handle); + ShareDetails details = m_shareDetails.findDetails(shareId); + TreeConnection conn = getTreeConnection(sess, shareId); + + path = getPathForHandle(sess, handle, conn); + + // Check if the session has the required access to the shared + // filesystem + + if (conn.hasWriteAccess() == false) + throw new AccessDeniedException(); + + // Get the disk interface from the disk driver + + DiskInterface disk = (DiskInterface) conn.getSharedDevice().getInterface(); + + // Get the pre-operation details for the directory + + FileInfo preInfo = disk.getFileInformation(sess, conn, path); + + // Build the full path string + + StringBuffer str = new StringBuffer(); + str.append(path); + if (path.endsWith("\\") == false) + str.append("\\"); + str.append(fileName); + String delPath = str.toString(); + + // Check if the file exists + + int existSts = disk.fileExists(sess, conn, delPath); + if (existSts == FileStatus.NotExist) { + errorSts = NFS.StsNoEnt; + } + else if (existSts == FileStatus.DirectoryExists) { + errorSts = NFS.StsIsDir; + } + else { + + // Get the file information for the file to be deleted + + FileInfo finfo = disk.getFileInformation(sess, conn, delPath); + + // Delete the file + + disk.deleteFile(sess, conn, delPath); + + // Remove the path from the cache + + if (finfo != null) + details.getFileIdCache().deletePath(finfo.getFileId()); + + // Get the post-operation details for the directory + + FileInfo postInfo = disk.getFileInformation(sess, conn, path); + + // Pack the response + + rpc.buildResponseHeader(); + rpc.packInt(NFS.StsSuccess); + + packPreOpAttr(sess, preInfo, rpc); + packPostOpAttr(sess, postInfo, shareId, rpc); + + // Check if there are any file/directory change notify requests + // active + + DiskDeviceContext diskCtx = (DiskDeviceContext) conn + .getContext(); + if (diskCtx.hasChangeHandler()) + diskCtx.getChangeHandler().notifyFileChanged( NotifyChange.ActionRemoved, delPath); + } + } + catch (BadHandleException ex) { + errorSts = NFS.StsBadHandle; + } + catch (StaleHandleException ex) { + errorSts = NFS.StsStale; + } + catch (SecurityException ex) { + errorSts = NFS.StsAccess; + } + catch (AccessDeniedException ex) { + errorSts = NFS.StsAccess; + } + catch (Exception ex) { + errorSts = NFS.StsServerFault; + + // DEBUG + + if (logger.isDebugEnabled() && hasDebugFlag(DBG_ERROR)) + logger.debug("GetAttr Exception: " + ex.toString()); + } + + // Check for an error status + + if (errorSts != NFS.StsSuccess) { + + // Pack the error response + + rpc.buildErrorResponse(errorSts); + packWccData(rpc, null); + packWccData(rpc, null); + + // DEBUG + + if (logger.isDebugEnabled() && hasDebugFlag(DBG_ERROR)) + logger.debug("Remove error=" + NFS.getStatusString(errorSts)); + } + + // Return the remove repsonse + + rpc.setLength(); + return rpc; + } + + /** + * Process the delete directory request + * + * @param sess + * NFSSrvSession + * @param rpc + * RpcPacket + * @return RpcPacket + */ + private final RpcPacket procRmDir(NFSSrvSession sess, RpcPacket rpc) { + + // Unpack the rmdir arguments + + byte[] handle = new byte[NFS.FileHandleSize]; + rpc.unpackByteArrayWithLength(handle); + + String dirName = rpc.unpackString(); + + // DEBUG + + if (logger.isDebugEnabled() && hasDebugFlag(DBG_DIRECTORY)) + logger.debug("RmDir request from " + rpc.getClientDetails() + ", name=" + dirName); + + // Check if the handle is valid + + if (NFSHandle.isValid(handle) == false) { + rpc.buildErrorResponse(NFS.StsBadHandle); + return rpc; + } + + int shareId = -1; + String path = null; + int errorSts = NFS.StsSuccess; + + try { + + // Get the share id and path + + shareId = getShareIdFromHandle(handle); + ShareDetails details = m_shareDetails.findDetails(shareId); + TreeConnection conn = getTreeConnection(sess, shareId); + + // Check if the session has the required access to the shared + // filesystem + + if (conn.hasWriteAccess() == false) + throw new AccessDeniedException(); + + // Build the pre-operation part of the response + + rpc.buildResponseHeader(); + rpc.packInt(NFS.StsSuccess); + + // Pack the pre operation attributes for the parent directory + + packPreOpAttr(sess, conn, handle, rpc); + + // Get the path to be removed + + path = getPathForHandle(sess, handle, conn); + + // Get the disk interface from the disk driver + + DiskInterface disk = (DiskInterface) conn.getSharedDevice().getInterface(); + + // Build the full path string + + StringBuffer str = new StringBuffer(); + str.append(path); + + if (path.endsWith("\\") == false) + str.append("\\"); + str.append(dirName); + + String delPath = str.toString(); + + // Check if the file exists + + int existSts = disk.fileExists(sess, conn, delPath); + if (existSts == FileStatus.NotExist) { + errorSts = NFS.StsNoEnt; + } + else if (existSts == FileStatus.FileExists) { + errorSts = NFS.StsNoEnt; + } + else { + + // Get the file information for the directory to be deleted + + FileInfo finfo = disk.getFileInformation(sess, conn, delPath); + + // Delete the directory + + disk.deleteDirectory(sess, conn, delPath); + + // Remove the path from the cache + + if (finfo != null) + details.getFileIdCache().deletePath(finfo.getFileId()); + + // Pack the post operation attributes for the parent directory + + packPostOpAttr(sess, conn, handle, rpc); + + // Check if there are any file/directory change notify requests + // active + + DiskDeviceContext diskCtx = (DiskDeviceContext) conn.getContext(); + if (diskCtx.hasChangeHandler()) + diskCtx.getChangeHandler().notifyFileChanged( NotifyChange.ActionRemoved, delPath); + } + } + catch (BadHandleException ex) { + errorSts = NFS.StsBadHandle; + } + catch (StaleHandleException ex) { + errorSts = NFS.StsStale; + } + catch (SecurityException ex) { + errorSts = NFS.StsAccess; + } + catch (AccessDeniedException ex) { + errorSts = NFS.StsAccess; + } + catch (Exception ex) { + errorSts = NFS.StsServerFault; + + // DEBUG + + if (logger.isDebugEnabled() && hasDebugFlag(DBG_ERROR)) + logger.debug("Rmdir Exception: " + ex.toString()); + } + + // Check if an error status is being returned + + if (errorSts != NFS.StsSuccess) { + + // Pack the error response + + rpc.buildErrorResponse(errorSts); + packWccData(rpc, null); + packWccData(rpc, null); + + // DEBUG + + if (logger.isDebugEnabled() && hasDebugFlag(DBG_ERROR)) + logger.debug("Rmdir error=" + NFS.getStatusString(errorSts)); + } + + // Return the response + + rpc.setLength(); + return rpc; + } + + /** + * Process the rename file request + * + * @param sess + * NFSSrvSession + * @param rpc + * RpcPacket + * @return RpcPacket + */ + private final RpcPacket procRename(NFSSrvSession sess, RpcPacket rpc) { + + // Unpack the rename arguments + + byte[] fromHandle = new byte[NFS.FileHandleSize]; + rpc.unpackByteArrayWithLength(fromHandle); + + String fromName = rpc.unpackString(); + + byte[] toHandle = new byte[NFS.FileHandleSize]; + rpc.unpackByteArrayWithLength(toHandle); + + String toName = rpc.unpackString(); + + // DEBUG + + if (logger.isDebugEnabled() && hasDebugFlag(DBG_FILE)) { + logger.debug("Rename request from " + rpc.getClientDetails() + ", fromHandle=" + NFSHandle.asString(fromHandle) + + ", fromname=" + fromName); + logger.debug(" tohandle=" + NFSHandle.asString(toHandle) + ", toname=" + toName); + } + + // Call the disk share driver to rename the file/directory + + int shareId = -1; + String fromPath = null; + String toPath = null; + int errorSts = NFS.StsSuccess; + + try { + + // Get the share id and path + + shareId = getShareIdFromHandle(fromHandle); + ShareDetails details = m_shareDetails.findDetails(shareId); + TreeConnection conn = getTreeConnection(sess, shareId); + + // Check if the session has the required access to the shared + // filesystem + + if (conn.hasWriteAccess() == false) + throw new AccessDeniedException(); + + // Get paths from the handles + + fromPath = getPathForHandle(sess, fromHandle, conn); + toPath = getPathForHandle(sess, toHandle, conn); + + // Build the full path string for the old name + + StringBuffer str = new StringBuffer(); + str.append(fromPath); + + if (fromPath.endsWith("\\") == false) + str.append("\\"); + str.append(fromName); + + String oldPath = str.toString(); + + // Build the full path string for the new name + + str.setLength(0); + str.append(toPath); + + if (toPath.endsWith("\\") == false) + str.append("\\"); + str.append(toName); + + String newPath = str.toString(); + + // Get the disk interface from the disk driver + + DiskInterface disk = (DiskInterface) conn.getSharedDevice() + .getInterface(); + + // Get the pre-operation details for the parent directories + + FileInfo preFromInfo = disk.getFileInformation(sess, conn, fromPath); + FileInfo preToInfo = null; + + if (NFSHandle.unpackDirectoryId(fromHandle) == NFSHandle.unpackDirectoryId(toHandle)) + preToInfo = preFromInfo; + else + preToInfo = disk.getFileInformation(sess, conn, toPath); + + // Check if the from path exists + + int existSts = disk.fileExists(sess, conn, oldPath); + + if (existSts == FileStatus.NotExist) { + errorSts = NFS.StsNoEnt; + } else { + + // DEBUG + + if (logger.isDebugEnabled() && hasDebugFlag(DBG_FILE)) + logger.debug("Rename from=" + oldPath + ", to=" + newPath); + + // Get the file details for the file/folder being renamed + + FileInfo finfo = disk.getFileInformation(sess, conn, oldPath); + + // Rename the file/directory + + disk.renameFile(sess, conn, oldPath, newPath); + + // Remove the original path from the cache + + if (finfo != null && finfo.getFileId() != -1) + details.getFileIdCache().deletePath(finfo.getFileId()); + + // Get the file id for the new file/directory + + finfo = disk.getFileInformation(sess, conn, newPath); + if (finfo != null) + details.getFileIdCache().addPath(finfo.getFileId(), newPath); + + // Check if there are any file/directory change notify requests + // active + + DiskDeviceContext diskCtx = (DiskDeviceContext) conn.getContext(); + if (diskCtx.hasChangeHandler()) + diskCtx.getChangeHandler().notifyRename(oldPath, newPath); + + // Get the post-operation details for the parent directories + + FileInfo postFromInfo = disk.getFileInformation(sess, conn, fromPath); + FileInfo postToInfo = null; + + if (NFSHandle.unpackDirectoryId(fromHandle) == NFSHandle.unpackDirectoryId(toHandle)) + postToInfo = postFromInfo; + else + postToInfo = disk.getFileInformation(sess, conn, toPath); + + // Pack the rename response + + rpc.buildResponseHeader(); + rpc.packInt(NFS.StsSuccess); + + packWccData(rpc, preFromInfo); + packPostOpAttr(sess, postFromInfo, shareId, rpc); + + packWccData(rpc, preToInfo); + packPostOpAttr(sess, postToInfo, shareId, rpc); + } + } + catch (BadHandleException ex) { + errorSts = NFS.StsBadHandle; + } + catch (StaleHandleException ex) { + errorSts = NFS.StsStale; + } + catch (SecurityException ex) { + errorSts = NFS.StsAccess; + } + catch (AccessDeniedException ex) { + errorSts = NFS.StsAccess; + } + catch (FileExistsException ex) { + errorSts = NFS.StsExist; + } + catch (Exception ex) { + errorSts = NFS.StsServerFault; + + // DEBUG + + if (logger.isDebugEnabled() && hasDebugFlag(DBG_ERROR)) + logger.debug("Rename Exception: " + ex.toString()); + } + + // Check for an error status + + if (errorSts != NFS.StsSuccess) { + + // Pack the error response + + rpc.buildErrorResponse(errorSts); + + // Pack the from dir WCC data + + packWccData(rpc, null); + packWccData(rpc, null); + + // Pack the to dir WCC data + + packWccData(rpc, null); + packWccData(rpc, null); + + // DEBUG + + if (logger.isDebugEnabled() && hasDebugFlag(DBG_ERROR)) + logger.debug("Rename error=" + NFS.getStatusString(errorSts)); + } + + // Return the rename response + + rpc.setLength(); + return rpc; + } + + /** + * Process the create hard link request + * + * @param sess + * NFSSrvSession + * @param rpc + * RpcPacket + * @return RpcPacket + */ + private final RpcPacket procLink(NFSSrvSession sess, RpcPacket rpc) { + + // DEBUG + + if (logger.isDebugEnabled() && hasDebugFlag(DBG_RXDATA)) + logger.debug("Link request from " + rpc.getClientDetails()); + + // Return an error status + + rpc.buildErrorResponse(NFS.StsAccess); + packPostOpAttr(sess, null, 0, rpc); + packWccData(rpc, null); + packWccData(rpc, null); + + rpc.setLength(); + return rpc; + } + + /** + * Process the read directory request + * + * @param sess + * NFSSrvSession + * @param rpc + * RpcPacket + * @return RpcPacket + */ + private final RpcPacket procReadDir(NFSSrvSession sess, RpcPacket rpc) { + + // Unpack the read directory arguments + + byte[] handle = new byte[NFS.FileHandleSize]; + rpc.unpackByteArrayWithLength(handle); + + long cookie = rpc.unpackLong(); + long cookieVerf = rpc.unpackLong(); + + int maxCount = rpc.unpackInt(); + + // DEBUG + + if (logger.isDebugEnabled() && hasDebugFlag(DBG_SEARCH)) + logger.debug("ReadDir request from " + rpc.getClientDetails() + + " handle=" + NFSHandle.asString(handle) + ", count=" + + maxCount); + + // Check if this is a share handle + + int shareId = -1; + String path = null; + + int errorSts = NFS.StsSuccess; + + // Call the disk share driver to get the file information for the path + + try { + + // Get the share id and path + + shareId = getShareIdFromHandle(handle); + ShareDetails details = m_shareDetails.findDetails(shareId); + TreeConnection conn = getTreeConnection(sess, shareId); + + // Check if the session has the required access to the shared + // filesystem + + if (conn.hasReadAccess() == false) + throw new AccessDeniedException(); + + // Get the disk interface from the disk driver + + DiskInterface disk = (DiskInterface) conn.getSharedDevice().getInterface(); + + // Get the path from the handle + + path = getPathForHandle(sess, handle, conn); + + // If the filesystem driver cannot convert file ids to relative + // paths we need to build a relative path for + // every file and sub-directory in the search + + StringBuffer pathBuf = null; + int pathLen = 0; + FileIdCache fileCache = details.getFileIdCache(); + + if (details.hasFileIdSupport() == false) { + + // Allocate the buffer for building the relative paths + + pathBuf = new StringBuffer(256); + pathBuf.append(path); + if (path.endsWith("\\") == false) + pathBuf.append("\\"); + + // Set the length of the search path portion of the string + + pathLen = pathBuf.length(); + } + + // Build the response header + + rpc.buildResponseHeader(); + rpc.packInt(NFS.StsSuccess); + + // Get the root directory information + + FileInfo dinfo = disk.getFileInformation(sess, conn, path); + packPostOpAttr(sess, dinfo, shareId, rpc); + + // Generate the search path + + String searchPath = generatePath(path, "*.*"); + + // DEBUG + + if (logger.isDebugEnabled() && hasDebugFlag(DBG_SEARCH)) + logger.debug("ReadDir searchPath=" + searchPath + ", cookie=" + cookie); + + // Check if this is the start of a search + + SearchContext search = null; + long searchId = -1; + + if (cookie == 0) { + + // Start a new search, allocate a search id + + search = disk.startSearch(sess, conn, searchPath, FileAttribute.Directory + FileAttribute.Normal); + + // Allocate a search id for the new search + + searchId = sess.allocateSearchSlot(search); + + // Set the cookie verifier + + cookieVerf = dinfo.getModifyDateTime(); + + // DEBUG + + if (logger.isDebugEnabled() && hasDebugFlag(DBG_SEARCH)) + logger.debug("ReadDir allocated searchId=" + searchId); + } else { + + // Check if the cookie verifier is valid + + if (cookieVerf != 0L && cookieVerf != dinfo.getModifyDateTime()) + throw new BadCookieException(); + + // Retrieve the search from the active search cache + + searchId = (cookie & COOKIE_SEARCHID_MASK) >> COOKIE_SEARCHID_SHIFT; + + // Get the active search + + search = sess.getSearchContext((int) searchId); + + // Check if the search has been closed, if so then restart the + // search + + if (search == null) { + + // Restart the search + + search = disk.startSearch(sess, conn, searchPath, FileAttribute.Directory + FileAttribute.Normal); + + // Allocate a search id for the new search + + searchId = sess.allocateSearchSlot(search); + + // Set the cookie verifier + + cookieVerf = dinfo.getModifyDateTime(); + + // DEBUG + + if (logger.isDebugEnabled() && hasDebugFlag(DBG_SEARCH)) + logger.debug("ReadDir restarted search, searchId=" + searchId); + } + + // Check if the search is at the required restart point + + int resumeId = (int) (cookie & COOKIE_RESUMEID_MASK); + if (search.getResumeId() != resumeId) + search.restartAt(resumeId); + } + + // Pack the cookie verifier + + rpc.packLong(cookieVerf); + + // Check if the search id is valid + + if (searchId == -1) + throw new Exception("Bad search id"); + + // Search id is masked into the top of the file index to make the + // resume cookie + + long searchMask = ((long) searchId) << COOKIE_SEARCHID_SHIFT; + + // Build the return file list + + int entCnt = 0; + + // Loop until the return buffer is full or there are no more files + + FileInfo finfo = new FileInfo(); + + // Check if this is the start of a search, if so then add the '.' + // and '..' entries + + if (cookie == 0) { + + // Add the search directory details, the '.' directory + + rpc.packInt(Rpc.True); + rpc.packLong(dinfo.getFileIdLong() + FILE_ID_OFFSET); + rpc.packString("."); + rpc.packLong(COOKIE_DOT_DIRECTORY); + + // Get the file information for the parent directory + + String parentPath = generatePath(path, ".."); + FileInfo parentInfo = disk.getFileInformation(sess, conn, + parentPath); + + // Add the parent of the search directory, the '..' directory + + rpc.packInt(Rpc.True); + rpc.packLong(parentInfo.getFileIdLong() + FILE_ID_OFFSET); + rpc.packString(".."); + rpc.packLong(COOKIE_DOTDOT_DIRECTORY); + + // Update the entry count and current used reply buffer count + + entCnt = 2; + } + + // Add file/sub-directory entries until there are no more entries or + // the buffer is full + + boolean replyFull = false; + + while (entCnt++ < maxCount && replyFull == false && search.nextFileInfo(finfo)) { + + // Check if the new file entry will fit into the reply buffer + // without exceeding the clients maximum + // reply size + + int entryLen = READDIR_ENTRY_LENGTH + ((finfo.getFileName().length() + 3) & 0xFFFFFFFC); + + if (entryLen > rpc.getAvailableLength() || (rpc.getPosition() + entryLen > maxCount)) { + replyFull = true; + break; + } + + // Fill in the entry details + + rpc.packInt(Rpc.True); + rpc.packLong(finfo.getFileIdLong() + FILE_ID_OFFSET); + rpc.packString(finfo.getFileName()); + rpc.packLong(search.getResumeId() + searchMask); + + // Check if the relative path should be added to the file id + // cache + + if (details.hasFileIdSupport() == false && fileCache.findPath(finfo.getFileId()) == null) { + + // Create a relative path for the current file/sub-directory + // and add to the file id cache + + pathBuf.setLength(pathLen); + pathBuf.append(finfo.getFileName()); + + fileCache.addPath(finfo.getFileId(), pathBuf.toString()); + } + } + + // Indicate no more file entries in this response + + rpc.packInt(Rpc.False); + + // Check if the search is complete + + if (search.hasMoreFiles()) { + + // Indicate that there are more files to be returned + + rpc.packInt(Rpc.False); + } + else { + + // Set the end of search flag + + rpc.packInt(Rpc.True); + + // Close the search, release the search slot + + search.closeSearch(); + sess.deallocateSearchSlot((int) searchId); + + // DEBUG + + if (logger.isDebugEnabled() && hasDebugFlag(DBG_SEARCH)) + logger.debug("ReadDir released searchId=" + searchId); + } + + // DEBUG + + if (logger.isDebugEnabled() && hasDebugFlag(DBG_SEARCH)) + logger.debug("ReadDir return entries=" + entCnt + ", eof=" + + search.hasMoreFiles()); + } + catch (BadHandleException ex) { + errorSts = NFS.StsBadHandle; + } + catch (BadCookieException ex) { + errorSts = NFS.StsBadCookie; + } + catch (AccessDeniedException ex) { + errorSts = NFS.StsAccess; + } + catch (Exception ex) { + errorSts = NFS.StsServerFault; + + // DEBUG + + if (logger.isDebugEnabled() && hasDebugFlag(DBG_ERROR)) { + logger.debug("ReadDir Exception: " + ex.toString()); + logger.debug(ex); + } + } + + // Check for an error status + + if (errorSts != NFS.StsSuccess) { + + // Pack the error response + + rpc.buildErrorResponse(errorSts); + packPostOpAttr(sess, null, shareId, rpc); + + // DEBUG + + if (logger.isDebugEnabled() && hasDebugFlag(DBG_ERROR)) + logger.debug("ReadDir error=" + NFS.getStatusString(errorSts)); + } + + // Return the read directory response + + rpc.setLength(); + return rpc; + } + + /** + * Process the read directory plus request + * + * @param sess + * NFSSrvSession + * @param rpc + * RpcPacket + * @return RpcPacket + */ + private final RpcPacket procReadDirPlus(NFSSrvSession sess, RpcPacket rpc) { + + // Unpack the read directory arguments + + byte[] handle = new byte[NFS.FileHandleSize]; + rpc.unpackByteArrayWithLength(handle); + + long cookie = rpc.unpackLong(); + long cookieVerf = rpc.unpackLong(); + + int maxDir = rpc.unpackInt(); + int maxCount = rpc.unpackInt(); + + // DEBUG + + if (logger.isDebugEnabled() && hasDebugFlag(DBG_SEARCH)) + logger.debug("ReadDir request from " + rpc.getClientDetails() + + " handle=" + NFSHandle.asString(handle) + ", dir=" + + maxDir + ", count=" + maxCount); + + // Check if this is a share handle + + int shareId = -1; + String path = null; + + int errorSts = NFS.StsSuccess; + + // Call the disk share driver to get the file information for the path + + try { + + // Get the share id and path + + shareId = getShareIdFromHandle(handle); + ShareDetails details = m_shareDetails.findDetails(shareId); + TreeConnection conn = getTreeConnection(sess, shareId); + + // Check if the session has the required access to the shared + // filesystem + + if (conn.hasReadAccess() == false) + throw new AccessDeniedException(); + + // Get the disk interface from the disk driver + + DiskInterface disk = (DiskInterface) conn.getSharedDevice().getInterface(); + + // Get the path from the handle + + path = getPathForHandle(sess, handle, conn); + + // If the filesystem driver cannot convert file ids to relative + // paths we need to build a relative path for + // every file and sub-directory in the search + + StringBuffer pathBuf = null; + int pathLen = 0; + FileIdCache fileCache = details.getFileIdCache(); + + if (details.hasFileIdSupport() == false) { + + // Allocate the buffer for building the relative paths + + pathBuf = new StringBuffer(256); + pathBuf.append(path); + if (path.endsWith("\\") == false) + pathBuf.append("\\"); + + // Set the length of the search path portion of the string + + pathLen = pathBuf.length(); + } + + // Build the response header + + rpc.buildResponseHeader(); + rpc.packInt(NFS.StsSuccess); + + // Get the root directory information + + FileInfo dinfo = disk.getFileInformation(sess, conn, path); + packPostOpAttr(sess, dinfo, shareId, rpc); + + // Generate the search path + + String searchPath = generatePath(path, "*.*"); + + // DEBUG + + if (logger.isDebugEnabled() && hasDebugFlag(DBG_SEARCH)) + logger.debug("ReadDirPlus searchPath=" + searchPath + + ", cookie=" + cookie); + + // Check if this is the start of a search + + SearchContext search = null; + long searchId = -1; + + if (cookie == 0L) { + + // Start a new search, allocate a search id + + search = disk.startSearch(sess, conn, searchPath, FileAttribute.Directory + FileAttribute.Normal); + + // Allocate a search id for the new search + + searchId = sess.allocateSearchSlot(search); + + // Set the cookie verifier + + cookieVerf = dinfo.getModifyDateTime(); + + // DEBUG + + if (logger.isDebugEnabled() && hasDebugFlag(DBG_SEARCH)) + logger.debug("ReadDirPlus allocated searchId=" + + searchId); + } + else { + + // Check if the cookie verifier is valid + + if (cookieVerf != 0L && cookieVerf != dinfo.getModifyDateTime()) { + logger.debug("Bad cookie verifier, verf=0x" + + Long.toHexString(cookieVerf) + ", modTime=0x" + + Long.toHexString(dinfo.getModifyDateTime())); + throw new BadCookieException(); + } + + // Retrieve the search from the active search cache + + searchId = (cookie & COOKIE_SEARCHID_MASK) >> COOKIE_SEARCHID_SHIFT; + + // Get the active search + + search = sess.getSearchContext((int) searchId); + + // Check if the search has been closed, if so then restart the + // search + + if (search == null) { + + // Restart the search + + search = disk.startSearch(sess, conn, searchPath, FileAttribute.Directory + FileAttribute.Normal); + + // Allocate a search id for the new search + + searchId = sess.allocateSearchSlot(search); + + // Set the cookie verifier + + cookieVerf = dinfo.getModifyDateTime(); + + // DEBUG + + if (logger.isDebugEnabled() && hasDebugFlag(DBG_SEARCH)) + logger.debug("ReadDirPlus restarted search, searchId=" + searchId); + } + + // Get the search resume id from the cookie + + int resumeId = (int) (cookie & COOKIE_RESUMEID_MASK); + if (search != null && search.getResumeId() != resumeId) + search.restartAt(resumeId); + } + + // Pack the cookie verifier + + rpc.packLong(cookieVerf); + + // Check if the search id is valid + + if (searchId == -1) + throw new Exception("Bad search id"); + + // Search id is masked into the top of the file index to make the + // resume cookie + + long searchMask = ((long) searchId) << COOKIE_SEARCHID_SHIFT; + + // Build the return file list + + int entCnt = 0; + + // Loop until the return buffer is full or there are no more files + + FileInfo finfo = new FileInfo(); + + // Check if this is the start of a search, if so then add the '.' + // and '..' entries + + if (cookie == 0) { + + // Add the search directory details, the '.' directory + + rpc.packInt(Rpc.True); + rpc.packLong(dinfo.getFileIdLong() + FILE_ID_OFFSET); + rpc.packString("."); + rpc.packLong(COOKIE_DOT_DIRECTORY); + + // Fill in the file attributes + + rpc.packInt(Rpc.True); + packAttributes3(rpc, dinfo, shareId); + + // Fill in the file handle + + packDirectoryHandle(shareId, dinfo.getFileId(), rpc); + + // Get the file information for the parent directory + + String parentPath = generatePath(path, ".."); + FileInfo parentInfo = disk.getFileInformation(sess, conn, + parentPath); + + // Add the parent of the search directory, the '..' directory + + rpc.packInt(Rpc.True); + rpc.packLong(parentInfo.getFileIdLong() + FILE_ID_OFFSET); + rpc.packString(".."); + rpc.packLong(COOKIE_DOTDOT_DIRECTORY); + + // Fill in the file attributes + + rpc.packInt(Rpc.True); + packAttributes3(rpc, parentInfo, shareId); + + // Fill in the file handle + + packDirectoryHandle(shareId, parentInfo.getFileId(), rpc); + + // Update the entry count and current used reply buffer count + + entCnt = 2; + } + + // Pack the file entries + + boolean replyFull = false; + + while (entCnt++ < maxDir && replyFull == false && search.nextFileInfo(finfo)) { + + // Check if the new file entry will fit into the reply buffer + // without exceeding the clients maximum + // reply size + + int entryLen = READDIRPLUS_ENTRY_LENGTH + ((finfo.getFileName().length() + 3) & 0xFFFFFFFC); + + if (entryLen > rpc.getAvailableLength() || (rpc.getPosition() + entryLen > maxCount)) { + replyFull = true; + break; + } + + // Fill in the entry details + + rpc.packInt(Rpc.True); + rpc.packLong(finfo.getFileIdLong() + FILE_ID_OFFSET); + rpc.packString(finfo.getFileName()); + rpc.packLong(search.getResumeId() + searchMask); + + // Fill in the file attributes + + rpc.packInt(Rpc.True); + packAttributes3(rpc, finfo, shareId); + + // Fill in the file or directory handle + + if (finfo.isDirectory()) + packDirectoryHandle(shareId, finfo.getFileId(), rpc); + else + packFileHandle(shareId, dinfo.getFileId(), finfo.getFileId(), rpc); + + // Check if the relative path should be added to the file id + // cache + + if (details.hasFileIdSupport() == false && fileCache.findPath(finfo.getFileId()) == null) { + + // Create a relative path for the current file/sub-directory + // and add to the file id cache + + pathBuf.setLength(pathLen); + pathBuf.append(finfo.getFileName()); + + fileCache.addPath(finfo.getFileId(), pathBuf.toString()); + } + + // Reset the file type + + finfo.setFileType(FileType.RegularFile); + } + + // Indicate that there are no more file entries in this response + + rpc.packInt(Rpc.False); + + // Check if the search is complete + + if (search.hasMoreFiles()) { + + // Indicate that there are more files to be returned + + rpc.packInt(Rpc.False); + } else { + + // Set the end of search flag + + rpc.packInt(Rpc.True); + + // Close the search, release the search slot + + search.closeSearch(); + sess.deallocateSearchSlot((int) searchId); + } + + // DEBUG + + if (logger.isDebugEnabled() && hasDebugFlag(DBG_SEARCH)) + logger.debug("ReadDirPlus return entries=" + entCnt + ", eof=" + (search.hasMoreFiles() ? false : true)); + } + catch (BadHandleException ex) { + errorSts = NFS.StsBadHandle; + } + catch (BadCookieException ex) { + errorSts = NFS.StsBadCookie; + } + catch (AccessDeniedException ex) { + errorSts = NFS.StsAccess; + } + catch (Exception ex) { + errorSts = NFS.StsServerFault; + + // DEBUG + + if (logger.isDebugEnabled() && hasDebugFlag(DBG_ERROR)) { + logger.debug("ReadDirPlus Exception: " + ex.toString()); + logger.debug(ex); + } + } + + // Check for an error status + + if (errorSts != NFS.StsSuccess) { + + // Pack the error response + + rpc.buildErrorResponse(errorSts); + packPostOpAttr(sess, null, shareId, rpc); + + // DEBUG + + if (logger.isDebugEnabled() && hasDebugFlag(DBG_ERROR)) + logger.debug("ReadDir error=" + NFS.getStatusString(errorSts)); + } + + // Return the read directory plus response + + rpc.setLength(); + return rpc; + } + + /** + * Process the filesystem status request + * + * @param sess + * NFSSrvSession + * @param rpc + * RpcPacket + * @return RpcPacket + */ + private final RpcPacket procFsStat(NFSSrvSession sess, RpcPacket rpc) { + + // Get the handle from the request + + byte[] handle = new byte[NFS.FileHandleSize]; + rpc.unpackByteArrayWithLength(handle); + + // DEBUG + + if (logger.isDebugEnabled() && hasDebugFlag(DBG_INFO)) + logger.debug("FsInfo request from " + rpc.getClientDetails()); + + // Call the disk share driver to get the disk size information + + int shareId = -1; + int errorSts = NFS.StsSuccess; + + try { + + // Get the share id + + shareId = getShareIdFromHandle(handle); + + // Get the required disk driver/tree connection + + TreeConnection conn = getTreeConnection(sess, shareId); + + // Check if the session has the required access to the shared + // filesystem + + if (conn.hasReadAccess() == false) + throw new AccessDeniedException(); + + // Get the static disk information from the context, if available + + DiskDeviceContext diskCtx = (DiskDeviceContext) conn.getContext(); + SrvDiskInfo diskInfo = diskCtx.getDiskInformation(); + + // If we did not get valid disk information from the device context + // check + // if the driver implements the + // disk sizing interface + + if (diskInfo == null) + diskInfo = new SrvDiskInfo(); + + // Get the disk interface from the disk driver + + DiskInterface disk = (DiskInterface) conn.getSharedDevice().getInterface(); + + // Check if the driver implements the dynamic sizing interface to + // get + // realtime disk size information + + if (disk instanceof DiskSizeInterface) { + + // Get the dynamic disk sizing information + + DiskSizeInterface sizeInterface = (DiskSizeInterface) disk; + sizeInterface.getDiskInformation(diskCtx, diskInfo); + } + + // Calculate the disk size information + + // int unitSize = diskInfo.getBlockSize() * + // diskInfo.getBlocksPerAllocationUnit(); + + // Get the file details for the root directory + + String rootPath = getPathForHandle(sess, handle, conn); + FileInfo rootInfo = disk.getFileInformation(sess, conn, rootPath); + + // Pack the response + + rpc.buildResponseHeader(); + rpc.packInt(NFS.StsSuccess); + + packPostOpAttr(sess, rootInfo, shareId, rpc); + + // Calculate the total/free disk space in bytes + + long totalSize = ((long) diskInfo.getDiskSizeKb()) * 1024L; + long freeSize = ((long) diskInfo.getDiskFreeSizeKb()) * 1024L; + + // Pack the total size, free size and space available to the user + + rpc.packLong(totalSize); + rpc.packLong(freeSize); + rpc.packLong(freeSize); + + // Total/free file slots in the file system, assume one file per 1Kb + // of + // space + + long totalSlots = diskInfo.getDiskSizeKb(); + long freeSlots = diskInfo.getDiskFreeSizeKb(); + + // Pack the total slots, free slots and user slots available + + rpc.packLong(totalSlots); + rpc.packLong(freeSlots); + rpc.packLong(freeSlots); + + // Pack the number of seconds for which the file system in not + // expected to + // change + + rpc.packInt(0); + } + catch (SecurityException ex) { + errorSts = NFS.StsAccess; + } + catch (AccessDeniedException ex) { + errorSts = NFS.StsAccess; + } + catch (Exception ex) { + errorSts = NFS.StsServerFault; + } + + // Check for an error status + + if (errorSts != NFS.StsSuccess) { + + // Pack the error response + + rpc.buildErrorResponse(errorSts); + packPostOpAttr(sess, null, shareId, rpc); + + // DEBUG + + if (logger.isDebugEnabled() && hasDebugFlag(DBG_ERROR)) + logger.debug("FsStat error=" + NFS.getStatusString(errorSts)); + } + + // Return the response + + rpc.setLength(); + return rpc; + } + + /** + * Process the filesystem information request + * + * @param sess + * NFSSrvSession + * @param rpc + * RpcPacket + * @return RpcPacket + */ + private final RpcPacket procFsInfo(NFSSrvSession sess, RpcPacket rpc) { + + // Get the handle from the request + + byte[] handle = new byte[NFS.FileHandleSize]; + rpc.unpackByteArrayWithLength(handle); + + // DEBUG + + if (logger.isDebugEnabled() && hasDebugFlag(DBG_INFO)) + logger.debug("[NFS] FsInfo request from " + rpc.getClientDetails()); + + // Check if the handle is valid + + if (NFSHandle.isValid(handle) == false) { + + // Return an error status + + rpc.buildErrorResponse(NFS.StsBadHandle); + return rpc; + } + + // Build the response header + + rpc.buildResponseHeader(); + + // Check if this is a share handle + + int shareId = -1; + int errorSts = NFS.StsSuccess; + + // Pack the filesystem information for the filesystem + + try { + + // Get the share id and path + + shareId = getShareIdFromHandle(handle); + TreeConnection conn = getTreeConnection(sess, shareId); + + // Check if the session has the required access to the shared + // filesystem + + if (conn.hasReadAccess() == false) + throw new AccessDeniedException(); + + // Pack the status code and post op attributes + + rpc.packInt(NFS.StsSuccess); + packPostOpAttr(sess, conn, handle, rpc); + + // Pack the filesystem information + // + // Maximum/preferred read request supported by the server + + rpc.packInt(MaxReadSize); + rpc.packInt(PrefReadSize); + rpc.packInt(MultReadSize); + + // Maximum/preferred write request supported by the server + + rpc.packInt(MaxWriteSize); + rpc.packInt(PrefWriteSize); + rpc.packInt(MultWriteSize); + + // Preferred READDIR request size + + rpc.packInt(PrefReadDirSize); + + // Maximum file size supported + + rpc.packLong(MaxFileSize); + + // Server time resolution, indicate to nearest second + + rpc.packInt(1); // seconds + rpc.packInt(0); // nano-seconds + + // Server properties, check if the filesystem supports symbolic + // links + + int fileSysProps = NFS.FileSysHomogeneuos + NFS.FileSysCanSetTime; + if (conn.getInterface() instanceof SymbolicLinkInterface) + fileSysProps += NFS.FileSysSymLink; + + rpc.packInt(fileSysProps); + } + catch (BadHandleException ex) { + errorSts = NFS.StsBadHandle; + } + catch (StaleHandleException ex) { + errorSts = NFS.StsStale; + } + catch (Exception ex) { + errorSts = NFS.StsServerFault; + + // DEBUG + + if (logger.isDebugEnabled() && hasDebugFlag(DBG_ERROR)) + logger.debug("Access Exception: " + ex.toString()); + } + + // Check for an error status + + if (errorSts != NFS.StsSuccess) { + rpc.buildErrorResponse(errorSts); + packPostOpAttr(sess, null, shareId, rpc); + + // DEBUG + + if (logger.isDebugEnabled() && hasDebugFlag(DBG_ERROR)) + logger.debug("Access error=" + NFS.getStatusString(errorSts)); + } + + // Return the response + + rpc.setLength(); + return rpc; + } + + /** + * Process the retrieve POSIX information request + * + * @param sess + * NFSSrvSession + * @param rpc + * RpcPacket + * @return RpcPacket + */ + private final RpcPacket procPathConf(NFSSrvSession sess, RpcPacket rpc) { + + // Unpack the pathconf arguments + + byte[] handle = new byte[NFS.FileHandleSize]; + rpc.unpackByteArrayWithLength(handle); + + // DEBUG + + if (logger.isDebugEnabled() && hasDebugFlag(DBG_SEARCH)) + logger.debug("PathConf request from " + rpc.getClientDetails() + + " handle=" + NFSHandle.asString(handle)); + + // Call the disk share driver to get the file information for the path + + int shareId = -1; + String path = null; + int errorSts = NFS.StsSuccess; + + try { + + // Get the share id and path + + shareId = getShareIdFromHandle(handle); + TreeConnection conn = getTreeConnection(sess, shareId); + + // Check if the session has the required access to the shared + // filesystem + + if (conn.hasReadAccess() == false) + throw new AccessDeniedException(); + + // Get the path from the handle + + path = getPathForHandle(sess, handle, conn); + + // Get the disk interface from the disk driver + + DiskInterface disk = (DiskInterface) conn.getSharedDevice().getInterface(); + + // Check if the file/directory exists + + if (disk.fileExists(sess, conn, path) != FileStatus.NotExist) { + + // Get file information for the path + + FileInfo finfo = disk.getFileInformation(sess, conn, path); + + // Build the response + + rpc.buildResponseHeader(); + rpc.packInt(NFS.StsSuccess); + + packPostOpAttr(sess, finfo, shareId, rpc); + + // Pack the filesystem options + + rpc.packInt(32767); + rpc.packInt(255); + + rpc.packInt(Rpc.True); // truncate over size names + rpc.packInt(Rpc.True); // chown restricted + rpc.packInt(Rpc.True); // case insensitive + rpc.packInt(Rpc.True); // case preserving + + // DEBUG + + if (logger.isDebugEnabled() && hasDebugFlag(DBG_SEARCH)) + logger.debug("Pathconf path=" + path + ", finfo=" + (finfo != null ? finfo.toString() : "")); + } + else { + + // File does not exist + + errorSts = NFS.StsNoEnt; + } + } + catch (BadHandleException ex) { + errorSts = NFS.StsBadHandle; + } + catch (StaleHandleException ex) { + errorSts = NFS.StsStale; + } + catch (AccessDeniedException ex) { + errorSts = NFS.StsAccess; + } + catch (Exception ex) { + errorSts = NFS.StsServerFault; + + // DEBUG + + if (logger.isDebugEnabled() && hasDebugFlag(DBG_ERROR)) + logger.debug("Pathconf Exception: " + ex.toString()); + } + + // Check if an error is being returned + + if (errorSts != NFS.StsSuccess) { + + // Pack the error response + + rpc.buildErrorResponse(errorSts); + packPostOpAttr(sess, null, shareId, rpc); + + // DEBUG + + if (logger.isDebugEnabled() && hasDebugFlag(DBG_ERROR)) + logger.debug("Pathconf error=" + NFS.getStatusString(errorSts)); + } + + // Return the path information response + + rpc.setLength(); + return rpc; + } + + /** + * Commit request + * + * @param sess + * NFSSrvSession + * @param rpc + * RpcPacket + * @return RpcPacket + */ + private final RpcPacket procCommit(NFSSrvSession sess, RpcPacket rpc) { + + // DEBUG + + if (logger.isDebugEnabled() && hasDebugFlag(DBG_FILEIO)) + logger.debug("Commit request from " + rpc.getClientDetails()); + + // Pack the response + + rpc.buildResponseHeader(); + + rpc.packInt(NFS.StsSuccess); + packWccData(rpc, null); + packPostOpAttr(sess, null, 0, rpc); + + // Pack the write verifier, indicates if the server has been restarted + // since the file write requests + + rpc.packLong(m_writeVerifier); + + // Return the response + + rpc.setLength(); + return rpc; + } + + /** + * Find, or create, the session for the specified RPC request. + * + * @param rpc + * RpcPacket + * @return NFSSrvSession + * @exception RpcAuthenticationException + */ + private final NFSSrvSession findSessionForRequest(RpcPacket rpc) + throws RpcAuthenticationException { + + // Check the authentication type and search the appropriate session + // table for an existing session + + int authType = rpc.getCredentialsType(); + + // Authenticate the request + + Object sessKey = getRpcAuthenticator().authenticateRpcClient(authType, rpc); + + NFSSrvSession sess = null; + + switch (authType) { + + // Null authentication + + case AuthType.Null: + sess = findAuthNullSession(rpc, sessKey); + break; + + // Unix authentication + + case AuthType.Unix: + sess = findAuthUnixSession(rpc, sessKey); + break; + } + + // Setup the authentication context for the request + + getRpcAuthenticator().setCurrentUser( sess, sess.getClientInformation()); + + // Return the server session + + return sess; + } + + /** + * Find, or create, a null authentication session for the specified request + * + * @param rpc + * RpcPacket + * @param sessKey + * Object + * @return NFSSrvSession + */ + private final NFSSrvSession findAuthNullSession(RpcPacket rpc, + Object sessKey) { + + // Check if the null authentication session table is valid + + NFSSrvSession sess = null; + + if (m_sessAuthNull != null) { + + // Search for the required session using the client IP address + + sess = m_sessAuthNull.findSession(sessKey); + } + else { + + // Allocate the null authentication session table + + m_sessAuthNull = new NFSSessionTable(); + } + + // Check if we found the required session object + + if (sess == null) { + + // Create a new session for the request + + sess = new NFSSrvSession(this, rpc.getClientAddress(), rpc.getClientPort(), rpc.getClientProtocol()); + sess.setAuthIdentifier(sessKey); + + // Get the client information from the RPC + + sess.setClientInformation(getRpcAuthenticator().getRpcClientInformation(sessKey, rpc)); + + // Add the new session to the session table + + m_sessAuthNull.addSession(sess); + + // Set the session id and debug output prefix + + sess.setUniqueId("" + sessKey.hashCode()); + sess.setDebug(getConfiguration().getNFSDebug()); + + // DEBUG + + if (logger.isDebugEnabled() && hasDebugFlag(DBG_SESSION)) + logger.debug("[NFS] Added Null session " + sess.getUniqueId()); + } + + // Return the session + + return sess; + } + + /** + * Find, or create, a Unix authentication session for the specified request + * + * @param rpc + * RpcPacket + * @param sessKey + * Object + * @return NFSSrvSession + */ + private final NFSSrvSession findAuthUnixSession(RpcPacket rpc, + Object sessKey) { + + // Check if the Unix authentication session table is valid + + NFSSrvSession sess = null; + + if (m_sessAuthUnix != null) { + + // Search for the required session using the client IP address + gid + // + uid + + sess = m_sessAuthUnix.findSession(sessKey); + } + else { + + // Allocate the Unix authentication session table + + m_sessAuthUnix = new NFSSessionTable(); + } + + // Check if we found the required session object + + if (sess == null) { + + // Create a new session for the request + + sess = new NFSSrvSession(this, rpc.getClientAddress(), rpc.getClientPort(), rpc.getClientProtocol()); + sess.setAuthIdentifier(sessKey); + + // Set the session id and debug output prefix + + sess.setUniqueId("" + sessKey.hashCode()); + sess.setDebug(getConfiguration().getNFSDebug()); + + // Get the client information from the RPC + + sess.setClientInformation(getRpcAuthenticator().getRpcClientInformation(sessKey, rpc)); + + // Add the new session to the session table + + m_sessAuthUnix.addSession(sess); + + // DEBUG + + if (logger.isDebugEnabled() && hasDebugFlag(DBG_SESSION)) + logger.debug("[NFS] Added Unix session " + sess.getUniqueId()); + } + + // Return the session + + return sess; + } + + /** + * Pack the NFS v3 file attributes structure using the file information + * + * @param rpc + * RpcPacket + * @param finfo + * FileInfo + * @param fileSysId + * int + */ + protected final void packAttributes3(RpcPacket rpc, FileInfo finfo, + int fileSysId) { + + // Pack the NFS format file attributes + + if (finfo.isDirectory()) { + + // Pack the directory information + + rpc.packInt(NFS.FileTypeDir); + if (finfo.hasMode()) + rpc.packInt(finfo.getMode()); + else + rpc.packInt(MODE_DIR_DEFAULT); + } else { + + // Pack the file information + + if (finfo.isFileType() == FileType.SymbolicLink) + rpc.packInt(NFS.FileTypeLnk); + else + rpc.packInt(NFS.FileTypeReg); + + if (finfo.hasMode()) + rpc.packInt(finfo.getMode()); + else + rpc.packInt(MODE_FILE_DEFAULT); + } + + // Set various Unix fields + + rpc.packInt(1); // number of links + + rpc.packInt(finfo.hasUid() ? finfo.getUid() : 0); + rpc.packInt(finfo.hasGid() ? finfo.getGid() : 0); + + // Set the size for the file + + if (finfo.isDirectory()) { + + // Pack the directory size/allocation + + rpc.packLong(512L); + rpc.packLong(1024L); + } else { + + // Pack the file size/allocation + + rpc.packLong(finfo.getSize()); + if (finfo.getAllocationSize() != 0) + rpc.packLong(finfo.getAllocationSize()); + else + rpc.packLong(finfo.getSize()); + } + + // Pack the rdev field + + rpc.packInt(0); // specdata1 + rpc.packInt(0); // specdata2 + + // Pack the file id + + long fid = ((long) finfo.getFileId()) & 0x0FFFFFFFFL; + fid += FILE_ID_OFFSET; + + rpc.packLong(fileSysId); + rpc.packLong(fid); // fid + + // Pack the file times + + if (finfo.hasAccessDateTime()) { + rpc.packInt((int) (finfo.getAccessDateTime() / 1000L)); + rpc.packInt(0); + } else + rpc.packLong(0); + + if (finfo.hasModifyDateTime()) { + rpc.packInt((int) (finfo.getModifyDateTime() / 1000L)); + rpc.packInt(0); + } else + rpc.packLong(0); + + if (finfo.hasChangeDateTime()) { + rpc.packInt((int) (finfo.getChangeDateTime() / 1000L)); + rpc.packInt(0); + } else + rpc.packLong(0); + } + + /** + * Pack a share handle + * + * @param shareName + * String + * @param rpc + * RpcPacket + */ + protected final void packShareHandle(String shareName, RpcPacket rpc) { + + // Indicate that a handle follows, pack the handle + + rpc.packInt(Rpc.True); + NFSHandle.packShareHandle(shareName, rpc, NFS.FileHandleSize); + } + + /** + * Pack a directory handle + * + * @param shareId + * int + * @param dirId + * int + * @param rpc + * RpcPacket + */ + protected final void packDirectoryHandle(int shareId, int dirId, + RpcPacket rpc) { + + // Indicate that a handle follows, pack the handle + + rpc.packInt(Rpc.True); + NFSHandle.packDirectoryHandle(shareId, dirId, rpc, NFS.FileHandleSize); + } + + /** + * Pack a directory handle + * + * @param shareId + * int + * @param dirId + * int + * @param fileId + * int + * @param rpc + * RpcPacket + */ + protected final void packFileHandle(int shareId, int dirId, int fileId, + RpcPacket rpc) { + + // Indicate that a handle follows, pack the handle + + rpc.packInt(Rpc.True); + NFSHandle.packFileHandle(shareId, dirId, fileId, rpc, + NFS.FileHandleSize); + } + + /** + * Get the share id from the specified handle + * + * @param handle + * byte[] + * @return int + * @exception BadHandleException + */ + protected final int getShareIdFromHandle(byte[] handle) + throws BadHandleException { + + // Check if this is a share handle + + int shareId = NFSHandle.unpackShareId(handle); + + // Check if the share id is valid + + if (shareId == -1) + throw new BadHandleException(); + + // Return the share id + + return shareId; + } + + /** + * Get the path for the specified handle + * + * @param sess + * NFSSrvSession + * @param handle + * byte[] + * @param tree + * TreeConnection + * @return String + * @exception BadHandleException + * @exception StaleHandleException + */ + protected final String getPathForHandle(NFSSrvSession sess, byte[] handle, + TreeConnection tree) throws BadHandleException, + StaleHandleException { + + // Get the share details via the share id hash + + ShareDetails details = m_shareDetails + .findDetails(getShareIdFromHandle(handle)); + + // Check if this is a share handle + + String path = null; + + int dirId = -1; + int fileId = -1; + + if (NFSHandle.isShareHandle(handle)) { + + // Use the root path + + path = "\\"; + } else if (NFSHandle.isDirectoryHandle(handle)) { + + // Get the directory id from the handle and get the associated path + + dirId = NFSHandle.unpackDirectoryId(handle); + path = details.getFileIdCache().findPath(dirId); + } else if (NFSHandle.isFileHandle(handle)) { + + // Get the file id from the handle and get the associated path + + fileId = NFSHandle.unpackFileId(handle); + path = details.getFileIdCache().findPath(fileId); + } else + throw new BadHandleException(); + + // Check if the path is valid. The path may not be valid if the server has + // been restarted as the file id cache will not contain the required path. + + if (path == null) { + + // Check if the filesystem driver supports converting file ids to + // paths + + if (details.hasFileIdSupport()) { + + // Get the file and directory ids from the handle + + dirId = NFSHandle.unpackDirectoryId(handle); + fileId = NFSHandle.unpackFileId(handle); + + // If the file id is not valid the handle is to a directory, use the + // directory id as the file id + + if (fileId == -1) { + fileId = dirId; + dirId = -1; + } + + // Convert the file id to a path + + FileIdInterface fileIdInterface = (FileIdInterface) tree + .getInterface(); + try { + + // Convert the file id to a path + + path = fileIdInterface.buildPathForFileId(sess, tree, + dirId, fileId); + + // Add the path to the cache + + details.getFileIdCache().addPath(fileId, path); + } catch (FileNotFoundException ex) { + } + } else if (NFSHandle.isDirectoryHandle(handle) && dirId == 0) { + + // Path is the root directory + + path = "\\"; + + // Add an entry to the cache + + details.getFileIdCache().addPath(dirId, path); + } + } + + // Check if the path is valid, filesystem driver may not support converting + // file ids to paths or the file/directory may have been deleted. + + if (path == null) + throw new StaleHandleException(); + + // Return the path + + return path; + } + + /** + * Get the file id from the specified handle + * + * @param handle + * byte[] + * @return String + * @exception BadHandleException + */ + protected final int getFileIdForHandle(byte[] handle) + throws BadHandleException { + + // Check the handle type + + int fileId = -1; + + if (NFSHandle.isShareHandle(handle)) { + + // Root file id + + fileId = 0; + } + else if (NFSHandle.isDirectoryHandle(handle)) { + + // Get the directory id from the handle + + fileId = NFSHandle.unpackDirectoryId(handle); + } + else if (NFSHandle.isFileHandle(handle)) { + + // Get the file id from the handle + + fileId = NFSHandle.unpackFileId(handle); + } + + // Check if the file id is valid + + if (fileId == -1) + throw new BadHandleException(); + + // Return the file id + + return fileId; + } + + /** + * Find, or open, the required network file using the file handle + * + * @param sess + * NFSSrvSession + * @param handle + * byte[] + * @param conn + * TreeConnection + * @param readOnly + * boolean + * @return NetworkFile + * @exception BadHandleException + * If the handle is not valid + * @exception StaleHandleException + * If the file id cannot be converted to a path + */ + protected final NetworkFile getNetworkFileForHandle(NFSSrvSession sess, + byte[] handle, TreeConnection conn, boolean readOnly) + throws BadHandleException, StaleHandleException { + + // Check if the handle is a file handle + + if (NFSHandle.isFileHandle(handle) == false) + throw new BadHandleException("Not a file handle"); + + // Get the file id from the handle + + int fileId = getFileIdForHandle(handle); + + // Get the per session network file cache, use this to synchronize + + NetworkFileCache fileCache = sess.getFileCache(); + NetworkFile file = null; + + synchronized (fileCache) { + + // Check the file cache, file may already be open + + file = fileCache.findFile(fileId); + if (file == null) { + + // Get the path for the file + + String path = getPathForHandle(sess, handle, conn); + if (path == null) + throw new StaleHandleException(); + + try { + + // Get the disk interface from the connection + + DiskInterface disk = (DiskInterface) conn.getSharedDevice().getInterface(); + + // Open the network file + + FileOpenParams params = new FileOpenParams(path, FileAction.OpenIfExists, AccessMode.ReadWrite, 0); + file = disk.openFile(sess, conn, params); + + // Add the file to the active file cache + + if (file != null) + fileCache.addFile(file, conn); + } + catch (AccessDeniedException ex) { + if (hasDebug()) + logger.debug(ex); + } + catch (Exception ex) { + logger.debug(ex); + } + } + } + + // Return the network file + + return file; + } + + /** + * Return the tree connection for the specified share index + * + * @param sess + * NFSSrvSession + * @param shareId + * int + * @return TreeConnection + * @exception BadHandleException + */ + protected final TreeConnection getTreeConnection(NFSSrvSession sess, + int shareId) throws BadHandleException { + + // Get the required tree connection from the session + + TreeConnection conn = sess.findConnection(shareId); + if (conn == null) { + + // Get a template tree connection from the global list + + TreeConnection template = m_connections.findConnection(shareId); + if (template == null) { + + // Check if any new shares have been added and try to find the + // required connection again + + if (checkForNewShares() > 0) + template = m_connections.findConnection(shareId); + } + + // Matching tree connection not found, handle is not valid + + if (template == null) + throw new BadHandleException(); + + // Check if there is an access control manager configured + + if (hasAccessControlManager()) { + + // Check if the session has access to the shared filesystem + + AccessControlManager aclMgr = getAccessControlManager(); + + int sharePerm = aclMgr.checkAccessControl(sess, template + .getSharedDevice()); + + if (sharePerm == AccessControl.NoAccess) { + + // Session does not have access to the shared filesystem, + // mount should + // have failed or permissions may have changed. + + throw new BadHandleException(); + } else if (sharePerm == AccessControl.Default) + sharePerm = AccessControl.ReadWrite; + + // Create a new tree connection from the template + + conn = new TreeConnection(template.getSharedDevice()); + conn.setPermission(sharePerm); + + // Add the tree connection to the active list for the session + + sess.addConnection(conn); + } + } + + // Return the tree connection + + return conn; + } + + /** + * Pack a weak cache consistency structure + * + * @param rpc + * RpcPacket + * @param finfo + * FileInfo + */ + protected final void packWccData(RpcPacket rpc, FileInfo finfo) { + + // Pack the weak cache consistency data + + if (finfo != null) { + + // Indicate that data follows + + rpc.packInt(Rpc.True); + + // Pack the file size + + if (finfo.isDirectory()) + rpc.packLong(512L); + else + rpc.packLong(finfo.getSize()); + + // Pack the file times + + if (finfo.hasModifyDateTime()) { + rpc.packInt((int) (finfo.getModifyDateTime() / 1000L)); + rpc.packInt(0); + } else + rpc.packLong(0); + + if (finfo.hasChangeDateTime()) { + rpc.packInt((int) (finfo.getChangeDateTime() / 1000L)); + rpc.packInt(0); + } else + rpc.packLong(0); + } else + rpc.packInt(Rpc.False); + } + + /** + * Check if a file path contains any directory components + * + * @param fpath + * String + * @return boolean + */ + protected final boolean pathHasDirectories(String fpath) { + + // Check if the file path is valid + + if (fpath == null || fpath.length() == 0) + return false; + + // Check if the file path starts with a directory component + + if (fpath.startsWith("\\") || fpath.startsWith("/") + || fpath.startsWith("..")) + return true; + + // Check if the file path contains directory components + + if (fpath.indexOf("\\") != -1 || fpath.indexOf("/") != -1) + return true; + + // File path does not have any directory components + + return false; + } + + /** + * Pack the pre operation weak cache consistency data for the specified + * file/directory + * + * @param sess + * NFSSrvSession + * @param finfo + * FileInfo + * @param rpc + * RpcPacket + */ + protected final void packPreOpAttr(NFSSrvSession sess, FileInfo finfo, + RpcPacket rpc) { + + // Pack the file information + + if (finfo != null) + packWccData(rpc, finfo); + else + rpc.packInt(Rpc.False); + } + + /** + * Pack the pre operation weak cache consistency data for the specified + * file/directory + * + * @param sess + * NFSSrvSession + * @param conn + * TreeConnection + * @param fhandle + * byte[] + * @param rpc + * RpcPacket + * @throws BadHandleException + * @throws StaleHandleException + * @throws InvalidDeviceInterfaceException + * @throws IOException + */ + protected final void packPreOpAttr(NFSSrvSession sess, TreeConnection conn, + byte[] fhandle, RpcPacket rpc) throws BadHandleException, + StaleHandleException, InvalidDeviceInterfaceException, IOException { + + // Get the path + + String path = getPathForHandle(sess, fhandle, conn); + + // Get the disk interface from the disk driver + + DiskInterface disk = (DiskInterface) conn.getSharedDevice().getInterface(); + + // Get the file information for the path + + FileInfo finfo = disk.getFileInformation(sess, conn, path); + + // Pack the file information + + packWccData(rpc, finfo); + } + + /** + * Pack the post operation weak cache consistency data for the specified + * file/directory + * + * @param sess + * NFSSrvSession + * @param conn + * TreeConnection + * @param fhandle + * byte[] + * @param rpc + * RpcPacket + * @throws BadHandleException + * @throws StaleHandleException + * @throws InvalidDeviceInterfaceException + * @throws IOException + */ + protected final void packPostOpAttr(NFSSrvSession sess, + TreeConnection conn, byte[] fhandle, RpcPacket rpc) + throws BadHandleException, StaleHandleException, + InvalidDeviceInterfaceException, IOException { + + // Get the path + + String path = getPathForHandle(sess, fhandle, conn); + + // Get the disk interface from the disk driver + + DiskInterface disk = (DiskInterface) conn.getSharedDevice().getInterface(); + + // Get the file information for the path + + FileInfo finfo = disk.getFileInformation(sess, conn, path); + + // Pack the file information + + if (finfo != null) { + rpc.packInt(Rpc.True); + packAttributes3(rpc, finfo, getShareIdFromHandle(fhandle)); + } else + rpc.packInt(Rpc.False); + } + + /** + * Pack the post operation weak cache consistency data for the specified + * file/directory + * + * @param sess + * NFSSrvSession + * @param finfo + * FileInfo + * @param fileSysId + * int + * @param rpc + * RpcPacket + */ + protected final void packPostOpAttr(NFSSrvSession sess, FileInfo finfo, + int fileSysId, RpcPacket rpc) { + + // Pack the file information + + if (finfo != null) { + + // Pack the post operation attributes + + rpc.packInt(Rpc.True); + packAttributes3(rpc, finfo, fileSysId); + } else + rpc.packInt(Rpc.False); + } + + /** + * Generate a share relative path from the directory path and argument path. + * The argument path may contain the value '..' in which case the directory + * path will be stipped back one level. + * + * @param dirPath + * String + * @param argPath + * String + * @return String + */ + protected final String generatePath(String dirPath, String argPath) { + + // If the argument path is '..', if so then strip the directory path + // back a + // level + + StringBuffer pathBuf = new StringBuffer(); + + if (argPath.equals("..")) { + + // Split the path into component directories + + String[] dirs = FileName.splitAllPaths(dirPath); + + // Rebuild the path without the last directory + + pathBuf.append("\\"); + int dirCnt = dirs.length - 1; + + if (dirCnt > 0) { + + // Add the paths + + for (int i = 0; i < dirCnt; i++) { + pathBuf.append(dirs[i]); + pathBuf.append("\\"); + } + } + + // Remove the trailing slash + + if (pathBuf.length() > 1) + pathBuf.setLength(pathBuf.length() - 1); + } else { + + // Add the share relative path + + pathBuf.append(dirPath); + if (dirPath.endsWith("\\") == false) + pathBuf.append("\\"); + pathBuf.append(argPath); + } + + // Return the path + + return pathBuf.toString(); + } + + /** + * Check for new shared devices and add them to the share and tree + * connection lists + * + * @return int + */ + protected final int checkForNewShares() { + + // Scan the shared device list and check for new shared devices + + SharedDeviceList shareList = getConfiguration().getShareMapper().getShareList(getConfiguration().getServerName(), null, false); + Enumeration shares = shareList.enumerateShares(); + + int newShares = 0; + + while (shares.hasMoreElements()) { + + // Get the shared device + + SharedDevice share = (SharedDevice) shares.nextElement(); + + // Check if it is a disk type shared device, if so then add a connection + // to the tree connection hash + + if (share != null && share.getType() == ShareType.DISK) { + + // Check if the filesystem driver has file id support + + boolean fileIdSupport = false; + try { + if (share.getInterface() instanceof FileIdInterface) + fileIdSupport = true; + } catch (InvalidDeviceInterfaceException ex) { + } + + // Check if the share is already in the share/tree connection + // lists + + if (m_shareDetails.findDetails(share.getName()) == null) { + + // Add the new share details + + m_shareDetails.addDetails(new ShareDetails(share.getName(), fileIdSupport)); + m_connections.addConnection(new TreeConnection(share)); + + // Update the new share count + + newShares++; + } + } + } + + // Return the count of new shares added + + return newShares; + } + + /** + * Return the next session id + * + * @return int + */ + protected final synchronized int getNextSessionId() { + return m_sessId++; + } + + /** + * Return the configured RPC authenticator + * + * @return RpcAuthenticator + */ + protected final RpcAuthenticator getRpcAuthenticator() { + return m_rpcAuthenticator; + } + + /** + * Inform session listeners that a new session has been created + * + * @param sess + * SrvSession + */ + protected final void fireSessionOpened(SrvSession sess) { + fireSessionOpenEvent(sess); + } + + /** + * Inform session listeners that a session has been closed + * + * @param sess + * SrvSession + */ + protected final void fireSessionClosed(SrvSession sess) { + fireSessionClosedEvent(sess); + } +} \ No newline at end of file diff --git a/source/java/org/alfresco/filesys/server/oncrpc/nfs/NFSSessionTable.java b/source/java/org/alfresco/filesys/server/oncrpc/nfs/NFSSessionTable.java new file mode 100644 index 0000000000..20e2e3820e --- /dev/null +++ b/source/java/org/alfresco/filesys/server/oncrpc/nfs/NFSSessionTable.java @@ -0,0 +1,110 @@ +/* + * Copyright (C) 2005 Alfresco, Inc. + * + * Licensed under the Mozilla Public License version 1.1 + * with a permitted attribution clause. You may obtain a + * copy of the License at + * + * http://www.alfresco.org/legal/license.txt + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the + * License. + */ +package org.alfresco.filesys.server.oncrpc.nfs; + +import java.util.*; + +/** + * NFS Server Session Table Class + * + * @author GKSpencer + */ +public class NFSSessionTable { + + // Session list + + private Hashtable m_sessions; + + /** + * Class constructor + */ + public NFSSessionTable() + { + m_sessions = new Hashtable(); + } + + /** + * Return the number of sessions in the list + * + * @return int + */ + public final int numberOfSessions() + { + return m_sessions.size(); + } + + /** + * Add a session to the list + * + * @param sess NFSSrvSession + */ + public final void addSession(NFSSrvSession sess) + { + m_sessions.put(sess.getAuthIdentifier(), sess); + } + + /** + * Find the session using the authentication identifier + * + * @param authIdent Object + * @return NFSSrvSession + */ + public final NFSSrvSession findSession(Object authIdent) + { + return (NFSSrvSession) m_sessions.get(authIdent); + } + + /** + * Remove a session from the list + * + * @param sess NFSSrvSession + * @return NFSSrvSession + */ + public final NFSSrvSession removeSession(NFSSrvSession sess) + { + return removeSession(sess.getAuthIdentifier()); + } + + /** + * Remove a session from the list + * + * @param authIdent Object + * @return NFSSrvSession + */ + public final NFSSrvSession removeSession(Object authIdent) + { + + // Find the required session + + NFSSrvSession sess = findSession(authIdent); + + // Remove the session and return the removed session + + m_sessions.remove(authIdent); + return sess; + } + + /** + * Enumerate the session ids + * + * @return Enumeration + */ + public final Enumeration enumerate() + { + return m_sessions.keys(); + } +} diff --git a/source/java/org/alfresco/filesys/server/oncrpc/nfs/NFSSrvSession.java b/source/java/org/alfresco/filesys/server/oncrpc/nfs/NFSSrvSession.java new file mode 100644 index 0000000000..32381944e7 --- /dev/null +++ b/source/java/org/alfresco/filesys/server/oncrpc/nfs/NFSSrvSession.java @@ -0,0 +1,471 @@ +/* + * Copyright (C) 2005 Alfresco, Inc. + * + * Licensed under the Mozilla Public License version 1.1 + * with a permitted attribution clause. You may obtain a + * copy of the License at + * + * http://www.alfresco.org/legal/license.txt + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the + * License. + */ +package org.alfresco.filesys.server.oncrpc.nfs; + +import java.net.*; +import java.util.*; + +import org.alfresco.filesys.server.NetworkServer; +import org.alfresco.filesys.server.SrvSession; +import org.alfresco.filesys.server.core.DeviceInterface; +import org.alfresco.filesys.server.filesys.SearchContext; +import org.alfresco.filesys.server.filesys.TreeConnection; +import org.alfresco.filesys.server.filesys.TreeConnectionHash; +import org.alfresco.filesys.server.oncrpc.Rpc; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +/** + * NFS Server Session Class + * + * @author GKSpencer + */ +public class NFSSrvSession extends SrvSession { + + // Debug logging + + private static final Log logger = LogFactory.getLog("org.alfresco.nfs.protocol"); + + // Default and maximum number of search slots + + private static final int DefaultSearches = 32; + private static final int MaxSearches = 256; + + // Remote address and port + + private InetAddress m_remAddr; + private int m_remPort; + + // Session type (TCP or UDP) + + private int m_type; + + // Authentication identifier + // + // Identifies this session uniquely within the authentication type being used by the client + + private Object m_authIdentifier; + + // Active tree connections + + private TreeConnectionHash m_connections; + + // Cache of currently open files + + private NetworkFileCache m_fileCache; + + // Last time the session was accessed. Used to determine when to expire UDP sessions. + + private long m_lastAccess; + + // Active search list for this session + + private SearchContext[] m_search; + private int m_searchCount; + + /** + * Class constructor + * + * @param srv NetworkServer + * @param addr InetAddress + * @param port int + * @param type int + */ + public NFSSrvSession(NetworkServer srv, InetAddress addr, int port, int type) + { + super(-1, srv, "NFS", null); + + // Save the remove address/port and type + + m_remAddr = addr; + m_remPort = port; + m_type = type; + + // Create a unique id for the session from the remote address, port and type + + StringBuffer str = new StringBuffer(); + + str.append(type == Rpc.TCP ? "T" : "U"); + str.append(m_remAddr.getHostAddress()); + str.append(":"); + str.append(m_remPort); + + setUniqueId(str.toString()); + + // Set the remote name + + setRemoteName(m_remAddr.getHostAddress()); + + // Initialize the last access date/time + + setLastAccess(System.currentTimeMillis()); + } + + /** + * Return the session type + * + * @return int + */ + public final int isType() + { + return m_type; + } + + /** + * Return the open file cache + * + * @return NetworkFileCache + */ + public final NetworkFileCache getFileCache() + { + if (m_fileCache == null) + m_fileCache = new NetworkFileCache(getUniqueId()); + return m_fileCache; + } + + /** + * Determine if the session has an authentication identifier + * + * @return boolean + */ + public final boolean hasAuthIdentifier() + { + return m_authIdentifier != null ? true : false; + } + + /** + * Return the authentication identifier + * + * @return Object + */ + public final Object getAuthIdentifier() + { + return m_authIdentifier; + } + + /** + * Return the client network address + * + * @return InetAddress + */ + public InetAddress getRemoteAddress() + { + return m_remAddr; + } + + /** + * Return the remote port + * + * @return int + */ + public final int getRemotePort() + { + return m_remPort; + } + + /** + * Get the last access date/time for the session + * + * @return long + */ + public final long getLastAccess() + { + return m_lastAccess; + } + + /** + * Find the tree connection for the specified share hash + * + * @param shareHash int + * @return TreeConnection + */ + public final TreeConnection findConnection(int shareHash) + { + if (m_connections == null) + return null; + return m_connections.findConnection(shareHash); + } + + /** + * Add a new connection to the list of active tree connections for this session + * + * @param tree TreeConnection + */ + public final void addConnection(TreeConnection tree) + { + if (m_connections == null) + m_connections = new TreeConnectionHash(); + m_connections.addConnection(tree); + } + + /** + * Remove a connection from the list of active tree connections for this session + * + * @param tree TreeConnection + */ + public final void removeConnection(TreeConnection tree) + { + if (m_connections == null) + return; + m_connections.deleteConnection(tree.getSharedDevice().getName()); + } + + /** + * Set the authentication identifier + * + * @param authIdent Object + */ + public final void setAuthIdentifier(Object authIdent) + { + m_authIdentifier = authIdent; + } + + /** + * Set the last access date/time for the session + * + * @param dateTime long + */ + public final void setLastAccess(long dateTime) + { + m_lastAccess = dateTime; + } + + /** + * Set the last access date/time for the session + */ + public final void setLastAccess() + { + m_lastAccess = System.currentTimeMillis(); + } + + /** + * Close the session, cleanup any resources. + */ + public void closeSession() + { + + // Cleanup open files, tree connections and searches + + cleanupSession(); + + // Call the base class + + super.closeSession(); + } + + /** + * Allocate a slot in the active searches list for a new search. + * + * @param search SearchContext + * @return int Search slot index, or -1 if there are no more search slots available. + */ + protected synchronized final int allocateSearchSlot(SearchContext search) + { + + // Check if the search array has been allocated + + if (m_search == null) + m_search = new SearchContext[DefaultSearches]; + + // Find a free slot for the new search + + int idx = 0; + + while (idx < m_search.length && m_search[idx] != null) + idx++; + + // Check if we found a free slot + + if (idx == m_search.length) + { + + // The search array needs to be extended, check if we reached the limit. + + if (m_search.length >= MaxSearches) + return -1; + + // Extend the search array + + SearchContext[] newSearch = new SearchContext[m_search.length * 2]; + System.arraycopy(m_search, 0, newSearch, 0, m_search.length); + m_search = newSearch; + } + + // If the search context is valid then store in the allocated slot + + if (search != null) + m_search[idx] = search; + + // Return the allocated search slot index + + m_searchCount++; + return idx; + } + + /** + * Deallocate the specified search context/slot. + * + * @param ctxId int + */ + protected synchronized final void deallocateSearchSlot(int ctxId) + { + + // Check if the search array has been allocated and that the index is valid + + if (m_search == null || ctxId >= m_search.length) + return; + + // Close the search + + if (m_search[ctxId] != null) + m_search[ctxId].closeSearch(); + + // Free the specified search context slot + + m_searchCount--; + m_search[ctxId] = null; + } + + /** + * Return the NFS server that the session is associated with + * + * @return NFSServer + */ + public final NFSServer getNFSServer() + { + return (NFSServer) getServer(); + } + + /** + * Return the search context for the specified search id. + * + * @return com.starla.smbsrv.SearchContext + * @param srchId int + */ + protected final SearchContext getSearchContext(int srchId) + { + + // Check if the search array is valid and the search index is valid + + if (m_search == null || srchId >= m_search.length) + return null; + + // Return the required search context + + return m_search[srchId]; + } + + /** + * Return the number of active tree searches. + * + * @return int + */ + public final int getSearchCount() + { + return m_searchCount; + } + + /** + * Store the seach context in the specified slot. + * + * @param slot Slot to store the search context. + * @param srch com.starla.smbsrv.SearchContext + */ + protected final void setSearchContext(int slot, SearchContext srch) + { + + // Check if the search slot id is valid + + if (m_search == null || slot > m_search.length) + return; + + // Store the context + + m_search[slot] = srch; + } + + /** + * Cleanup any resources owned by this session, close files, searches and change notification requests. + */ + protected final void cleanupSession() + { + + // Debug + + if (logger.isDebugEnabled() && hasDebug(NFSServer.DBG_SESSION)) + logger.debug("NFS Cleanup session, searches=" + getSearchCount() + ", files=" + + (m_fileCache != null ? m_fileCache.numberOfEntries() : 0) + ", treeConns=" + + (m_connections != null ? m_connections.numberOfEntries() : 0)); + + // Check if there are any active searches + + if (m_search != null) + { + + // Close all active searches + + for (int idx = 0; idx < m_search.length; idx++) + { + + // Check if the current search slot is active + + if (m_search[idx] != null) + deallocateSearchSlot(idx); + } + + // Release the search context list, clear the search count + + m_search = null; + m_searchCount = 0; + } + + // Close any open files + + if (m_fileCache != null) + m_fileCache.closeAllFiles(); + + // Check if there are open tree connections + + if (m_connections != null && m_connections.numberOfEntries() > 0) + { + + // Enumerate the active connections + + Enumeration conns = m_connections.enumerateConnections(); + + while (conns.hasMoreElements()) + { + + // Get the current tree connection + + TreeConnection tree = (TreeConnection) conns.nextElement(); + + tree.closeConnection(this); + + // Inform the driver that the connection has been closed + + DeviceInterface devIface = tree.getInterface(); + if (devIface != null) + devIface.treeClosed(this, tree); + + // Release the connection list + + m_connections = null; + } + } + } +} diff --git a/source/java/org/alfresco/filesys/server/oncrpc/nfs/NetworkFileCache.java b/source/java/org/alfresco/filesys/server/oncrpc/nfs/NetworkFileCache.java new file mode 100644 index 0000000000..8b3d2aadd0 --- /dev/null +++ b/source/java/org/alfresco/filesys/server/oncrpc/nfs/NetworkFileCache.java @@ -0,0 +1,444 @@ +/* + * Copyright (C) 2005 Alfresco, Inc. + * + * Licensed under the Mozilla Public License version 1.1 + * with a permitted attribution clause. You may obtain a + * copy of the License at + * + * http://www.alfresco.org/legal/license.txt + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the + * License. + */ +package org.alfresco.filesys.server.oncrpc.nfs; + +import java.util.*; +import java.io.*; + +import org.alfresco.filesys.server.filesys.DiskInterface; +import org.alfresco.filesys.server.filesys.NetworkFile; +import org.alfresco.filesys.server.filesys.TreeConnection; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +/** + * Network File Cache Class + * + *

Caches the network files that are currently being accessed by the NFS server. + * + * @author GKSpencer + */ +public class NetworkFileCache { + + // Debug logging + + private static final Log logger = LogFactory.getLog(NetworkFileCache.class); + + // Default file timeout + + public static final long DefaultFileTimeout = 5000L; // 5 seconds + + // Network file cache, key is the file id + + private Hashtable m_fileCache; + + // File expiry thread + + private FileExpiry m_expiryThread; + + // File timeout + + private long m_fileTmo = DefaultFileTimeout; + + // Debug enable flag + + private boolean m_debug = false; + + /** + * File Entry Class + */ + protected class FileEntry { + + // Network file + + private NetworkFile m_file; + + // Disk share connection + + private TreeConnection m_conn; + + // File timeout + + private long m_timeout; + + /** + * Class constructor + * + * @param file + * NetworkFile + * @param conn + * TreeConnection + */ + public FileEntry(NetworkFile file, TreeConnection conn) { + m_file = file; + m_conn = conn; + updateTimeout(); + } + + /** + * Return the file timeout + * + * @return long + */ + public final long getTimeout() { + return m_timeout; + } + + /** + * Return the network file + * + * @return NetworkFile + */ + public final NetworkFile getFile() { + return m_file; + } + + /** + * Return the disk share connection + * + * @return TreeConnection + */ + public final TreeConnection getConnection() { + return m_conn; + } + + /** + * Update the file timeout + */ + public final void updateTimeout() { + m_timeout = System.currentTimeMillis() + m_fileTmo; + } + + /** + * Update the file timeout + * + * @param tmo + * long + */ + public final void updateTimeout(long tmo) { + m_timeout = tmo; + } + }; + + /** + * File Expiry Thread Class + */ + protected class FileExpiry implements Runnable { + + // Expiry thread + + private Thread m_thread; + + // Wakeup interval + + private long m_wakeup; + + // Shutdown flag + + private boolean m_shutdown; + + /** + * Class Constructor + * + * @param wakeup + * long + * @param name + * String + */ + public FileExpiry(long wakeup, String name) { + + // Set the wakeup interval + + m_wakeup = wakeup; + + // Create and start the file expiry thread + + m_thread = new Thread(this); + m_thread.setDaemon(true); + m_thread.setName("NFSFileExpiry_" + name); + m_thread.start(); + } + + /** + * Main thread method + */ + public void run() { + + // Loop until shutdown + + while (m_shutdown == false) { + + // Sleep for a while + + try { + Thread.sleep(m_wakeup); + } catch (InterruptedException ex) { + } + + // Get the current system time + + long timeNow = System.currentTimeMillis(); + + // Check for expired files + + synchronized (m_fileCache) { + + // Enumerate the cache entries + + Enumeration enm = m_fileCache.keys(); + + while (enm.hasMoreElements()) { + + // Get the current key + + Integer fileId = (Integer) enm.nextElement(); + + // Get the file entry and check if it has expired + + FileEntry fentry = (FileEntry) m_fileCache.get(fileId); + + if (fentry != null && fentry.getTimeout() < timeNow) { + + // Get the network file + + NetworkFile netFile = fentry.getFile(); + + // Check if the file has an I/O request pending, if + // so then reset the file expiry time + // for the file + + if (netFile.hasIOPending()) { + + // Update the expiry time for the file entry + + fentry.updateTimeout(); + + // DEBUG + + if (logger.isDebugEnabled()) + logger.debug("NFSFileExpiry: I/O pending file=" + fentry.getFile().getFullName() + ", fid=" + fileId); + } else { + + // File entry has expired, remove it from the + // cache + + m_fileCache.remove(fileId); + + // Close the file via the disk interface + + try { + + // Get the disk interface + + DiskInterface disk = (DiskInterface) fentry + .getConnection().getInterface(); + + // Close the file + + disk.closeFile(null, + fentry.getConnection(), netFile); + } catch (IOException ex) { + } + + // DEBUG + + if (logger.isDebugEnabled()) + logger.debug("NFSFileExpiry: Closed file=" + fentry.getFile().getFullName() + ", fid=" + fileId); + } + } + } + } + } + } + + /** + * Request the file expiry thread to shutdown + */ + public final void requestShutdown() { + + // Set the shutdown flag + + m_shutdown = true; + + // Wakeup the thread + + try { + m_thread.interrupt(); + } catch (Exception ex) { + } + + // Wait for the expiry thread to complete + + try { + m_thread.join(DefaultFileTimeout); + } catch (Exception ex) { + } + } + }; + + /** + * Class constructor + * + * @param name + * String + */ + public NetworkFileCache(String name) { + + // Create the file cache + + m_fileCache = new Hashtable(); + + // Start the file expiry thread + + m_expiryThread = new FileExpiry(DefaultFileTimeout / 4, name); + } + + /** + * Determine if debug output is enabled + * + * @return boolean + */ + public final boolean hasDebug() { + return m_debug; + } + + /** + * Add a file to the cache + * + * @param file + * NetworkFile + * @param conn + * TreeConnection + */ + public synchronized final void addFile(NetworkFile file, TreeConnection conn) { + synchronized (m_fileCache) { + m_fileCache.put(new Integer(file.getFileId()), new FileEntry(file, + conn)); + } + } + + /** + * Remove a file from the cache + * + * @param id + */ + public synchronized final void removeFile(int id) { + + // Create the search key + + Integer fileId = new Integer(id); + + synchronized (m_fileCache) { + m_fileCache.remove(fileId); + } + } + + /** + * Find a file via the file id + * + * @param id + * int + * @return NetworkFile + */ + public synchronized final NetworkFile findFile(int id) { + + // Create the search key + + Integer fileId = new Integer(id); + FileEntry fentry = null; + + synchronized (m_fileCache) { + fentry = (FileEntry) m_fileCache.get(fileId); + } + + // Return the file, or null if not found + + if (fentry != null) { + + // Update the file timeout and return the file + + fentry.updateTimeout(); + return fentry.getFile(); + } + + // Invalid file id + + return null; + } + + /** + * Return the count of entries in the cache + * + * @return int + */ + public final int numberOfEntries() { + return m_fileCache.size(); + } + + /** + * Close the expiry cache, close and remove all files from the cache and + * stop the expiry thread. + */ + public final void closeAllFiles() { + + // Enumerate the cache entries + + Enumeration keys = m_fileCache.keys(); + + while (keys.hasMoreElements()) { + + // Get the current key and lookup the matching value + + Integer key = (Integer) keys.nextElement(); + FileEntry entry = (FileEntry) m_fileCache.get(key); + + // Expire the file entry + + entry.updateTimeout(0L); + } + + // Shutdown the expiry thread, this should close the files + + m_expiryThread.requestShutdown(); + } + + /** + * Dump the cache entries to the debug device + */ + public final void dumpCache() { + + // Dump the count of entries in the cache + + logger.debug("NetworkFileCache entries=" + numberOfEntries()); + + // Enumerate the cache entries + + Enumeration keys = m_fileCache.keys(); + + while (keys.hasMoreElements()) { + + // Get the current key and lookup the matching value + + Integer key = (Integer) keys.nextElement(); + FileEntry entry = (FileEntry) m_fileCache.get(key); + + // Dump the entry details + + logger.debug("fid=" + key + ": " + entry); + } + } +} diff --git a/source/java/org/alfresco/filesys/server/oncrpc/nfs/SearchCache.java b/source/java/org/alfresco/filesys/server/oncrpc/nfs/SearchCache.java new file mode 100644 index 0000000000..c7dd9b11d2 --- /dev/null +++ b/source/java/org/alfresco/filesys/server/oncrpc/nfs/SearchCache.java @@ -0,0 +1,366 @@ +/* + * Copyright (C) 2005 Alfresco, Inc. + * + * Licensed under the Mozilla Public License version 1.1 + * with a permitted attribution clause. You may obtain a + * copy of the License at + * + * http://www.alfresco.org/legal/license.txt + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the + * License. + */ +package org.alfresco.filesys.server.oncrpc.nfs; + +import org.alfresco.filesys.server.filesys.SearchContext; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +/** + * Search Cache Class + * + *

Holds the details of the active searches for the NFS server + * + * @author GKSpencer + */ +public class SearchCache { + + // Debug logging + + private static final Log logger = LogFactory.getLog(SearchCache.class); + + // Maximum number of active searches + + public static final int MaximumSearches = 255; + + // Default search timeout + + public static final long DefaultSearchTimeout = 30000L; // 30 seconds + + // Array of active searches, last allocated index + + private SearchEntry[] m_searches; + private int m_lastIdx; + + // Search timeout + + private long m_searchTmo = DefaultSearchTimeout; + + // Debug enable flag + + private boolean m_debug = true; + + /** + * Search Entry Class + */ + protected class SearchEntry + { + + // Search context + + private SearchContext m_search; + + // Search timeout + + private long m_timeout; + + /** + * Class constructor + * + * @param search SearchContext + */ + public SearchEntry(SearchContext search) + { + m_search = search; + updateTimeout(); + } + + /** + * Return the search timeout + * + * @return long + */ + public final long getTimeout() + { + return m_timeout; + } + + /** + * Return the search context + * + * @return SearchContext + */ + public final SearchContext getSearch() + { + return m_search; + } + + /** + * Update the search timeout + */ + public final void updateTimeout() + { + m_timeout = System.currentTimeMillis() + m_searchTmo; + } + }; + + /** + * Search Expiry Thread Class + */ + protected class SearchExpiry implements Runnable + { + + // Expiry thread + + private Thread m_thread; + + // Wakeup interval + + private long m_wakeup; + + /** + * Class Constructor + * + * @param wakeup long + */ + public SearchExpiry(long wakeup) + { + + // Set the wakeup interval + + m_wakeup = wakeup; + + // Create and start the search expiry thread + + m_thread = new Thread(this); + m_thread.setDaemon(true); + m_thread.setName("NFSSearchExpiry"); + m_thread.start(); + } + + /** + * Main thread method + */ + public void run() + { + + // Loop until shutdown + + while (true) + { + + // Sleep for a while + + try + { + Thread.sleep(m_wakeup); + } catch (InterruptedException ex) + { + } + + // Get the current system time + + long timeNow = System.currentTimeMillis(); + + // Check for expired searches + + synchronized (m_searches) + { + + // Check all allocated slots + + for (int i = 0; i < m_searches.length; i++) + { + + // Check if the current slot has a valid entry + + if (m_searches[i] != null && m_searches[i].getTimeout() < timeNow) + { + + // Remove the current search entry + + SearchEntry entry = m_searches[i]; + m_searches[i] = null; + + // Close the search + + entry.getSearch().closeSearch(); + + // DEBUG + + if (logger.isDebugEnabled()) + logger.debug("NFSSearchExpiry: Closed search=" + entry.getSearch().getSearchString() + + ", id=" + i); + } + } + } + } + } + }; + + /** + * Default constructor + */ + public SearchCache() + { + + // Create the active search list + + m_searches = new SearchEntry[MaximumSearches]; + + // Start the search expiry thread + + new SearchExpiry(DefaultSearchTimeout / 2); + } + + /** + * Determine if debug output is enabled + * + * @return boolean + */ + public final boolean hasDebug() + { + return m_debug; + } + + /** + * Allocate a search slot + * + * @param search SearchContext + * @return int + */ + public final int allocateSearchId(SearchContext search) + { + + synchronized (m_searches) + { + + // Search for a free slot in the search list + + int cnt = 0; + + while (cnt < MaximumSearches) + { + + // Check if the index has wrapped + + if (m_lastIdx >= MaximumSearches) + m_lastIdx = 0; + + // Check if the current slot is empty + + if (m_searches[m_lastIdx] == null) + { + + // Use this slot + + SearchEntry entry = new SearchEntry(search); + m_searches[m_lastIdx] = entry; + return m_lastIdx++; + } else + m_lastIdx++; + + // Update the slot count + + cnt++; + } + } + + // No empty search slot found + + return -1; + } + + /** + * Release a search slot + * + * @param id int + */ + public final void releaseSearchId(int id) + { + + // Range check the id + + if (id < 0 || id >= MaximumSearches) + return; + + // Delete the search entry + + synchronized (m_searches) + { + m_searches[id] = null; + } + } + + /** + * Return the required search context + * + * @param id int + * @return SearchContext + */ + public final SearchContext getSearch(int id) + { + + // Range check the id + + if (id < 0 || id >= MaximumSearches) + return null; + + // Get the search entry + + SearchEntry entry = null; + + synchronized (m_searches) + { + entry = m_searches[id]; + } + + // Return the search context, if valid + + if (entry != null) + { + + // Update the search timeout and return the search + + entry.updateTimeout(); + return entry.getSearch(); + } + + // Invalid search + + return null; + } + + /** + * Dump the active search list + */ + public final void dumpSearches() + { + + synchronized (m_searches) + { + + // Find all active searches in the list + + for (int i = 0; i < m_searches.length; i++) + { + + // Check if the current search slot is active + + if (m_searches[i] != null) + { + + // Get the search details + + SearchEntry entry = m_searches[i]; + + logger.debug("" + i + ": " + entry.getSearch().toString()); + } + } + } + } +} diff --git a/source/java/org/alfresco/filesys/server/oncrpc/nfs/ShareDetails.java b/source/java/org/alfresco/filesys/server/oncrpc/nfs/ShareDetails.java new file mode 100644 index 0000000000..f34a4f2fec --- /dev/null +++ b/source/java/org/alfresco/filesys/server/oncrpc/nfs/ShareDetails.java @@ -0,0 +1,94 @@ +/* + * Copyright (C) 2005 Alfresco, Inc. + * + * Licensed under the Mozilla Public License version 1.1 + * with a permitted attribution clause. You may obtain a + * copy of the License at + * + * http://www.alfresco.org/legal/license.txt + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the + * License. + */ +package org.alfresco.filesys.server.oncrpc.nfs; + +/** + * Share Details Class + * + *

Contains the file id cache, active search cache and tree connection details + * of a shared filesystem. + * + * @author GKSpencer + */ +public class ShareDetails { + + // Share name + + private String m_name; + + // File id to path conversion cache + + private FileIdCache m_idCache; + + // Flag to indicate if the filesystem driver for this share supports file id + // lookups + // via the FileIdInterface + + private boolean m_fileIdLookup; + + /** + * Class constructor + * + * @param name String + * @param fileIdSupport boolean + */ + public ShareDetails(String name, boolean fileIdSupport) + { + + // Save the share name + + m_name = name; + + // Set the file id support flag + + m_fileIdLookup = fileIdSupport; + + // Create the file id and search caches + + m_idCache = new FileIdCache(); + } + + /** + * Return the share name + * + * @return String + */ + public final String getName() + { + return m_name; + } + + /** + * Return the file id cache + * + * @return FileIdCache + */ + public final FileIdCache getFileIdCache() + { + return m_idCache; + } + + /** + * Determine if the filesystem driver for this share has file id support + * + * @return boolean + */ + public final boolean hasFileIdSupport() + { + return m_fileIdLookup; + } +} diff --git a/source/java/org/alfresco/filesys/server/oncrpc/nfs/ShareDetailsHash.java b/source/java/org/alfresco/filesys/server/oncrpc/nfs/ShareDetailsHash.java new file mode 100644 index 0000000000..9a114cf859 --- /dev/null +++ b/source/java/org/alfresco/filesys/server/oncrpc/nfs/ShareDetailsHash.java @@ -0,0 +1,99 @@ +/* + * Copyright (C) 2005 Alfresco, Inc. + * + * Licensed under the Mozilla Public License version 1.1 + * with a permitted attribution clause. You may obtain a + * copy of the License at + * + * http://www.alfresco.org/legal/license.txt + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the + * License. + */ +package org.alfresco.filesys.server.oncrpc.nfs; + +import java.util.*; + +/** + * Share Details Hash Class + * + *

Hashtable of ShareDetails for the available disk shared devices. ShareDetails are indexed using the + * hash of the share name to allow mounts to be persistent across server restarts. + * + * @author GKSpencer + */ +public class ShareDetailsHash { + + // Share name hash to share details + + private Hashtable m_details; + + /** + * Class constructor + */ + public ShareDetailsHash() + { + m_details = new Hashtable(); + } + + /** + * Add share details to the list of available shares + * + * @param details ShareDetails + */ + public final void addDetails(ShareDetails details) + { + m_details.put(new Integer(details.getName().hashCode()), details); + } + + /** + * Delete share details from the list + * + * @param shareName String + * @return ShareDetails + */ + public final ShareDetails deleteDetails(String shareName) + { + return (ShareDetails) m_details.get(new Integer(shareName.hashCode())); + } + + /** + * Find share details for the specified share name + * + * @param shareName String + * @return ShareDetails + */ + public final ShareDetails findDetails(String shareName) + { + + // Get the share details for the associated share name + + ShareDetails details = (ShareDetails) m_details.get(new Integer(shareName.hashCode())); + + // Return the share details + + return details; + } + + /** + * Find share details for the specified share name hash code + * + * @param hashCode int + * @return ShareDetails + */ + public final ShareDetails findDetails(int hashCode) + { + + // Get the share details for the associated share name + + ShareDetails details = (ShareDetails) m_details.get(new Integer(hashCode)); + + // Return the share details + + return details; + } +} diff --git a/source/java/org/alfresco/filesys/server/oncrpc/nfs/StaleHandleException.java b/source/java/org/alfresco/filesys/server/oncrpc/nfs/StaleHandleException.java new file mode 100644 index 0000000000..c006774c97 --- /dev/null +++ b/source/java/org/alfresco/filesys/server/oncrpc/nfs/StaleHandleException.java @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2005 Alfresco, Inc. + * + * Licensed under the Mozilla Public License version 1.1 + * with a permitted attribution clause. You may obtain a + * copy of the License at + * + * http://www.alfresco.org/legal/license.txt + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the + * License. + */ +package org.alfresco.filesys.server.oncrpc.nfs; + +/** + * Stale Handle Exception Class + * + * @author GKSpencer + */ +public class StaleHandleException extends Exception { + + // Object version id + + private static final long serialVersionUID = -8607694363687774475L; + + /** + * Default constructor + */ + public StaleHandleException() + { + super(); + } + + /** + * Class constructor + * + * @param msg String + */ + public StaleHandleException(String msg) + { + super(msg); + } +} diff --git a/source/java/org/alfresco/filesys/server/oncrpc/portmap/PortMapper.java b/source/java/org/alfresco/filesys/server/oncrpc/portmap/PortMapper.java new file mode 100644 index 0000000000..58b4f23bbf --- /dev/null +++ b/source/java/org/alfresco/filesys/server/oncrpc/portmap/PortMapper.java @@ -0,0 +1,61 @@ +/* + * Copyright (C) 2005 Alfresco, Inc. + * + * Licensed under the Mozilla Public License version 1.1 + * with a permitted attribution clause. You may obtain a + * copy of the License at + * + * http://www.alfresco.org/legal/license.txt + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the + * License. + */ +package org.alfresco.filesys.server.oncrpc.portmap; + +/** + * PortMapper RPC Service Constants Class + * + * @author GKSpencer + */ +public class PortMapper { + + // Default port mapper port + + public static final int DefaultPort = 111; + + // Program and version id + + public static final int ProgramId = 100000; + public static final int VersionId = 2; + + // RPC procedure ids + + public static final int ProcNull = 0; + public static final int ProcSet = 1; + public static final int ProcUnSet = 2; + public static final int ProcGetPort = 3; + public static final int ProcDump = 4; + public static final int ProcMax = 4; + + // RPC procedure names + + private static final String[] _procNames = { "Null", "Set", "UnSet", + "GetPort", "Dump" }; + + /** + * Return a procedure id as a name + * + * @param id + * int + * @return String + */ + public final static String getProcedureName(int id) { + if (id < 0 || id > ProcMax) + return null; + return _procNames[id]; + } +} diff --git a/source/java/org/alfresco/filesys/server/oncrpc/portmap/PortMapperServer.java b/source/java/org/alfresco/filesys/server/oncrpc/portmap/PortMapperServer.java new file mode 100644 index 0000000000..1ab72b2a4c --- /dev/null +++ b/source/java/org/alfresco/filesys/server/oncrpc/portmap/PortMapperServer.java @@ -0,0 +1,551 @@ +/* + * Copyright (C) 2005 Alfresco, Inc. + * + * Licensed under the Mozilla Public License version 1.1 + * with a permitted attribution clause. You may obtain a + * copy of the License at + * + * http://www.alfresco.org/legal/license.txt + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the + * License. + */ +package org.alfresco.filesys.server.oncrpc.portmap; + +import java.io.*; +import java.util.*; + +import org.alfresco.filesys.server.NetworkServer; +import org.alfresco.filesys.server.ServerListener; +import org.alfresco.filesys.server.config.ServerConfiguration; +import org.alfresco.filesys.server.oncrpc.PortMapping; +import org.alfresco.filesys.server.oncrpc.Rpc; +import org.alfresco.filesys.server.oncrpc.RpcPacket; +import org.alfresco.filesys.server.oncrpc.RpcProcessor; +import org.alfresco.filesys.server.oncrpc.TcpRpcSessionHandler; +import org.alfresco.filesys.server.oncrpc.UdpRpcDatagramHandler; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +/** + * Port Mapper Server Class + * + * @author GKSpencer + */ +public class PortMapperServer extends NetworkServer implements RpcProcessor { + + // Debug logging + + protected static final Log logger = LogFactory.getLog("org.alfresco.nfs.protocol"); + + // Constants + // + // Default port mapper port + + public final static int DefaultPort = 111; + + // Maximum request size to accept + + public final static int MaxRequestSize = 1024; + + // Incoming datagram handler for UDP requests + + private UdpRpcDatagramHandler m_udpHandler; + + // Incoming session handler for TCP requests + + private TcpRpcSessionHandler m_tcpHandler; + + // Portmapper port + + private int m_port; + + // Table of active port mappings + + private Hashtable m_mappings; + private Hashtable m_noVerMappings; + + /** + * Class constructor + * + * @param config + * ServerConfiguration + */ + public PortMapperServer(ServerConfiguration config) { + super("Portmap", config); + + // Enable/disable debug output + + setDebug(config.hasPortMapperDebug()); + + // Set the port to use + + if (config.getPortMapperPort() != 0) + setPort(config.getPortMapperPort()); + else + setPort(DefaultPort); + + // Create the mappings tables + + m_mappings = new Hashtable(); + m_noVerMappings = new Hashtable(); + } + + /** + * Return the server port + * + * @return int + */ + public final int getPort() { + return m_port; + } + + /** + * Start the portmapper server + */ + public void startServer() { + + try { + + // Create the UDP RPC handler to accept incoming requests + + m_udpHandler = new UdpRpcDatagramHandler("PortMap", "Port", this, this, null, getPort(), MaxRequestSize); + m_udpHandler.initializeSessionHandler(this); + + // Start the UDP request listener is a seperate thread + + Thread udpThread = new Thread(m_udpHandler); + udpThread.setName("PortMap_UDP"); + udpThread.start(); + + // Create the TCP RPC handler to accept incoming requests + + m_tcpHandler = new TcpRpcSessionHandler("PortMap", "Port", this, this, null, getPort(), MaxRequestSize); + m_tcpHandler.initializeSessionHandler(this); + + // Start the UDP request listener is a seperate thread + + Thread tcpThread = new Thread(m_tcpHandler); + tcpThread.setName("PortMap_TCP"); + tcpThread.start(); + + // Add port mapper entries for the portmapper service + + PortMapping portMap = new PortMapping(PortMapper.ProgramId, PortMapper.VersionId, Rpc.UDP, getPort()); + addPortMapping(portMap); + + portMap = new PortMapping(PortMapper.ProgramId, PortMapper.VersionId, Rpc.TCP, getPort()); + addPortMapping(portMap); + } + catch (Exception ex) { + logger.debug(ex); + } + } + + /** + * Shutdown the server + * + * @param immediate + * boolean + */ + public void shutdownServer(boolean immediate) { + + // Stop the RPC handlers + + if (m_udpHandler != null) { + m_udpHandler.closeSessionHandler(this); + m_udpHandler = null; + } + + if (m_tcpHandler != null) { + m_tcpHandler.closeSessionHandler(this); + m_tcpHandler = null; + } + + // Fire a shutdown notification event + + fireServerEvent(ServerListener.ServerShutdown); + } + + /** + * Set the server port + * + * @param port + * int + */ + public final void setPort(int port) { + m_port = port; + } + + /** + * Process an RPC request + * + * @param rpc + * RpcPacket + * @return RpcPacket + * @throws IOException + */ + public RpcPacket processRpc(RpcPacket rpc) throws IOException { + + // Validate the request + + if (rpc.getProgramId() != PortMapper.ProgramId) { + + // Request is not for us + + rpc.buildAcceptErrorResponse(Rpc.StsProgUnavail); + return rpc; + } else if (rpc.getProgramVersion() != PortMapper.VersionId) { + + // Request is not for this version of portmapper + + rpc.buildProgramMismatchResponse(PortMapper.VersionId, + PortMapper.VersionId); + return rpc; + } + + // Position the RPC buffer pointer at the start of the call parameters + + rpc.positionAtParameters(); + + // Process the RPC request + + RpcPacket response = null; + + switch (rpc.getProcedureId()) { + + // Null request + + case PortMapper.ProcNull: + response = procNull(rpc); + break; + + // Set a port + + case PortMapper.ProcSet: + response = procSet(rpc); + break; + + // Release a port + + case PortMapper.ProcUnSet: + response = procUnSet(rpc); + break; + + // Get the port for a service + + case PortMapper.ProcGetPort: + response = procGetPort(rpc); + break; + + // Dump ports request + + case PortMapper.ProcDump: + response = procDump(rpc); + break; + } + + // Return the RPC response + + return response; + } + + /** + * Process the null request + * + * @param rpc + * RpcPacket + * @return RpcPacket + */ + private final RpcPacket procNull(RpcPacket rpc) { + + // Build the response + + rpc.buildResponseHeader(); + return rpc; + } + + /** + * Process the set request + * + * @param rpc + * RpcPacket + * @return RpcPacket + */ + private final RpcPacket procSet(RpcPacket rpc) { + + // Get the call parameters + + int progId = rpc.unpackInt(); + int verId = rpc.unpackInt(); + int proto = rpc.unpackInt(); + int port = rpc.unpackInt(); + + // DEBUG + + if (logger.isDebugEnabled()) + logger.debug("[PortMap] Set port program=" + Rpc.getServiceName(progId) + ", version=" + verId + + ", protocol=" + (proto == Rpc.TCP ? "TCP" : "UDP") + ", port=" + port); + + // Check if the port is already mapped + + PortMapping portMap = findPortMapping(progId, verId, proto); + int portAdded = Rpc.False; + + if (portMap == null) { + + // Add a mapping for the new service + + portMap = new PortMapping(progId, verId, proto, port); + if (addPortMapping(portMap) == true) + portAdded = Rpc.True; + } + + // Check if the service is on the same port as the current port mapping, + // and it is not + // an attempt to set the port mapper service port. + + else if (progId != PortMapper.ProgramId && portMap.getPort() == port) { + + // Settings are the same as the existing service settings so accept + // it + + portAdded = Rpc.True; + } + + // Build the response header + + rpc.buildResponseHeader(); + + // Pack a boolean indicating if the port was added, or not + + rpc.packInt(portAdded); + rpc.setLength(); + + // Return the response + + return rpc; + } + + /** + * Process the unset request + * + * @param rpc + * RpcPacket + * @return RpcPacket + */ + private final RpcPacket procUnSet(RpcPacket rpc) { + + // Get the call parameters + + int progId = rpc.unpackInt(); + int verId = rpc.unpackInt(); + int proto = rpc.unpackInt(); + int port = rpc.unpackInt(); + + // DEBUG + + if (logger.isDebugEnabled()) + logger.debug("[PortMap] UnSet port program=" + Rpc.getServiceName(progId) + ", version=" + verId + + ", protocol=" + (proto == Rpc.TCP ? "TCP" : "UDP") + ", port=" + port); + + // Check if the port is mapped, and it is not an attempt to remove a + // portmapper portt + + PortMapping portMap = findPortMapping(progId, verId, proto); + int portRemoved = Rpc.False; + + if (portMap != null && progId != PortMapper.ProgramId) { + + // Remove the port mapping + + if (removePortMapping(portMap) == true) + portRemoved = Rpc.True; + } + + // Build the response header + + rpc.buildResponseHeader(); + + // Pack a boolean indicating if the port was removed, or not + + rpc.packInt(portRemoved); + rpc.setLength(); + + // Return the response + + return rpc; + } + + /** + * Process the get port request + * + * @param rpc + * RpcPacket + * @return RpcPacket + */ + private final RpcPacket procGetPort(RpcPacket rpc) { + + // Get the call parameters + + int progId = rpc.unpackInt(); + int verId = rpc.unpackInt(); + int proto = rpc.unpackInt(); + + // Find the required port mapping + + PortMapping portMap = findPortMapping(progId, verId, proto); + + // DEBUG + + if (logger.isDebugEnabled()) + logger.debug("[PortMap] Get port program=" + Rpc.getServiceName(progId) + ", version=" + verId + + ", protocol=" + (proto == Rpc.TCP ? "TCP" : "UDP") + ", port=" + (portMap != null ? portMap.getPort() : 0)); + + // Build the response header + + rpc.buildResponseHeader(); + + // Pack the port number of the requested RPC service, or zero if not + // found + + rpc.packInt(portMap != null ? portMap.getPort() : 0); + rpc.setLength(); + + // Return the response + + return rpc; + } + + /** + * Process the dump request + * + * @param rpc + * RpcPacket + * @return RpcPacket + */ + private final RpcPacket procDump(RpcPacket rpc) { + + // DEBUG + + if (logger.isDebugEnabled()) + logger.debug("[PortMap] Dump ports request from " + rpc.getClientDetails()); + + // Build the response + + rpc.buildResponseHeader(); + + // Pack the active port mappings structures + + Enumeration enm = m_mappings.elements(); + + while (enm.hasMoreElements()) { + + // Get the current port mapping + + PortMapping portMap = (PortMapping) enm.nextElement(); + + // Pack the port mapping structure + + rpc.packInt(Rpc.True); + rpc.packPortMapping(portMap); + } + + // Pack the end of list structure, set the response length + + rpc.packInt(Rpc.False); + rpc.setLength(); + + // Return the response + + return rpc; + } + + /** + * Add a port mapping to the active list + * + * @param portMap + * PortMapping + * @return boolean + */ + private final boolean addPortMapping(PortMapping portMap) { + + // Check if there is an existing port mapping that matches the new port + + Integer key = new Integer(portMap.hashCode()); + if (m_mappings.get(key) != null) + return false; + + // Add the port mapping + + m_mappings.put(key, portMap); + + // Add a port mapping with a version id of zero + + key = new Integer(PortMapping.generateHashCode(portMap.getProgramId(), 0, portMap.getProtocol())); + m_noVerMappings.put(key, portMap); + + // Indicate that the mapping was added + + return true; + } + + /** + * Remove a port mapping from the active list + * + * @param portMap + * PortMapping + * @return boolean + */ + private final boolean removePortMapping(PortMapping portMap) { + + // Remove the port mapping from the active lists + + Integer key = new Integer(portMap.hashCode()); + Object removedObj = m_mappings.remove(key); + + key = new Integer(PortMapping.generateHashCode(portMap.getProgramId(), 0, portMap.getProtocol())); + m_noVerMappings.remove(key); + + // Return a status indicating if the mapping was removed + + return removedObj != null ? true : false; + } + + /** + * Search for a port mapping + * + * @param progId + * int + * @param verId + * int + * @param proto + * int + * @return PortMapping + */ + private final PortMapping findPortMapping(int progId, int verId, int proto) { + + // Create a key for the RPC service + + Integer key = new Integer(PortMapping.generateHashCode(progId, verId, + proto)); + + // Search for the required port mapping, including the version id + + PortMapping portMap = (PortMapping) m_mappings.get(key); + if (portMap == null && verId == 0) { + + // Search for the port mapping without the version id + + portMap = (PortMapping) m_noVerMappings.get(key); + } + + // Return the port mapping, or null if not found + + return portMap; + } +}