libclamav/lockdb.c
06646acf
 /*
  *  Copyright (C) 2006 Mark Pizzolato <clamav-devel@subscriptions.pizzolato.net>
  *
  *  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 program is distributed in the hope that it will be useful,
  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  *  GNU General Public License for more details.
  *
  *  You should have received a copy of the GNU General Public License
  *  along with this program; if not, write to the Free Software
  *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
  *  MA 02110-1301, USA.
  */
 
 /*
  * This is a problem, which from a purist point of view, best wants an 
  * RW locking mechanism.
  * On Posix platforms, we leverage advisory locks provided by fcntl().
  * Windows doesn't have a native interprocess RW exclusion mechanism, 
  * one could be constructed from the services available, but it is somewhat
  * complicated.  Meanwhile, we observe that in ClamAV, it is extremely rare 
  * that there will ever be an occasion when multiple processes will be 
  * reading the ClamAV database from a given directory at the same, and in 
  * none of those possible cases would it matter if they serialized their 
  * accesses.  So, a simple mutual exclusion mechanism will suffice for both 
  * the reader and writer locks on Windows.
  */
46e3fab8
 #ifdef	_MSC_VER
 #include <windows.h>
 #endif
06646acf
 
 #if HAVE_CONFIG_H
 #include "clamav-config.h"
 #endif
 
 #include <stdio.h>
 #include <stdarg.h>
 #include <stdlib.h>
 #include <string.h>
 #include <ctype.h>
46fec865
 #ifdef HAVE_UNISTD_H
06646acf
 #include <unistd.h>
46fec865
 #endif
46e3fab8
 #ifdef	HAVE_SYS_STAT_H
06646acf
 #include <sys/stat.h>
 #endif
46e3fab8
 #include <fcntl.h>
 #include <errno.h>
06646acf
 
 #include "clamav.h"
 #include "others.h"
fc83da82
 #include "lockdb.h"
06646acf
 
 #ifdef CL_THREAD_SAFE
 #include <pthread.h>
 pthread_mutex_t lock_mutex = PTHREAD_MUTEX_INITIALIZER;
 #else
 #define pthread_mutex_lock(arg)
 #define pthread_mutex_unlock(arg)
 #endif
 
d3a6556d
 #ifdef C_WINDOWS /* FIXME */
 #define DONT_LOCK_DBDIRS
 #endif
 
06646acf
 struct dblock {
 	struct dblock *lock_link;
 	char lock_file[NAME_MAX];
 #ifndef C_WINDOWS
 	int lock_fd;
 #else
 	HANDLE lock_fd;
 #endif
 	int lock_type;
 };
 
 static struct dblock *dblocks = NULL;
 
 static void cli_lockname(char *lock_file, size_t lock_file_size, const char *dbdirpath);
 static int cli_lockdb(const char *dbdirpath, int wait, int writelock);
 
 #ifdef DONT_LOCK_DBDIRS
 
 int cli_readlockdb(const char *dbdirpath, int wait)
 {
     return CL_SUCCESS;
 }
 
 int cli_writelockdb(const char *dbdirpath, int wait)
 {
     return CL_SUCCESS;
 }
 
 int cli_unlockdb(const char *dbdirpath)
 {
     return CL_SUCCESS;
 }
 
52c3c225
 int cli_freelocks(void)
 {
 	return CL_SUCCESS;
 }
 
06646acf
 #else /* !DONT_LOCK_DBDIRS */
 
 int cli_readlockdb(const char *dbdirpath, int wait)
 {
     return cli_lockdb(dbdirpath, wait, 0);
 }
 
 int cli_writelockdb(const char *dbdirpath, int wait)
 {
     return cli_lockdb(dbdirpath, wait, 1);
 }
 
52c3c225
 int cli_freelocks(void)
 {
 	struct dblock * lock, *nextlock, *usedlocks = NULL;
 
 	pthread_mutex_lock(&lock_mutex);
 	for(lock = dblocks; lock; lock = nextlock) {
 		/* there might be some locks in use, eg: during a db reload, a failure can lead 
 		 * to cl_free being called */
 		nextlock = lock->lock_link;
 		if(lock->lock_type != -1 && lock->lock_fd != -1) {
 			lock->lock_link = usedlocks;
 			usedlocks = lock;
 		}
 		else {
 			free(lock);
 		}
 	}
 	dblocks = usedlocks;
 	pthread_mutex_unlock(&lock_mutex);
 	return CL_SUCCESS;
 }
 
 
06646acf
 int cli_unlockdb(const char *dbdirpath)
 {
 	char lock_file[NAME_MAX];
 	struct dblock *lock;
 #ifndef C_WINDOWS
 	struct flock fl;
 #endif
 
     cli_lockname(lock_file, sizeof(lock_file), dbdirpath);
     pthread_mutex_lock(&lock_mutex);
     for(lock=dblocks; lock; lock=lock->lock_link)
 	if(!strcmp(lock_file, lock->lock_file))
 	    break;
     if((!lock) || (lock->lock_type == -1)) {
 	cli_errmsg("Database Directory: %s not locked\n", dbdirpath);
 	pthread_mutex_unlock(&lock_mutex);
 	return CL_ELOCKDB;
     }
 #ifndef C_WINDOWS
     memset(&fl, 0, sizeof(fl));
     fl.l_type = F_UNLCK;
     if(fcntl(lock->lock_fd, F_SETLK, &fl) == -1) {
 #else
     if(!ReleaseMutex(lock->lock_fd)) {
 #endif
 	cli_errmsg("Error Unlocking Database Directory %s\n", dbdirpath);
 	pthread_mutex_unlock(&lock_mutex);
1e6ca68a
 #ifndef C_WINDOWS
 	close(lock->lock_fd);
4b3a6d83
 	lock->lock_fd=-1;
1e6ca68a
 	unlink(lock->lock_file);
 #endif
06646acf
 	return CL_ELOCKDB;
     }
     lock->lock_type = -1;
1e6ca68a
 #ifndef C_WINDOWS
     close(lock->lock_fd);
4b3a6d83
     lock->lock_fd=-1;
1e6ca68a
     unlink(lock->lock_file);
 #endif
06646acf
     pthread_mutex_unlock(&lock_mutex);
 
     return CL_SUCCESS;
 }
 
 static int cli_lockdb(const char *dbdirpath, int wait, int writelock)
 {
 	char lock_file[NAME_MAX];
 	struct dblock *lock;
 #ifndef C_WINDOWS
 	struct flock fl;
 	mode_t old_mask;
093e555c
 	unsigned int existing = 0;
06646acf
 #else
 	DWORD LastError;
 	SECURITY_ATTRIBUTES saAttr;
 	SECURITY_DESCRIPTOR sdDesc;
 #endif
 
     cli_lockname(lock_file, sizeof(lock_file), dbdirpath);
     pthread_mutex_lock(&lock_mutex);
     for(lock=dblocks; lock; lock=lock->lock_link)
 	if(!strcmp(lock_file, lock->lock_file))
 	    break;
     if(!lock) {
 	lock = cli_calloc(1, sizeof(*lock));
 	if(!lock) {
 	    cli_errmsg("cli_lockdb(): Can't allocate lock structure to lock Database Directory: %s\n", dbdirpath);
 	    pthread_mutex_unlock(&lock_mutex);
148986bf
 	    return CL_EMEM;
06646acf
 	}
 	lock->lock_link = dblocks;
 	strcpy(lock->lock_file, lock_file);
 	lock->lock_fd = -1;
 	lock->lock_type = -1;
 	dblocks = lock;
     }
     if(lock->lock_type != -1) {
148986bf
 	cli_dbgmsg("Database Directory: %s already %s locked\n", dbdirpath, (lock->lock_type? "write" : "read"));
06646acf
 	pthread_mutex_unlock(&lock_mutex);
 	return CL_ELOCKDB;
     }
 #ifndef C_WINDOWS
     if(lock->lock_fd == -1) {
 	old_mask = umask(0);
 	if(-1 == (lock->lock_fd = open(lock->lock_file, O_RDWR|O_CREAT|O_TRUNC, S_IRWXU|S_IRWXG|S_IROTH))) {
 	    if((writelock) ||
093e555c
 	       (-1 == (lock->lock_fd = open(lock->lock_file, O_RDONLY)))) {
148986bf
 		cli_dbgmsg("Can't %s Lock file for Database Directory: %s\n", (writelock ? "create" : "open"), dbdirpath);
06646acf
 		umask(old_mask);
 		pthread_mutex_unlock(&lock_mutex);
148986bf
 		return CL_EIO; /* or CL_EACCESS */
093e555c
 	    } else {
 		existing = 1;
06646acf
 	    }
 	}
 	umask(old_mask);
     }
 #else
a5e7095a
     if(lock->lock_fd == -1) {
06646acf
 	/* Create a security descriptor which allows any process to acquire the Mutex */
 	InitializeSecurityDescriptor(&sdDesc, SECURITY_DESCRIPTOR_REVISION);
 	SetSecurityDescriptorDacl(&sdDesc, TRUE, NULL, FALSE);
 	saAttr.nLength = sizeof(saAttr);
 	saAttr.bInheritHandle = FALSE;
 	saAttr.lpSecurityDescriptor = &sdDesc;
 	if(!(lock->lock_fd = CreateMutexA(&saAttr, TRUE, lock->lock_file))) {
 	    if((GetLastError() != ERROR_ACCESS_DENIED) || 
 	       (!(lock->lock_fd = OpenMutexA(MUTEX_MODIFY_STATE, FALSE, lock->lock_file)))) {
148986bf
 		cli_dbgmsg("Can't Create Mutex Lock for Database Directory: %s\n", dbdirpath);
06646acf
 		pthread_mutex_unlock(&lock_mutex);
148986bf
 		return CL_EIO;
06646acf
 	    }
 	    LastError = ERROR_ALREADY_EXISTS;
 	}
 	LastError = GetLastError();
     } else {
 	LastError = ERROR_ALREADY_EXISTS;
     }
 #endif
     pthread_mutex_unlock(&lock_mutex);
 
 #ifndef C_WINDOWS
     memset(&fl, 0, sizeof(fl));
     fl.l_type = (writelock ? F_WRLCK : F_RDLCK);
     if(fcntl(lock->lock_fd, ((wait) ? F_SETLKW : F_SETLK), &fl) == -1) {
1e6ca68a
 #ifndef C_WINDOWS
bb5b480b
 	close(lock->lock_fd);
 	lock->lock_fd = -1;
148986bf
 	if(errno != EACCES && errno != EAGAIN) {
093e555c
 	    if(!existing)
 		unlink(lock->lock_file);
 	    cli_errmsg("Can't acquire %s lock: %s\n", writelock ? "write" : "read", strerror(errno));
148986bf
 	    return CL_EIO;
 	}
1e6ca68a
 #endif
06646acf
 	return CL_ELOCKDB;
     }
 #else
     if(LastError == ERROR_ALREADY_EXISTS) {
 	if(WAIT_TIMEOUT == WaitForSingleObject(lock->lock_fd, ((wait) ? INFINITE : 0))) {
 	    lock->lock_type = -1;
 	    return CL_ELOCKDB;
 	}
     }
 #endif
     lock->lock_type = writelock;
 
     return CL_SUCCESS;
 }
 
 static void cli_lockname(char *lock_file, size_t lock_file_size, const char *dbdirpath)
 {
 	char *c;
 
     lock_file[lock_file_size-1] = '\0';
 #ifndef C_WINDOWS
     snprintf(lock_file, lock_file_size-1, "%s/.dbLock", dbdirpath);
     for (c=lock_file; *c; ++c) {
 #else
     snprintf(lock_file, lock_file_size-1, "Global\\ClamAVDB-%s", dbdirpath);
     for (c=lock_file+16; *c; ++c) {
 #endif
 	switch (*c) {
 #ifdef C_WINDOWS
 	case '\\':
 	    *c = '/';
 #endif
 	case '/':
134ce6c8
 	    if(c!=lock_file && *(c-1) == '/') { /* compress imbedded // */
06646acf
 		--c;
134ce6c8
 		memmove(c, c+1,strlen(c+1)+1);
             } else if(c > lock_file+1 && (*(c-2) == '/') && (*(c-1) == '.')) { /* compress imbedded /./ */
06646acf
 		c -= 2;
134ce6c8
 		memmove(c, c+2,strlen(c+2)+1);
06646acf
             }
 	    break;
 #ifdef C_WINDOWS
 	default:
 	    if(islower(*c)) /* Normalize to upper case */
 		*c = toupper(*c);
 	    break;
 #endif
 	}
     }
 #ifdef C_WINDOWS
     if('/' == lock_file[strlen(lock_file)-1]) /* Remove trailing / */
 	lock_file[strlen(lock_file)-1] = '\0';
 #endif
 }
 
 #endif /* DONT_LOCK_DBDIRS */