blob: b3559a7cf3ace26014ccbe65388f7689522e02f4 [file] [log] [blame]
/*
* 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.
*/
#ifdef _MSC_VER
#include <windows.h>
#endif
#if HAVE_CONFIG_H
#include "clamav-config.h"
#endif
#include <stdio.h>
#include <stdarg.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif
#ifdef HAVE_SYS_STAT_H
#include <sys/stat.h>
#endif
#include <fcntl.h>
#include <errno.h>
#include "clamav.h"
#include "others.h"
#include "lockdb.h"
#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
#ifdef C_WINDOWS /* FIXME */
#define DONT_LOCK_DBDIRS
#endif
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;
}
int cli_freelocks(void)
{
return CL_SUCCESS;
}
#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);
}
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;
}
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);
#ifndef C_WINDOWS
close(lock->lock_fd);
lock->lock_fd=-1;
unlink(lock->lock_file);
#endif
return CL_ELOCKDB;
}
lock->lock_type = -1;
#ifndef C_WINDOWS
close(lock->lock_fd);
lock->lock_fd=-1;
unlink(lock->lock_file);
#endif
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;
unsigned int existing = 0;
#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);
return CL_EMEM;
}
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) {
cli_dbgmsg("Database Directory: %s already %s locked\n", dbdirpath, (lock->lock_type? "write" : "read"));
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) ||
(-1 == (lock->lock_fd = open(lock->lock_file, O_RDONLY)))) {
cli_dbgmsg("Can't %s Lock file for Database Directory: %s\n", (writelock ? "create" : "open"), dbdirpath);
umask(old_mask);
pthread_mutex_unlock(&lock_mutex);
return CL_EIO; /* or CL_EACCESS */
} else {
existing = 1;
}
}
umask(old_mask);
}
#else
if(lock->lock_fd == -1) {
/* 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)))) {
cli_dbgmsg("Can't Create Mutex Lock for Database Directory: %s\n", dbdirpath);
pthread_mutex_unlock(&lock_mutex);
return CL_EIO;
}
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) {
#ifndef C_WINDOWS
close(lock->lock_fd);
lock->lock_fd = -1;
if(errno != EACCES && errno != EAGAIN) {
if(!existing)
unlink(lock->lock_file);
cli_errmsg("Can't acquire %s lock: %s\n", writelock ? "write" : "read", strerror(errno));
return CL_EIO;
}
#endif
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 '/':
if(c!=lock_file && *(c-1) == '/') { /* compress imbedded // */
--c;
memmove(c, c+1,strlen(c+1)+1);
} else if(c > lock_file+1 && (*(c-2) == '/') && (*(c-1) == '.')) { /* compress imbedded /./ */
c -= 2;
memmove(c, c+2,strlen(c+2)+1);
}
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 */