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
010     * trunk/opends/resource/legal-notices/OpenDS.LICENSE
011     * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
012     * See the License for the specific language governing permissions
013     * and limitations under the License.
014     *
015     * When distributing Covered Code, include this CDDL HEADER in each
016     * file and include the License file at
017     * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
018     * add the following below this CDDL HEADER, with the fields enclosed
019     * by brackets "[]" replaced with your own identifying information:
020     *      Portions Copyright [yyyy] [name of copyright owner]
021     *
022     * CDDL HEADER END
023     *
024     *
025     *      Copyright 2006-2008 Sun Microsystems, Inc.
026     */
027    package org.opends.server.core;
028    
029    
030    
031    import java.io.File;
032    import java.io.RandomAccessFile;
033    import java.nio.channels.FileChannel;
034    import java.nio.channels.FileLock;
035    import java.util.HashMap;
036    
037    import org.opends.server.api.Backend;
038    import org.opends.server.loggers.debug.DebugTracer;
039    import org.opends.server.types.DebugLogLevel;
040    
041    import static org.opends.messages.CoreMessages.*;
042    import static org.opends.server.loggers.debug.DebugLogger.*;
043    import static org.opends.server.util.ServerConstants.*;
044    import static org.opends.server.util.StaticUtils.*;
045    
046    
047    
048    /**
049     * This class provides a mechanism for allowing the Directory Server to utilize
050     * file locks as provided by the underlying OS.  File locks may be exclusive or
051     * shared, and will be visible between different processes on the same system.
052     */
053    public class LockFileManager
054    {
055      /**
056       * The tracer object for the debug logger.
057       */
058      private static final DebugTracer TRACER = getTracer();
059    
060    
061    
062    
063      // A map between the filenames and the lock files for exclusive locks.
064      private static HashMap<String,FileLock> exclusiveLocks =
065           new HashMap<String,FileLock>();
066    
067      // A map between the filenames and the lock files for shared locks.
068      private static HashMap<String,FileLock> sharedLocks =
069           new HashMap<String,FileLock>();
070    
071      // A map between the filenames and reference counts for shared locks.
072      private static HashMap<String,Integer> sharedLockReferences =
073           new HashMap<String,Integer>();
074    
075      // The lock providing threadsafe access to the lock map data.
076      private static Object mapLock = new Object();
077    
078    
079    
080      /**
081       * Attempts to acquire a shared lock on the specified file.
082       *
083       * @param  lockFile       The file for which to obtain the shared lock.
084       * @param  failureReason  A buffer that can be used to hold a reason that the
085       *                        lock could not be acquired.
086       *
087       * @return  <CODE>true</CODE> if the lock was obtained successfully, or
088       *          <CODE>false</CODE> if it could not be obtained.
089       */
090      public static boolean acquireSharedLock(String lockFile,
091                                              StringBuilder failureReason)
092      {
093        synchronized (mapLock)
094        {
095          // Check to see if there's already an exclusive lock on the file.  If so,
096          // then we can't get a shared lock on it.
097          if (exclusiveLocks.containsKey(lockFile))
098          {
099    
100            failureReason.append(
101                    ERR_FILELOCKER_LOCK_SHARED_REJECTED_BY_EXCLUSIVE.get(lockFile));
102            return false;
103          }
104    
105    
106          // Check to see if we already hold a shared lock on the file.  If so, then
107          // increase its refcount and return true.
108          FileLock sharedLock = sharedLocks.get(lockFile);
109          if (sharedLock != null)
110          {
111            int numReferences = sharedLockReferences.get(lockFile);
112            numReferences++;
113            sharedLockReferences.put(lockFile, numReferences);
114            return true;
115          }
116    
117    
118          // We don't hold a lock on the file so we need to create it.  First,
119          // create the file only if it doesn't already exist.
120          File f = getFileForPath(lockFile);
121          try
122          {
123            if (! f.exists())
124            {
125              f.createNewFile();
126            }
127          }
128          catch (Exception e)
129          {
130            if (debugEnabled())
131            {
132              TRACER.debugCaught(DebugLogLevel.ERROR, e);
133            }
134    
135            failureReason.append(
136                    ERR_FILELOCKER_LOCK_SHARED_FAILED_CREATE.get(lockFile,
137                                            getExceptionMessage(e)));
138            return false;
139          }
140    
141    
142          // Open the file for reading and get the corresponding file channel.
143          FileChannel channel = null;
144          RandomAccessFile raf = null;
145          try
146          {
147            raf = new RandomAccessFile(lockFile, "r");
148            channel = raf.getChannel();
149          }
150          catch (Exception e)
151          {
152            if (debugEnabled())
153            {
154              TRACER.debugCaught(DebugLogLevel.ERROR, e);
155            }
156    
157            failureReason.append(ERR_FILELOCKER_LOCK_SHARED_FAILED_OPEN.get(
158                    lockFile, getExceptionMessage(e)));
159    
160            if (raf != null)
161            {
162              try
163              {
164                raf.close();
165              }
166              catch (Throwable t)
167              {
168              }
169            }
170            return false;
171          }
172    
173    
174          // Try to obtain a shared lock on the file channel.
175          FileLock fileLock;
176          try
177          {
178            fileLock = channel.tryLock(0L, Long.MAX_VALUE, true);
179          }
180          catch (Exception e)
181          {
182            if (debugEnabled())
183            {
184              TRACER.debugCaught(DebugLogLevel.ERROR, e);
185            }
186    
187            failureReason.append(
188                    ERR_FILELOCKER_LOCK_SHARED_FAILED_LOCK.get(
189                            lockFile, getExceptionMessage(e)));
190            if (channel != null)
191            {
192              try
193              {
194                channel.close();
195              }
196              catch (Throwable t)
197              {
198              }
199            }
200            if (raf != null)
201            {
202              try
203              {
204                raf.close();
205              }
206              catch (Throwable t)
207              {
208              }
209            }
210            return false;
211          }
212    
213    
214          // If we could not get the lock, then return false.  Otherwise, put it in
215          // the shared lock table with a reference count of 1 and return true.
216          if (fileLock == null)
217          {
218            failureReason.append(
219                    ERR_FILELOCKER_LOCK_SHARED_NOT_GRANTED.get(lockFile));
220            if (channel != null)
221            {
222              try
223              {
224                channel.close();
225              }
226              catch (Throwable t)
227              {
228              }
229            }
230            if (raf != null)
231            {
232              try
233              {
234                raf.close();
235              }
236              catch (Throwable t)
237              {
238              }
239            }
240            return false;
241          }
242          else
243          {
244            sharedLocks.put(lockFile, fileLock);
245            sharedLockReferences.put(lockFile, 1);
246            return true;
247          }
248        }
249      }
250    
251    
252    
253      /**
254       * Attempts to acquire an exclusive lock on the specified file.
255       *
256       * @param  lockFile       The file for which to obtain the exclusive lock.
257       * @param  failureReason  A buffer that can be used to hold a reason that the
258       *                        lock could not be acquired.
259       *
260       * @return  <CODE>true</CODE> if the lock was obtained successfully, or
261       *          <CODE>false</CODE> if it could not be obtained.
262       */
263      public static boolean acquireExclusiveLock(String lockFile,
264                                                 StringBuilder failureReason)
265      {
266        synchronized (mapLock)
267        {
268          // Check to see if there's already an exclusive lock on the file.  If so,
269          // then we can't get another exclusive lock on it.
270          if (exclusiveLocks.containsKey(lockFile))
271          {
272            failureReason.append(
273                    ERR_FILELOCKER_LOCK_EXCLUSIVE_REJECTED_BY_EXCLUSIVE.get(
274                            lockFile));
275            return false;
276          }
277    
278    
279          // Check to see if we already hold a shared lock on the file.  If so, then
280          // we can't get an exclusive lock on it.
281          if (sharedLocks.containsKey(lockFile))
282          {
283            failureReason.append(
284                    ERR_FILELOCKER_LOCK_EXCLUSIVE_REJECTED_BY_SHARED.get(lockFile));
285            return false;
286          }
287    
288    
289          // We don't hold a lock on the file so we need to create it.  First,
290          // create the file only if it doesn't already exist.
291          File f = getFileForPath(lockFile);
292          try
293          {
294            if (! f.exists())
295            {
296              f.createNewFile();
297            }
298          }
299          catch (Exception e)
300          {
301            if (debugEnabled())
302            {
303              TRACER.debugCaught(DebugLogLevel.ERROR, e);
304            }
305            failureReason.append(
306                    ERR_FILELOCKER_LOCK_EXCLUSIVE_FAILED_CREATE.get(lockFile,
307                                            getExceptionMessage(e)));
308            return false;
309          }
310    
311    
312          // Open the file read+write and get the corresponding file channel.
313          FileChannel channel = null;
314          RandomAccessFile raf = null;
315          try
316          {
317            raf = new RandomAccessFile(lockFile, "rw");
318            channel = raf.getChannel();
319          }
320          catch (Exception e)
321          {
322            if (debugEnabled())
323            {
324              TRACER.debugCaught(DebugLogLevel.ERROR, e);
325            }
326    
327            failureReason.append(ERR_FILELOCKER_LOCK_EXCLUSIVE_FAILED_OPEN.get(
328                    lockFile, getExceptionMessage(e)));
329            if (raf != null)
330            {
331              try
332              {
333                raf.close();
334              }
335              catch (Throwable t)
336              {
337              }
338            }
339            return false;
340          }
341    
342    
343          // Try to obtain an exclusive lock on the file channel.
344          FileLock fileLock;
345          try
346          {
347            fileLock = channel.tryLock(0L, Long.MAX_VALUE, false);
348          }
349          catch (Exception e)
350          {
351            if (debugEnabled())
352            {
353              TRACER.debugCaught(DebugLogLevel.ERROR, e);
354            }
355    
356            failureReason.append(
357                    ERR_FILELOCKER_LOCK_EXCLUSIVE_FAILED_LOCK.get(lockFile,
358                                            getExceptionMessage(e)));
359            if (channel != null)
360            {
361              try
362              {
363                channel.close();
364              }
365              catch (Throwable t)
366              {
367              }
368            }
369            if (raf != null)
370            {
371              try
372              {
373                raf.close();
374              }
375              catch (Throwable t)
376              {
377              }
378            }
379    
380            return false;
381          }
382    
383    
384          // If we could not get the lock, then return false.  Otherwise, put it in
385          // the exclusive lock table and return true.
386          if (fileLock == null)
387          {
388            failureReason.append(
389                    ERR_FILELOCKER_LOCK_EXCLUSIVE_NOT_GRANTED.get(lockFile));
390            if (channel != null)
391            {
392              try
393              {
394                channel.close();
395              }
396              catch (Throwable t)
397              {
398              }
399            }
400            if (raf != null)
401            {
402              try
403              {
404                raf.close();
405              }
406              catch (Throwable t)
407              {
408              }
409            }
410            return false;
411          }
412          else
413          {
414            exclusiveLocks.put(lockFile, fileLock);
415            return true;
416          }
417        }
418      }
419    
420    
421    
422      /**
423       * Attempts to release the lock on the specified file.  If an exclusive lock
424       * is held, then it will be released.  If a shared lock is held, then its
425       * reference count will be reduced, and the lock will be released if the
426       * resulting reference count is zero.  If we don't know anything about the
427       * requested file, then don't do anything.
428       *
429       * @param  lockFile       The file for which to release the associated lock.
430       * @param  failureReason  A buffer that can be used to hold information about
431       *                        a problem that occurred preventing the successful
432       *                        release.
433       *
434       * @return  <CODE>true</CODE> if the lock was found and released successfully,
435       *          or <CODE>false</CODE> if a problem occurred that might have
436       *          prevented the lock from being released.
437       */
438      public static boolean releaseLock(String lockFile,
439                                        StringBuilder failureReason)
440      {
441        synchronized (mapLock)
442        {
443          // See if we hold an exclusive lock on the file.  If so, then release it
444          // and get remove it from the lock table.
445          FileLock lock = exclusiveLocks.remove(lockFile);
446          if (lock != null)
447          {
448            try
449            {
450              lock.release();
451            }
452            catch (Exception e)
453            {
454              if (debugEnabled())
455              {
456                TRACER.debugCaught(DebugLogLevel.ERROR, e);
457              }
458    
459              failureReason.append(
460                      ERR_FILELOCKER_UNLOCK_EXCLUSIVE_FAILED_RELEASE.get(lockFile,
461                                              getExceptionMessage(e)));
462              return false;
463            }
464    
465            try
466            {
467              lock.channel().close();
468            }
469            catch (Exception e)
470            {
471              if (debugEnabled())
472              {
473                TRACER.debugCaught(DebugLogLevel.ERROR, e);
474              }
475    
476              // Even though we couldn't close the channel for some reason, this
477              // should still be OK because we released the lock above.
478            }
479    
480            return true;
481          }
482    
483    
484          // See if we hold a shared lock on the file.  If so, then reduce its
485          // refcount and release only if the resulting count is zero.
486          lock = sharedLocks.get(lockFile);
487          if (lock != null)
488          {
489            int refCount = sharedLockReferences.get(lockFile);
490            refCount--;
491            if (refCount <= 0)
492            {
493              sharedLocks.remove(lockFile);
494              sharedLockReferences.remove(lockFile);
495    
496              try
497              {
498                lock.release();
499              }
500              catch (Exception e)
501              {
502                if (debugEnabled())
503                {
504                  TRACER.debugCaught(DebugLogLevel.ERROR, e);
505                }
506    
507                failureReason.append(ERR_FILELOCKER_UNLOCK_SHARED_FAILED_RELEASE
508                        .get(lockFile, getExceptionMessage(e)));
509                return false;
510              }
511    
512              try
513              {
514                lock.channel().close();
515              }
516              catch (Exception e)
517              {
518                if (debugEnabled())
519                {
520                  TRACER.debugCaught(DebugLogLevel.ERROR, e);
521                }
522    
523                // Even though we couldn't close the channel for some reason, this
524                // should still be OK because we released the lock above.
525              }
526            }
527            else
528            {
529              sharedLockReferences.put(lockFile, refCount);
530            }
531    
532            return true;
533          }
534    
535    
536          // We didn't find a reference to the file.  We'll have to return false
537          // since either we lost the reference or we're trying to release a lock
538          // we never had.  Both of them are bad.
539          failureReason.append(ERR_FILELOCKER_UNLOCK_UNKNOWN_FILE.get(lockFile));
540          return false;
541        }
542      }
543    
544    
545    
546      /**
547       * Retrieves the path to the directory that should be used to hold the lock
548       * files.
549       *
550       * @return  The path to the directory that should be used to hold the lock
551       *          files.
552       */
553      public static String getLockDirectoryPath()
554      {
555        File lockDirectory =
556                  DirectoryServer.getEnvironmentConfig().getLockDirectory();
557        return lockDirectory.getAbsolutePath();
558      }
559    
560    
561    
562      /**
563       * Retrieves the filename that should be used for the lock file for the
564       * Directory Server instance.
565       *
566       * @return  The filename that should be used for the lock file for the
567       *          Directory Server instance.
568       */
569      public static String getServerLockFileName()
570      {
571        StringBuilder buffer = new StringBuilder();
572        buffer.append(getLockDirectoryPath());
573        buffer.append(File.separator);
574        buffer.append(SERVER_LOCK_FILE_NAME);
575        buffer.append(LOCK_FILE_SUFFIX);
576    
577        return buffer.toString();
578      }
579    
580    
581    
582      /**
583       * Retrieves the filename that should be used for the lock file for the
584       * specified backend.
585       *
586       * @param  backend  The backend for which to retrieve the filename for the
587       *                  lock file.
588       *
589       * @return  The filename that should be used for the lock file for the
590       *          specified backend.
591       */
592      public static String getBackendLockFileName(Backend backend)
593      {
594        StringBuilder buffer = new StringBuilder();
595        buffer.append(getLockDirectoryPath());
596        buffer.append(File.separator);
597        buffer.append(BACKEND_LOCK_FILE_PREFIX);
598        buffer.append(backend.getBackendID());
599        buffer.append(LOCK_FILE_SUFFIX);
600    
601        return buffer.toString();
602      }
603    }
604