Browse code

add dbdir locking mechanism (closes bb#113, #143)

git-svn: trunk@2620

Tomasz Kojm authored on 2007/01/14 01:57:58
Showing 10 changed files
... ...
@@ -1,3 +1,8 @@
1
+Sat Jan 13 17:55:25 CET 2007 (tk)
2
+---------------------------------
3
+  * libclamav, freshclam: add dbdir locking mechanism (closes bb#113, #143)
4
+			  Patch from Mark Pizzolato
5
+
1 6
 Sat Jan 13 15:37:51 CET 2007 (acab)
2 7
 -----------------------------------
3 8
   * libclamav: add Upack support from Michal Spadlinski <gim913 * gmail.com>
... ...
@@ -55,6 +55,8 @@
55 55
 #include "shared/output.h"
56 56
 #include "shared/misc.h"
57 57
 
58
+#include "libclamav/lockdb.h"
59
+
58 60
 #include "execute.h"
59 61
 #include "manager.h"
60 62
 #include "mirman.h"
... ...
@@ -148,7 +150,7 @@ void help(void)
148 148
     mprintf("\n");
149 149
 }
150 150
 
151
-int download(const struct cfgstruct *copt, const struct optstruct *opt)
151
+int download(const struct cfgstruct *copt, const struct optstruct *opt, const char *datadir)
152 152
 {
153 153
 	int ret = 0, try = 0, maxattempts = 0;
154 154
 	struct cfgstruct *cpt;
... ...
@@ -161,7 +163,15 @@ int download(const struct cfgstruct *copt, const struct optstruct *opt)
161 161
 	logg("^You must specify at least one database mirror.\n");
162 162
 	return 56;
163 163
     } else {
164
-
164
+	while(cli_writelockdb(datadir, 0) == CL_ELOCKDB) {
165
+            logg("*Waiting to lock database directory: %s\n", datadir);
166
+	    sleep(5);
167
+	    if(++try > 12) {
168
+		logg("!Can't lock database directory: %s\n", datadir);
169
+		return 61; 
170
+	    }
171
+	}
172
+	try = 0;
165 173
 	while(cpt) {
166 174
 	    ret = downloadmanager(copt, opt, cpt->strarg);
167 175
 	    alarm(0);
... ...
@@ -182,9 +192,11 @@ int download(const struct cfgstruct *copt, const struct optstruct *opt)
182 182
 		}
183 183
 
184 184
 	    } else {
185
+		cli_unlockdb(datadir);
185 186
 		return ret;
186 187
 	    }
187 188
 	}
189
+	cli_unlockdb(datadir);
188 190
     }
189 191
 
190 192
     return ret;
... ...
@@ -489,7 +501,7 @@ int main(int argc, char **argv)
489 489
 #endif
490 490
 
491 491
 	while(!terminate) {
492
-	    ret = download(copt, opt);
492
+	    ret = download(copt, opt, newdir);
493 493
 
494 494
             if(ret > 1) {
495 495
 		    const char *arg = NULL;
... ...
@@ -541,7 +553,7 @@ int main(int argc, char **argv)
541 541
 	}
542 542
 
543 543
     } else
544
-	ret = download(copt, opt);
544
+	ret = download(copt, opt, newdir);
545 545
 
546 546
     if(opt_check(opt, "on-error-execute")) {
547 547
 	if(ret > 1)
... ...
@@ -164,6 +164,8 @@ libclamav_la_SOURCES = \
164 164
 	hashtab.c \
165 165
 	hashtab.h \
166 166
 	dconf.c \
167
-	dconf.h
167
+	dconf.h \
168
+	lockdb.c \
169
+	lockdb.h
168 170
 
169 171
 lib_LTLIBRARIES = libclamav.la
... ...
@@ -88,7 +88,8 @@ am_libclamav_la_OBJECTS = matcher-ac.lo matcher-bm.lo matcher-ncore.lo \
88 88
 	unrarfilter.lo unrarppm.lo unrar20.lo unrarcmd.lo pdf.lo \
89 89
 	spin.lo yc.lo elf.lo sis.lo uuencode.lo pst.lo phishcheck.lo \
90 90
 	phish_domaincheck_db.lo phish_whitelist.lo regex_list.lo \
91
-	sha256.lo mspack.lo cab.lo entconv.lo hashtab.lo dconf.lo
91
+	sha256.lo mspack.lo cab.lo entconv.lo hashtab.lo dconf.lo \
92
+	lockdb.lo
92 93
 libclamav_la_OBJECTS = $(am_libclamav_la_OBJECTS)
93 94
 DEFAULT_INCLUDES = -I. -I$(srcdir) -I$(top_builddir)
94 95
 depcomp = $(SHELL) $(top_srcdir)/depcomp
... ...
@@ -376,7 +377,9 @@ libclamav_la_SOURCES = \
376 376
 	hashtab.c \
377 377
 	hashtab.h \
378 378
 	dconf.c \
379
-	dconf.h
379
+	dconf.h \
380
+	lockdb.c \
381
+	lockdb.h
380 382
 
381 383
 lib_LTLIBRARIES = libclamav.la
382 384
 all: all-am
... ...
@@ -463,6 +466,7 @@ distclean-compile:
463 463
 @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/htmlnorm.Plo@am__quote@
464 464
 @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/is_tar.Plo@am__quote@
465 465
 @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/line.Plo@am__quote@
466
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/lockdb.Plo@am__quote@
466 467
 @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/matcher-ac.Plo@am__quote@
467 468
 @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/matcher-bm.Plo@am__quote@
468 469
 @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/matcher-ncore.Plo@am__quote@
... ...
@@ -62,7 +62,7 @@ extern "C"
62 62
 #define CL_EIO		-123 /* general I/O error */
63 63
 #define CL_EFORMAT	-124 /* bad format or broken file */
64 64
 #define CL_ESUPPORT	-125 /* not supported data format */
65
-#define CL_EFOO		-126 /* fake error code */
65
+#define CL_ELOCKDB	-126 /* can't lock DB directory */
66 66
 
67 67
 /* NodalCore */
68 68
 #define CL_ENCINIT	-200 /* NodalCore initialization failed */
69 69
new file mode 100644
... ...
@@ -0,0 +1,277 @@
0
+/*
1
+ *  Copyright (C) 2006 Mark Pizzolato <clamav-devel@subscriptions.pizzolato.net>
2
+ *
3
+ *  This program is free software; you can redistribute it and/or modify
4
+ *  it under the terms of the GNU General Public License as published by
5
+ *  the Free Software Foundation; either version 2 of the License, or
6
+ *  (at your option) any later version.
7
+ *
8
+ *  This program is distributed in the hope that it will be useful,
9
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
10
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11
+ *  GNU General Public License for more details.
12
+ *
13
+ *  You should have received a copy of the GNU General Public License
14
+ *  along with this program; if not, write to the Free Software
15
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
16
+ *  MA 02110-1301, USA.
17
+ */
18
+
19
+/*
20
+ * This is a problem, which from a purist point of view, best wants an 
21
+ * RW locking mechanism.
22
+ * On Posix platforms, we leverage advisory locks provided by fcntl().
23
+ * Windows doesn't have a native interprocess RW exclusion mechanism, 
24
+ * one could be constructed from the services available, but it is somewhat
25
+ * complicated.  Meanwhile, we observe that in ClamAV, it is extremely rare 
26
+ * that there will ever be an occasion when multiple processes will be 
27
+ * reading the ClamAV database from a given directory at the same, and in 
28
+ * none of those possible cases would it matter if they serialized their 
29
+ * accesses.  So, a simple mutual exclusion mechanism will suffice for both 
30
+ * the reader and writer locks on Windows.
31
+ */
32
+
33
+#if HAVE_CONFIG_H
34
+#include "clamav-config.h"
35
+#endif
36
+
37
+#include <stdio.h>
38
+#include <stdarg.h>
39
+#include <stdlib.h>
40
+#include <string.h>
41
+#include <ctype.h>
42
+#ifndef C_WINDOWS
43
+#include <unistd.h>
44
+#include <fcntl.h>
45
+#include <sys/stat.h>
46
+#else
47
+#include <windows.h>
48
+#endif
49
+
50
+#include "clamav.h"
51
+#include "others.h"
52
+
53
+#ifdef CL_THREAD_SAFE
54
+#include <pthread.h>
55
+pthread_mutex_t lock_mutex = PTHREAD_MUTEX_INITIALIZER;
56
+#else
57
+#define pthread_mutex_lock(arg)
58
+#define pthread_mutex_unlock(arg)
59
+#endif
60
+
61
+struct dblock {
62
+	struct dblock *lock_link;
63
+	char lock_file[NAME_MAX];
64
+#ifndef C_WINDOWS
65
+	int lock_fd;
66
+#else
67
+	HANDLE lock_fd;
68
+#endif
69
+	int lock_type;
70
+};
71
+
72
+static struct dblock *dblocks = NULL;
73
+
74
+static void cli_lockname(char *lock_file, size_t lock_file_size, const char *dbdirpath);
75
+static int cli_lockdb(const char *dbdirpath, int wait, int writelock);
76
+
77
+#ifdef DONT_LOCK_DBDIRS
78
+
79
+int cli_readlockdb(const char *dbdirpath, int wait)
80
+{
81
+    return CL_SUCCESS;
82
+}
83
+
84
+int cli_writelockdb(const char *dbdirpath, int wait)
85
+{
86
+    return CL_SUCCESS;
87
+}
88
+
89
+int cli_unlockdb(const char *dbdirpath)
90
+{
91
+    return CL_SUCCESS;
92
+}
93
+
94
+#else /* !DONT_LOCK_DBDIRS */
95
+
96
+int cli_readlockdb(const char *dbdirpath, int wait)
97
+{
98
+    return cli_lockdb(dbdirpath, wait, 0);
99
+}
100
+
101
+int cli_writelockdb(const char *dbdirpath, int wait)
102
+{
103
+    return cli_lockdb(dbdirpath, wait, 1);
104
+}
105
+
106
+int cli_unlockdb(const char *dbdirpath)
107
+{
108
+	char lock_file[NAME_MAX];
109
+	struct dblock *lock;
110
+#ifndef C_WINDOWS
111
+	struct flock fl;
112
+#endif
113
+
114
+    cli_lockname(lock_file, sizeof(lock_file), dbdirpath);
115
+    pthread_mutex_lock(&lock_mutex);
116
+    for(lock=dblocks; lock; lock=lock->lock_link)
117
+	if(!strcmp(lock_file, lock->lock_file))
118
+	    break;
119
+    if((!lock) || (lock->lock_type == -1)) {
120
+	cli_errmsg("Database Directory: %s not locked\n", dbdirpath);
121
+	pthread_mutex_unlock(&lock_mutex);
122
+	return CL_ELOCKDB;
123
+    }
124
+#ifndef C_WINDOWS
125
+    memset(&fl, 0, sizeof(fl));
126
+    fl.l_type = F_UNLCK;
127
+    if(fcntl(lock->lock_fd, F_SETLK, &fl) == -1) {
128
+#else
129
+    if(!ReleaseMutex(lock->lock_fd)) {
130
+#endif
131
+	cli_errmsg("Error Unlocking Database Directory %s\n", dbdirpath);
132
+	pthread_mutex_unlock(&lock_mutex);
133
+	return CL_ELOCKDB;
134
+    }
135
+    lock->lock_type = -1;
136
+    pthread_mutex_unlock(&lock_mutex);
137
+
138
+    return CL_SUCCESS;
139
+}
140
+
141
+static int cli_lockdb(const char *dbdirpath, int wait, int writelock)
142
+{
143
+	char lock_file[NAME_MAX];
144
+	struct dblock *lock;
145
+#ifndef C_WINDOWS
146
+	struct flock fl;
147
+	mode_t old_mask;
148
+#else
149
+	DWORD LastError;
150
+	SECURITY_ATTRIBUTES saAttr;
151
+	SECURITY_DESCRIPTOR sdDesc;
152
+#endif
153
+
154
+    cli_lockname(lock_file, sizeof(lock_file), dbdirpath);
155
+    pthread_mutex_lock(&lock_mutex);
156
+    for(lock=dblocks; lock; lock=lock->lock_link)
157
+	if(!strcmp(lock_file, lock->lock_file))
158
+	    break;
159
+    if(!lock) {
160
+	lock = cli_calloc(1, sizeof(*lock));
161
+	if(!lock) {
162
+	    cli_errmsg("cli_lockdb(): Can't allocate lock structure to lock Database Directory: %s\n", dbdirpath);
163
+	    pthread_mutex_unlock(&lock_mutex);
164
+	    return CL_ELOCKDB;
165
+	}
166
+	lock->lock_link = dblocks;
167
+	strcpy(lock->lock_file, lock_file);
168
+	lock->lock_fd = -1;
169
+	lock->lock_type = -1;
170
+	dblocks = lock;
171
+    }
172
+    if(lock->lock_type != -1) {
173
+	cli_errmsg("Database Directory: %s already %s locked\n", dbdirpath, (lock->lock_type? "write" : "read"));
174
+	pthread_mutex_unlock(&lock_mutex);
175
+	return CL_ELOCKDB;
176
+    }
177
+#ifndef C_WINDOWS
178
+    if(lock->lock_fd == -1) {
179
+	old_mask = umask(0);
180
+	if(-1 == (lock->lock_fd = open(lock->lock_file, O_RDWR|O_CREAT|O_TRUNC, S_IRWXU|S_IRWXG|S_IROTH))) {
181
+	    if((writelock) ||
182
+	       (-1 == (lock->lock_fd = open(lock->lock_file, O_RDWR, 0)))) {
183
+		cli_errmsg("Can't %s Lock file for Database Directory: %s\n", (writelock ? "create" : "open"), dbdirpath);
184
+		umask(old_mask);
185
+		pthread_mutex_unlock(&lock_mutex);
186
+		return CL_ELOCKDB;
187
+	    }
188
+	}
189
+	umask(old_mask);
190
+    }
191
+#else
192
+    if(!lock->lock_fd) {
193
+	/* Create a security descriptor which allows any process to acquire the Mutex */
194
+	InitializeSecurityDescriptor(&sdDesc, SECURITY_DESCRIPTOR_REVISION);
195
+	SetSecurityDescriptorDacl(&sdDesc, TRUE, NULL, FALSE);
196
+	saAttr.nLength = sizeof(saAttr);
197
+	saAttr.bInheritHandle = FALSE;
198
+	saAttr.lpSecurityDescriptor = &sdDesc;
199
+	if(!(lock->lock_fd = CreateMutexA(&saAttr, TRUE, lock->lock_file))) {
200
+	    if((GetLastError() != ERROR_ACCESS_DENIED) || 
201
+	       (!(lock->lock_fd = OpenMutexA(MUTEX_MODIFY_STATE, FALSE, lock->lock_file)))) {
202
+		cli_errmsg("Can't Create Mutex Lock for Database Directory: %s\n", dbdirpath);
203
+		pthread_mutex_unlock(&lock_mutex);
204
+		return CL_ELOCKDB;
205
+	    }
206
+	    LastError = ERROR_ALREADY_EXISTS;
207
+	}
208
+	LastError = GetLastError();
209
+    } else {
210
+	LastError = ERROR_ALREADY_EXISTS;
211
+    }
212
+#endif
213
+    pthread_mutex_unlock(&lock_mutex);
214
+
215
+#ifndef C_WINDOWS
216
+    memset(&fl, 0, sizeof(fl));
217
+    fl.l_type = (writelock ? F_WRLCK : F_RDLCK);
218
+    cli_dbgmsg("przed fcntl: %d\n", lock->lock_fd);
219
+    if(fcntl(lock->lock_fd, ((wait) ? F_SETLKW : F_SETLK), &fl) == -1) {
220
+	perror("FCNTL");
221
+	return CL_ELOCKDB;
222
+    }
223
+#else
224
+    if(LastError == ERROR_ALREADY_EXISTS) {
225
+	if(WAIT_TIMEOUT == WaitForSingleObject(lock->lock_fd, ((wait) ? INFINITE : 0))) {
226
+	    lock->lock_type = -1;
227
+	    return CL_ELOCKDB;
228
+	}
229
+    }
230
+#endif
231
+    lock->lock_type = writelock;
232
+
233
+    return CL_SUCCESS;
234
+}
235
+
236
+static void cli_lockname(char *lock_file, size_t lock_file_size, const char *dbdirpath)
237
+{
238
+	char *c;
239
+
240
+    lock_file[lock_file_size-1] = '\0';
241
+#ifndef C_WINDOWS
242
+    snprintf(lock_file, lock_file_size-1, "%s/.dbLock", dbdirpath);
243
+    for (c=lock_file; *c; ++c) {
244
+#else
245
+    snprintf(lock_file, lock_file_size-1, "Global\\ClamAVDB-%s", dbdirpath);
246
+    for (c=lock_file+16; *c; ++c) {
247
+#endif
248
+	switch (*c) {
249
+#ifdef C_WINDOWS
250
+	case '\\':
251
+	    *c = '/';
252
+#endif
253
+	case '/':
254
+	    if(*(c-1) == '/') { /* compress imbedded // */
255
+		--c;
256
+		strcpy(c, c+1);
257
+            } else if((*(c-2) == '/') && (*(c-1) == '.')) { /* compress imbedded /./ */
258
+		c -= 2;
259
+		strcpy(c, c+2);
260
+            }
261
+	    break;
262
+#ifdef C_WINDOWS
263
+	default:
264
+	    if(islower(*c)) /* Normalize to upper case */
265
+		*c = toupper(*c);
266
+	    break;
267
+#endif
268
+	}
269
+    }
270
+#ifdef C_WINDOWS
271
+    if('/' == lock_file[strlen(lock_file)-1]) /* Remove trailing / */
272
+	lock_file[strlen(lock_file)-1] = '\0';
273
+#endif
274
+}
275
+
276
+#endif /* DONT_LOCK_DBDIRS */
0 277
new file mode 100644
... ...
@@ -0,0 +1,27 @@
0
+/*
1
+ *  Copyright (C) 2006 Mark Pizzolato <clamav-devel@subscriptions.pizzolato.net>
2
+ *
3
+ *  This program is free software; you can redistribute it and/or modify
4
+ *  it under the terms of the GNU General Public License as published by
5
+ *  the Free Software Foundation; either version 2 of the License, or
6
+ *  (at your option) any later version.
7
+ *
8
+ *  This program is distributed in the hope that it will be useful,
9
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
10
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11
+ *  GNU General Public License for more details.
12
+ *
13
+ *  You should have received a copy of the GNU General Public License
14
+ *  along with this program; if not, write to the Free Software
15
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
16
+ *  MA 02110-1301, USA.
17
+ */
18
+
19
+#ifndef __LOCKDB_H
20
+#define __LOCKDB_H
21
+
22
+int cli_writelockdb(const char *dbdirpath, int wait);
23
+int cli_readlockdb(const char *dbdirpath, int wait);
24
+int cli_unlockdb(const char *dbdirpath);
25
+
26
+#endif
... ...
@@ -223,6 +223,8 @@ const char *cl_strerror(int clerror)
223 223
 	    return "Error loading NodalCore database";
224 224
 	case CL_ENCIO:
225 225
 	    return "NodalCore accelerator Input/Output error";
226
+	case CL_ELOCKDB:
227
+	    return "Unable to lock database directory";
226 228
 	default:
227 229
 	    return "Unknown error code";
228 230
     }
... ...
@@ -46,6 +46,17 @@
46 46
 
47 47
 #define CLI_MAX_ALLOCATION 184549376
48 48
 
49
+/* Maximum filenames under various systems - njh */
50
+#ifndef	NAME_MAX	/* e.g. Linux */
51
+# ifdef	MAXNAMELEN	/* e.g. Solaris */
52
+#   define	NAME_MAX	MAXNAMELEN
53
+# else
54
+#   ifdef	FILENAME_MAX	/* e.g. SCO */
55
+#     define	NAME_MAX	FILENAME_MAX
56
+#   endif
57
+# endif
58
+#endif
59
+
49 60
 /* internal clamav context */
50 61
 typedef struct {
51 62
     const char **virname;
... ...
@@ -49,6 +49,7 @@
49 49
 #include "str.h"
50 50
 #include "defaults.h"
51 51
 #include "dconf.h"
52
+#include "lockdb.h"
52 53
 
53 54
 #ifdef CL_EXPERIMENTAL
54 55
 #include "phish_whitelist.h"
... ...
@@ -61,19 +62,6 @@
61 61
 #include <stddef.h>
62 62
 #endif
63 63
 
64
-/* Maximum filenames under various systems - njh */
65
-#ifndef	NAME_MAX	/* e.g. Linux */
66
-# ifdef	MAXNAMELEN	/* e.g. Solaris */
67
-#   define	NAME_MAX	MAXNAMELEN
68
-# else
69
-#   ifdef	FILENAME_MAX	/* e.g. SCO */
70
-#     define	NAME_MAX	FILENAME_MAX
71
-#   else
72
-#     define	NAME_MAX	256
73
-#   endif
74
-# endif
75
-#endif
76
-
77 64
 #ifdef CL_THREAD_SAFE
78 65
 #  include <pthread.h>
79 66
 static pthread_mutex_t cli_ref_mutex = PTHREAD_MUTEX_INITIALIZER;
... ...
@@ -1210,7 +1198,7 @@ int cl_loaddb(const char *filename, struct cl_engine **engine, unsigned int *sig
1210 1210
     return cli_load(filename, engine, signo, 0);
1211 1211
 }
1212 1212
 
1213
-static int cli_loaddbdir(const char *dirname, struct cl_engine **engine, unsigned int *signo, unsigned int options)
1213
+static int cli_loaddbdir_l(const char *dirname, struct cl_engine **engine, unsigned int *signo, unsigned int options)
1214 1214
 {
1215 1215
 	DIR *dd;
1216 1216
 	struct dirent *dent;
... ...
@@ -1285,6 +1273,25 @@ static int cli_loaddbdir(const char *dirname, struct cl_engine **engine, unsigne
1285 1285
     return CL_SUCCESS;
1286 1286
 }
1287 1287
 
1288
+static int cli_loaddbdir(const char *dirname, struct cl_engine **engine, unsigned int *signo, unsigned int options)
1289
+{
1290
+	int ret, try;
1291
+
1292
+
1293
+    cli_dbgmsg("cli_loaddbdir: Acquiring dbdir lock\n");
1294
+    while(cli_readlockdb(dirname, 0) == CL_ELOCKDB) {
1295
+	sleep(5);
1296
+	if(try++ > 24) {
1297
+	    cli_errmsg("cl_load(): Unable to lock database directory: %s\n", dirname);
1298
+	    return CL_ELOCKDB;
1299
+	}
1300
+    }
1301
+
1302
+    ret = cli_loaddbdir_l(dirname, engine, signo, options);
1303
+    cli_unlockdb(dirname);
1304
+    return ret;
1305
+}
1306
+
1288 1307
 int cl_loaddbdir(const char *dirname, struct cl_engine **engine, unsigned int *signo) {
1289 1308
     return cli_loaddbdir(dirname, engine, signo, 0);
1290 1309
 }