/* * Copyright (C) 2006 Mark Pizzolato * * 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 #endif #if HAVE_CONFIG_H #include "clamav-config.h" #endif #include #include #include #include #include #ifdef HAVE_UNISTD_H #include #endif #ifdef HAVE_SYS_STAT_H #include #endif #include #include #include "clamav.h" #include "others.h" #include "lockdb.h" #ifdef CL_THREAD_SAFE #include pthread_mutex_t lock_mutex = PTHREAD_MUTEX_INITIALIZER; #else #define pthread_mutex_lock(arg) #define pthread_mutex_unlock(arg) #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; #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_RDWR, 0)))) { 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 */ } } 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 if(errno != EACCES && errno != EAGAIN) { close(lock->lock_fd); lock->lock_fd=-1; unlink(lock->lock_file); 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 */