/*
  lock.c

  $Id: lock.c 316 2001-10-31 11:44:53Z bears $
*/

#if HAVE_CONFIG_H
#include <config.h>
#endif

#include <stdio.h>
#include <errno.h>
#include <ctype.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <signal.h>

#if TIME_WITH_SYS_TIME
#include <sys/time.h>
#include <time.h>
#else
#if HAVE_SYS_TIME_H
#include <sys/time.h>
#else
#include <time.h>
#endif
#endif

#include <unistd.h>
#include "configfile.h"
#include "content.h"
#include "lock.h"
#include "log.h"
#include "database.h"
#include "group.h"
#include "request.h"
#include "portable.h"
#include "server.h"
#include "util.h"

struct Lock
{
    const char *name;
    int lockFd;
    Str lockFile;
    Bool doLazyLocking;
    volatile Bool lazyClose;
    volatile Bool lazyLockBusy;
};

static struct Lock globalLock = { "global", -1, "", TRUE, FALSE, FALSE };
static struct Lock fetchLock = { "fetch", -1, "", FALSE, FALSE, FALSE };

static sig_t oldHandler = NULL;

/* Block/unblock SIGUSR1. */
static Bool
blockSignal( Bool block )
{
    sigset_t sigs;

    sigemptyset(&sigs);
    sigaddset(&sigs, SIGUSR1);
    
    for(;;)
    {
	if ( sigprocmask( block ? SIG_BLOCK : SIG_UNBLOCK, &sigs, NULL ) != 0 )
	{
	    if ( errno != EINTR )
	    {
		Log_err( "Can't block/unblock signal" );
		return FALSE;
	    }
	}
	else
	    return TRUE;
    }
    /* NOTREACHED */
}

/* Check the global lock held. */
static Bool
gotLock( struct Lock *lock )
{
    return ( lock->lockFd != -1 );    
}

static void
lockWaitAlarm( int sig )
{
    UNUSED( sig );
    
    return;
}

static Bool
waitLock( struct Lock *lock, enum LockRequestWait wait )
{
    int fd;
    struct flock l;

    ASSERT( ! gotLock( lock ) );
    Log_dbg( LOG_DBG_NEWSBASE, "Waiting for lock %s ...", lock->name );
    if ( lock->lockFile[ 0 ] == '\0' )
	snprintf( lock->lockFile, MAXCHAR, "%s/lock/%s",
		  Cfg_spoolDir(), lock->name );
    if ( ( fd = open( lock->lockFile, O_WRONLY | O_CREAT, 0644 ) ) < 0 )
    {
        Log_err( "Cannot open %s (%s)", lock->lockFile, strerror( errno ) );
        return FALSE;
    }
    l.l_type = F_WRLCK;
    l.l_start = 0;
    l.l_whence = SEEK_SET;
    l.l_len = 0;
    if ( wait == LOCK_WAIT )
    {
	sig_t oldAlarmHandler;
	unsigned oldAlarm;

	oldAlarmHandler = Utl_installSignalHandler( SIGALRM, lockWaitAlarm );
	oldAlarm = alarm( 1 );
	for(;;)
	{
	    alarm( 1 );
	    if ( fcntl( fd, F_SETLKW, &l ) < 0 )
		if ( errno != EINTR )
		{
		    Utl_installSignalHandler( SIGALRM, oldAlarmHandler );
		    alarm( oldAlarm );
		    Log_err( "Cannot lock %s: %s", lock->lockFile,
			     strerror( errno ) );
		    close( lock->lockFd );
		    return FALSE;
		}
		else
		{
		    /* Send SIGUSR1 to the process holding the lock. */
		    if ( fcntl( fd, F_GETLK, &l) == 0 && l.l_type != F_UNLCK )
			if ( kill( l.l_pid, SIGUSR1 ) < 0 )
			    Log_err( "Can't signal process %d: %s", l.l_pid,
				     strerror( errno ) );
		}
	    else
		break;
	}
	Utl_installSignalHandler( SIGALRM, oldAlarmHandler );
	alarm( oldAlarm );
    }
    else
    {
	if ( fcntl( fd, F_SETLK, &l ) < 0 )
	{
	    close( lock->lockFd );
	    return FALSE;
	}
    }
	
    lock->lockFd = fd;
    Log_dbg( LOG_DBG_NEWSBASE, "Lock successful" );
    return TRUE;
}

static void
releaseLock( struct Lock *lock )
{
    struct flock l;

    ASSERT( gotLock( lock ) );    
    l.l_type = F_UNLCK;
    l.l_start = 0;
    l.l_whence = SEEK_SET;
    l.l_len = 0;
    if ( fcntl( lock->lockFd, F_SETLK, &l ) < 0 )
        Log_err( "Cannot release %s: %s", lock->lockFile,
                 strerror( errno ) );
    close( lock->lockFd );
    lock->lockFd = -1;
    Log_dbg( LOG_DBG_NEWSBASE, "Releasing lock" );
}

static Bool
openDatabases( void )
{
    globalLock.lazyClose = FALSE;
    if ( ! waitLock( &globalLock, LOCK_WAIT ) )
    {
	Log_err( "Could not get write lock" );
	return FALSE;
    }
    if ( ! Db_open() )
    {
	Log_err( "Could not open database" );
	releaseLock( &globalLock );
	return FALSE;
    }
    if ( ! Grp_open() )
    {
	Log_err( "Could not open groupinfo" );
	Db_close();
	releaseLock( &globalLock );
	return FALSE;
    }
    if ( ! Req_open() )
    {
	Log_err( "Could not initialize request database" );
	Grp_close();
	Db_close();
	releaseLock( &globalLock );
	return FALSE;
    }

    globalLock.lazyClose = globalLock.doLazyLocking;
    globalLock.lazyLockBusy = TRUE;
    return TRUE;
}

static void
closeDatabases( void )
{
    Grp_close();
    Db_close();
    Req_close();
    Server_flushCache();
    releaseLock( &globalLock );
    globalLock.lazyLockBusy = FALSE;
    globalLock.lazyClose = FALSE;
}

static void
lockSignal( int sig )
{
    UNUSED( sig );

    /*
     * If we hold the lock right now, flag it to be released next close.
     * Otherwise, if we are holding it lazily, release it.
     */
    if ( globalLock.lazyLockBusy )
	globalLock.lazyClose = FALSE;
    else if ( gotLock( &globalLock ) )
	closeDatabases();
    return;
}

/* Open all databases and set global lock. */
Bool
Lock_openDatabases( void )
{
    Bool res;

    /* First time - need to initialise signal handler? */
    if ( oldHandler == NULL )
	oldHandler = Utl_installSignalHandler( SIGUSR1, lockSignal );

    if ( ! blockSignal( TRUE ) )
	return FALSE;
    
    if ( ! globalLock.lazyClose )
	res = openDatabases();
    else
	res = TRUE;
    
    globalLock.lazyLockBusy = res;

    if ( ! blockSignal( FALSE ) )
	return FALSE;

    return res;
}

/* Close all databases and release global lock. */
void
Lock_closeDatabases( void )
{
    blockSignal( TRUE );
    
    if ( ! globalLock.lazyClose )
	closeDatabases();
    else
	globalLock.lazyLockBusy = FALSE;
    
    blockSignal( FALSE );
}

/* Sync all databases to disc. Maintain global lock status. */
void
Lock_syncDatabases( void )
{
    Bool wasOpen;
    
    if ( gotLock( &globalLock ) )
    {
	blockSignal( TRUE );
    
	wasOpen = globalLock.lazyLockBusy;
	closeDatabases();
	if ( wasOpen )
	    openDatabases();
	
	blockSignal( FALSE );
    }
}

/* Check the global lock held. */
Bool
Lock_gotLock( void )
{
    return globalLock.lazyLockBusy;
}

/* Get fetch lock. */
Bool
Lock_getFetchLock( enum LockRequestWait wait )
{
    return waitLock( &fetchLock, wait );
}

/* Release fetch lock. */
void
Lock_releaseFetchLock( void )
{
    releaseLock( &fetchLock );
}

/* Check the fetch lock held. */
Bool
Lock_fetchLock( void )
{
    return gotLock( &fetchLock );
}

