/* * Copyright (C) 2005-2010 Alfresco Software Limited. * * This file is part of Alfresco * * Alfresco 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 3 of the License, or * (at your option) any later version. * * Alfresco 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 Alfresco. If not, see . */ package org.alfresco.filesys.state; import java.io.IOException; import java.util.Enumeration; import java.util.Hashtable; import org.alfresco.jlan.debug.Debug; import org.alfresco.jlan.locking.FileLock; import org.alfresco.jlan.locking.LockConflictException; import org.alfresco.jlan.locking.NotLockedException; import org.alfresco.jlan.server.SrvSession; import org.alfresco.jlan.server.filesys.ExistingOpLockException; import org.alfresco.jlan.server.filesys.NetworkFile; import org.alfresco.jlan.server.filesys.TreeConnection; import org.alfresco.jlan.server.filesys.pseudo.MemoryNetworkFile; import org.alfresco.jlan.server.locking.LockManager; import org.alfresco.jlan.server.locking.OpLockDetails; import org.alfresco.jlan.server.locking.OpLockManager; import org.alfresco.jlan.smb.OpLock; import org.alfresco.jlan.smb.SMBStatus; import org.alfresco.jlan.smb.server.SMBSrvPacket; import org.alfresco.jlan.smb.server.SMBSrvSession; import org.alfresco.filesys.alfresco.AlfrescoNetworkFile; /** * File State Lock Manager Class * *

Implementation of a lock manager that uses the file state cache to track locks on a file. * * @author gkspencer */ public class FileStateLockManager implements LockManager, OpLockManager, Runnable { // Oplock break timeout private static final long OpLockBreakTimeout = 5000L; // 5 seconds // File state cache used for byte range locks/oplocks private FileStateTable m_stateCache; // Oplock breaks in progress private Hashtable m_oplockQueue; // Oplock break timeout thread private Thread m_expiryThread; private boolean m_shutdown; /** * Lock a byte range within a file, or the whole file. * * @param sess SrvSession * @param tree TreeConnection * @param file NetworkFile * @param lock FileLock * @exception LockConflictException * @exception IOException */ public void lockFile(SrvSession sess, TreeConnection tree, NetworkFile file, FileLock lock) throws LockConflictException, IOException { // Get the file state associated with the file FileState fstate = null; if ( file instanceof AlfrescoNetworkFile) { AlfrescoNetworkFile alfFile = (AlfrescoNetworkFile) file; fstate = alfFile.getFileState(); } else if ( file instanceof MemoryNetworkFile) { file.addLock(lock); return; } if ( fstate == null) throw new IOException("Open file without state (lock)"); // Add the lock to the active lock list for the file, check if the new lock conflicts with // any existing locks. Add the lock to the file instance so that locks can be removed if the // file is closed/session abnormally terminates. fstate.addLock(lock); file.addLock(lock); } /** * Unlock a byte range within a file, or the whole file * * @param sess SrvSession * @param tree TreeConnection * @param file NetworkFile * @param lock FileLock * @exception NotLockedException * @exception IOException */ public void unlockFile(SrvSession sess, TreeConnection tree, NetworkFile file, FileLock lock) throws NotLockedException, IOException { // Get the file state associated with the file FileState fstate = null; if ( file instanceof AlfrescoNetworkFile) { AlfrescoNetworkFile alfFile = (AlfrescoNetworkFile) file; fstate = alfFile.getFileState(); } else if ( file instanceof MemoryNetworkFile) { file.removeLock(lock); return; } if ( fstate == null) throw new IOException("Open file without state (unlock)"); // Remove the lock from the active lock list for the file, and the file instance fstate.removeLock(lock); file.removeLock(lock); } /** * Create a lock object, use the standard FileLock object. * * @param sess SrvSession * @param tree TreeConnection * @param file NetworkFile * @param offset long * @param len long * @param pid int */ public FileLock createLockObject(SrvSession sess, TreeConnection tree, NetworkFile file, long offset, long len, int pid) { // Create a lock object to represent the file lock return new FileLock(offset, len, pid); } /** * Release all locks that a session has on a file. This method is called to perform cleanup if a file * is closed that has active locks or if a session abnormally terminates. * * @param sess SrvSession * @param tree TreeConnection * @param file NetworkFile */ public void releaseLocksForFile(SrvSession sess, TreeConnection tree, NetworkFile file) { // Check if the file has active locks if ( file.hasLocks()) { synchronized ( file) { // Enumerate the locks and remove while ( file.numberOfLocks() > 0) { // Get the current file lock FileLock curLock = file.getLockAt(0); // Remove the lock, ignore errors try { // Unlock will remove the lock from the global list and the local files list unlockFile(sess, tree, file, curLock); } catch (Exception ex) { } } } } } /** * Enable oplock support by setting the file state table * * @param stateTable FileStateTable */ public final void setStateTable(FileStateTable stateTable) { m_stateCache = stateTable; // Create the oplock break queue m_oplockQueue = new Hashtable(); // Start the oplock break expiry thread m_expiryThread = new Thread(this); m_expiryThread.setDaemon(true); m_expiryThread.setName("OpLockExpire"); m_expiryThread.start(); } /** * Check if there is an oplock for the specified path, return the oplock type. * * @param path String * @return int */ public int hasOpLock(String path) { // Check if oplocks/state cache are enabled if ( m_stateCache == null) return OpLock.TypeNone; // Get the file state FileState fstate = m_stateCache.findFileState(path); if ( fstate != null && fstate.hasOpLock()) { // Return the oplock type OpLockDetails oplock = fstate.getOpLock(); if ( oplock != null) return oplock.getLockType(); } // No oplock return OpLock.TypeNone; } /** * Return the oplock details for a path, or null if there is no oplock on the path * * @param path String * @return OpLockDetails */ public OpLockDetails getOpLockDetails(String path) { // Check if oplocks/state cache are enabled if ( m_stateCache == null) return null; // Get the file state FileState fstate = m_stateCache.findFileState(path); if ( fstate != null) return fstate.getOpLock(); // No oplock return null; } /** * Grant an oplock, store the oplock details * * @param path String * @param oplock OpLockDetails * @return boolean * @exception ExistingOpLockException If the file already has an oplock */ public boolean grantOpLock(String path, OpLockDetails oplock) throws ExistingOpLockException { // Check if oplocks/state cache are enabled if ( m_stateCache == null) return false; // Get, or create, a file state FileState fstate = m_stateCache.findFileState(path, false, true); // Check if the file is already in use if ( fstate.getOpenCount() != 1) return false; // Set the oplock fstate.setOpLock( oplock); return true; } /** * Inform the oplock manager that an oplock break is in progress for the specified file/oplock * * @param path String * @param oplock OpLockDetails */ public void informOpLockBreakInProgress(String path, OpLockDetails oplock) { // Check if oplocks/state cache are enabled if ( m_stateCache == null) return; // Add the oplock to the break in progress queue synchronized ( m_oplockQueue) { m_oplockQueue.put( path, oplock); m_oplockQueue.notify(); } } /** * Release an oplock * * @param path String */ public void releaseOpLock(String path) { // Check if oplocks/state cache are enabled if ( m_stateCache == null) return; // Get the file state FileState fstate = m_stateCache.findFileState(path); if ( fstate != null) fstate.clearOpLock(); // Remove from the pending oplock break queue synchronized ( m_oplockQueue) { m_oplockQueue.remove( path); } } /** * Check for expired oplock break requests * * @return int */ public int checkExpiredOplockBreaks() { // Check if there are ny oplock breaks in progress if ( m_oplockQueue.size() == 0) return 0; // Check for oplock break requests that have expired int expireCnt = 0; long timeNow = System.currentTimeMillis(); Enumeration opBreakKeys = m_oplockQueue.keys(); while ( opBreakKeys.hasMoreElements()) { // Check the current oplock break String path = opBreakKeys.nextElement(); OpLockDetails opLock = m_oplockQueue.get( path); if ( opLock != null) { // Check if the oplock break has timed out if ( opLock.hasDeferredSession() && (opLock.getOplockBreakTime() + OpLockBreakTimeout) <= timeNow) { // Get the deferred request details SMBSrvSession sess = opLock.getDeferredSession(); SMBSrvPacket pkt = opLock.getDeferredPacket(); try { // Return an error for the deferred file open request if ( sess.sendAsyncErrorResponseSMB( pkt, SMBStatus.NTAccessDenied, SMBStatus.NTErr) == true) { // DEBUG if ( Debug.EnableDbg && sess.hasDebug( SMBSrvSession.DBG_OPLOCK)) sess.debugPrintln( "Oplock break timeout, oplock=" + opLock); // Release the packet back to the pool sess.getPacketPool().releasePacket( pkt); } else if ( Debug.EnableDbg && sess.hasDebug( SMBSrvSession.DBG_OPLOCK)) sess.debugPrintln( "Failed to send open reject, oplock break timed out, oplock=" + opLock); } catch ( IOException ex) { } // Remove the oplock break from the queue m_oplockQueue.remove( path); // Clear the deferred packet details opLock.clearDeferredSession(); // Mark the oplock has having a failed oplock break opLock.setOplockBreakFailed(); // Update the expired oplock break count expireCnt++; } } } // Return the count of expired oplock breaks return expireCnt; } /** * Run the oplock break expiry */ public void run() { // Loop forever m_shutdown = false; while ( m_shutdown == false) { // Wait for an oplock break or sleep for a while if there are active oplock break requests try { synchronized ( m_oplockQueue) { if ( m_oplockQueue.size() == 0) m_oplockQueue.wait(); } // Oplock break added to the queue, wait a while before checking the queue if ( m_oplockQueue.size() > 0) Thread.sleep( OpLockBreakTimeout); } catch (InterruptedException ex) { } // Check for shutdown if ( m_shutdown == true) return; // Check for expired oplock break requests checkExpiredOplockBreaks(); } } /** * Request the oplock break expiry thread to shutdown */ public final void shutdownRequest() { m_shutdown = true; if ( m_expiryThread != null) { try { m_expiryThread.interrupt(); } catch (Exception ex) { } } } }