/* * 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.win32; import java.io.IOException; import java.util.Enumeration; import java.util.Hashtable; import java.util.Vector; import org.alfresco.jlan.debug.Debug; import org.alfresco.jlan.netbios.NetBIOSName; import org.alfresco.jlan.netbios.win32.NetBIOSSocket; import org.alfresco.jlan.netbios.win32.Win32NetBIOS; import org.alfresco.jlan.netbios.win32.Winsock2; import org.alfresco.jlan.netbios.win32.WinsockNetBIOSException; import org.alfresco.jlan.server.SrvSessionQueue; import org.alfresco.jlan.server.thread.ThreadRequestPool; import org.alfresco.jlan.smb.server.SMBSrvSession; import org.alfresco.jlan.smb.server.nio.AsynchronousWritesHandler; import org.alfresco.jlan.smb.server.nio.RequestHandler; import org.alfresco.jlan.smb.server.win32.WinsockNetBIOSPacketHandler; /** * CIFS Request Handler Class * * <p>Handles the receiving of CIFS requests for a number of CIFS sessions. * * @author gkspencer */ public class AsyncWinsockCIFSRequestHandler extends RequestHandler implements Runnable { // Request handler index, used to generate the thread name private static int _handlerId; // NetBIOS name that the clients are connecting to, required to make a loopback connection private NetBIOSName m_srvName; private int m_srvLANA; // Selector used to monitor a group of socket channels for incoming requests // private NetBIOSSelector m_nbSelector; // Thread that the request handler runs in private Thread m_thread; private int m_threadId; // Thread pool for processing requests private ThreadRequestPool m_threadPool; // Queue of sessions that are pending setup with the selector private SrvSessionQueue m_sessQueue; // Client socket timeout private int m_clientSocketTimeout; // Socket event to session mapping private Hashtable<Integer, SMBSrvSession> m_eventTable; // Socket event arrays // // The first element in the active array is used to wakeup the main request handler thread. private int[] m_win32ActiveEvents; private int m_activeEventsLen; private int[] m_requeueEvents; private int m_requeueLen; // shutdown request flag private boolean m_shutdown; /** * Class constructor * * @param srvName NetBIOSName * @param srvLANA int * @param threadPool ThreadRequestPool * @param maxSess int * @param sockTmo int */ public AsyncWinsockCIFSRequestHandler( NetBIOSName srvName, int srvLANA, ThreadRequestPool threadPool, int maxSess, int sockTmo) { super( maxSess); // File server name and LANA m_srvName = srvName; m_srvLANA = srvLANA; // Set the thread pool to use for request processing m_threadPool = threadPool; // Set the client socket timeout m_clientSocketTimeout = sockTmo; // Create the session queue m_sessQueue = new SrvSessionQueue(); // Allocate the active event array, and requeue array m_win32ActiveEvents = new int[maxSess]; m_requeueEvents = new int[maxSess]; // Create the event to CIFS session mapping table m_eventTable = new Hashtable<Integer, SMBSrvSession>( maxSess); // Unique id for the thread m_threadId = ++_handlerId; // Start the request handler in a seperate thread m_thread = new Thread( this); m_thread.setName( "AsyncWinsockRequestHandler_" + m_threadId); m_thread.setDaemon( false); m_thread.start(); } /** * Return the current session count * * @return int */ public final int getCurrentSessionCount() { return m_eventTable.size(); } /** * Check if this request handler has free session slots available * * @return boolean */ public final boolean hasFreeSessionSlot() { return ( getCurrentSessionCount() + m_sessQueue.numberOfSessions()) < getMaximumSessionCount() ? true : false; } /** * Return the client socket timeout, in milliseconds * * @return int */ public final int getSocketTimeout() { return m_clientSocketTimeout; } /** * Set the client socket timeout, in milliseconds * * @param tmo int */ public final void setSocketTimeout(int tmo) { m_clientSocketTimeout = tmo; } /** * Queue a new session to the request handler, wakeup the request handler thread to register it with the * selector. * * @param sess SMBSrvSession */ public final void queueSessionToHandler( SMBSrvSession sess) { // Add the new session to the pending queue m_sessQueue.addSession( sess); // Wakeup the main thread to process the new session queue wakeupRequestHandler(); } /** * Return the request handler name * * @return String */ public final String getName() { if ( m_thread != null) return m_thread.getName(); return "AsyncWinsockRequestHandler"; } /** * Run the main processing in a seperate thread */ public void run() { // Clear the shutdown flag, may have been restarted m_shutdown = false; // Create the wakeup event try { // Create the event used to wakeup the request handler thread to add/remove sessions m_win32ActiveEvents[0] = Win32NetBIOS.Win32CreateEvent(); m_activeEventsLen++; } catch ( Exception ex) { // DEBUG if ( Debug.EnableError && hasDebug()) { Debug.println( "[SMB] Failed to initialize wakeup event " + m_thread.getName()); Debug.println(ex); } // Force the request handler to shut down m_shutdown = true; } // Loop until shutdown int eventIdx = -1; while ( m_shutdown == false) { // Check if there are any sessions registered if ( m_activeEventsLen == 1 && m_eventTable.size() == 0) { // Indicate that this request handler has no active sessions fireRequestHandlerEmptyEvent(); // DEBUG if ( Debug.EnableInfo && hasDebug()) Debug.println( "[SMB] Request handler " + m_thread.getName() + " waiting for session ..."); } // Wait for a wakeup event or socket event try { eventIdx = Win32NetBIOS.WinsockWaitForMultipleEvents( m_activeEventsLen, m_win32ActiveEvents, false, Winsock2.WSA_INFINITE, false); } catch ( WinsockNetBIOSException ex) { // DEBUG if ( Debug.EnableDbg && hasDebug()) { Debug.println("[SMB] Error waiting for event"); Debug.println( ex); } // Set the event index, might as well process the session queue eventIdx = 0; } // Check if the shutdown flag has been set if ( m_shutdown == true) continue; // Determine if the triggered event is wakeup or socket event if ( eventIdx == 0) { // DEBUG // if ( Debug.EnableInfo && hasDebug()) // Debug.println( "[SMB] Wakeup event, active=" + m_activeEventsLen); // Reset the wakeup event Win32NetBIOS.Win32ResetEvent( m_win32ActiveEvents[ 0]); // Requeue socket events if ( m_requeueLen > 0) { // DEBUG // if ( Debug.EnableDbg && hasDebug()) // Debug.println("[SMB] Requeue events to active list, requeueLen=" + m_requeueLen); // Requeue socket events to the active event list synchronized ( m_requeueEvents) { // Copy the events to the active list for ( int i = 0; i < m_requeueLen; i++) { // Get the event id of the event being requeued Integer eventId = new Integer( m_requeueEvents[ i]); SMBSrvSession sess = m_eventTable.get( eventId); // Get the socket via the sessions packet handler WinsockNetBIOSPacketHandler winsockPktHandler = (WinsockNetBIOSPacketHandler) sess.getPacketHandler(); NetBIOSSocket nbSocket = winsockPktHandler.getSocket(); // Reset the events to trigger on try { // Process any pending events for the socket processSocketEvent( m_requeueEvents [ i]); int evtEnum = Win32NetBIOS.WinsockEnumNetworkEvents( nbSocket.getSocket(), 0); if ( evtEnum != 0) Debug.println("[SMB] Requeue event, evtEnum=0x" + Integer.toHexString( evtEnum)); // Reset the events to be reported for the socket Win32NetBIOS.WinsockEventSelect( nbSocket.getSocket(), m_requeueEvents[ i], Winsock2.FD_READ | Winsock2.FD_WRITE | Winsock2.FD_CLOSE); } catch ( Exception ex) { Debug.println("[SMB] Error re-enabling FD_READ events sess=" + sess.getUniqueId()); Debug.println(ex); } } // Reset the requeue list m_requeueLen = 0; } } // Register the new sessions while ( m_sessQueue.numberOfSessions() > 0) { // Get a new session from the queue SMBSrvSession sess = (SMBSrvSession) m_sessQueue.removeSessionNoWait(); if ( sess != null) { // DEBUG if ( Debug.EnableError && hasDebug()) Debug.println( "[SMB] Register session with request handler, handler=" + m_thread.getName() + ", sess=" + sess.getUniqueId()); // Get the NetBIOS socket from the sessions packet handler if ( sess.getPacketHandler() instanceof WinsockNetBIOSPacketHandler) { // Get the channel packet handler and create a socket event for the new session WinsockNetBIOSPacketHandler winsockPktHandler = (WinsockNetBIOSPacketHandler) sess.getPacketHandler(); NetBIOSSocket nbSocket = winsockPktHandler.getSocket(); try { // Create a socket event for the new session int sockEvent = Win32NetBIOS.WinsockCreateEvent(); // Set the socket buffer sizes Win32NetBIOS.setSocketReceiveBufferSize(nbSocket.getSocket(), 128000); Win32NetBIOS.setSocketSendBufferSize(nbSocket.getSocket(), 256000); // Enable the required socket events for the session socket nbSocket.configureBlocking( false); Win32NetBIOS.WinsockEventSelect( nbSocket.getSocket(), sockEvent, Winsock2.FD_READ | Winsock2.FD_WRITE | Winsock2.FD_CLOSE); // Add the event/session mapping m_eventTable.put( new Integer( sockEvent), sess); // Add the socket event to the active event list m_win32ActiveEvents[ m_activeEventsLen++] = sockEvent; } catch ( IOException ex) { // DEBUG if ( Debug.EnableError && hasDebug()) { Debug.println( "[SMB] Failed to initialize socket event for new session"); Debug.println( ex); } } } } } } else { // Process the socket event processSocketEvent( m_win32ActiveEvents[ eventIdx]); } } // Close all sessions and events if ( m_eventTable.size() > 0) { // Enumerate the event ids Enumeration<Integer> eventIds = m_eventTable.keys(); while ( eventIds.hasMoreElements()) { // Get the current session via the associated event id Integer eventId = eventIds.nextElement(); SMBSrvSession sess = m_eventTable.get( eventId); if ( sess != null) { // DEBUG if ( Debug.EnableDbg && hasDebug()) Debug.println("[SMB] Closing session " + sess.getUniqueId() + ", event=" + eventId); // Release the socket event try { Win32NetBIOS.WinsockCloseEvent( eventId.intValue()); } catch ( Exception ex) { Debug.println(ex); } // Close the session try { sess.closeSession(); } catch ( Exception ex) { Debug.println(ex); } } } } // Close the wakeup event if ( m_win32ActiveEvents[0] != 0) { try { Win32NetBIOS.Win32CloseEvent( m_win32ActiveEvents[0]); } catch ( Exception ex) { Debug.println(ex); } } // DEBUG if ( Debug.EnableInfo && hasDebug()) Debug.println( "[SMB] Closed CIFS request handler, " + m_thread.getName()); } /** * Close the request handler */ public final void closeHandler() { // Check if the thread is running if ( m_thread != null) { m_shutdown = true; // Wakeup the request handler thread wakeupRequestHandler(); } } /** * Check for idle sessions * * @return int */ protected final int checkForIdleSessions() { // Check if the request handler has any active sessions int idleCnt = 0; if ( m_eventTable.size() > 0) { // Time to check long checkTime = System.currentTimeMillis() - (long) m_clientSocketTimeout; // Enumerate the session list Enumeration<SMBSrvSession> enumSess = m_eventTable.elements(); Vector<SMBSrvSession> idleSessList = null; while ( enumSess.hasMoreElements()) { // Get the current session SMBSrvSession sess = enumSess.nextElement(); // Check the time of the last I/O request on this session if ( sess != null && sess.getLastIOTime() < checkTime) { // Add to the list of idle sessions if ( idleSessList == null) idleSessList = new Vector<SMBSrvSession>(); idleSessList.add( sess); } } // Close any sessions that are on the idle list if ( idleSessList != null) { // Close the idle sessions for ( int i = 0; i < idleSessList.size(); i++) { // Get the current idle session SMBSrvSession sess = idleSessList.get( i); // DEBUG if ( Debug.EnableInfo && hasDebug()) Debug.println( "[SMB] Closing idle session, " + sess.getUniqueId()); // Close the session sess.closeSession(); sess.processPacket( null); // Update the idle session count idleCnt++; } } } // Return the count of idle sessions that were queued for removal return idleCnt; } /** * Wakeup the main thread */ protected void wakeupRequestHandler() { // Set the wakeup event if ( Win32NetBIOS.Win32SetEvent( m_win32ActiveEvents[0]) == false) { // DEBUG if ( Debug.EnableDbg && hasDebug()) Debug.println("[SMB] Failed to wakeup request handler, " + m_thread.getName()); } } /** * Requeue a socket event to the active list * * @param sockEvent int * @param sockPtr int */ protected void requeueSocketEvent( int sockEvent, int sockPtr) { // Reset the event status Win32NetBIOS.WinsockResetEvent( sockEvent); /** // Re-enable read events for the socket try { Win32NetBIOS.WinsockEventSelect( sockPtr, sockEvent, Winsock2.FD_READ | Winsock2.FD_WRITE | Winsock2.FD_CLOSE); } catch ( WinsockNetBIOSException ex) { // DEBUG if ( Debug.EnableDbg && hasDebug()) { Debug.println("[SMB] Error setting FD_READ socket event flag"); Debug.println(ex); } } **/ // Add to the requeue list synchronized ( m_requeueEvents) { m_requeueEvents[ m_requeueLen++] = sockEvent; } // Wakeup the main request handler thread // Set the wakeup event if ( Win32NetBIOS.Win32SetEvent( m_win32ActiveEvents[0]) == false) { // DEBUG if ( Debug.EnableDbg && hasDebug()) Debug.println("[SMB] Failed to wakeup request handler (requeue), " + m_thread.getName()); } } /** * Process the socket event * * @param eventIdx int */ private void processSocketEvent( int eventId) { // Get the associated sessions, and socket, for the event Integer eventIdKey = new Integer( eventId); SMBSrvSession sess = m_eventTable.get( eventIdKey); // Get the socket via the sessions packet handler WinsockNetBIOSPacketHandler winsockPktHandler = (WinsockNetBIOSPacketHandler) sess.getPacketHandler(); NetBIOSSocket nbSocket = winsockPktHandler.getSocket(); int triggeredEvent = 0; try { // Find out the event that triggered, check for socket errors triggeredEvent = Win32NetBIOS.WinsockEnumNetworkEvents( nbSocket.getSocket(), 0); } catch ( WinsockNetBIOSException ex) { // DEBUG if ( Debug.EnableDbg && hasDebug()) { Debug.println("[SMB] Socket error event, sess=" + sess.getUniqueId() + ", eventId=" + eventId); Debug.println(ex); } // Close the socket triggeredEvent = Winsock2.FD_CLOSE; } // DEBUG // if ( Debug.EnableDbg && hasDebug()) // Debug.println("[SMB] Triggered id=" + eventId + ", sess=" + sess.getUniqueId() + ", event=0x" + Integer.toHexString( triggeredEvent)); // Check if any events are pending on the socket if ( triggeredEvent == 0) return; // Socket closed if (( triggeredEvent & Winsock2.FD_CLOSE) != 0) { // DEBUG if ( Debug.EnableDbg && hasDebug()) Debug.println("[SMB] Close session event, sess=" + sess.getUniqueId()); // Find the socket event within the list int eventIdx = 0; while ( m_win32ActiveEvents[ eventIdx] != eventId && eventIdx < m_activeEventsLen) eventIdx++; if ( eventIdx == m_activeEventsLen) return; // Remove the socket event from the active list, and shorten the list m_win32ActiveEvents [ eventIdx] = m_win32ActiveEvents[ m_activeEventsLen--]; // Close the session sess.hangupSession( "Client closed socket"); // Close the event try { Win32NetBIOS.WinsockCloseEvent( eventIdKey.intValue()); } catch ( WinsockNetBIOSException ex) { // DEBUG if ( Debug.EnableDbg && hasDebug()) { Debug.println("[SMB] Error closing socket event"); Debug.println(ex); } } // Remove the event/session mapping m_eventTable.remove( eventId); // DEBUG if ( Debug.EnableDbg && hasDebug()) Debug.println("[SMB] Removed session " + sess.getUniqueId()); } // Check for a read event, incoming data on the socket if (( triggeredEvent & Winsock2.FD_READ) != 0) { // DEBUG // if ( Debug.EnableDbg && hasDebug()) // Debug.println("[SMB] Read event, sess=" + sess.getUniqueId()); // Clear the event Win32NetBIOS.WinsockResetEvent( eventIdKey.intValue()); // Remove the read event for the socket /** try { Win32NetBIOS.WinsockEventSelect( nbSocket.getSocket(), eventId, Winsock2.FD_WRITE | Winsock2.FD_CLOSE); } catch ( WinsockNetBIOSException ex) { // DEBUG if ( Debug.EnableDbg && hasDebug()) { Debug.println("[SMB] Error clearing FD_READ socket event flag"); Debug.println(ex); } } **/ // Check if the session is already processing the incoming request if ( sess.hasReadInProgress() == false) { // Update the last I/O time for the session sess.setLastIOTime( System.currentTimeMillis()); // Queue the session to the thread pool for processing AsyncWinsockCIFSReadRequest threadReq = new AsyncWinsockCIFSReadRequest( sess, eventIdKey.intValue(), this); m_threadPool.queueRequest( threadReq); } } // Check for a write event, socket has buffer space for outgoing data if (( triggeredEvent & Winsock2.FD_WRITE) != 0) { // DEBUG if ( Debug.EnableDbg && hasDebug()) Debug.println("[SMB] Write event, sess=" + sess.getUniqueId()); // Clear the event Win32NetBIOS.WinsockResetEvent( eventIdKey.intValue()); // Check if the packet handler has any queued writes AsynchronousWritesHandler writesHandler = (AsynchronousWritesHandler) winsockPktHandler; if ( writesHandler.getQueuedWriteCount() > 0) { // DEBUG if ( Debug.EnableDbg && hasDebug()) Debug.println("[SMB] Submit queued writes for processing, sess=" + sess.getUniqueId()); // Queue the packet handler to the thread pool for processing AsyncWinsockCIFSWriteRequest threadReq = new AsyncWinsockCIFSWriteRequest(sess, eventIdKey.intValue(), this); m_threadPool.queueRequest( threadReq); } } } }