001/* 002 * The contents of this file are subject to the terms of the Common Development and 003 * Distribution License (the License). You may not use this file except in compliance with the 004 * License. 005 * 006 * You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the 007 * specific language governing permission and limitations under the License. 008 * 009 * When distributing Covered Software, include this CDDL Header Notice in each file and include 010 * the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL 011 * Header, with the fields enclosed by brackets [] replaced by your own identifying 012 * information: "Portions Copyright [year] [name of copyright owner]". 013 * 014 * Copyright 2006-2008 Sun Microsystems, Inc. 015 * Portions Copyright 2013-2015 ForgeRock AS. 016 */ 017package org.opends.server.core; 018 019import java.io.File; 020import java.io.RandomAccessFile; 021import java.nio.channels.FileChannel; 022import java.nio.channels.FileLock; 023import java.util.HashMap; 024import java.util.Map; 025 026import org.opends.server.api.Backend; 027import org.forgerock.i18n.slf4j.LocalizedLogger; 028 029import static org.opends.messages.CoreMessages.*; 030import static org.opends.server.util.ServerConstants.*; 031import static org.opends.server.util.StaticUtils.*; 032 033/** 034 * This class provides a mechanism for allowing the Directory Server to utilize 035 * file locks as provided by the underlying OS. File locks may be exclusive or 036 * shared, and will be visible between different processes on the same system. 037 */ 038public class LockFileManager 039{ 040 private static final LocalizedLogger logger = LocalizedLogger.getLoggerForThisClass(); 041 042 /** A map between the filenames and the lock files for exclusive locks. */ 043 private static Map<String, FileLock> exclusiveLocks = new HashMap<>(); 044 /** A map between the filenames and the lock files for shared locks. */ 045 private static Map<String, FileLock> sharedLocks = new HashMap<>(); 046 /** A map between the filenames and reference counts for shared locks. */ 047 private static Map<String, Integer> sharedLockReferences = new HashMap<>(); 048 049 /** The lock providing threadsafe access to the lock map data. */ 050 private static Object mapLock = new Object(); 051 052 053 054 /** 055 * Attempts to acquire a shared lock on the specified file. 056 * 057 * @param lockFile The file for which to obtain the shared lock. 058 * @param failureReason A buffer that can be used to hold a reason that the 059 * lock could not be acquired. 060 * 061 * @return <CODE>true</CODE> if the lock was obtained successfully, or 062 * <CODE>false</CODE> if it could not be obtained. 063 */ 064 public static boolean acquireSharedLock(String lockFile, 065 StringBuilder failureReason) 066 { 067 synchronized (mapLock) 068 { 069 // Check to see if there's already an exclusive lock on the file. If so, 070 // then we can't get a shared lock on it. 071 if (exclusiveLocks.containsKey(lockFile)) 072 { 073 failureReason.append( 074 ERR_FILELOCKER_LOCK_SHARED_REJECTED_BY_EXCLUSIVE.get(lockFile)); 075 return false; 076 } 077 078 079 // Check to see if we already hold a shared lock on the file. If so, then 080 // increase its refcount and return true. 081 FileLock sharedLock = sharedLocks.get(lockFile); 082 if (sharedLock != null) 083 { 084 int numReferences = sharedLockReferences.get(lockFile); 085 numReferences++; 086 sharedLockReferences.put(lockFile, numReferences); 087 return true; 088 } 089 090 091 // We don't hold a lock on the file so we need to create it. First, 092 // create the file only if it doesn't already exist. 093 File f = getFileForPath(lockFile); 094 try 095 { 096 if (! f.exists()) 097 { 098 f.createNewFile(); 099 } 100 } 101 catch (Exception e) 102 { 103 logger.traceException(e); 104 105 failureReason.append( 106 ERR_FILELOCKER_LOCK_SHARED_FAILED_CREATE.get(lockFile, 107 getExceptionMessage(e))); 108 return false; 109 } 110 111 112 // Open the file for reading and get the corresponding file channel. 113 FileChannel channel = null; 114 RandomAccessFile raf = null; 115 try 116 { 117 raf = new RandomAccessFile(lockFile, "r"); 118 channel = raf.getChannel(); 119 } 120 catch (Exception e) 121 { 122 logger.traceException(e); 123 124 failureReason.append(ERR_FILELOCKER_LOCK_SHARED_FAILED_OPEN.get( 125 lockFile, getExceptionMessage(e))); 126 close(raf); 127 return false; 128 } 129 130 131 // Try to obtain a shared lock on the file channel. 132 FileLock fileLock; 133 try 134 { 135 fileLock = channel.tryLock(0L, Long.MAX_VALUE, true); 136 } 137 catch (Exception e) 138 { 139 logger.traceException(e); 140 141 failureReason.append( 142 ERR_FILELOCKER_LOCK_SHARED_FAILED_LOCK.get( 143 lockFile, getExceptionMessage(e))); 144 close(channel, raf); 145 return false; 146 } 147 148 149 // If we could not get the lock, then return false. Otherwise, put it in 150 // the shared lock table with a reference count of 1 and return true. 151 if (fileLock == null) 152 { 153 failureReason.append( 154 ERR_FILELOCKER_LOCK_SHARED_NOT_GRANTED.get(lockFile)); 155 close(channel, raf); 156 return false; 157 } 158 else 159 { 160 sharedLocks.put(lockFile, fileLock); 161 sharedLockReferences.put(lockFile, 1); 162 return true; 163 } 164 } 165 } 166 167 168 169 /** 170 * Attempts to acquire an exclusive lock on the specified file. 171 * 172 * @param lockFile The file for which to obtain the exclusive lock. 173 * @param failureReason A buffer that can be used to hold a reason that the 174 * lock could not be acquired. 175 * 176 * @return <CODE>true</CODE> if the lock was obtained successfully, or 177 * <CODE>false</CODE> if it could not be obtained. 178 */ 179 public static boolean acquireExclusiveLock(String lockFile, 180 StringBuilder failureReason) 181 { 182 synchronized (mapLock) 183 { 184 // Check to see if there's already an exclusive lock on the file. If so, 185 // then we can't get another exclusive lock on it. 186 if (exclusiveLocks.containsKey(lockFile)) 187 { 188 failureReason.append( 189 ERR_FILELOCKER_LOCK_EXCLUSIVE_REJECTED_BY_EXCLUSIVE.get( 190 lockFile)); 191 return false; 192 } 193 194 195 // Check to see if we already hold a shared lock on the file. If so, then 196 // we can't get an exclusive lock on it. 197 if (sharedLocks.containsKey(lockFile)) 198 { 199 failureReason.append( 200 ERR_FILELOCKER_LOCK_EXCLUSIVE_REJECTED_BY_SHARED.get(lockFile)); 201 return false; 202 } 203 204 205 // We don't hold a lock on the file so we need to create it. First, 206 // create the file only if it doesn't already exist. 207 File f = getFileForPath(lockFile); 208 try 209 { 210 if (! f.exists()) 211 { 212 f.createNewFile(); 213 } 214 } 215 catch (Exception e) 216 { 217 logger.traceException(e); 218 failureReason.append( 219 ERR_FILELOCKER_LOCK_EXCLUSIVE_FAILED_CREATE.get(lockFile, 220 getExceptionMessage(e))); 221 return false; 222 } 223 224 225 // Open the file read+write and get the corresponding file channel. 226 FileChannel channel = null; 227 RandomAccessFile raf = null; 228 try 229 { 230 raf = new RandomAccessFile(lockFile, "rw"); 231 channel = raf.getChannel(); 232 } 233 catch (Exception e) 234 { 235 logger.traceException(e); 236 237 failureReason.append(ERR_FILELOCKER_LOCK_EXCLUSIVE_FAILED_OPEN.get( 238 lockFile, getExceptionMessage(e))); 239 close(raf); 240 return false; 241 } 242 243 244 // Try to obtain an exclusive lock on the file channel. 245 FileLock fileLock; 246 try 247 { 248 fileLock = channel.tryLock(0L, Long.MAX_VALUE, false); 249 } 250 catch (Exception e) 251 { 252 logger.traceException(e); 253 254 failureReason.append( 255 ERR_FILELOCKER_LOCK_EXCLUSIVE_FAILED_LOCK.get(lockFile, 256 getExceptionMessage(e))); 257 close(channel, raf); 258 return false; 259 } 260 261 262 // If we could not get the lock, then return false. Otherwise, put it in 263 // the exclusive lock table and return true. 264 if (fileLock == null) 265 { 266 failureReason.append( 267 ERR_FILELOCKER_LOCK_EXCLUSIVE_NOT_GRANTED.get(lockFile)); 268 close(channel, raf); 269 return false; 270 } 271 else 272 { 273 exclusiveLocks.put(lockFile, fileLock); 274 return true; 275 } 276 } 277 } 278 279 280 281 /** 282 * Attempts to release the lock on the specified file. If an exclusive lock 283 * is held, then it will be released. If a shared lock is held, then its 284 * reference count will be reduced, and the lock will be released if the 285 * resulting reference count is zero. If we don't know anything about the 286 * requested file, then don't do anything. 287 * 288 * @param lockFile The file for which to release the associated lock. 289 * @param failureReason A buffer that can be used to hold information about 290 * a problem that occurred preventing the successful 291 * release. 292 * 293 * @return <CODE>true</CODE> if the lock was found and released successfully, 294 * or <CODE>false</CODE> if a problem occurred that might have 295 * prevented the lock from being released. 296 */ 297 public static boolean releaseLock(String lockFile, 298 StringBuilder failureReason) 299 { 300 synchronized (mapLock) 301 { 302 // See if we hold an exclusive lock on the file. If so, then release it 303 // and get remove it from the lock table. 304 FileLock lock = exclusiveLocks.remove(lockFile); 305 if (lock != null) 306 { 307 try 308 { 309 lock.release(); 310 } 311 catch (Exception e) 312 { 313 logger.traceException(e); 314 315 failureReason.append( 316 ERR_FILELOCKER_UNLOCK_EXCLUSIVE_FAILED_RELEASE.get(lockFile, 317 getExceptionMessage(e))); 318 return false; 319 } 320 321 try 322 { 323 lock.channel().close(); 324 } 325 catch (Exception e) 326 { 327 logger.traceException(e); 328 329 // Even though we couldn't close the channel for some reason, this 330 // should still be OK because we released the lock above. 331 } 332 333 return true; 334 } 335 336 337 // See if we hold a shared lock on the file. If so, then reduce its 338 // refcount and release only if the resulting count is zero. 339 lock = sharedLocks.get(lockFile); 340 if (lock != null) 341 { 342 int refCount = sharedLockReferences.get(lockFile); 343 refCount--; 344 if (refCount <= 0) 345 { 346 sharedLocks.remove(lockFile); 347 sharedLockReferences.remove(lockFile); 348 349 try 350 { 351 lock.release(); 352 } 353 catch (Exception e) 354 { 355 logger.traceException(e); 356 357 failureReason.append(ERR_FILELOCKER_UNLOCK_SHARED_FAILED_RELEASE 358 .get(lockFile, getExceptionMessage(e))); 359 return false; 360 } 361 362 try 363 { 364 lock.channel().close(); 365 } 366 catch (Exception e) 367 { 368 logger.traceException(e); 369 370 // Even though we couldn't close the channel for some reason, this 371 // should still be OK because we released the lock above. 372 } 373 } 374 else 375 { 376 sharedLockReferences.put(lockFile, refCount); 377 } 378 379 return true; 380 } 381 382 383 // We didn't find a reference to the file. We'll have to return false 384 // since either we lost the reference or we're trying to release a lock 385 // we never had. Both of them are bad. 386 failureReason.append(ERR_FILELOCKER_UNLOCK_UNKNOWN_FILE.get(lockFile)); 387 return false; 388 } 389 } 390 391 392 393 /** 394 * Retrieves the path to the directory that should be used to hold the lock 395 * files. 396 * 397 * @return The path to the directory that should be used to hold the lock 398 * files. 399 */ 400 public static String getLockDirectoryPath() 401 { 402 File lockDirectory = 403 DirectoryServer.getEnvironmentConfig().getLockDirectory(); 404 return lockDirectory.getAbsolutePath(); 405 } 406 407 408 409 /** 410 * Retrieves the filename that should be used for the lock file for the 411 * Directory Server instance. 412 * 413 * @return The filename that should be used for the lock file for the 414 * Directory Server instance. 415 */ 416 public static String getServerLockFileName() 417 { 418 StringBuilder buffer = new StringBuilder(); 419 buffer.append(getLockDirectoryPath()); 420 buffer.append(File.separator); 421 buffer.append(SERVER_LOCK_FILE_NAME); 422 buffer.append(LOCK_FILE_SUFFIX); 423 424 return buffer.toString(); 425 } 426 427 428 429 /** 430 * Retrieves the filename that should be used for the lock file for the 431 * specified backend. 432 * 433 * @param backend The backend for which to retrieve the filename for the 434 * lock file. 435 * 436 * @return The filename that should be used for the lock file for the 437 * specified backend. 438 */ 439 public static String getBackendLockFileName(Backend backend) 440 { 441 StringBuilder buffer = new StringBuilder(); 442 buffer.append(getLockDirectoryPath()); 443 buffer.append(File.separator); 444 buffer.append(BACKEND_LOCK_FILE_PREFIX); 445 buffer.append(backend.getBackendID()); 446 buffer.append(LOCK_FILE_SUFFIX); 447 448 return buffer.toString(); 449 } 450} 451