/* Copyright (c) 2001-2009, The HSQL Development Group * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * Redistributions of source code must retain the above copyright notice, this * list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * Neither the name of the HSQL Development Group nor the names of its * contributors may be used to endorse or promote products derived from this * software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL HSQL DEVELOPMENT GROUP, HSQLDB.ORG, * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ package org.hsqldb.persist; import java.io.RandomAccessFile; import java.nio.channels.FileLock; /** * LockFile variant that capitalizes upon the availability of * {@link java.nio.channels.FileLock FileLock}. <p> * * <b>Configuration</b>:<p> * * To enable POSIX mandatory file lock tagging, set the system property * {@link #POSIX_MANDITORY_FILELOCK_PROPERTY} true. <p> * * <hr> * * <b><em>Windows</em><sup>tm</sup> File Locking Notes</b>:<p> * * There are two major differences between Unix and Windows locking.<p> * * First, <em>Windows</em><sup>tm</sup> supports a share reservation programming * interface. Share reservations apply to the entire file and are specified at * the time a file is created or opened. A share reservation consists of a pair * of modes. The first is the access mode, which is how the application will * access the file: read, write, or read/write. The second is the access * that the application will deny to other applications: none, read, write, * or read/write. When the application attempts to open a file, the operating * system checks to see if there are any other open requests on the file. If so, * it first compares the application's access mode with the deny mode of the * other openers. If there is a match, then the open is denied. If not, then * the operating system compares the application's deny mode with the access * mode of the other openers. Again, if there is a match, the open is denied.<p> * * Second, there is no advisory locking under <em>Windows</em>. Whole file * locking, byte range locking, and share reservation locking are all * mandatory. <p> * * As a side note, research indicates that very few <em>Windows</em> programs * actually rely on byte range mandatory locking. <p> * * <b>POSIX File Locking Notes</b>:<p> * * There are three commonly found locking functions under POSIX - flock, lockf, * and fcntl.<p> * * These functions, while managed by the kernel, are advisory locking mechanisms * by default. That is, the kernel does not actually stop programs from reading, * writing or deleting locked files. Instead, each program must check to see if * a lock is in place and act accordingly (be cooperative).<p> * * This might be problematic when some programs are cooperative and others are * not, especially if you do not have the ability to recompile the uncooperative * code, yet it must be allowed to run.<p> * * As POSIX has evolved, provisions have been made to allow locks to be enforced * at the kernel level with mandatory locking semantics. For historical reasons * <sup><a href="#note1">[1]</a></sup> and for practical reasons <sup> * <a href="#note2">[2]</a></sup>, POSIX mandatory locking is generally * implemented such that it must be configured on a file-by-file basis. When a * program attempts to lock a file with lockf or fcntl, the kernel will prevent * other programs from accessing the file if and only if the file has mandatory * locking set (note: processes which use flock never trigger a mandatory * lock).<p> * * In addition, to enable mandatory locking under Linux, the target filesystem * device must be mounted with the <tt>mand</tt> option, for example:<p> * * <pre> * # mount -omand /dev/hdb3 /mnt/data * * # mount | grep /mnt/data * /dev/hdb3 on /mnt/data type ext3 (rw,mand) *</pre> * * To automate this across reboots, <tt>mand</tt> must be added to the * appropriate <tt>/etc/fstab</tt> entry. <p> * * Although Linux, Solaris and HP/UX are known to support kernel-enforced * mandatory locking semantics, some POSIX systems may provide no support at all, * while others may have system-specific configuration prerequisites. Some may * even enforce manditory locking semantics by default under certain conditions * while others may silently ignore mandatory locking directives or consistently * raise exception conditions in response to such directives. <p> * * Regardless of any wider scoped prerequisites, restrictions and behavioural * variations, it is generally true under POSIX that only specifically tagged * files will exhibit mandatory locks. <p> * * And it is generally accepted that the way to designate a file to be governed * by mandatory locking semantics is to set its sgid (setgroupid) bit and unset * its group execute bit. This has become the commonly accepted practice * specifically because the combination is devoid of meaning in the regular * sense. <p> * * As a specific illustration, if mandatory locking is required on all * pre-existing files in a certain directory, then the corresponding * <tt>chmod</tt> invocation would be: <p> * * <pre> * $ chmod g+s,g-x /path/to/directory/* * </pre> * * <hr> * * <a name="note1"/>[1]</a> * The earliest versions of Unix had no way to lock files except to create * lock files. The idea is that two or more processes would more or less * simultaneously try to create a lock file in exclusive mode, via the * O_EXCL flag of the open( ) system call. The operating system would * return success to the process that won the race, and a "file exists" * error to losing processes. One problem with this scheme is that it * relies on the winning process to remove the lock file before it exits. * If the process is running buggy software, this might not happen. * Some applications mitigate this problem by recording the process ID of * the winner into the contents of the lock file. A process that finds that * it gets a "file exists" error can then read the lock file to see if the * owning process is still running. <p> * * Still, lock files can be clumsy. In the 1980s, Unix versions were * released with file locking support built into the operating system. * The System V branch of Unix offered file locking via the fcntl( ) system * call, whereas the BSD branch provided the flock( ) system call. In both * cases, when the process that creates the lock dies, the lock will be * automatically released. <p> * * In a perfect world all processes would use and honour a cooperative, or * "advisory" locking scheme. However, the world isn't perfect, and there's * a lot of poorly written code out there. <p> * * In trying to address this problem, the designers of System V UNIX came up * with a "mandatory" locking scheme, whereby the operating system kernel * would block attempts by a process to write to a file upon which another * process holds a "read" -or- "shared" lock, and block attempts both to * read and to write a file upon which a process holds a "write " -or- * "exclusive" lock. <p> * * The System V mandatory locking scheme was intended to have as little * impact as possible on existing user code. The scheme is based on marking * individual files as candidates for mandatory locking, and using the * existing fcntl()/lockf() interface for applying locks, just as if they * were normal, advisory locks. <p> * * <a name="note2"/>[2]</a> * Even with mandatory locks, conflicts can occur. If program A reads in a * file, program B locks, edits, and unlocks the file, and program A then * writes out what it originally read, this may still be less than * desirable. <p> * * As well, it is generally true that nothing can override a mandatory * lock, not even root-owned processes. In this situation, the best root * can do is kill the process that has the lock upon the file. <p> * * And this can be especially problematic if a file upon which a mandatory * lock exists is also available via NFS or some other remotely-accessible * filesystem, specifically because the entire fileserver process may be * forced to block until the lock is released. <p> * * Indeed, these effects of mandatory locking policy are commonly encountered in * the <em>Windows</em><sup>tm</sup> environment, where all locks are of the * mandatory style. <p> * * <hr> * * <b>Research Results</b>:<p> * * After some experimentation under JDK 1.5/6 and Linux (at least Fedora * Core 4), research results indicate that, after mounting the target file * system device using the described <tt>mand</tt> option and doing a * <tt>chmod g+s,g-x</tt> on the lock file before issuing a * <tt>FileChannel.tryLock(...)</tt>, it is still possible to delete the lock * file from another process while the resulting <tt>FileLock</tt> is held, * although mandatory locking does appear to be in effect for read/write * operations. <p> * * This result was actually the one anticipated, because deletion of open files * is generally possible under POSIX. In turn, this is because POSIX file * deletion simply removes the iNode entry from the file's parent directory, * while any processes with open file handles continue to have access to the * deleted file. Only when all file handles have been released does the space * occupied by the file become elligible for reuse.<p> * * In other words, under both <em>Windows</em><sup>tm</sup> and Linux (at lease * FC 4), it appears this class is a practically useless extension to the base. * Under Java for <em>Windows</em>, the act of holding a file handle open is * enough to produce a mandatory lock on the underlying file and also prevents * the file's deletion (i.e. the ultimately desired behavior occurs with or * without <tt>NIOLockFile</tt>), while under Java for Linux it appears * impossible to use NIO to produce a lock that prevents file deletion, yeilding * protection against inadvertent modification of the lock file practically * useless. <p> * * To put it another way, even after the much-heralded introduction of Java * NIO, without further resorting to platform-specific JNI alternatives, we * might as well still be back in the early 80's, because we cannot guarantee, * in a cross-platform manner, conditions better than offered by the earliest * known lock file approach, even on systems that do support mandatory file * locking semantics. Instead, we must simply still trust that all software is * well written and cooperative in nature. <p> * * @author boucherb@users * @version 1.8.0.3 * @since 1.7.2 */ final class NIOLockFile extends LockFile { /** The largest lock region that can be specified with <tt>java.nio</tt>. */ public static final long MAX_LOCK_REGION = Long.MAX_VALUE; /** * Generally, the largest lock region that can be specified for files on * network file systems. <p> * * From the java.nio.channels.FileLock API JavaDocs: <p> * * Some network filesystems do not implement file locks on regions * that extend past a certain position, often 2**30 or 2**31. * In general, great care should be taken when locking files that * reside on network filesystems. */ public static final long MAX_NFS_LOCK_REGION = (1L << 30); /** * The smallest lock region that still protects the area actually used to * record lock information. */ public static final long MIN_LOCK_REGION = LockFile.USED_REGION; /** * Whether POSIX mandatory file lock tagging is performed by default. <p> * * Under the default build, this is <tt>false</tt>, but custom * distributions are free to rebuild with a default <tt>true</tt> * value. <p> * * For <em>Windows</em> targets, distributions should build with this set * false. */ public static final boolean POSIX_MANDITORY_FILELOCK_DEFAULT = false; /** * System property that can be used to control whether POSIX mandatory * file lock tagging is performed. */ public static final String POSIX_MANDITORY_FILELOCK_PROPERTY = "hsqldb.lockfile.posix.manditory.filelock"; /** * Represents an OS-enforced lock region upon this object's lock file. */ private volatile FileLock fileLock; /** * Retrieves whether POSIX mandatory file lock tagging is performed. <p> * * The value is obtained in the following manner: <p> * * <ol> * <li>manditory is assigned <tt>POSIX_MANDITORY_FILELOCK_DEFAULT</tt> * <li>manditory is assigned <tt>"true".equalsIgnoreCase( * System.getProperty(POSIX_MANDITORY_FILELOCK_PROPERTY, * manditory ? "true" : "false")); * </tt>, inside a try-catch block, to silently ignore any security * exception. * </ol> * @return <tt>true</tt> if POSIX mandatory file lock tagging is performed */ public boolean isPosixManditoryFileLock() { boolean manditory = POSIX_MANDITORY_FILELOCK_DEFAULT; try { manditory = "true".equalsIgnoreCase( System.getProperty( POSIX_MANDITORY_FILELOCK_PROPERTY, manditory ? "true" : "false")); } catch (Exception e) {} return manditory; } /** * Does work toward ensuring that a {@link #fileLock FileLock} exists upon * this object's lock file. <p> * * <b>POST:</b><p> * * Upon exit, if a valid <tt>fileLock</tt> could not be aquired for any * reason, then a best effort has also been expended toward releasing and * nullifying any resources obtained in the process. * * @return true if there is valid FileLock on exit, else false. */ protected boolean doOptionalLockActions() { // override return this.aquireFileLock(); } /** * Peforms best effort work toward releasing this object's * {@link #fileLock FileLock}, nullifying it in the process. * * @return true if <tt>fileLock</tt> is released successfully, else false */ protected boolean doOptionalReleaseActions() { // override return this.releaseFileLock(); } /** * Retrieves whether this object's {@link #fileLock FileLock} * represents a valid lock region upon this object's lock file. * * @return true if this object's {@link #fileLock FileLock} attribute is * valid, else false * @throws SecurityException if a required system property value cannot * be accessed, or if a security manager exists and its <tt>{@link * java.lang.SecurityManager#checkRead}</tt> method denies * read access to the lock file; */ public boolean isValid() { // override try { return super.isValid() && (this.fileLock != null && this.fileLock.isValid()); } catch (Exception e) { return false; } } /** * Retrieves the String value: "fileLock = " + {@link #fileLock FileLock}.<p> * * @return the String value: "fileLock = " + fileLock */ protected String toStringImpl() { // override return "fileLock = " + this.fileLock; } // ------------------------- Internal Implementation------------------------ // does the real work of aquiring the FileLock private boolean aquireFileLock() { // PRE: // // raf is never null and is never closed upon entry. // // Rhetorical question to self: How does one tell if a RandomAccessFile // is closed, short of invoking an operation and getting an IOException // the says its closed (assuming you can control the Locale of the error // message)? // final RandomAccessFile lraf = super.raf; // In an ideal world, we would use a lock region back off approach, // starting with region MAX_LOCK_REGION, then MAX_NFS_LOCK_REGION, // then MIN_LOCK_REGION. // // In practice, however, it is just generally unwise to mount network // file system database instances. Be warned. // // In general, it is probably also unwise to mount removable media // database instances that are not read-only. boolean success = false; try { if (this.fileLock != null) { // API says never throws exception, but I suspect // it's quite possible some research / FOSS JVMs might // still throw unsupported operation exceptions on certain // NIO classes...better to be safe than sorry. if (this.fileLock.isValid()) { return true; } else { // It's not valid, so releasing it is a no-op. // // However, we should still clean up the referenceand hope // no previous complications exist (a hung FileLock in a // flaky JVM) or that gc kicks in and saves the day... // (unlikely, though). this.releaseFileLock(); } } if (isPosixManditoryFileLock()) { try { Runtime.getRuntime().exec(new String[] { "chmod", "g+s,g-x", file.getPath() }); } catch (Exception ex) { //ex.printStackTrace(); } } // Note: from FileChannel.tryLock(...) JavaDoc: // // @return A lock object representing the newly-acquired lock, // or <tt>null</tt> if the lock could not be acquired // because another program holds an overlapping lock this.fileLock = lraf.getChannel().tryLock(0, MIN_LOCK_REGION, false); // According to the API, if it's non-null, it must be valid. // This may not actually yet be the full truth of the matter under // all commonly available JVM implementations. // fileLock.isValid() API says it never throws, though, so // with fingers crossed... success = (this.fileLock != null && this.fileLock.isValid()); } catch (Exception e) {} if (!success) { this.releaseFileLock(); } return success; } // does the real work of releasing the FileLock private boolean releaseFileLock() { // Note: Closing the super class RandomAccessFile has the // side-effect of closing the file lock's FileChannel, // so we do not deal with this here. boolean success = false; if (this.fileLock == null) { success = true; } else { try { this.fileLock.release(); success = true; } catch (Exception e) {} finally { this.fileLock = null; } } return success; } }