mirror of https://github.com/Cisco-Talos/clamav
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
335 lines
8.7 KiB
335 lines
8.7 KiB
/*
|
|
* 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 */
|
|
|