/* lock.c $Id: lock.c,v 1.9 2002/11/10 11:32:17 bears Exp $ */ #if HAVE_CONFIG_H #include #endif #include #include #include #include #include #include #include #if TIME_WITH_SYS_TIME #include #include #else #if HAVE_SYS_TIME_H #include #else #include #endif #endif #include #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 SignalHandler 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 ) { SignalHandler 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 ); }