/* * Copyright (C) 2006-2008 Alfresco Software Limited. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * As a special exception to the terms and conditions of version 2.0 of * the GPL, you may redistribute this Program in connection with Free/Libre * and Open Source Software ("FLOSS") applications as described in Alfresco's * FLOSS exception. You should have recieved a copy of the text describing * the FLOSS exception, and it is also available here: * http://www.alfresco.com/legal/licensing" */ package org.alfresco.jlan.smb.server.nio; import java.io.IOException; import java.nio.channels.SelectionKey; import java.nio.channels.Selector; import java.nio.channels.ServerSocketChannel; import java.nio.channels.SocketChannel; import java.util.Enumeration; import java.util.Iterator; import java.util.Vector; import org.alfresco.jlan.debug.Debug; import org.alfresco.jlan.server.ChannelSessionHandler; import org.alfresco.jlan.server.SessionHandlerList; import org.alfresco.jlan.server.config.InvalidConfigurationException; import org.alfresco.jlan.smb.server.CIFSConfigSection; import org.alfresco.jlan.smb.server.CifsConnectionsHandler; import org.alfresco.jlan.smb.server.PacketHandler; import org.alfresco.jlan.smb.server.SMBServer; import org.alfresco.jlan.smb.server.SMBSrvSession; /** * NIO Connections Handler Class * * <p>Initializes the configured CIFS session handlers and listens for incoming requests using a single thread. * * @author gkspencer */ public class NIOCifsConnectionsHandler implements CifsConnectionsHandler, RequestHandlerListener, Runnable { // Constants // // Number of session socket channels each request handler thread monitors public static final int SessionSocketsPerHandler = 50; // 250; // List of session handlers that are waiting for incoming requests private SessionHandlerList m_handlerList; // Selector used to monitor incoming connections private Selector m_selector; // Session request handler(s) // // Each handler processes the socket read events for a number of session socket channels private Vector<CIFSRequestHandler> m_requestHandlers; // SMB server private SMBServer m_server; // Connection handler thread private Thread m_thread; // Shutdown request flag private boolean m_shutdown; // Session id private int m_sessId; // Client socket timeout, in milliseconds private int m_clientSocketTimeout; // Idle session reper thread private IdleSessionReaper m_idleSessReaper; // Debug output private boolean m_debug; // Thread pool debug private boolean m_threadDebug; /** * Idle Session Reaper Thread Class * * <p>Check for sessions that have no recent I/O requests. The session timeout is configurable. */ protected class IdleSessionReaper implements Runnable { // Reaper wakeup interval private long m_wakeup; // Reaper thread private Thread m_reaperThread; // Shutdown request flag private boolean m_shutdown = false; /** * Class constructor * * @param intvl long */ public IdleSessionReaper(long intvl) { m_wakeup = intvl; // Create a thread for the reaper, and start the thread m_reaperThread = new Thread( this); m_reaperThread.setDaemon( true); m_reaperThread.setName( "CIFS_IdleSessionReaper_NIO"); m_reaperThread.start(); } /** * Shutdown the connection reaper */ public final void shutdownRequest() { m_shutdown = true; m_reaperThread.interrupt(); } /** * Connection reaper thread */ public void run() { // Loop forever, or until shutdown while ( m_shutdown == false) { // Sleep for a while try { Thread.sleep(m_wakeup); } catch(InterruptedException ex) { } // Check if there is a shutdown pending if ( m_shutdown == true) break; // Check for idle sessions in the active CIFS request handlers Enumeration<CIFSRequestHandler> enumHandlers = m_requestHandlers.elements(); while ( enumHandlers.hasMoreElements()) { // Get the current request handler and check for idle session CIFSRequestHandler curHandler = enumHandlers.nextElement(); if ( curHandler != null) { // Check for idle sessions int idleCnt = curHandler.checkForIdleSessions(); // DEBUG if ( idleCnt > 0 && Debug.EnableInfo && hasDebug()) Debug.println( "[SMB] Idle session check, removed " + idleCnt + " sessions for " + curHandler.getName()); } } } } }; /** * Class constructor */ public NIOCifsConnectionsHandler() { m_handlerList = new SessionHandlerList(); } /** * Check if debug output is enabled * * @return boolean */ public final boolean hasDebug() { return m_debug; } /** * Return the count of active session handlers * * @return int */ public int numberOfSessionHandlers() { return m_handlerList.numberOfHandlers(); } /** * Initialize the connections handler * * @param srv SMBServer * @param config CIFSConfigSection * @exception InvalidConfigurationException */ public final void initializeHandler( SMBServer srv, CIFSConfigSection config) throws InvalidConfigurationException { // Save the server the handler is associated with m_server = srv; // Check if socket debug output is enabled if ( (config.getSessionDebugFlags() & SMBSrvSession.DBG_SOCKET) != 0) m_debug = true; // Check if thread pool debug is enabled if ( (config.getSessionDebugFlags() & SMBSrvSession.DBG_THREADPOOL) != 0) m_threadDebug = true; // Create the native SMB/port 445 session handler, if enabled if ( config.hasTcpipSMB()) { // Create the native SMB/port 445 session handler ChannelSessionHandler sessHandler = new TcpipSMBChannelSessionHandler( srv, config.getSMBBindAddress(), config.getTcpipSMBPort()); sessHandler.setDebug( hasDebug()); try { // Initialize the session handler, and add to the active list sessHandler.initializeSessionHandler( srv); m_handlerList.addHandler( sessHandler); } catch ( IOException ex) { throw new InvalidConfigurationException( "Error initializing TCP-IP SMB session handler, " + ex.getMessage()); } } // Create the NetBIOS session handler, if enabled if ( config.hasNetBIOSSMB()) { // Create the NetBIOS SMB session handler ChannelSessionHandler sessHandler = new NetBIOSSMBChannelSessionHandler( srv, config.getSMBBindAddress(), config.getSessionPort()); sessHandler.setDebug( hasDebug()); try { // Initialize the session handler, and add to the active list sessHandler.initializeSessionHandler( srv); m_handlerList.addHandler( sessHandler); } catch ( IOException ex) { throw new InvalidConfigurationException( "Error initializing NetBIOS SMB session handler, " + ex.getMessage()); } } // Check if any session handlers were created if ( m_handlerList.numberOfHandlers() == 0) throw new InvalidConfigurationException( "No CIFS session handlers enabled"); // Set the client socket timeout m_clientSocketTimeout = config.getSocketTimeout(); // Create the session request handler list and add the first handler m_requestHandlers = new Vector<CIFSRequestHandler>(); CIFSRequestHandler reqHandler = new CIFSRequestHandler( m_server.getThreadPool(), SessionSocketsPerHandler, m_clientSocketTimeout, hasDebug()); reqHandler.setThreadDebug( m_threadDebug); reqHandler.setListener( this); m_requestHandlers.add( reqHandler); } /** * Start the connection handler thread */ public final void startHandler() { // Start the connection handler in its own thread m_thread = new Thread( this); m_thread.setName( "CIFSConnectionsHandler"); m_thread.setDaemon( false); m_thread.start(); // Start the idle session reaper thread, if session timeouts are enabled if ( m_clientSocketTimeout > 0) m_idleSessReaper = new IdleSessionReaper( m_clientSocketTimeout / 2); } /** * Stop the connections handler */ public final void stopHandler() { // Check if the thread is running if ( m_thread != null) { m_shutdown = true; try { m_thread.interrupt(); } catch (Exception ex) { } // Stop the idle session reaper thread, if enabled if ( m_idleSessReaper != null) m_idleSessReaper.shutdownRequest(); } } /** * Run the connections handler in a seperate thread */ public void run() { // Clear the shutdown flag, may have been restarted m_shutdown = false; // Initialize the socket selector try { // Create the selector m_selector = Selector.open(); // Register the server sockets with the selector for ( int idx = 0; idx < m_handlerList.numberOfHandlers(); idx++) { // Get the current server socket channel and register with the selector for socket accept events ChannelSessionHandler curHandler = (ChannelSessionHandler) m_handlerList.getHandlerAt( idx); ServerSocketChannel sockChannel = curHandler.getSocketChannel(); sockChannel.configureBlocking( false); sockChannel.register( m_selector, SelectionKey.OP_ACCEPT, curHandler); // DEBUG if ( Debug.EnableInfo && hasDebug()) Debug.println( "[SMB] Listening for connections on " + curHandler); } } catch ( IOException ex) { // DEBUG if ( Debug.EnableInfo && hasDebug()) { Debug.println( "[SMB] Error opening/registering Selector"); Debug.println( ex); } m_shutdown = true; } // Loop until shutdown while ( m_shutdown != true) { // DEBUG if ( Debug.EnableInfo && hasDebug()) Debug.println( "[SMB] Waiting for new connection ..."); // Wait until there are some connections int connCnt = 0; try { connCnt = m_selector.select(); } catch ( IOException ex) { // DEBUG if ( Debug.EnableError && hasDebug()) { Debug.println( "[SMB] Error waiting for connection"); Debug.println(ex); } } // Check if there are any connection events to process if ( connCnt == 0) continue; // Iterate the selected keys Iterator<SelectionKey> keysIter = m_selector.selectedKeys().iterator(); while ( keysIter.hasNext()) { // Get the current selection key and check if there is an incoming connection SelectionKey selKey = keysIter.next(); if ( selKey.isAcceptable()) { try { // Get the listening server socket, accept the new client connection ServerSocketChannel srvChannel = (ServerSocketChannel) selKey.channel(); SocketChannel sockChannel = srvChannel.accept(); // Create a packet handler for the new connection ChannelSessionHandler channelHandler = (ChannelSessionHandler) selKey.attachment(); PacketHandler pktHandler = channelHandler.createPacketHandler(sockChannel); // Create the new session SMBSrvSession sess = SMBSrvSession.createSession( pktHandler, m_server, ++m_sessId); // DEBUG if ( Debug.EnableInfo && hasDebug()) Debug.println( "[SMB] Connection from " + sockChannel.socket().getRemoteSocketAddress() + ", handler=" + channelHandler + ", sess=" + sess.getUniqueId()); // Add the new session to a request handler thread queueSessionToHandler( sess); } catch ( IOException ex) { // DEBUG if ( Debug.EnableError && hasDebug()) { Debug.println( "[SMB] Failed to accept connection"); Debug.println( ex); } } } // Remove the key from the selected list keysIter.remove(); } } // Close the session handlers for ( int idx = 0; idx < m_handlerList.numberOfHandlers(); idx++) { // Close the current session handler ChannelSessionHandler sessHandler = (ChannelSessionHandler) m_handlerList.getHandlerAt( idx); sessHandler.closeSessionHandler( null); // DEBUG if ( Debug.EnableInfo && hasDebug()) Debug.println( "[SMB] Closed session handler " + sessHandler); } // Close the request handlers while ( m_requestHandlers.size() > 0) { // Close the current request handler CIFSRequestHandler reqHandler = m_requestHandlers.remove( 0); reqHandler.closeHandler(); // DEBUG if ( Debug.EnableInfo && hasDebug()) Debug.println( "[SMB] Closed request handler, " + reqHandler.getName()); } // Close the selector if ( m_selector != null) { try { m_selector.close(); } catch (Exception ex) { // DEBUG if ( Debug.EnableError && hasDebug()) Debug.println( "[SMB] Error closing socket selector, " + ex.getMessage()); } } // Clear the active thread before exiting m_thread = null; } /** * Queue a new session to a request handler * * @param sess SMBSrvSession */ private final void queueSessionToHandler( SMBSrvSession sess) { // Check if the current handler has room for a new session CIFSRequestHandler reqHandler = null; synchronized ( m_requestHandlers) { // Get the head of the request handler list reqHandler = m_requestHandlers.firstElement(); if ( reqHandler == null || reqHandler.hasFreeSessionSlot() == false) { // Create a new session request handler and add to the head of the list reqHandler = new CIFSRequestHandler( m_server.getThreadPool(), SessionSocketsPerHandler, m_clientSocketTimeout, hasDebug()); reqHandler.setThreadDebug( m_threadDebug); reqHandler.setListener( this); m_requestHandlers.add( 0, reqHandler); // DEBUG if ( Debug.EnableInfo && hasDebug()) Debug.println( "[SMB] Added new CIFS request handler, " + reqHandler); } } // Queue the new session to the current request handler reqHandler.queueSessionToHandler( sess); } /** * Enable/disable debug output * * @param ena boolean */ public final void setDebug( boolean ena) { m_debug = ena; } /** * Request handler has no sessions to listen for events for * * @param reqHandler RequestHandler */ public void requestHandlerEmpty(RequestHandler reqHandler) { synchronized ( m_handlerList) { // Check if the request handler is the current head of the handler list, if not then we can close // this request handler if ( m_requestHandlers.get( 0).getName().equals( reqHandler.getName()) == false) { // Remove the handler from the request handler list m_requestHandlers.remove( reqHandler); // Close the request handler reqHandler.closeHandler(); // DEBUG if ( Debug.EnableInfo && hasDebug()) Debug.println( "[SMB] Removed empty request handler, " + reqHandler.getName()); } } } }