/*
* 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());
}
}
}
}