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