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