/* Copyright (c) 2001-2008, 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;
    }
}
