/************************************************************************
 * pidfile.c                                                            *
 *                                                                      *
 * Copyright (C) 2002 - 2016 Joshua Birnbaum <engineer@noorg.org>.      *
 * All Rights Reserved.                                                 *
 *                                                                      *
 * This program is free software; you can redistribute it and/or modify *
 * it under the terms of the GNU General Public License as published    *
 * by the Free Software Foundation; either version 2 of the License, or *
 * (at your option) any later version.                                  *
 *                                                                      *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 THE AUTHOR 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.         *
 *                                                                      *
 * You should have received a copy of the GNU General Public License    *
 * along with this program; see the file COPYING. If not, write to the  *
 * Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, *
 * MA 02111-1307, USA.                                                  *
 ************************************************************************/

/*************************************************************
 * Project ifchk -- Host based network interface promiscuous *
 *                  mode detection and handling.             *
 *                                                           *
 * Version:         1.0.2                                    *
 *                                                           *
 * --------------------------------------------------------- *
 *                                                           *
 * pidfile.c        ifchk file locking subsystem.            *
 *                                                           *
 * Functions:     - Performs file locking operations on the  *
 *                  ifchk PID file, /tmp/.ifchk.pid.         *
 *************************************************************/
 
/**********************
 * include directives *
 **********************/

#include "pidfile.h"


/************************************************************
 * lockCtl() - Queries, obtains and releases an exclusive   *
 *             write lock on /tmp/.ifchk.pid.               *
 *                                                          *
 * Parameters: lockCmd: File lock operation to perform (one *
 *                      of QUERYLOCK, LOCK or UNLOCK).      *
 *                                                          *
 * Returns:    0 on success.                                *
 *             -1 on failure.                               *
 ************************************************************/

int lockCtl( int lockCmd )
{
    int pidfilefd = 0;			/* PID file descriptor for open(). */
    struct flock lock;			/* Structure for fcntl(). */
    char pidBuf[32] = "";		/* PID buffer for PID file population. */
    int pidLen = 0;			/* ifchk PID character count. */

    /*
     * Open /tmp/.ifchk.pid write-only/create
     * and set its permissions to mode 0600.
     */
    if( ( pidfilefd = open( "/tmp/.ifchk.pid", O_WRONLY | O_CREAT, PIDMASK ) ) == -1 )
    {
        errHandler( "ERROR: open(): could not open file /tmp/.ifchk.pid: %s\n",
                                                              strerror( errno ) );
        return (-1);
     }

    /*
     * Query /tmp/.ifchk.pid to see if another PID
     * already has a write lock on the entire file.
     * This should be another ifchk instance, if so.
     */
    if( lockCmd == QUERYLOCK )
    {
        lock.l_type = F_WRLCK;
        lock.l_whence = SEEK_SET;
        lock.l_start = 0;
        lock.l_len = 0;
        lock.l_pid = 0;

        if( ( fcntl( pidfilefd, F_GETLK, &lock ) ) == -1 )
        {
            errHandler( "ERROR: fcntl(): could not query lock for file /tmp/.ifchk.pid: %s\n",
	                                                                     strerror( errno ) );
            return (-1);
         }

        /*
         * Close the PID file after our query.
         */
        if( ( close( pidfilefd ) ) != 0 )
        {
            errHandler( "ERROR: close(): could not close /tmp/.ifchk.pid: %s\n",
                                                               strerror( errno ) );
            return (-1);
         }

        /*
         * There can only be one running ifchk
         * instance per host. Report and abort
         * attempts to start another instance.
         */
        if( lock.l_pid != 0 )
        {
            errHandler( "ERROR: ifchk is already running [PID: %d]\n", lock.l_pid );
            return (-1);
         }
     }

    /*
     * Take out an exclusive write lock on the
     * whole /tmp/.ifchk.pid file. Getting to
     * this point indicates that we are the only
     * ifchk instance running on this host.
     */
    else if( lockCmd == LOCK )
    {
        lock.l_type = F_WRLCK;
        lock.l_whence = SEEK_SET;
        lock.l_start = 0;
        lock.l_len = 0;

        if( ( fcntl( pidfilefd, F_SETLK, &lock ) ) == -1 )
        {
            errHandler( "ERROR: fcntl(): could not lock file /tmp/.ifchk.pid: %s\n",
                                                                   strerror( errno ) );
            return (-1);
         }

        /*
         * Truncate /tmp/.ifchk.pid
         * to zero bytes in size.
         */
        if( ftruncate( pidfilefd, 0 ) == -1 )
        {
            errHandler( "ERROR: ftruncate(): could not truncate file /tmp/.ifchk.pid: %s\n",
                                                                           strerror( errno ) );
            return (-1);
         }

        /*
         * Get our PID.
         */
        if( ( pidLen = snprintf( pidBuf, sizeof( pidBuf ), "%d\n", getpid() ) ) < 1 )
        {
            errHandler( "ERROR: snprintf(): could not write to PID buffer: %s\n",
                                                                strerror( errno ) );
            exit( EXIT_FAILURE );
         }

        /*
         * Write our PID to /tmp/.ifchk.pid.
         */
        if( write( pidfilefd, pidBuf, (size_t)pidLen ) == -1 )
        {
            errHandler( "ERROR: write(): could not write to file /tmp/.ifchk.pid: %s\n",
                                                                       strerror( errno ) );
            return (-1);
         }
     }

    /*
     * Release the lock on /tmp/.ifchk.pid and delete it.
     */
    else if( lockCmd == UNLOCK )
    { 
        lock.l_type = F_UNLCK;
        lock.l_whence = SEEK_SET;
        lock.l_start = 0;
        lock.l_len = 0;

        if( ( fcntl( pidfilefd, F_SETLK, &lock ) ) == -1 )
        {
            errHandler( "ERROR: fcntl(): could not release lock on file /tmp/.ifchk.pid: %s\n",
	                                                                      strerror( errno ) );
            return (-1);
         }

	/*
	 * Close the PID file after rele-
	 * asing the lock we placed on it.
	 */
        if( ( close( pidfilefd ) ) != 0 )
        {
            errHandler( "ERROR: close(): could not close /tmp/.ifchk.pid: %s\n",
                                                               strerror( errno ) );
            return (-1);
         }

	/*
	 * Delete the PID file.
	 */
        if( unlink( "/tmp/.ifchk.pid" ) != 0 )
        {
            errHandler( "ERROR: unlink(): could not remove file /tmp/.ifchk.pid: %s\n",
                                                                      strerror( errno ) );
            return (-1);
         }
     }

    /*
     * The file locking command we received was
     * not one of QUERYLOCK, LOCK, or UNLOCK.
     */
    else
    {
        errHandler( "ERROR: unrecognized record locking command \"%d\"\n", lockCmd );
        return (-1);
     }

    return (0);
}
