| /* |
| * 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 */ |