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