/* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License, Version 1.0 only * (the "License"). You may not use this file except in compliance * with the License. * * You can obtain a copy of the license at * trunk/opends/resource/legal-notices/OpenDS.LICENSE * or https://OpenDS.dev.java.net/OpenDS.LICENSE. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable, * add the following below this CDDL HEADER, with the fields enclosed * by brackets "[]" replaced with your own identifying information: * Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END * * * Copyright 2006-2008 Sun Microsystems, Inc. * Portions Copyright 2013 ForgeRock AS */ package org.opends.server.core; import java.io.File; import java.io.RandomAccessFile; import java.nio.channels.FileChannel; import java.nio.channels.FileLock; import java.util.HashMap; import org.opends.server.api.Backend; import org.opends.server.loggers.debug.DebugTracer; import org.opends.server.types.DebugLogLevel; import static org.opends.messages.CoreMessages.*; import static org.opends.server.loggers.debug.DebugLogger.*; import static org.opends.server.util.ServerConstants.*; import static org.opends.server.util.StaticUtils.*; /** * This class provides a mechanism for allowing the Directory Server to utilize * file locks as provided by the underlying OS. File locks may be exclusive or * shared, and will be visible between different processes on the same system. */ public class LockFileManager { /** * The tracer object for the debug logger. */ private static final DebugTracer TRACER = getTracer(); /** A map between the filenames and the lock files for exclusive locks. */ private static HashMap<String,FileLock> exclusiveLocks = new HashMap<String,FileLock>(); /** A map between the filenames and the lock files for shared locks. */ private static HashMap<String,FileLock> sharedLocks = new HashMap<String,FileLock>(); /** A map between the filenames and reference counts for shared locks. */ private static HashMap<String,Integer> sharedLockReferences = new HashMap<String,Integer>(); /** The lock providing threadsafe access to the lock map data. */ private static Object mapLock = new Object(); /** * Attempts to acquire a shared lock on the specified file. * * @param lockFile The file for which to obtain the shared lock. * @param failureReason A buffer that can be used to hold a reason that the * lock could not be acquired. * * @return <CODE>true</CODE> if the lock was obtained successfully, or * <CODE>false</CODE> if it could not be obtained. */ public static boolean acquireSharedLock(String lockFile, StringBuilder failureReason) { synchronized (mapLock) { // Check to see if there's already an exclusive lock on the file. If so, // then we can't get a shared lock on it. if (exclusiveLocks.containsKey(lockFile)) { failureReason.append( ERR_FILELOCKER_LOCK_SHARED_REJECTED_BY_EXCLUSIVE.get(lockFile)); return false; } // Check to see if we already hold a shared lock on the file. If so, then // increase its refcount and return true. FileLock sharedLock = sharedLocks.get(lockFile); if (sharedLock != null) { int numReferences = sharedLockReferences.get(lockFile); numReferences++; sharedLockReferences.put(lockFile, numReferences); return true; } // We don't hold a lock on the file so we need to create it. First, // create the file only if it doesn't already exist. File f = getFileForPath(lockFile); try { if (! f.exists()) { f.createNewFile(); } } catch (Exception e) { if (debugEnabled()) { TRACER.debugCaught(DebugLogLevel.ERROR, e); } failureReason.append( ERR_FILELOCKER_LOCK_SHARED_FAILED_CREATE.get(lockFile, getExceptionMessage(e))); return false; } // Open the file for reading and get the corresponding file channel. FileChannel channel = null; RandomAccessFile raf = null; try { raf = new RandomAccessFile(lockFile, "r"); channel = raf.getChannel(); } catch (Exception e) { if (debugEnabled()) { TRACER.debugCaught(DebugLogLevel.ERROR, e); } failureReason.append(ERR_FILELOCKER_LOCK_SHARED_FAILED_OPEN.get( lockFile, getExceptionMessage(e))); close(raf); return false; } // Try to obtain a shared lock on the file channel. FileLock fileLock; try { fileLock = channel.tryLock(0L, Long.MAX_VALUE, true); } catch (Exception e) { if (debugEnabled()) { TRACER.debugCaught(DebugLogLevel.ERROR, e); } failureReason.append( ERR_FILELOCKER_LOCK_SHARED_FAILED_LOCK.get( lockFile, getExceptionMessage(e))); close(channel, raf); return false; } // If we could not get the lock, then return false. Otherwise, put it in // the shared lock table with a reference count of 1 and return true. if (fileLock == null) { failureReason.append( ERR_FILELOCKER_LOCK_SHARED_NOT_GRANTED.get(lockFile)); close(channel, raf); return false; } else { sharedLocks.put(lockFile, fileLock); sharedLockReferences.put(lockFile, 1); return true; } } } /** * Attempts to acquire an exclusive lock on the specified file. * * @param lockFile The file for which to obtain the exclusive lock. * @param failureReason A buffer that can be used to hold a reason that the * lock could not be acquired. * * @return <CODE>true</CODE> if the lock was obtained successfully, or * <CODE>false</CODE> if it could not be obtained. */ public static boolean acquireExclusiveLock(String lockFile, StringBuilder failureReason) { synchronized (mapLock) { // Check to see if there's already an exclusive lock on the file. If so, // then we can't get another exclusive lock on it. if (exclusiveLocks.containsKey(lockFile)) { failureReason.append( ERR_FILELOCKER_LOCK_EXCLUSIVE_REJECTED_BY_EXCLUSIVE.get( lockFile)); return false; } // Check to see if we already hold a shared lock on the file. If so, then // we can't get an exclusive lock on it. if (sharedLocks.containsKey(lockFile)) { failureReason.append( ERR_FILELOCKER_LOCK_EXCLUSIVE_REJECTED_BY_SHARED.get(lockFile)); return false; } // We don't hold a lock on the file so we need to create it. First, // create the file only if it doesn't already exist. File f = getFileForPath(lockFile); try { if (! f.exists()) { f.createNewFile(); } } catch (Exception e) { if (debugEnabled()) { TRACER.debugCaught(DebugLogLevel.ERROR, e); } failureReason.append( ERR_FILELOCKER_LOCK_EXCLUSIVE_FAILED_CREATE.get(lockFile, getExceptionMessage(e))); return false; } // Open the file read+write and get the corresponding file channel. FileChannel channel = null; RandomAccessFile raf = null; try { raf = new RandomAccessFile(lockFile, "rw"); channel = raf.getChannel(); } catch (Exception e) { if (debugEnabled()) { TRACER.debugCaught(DebugLogLevel.ERROR, e); } failureReason.append(ERR_FILELOCKER_LOCK_EXCLUSIVE_FAILED_OPEN.get( lockFile, getExceptionMessage(e))); close(raf); return false; } // Try to obtain an exclusive lock on the file channel. FileLock fileLock; try { fileLock = channel.tryLock(0L, Long.MAX_VALUE, false); } catch (Exception e) { if (debugEnabled()) { TRACER.debugCaught(DebugLogLevel.ERROR, e); } failureReason.append( ERR_FILELOCKER_LOCK_EXCLUSIVE_FAILED_LOCK.get(lockFile, getExceptionMessage(e))); close(channel, raf); return false; } // If we could not get the lock, then return false. Otherwise, put it in // the exclusive lock table and return true. if (fileLock == null) { failureReason.append( ERR_FILELOCKER_LOCK_EXCLUSIVE_NOT_GRANTED.get(lockFile)); close(channel, raf); return false; } else { exclusiveLocks.put(lockFile, fileLock); return true; } } } /** * Attempts to release the lock on the specified file. If an exclusive lock * is held, then it will be released. If a shared lock is held, then its * reference count will be reduced, and the lock will be released if the * resulting reference count is zero. If we don't know anything about the * requested file, then don't do anything. * * @param lockFile The file for which to release the associated lock. * @param failureReason A buffer that can be used to hold information about * a problem that occurred preventing the successful * release. * * @return <CODE>true</CODE> if the lock was found and released successfully, * or <CODE>false</CODE> if a problem occurred that might have * prevented the lock from being released. */ public static boolean releaseLock(String lockFile, StringBuilder failureReason) { synchronized (mapLock) { // See if we hold an exclusive lock on the file. If so, then release it // and get remove it from the lock table. FileLock lock = exclusiveLocks.remove(lockFile); if (lock != null) { try { lock.release(); } catch (Exception e) { if (debugEnabled()) { TRACER.debugCaught(DebugLogLevel.ERROR, e); } failureReason.append( ERR_FILELOCKER_UNLOCK_EXCLUSIVE_FAILED_RELEASE.get(lockFile, getExceptionMessage(e))); return false; } try { lock.channel().close(); } catch (Exception e) { if (debugEnabled()) { TRACER.debugCaught(DebugLogLevel.ERROR, e); } // Even though we couldn't close the channel for some reason, this // should still be OK because we released the lock above. } return true; } // See if we hold a shared lock on the file. If so, then reduce its // refcount and release only if the resulting count is zero. lock = sharedLocks.get(lockFile); if (lock != null) { int refCount = sharedLockReferences.get(lockFile); refCount--; if (refCount <= 0) { sharedLocks.remove(lockFile); sharedLockReferences.remove(lockFile); try { lock.release(); } catch (Exception e) { if (debugEnabled()) { TRACER.debugCaught(DebugLogLevel.ERROR, e); } failureReason.append(ERR_FILELOCKER_UNLOCK_SHARED_FAILED_RELEASE .get(lockFile, getExceptionMessage(e))); return false; } try { lock.channel().close(); } catch (Exception e) { if (debugEnabled()) { TRACER.debugCaught(DebugLogLevel.ERROR, e); } // Even though we couldn't close the channel for some reason, this // should still be OK because we released the lock above. } } else { sharedLockReferences.put(lockFile, refCount); } return true; } // We didn't find a reference to the file. We'll have to return false // since either we lost the reference or we're trying to release a lock // we never had. Both of them are bad. failureReason.append(ERR_FILELOCKER_UNLOCK_UNKNOWN_FILE.get(lockFile)); return false; } } /** * Retrieves the path to the directory that should be used to hold the lock * files. * * @return The path to the directory that should be used to hold the lock * files. */ public static String getLockDirectoryPath() { File lockDirectory = DirectoryServer.getEnvironmentConfig().getLockDirectory(); return lockDirectory.getAbsolutePath(); } /** * Retrieves the filename that should be used for the lock file for the * Directory Server instance. * * @return The filename that should be used for the lock file for the * Directory Server instance. */ public static String getServerLockFileName() { StringBuilder buffer = new StringBuilder(); buffer.append(getLockDirectoryPath()); buffer.append(File.separator); buffer.append(SERVER_LOCK_FILE_NAME); buffer.append(LOCK_FILE_SUFFIX); return buffer.toString(); } /** * Retrieves the filename that should be used for the lock file for the * specified backend. * * @param backend The backend for which to retrieve the filename for the * lock file. * * @return The filename that should be used for the lock file for the * specified backend. */ public static String getBackendLockFileName(Backend backend) { StringBuilder buffer = new StringBuilder(); buffer.append(getLockDirectoryPath()); buffer.append(File.separator); buffer.append(BACKEND_LOCK_FILE_PREFIX); buffer.append(backend.getBackendID()); buffer.append(LOCK_FILE_SUFFIX); return buffer.toString(); } }