Browse code

merge from clamd-proto branch: handle multiple %v parameters introduce poll_fds that also does the read into a buffer introduce thrmgr_group* commands for keeping track of groups of commands (multiscan, IDSESSION) introduce 2 queues in thrmgr, multiscan queues get lower priority. Commands are processed from both queues in a 4:1 ratio to avoid starvation. unify reply code: conn_reply* unify scanner code into scan_callback that is called from cli_ftw multiscan doesn't need stat() now if readdir() provides dt_type redesign clamd main loop: there is now an accept thread, and a recv()/dispatch thread, +MaxThreads worker (scanner) threads. introduce limiting on commands when worker threads are contended. introduce IDSESSION, a replacement for the old SESSION command, that allows asyncronous (scan) commands and scan replies introduce INSTREAM that allows sending the data to scan on same connection (vs STREAM that requires another port) introduce zCOMMANDS

git-svn: trunk@4755

Török Edvin authored on 2009/02/13 01:51:09
Showing 15 changed files
... ...
@@ -1,3 +1,17 @@
1
+Thu Feb 12 19:19:42 EET 2009 (edwin)
2
+------------------------------------
3
+ * clamd/, shared/: merge from clamd-proto branch:     handle multiple %v parameters     introduce poll_fds that also does the read into a buffer     introduce thrmgr_group* commands for keeping track of groups of
4
+     commands (multiscan, IDSESSION)     introduce 2 queues in thrmgr, multiscan queues get lower
5
+     priority. Commands are processed from both queues in a 4:1 ratio
6
+     to avoid starvation.  unify reply code: conn_reply*     unify scanner code into scan_callback that is called from
7
+     cli_ftw multiscan doesn't need stat() now if readdir() provides
8
+     dt_type redesign clamd main loop: there is now an accept thread, and
9
+     a recv()/dispatch thread, +MaxThreads worker (scanner) threads.      introduce limiting on commands when worker threads are
10
+     contended.  introduce IDSESSION, a replacement for the old
11
+     SESSION command, that allows asyncronous (scan) commands and scan
12
+     replies introduce INSTREAM that allows sending the data to scan on
13
+     same connection (vs STREAM that requires another port)     introduce zCOMMANDS
14
+
1 15
 Thu Feb 12 19:08:28 EET 2009 (edwin)
2 16
 ------------------------------------
3 17
  * libclamav/libclamav.map, libclamav/others.h,
... ...
@@ -99,7 +99,7 @@ int main(int argc, char **argv)
99 99
 	time_t currtime;
100 100
 	const char *dbdir, *cfgfile;
101 101
 	char *pua_cats = NULL, *pt;
102
-	int ret, tcpsock = 0, localsock = 0, i;
102
+	int ret, tcpsock = 0, localsock = 0, i, min_port, max_port;
103 103
 	unsigned int sigs = 0;
104 104
 	int lsockets[2], nlsockets = 0;
105 105
 	unsigned int dboptions = 0;
... ...
@@ -287,6 +287,15 @@ int main(int argc, char **argv)
287 287
     else
288 288
 	logg("#Log file size limit disabled.\n");
289 289
 
290
+    min_port = optget(opts, "StreamMinPort")->numarg;
291
+    max_port = optget(opts, "StreamMaxPort")->numarg;
292
+    if (min_port < 1024 || min_port > max_port || max_port > 65535) {
293
+	logg("!Invalid StreaMinPort/StreamMaxPort: %d, %d\n", min_port, max_port);
294
+	logg_close();
295
+	optfree(opts);
296
+	return 1;
297
+    }
298
+
290 299
     if(!(engine = cl_engine_new())) {
291 300
 	logg("!Can't initialize antivirus engine\n");
292 301
 	logg_close();
... ...
@@ -488,7 +497,7 @@ int main(int argc, char **argv)
488 488
         foreground = 1;
489 489
 
490 490
 
491
-    ret = acceptloop_th(lsockets, nlsockets, engine, dboptions, opts);
491
+    ret = recvloop_th(lsockets, nlsockets, engine, dboptions, opts);
492 492
 
493 493
 #ifdef C_WINDOWS
494 494
     if(tcpsock)
... ...
@@ -40,7 +40,7 @@
40 40
 #include "clamuko.h"
41 41
 
42 42
 struct dazuko_access *acc;
43
-
43
+short int clamuko_scanning;
44 44
 static void clamuko_exit(int sig)
45 45
 {
46 46
 
... ...
@@ -21,7 +21,7 @@
21 21
 #ifndef __CLAMUKO_H
22 22
 #define __CLAMUKO_H
23 23
 
24
-short int clamuko_scanning;
24
+extern short int clamuko_scanning;
25 25
 void *clamukoth(void *arg);
26 26
 
27 27
 #endif
... ...
@@ -80,9 +80,6 @@
80 80
 #include "session.h"
81 81
 #include "others.h"
82 82
 
83
-#define ENV_FILE  "CLAM_VIRUSEVENT_FILENAME"
84
-#define ENV_VIRUS "CLAM_VIRUSEVENT_VIRUSNAME"
85
-
86 83
 #ifdef	C_WINDOWS
87 84
 void virusaction(const char *filename, const char *virname, const struct optstruct *opts)
88 85
 {
... ...
@@ -91,14 +88,19 @@ void virusaction(const char *filename, const char *virname, const struct optstru
91 91
 }
92 92
 
93 93
 #else
94
+
95
+#define VE_FILENAME  "CLAM_VIRUSEVENT_FILENAME"
96
+#define VE_VIRUSNAME "CLAM_VIRUSEVENT_VIRUSNAME"
97
+
94 98
 static pthread_mutex_t virusaction_lock = PTHREAD_MUTEX_INITIALIZER;
95 99
 
96 100
 void virusaction(const char *filename, const char *virname, const struct optstruct *opts)
97 101
 {
98 102
 	pid_t pid;
99 103
 	const struct optstruct *opt;
100
-	char *buffer, *pt, *cmd, *buffer_file, *buffer_vir;
101
-	size_t j;
104
+	char *buffer_file, *buffer_vir, *buffer_cmd;
105
+	const char *pt;
106
+	size_t i, j, v = 0, len;
102 107
 	char *env[4];
103 108
 
104 109
 	if(!(opt = optget(opts, "VirusEvent"))->enabled)
... ...
@@ -107,336 +109,487 @@ void virusaction(const char *filename, const char *virname, const struct optstru
107 107
 	env[0] = getenv("PATH");
108 108
 	j = env[0] ? 1 : 0;
109 109
 	/* Allocate env vars.. to be portable env vars should not be freed */
110
-	buffer_file = (char *) malloc(strlen(ENV_FILE) + strlen(filename) + 2);
110
+	buffer_file = (char *) malloc(strlen(VE_FILENAME) + strlen(filename) + 2);
111 111
 	if(buffer_file) {
112
-		sprintf(buffer_file, "%s=%s", ENV_FILE, filename);
112
+		sprintf(buffer_file, "%s=%s", VE_FILENAME, filename);
113 113
 		env[j++] = buffer_file;
114 114
 	}
115 115
 
116
-	buffer_vir = (char *) malloc(strlen(ENV_VIRUS) + strlen(virname) + 2);
116
+	buffer_vir = (char *) malloc(strlen(VE_VIRUSNAME) + strlen(virname) + 2);
117 117
 	if(buffer_vir) {
118
-		sprintf(buffer_vir, "%s=%s", ENV_VIRUS, virname);
118
+		sprintf(buffer_vir, "%s=%s", VE_VIRUSNAME, virname);
119 119
 		env[j++] = buffer_vir;
120 120
 	}
121 121
 	env[j++] = NULL;
122 122
 
123
-	cmd = strdup(opt->strarg);
124
-
125
-	if(cmd && (pt = strstr(cmd, "%v"))) {
126
-		buffer = (char *) malloc(strlen(cmd) + strlen(virname) + 10);
127
-		if(buffer) {
128
-			*pt = 0; pt += 2;
129
-			strcpy(buffer, cmd);
130
-			strcat(buffer, virname);
131
-			strcat(buffer, pt);
132
-			free(cmd);
133
-			cmd = strdup(buffer);
134
-			free(buffer);
135
-		}
123
+	pt = opt->strarg;
124
+	while((pt = strstr(pt, "%v"))) {
125
+	    pt += 2;
126
+	    v++;
136 127
 	}
137
-
138
-	if(!cmd) {
139
-		free(buffer_file);
140
-		free(buffer_vir);
141
-		return;
128
+	len = strlen(opt->strarg);
129
+	buffer_cmd = (char *) calloc(len + v * strlen(virname) + 1, sizeof(char));
130
+	if(!buffer_cmd) {
131
+	    free(buffer_file);
132
+	    free(buffer_vir);
133
+	    return;
142 134
 	}
135
+	for(i = 0, j = 0; i < len; i++) {
136
+	    if(i + 1 < len && opt->strarg[i] == '%' && opt->strarg[i + 1] == 'v') {
137
+		strcat(buffer_cmd, virname);
138
+		j += strlen(virname);
139
+		i++;
140
+	    } else {
141
+		buffer_cmd[j++] = opt->strarg[i];
142
+	    }
143
+	}
144
+
143 145
 	pthread_mutex_lock(&virusaction_lock);
144 146
 	/* We can only call async-signal-safe functions after fork(). */
145 147
 	pid = fork();
146
-
147
-	if ( pid == 0 ) {
148
-		/* child... */
149
-		/* WARNING: this is uninterruptable ! */
150
-		exit(execle("/bin/sh", "sh", "-c", cmd, NULL, env));
151
-	} else if (pid > 0) {
152
-		pthread_mutex_unlock(&virusaction_lock);
153
-		/* parent */
154
-		waitpid(pid, NULL, 0);
148
+	if(!pid) { /* child */
149
+	    exit(execle("/bin/sh", "sh", "-c", buffer_cmd, NULL, env));
150
+	} else if(pid > 0) { /* parent */
151
+	    pthread_mutex_unlock(&virusaction_lock);
152
+	    waitpid(pid, NULL, 0);
155 153
 	} else {
156
-		pthread_mutex_unlock(&virusaction_lock);
157
-		/* error.. */
158
-		logg("!VirusAction: fork failed.\n");
154
+	    logg("!VirusEvent: fork failed.\n");
159 155
 	}
160
-	free(cmd);
156
+	free(buffer_cmd);
161 157
 	free(buffer_file);
162 158
 	free(buffer_vir);
163 159
 }
164 160
 #endif /* C_WINDOWS */
165 161
 
166
-int poll_fds(int *fds, int nfds, int timeout_sec, int check_signals)
162
+/* Function: writen
163
+	Try hard to write the specified number of bytes
164
+*/
165
+int writen(int fd, void *buff, unsigned int count)
167 166
 {
168 167
 	int retval;
169
-	int i;
170
-#ifdef HAVE_POLL
171
-	struct pollfd poll_1[1];
172
-	struct pollfd *poll_data = poll_1;
173
-
174
-    if (nfds>1) {
175
-	poll_data = malloc(nfds*sizeof(*poll_data));
176
-	if(!poll_data) {
177
-	    logg("!poll_fds: Can't allocate memory for poll_data\n");
178
-	    return -1;
179
-	}
180
-    }
181
-
182
-    for (i=0; i<nfds; i++) {
183
-	poll_data[i].fd = fds[i];
184
-	poll_data[i].events = POLLIN;
185
-	poll_data[i].revents = 0;
186
-    }
187
-
188
-    if (timeout_sec > 0) {
189
-    	timeout_sec *= 1000;
190
-    }
191
-    while (1) {
192
-    	retval = poll(poll_data, nfds, timeout_sec);
193
-	if (retval == -1) {
194
-	    if (errno == EINTR && !check_signals) {
168
+	unsigned int todo;
169
+	unsigned char *current;
170
+ 
171
+    todo = count;
172
+    current = (unsigned char *) buff;
173
+ 
174
+    do {
175
+	retval = write(fd, current, todo);
176
+	if (retval < 0) {
177
+	    if (errno == EINTR) {
195 178
 		continue;
196 179
 	    }
197
-	    if (nfds>1)
198
-		free(poll_data);
199 180
 	    return -1;
200 181
 	}
201
-	if (nfds>1) {
202
-	    if (retval>0) {
203
-		for (i=0; i<nfds; i++) {
204
-		    if (poll_data[i].revents) {
205
-			retval = i+1;
206
-			break;
207
-		    }
208
-		}
209
-	    }
210
-	    free(poll_data);
211
-	}
212
-	return retval;
213
-    }
214
-
215
-#else
216
-	fd_set rfds;
217
-	struct timeval tv;
218
-	int maxfd = 0;
219
-
220
-    for (i=0; i<nfds; i++) {
221
-#ifndef	C_WINDOWS
222
-	if (fds[i] >= DEFAULT_FD_SETSIZE) {
223
-	    return -1;
224
-	}
225
-#endif
226
-	if (fds[i] > maxfd)
227
-	    maxfd = fds[i];
228
-    }
229
-
230
-    while (1) {
231
-	FD_ZERO(&rfds);
232
-	for (i=0; i<nfds; i++)
233
-	    FD_SET(fds[i], &rfds);
234
-	tv.tv_sec = timeout_sec;
235
-	tv.tv_usec = 0;
182
+	todo -= retval;
183
+	current += retval;
184
+    } while (todo > 0);
185
+ 
186
+    return count;
187
+}
236 188
 
237
-	retval = select(maxfd+1, &rfds, NULL, NULL,
238
-			(timeout_sec>0 ? &tv : NULL));
239
-	if (retval == -1) {
240
-	    if (errno == EINTR && !check_signals) {
241
-		continue;
242
-	    }
243
-	    return -1;
244
-	}
245
-	if ((nfds>1) && (retval>0)) {
246
-	    for (i=0; i<nfds; i++) {
247
-		if (FD_ISSET(fds[i],&rfds)) {
248
-		    retval = i+1;
249
-		    break;
250
-		}
251
-	    }
252
-	}
253
-	return retval;
189
+static int realloc_polldata(struct fd_data *data)
190
+{
191
+#ifdef HAVE_POLL
192
+    if (data->poll_data_nfds == data->nfds)
193
+	return 0;
194
+    if (data->poll_data)
195
+	free(data->poll_data);
196
+    data->poll_data = malloc(data->nfds*sizeof(*data->poll_data));
197
+    if (!data->poll_data) {
198
+	logg("!realloc_polldata: Memory allocation failed for poll_data\n");
199
+	return -1;
254 200
     }
201
+    data->poll_data_nfds = data->nfds;
255 202
 #endif
256
-
257
-    return -1;
203
+    return 0;
258 204
 }
259 205
 
260 206
 int poll_fd(int fd, int timeout_sec, int check_signals)
261 207
 {
262
-    return poll_fds(&fd, 1, timeout_sec, check_signals);
208
+    int ret;
209
+    struct fd_data fds;
210
+
211
+    memset(&fds, 0, sizeof(fds));
212
+    if (fds_add(&fds, fd, 1) == -1)
213
+	return -1;
214
+    ret = fds_poll_recv(&fds, timeout_sec, check_signals);
215
+    fds_free(&fds);
216
+    return ret;
217
+}
218
+
219
+static void cleanup_fds(struct fd_data *data)
220
+{
221
+    struct fd_buf *newbuf;
222
+    unsigned i,j;
223
+    for (i=0,j=0;i < data->nfds; i++) {
224
+	if (data->buf[i].fd < 0) {
225
+	    if (data->buf[i].buffer)
226
+		free(data->buf[i].buffer);
227
+	    continue;
228
+	}
229
+	if (i != j)
230
+	    data->buf[j] = data->buf[i];
231
+	j++;
232
+    }
233
+    for (i = j ; i < data->nfds; i++)
234
+	data->buf[i].fd = -1;
235
+    data->nfds = j;
236
+    /* Shrink buffer */
237
+    newbuf = realloc(data->buf, j*sizeof(*newbuf));
238
+    if (newbuf)
239
+	data->buf = newbuf;/* non-fatal if shrink fails */
263 240
 }
264 241
 
265
-int is_fd_connected(int fd)
242
+static int read_fd_data(struct fd_buf *buf)
266 243
 {
267
-#ifdef HAVE_POLL
268
-	struct pollfd poll_data[1];
269
-	int count;
244
+    ssize_t n;
270 245
 
271
-    poll_data[0].fd = fd;
272
-    poll_data[0].events = POLLIN;
273
-    poll_data[0].revents = 0;
246
+    buf->got_newdata=1;
247
+    if (!buf->buffer) /* listen-only socket */
248
+	return 1;
274 249
 
275
-    if ((count=poll(poll_data, 1, 0)) == -1) {
276
-    	if (errno == EINTR) {
277
-		return 1;
278
-	}
279
-	return 0;
280
-    }
281
-    if (count == 0) {
282
-    	return 1;
283
-    }
284
-    if (poll_data[0].revents & POLLHUP) {
285
-	return 0;
286
-    }
287
-    if ((poll_data[0].revents & POLLIN) && (ioctl(fd, FIONREAD, &count) == 0)) {
288
-    	if (count == 0) {
289
-		return 0;
290
-	}
291
-    }
292
-    return 1;
250
+    if (buf->off >= buf->bufsize)
251
+	return -1;
293 252
 
253
+   /* Read the pending packet, it may contain more than one command, but
254
+    * that is to the cmdparser to handle. 
255
+    * It will handle 1st command, and then move leftover to beginning of buffer
256
+    */
257
+#ifdef HAVE_FD_PASSING
258
+  {
259
+      struct msghdr msg;
260
+      struct cmsghdr *cmsg;
261
+      unsigned char buff[CMSG_SPACE(sizeof(int))];
262
+      struct iovec iov[1];
263
+
264
+      if (buf->recvfd != -1) {
265
+	  close(buf->recvfd);
266
+	  buf->recvfd = -1;
267
+      }
268
+      memset(&msg, 0, sizeof(msg));
269
+      iov[0].iov_base = buf->buffer + buf->off;
270
+      iov[0].iov_len = buf->bufsize - buf->off;
271
+      msg.msg_iov = iov;
272
+      msg.msg_iovlen = 1;
273
+      msg.msg_control = buff;
274
+      msg.msg_controllen = sizeof(buff);
275
+
276
+      n = recvmsg(buf->fd, &msg, 0);
277
+      if (n < 0)
278
+	  return -1;
279
+      if ((msg.msg_flags & MSG_TRUNC) || (msg.msg_flags & MSG_CTRUNC)) {
280
+	  logg("!control message truncated");
281
+	  return -1;
282
+      }
283
+      for (cmsg = CMSG_FIRSTHDR(&msg); cmsg != NULL;
284
+	   cmsg = CMSG_NXTHDR(&msg, cmsg)) {
285
+	  if (cmsg->cmsg_len == CMSG_LEN(sizeof(int)) &&
286
+	      cmsg->cmsg_level == SOL_SOCKET &&
287
+	      cmsg->cmsg_type == SCM_RIGHTS) {
288
+	      if (buf->recvfd != -1) {
289
+		  logg("^Unclaimed file descriptor received. closing\n");
290
+		  close(buf->recvfd);
291
+	      }
292
+	      buf->recvfd = *(int *)CMSG_DATA(cmsg);
293
+	      logg("*RECVMSG: got FD %d\n", buf->recvfd);
294
+	  }
295
+      }
296
+  }
294 297
 #else
295
-	fd_set rfds;
296
-	struct timeval tv;
297
-	char buff[1];
298
+   n = recv(buf->fd, buf->buffer + buf->off, buf->bufsize - buf->off,0);
299
+   if (n < 0)
300
+       return -1;
301
+#endif
302
+   buf->off += n;
303
+   return n;
304
+}
298 305
 
299
-#ifndef	C_WINDOWS
300
-    if (fd >= DEFAULT_FD_SETSIZE) {
301
-        return 1;
306
+static int buf_init(struct fd_buf *buf, int listen_only)
307
+{
308
+    buf->off = 0;
309
+    buf->got_newdata = 0;
310
+    buf->recvfd = -1;
311
+    buf->mode = MODE_COMMAND;
312
+    buf->id = 0;
313
+    buf->dumpfd = -1;
314
+    buf->chunksize = 0;
315
+    buf->quota = 0;
316
+    buf->dumpname = NULL;
317
+    buf->group = NULL;
318
+    buf->term = '\0';
319
+    if (!listen_only) {
320
+	if (!buf->buffer) {
321
+	    buf->bufsize = PATH_MAX+8;
322
+	    /* plus extra space for a \0 so we can make sure every command is \0
323
+	     * terminated */
324
+	    if (!(buf->buffer = malloc(buf->bufsize + 1))) {
325
+		logg("!add_fd: Memory allocation failed for command buffer\n");
326
+		return -1;
327
+	    }
328
+	}
329
+    } else {
330
+	if (buf->buffer)
331
+	    free(buf->buffer);
332
+	buf->bufsize = 0;
333
+	buf->buffer = NULL;
302 334
     }
303
-#endif
335
+    return 0;
336
+}
304 337
 
305
-    FD_ZERO(&rfds);
306
-    FD_SET(fd, &rfds);
307
-    tv.tv_sec = 0;
308
-    tv.tv_usec = 0;
309
-    if (select(fd+1, &rfds, NULL, NULL, &tv) <= 0) {
310
-	return 1;
338
+int fds_add(struct fd_data *data, int fd, int listen_only)
339
+{
340
+    struct fd_buf *buf;
341
+    unsigned n;
342
+    if (fd < 0) {
343
+	logg("!add_fd: invalid fd passed to add_fd\n");
344
+	return -1;
311 345
     }
312
-    if (FD_ISSET(fd, &rfds)) {
313
-	if (recv(fd, buff, 1, MSG_PEEK) != 1) {
346
+    /* we may already have this fd, if
347
+     * the old FD got closed, and the kernel reused the FD */
348
+    for (n = 0; n < data->nfds; n++)
349
+	if (data->buf[n].fd == fd) {
350
+	    /* clear stale data in buffer */
351
+	    if (buf_init(&data->buf[n], listen_only) < 0)
352
+		return -1;
314 353
 	    return 0;
315 354
 	}
355
+
356
+    n++;
357
+    buf = realloc(data->buf, n*sizeof(*buf));
358
+    if (!buf) {
359
+	logg("!add_fd: Memory allocation failed for fd_buf\n");
360
+	return -1;
316 361
     }
317
-    return 1;
318
-#endif
362
+    data->buf = buf;
363
+    data->nfds = n;
364
+    data->buf[n-1].buffer = NULL;
365
+    if (buf_init(&data->buf[n-1], listen_only) < 0)
366
+	return -1;
367
+    data->buf[n-1].fd = fd;
368
+    return 0;
319 369
 }
320 370
 
321
-/* Function: writen
322
-	Try hard to write the specified number of bytes
323
-*/
324
-int writen(int fd, void *buff, unsigned int count)
371
+void fds_remove(struct fd_data *data, int fd)
325 372
 {
326
-	int retval;
327
-	unsigned int todo;
328
-	unsigned char *current;
329
- 
330
-    todo = count;
331
-    current = (unsigned char *) buff;
332
- 
333
-    do {
334
-	retval = write(fd, current, todo);
335
-	if (retval < 0) {
336
-	    if (errno == EINTR) {
337
-		continue;
373
+    size_t i;
374
+    pthread_mutex_lock(&data->buf_mutex);
375
+    if (data->buf) {
376
+	for (i=0;i<data->nfds;i++) {
377
+	    if (data->buf[i].fd == fd) {
378
+		data->buf[i].fd = -1;
379
+		break;
338 380
 	    }
339
-	    return -1;
340 381
 	}
341
-	todo -= retval;
342
-	current += retval;
343
-    } while (todo > 0);
344
- 
345
-    return count;
382
+    }
383
+    pthread_mutex_unlock(&data->buf_mutex);
346 384
 }
347 385
 
348
-/* FD Support Submitted by Richard Lyons <frob-clamav*webcentral.com.au> */
349
-/*
350
-   This procedure does timed clamd command and delimited input processing.  
351
-   It is complex for several reasons:
352
-       2) Newline delimited commands are indicated by a command which is prefixed by an 'n' character.  
353
-          This character serves to indicate that the command will contain a newline which will cause
354
-          command data to be read until the command input buffer is full or a newline is encountered.
355
-          Once the delimiter is encountered, the data is returned without the prefixing 'n' byte.
356
-       3) Legacy clamd clients presented commands which may or may not have been delimited by a newline.
357
-          If a command happens to be delimted by a newline, then only that command (and its newline) is
358
-          read and passed back, otherwise, all data read (in a single read) which fits in the specified
359
-          buffer will be returned.
360
-*/
361
-int readsock(int sockfd, char *buf, size_t size, unsigned char delim, int timeout_sec, int force_delim, int read_command)
386
+#ifndef	C_WINDOWS
387
+#define	closesocket(s)	close(s)
388
+#endif
389
+#define BUFFSIZE 1024
390
+/* Wait till data is available to be read on any of the fds,
391
+ * read available data on all fds, and mark them as appropriate.
392
+ * One of the fds should be a pipe, used by the accept thread to wake us.
393
+ * timeout is specified in seconds, if check_signals is non-zero, then
394
+ * poll_recv_fds() will return upon receipt of a signal, even if no data
395
+ * is received on any of the sockets.
396
+ * Must be called with buf_mutex held.
397
+ */
398
+/* TODO: handle ReadTimeout */
399
+int fds_poll_recv(struct fd_data *data, int timeout, int check_signals)
362 400
 {
363
-	ssize_t n;
364
-	size_t boff = 0;
365
-	char *pdelim;
366
-	time_t starttime, timenow;
367
-
368
-    time(&starttime);
369
-    while(1) {
370
-	time(&timenow);
371
-	switch(poll_fd(sockfd, (timeout_sec && ((timeout_sec-(timenow-starttime)) > 0)) ? timeout_sec-(timenow-starttime) : 0, 0)) {
372
-	    case 0: /* timeout */
373
-		return -2;
374
-	    case -1:
375
-		if(errno == EINTR)
401
+    unsigned fdsok = data->nfds;
402
+    size_t i;
403
+    int retval;
404
+
405
+    /* we must have at least one fd, the control fd! */
406
+    if (!data->nfds)
407
+	return 0;
408
+    for (i=0;i < data->nfds;i++)
409
+	data->buf[i].got_newdata = 0;
410
+#ifdef HAVE_POLL
411
+    /* Use poll() if available, preferred because:
412
+     *  - can poll any number of FDs
413
+     *  - can notify of both data available / socket disconnected events
414
+     *  - when it says POLLIN it is guaranteed that a following recv() won't
415
+     *  block (select may say that data is available to read, but a following 
416
+     *  recv() may still block according to the manpage
417
+     */
418
+
419
+    if (realloc_polldata(data) == -1)
420
+	return -1;
421
+    if (timeout > 0) {
422
+	/* seconds to ms */
423
+	timeout *= 1000;
424
+    }
425
+    for (i=0;i < data->nfds;i++) {
426
+	data->poll_data[i].fd = data->buf[i].fd;
427
+	data->poll_data[i].events = POLLIN;
428
+	data->poll_data[i].revents = 0;
429
+    }
430
+    do {
431
+	int n = data->nfds;
432
+
433
+	pthread_mutex_unlock(&data->buf_mutex);
434
+	retval = poll(data->poll_data, n, timeout);
435
+	pthread_mutex_lock(&data->buf_mutex);
436
+
437
+	if (retval > 0) {
438
+	    fdsok = 0;
439
+	    /* nfds may change during poll, but not
440
+	     * poll_data_nfds */
441
+	    for (i=0;i < data->poll_data_nfds; i++) {
442
+		short revents;
443
+		if (data->buf[i].fd < 0)
376 444
 		    continue;
377
-		return -1;
445
+		if (data->buf[i].fd != data->poll_data[i].fd) {
446
+		    /* should never happen */
447
+		    logg("!poll_recv_fds FD mismatch\n");
448
+		    continue;
449
+		}
450
+		revents = data->poll_data[i].revents;
451
+		if (revents & (POLLIN|POLLHUP)) {
452
+		    logg("*POLL: POLLIN|POLLHUP on fd %d\n",data->poll_data[i].fd);
453
+		}
454
+		if (revents & POLLIN) {
455
+		    int ret = read_fd_data(&data->buf[i]);
456
+		    /* Data available to be read */
457
+		    if (ret == -1)
458
+			revents |= POLLERR;
459
+		    else if (!ret)
460
+			revents = POLLHUP;
461
+		}
462
+
463
+		if (revents & (POLLHUP | POLLERR | POLLNVAL)) {
464
+		    if (revents & (POLLHUP| POLLNVAL)) {
465
+			/* remote disconnected */
466
+			logg("^poll_recv_fds: Client disconnected (FD %d)\n",
467
+			     data->poll_data[i].fd);
468
+		    } else {
469
+			/* error on file descriptor */
470
+			logg("!poll_recv_fds: Error condition on fd %d\n",
471
+			     data->poll_data[i].fd);
472
+		    }
473
+		    data->buf[i].got_newdata = -1;
474
+		} else {
475
+		    fdsok++;
476
+		}
477
+	    }
378 478
 	}
379
-	break;
380
-    }
381
-    n = recv(sockfd, buf, size, MSG_PEEK);
382
-    if(n < 0)
383
-	return -1;
384
-    if(read_command) {
385
-	if((n >= 1) && (buf[0] == 'n')) { /* Newline delimited command */
386
-	    force_delim = 1;
387
-	    delim = '\n';
479
+    } while (retval == -1 && !check_signals && errno == EINTR);
480
+#else
481
+    fd_set rfds;
482
+    struct timeval tv;
483
+    int maxfd = -1;
484
+
485
+    for (i=0;i < data->nfds; i++) {
486
+	int fd = data->buf[i].fd;
487
+	if (fd >= FD_SETSIZE) {
488
+	    logg ("!poll_recv_fds: file descriptor is not valid for FD_SET\n");
489
+	    return -1;
388 490
 	}
491
+
492
+	maxfd = MAX(maxfd, fd);
389 493
     }
390
-    while(boff < size) {
391
-	if(force_delim) {
392
-	    pdelim = memchr(buf, delim, n+boff);
393
-	    if(pdelim) {
394
-		n = recv(sockfd, buf+boff, pdelim-buf+1-boff, 0);
395
-		break;
396
-	    } else {
397
-		n = recv(sockfd, buf+boff, n, 0);
398
-		if(n < 0)
399
-		    return -1;
400
-		if((boff+n) == size)
401
-		    break;
402
-		boff += n;
494
+
495
+    do {
496
+	FD_ZERO(&rfds);
497
+	for(i=0;i < data->nfds;i++) {
498
+	    int fd = data->buf[i].fd;
499
+	    if (fd >= 0)
500
+		FD_SET(fd, &rfds);
501
+	}
502
+	tv.tv_sec = timeout;
503
+	tv.tv_usec = 0;
504
+
505
+	pthread_mutex_unlock(&data->buf_mutex);
506
+	retval = select(maxfd+1, &rfds, NULL, NULL, timeout > 0 ? &tv : NULL);
507
+	pthread_mutex_lock(&data->buf_mutex);
508
+	if (retval > 0) {
509
+	    fdsok = data->nfds;
510
+	    for (i=0; i < data->nfds; i++) {
511
+		if (data->buf[i].fd < 0) {
512
+		    fdsok--;
513
+		    continue;
514
+		}
515
+		if (FD_ISSET(data->buf[i].fd, &rfds)) {
516
+		    int ret = read_fd_data(&data->buf[i]);
517
+		    if (ret == -1 || !ret) {
518
+			if (ret == -1)
519
+			    logg("!poll_recv_fds: Error condition on fd %d\n",
520
+				 data->buf[i].fd);
521
+			else
522
+			    logg("^poll_recv_fds: Client disconnected\n");
523
+			data->buf[i].got_newdata = -1;
524
+		    }
525
+		}
403 526
 	    }
404
-	} else {
405
-	    pdelim = memchr(buf, delim, n+boff);
406
-	    if(pdelim)
407
-		n = recv(sockfd, buf+boff, pdelim-buf+1-boff, 0);
408
-	    else
409
-		n = recv(sockfd, buf+boff, size-boff, 0);
410
-	    break;
411 527
 	}
412
-	while(1) {
413
-	    time(&timenow);
414
-	    switch(poll_fd(sockfd, ((timeout_sec-(timenow-starttime)) > 0) ? timeout_sec-(timenow-starttime) : 0, 0)) {
415
-		case 0: /* timeout */
416
-		    return -2;
417
-		case -1:
418
-		    if(errno == EINTR)
419
-			continue;
420
-		    return -1;
528
+	if (retval < 0 && errno == EBADF) {
529
+	    /* unlike poll(),  select() won't tell us which FD is bad, so
530
+	     * we have to check them one by one. */
531
+	    tv.tv_sec = 0;
532
+	    tv.tv_usec = 0;
533
+	    /* with tv == 0 it doesn't check for EBADF */
534
+	    FD_ZERO(&rfds);
535
+	    for (i=0; i< data->nfds; i++) {
536
+		if (data->buf[i].fd == -1)
537
+		    continue;
538
+		FD_SET(data->buf[i].fd, &rfds);
539
+		do {
540
+		    retval = select(data->buf[i].fd+1, &rfds, NULL, NULL, &tv);
541
+		} while (retval == -1 && errno == EINTR);
542
+		if (retval == -1) {
543
+		    data->buf[i].fd = -1;
544
+		} else {
545
+		    FD_CLR(data->buf[i].fd, &rfds);
546
+		}
421 547
 	    }
422
-	    break;
548
+	    retval = -1;
549
+	    errno = EINTR;
550
+	    continue;
423 551
 	}
424
-        n = recv(sockfd, buf+boff, size-boff, MSG_PEEK);
425
-	if(n < 0)
426
-	    return -1;
427
-	if(n == 0)
428
-	    break;
552
+    } while (retval == -1 && !check_signals && errno == EINTR);
553
+#endif
554
+
555
+    if (retval == -1 && errno != EINTR) {
556
+	char buff[BUFFSIZE + 1];
557
+#ifdef HAVE_STRERROR_R
558
+	strerror_r(errno, buff, BUFFSIZE);
559
+#else
560
+	buff[0] = '\0';
561
+#endif
562
+#ifdef HAVE_POLL
563
+	logg("!poll_recv_fds: poll failed: %s\n", buff);
564
+#else
565
+	logg("!poll_recv_fds: select failed: %s\n", buff);
566
+#endif
429 567
     }
430
-    if(n < 0)
431
-	return -1;
432
-    n += boff;
433
-    if(read_command) {
434
-	if((n >= 1) && (buf[0] == 'n')) { /* Need to strip leading 'n' from command to attain standard command */
435
-	    --n;
436
-	    memcpy(buf, buf+1, n);
437
-	    buf[n] = '\0';
568
+
569
+    /* Remove closed / error fds */
570
+    if (fdsok != data->poll_data_nfds)
571
+	cleanup_fds(data);
572
+    return retval;
573
+}
574
+
575
+void fds_free(struct fd_data *data)
576
+{
577
+    unsigned i;
578
+    pthread_mutex_lock(&data->buf_mutex);
579
+    for (i=0;i < data->nfds;i++) {
580
+	if (data->buf[i].buffer) {
581
+	    free(data->buf[i].buffer);
438 582
 	}
439
-	return !strncmp(buf, "FD", 2) ? -1 : n; /* an explicit FD command is invalid */
440 583
     }
441
-    return n;
584
+    if (data->buf)
585
+	free(data->buf);
586
+#ifdef HAVE_POLL
587
+    if (data->poll_data)
588
+	free(data->poll_data);
589
+#endif
590
+    data->buf = NULL;
591
+    data->nfds = 0;
592
+    pthread_mutex_unlock(&data->buf_mutex);
442 593
 }
... ...
@@ -25,13 +25,51 @@
25 25
 
26 26
 #include <stdlib.h>
27 27
 #include "shared/optparser.h"
28
+#include "thrmgr.h"
29
+#include "cltypes.h"
30
+
31
+enum mode {
32
+    MODE_COMMAND,
33
+    MODE_STREAM,
34
+    MODE_WAITREPLY,
35
+    MODE_WAITANCILL
36
+};
37
+
38
+struct fd_buf {
39
+    unsigned char *buffer;
40
+    size_t bufsize;
41
+    size_t off;
42
+    int fd;
43
+    char term;
44
+    int got_newdata; /* 0: no, 1: yes, -1: error */
45
+    int recvfd;
46
+    /* TODO: these fields don't belong here, there are identical fields in conn
47
+     * too that don't belong there either. */
48
+    enum mode mode;
49
+    int id;
50
+    int dumpfd;
51
+    uint32_t chunksize;
52
+    long quota;
53
+    char *dumpname;
54
+    jobgroup_t *group;
55
+};
56
+
57
+struct fd_data {
58
+    pthread_mutex_t buf_mutex; /* protects buf and nfds */
59
+    struct fd_buf *buf;
60
+    size_t nfds;
61
+#ifdef HAVE_POLL
62
+    struct pollfd *poll_data;
63
+    size_t poll_data_nfds;
64
+#endif
65
+};
28 66
 
29
-int poll_fds(int *fds, int nfds, int timeout_sec, int check_signals);
30 67
 int poll_fd(int fd, int timeout_sec, int check_signals);
31
-int is_fd_connected(int fd);
32 68
 void virusaction(const char *filename, const char *virname, const struct optstruct *opts);
33 69
 int writen(int fd, void *buff, unsigned int count);
34
-
35
-int readsock(int sockfd, char *buf, size_t size, unsigned char delim, int timeout_sec, int force_delim, int read_command);
70
+int fds_add(struct fd_data *data, int fd, int listen_only);
71
+void fds_remove(struct fd_data *data, int fd);
72
+int fds_poll_recv(struct fd_data *data, int timeout, int check_signals);
73
+void fds_free(struct fd_data *data);
36 74
 
37 75
 #endif
... ...
@@ -65,444 +65,252 @@
65 65
 #include "shared.h"
66 66
 #include "network.h"
67 67
 #include "thrmgr.h"
68
+#include "server.h"
68 69
 
69 70
 #ifdef C_LINUX
70 71
 dev_t procdev; /* /proc device */
71 72
 #endif
72 73
 
73
-extern int progexit;
74
-
75 74
 #ifndef	C_WINDOWS
76 75
 #define	closesocket(s)	close(s)
77 76
 #endif
78 77
 
79
-struct multi_tag {
80
-    int sd;
81
-    unsigned int options;
82
-    const struct optstruct *opts;
83
-    char *fname;
84
-    const struct cl_engine *engine;
85
-};
78
+extern int progexit;
79
+extern time_t reloaded_time;
80
+extern pthread_mutex_t reload_mutex;
86 81
 
87
-static int checksymlink(const char *path)
82
+#define BUFFSIZE 1024
83
+int scan_callback(struct stat *sb, char *filename, const char *msg, enum cli_ftw_reason reason, struct cli_ftw_cbdata *data)
88 84
 {
89
-	struct stat statbuf;
90
-
91
-    if(stat(path, &statbuf) == -1)
92
-	return -1;
93
-
94
-    if(S_ISDIR(statbuf.st_mode))
95
-	return 1;
96
-
97
-    if(S_ISREG(statbuf.st_mode))
98
-	return 2;
99
-
100
-    return 0;
101
-}
85
+    struct scan_cb_data *scandata = data->data;
86
+    const char *virname;
87
+    int ret;
88
+    int type = scandata->type;
89
+    const struct optstruct *opt;
90
+
91
+    if (thrmgr_group_need_terminate(scandata->conn->group)) {
92
+	logg("^Client disconnected while scanjob was active\n");
93
+	if (reason == visit_file)
94
+	    free(filename);
95
+	return CL_BREAK;
96
+    }
97
+    scandata->total++;
98
+    switch (reason) {
99
+	case error_mem:
100
+	    if (msg)
101
+		logg("!Memory allocation failed during cli_ftw() on %s\n",
102
+		     msg);
103
+	    else
104
+		logg("!Memory allocation failed during cli_ftw()\n");
105
+	    scandata->errors++;
106
+	    return CL_EMEM;
107
+	case error_stat:
108
+	    if (msg == scandata->toplevel_path)
109
+		conn_reply_errno(scandata->conn, msg, "lstat() failed:");
110
+	    logg("^lstat() failed on: %s\n", msg);
111
+	    scandata->errors++;
112
+	    return CL_SUCCESS;
113
+	case warning_skipped_dir:
114
+	    logg("^Directory recursion limit reached, skipping %s\n",
115
+		     msg);
116
+	    return CL_SUCCESS;
117
+	case warning_skipped_link:
118
+	    logg("*Skipping symlink: %s\n", msg);
119
+	    return CL_SUCCESS;
120
+	case warning_skipped_special:
121
+	    if (msg == scandata->toplevel_path)
122
+		conn_reply(scandata->conn, msg, "Not supported file type", "ERROR");
123
+	    logg("*Not supported file type: %s\n", msg);
124
+	    return CL_SUCCESS;
125
+	case visit_directory_toplev:
126
+	    return CL_SUCCESS;
127
+	case visit_file:
128
+	    break;
129
+    }
102 130
 
103
-static int dirscan(const char *dirname, const char **virname, unsigned long int *scanned, const struct cl_engine *engine, unsigned int options, const struct optstruct *opts, int odesc, unsigned int *reclev, unsigned int type, threadpool_t *multi_pool)
104
-{
105
-	DIR *dd;
106
-	struct dirent *dent;
107
-#if defined(HAVE_READDIR_R_3) || defined(HAVE_READDIR_R_2)
108
-	union {
109
-	    struct dirent d;
110
-	    char b[offsetof(struct dirent, d_name) + NAME_MAX + 1];
111
-	} result;
131
+    /* check whether the file is excluded */
132
+#ifdef C_LINUX
133
+    if(procdev && sb && (sb->st_dev == procdev)) {
134
+	free(filename);
135
+	return CL_SUCCESS;
136
+    }
112 137
 #endif
113
-	struct stat statbuf;
114
-	char *fname;
115
-	int ret = 0, scanret = 0;
116
-	unsigned int maxdirrec = 0;
117
-	struct multi_tag *scandata;
118
-	const struct optstruct *opt;
119
-
120
-
121
-    if((opt = optget(opts, "ExcludePath"))->enabled) {
122
-	while(opt) {
123
-	    if(match_regex(dirname, opt->strarg) == 1) {
124
-		mdprintf(odesc, "%s: Excluded\n", dirname);
125
-		return 0;
126
-	    }
127
-	    opt = (struct optstruct *) opt->nextarg;
138
+    if((opt = optget(scandata->opts, "ExcludePath"))->enabled) {
139
+	/* TODO: perhaps multiscan should skip this check? 
140
+	 * This should work unless the user is doing something stupid like
141
+	 * MULTISCAN / */
142
+	if(match_regex(filename, opt->strarg) == 1) {
143
+	    if (type != TYPE_MULTISCAN)
144
+		conn_reply_single(scandata->conn, filename, "Excluded");
145
+	    free(filename);
146
+	    return CL_SUCCESS;
128 147
 	}
129 148
     }
130 149
 
131
-    maxdirrec = optget(opts, "MaxDirectoryRecursion")->numarg;
132
-    if(maxdirrec) {
133
-	if(*reclev > maxdirrec) {
134
-	    logg("*Directory recursion limit exceeded at %s\n", dirname);
135
-	    return 0;
136
-	}
137
-	(*reclev)++;
150
+    if(sb && sb->st_size == 0) { /* empty file */
151
+	if (msg == scandata->toplevel_path)
152
+	    conn_reply_single(scandata->conn, filename, "Empty file");
153
+	free(filename);
154
+	return CL_SUCCESS;
138 155
     }
139 156
 
140
-    if((dd = opendir(dirname)) != NULL) {
141
-#ifdef HAVE_READDIR_R_3
142
-	while(!readdir_r(dd, &result.d, &dent) && dent) {
143
-#elif defined(HAVE_READDIR_R_2)
144
-	while((dent = (struct dirent *) readdir_r(dd, &result.d))) {
145
-#else
146
-	while((dent = readdir(dd))) {
147
-#endif
148
-	    if (!is_fd_connected(odesc)) {
149
-		logg("Client disconnected\n");
150
-		closedir(dd);
151
-		return 1;
152
-	    }
153
-
154
-            if(progexit) {
155
-		closedir(dd);
156
-		return 1;
157
-	    }
158
-
159
-#if	(!defined(C_INTERIX)) && (!defined(C_WINDOWS))
160
-	    if(dent->d_ino)
161
-#endif
162
-	    {
163
-		if(strcmp(dent->d_name, ".") && strcmp(dent->d_name, "..")) {
164
-		    /* build the full name */
165
-		    fname = (char *) malloc(strlen(dirname) + strlen(dent->d_name) + 2);
166
-                    if(!fname) {
167
-			logg("!Can't allocate memory for fname\n");
168
-			closedir(dd);
169
-			return -2;
170
-		    }
171
-		    sprintf(fname, "%s/%s", dirname, dent->d_name);
172
-
173
-		    /* stat the file */
174
-		    if(lstat(fname, &statbuf) != -1) {
175
-			if((S_ISDIR(statbuf.st_mode) && !S_ISLNK(statbuf.st_mode)) || (S_ISLNK(statbuf.st_mode) && (checksymlink(fname) == 1) && optget(opts, "FollowDirectorySymlinks")->enabled)) {
176
-			    if(dirscan(fname, virname, scanned, engine, options, opts, odesc, reclev, type, multi_pool) == 1) {
177
-				free(fname);
178
-				closedir(dd);
179
-				return 1;
180
-			    }
181
-			    free(fname);
182
-			} else {
183
-			    if(S_ISREG(statbuf.st_mode) || (S_ISLNK(statbuf.st_mode) && (checksymlink(fname) == 2) && optget(opts, "FollowFileSymlinks")->enabled)) {
184
-
185
-#ifdef C_LINUX
186
-				if(procdev && (statbuf.st_dev == procdev))
187
-				    free(fname);
188
-				else
189
-#endif
190
-				{
191
-				    if(type == TYPE_MULTISCAN) {
192
-
193
-					scandata = (struct multi_tag *) malloc(sizeof(struct multi_tag));
194
-					if(!scandata) {
195
-					    logg("!Can't allocate memory for scandata\n");
196
-					    free(fname);
197
-					    closedir(dd);
198
-					    return -2;
199
-					}
200
-					scandata->sd = odesc;
201
-					scandata->options = options;
202
-					scandata->opts = opts;
203
-					scandata->fname = fname;
204
-					scandata->engine = engine;
205
-					if(!thrmgr_dispatch(multi_pool, scandata)) {
206
-					    logg("!thread dispatch failed for multi_pool (file %s)\n", fname);
207
-					    mdprintf(odesc, "ERROR: Can't scan file %s\n", fname);
208
-					    free(fname);
209
-					    free(scandata);
210
-					    closedir(dd);
211
-					    return 1;
212
-					}
213
-
214
-					pthread_mutex_lock(&multi_pool->pool_mutex);
215
-					while(!multi_pool->thr_idle) /* non-critical */ {
216
-						pthread_cond_wait(&multi_pool->idle_cond, &multi_pool->pool_mutex);
217
-					}
218
-					pthread_mutex_unlock(&multi_pool->pool_mutex);
219
-
220
-				    } else { /* CONTSCAN, SCAN */
221
-					thrmgr_setactivetask(fname, NULL);
222
-					scanret = cl_scanfile(fname, virname, scanned, engine, options);
223
-					thrmgr_setactivetask(NULL, NULL);
224
-
225
-					if(scanret == CL_VIRUS) {
226
-
227
-					    mdprintf(odesc, "%s: %s FOUND\n", fname, *virname);
228
-					    logg("~%s: %s FOUND\n", fname, *virname);
229
-					    virusaction(fname, *virname, opts);
230
-					    if(type == TYPE_SCAN) {
231
-						closedir(dd);
232
-						free(fname);
233
-						return 1;
234
-					    } else /* CONTSCAN */
235
-						ret = 2;
236
-
237
-					} else if(scanret != CL_CLEAN) {
238
-					    mdprintf(odesc, "%s: %s ERROR\n", fname, cl_strerror(scanret));
239
-					    logg("~%s: %s ERROR\n", fname, cl_strerror(scanret));
240
-					    if(scanret == CL_EMEM) {
241
-						closedir(dd);
242
-						free(fname);
243
-						return -2;
244
-					    }
245
-
246
-					} else if(logok) {
247
-					    logg("~%s: OK\n", fname);
248
-					}
249
-					free(fname);
250
-				    }
251
-				}
252
-			    } else {
253
-				    free(fname);
254
-			    }
255
-			}
256
-		    } else {
257
-			logg("^lstat failed on %s: %s\n", fname, strerror(errno)); 
258
-			free(fname);
259
-		    }
157
+    if (type == TYPE_MULTISCAN) {
158
+	client_conn_t *client_conn = (client_conn_t *) calloc(1, sizeof(struct client_conn_tag));
159
+	if(client_conn) {
160
+	    client_conn->scanfd = -1;
161
+	    client_conn->sd = scandata->odesc;
162
+	    client_conn->filename = filename;
163
+	    client_conn->cmdtype = COMMAND_MULTISCANFILE;
164
+	    client_conn->term = scandata->conn->term;
165
+	    client_conn->options = scandata->options;
166
+	    client_conn->opts = scandata->opts;
167
+	    client_conn->group = scandata->group;
168
+	    if(cl_engine_addref(scandata->engine)) {
169
+		logg("!cl_engine_addref() failed\n");
170
+		free(filename);
171
+		return CL_EMEM;
172
+	    } else {
173
+		client_conn->engine = scandata->engine;
174
+		pthread_mutex_lock(&reload_mutex);
175
+		client_conn->engine_timestamp = reloaded_time;
176
+		pthread_mutex_unlock(&reload_mutex);
177
+		if(!thrmgr_group_dispatch(scandata->thr_pool, scandata->group, client_conn)) {
178
+		    logg("!thread dispatch failed\n");
179
+		    free(filename);
180
+		    return CL_EMEM;
260 181
 		}
261 182
 	    }
183
+	} else {
184
+	    logg("!Can't allocate memory for client_conn\n");
185
+	    scandata->errors++;
186
+	    free(filename);
187
+	    return CL_EMEM;
262 188
 	}
263
-	closedir(dd);
264
-    } else {
265
-	return -1;
189
+	return CL_SUCCESS;
266 190
     }
267 191
 
268
-    (*reclev)--;
269
-    return ret;
270
-}
271
-
272
-static void multiscanfile(void *arg)
273
-{
274
-	struct multi_tag *tag = (struct multi_tag *) arg;
275
-	const char *virname;
276
-#ifndef	C_WINDOWS
277
-        sigset_t sigset;
278
-#endif
279
-	int ret;
280
-
281
-
282
-#ifndef	C_WINDOWS
283
-    /* ignore all signals */
284
-    sigfillset(&sigset);
285
-    pthread_sigmask(SIG_SETMASK, &sigset, NULL);
286
-#endif
287
-
288
-    thrmgr_setactivetask(tag->fname, "MULTISCANFILE");
289
-    ret = cl_scanfile(tag->fname, &virname, NULL, tag->engine, tag->options);
290
-    thrmgr_setactivetask(NULL, NULL);
291
-
292
-    if(ret == CL_VIRUS) {
293
-	mdprintf(tag->sd, "%s: %s FOUND\n", tag->fname, virname);
294
-	logg("~%s: %s FOUND\n", tag->fname, virname);
295
-	virusaction(tag->fname, virname, tag->opts);
296
-    } else if(ret != CL_CLEAN) {
297
-	mdprintf(tag->sd, "%s: %s ERROR\n", tag->fname, cl_strerror(ret));
298
-	logg("~%s: %s ERROR\n", tag->fname, cl_strerror(ret));
299
-    } else if(logok) {
300
-	logg("~%s: OK\n", tag->fname);
192
+    if (access(filename, R_OK)) {
193
+	conn_reply(scandata->conn, filename, "Access denied.", "ERROR");
194
+	logg("*Access denied: %s\n", filename);
195
+	scandata->errors++;
196
+	free(filename);
197
+	return CL_SUCCESS;
301 198
     }
302 199
 
303
-    free(tag->fname);
304
-    free(tag);
305
-    return;
306
-}
307
-
308
-int scan(const char *filename, unsigned long int *scanned, const struct cl_engine *engine, unsigned int options, const struct optstruct *opts, int odesc, unsigned int type)
309
-{
310
-	struct stat sb;
311
-	int ret = 0;
312
-	unsigned int reclev = 0;
313
-	const char *virname;
314
-	const struct optstruct *opt;
315
-	threadpool_t *multi_pool = NULL;
316
-
200
+    thrmgr_setactivetask(filename,
201
+			 type == TYPE_MULTISCAN ? "MULTISCANFILE" : NULL);
202
+    ret = cl_scanfile(filename, &virname, &scandata->scanned, scandata->engine, scandata->options);
203
+    thrmgr_setactivetask(NULL, NULL);
317 204
 
318
-    /* stat file */
319
-    if(lstat(filename, &sb) == -1) {
320
-	mdprintf(odesc, "%s: lstat() failed. ERROR\n", filename);
321
-	return -1;
205
+    if (ret == CL_VIRUS) {
206
+	scandata->infected++;
207
+	conn_reply(scandata->conn, filename, virname, "FOUND");
208
+	logg("~%s: %s FOUND\n", filename, virname);
209
+	virusaction(filename, virname, scandata->opts);
210
+    } else if (ret != CL_CLEAN) {
211
+	scandata->errors++;
212
+	conn_reply(scandata->conn, filename, cl_strerror(ret), "ERROR");
213
+	logg("~%s: %s ERROR\n", filename, cl_strerror(ret));
214
+    } else if (logok) {
215
+	logg("~%s: OK\n", filename);
322 216
     }
323 217
 
324
-    /* check permissions  */
325
-    if(access(filename, R_OK)) {
326
-	mdprintf(odesc, "%s: Access denied. ERROR\n", filename);
327
-	return -1;
328
-    }
218
+    free(filename);
219
+    if(ret == CL_EMEM) /* stop scanning */
220
+	return ret;
329 221
 
330
-    if((opt = optget(opts, "ExcludePath"))->enabled) {
331
-	if(match_regex(filename, opt->strarg) == 1) {
332
-	    mdprintf(odesc, "%s: Excluded\n", filename);
333
-	    return 0;
334
-	}
222
+    if (thrmgr_group_need_terminate(scandata->conn->group)) {
223
+	logg("^Client disconnected while scanjob was active\n");
224
+	return CL_BREAK;
335 225
     }
336
-
337
-    switch(sb.st_mode & S_IFMT) {
338
-#ifdef	S_IFLNK
339
-	case S_IFLNK:
340
-	    if(!optget(opts, "FollowFileSymlinks")->enabled)
341
-		break;
342
-	    /* else go to the next case */
343
-#endif
344
-	case S_IFREG: 
345
-	    if(sb.st_size == 0) { /* empty file */
346
-		mdprintf(odesc, "%s: Empty file\n", filename);
347
-		return 0;
348
-	    }
349
-#ifdef C_LINUX
350
-	    if(procdev && (sb.st_dev == procdev))
351
-		ret = CL_CLEAN;
352
-	    else
353
-#endif
354
-	    {
355
-		thrmgr_setactivetask(filename, NULL);
356
-		ret = cl_scanfile(filename, &virname, scanned, engine, options);
357
-		thrmgr_setactivetask(NULL, NULL);
358
-	    }
359
-
360
-	    if(ret == CL_VIRUS) {
361
-		mdprintf(odesc, "%s: %s FOUND\n", filename, virname);
362
-		logg("~%s: %s FOUND\n", filename, virname);
363
-		virusaction(filename, virname, opts);
364
-	    } else if(ret != CL_CLEAN) {
365
-		mdprintf(odesc, "%s: %s ERROR\n", filename, cl_strerror(ret));
366
-		logg("~%s: %s ERROR\n", filename, cl_strerror(ret));
367
-		if(ret == CL_EMEM)
368
-		    return -2;
369
-	    } else if (logok) {
370
-		logg("~%s: OK\n", filename);
371
-	    }
372
-	    break;
373
-	case S_IFDIR:
374
-	    if(type == TYPE_MULTISCAN) {
375
-		    int idletimeout = optget(opts, "IdleTimeout")->numarg;
376
-		    int max_threads = optget(opts, "MaxThreads")->numarg;
377
-
378
-		if((multi_pool = thrmgr_new(max_threads, idletimeout, multiscanfile)) == NULL) {
379
-		    logg("!thrmgr_new failed for multi_pool\n");
380
-		    mdprintf(odesc, "thrmgr_new failed for multi_pool ERROR\n");
381
-		    return -1;
382
-		}
383
-	    }
384
-
385
-	    ret = dirscan(filename, &virname, scanned, engine, options, opts, odesc, &reclev, type, multi_pool);
386
-
387
-	    if(multi_pool)
388
-		thrmgr_destroy(multi_pool);
389
-
390
-	    break;
391
-	default:
392
-	    mdprintf(odesc, "%s: Not supported file type. ERROR\n", filename);
393
-	    return -1;
226
+    if (type == TYPE_SCAN) {
227
+	/* virus -> break */
228
+	return ret;
394 229
     }
395 230
 
396
-    if(!ret)
397
-	mdprintf(odesc, "%s: OK\n", filename);
398
-
399
-    /* mdprintf(odesc, "\n"); */ /* Terminate response with a blank line boundary */
400
-    return ret;
231
+    /* keep scanning always */
232
+    return CL_SUCCESS;
401 233
 }
402 234
 
403
-/*
404
- * This function was readded by mbalmer@openbsd.org.  That is the reason
405
- * why it is so nicely formatted.
406
- */
407
-int scanfd(const int fd, unsigned long int *scanned,
408
-    const struct cl_engine *engine,
409
-    unsigned int options, const struct optstruct *opts, int odesc)  
235
+int scanfd(const int fd, const client_conn_t *conn, unsigned long int *scanned,
236
+	   const struct cl_engine *engine,
237
+	   unsigned int options, const struct optstruct *opts, int odesc, int stream)
410 238
 {
411 239
 	int ret;
412 240
 	const char *virname;
413 241
 	struct stat statbuf;
414 242
 	char fdstr[32];
415 243
 
416
-
417
-	if(fstat(fd, &statbuf) == -1)
418
-		return -1;
419
-
420
-	if(!S_ISREG(statbuf.st_mode))
244
+	if (stream)
245
+	    strncpy(fdstr, "stream", sizeof(fdstr));
246
+	else
247
+	    snprintf(fdstr, sizeof(fdstr), "fd[%d]", fd);
248
+	if(fstat(fd, &statbuf) == -1 || !S_ISREG(statbuf.st_mode)) {
249
+		conn_reply(conn, fdstr, "Not a regular file", "ERROR");
250
+		logg("%s: Not a regular file. ERROR\n", fdstr);
421 251
 		return -1;
422
-
423
-	snprintf(fdstr, sizeof(fdstr), "fd[%d]", fd);
252
+	}
424 253
 
425 254
 	thrmgr_setactivetask(fdstr, NULL);
426 255
 	ret = cl_scandesc(fd, &virname, scanned, engine, options);
427 256
 	thrmgr_setactivetask(NULL, NULL);
428 257
 
429 258
 	if(ret == CL_VIRUS) {
430
-	mdprintf(odesc, "%s: %s FOUND\n", fdstr, virname);
259
+		conn_reply(conn, fdstr, virname, "FOUND");
431 260
 		logg("%s: %s FOUND\n", fdstr, virname);
432 261
 		virusaction(fdstr, virname, opts);
433 262
 	} else if(ret != CL_CLEAN) {
434
-		mdprintf(odesc, "%s: %s ERROR\n", fdstr, cl_strerror(ret));
263
+		conn_reply(conn, fdstr, cl_strerror(ret), "ERROR");
435 264
 		logg("%s: %s ERROR\n", fdstr, cl_strerror(ret));
436 265
 	} else {
437
-		mdprintf(odesc, "%s: OK\n", fdstr);
266
+		conn_reply_single(conn, fdstr, "OK");
438 267
 		if(logok)
439 268
 			logg("%s: OK\n", fdstr);
440 269
 	}
441 270
 	return ret;
442 271
 }
443 272
 
444
-int scanstream(int odesc, unsigned long int *scanned, const struct cl_engine *engine, unsigned int options, const struct optstruct *opts)
273
+int scanstream(int odesc, unsigned long int *scanned, const struct cl_engine *engine, unsigned int options, const struct optstruct *opts, char term)
445 274
 {
446 275
 	int ret, sockfd, acceptd;
447 276
 	int tmpd, bread, retval, timeout, btread;
448
-	unsigned int port = 0, portscan = 1000, min_port, max_port;
449
-	unsigned long int size = 0, maxsize = 0;
450
-	short bound = 0, rnd_port_first = 1;
277
+	unsigned int port = 0, portscan, min_port, max_port;
278
+	unsigned long int quota = 0, maxsize = 0;
279
+	short bound = 0;
451 280
 	const char *virname;
452 281
 	char buff[FILEBUFF];
453 282
 	char peer_addr[32];
454 283
 	struct sockaddr_in server;
455 284
 	struct sockaddr_in peer;
456 285
 	socklen_t addrlen;
457
-	struct hostent he;
458
-	const struct optstruct *opt;
459 286
 	char *tmpname;
460 287
 
461 288
 
462
-    /* get min port */
463 289
     min_port = optget(opts, "StreamMinPort")->numarg;
464
-    if(min_port < 1024 || min_port > 65535)
465
-	min_port = 1024;
466
-
467
-    /* get max port */
468 290
     max_port = optget(opts, "StreamMaxPort")->numarg;
469
-    if(max_port < min_port || max_port > 65535)
470
-	max_port = 65535;
471
-
472
-    /* bind to a free port */
473
-    while(!bound && --portscan) {
474
-	if(rnd_port_first) {
475
-	    /* try a random port first */
476
-	    port = min_port + cli_rndnum(max_port - min_port);
477
-	    rnd_port_first = 0;
478
-	} else {
479
-	    /* try the neighbor ports */
480
-	    if(--port < min_port)
481
-		port=max_port;
482
-	}
291
+
292
+    /* search for a free port to bind to */
293
+    port = cli_rndnum(max_port - min_port);
294
+    bound = 0;
295
+    for (portscan = 0; portscan < 1000; portscan++) {
296
+	port = (port - 1) % (max_port - min_port + 1);
483 297
 
484 298
 	memset((char *) &server, 0, sizeof(server));
485 299
 	server.sin_family = AF_INET;
486
-	server.sin_port = htons(port);
487
-
488
-	if((opt = optget(opts, "TCPAddr"))->enabled) {
489
-	    if(r_gethostbyname(opt->strarg, &he, buff, sizeof(buff)) == -1) {
490
-		logg("!r_gethostbyname(%s) error: %s\n", opt->strarg, strerror(errno));
491
-		mdprintf(odesc, "r_gethostbyname(%s) ERROR\n", opt->strarg);
492
-		return -1;
493
-	    }
494
-	    server.sin_addr = *(struct in_addr *) he.h_addr_list[0];
495
-	} else
496
-	    server.sin_addr.s_addr = INADDR_ANY;
300
+	server.sin_port = htons(min_port + port);
301
+	server.sin_addr.s_addr = htonl(INADDR_ANY);
497 302
 
498 303
 	if((sockfd = socket(AF_INET, SOCK_STREAM, 0)) == -1)
499 304
 	    continue;
500 305
 
501 306
 	if(bind(sockfd, (struct sockaddr *) &server, sizeof(struct sockaddr_in)) == -1)
502 307
 	    closesocket(sockfd);
503
-	else
308
+	else {
504 309
 	    bound = 1;
310
+	    break;
311
+	}
505 312
     }
313
+    port += min_port;
506 314
 
507 315
     timeout = optget(opts, "ReadTimeout")->numarg;
508 316
     if(timeout == 0)
... ...
@@ -510,35 +318,31 @@ int scanstream(int odesc, unsigned long int *scanned, const struct cl_engine *en
510 510
 
511 511
     if(!bound && !portscan) {
512 512
 	logg("!ScanStream: Can't find any free port.\n");
513
-	mdprintf(odesc, "Can't find any free port. ERROR\n");
513
+	mdprintf(odesc, "Can't find any free port. ERROR%c", term);
514 514
 	closesocket(sockfd);
515 515
 	return -1;
516 516
     } else {
517 517
 	listen(sockfd, 1);
518
-	if(mdprintf(odesc, "PORT %u\n", port) <= 0) {
518
+	if(mdprintf(odesc, "PORT %u%c", port, term) <= 0) {
519 519
 	    logg("!ScanStream: error transmitting port.\n");
520 520
 	    closesocket(sockfd);
521 521
 	    return -1;
522 522
 	}
523 523
     }
524 524
 
525
-    switch(retval = poll_fd(sockfd, timeout, 0)) {
526
-	case 0: /* timeout */
527
-	    mdprintf(odesc, "Accept timeout. ERROR\n");
528
-	    logg("!ScanStream %u: accept timeout.\n", port);
529
-	    closesocket(sockfd);
530
-	    return -1;
531
-	case -1:
532
-	    mdprintf(odesc, "Accept poll. ERROR\n");
533
-	    logg("!ScanStream %u: accept poll failed.\n", port);
534
-	    closesocket(sockfd);
535
-	    return -1;
525
+    retval = poll_fd(sockfd, timeout, 0);
526
+    if (!retval || retval == -1) {
527
+	const char *reason = !retval ? "timeout" : "poll";
528
+	mdprintf(odesc, "Accept %s. ERROR%c", reason, term);
529
+	logg("!ScanStream %u: accept %s.\n", port, reason);
530
+	closesocket(sockfd);
531
+	return -1;
536 532
     }
537 533
 
538 534
     addrlen = sizeof(peer);
539 535
     if((acceptd = accept(sockfd, (struct sockaddr *) &peer, &addrlen)) == -1) {
540 536
 	closesocket(sockfd);
541
-	mdprintf(odesc, "accept() ERROR\n");
537
+	mdprintf(odesc, "accept() ERROR%c", term);
542 538
 	logg("!ScanStream %u: accept() failed.\n", port);
543 539
 	return -1;
544 540
     }
... ...
@@ -550,26 +354,31 @@ int scanstream(int odesc, unsigned long int *scanned, const struct cl_engine *en
550 550
 	shutdown(sockfd, 2);
551 551
 	closesocket(sockfd);
552 552
 	closesocket(acceptd);
553
-	mdprintf(odesc, "cli_gentempfd() failed. ERROR\n");
553
+	mdprintf(odesc, "cli_gentempfd() failed. ERROR%c", term);
554 554
 	logg("!ScanStream(%s@%u): Can't create temporary file.\n", peer_addr, port);
555 555
 	return -1;
556 556
     }
557 557
 
558
-    maxsize = optget(opts, "StreamMaxLength")->numarg;
559
-
560
-    btread = sizeof(buff);
558
+    quota = maxsize = optget(opts, "StreamMaxLength")->numarg;
561 559
 
562 560
     while((retval = poll_fd(acceptd, timeout, 0)) == 1) {
561
+	/* only read up to max */
562
+	btread = (maxsize && (quota < sizeof(buff))) ? quota : sizeof(buff);
563
+	if (!btread) {
564
+		logg("^ScanStream(%s@%u): Size limit reached (max: %lu)\n", peer_addr, port, maxsize);
565
+		break; /* Scan what we have */
566
+	}
563 567
 	bread = recv(acceptd, buff, btread, 0);
564 568
 	if(bread <= 0)
565 569
 	    break;
566
-	size += bread;
570
+
571
+	quota -= bread;
567 572
 
568 573
 	if(writen(tmpd, buff, bread) != bread) {
569 574
 	    shutdown(sockfd, 2);
570 575
 	    closesocket(sockfd);
571 576
 	    closesocket(acceptd);
572
-	    mdprintf(odesc, "Temporary file -> write ERROR\n");
577
+	    mdprintf(odesc, "Temporary file -> write ERROR%c", term);
573 578
 	    logg("!ScanStream(%s@%u): Can't write to temporary file.\n", peer_addr, port);
574 579
 	    close(tmpd);
575 580
 	    if(!optget(opts, "LeaveTemporaryFiles")->enabled)
... ...
@@ -577,24 +386,15 @@ int scanstream(int odesc, unsigned long int *scanned, const struct cl_engine *en
577 577
 	    free(tmpname);
578 578
 	    return -1;
579 579
 	}
580
-
581
-	if(maxsize && (size + btread >= maxsize)) {
582
-	    btread = (maxsize - size); /* only read up to max */
583
-
584
-	    if(btread <= 0) {
585
-		logg("^ScanStream(%s@%u): Size limit reached (max: %lu)\n", peer_addr, port, maxsize);
586
-	    	break; /* Scan what we have */
587
-	    }
588
-	}
589 580
     }
590 581
 
591 582
     switch(retval) {
592 583
 	case 0: /* timeout */
593
-	    mdprintf(odesc, "read timeout ERROR\n");
584
+	    mdprintf(odesc, "read timeout ERROR%c", term);
594 585
 	    logg("!ScanStream(%s@%u): read timeout.\n", peer_addr, port);
595 586
 	    break;
596 587
 	case -1:
597
-	    mdprintf(odesc, "read poll ERROR\n");
588
+	    mdprintf(odesc, "read poll ERROR%c", term);
598 589
 	    logg("!ScanStream(%s@%u): read poll failed.\n", peer_addr, port);
599 590
 	    break;
600 591
     }
... ...
@@ -616,16 +416,16 @@ int scanstream(int odesc, unsigned long int *scanned, const struct cl_engine *en
616 616
     closesocket(sockfd);
617 617
 
618 618
     if(ret == CL_VIRUS) {
619
-	mdprintf(odesc, "stream: %s FOUND\n", virname);
619
+	mdprintf(odesc, "stream: %s FOUND%c", virname, term);
620 620
 	logg("stream(%s@%u): %s FOUND\n", peer_addr, port, virname);
621 621
 	virusaction("stream", virname, opts);
622 622
     } else if(ret != CL_CLEAN) {
623 623
     	if(retval == 1) {
624
-	    mdprintf(odesc, "stream: %s ERROR\n", cl_strerror(ret));
624
+	    mdprintf(odesc, "stream: %s ERROR%c", cl_strerror(ret), term);
625 625
 	    logg("stream(%s@%u): %s ERROR\n", peer_addr, port, cl_strerror(ret));
626 626
 	}
627 627
     } else {
628
-	mdprintf(odesc, "stream: OK\n");
628
+	mdprintf(odesc, "stream: OK%c", term);
629 629
         if(logok)
630 630
 	    logg("stream(%s@%u): OK\n", peer_addr, port); 
631 631
     }
... ...
@@ -25,11 +25,29 @@
25 25
 
26 26
 #include "libclamav/clamav.h"
27 27
 #include "shared/optparser.h"
28
+#include "thrmgr.h"
29
+#include "session.h"
28 30
 
29
-int scan(const char *filename, unsigned long int *scanned, const struct cl_engine *engine, unsigned int options, const struct optstruct *opts, int odesc, unsigned int type);
31
+struct scan_cb_data {
32
+    int scantype;
33
+    int odesc;
34
+    int type;
35
+    int infected;
36
+    int errors;
37
+    int total;
38
+    int id;
39
+    const client_conn_t *conn;
40
+    const char *toplevel_path;
41
+    unsigned long scanned;
42
+    unsigned int options;
43
+    struct cl_engine *engine;
44
+    const struct optstruct *opts;
45
+    threadpool_t *thr_pool;
46
+    jobgroup_t *group;
47
+};
30 48
 
31
-int scanfd(const int fd, unsigned long int *scanned, const struct cl_engine *engine, unsigned int options, const struct optstruct *opts, int odesc);
32
-
33
-int scanstream(int odesc, unsigned long int *scanned, const struct cl_engine *engine, unsigned int options, const struct optstruct *opts);
49
+int scanfd(const int fd, const client_conn_t *conn, unsigned long int *scanned, const struct cl_engine *engine, unsigned int options, const struct optstruct *opts, int odesc, int stream);
50
+int scanstream(int odesc, unsigned long int *scanned, const struct cl_engine *engine, unsigned int options, const struct optstruct *opts, char term);
51
+int scan_callback(struct stat *sb, char *filename, const char *msg, enum cli_ftw_reason reason, struct cli_ftw_cbdata *data);
34 52
 
35 53
 #endif
... ...
@@ -42,6 +42,7 @@
42 42
 #include <unistd.h>
43 43
 #endif
44 44
 
45
+#include <arpa/inet.h>
45 46
 #include "libclamav/clamav.h"
46 47
 
47 48
 #include "shared/output.h"
... ...
@@ -75,17 +76,7 @@ int reload = 0;
75 75
 time_t reloaded_time = 0;
76 76
 pthread_mutex_t reload_mutex = PTHREAD_MUTEX_INITIALIZER;
77 77
 int sighup = 0;
78
-static struct cl_stat *dbstat = NULL;
79
-
80
-typedef struct client_conn_tag {
81
-    int sd;
82
-    unsigned int options;
83
-    const struct optstruct *opts;
84
-    struct cl_engine *engine;
85
-    time_t engine_timestamp;
86
-    int *socketds;
87
-    int nsockets;
88
-} client_conn_t;
78
+static struct cl_stat dbstat;
89 79
 
90 80
 static void scanner_thread(void *arg)
91 81
 {
... ...
@@ -93,8 +84,8 @@ static void scanner_thread(void *arg)
93 93
 #ifndef	C_WINDOWS
94 94
 	sigset_t sigset;
95 95
 #endif
96
-	int ret, timeout, i, session=FALSE;
97
-
96
+	int ret, timeout;
97
+	unsigned virus=0, errors = 0;
98 98
 
99 99
 #ifndef	C_WINDOWS
100 100
     /* ignore all signals */
... ...
@@ -107,61 +98,36 @@ static void scanner_thread(void *arg)
107 107
 #ifdef SIGBUS
108 108
     sigdelset(&sigset, SIGBUS);
109 109
 #endif
110
+    sigdelset(&sigset, SIGTSTP);
111
+    sigdelset(&sigset, SIGCONT);
110 112
     pthread_sigmask(SIG_SETMASK, &sigset, NULL);
111 113
 #endif
112 114
 
113 115
     timeout = optget(conn->opts, "ReadTimeout")->numarg;
114 116
     if(!timeout)
115
-    	timeout = -1;
116
-
117
-    do {
118
-    	ret = command(conn->sd, conn->engine, conn->options, conn->opts, timeout);
119
-	if (ret < 0) {
120
-		break;
121
-	}
122
-
123
-	switch(ret) {
124
-	    case COMMAND_SHUTDOWN:
125
-		pthread_mutex_lock(&exit_mutex);
126
-		progexit = 1;
127
-		for(i = 0; i < conn->nsockets; i++) {
128
-		    shutdown(conn->socketds[i], 2);
129
-		    closesocket(conn->socketds[i]);
130
-		}
131
-		pthread_mutex_unlock(&exit_mutex);
132
-		break;
133
-
134
-	    case COMMAND_RELOAD:
135
-		pthread_mutex_lock(&reload_mutex);
136
-		reload = 1;
137
-		pthread_mutex_unlock(&reload_mutex);
138
-		break;
139
-
140
-	    case COMMAND_SESSION:
141
-		session = TRUE;
142
-		break;
117
+	timeout = -1;
143 118
 
144
-	    case COMMAND_END:
145
-		session = FALSE;
146
-		break;
147
-	}
148
-	if (session) {
149
-	    pthread_mutex_lock(&exit_mutex);
150
-	    if(progexit) {
151
-		session = FALSE;
152
-	    }
153
-	    pthread_mutex_unlock(&exit_mutex);
154
-	    pthread_mutex_lock(&reload_mutex);
155
-	    if (conn->engine_timestamp != reloaded_time) {
156
-		session = FALSE;
157
-	    }
158
-	    pthread_mutex_unlock(&reload_mutex);
159
-	}
160
-    } while (session);
119
+    ret = command(conn, &virus);
120
+    if (ret == -1) {
121
+	pthread_mutex_lock(&exit_mutex);
122
+	progexit = 1;
123
+	pthread_mutex_unlock(&exit_mutex);
124
+	errors = 1;
125
+    } else
126
+	errors = ret;
161 127
 
162
-    shutdown(conn->sd, 2);
163
-    closesocket(conn->sd);
164 128
     thrmgr_setactiveengine(NULL);
129
+
130
+    if (conn->filename)
131
+	free(conn->filename);
132
+    logg("*SCANTH: finished\n");
133
+    if (thrmgr_group_finished(conn->group, virus ? EXIT_OTHER :
134
+			      errors ? EXIT_ERROR : EXIT_OK)) {
135
+	logg("*SCANTH: connection shut down\n");
136
+	/* close connection if we were last in group */
137
+	shutdown(conn->sd, 2);
138
+	closesocket(conn->sd);
139
+    }
165 140
     cl_engine_free(conn->engine);
166 141
     free(conn);
167 142
     return;
... ...
@@ -202,12 +168,12 @@ static struct cl_engine *reload_db(struct cl_engine *engine, unsigned int dbopti
202 202
     pua_cats[0] = 0;
203 203
     *ret = 0;
204 204
     if(do_check) {
205
-	if(dbstat == NULL) {
205
+	if(!dbstat.entries) {
206 206
 	    logg("No stats for Database check - forcing reload\n");
207 207
 	    return engine;
208 208
 	}
209 209
 
210
-	if(cl_statchkdir(dbstat) == 1) {
210
+	if(cl_statchkdir(&dbstat) == 1) {
211 211
 	    logg("SelfCheck: Database modification detected. Forcing reload.\n");
212 212
 	    return engine;
213 213
 	} else {
... ...
@@ -229,19 +195,11 @@ static struct cl_engine *reload_db(struct cl_engine *engine, unsigned int dbopti
229 229
     dbdir = optget(opts, "DatabaseDirectory")->strarg;
230 230
     logg("Reading databases from %s\n", dbdir);
231 231
 
232
-    if(dbstat == NULL) {
233
-	dbstat = (struct cl_stat *) malloc(sizeof(struct cl_stat));
234
-	if(!dbstat) {
235
-	    logg("!Can't allocate memory for dbstat\n");
236
-	    *ret = 1;
237
-	    return NULL;
238
-	}
239
-    } else {
240
-	cl_statfree(dbstat);
241
-    }
232
+    if(dbstat.entries)
233
+	cl_statfree(&dbstat);
242 234
 
243
-    memset(dbstat, 0, sizeof(struct cl_stat));
244
-    if((retval = cl_statinidir(dbdir, dbstat))) {
235
+    memset(&dbstat, 0, sizeof(struct cl_stat));
236
+    if((retval = cl_statinidir(dbdir, &dbstat))) {
245 237
 	logg("!cl_statinidir() failed: %s\n", cl_strerror(retval));
246 238
 	*ret = 1;
247 239
 	return NULL;
... ...
@@ -281,11 +239,170 @@ static struct cl_engine *reload_db(struct cl_engine *engine, unsigned int dbopti
281 281
     return engine;
282 282
 }
283 283
 
284
-int acceptloop_th(int *socketds, int nsockets, struct cl_engine *engine, unsigned int dboptions, const struct optstruct *opts)
284
+static const char *get_cmd(struct fd_buf *buf, size_t off, size_t *len, char *term)
285
+{
286
+    unsigned char *pos;
287
+    if (!buf->off || off >= buf->off) {
288
+	*len = 0;
289
+	return NULL;
290
+    }
291
+
292
+    *term = '\n';
293
+    switch (buf->buffer[0]) {
294
+	/* commands terminated by delimiters */
295
+	case 'z':
296
+	    *term = '\0';
297
+	case 'n':
298
+	    pos = memchr(buf->buffer + off, *term, buf->off - off);
299
+	    if (!pos) {
300
+		/* we don't have another full command yet */
301
+		*len = 0;
302
+		return NULL;
303
+	    }
304
+	    *pos = '\0';
305
+	    if (*term) {
306
+		*len = cli_chomp(buf->buffer + off);
307
+	    } else {
308
+		*len = pos - buf->buffer - off;
309
+	    }
310
+	    return buf->buffer + off + 1;
311
+	default:
312
+	    /* one packet = one command */
313
+	    *len = buf->off - off;
314
+	    buf->buffer[buf->off] = '\0';
315
+	    cli_chomp(buf->buffer + off);
316
+	    return buf->buffer + off;
317
+    }
318
+}
319
+
320
+struct acceptdata {
321
+    struct fd_data fds;
322
+    struct fd_data recv_fds;
323
+    int syncpipe_wake_recv[2];
324
+    int syncpipe_wake_accept[2];
325
+};
326
+
327
+static void *acceptloop_th(void *arg)
328
+{
329
+#ifdef HAVE_STRERROR_R
330
+    char buff[BUFFSIZE + 1];
331
+#endif
332
+    size_t i;
333
+    struct acceptdata *data = (struct acceptdata*)arg;
334
+    struct fd_data *fds = &data->fds;
335
+    struct fd_data *recv_fds = &data->recv_fds;
336
+
337
+    pthread_mutex_lock(&fds->buf_mutex);
338
+    for (;;) {
339
+	/* Block waiting for data to become available for reading */
340
+	int new_sd = fds_poll_recv(fds, -1, 0);
341
+
342
+	/* TODO: what about sockets that get rm-ed? */
343
+	if (!fds->nfds) {
344
+	    /* no more sockets to poll, all gave an error */
345
+	    logg("!Main socket gone: fatal\n");
346
+	    break;
347
+	}
348
+
349
+	if (new_sd == -1 && errno != EINTR) {
350
+	    logg("!Failed to poll sockets, fatal\n");
351
+	    pthread_mutex_lock(&exit_mutex);
352
+	    progexit = 1;
353
+	    pthread_mutex_unlock(&exit_mutex);
354
+	    break;
355
+	}
356
+
357
+	/* accept() loop */
358
+	for (i=0;i < fds->nfds && new_sd >= 0; i++) {
359
+	    struct fd_buf *buf = &fds->buf[i];
360
+	    if (!buf->got_newdata)
361
+		continue;
362
+	    if (buf->fd == data->syncpipe_wake_accept[0]) {
363
+		/* dummy sync pipe, just to wake us */
364
+		if (read(buf->fd, buff, sizeof(buff)) < 0) {
365
+		    logg("^Syncpipe read failed\n");
366
+		}
367
+		continue;
368
+	    }
369
+	    if (buf->got_newdata == -1) {
370
+		shutdown(buf->fd, 2);
371
+		closesocket(buf->fd);
372
+		buf->fd = -1;
373
+		continue;
374
+	    }
375
+	    /* listen only socket */
376
+	    new_sd = accept(fds->buf[i].fd, NULL, NULL);
377
+
378
+	    pthread_mutex_lock(&exit_mutex);
379
+	    if(progexit) {
380
+		pthread_mutex_unlock(&exit_mutex);
381
+		break;
382
+	    }
383
+	    pthread_mutex_unlock(&exit_mutex);
384
+
385
+	    if (new_sd >= 0) {
386
+		int ret;
387
+		pthread_mutex_lock(&recv_fds->buf_mutex);
388
+		ret = fds_add(recv_fds, new_sd, 0);
389
+		pthread_mutex_unlock(&recv_fds->buf_mutex);
390
+
391
+		if (ret == -1) {
392
+		    logg("!fds_add failed\n");
393
+		    closesocket(new_sd);
394
+		    continue;
395
+		}
396
+
397
+		/* notify recvloop */
398
+		if (write(data->syncpipe_wake_recv[1], "", 1) == -1) {
399
+		    logg("!write syncpipe failed\n");
400
+		    continue;
401
+		}
402
+	    } else if (errno != EINTR) {
403
+		/* very bad - need to exit or restart */
404
+#ifdef HAVE_STRERROR_R
405
+		strerror_r(errno, buff, BUFFSIZE);
406
+		logg("!accept() failed: %s\n", buff);
407
+#else
408
+		logg("!accept() failed\n");
409
+#endif
410
+		/* give the poll loop a chance to close disconnected FDs */
411
+		break;
412
+	    }
413
+
414
+	}
415
+
416
+	/* handle progexit */
417
+	pthread_mutex_lock(&exit_mutex);
418
+	if (progexit) {
419
+	    pthread_mutex_unlock(&exit_mutex);
420
+	    break;
421
+	}
422
+	pthread_mutex_unlock(&exit_mutex);
423
+    }
424
+    pthread_mutex_unlock(&fds->buf_mutex);
425
+
426
+    for (i=0;i < fds->nfds; i++) {
427
+	if (fds->buf[i].fd == -1)
428
+	    continue;
429
+	shutdown(fds->buf[i].fd, 2);
430
+	closesocket(fds->buf[i].fd);
431
+    }
432
+    fds_free(fds);
433
+
434
+    pthread_mutex_lock(&exit_mutex);
435
+    progexit = 1;
436
+    pthread_mutex_unlock(&exit_mutex);
437
+    if (write(data->syncpipe_wake_recv[1], "", 1) < 0) {
438
+	logg("^Syncpipe write failed\n");
439
+    }
440
+
441
+    return NULL;
442
+}
443
+
444
+int recvloop_th(int *socketds, unsigned nsockets, struct cl_engine *engine, unsigned int dboptions, const struct optstruct *opts)
285 445
 {
286
-	int max_threads, i, ret = 0;
446
+	int max_threads, max_queue, ret = 0;
287 447
 	unsigned int options = 0;
288
-	threadpool_t *thr_pool;
289 448
 	char timestr[32];
290 449
 #ifndef	C_WINDOWS
291 450
 	struct sigaction sigact;
... ...
@@ -293,17 +410,19 @@ int acceptloop_th(int *socketds, int nsockets, struct cl_engine *engine, unsigne
293 293
 	struct rlimit rlim;
294 294
 #endif
295 295
 	mode_t old_umask;
296
-	client_conn_t *client_conn;
297 296
 	const struct optstruct *opt;
298
-#ifdef HAVE_STRERROR_R
299 297
 	char buff[BUFFSIZE + 1];
300
-#endif
301
-	unsigned int selfchk;
302
-	time_t start_time, current_time;
303 298
 	pid_t mainpid;
304 299
 	int idletimeout;
305 300
 	uint32_t val32;
306 301
 	uint64_t val64;
302
+	size_t i, j, rr_last = 0;
303
+	pthread_t accept_th;
304
+	struct acceptdata acceptdata;
305
+	struct fd_data *fds = &acceptdata.recv_fds;
306
+	time_t start_time, current_time;
307
+	unsigned int selfchk;
308
+	threadpool_t *thr_pool;
307 309
 
308 310
 #ifdef CLAMUKO
309 311
 	pthread_t clamuko_pid;
... ...
@@ -522,6 +641,7 @@ int acceptloop_th(int *socketds, int nsockets, struct cl_engine *engine, unsigne
522 522
     } else {
523 523
 	logg("Self checking every %u seconds.\n", selfchk);
524 524
     }
525
+    memset(&dbstat, 0, sizeof(dbstat));
525 526
 
526 527
     /* save the PID */
527 528
     mainpid = getpid();
... ...
@@ -541,6 +661,7 @@ int acceptloop_th(int *socketds, int nsockets, struct cl_engine *engine, unsigne
541 541
 
542 542
     logg("*Listening daemon: PID: %u\n", (unsigned int) mainpid);
543 543
     max_threads = optget(opts, "MaxThreads")->numarg;
544
+    max_queue = optget(opts, "MaxQueue")->numarg;
544 545
 
545 546
     if(optget(opts, "ClamukoScanOnAccess")->enabled)
546 547
 #ifdef CLAMUKO
... ...
@@ -579,6 +700,8 @@ int acceptloop_th(int *socketds, int nsockets, struct cl_engine *engine, unsigne
579 579
 #ifdef SIGBUS    
580 580
     sigdelset(&sigset, SIGBUS);
581 581
 #endif
582
+    sigdelset(&sigset, SIGTSTP);
583
+    sigdelset(&sigset, SIGCONT);
582 584
     sigprocmask(SIG_SETMASK, &sigset, NULL);
583 585
 
584 586
     /* SIGINT, SIGTERM, SIGSEGV */
... ...
@@ -598,114 +721,342 @@ int acceptloop_th(int *socketds, int nsockets, struct cl_engine *engine, unsigne
598 598
 
599 599
     idletimeout = optget(opts, "IdleTimeout")->numarg;
600 600
 
601
-    if((thr_pool=thrmgr_new(max_threads, idletimeout, scanner_thread)) == NULL) {
601
+    memset(&acceptdata, 0, sizeof(acceptdata));
602
+
603
+    for (i=0;i < nsockets;i++)
604
+	if (fds_add(&acceptdata.fds, socketds[i], 1) == -1) {
605
+	    logg("!fds_add failed\n");
606
+	    cl_engine_free(engine);
607
+	    return 1;
608
+	}
609
+
610
+    if (pipe(acceptdata.syncpipe_wake_recv) == -1 ||
611
+	(pipe(acceptdata.syncpipe_wake_accept) == -1)) {
612
+
613
+	logg("!pipe failed\n");
614
+	exit(-1);
615
+    }
616
+
617
+    if (fds_add(fds, acceptdata.syncpipe_wake_recv[0], 1) == -1 ||
618
+	fds_add(&acceptdata.fds, acceptdata.syncpipe_wake_accept[0], 1)) {
619
+	logg("!failed to add pipe fd\n");
620
+	exit(-1);
621
+    }
622
+
623
+    if ((thr_pool = thrmgr_new(max_threads, idletimeout, max_queue, scanner_thread)) == NULL) {
602 624
 	logg("!thrmgr_new failed\n");
603 625
 	exit(-1);
604 626
     }
605 627
 
606
-    time(&start_time);
628
+    if (pthread_create(&accept_th, NULL, acceptloop_th, &acceptdata)) {
629
+	logg("!pthread_create failed\n");
630
+	exit(-1);
631
+    }
607 632
 
608
-    for(;;) {				
609
-#if !defined(C_WINDOWS) && !defined(C_BEOS)
610
-	    struct stat st_buf;
611
-#endif
612
-    	int socketd = socketds[0];
613
-	int new_sd = 0;
614
-
615
-    	if(nsockets > 1) {
616
-	    int pollret = poll_fds(socketds, nsockets, -1, 1);
617
-    	    if(pollret > 0) {
618
-    		socketd = socketds[pollret - 1];
619
-    	    } else {
620
-		new_sd = -1;
621
-    	    }
622
-    	}
623
-#if !defined(C_WINDOWS) && !defined(C_BEOS)
624
-	if(new_sd != -1 && fstat(socketd, &st_buf) == -1) {
625
-	    logg("!fstat(): socket descriptor gone\n");
626
-	    memmove(socketds, socketds + 1, sizeof(socketds[0]) * nsockets);
627
-	    nsockets--;
628
-	    if(!nsockets) {
629
-		logg("!Main socket gone: fatal\n");
630
-		break;
631
-	    }
632
-	}
633
-#endif
634
-	if (new_sd != -1)
635
-	    new_sd = accept(socketd, NULL, NULL);
636
-	if((new_sd == -1) && (errno != EINTR)) {
633
+    time(&start_time);
634
+    for(;;) {
635
+	int new_sd;
636
+	/* Block waiting for connection on any of the sockets */
637
+	pthread_mutex_lock(&fds->buf_mutex);
638
+	new_sd = fds_poll_recv(fds, -1, 1);
639
+
640
+	if (!fds->nfds) {
641
+	    /* at least the dummy/sync pipe should have remained */
642
+	    logg("!All recv() descriptors gone: fatal\n");
637 643
 	    pthread_mutex_lock(&exit_mutex);
638
-	    if(progexit) {
639
-		pthread_mutex_unlock(&exit_mutex);
640
-	    	break;
641
-	    }
644
+	    progexit = 1;
642 645
 	    pthread_mutex_unlock(&exit_mutex);
643
-	    /* very bad - need to exit or restart */
644
-#ifdef HAVE_STRERROR_R
645
-	    strerror_r(errno, buff, BUFFSIZE);
646
-	    logg("!accept() failed: %s\n", buff);
647
-#else
648
-	    logg("!accept() failed\n");
649
-#endif
650
-	    continue;
646
+	    pthread_mutex_unlock(&fds->buf_mutex);
647
+	    break;
651 648
 	}
652 649
 
653
-	if (sighup) {
654
-		logg("SIGHUP caught: re-opening log file.\n");
655
-		logg_close();
656
-		sighup = 0;
657
-		if(!logg_file && (opt = optget(opts, "LogFile"))->enabled)
658
-		    logg_file = opt->strarg;
650
+	if (new_sd == -1 && errno != EINTR) {
651
+	    logg("!Failed to poll sockets, fatal\n");
652
+	    pthread_mutex_lock(&exit_mutex);
653
+	    progexit = 1;
654
+	    pthread_mutex_unlock(&exit_mutex);
659 655
 	}
660 656
 
661
-	if (!progexit && new_sd >= 0) {
662
-		client_conn = (client_conn_t *) malloc(sizeof(struct client_conn_tag));
663
-		if(client_conn) {
664
-		    client_conn->sd = new_sd;
665
-		    client_conn->options = options;
666
-		    client_conn->opts = opts;
667
-		    if(cl_engine_addref(engine)) {
668
-			closesocket(client_conn->sd);
669
-			free(client_conn);
670
-			logg("!cl_engine_addref() failed\n");
671
-			pthread_mutex_lock(&exit_mutex);
672
-			progexit = 1;
673
-			pthread_mutex_unlock(&exit_mutex);
674
-		    } else {
675
-			client_conn->engine = engine;
676
-			client_conn->engine_timestamp = reloaded_time;
677
-			client_conn->socketds = socketds;
678
-			client_conn->nsockets = nsockets;
679
-			if(!thrmgr_dispatch(thr_pool, client_conn)) {
680
-			    closesocket(client_conn->sd);
681
-			    free(client_conn);
682
-			    logg("!thread dispatch failed\n");
657
+	i = (rr_last + 1) % fds->nfds;
658
+	for (j = 0;  j < fds->nfds && new_sd >= 0; j++, i = (i+1) % fds->nfds) {
659
+	    size_t pos = 0;
660
+	    int error = 0;
661
+	    struct fd_buf *buf = &fds->buf[i];
662
+	    if (!buf->got_newdata)
663
+		continue;
664
+
665
+	    if (buf->fd == acceptdata.syncpipe_wake_recv[0]) {
666
+		/* dummy sync pipe, just to wake us */
667
+		if (read(buf->fd, buff, sizeof(buff)) < 0) {
668
+		    logg("^Syncpipe read failed\n");
669
+		}
670
+		continue;
671
+	    }
672
+
673
+	    if (buf->got_newdata == -1) {
674
+		if (buf->mode == MODE_WAITREPLY) {
675
+		    logg("*RECVTH: mode WAIT_REPLY -> closed\n");
676
+		    buf->fd = -1;
677
+		    continue;
678
+		} else {
679
+		    logg("*RECVTH: client read error or EOF on read\n");
680
+		    error = 1;
681
+		}
682
+	    }
683
+
684
+	    rr_last = i;
685
+	    if (buf->mode == MODE_WAITANCILL) {
686
+		buf->mode = MODE_COMMAND;
687
+		logg("*RECVTH: mode -> MODE_COMMAND\n");
688
+	    }
689
+	    while (!error && buf->fd != -1 && buf->buffer && pos < buf->off &&
690
+		   buf->mode != MODE_WAITANCILL) {
691
+		client_conn_t conn;
692
+		const unsigned char *cmd = NULL;
693
+		size_t cmdlen = 0;
694
+		char term = '\n';
695
+		int rc;
696
+		/* New data available to read on socket. */
697
+
698
+		memset(&conn, 0, sizeof(conn));
699
+		conn.scanfd = buf->recvfd;
700
+		buf->recvfd = -1;
701
+		conn.sd = buf->fd;
702
+		conn.options = options;
703
+		conn.opts = opts;
704
+		conn.thrpool = thr_pool;
705
+		conn.engine = engine;
706
+		conn.group = buf->group;
707
+		conn.id = buf->id;
708
+		conn.quota = buf->quota;
709
+		conn.filename = buf->dumpname;
710
+		conn.mode = buf->mode;
711
+		/* Parse & dispatch commands */
712
+		while ((conn.mode == MODE_COMMAND) &&
713
+		       (cmd = get_cmd(buf, pos, &cmdlen, &term)) != NULL) {
714
+		    const char *argument;
715
+		    enum commands cmdtype = parse_command(cmd, &argument);
716
+		    logg("*RECVTH: got command %s (%u, %u), argument: %s\n",
717
+			 cmd, (unsigned)cmdlen, (unsigned)cmdtype, argument ? argument : "");
718
+		    if (cmdtype == COMMAND_FILDES) {
719
+			if (buf->buffer + buf->off <= cmd + strlen("FILDES\n")) {
720
+			    /* we need the extra byte from recvmsg */
721
+			    conn.mode = MODE_WAITANCILL;
722
+			    buf->mode = MODE_WAITANCILL;
723
+			    cmdlen = 0;
724
+			    logg("*RECVTH: mode -> MODE_WAITANCILL\n");
725
+			    break;
683 726
 			}
727
+			/* eat extra \0 for controlmsg */
728
+			cmdlen++;
729
+			logg("*RECVTH: FILDES command complete\n");
684 730
 		    }
685
-		} else {
686
-		    logg("!Can't allocate memory for client_conn\n");
687
-		    closesocket(new_sd);
688
-		    if(optget(opts, "ExitOnOOM")->enabled) {
689
-			pthread_mutex_lock(&exit_mutex);
690
-			progexit = 1;
691
-			pthread_mutex_unlock(&exit_mutex);
731
+
732
+		    conn.term = term;
733
+		    buf->term = term;
734
+
735
+		    if ((rc = execute_or_dispatch_command(&conn, cmdtype, argument)) < 0) {
736
+			logg("!Command dispatch failed\n");
737
+			if(rc == -1 && optget(opts, "ExitOnOOM")->enabled) {
738
+			    pthread_mutex_lock(&exit_mutex);
739
+			    progexit = 1;
740
+			    pthread_mutex_unlock(&exit_mutex);
741
+			}
742
+			error = 1;
743
+		    }
744
+		    if (error || !conn.group || rc) {
745
+			if (rc && thrmgr_group_finished(conn.group, EXIT_OK)) {
746
+			    logg("*RECVTH: closing conn, group finished\n");
747
+			    /* if there are no more active jobs */
748
+			    shutdown(conn.sd, 2);
749
+			    closesocket(conn.sd);
750
+			    buf->fd = -1;
751
+			    conn.group = NULL;
752
+			} else if (conn.mode != MODE_STREAM) {
753
+			    logg("*RECVTH: mode -> MODE_WAITREPLY\n");
754
+			    /* no more commands are accepted */
755
+			    conn.mode = MODE_WAITREPLY;
756
+			    /* Stop monitoring this FD, it will be closed either
757
+			     * by us, or by the scanner thread. 
758
+			     * Never close a file descriptor that is being
759
+			     * monitored by poll()/select() from another thread,
760
+			     * because this can lead to subtle bugs such as:
761
+			     * Other thread closes file descriptor -> POLLHUP is
762
+			     * set, but the poller thread doesn't wake up yet.
763
+			     * Another client opens a connection and sends some
764
+			     * data. If the socket reuses the previous file descriptor,
765
+			     * then POLLIN is set on the file descriptor too.
766
+			     * When poll() wakes up it sees POLLIN | POLLHUP
767
+			     * and thinks that the client has sent some data,
768
+			     * and closed the connection, so clamd closes the
769
+			     * connection in turn resulting in a bug.
770
+			     *
771
+			     * If we wouldn't have poll()-ed the file descriptor
772
+			     * we closed in another thread, but rather made sure
773
+			     * that we don't put a FD that we're about to close
774
+			     * into poll()'s list of watched fds; then POLLHUP
775
+			     * would be set, but the file descriptor would stay
776
+			     * open, until we wake up from poll() and close it.
777
+			     * Thus a new connection won't be able to reuse the
778
+			     * same FD, and there is no bug.
779
+			     * */
780
+			    buf->fd = -1;
781
+			}
782
+		    }
783
+		    pos += cmdlen+1;
784
+		    if (conn.mode == MODE_STREAM) {
785
+			/* TODO: this doesn't belong here */
786
+			buf->dumpname = conn.filename;
787
+			buf->dumpfd = conn.scanfd;
788
+			logg("*RECVTH: STREAM: %s fd %u\n", buf->dumpname, buf->dumpfd);
789
+		    }
790
+		    if (conn.mode != MODE_COMMAND) {
791
+			logg("*RECVTH: breaking command loop, mode is no longer MODE_COMMAND\n");
792
+			break;
692 793
 		    }
794
+		    conn.id++;
693 795
 		}
796
+		buf->mode = conn.mode;
797
+		buf->id = conn.id;
798
+		buf->group = conn.group;
799
+		buf->quota = conn.quota;
800
+		if (!error) {
801
+		    /* move partial command to beginning of buffer */
802
+		    if (pos < buf->off) {
803
+			memmove (buf->buffer, &buf->buffer[pos], buf->off - pos);
804
+			buf->off -= pos;
805
+		    } else
806
+			buf->off = 0;
807
+		    if (buf->off)
808
+			logg("*RECVTH: moved partial command: %lu\n", (unsigned long)buf->off);
809
+		    else
810
+			logg("*RECVTH: consumed entire command\n");
811
+		}
812
+		if (conn.mode == MODE_COMMAND && !cmd)
813
+		    break;
814
+		if (!error && buf->mode == MODE_WAITREPLY && buf->off) {
815
+		    /* Client is not supposed to send anything more */
816
+		    logg("^Client sent garbage after last command: %lu bytes\n", (unsigned long)buf->off);
817
+		    buf->buffer[buf->off] = '\0';
818
+		    logg("*RECVTH: garbage: %s\n", buf->buffer);
819
+		    error = 1;
820
+		}
821
+		if (!error && buf->mode == MODE_STREAM) {
822
+		    logg("*RECVTH: mode == MODE_STREAM\n");
823
+		    if (!buf->chunksize) {
824
+			/* read chunksize */
825
+			if (buf->off >= 4) {
826
+			    uint32_t cs = *(uint32_t*)buf->buffer;
827
+			    buf->chunksize = ntohl(cs);
828
+			    logg("*RECVTH: chunksize: %u\n", buf->chunksize);
829
+			    if (!buf->chunksize) {
830
+				/* chunksize 0 marks end of stream */
831
+				conn.scanfd = buf->dumpfd;
832
+				conn.term = buf->term;
833
+				buf->dumpfd = -1;
834
+				buf->mode = buf->group ? MODE_COMMAND : MODE_WAITREPLY;
835
+				logg("*RECVTH: chunks complete\n");
836
+				buf->dumpname = NULL;
837
+				if ((rc = execute_or_dispatch_command(&conn, COMMAND_INSTREAMSCAN, NULL)) < 0) {
838
+				    logg("!Command dispatch failed\n");
839
+				    if(rc == -1 && optget(opts, "ExitOnOOM")->enabled) {
840
+					pthread_mutex_lock(&exit_mutex);
841
+					progexit = 1;
842
+					pthread_mutex_unlock(&exit_mutex);
843
+				    }
844
+				    error = 1;
845
+				} else {
846
+				    pos = 4;
847
+				    memmove (buf->buffer, &buf->buffer[pos], buf->off - pos);
848
+				    buf->off -= pos;
849
+				    pos = 0;
850
+				    buf->id++;
851
+				    continue;
852
+				}
853
+			    }
854
+			    if (buf->chunksize > buf->quota) {
855
+				logg("^INSTREAM: Size limit reached, (requested: %lu, max: %lu)\n", 
856
+				     (unsigned long)buf->chunksize, (unsigned long)buf->quota);
857
+				conn_reply_error(&conn, "INSTREAM size limit exceeded. ERROR");
858
+				error = 1;
859
+			    } else {
860
+				buf->quota -= buf->chunksize;
861
+			    }
862
+			    logg("*RECVTH: quota: %lu\n", buf->quota);
863
+			    pos = 4;
864
+			} else
865
+			    break;
866
+		    } else
867
+			pos = 0;
868
+		    if (pos + buf->chunksize < buf->off)
869
+			cmdlen = buf->chunksize;
870
+		    else
871
+			cmdlen = buf->off - pos;
872
+		    buf->chunksize -= cmdlen;
873
+		    if (cli_writen(buf->dumpfd, buf->buffer + pos, cmdlen) < 0) {
874
+			conn_reply_error(&conn, "Error writing to temporary file");
875
+			logg("!INSTREAM: Can't write to temporary file.\n");
876
+			error = 1;
877
+		    }
878
+		    logg("*RECVTH: processed %lu bytes of chunkdata\n", cmdlen);
879
+		    pos += cmdlen;
880
+		    if (pos == buf->off) {
881
+			buf->off = 0;
882
+		    }
883
+		}
884
+		if (error) {
885
+		    conn_reply_error(&conn, "Error processing command.");
886
+		}
887
+	    }
888
+	    if (error) {
889
+		if (buf->dumpfd != -1) {
890
+		    close(buf->dumpfd);
891
+		    if (buf->dumpname) {
892
+			cli_unlink(buf->dumpname);
893
+			free(buf->dumpname);
894
+		    }
895
+		    buf->dumpfd = -1;
896
+		}
897
+		if (thrmgr_group_terminate(buf->group)) {
898
+		    logg("*RECVTH: shutting down socket after error\n");
899
+		    shutdown(buf->fd, 2);
900
+		    closesocket(buf->fd);
901
+		} else
902
+		    logg("*RECVTH: socket not shut down due to active tasks\n");
903
+		buf->fd = -1;
904
+	    }
694 905
 	}
906
+	pthread_mutex_unlock(&fds->buf_mutex);
695 907
 
908
+	/* handle progexit */
696 909
 	pthread_mutex_lock(&exit_mutex);
697
-	if(progexit) {
698
-#ifdef C_WINDOWS
699
-	    closesocket(new_sd);
700
-#else
701
-  	    if(new_sd >= 0)
702
-		close(new_sd);
703
-#endif
910
+	if (progexit) {
704 911
 	    pthread_mutex_unlock(&exit_mutex);
912
+	    pthread_mutex_lock(&fds->buf_mutex);
913
+	    for (i=0;i < fds->nfds; i++) {
914
+		if (fds->buf[i].fd == -1)
915
+		    continue;
916
+		if (thrmgr_group_terminate(fds->buf[i].group)) {
917
+		    shutdown(fds->buf[i].fd, 2);
918
+		    closesocket(fds->buf[i].fd);
919
+		    fds->buf[i].fd = -1;
920
+		}
921
+	    }
922
+	    pthread_mutex_unlock(&fds->buf_mutex);
705 923
 	    break;
706 924
 	}
707 925
 	pthread_mutex_unlock(&exit_mutex);
708 926
 
927
+	/* SIGHUP */
928
+	if (sighup) {
929
+	    logg("SIGHUP caught: re-opening log file.\n");
930
+	    logg_close();
931
+	    sighup = 0;
932
+	    if(!logg_file && (opt = optget(opts, "LogFile"))->enabled)
933
+		logg_file = opt->strarg;
934
+	}
935
+
936
+	/* SelfCheck */
709 937
 	if(selfchk) {
710 938
 	    time(&current_time);
711 939
 	    if((current_time - start_time) > (time_t)selfchk) {
... ...
@@ -718,18 +1069,15 @@ int acceptloop_th(int *socketds, int nsockets, struct cl_engine *engine, unsigne
718 718
 	    }
719 719
 	}
720 720
 
721
+	/* DB reload */
721 722
 	pthread_mutex_lock(&reload_mutex);
722 723
 	if(reload) {
723 724
 	    pthread_mutex_unlock(&reload_mutex);
724 725
 	    engine = reload_db(engine, dboptions, opts, FALSE, &ret);
725 726
 	    if(ret) {
726 727
 		logg("Terminating because of a fatal error.\n");
727
-#ifdef C_WINDOWS
728
-		closesocket(new_sd);
729
-#else
730 728
 		if(new_sd >= 0)
731
-		    close(new_sd);
732
-#endif
729
+		    closesocket(new_sd);
733 730
 		break;
734 731
 	    }
735 732
 
... ...
@@ -751,9 +1099,16 @@ int acceptloop_th(int *socketds, int nsockets, struct cl_engine *engine, unsigne
751 751
 	}
752 752
     }
753 753
 
754
+    pthread_mutex_lock(&exit_mutex);
755
+    progexit = 1;
756
+    pthread_mutex_unlock(&exit_mutex);
757
+    if (write(acceptdata.syncpipe_wake_accept[1], "", 1) < 0) {
758
+	logg("^Write to syncpipe failed\n");
759
+    }
754 760
     /* Destroy the thread manager.
755 761
      * This waits for all current tasks to end
756 762
      */
763
+    logg("*Waiting for all threads to finish\n");
757 764
     thrmgr_destroy(thr_pool);
758 765
 #ifdef CLAMUKO
759 766
     if(optget(opts, "ClamukoScanOnAccess")->enabled) {
... ...
@@ -767,8 +1122,12 @@ int acceptloop_th(int *socketds, int nsockets, struct cl_engine *engine, unsigne
767 767
 	cl_engine_free(engine);
768 768
     }
769 769
 
770
-    if(dbstat)
771
-	cl_statfree(dbstat);
770
+    pthread_join(accept_th, NULL);
771
+    fds_free(fds);
772
+    close(acceptdata.syncpipe_wake_accept[1]);
773
+    close(acceptdata.syncpipe_wake_recv[1]);
774
+    if(dbstat.entries)
775
+	cl_statfree(&dbstat);
772 776
     logg("*Shutting down the main socket%s.\n", (nsockets > 1) ? "s" : "");
773 777
     for (i = 0; i < nsockets; i++)
774 778
 	shutdown(socketds[i], 2);
... ...
@@ -25,7 +25,8 @@
25 25
 
26 26
 #include "libclamav/clamav.h"
27 27
 #include "shared/optparser.h"
28
-
28
+#include "thrmgr.h"
29
+#include "session.h"
29 30
 struct thrarg {
30 31
     int sid;
31 32
     int options;
... ...
@@ -43,9 +44,12 @@ struct thrwarg {
43 43
     unsigned int options;
44 44
 };
45 45
 
46
-int acceptloop_th(int *socketds, int nsockets, struct cl_engine *engine, unsigned int dboptions, const struct optstruct *opts);
46
+int recvloop_th(int *socketds, unsigned nsockets, struct cl_engine *engine, unsigned int dboptions, const struct optstruct *opts);
47 47
 void sighandler(int sig);
48 48
 void sighandler_th(int sig);
49 49
 void sigsegv(int sig);
50 50
 
51
+extern pthread_mutex_t exit_mutex, reload_mutex;
52
+extern int progexit, reload;
53
+
51 54
 #endif
... ...
@@ -67,152 +67,416 @@
67 67
 #include "session.h"
68 68
 #include "thrmgr.h"
69 69
 
70
-#ifdef HAVE_FD_PASSING
71
-static int recvfd_and_scan(int desc, const struct cl_engine *engine, unsigned int options, const struct optstruct *opts)
70
+static struct {
71
+    const char *cmd;
72
+    const size_t len;
73
+    enum commands cmdtype;
74
+    int need_arg;
75
+} commands[] = {
76
+    {CMD1,  sizeof(CMD1)-1,	COMMAND_SCAN,	    1},
77
+    {CMD3,  sizeof(CMD3)-1,	COMMAND_SHUTDOWN,   0},
78
+    {CMD4,  sizeof(CMD4)-1,	COMMAND_RELOAD,	    0},
79
+    {CMD5,  sizeof(CMD5)-1,	COMMAND_PING,	    0},
80
+    {CMD6,  sizeof(CMD6)-1,	COMMAND_CONTSCAN,   1},
81
+    {CMD7,  sizeof(CMD7)-1,	COMMAND_VERSION,    0},
82
+    {CMD8,  sizeof(CMD8)-1,	COMMAND_STREAM,	    0},
83
+    {CMD10, sizeof(CMD10)-1,	COMMAND_END,	    0},
84
+    {CMD11, sizeof(CMD11)-1,	COMMAND_SHUTDOWN,   0},
85
+    {CMD13, sizeof(CMD13)-1,	COMMAND_MULTISCAN,  1},
86
+    {CMD14, sizeof(CMD14)-1,	COMMAND_FILDES,	    0},
87
+    {CMD15, sizeof(CMD15)-1,	COMMAND_STATS,	    0},
88
+    {CMD16, sizeof(CMD16)-1,	COMMAND_IDSESSION,  0},
89
+    {CMD17, sizeof(CMD17)-1,	COMMAND_INSTREAM,   0}
90
+};
91
+
92
+
93
+enum commands parse_command(const char *cmd, const char **argument)
72 94
 {
73
-	struct msghdr msg;
74
-	struct cmsghdr *cmsg;
75
-	unsigned char buf[CMSG_SPACE(sizeof(int))];
76
-	struct iovec iov[1];
77
-	char dummy;
78
-	int ret=-1;
79
-
80
-	memset(&msg, 0, sizeof(msg));
81
-	iov[0].iov_base = &dummy;
82
-	iov[0].iov_len = 1;
83
-	msg.msg_iov = iov;
84
-	msg.msg_iovlen = 1;
85
-	msg.msg_control = buf;
86
-	msg.msg_controllen = sizeof(buf);
87
-
88
-	if (recvmsg(desc, &msg, 0) == -1) {
89
-	    logg("recvmsg failed: %s!", strerror(errno));
90
-	    return -1;
91
-	}
92
-	if ((msg.msg_flags & MSG_TRUNC) || (msg.msg_flags & MSG_CTRUNC)) {
93
-	    logg("control message truncated");
94
-	    return -1;
95
-	}
96
-	for (cmsg = CMSG_FIRSTHDR(&msg); cmsg != NULL;
97
-	    cmsg = CMSG_NXTHDR(&msg, cmsg)) {
98
-		if (cmsg->cmsg_len == CMSG_LEN(sizeof(int)) &&
99
-		    cmsg->cmsg_level == SOL_SOCKET &&
100
-		    cmsg->cmsg_type == SCM_RIGHTS) {
101
-			int fd = *(int *)CMSG_DATA(cmsg);
102
-			ret = scanfd(fd, NULL, engine, options, opts, desc);
103
-			close(fd);
95
+    size_t i;
96
+    *argument = NULL;
97
+    for (i=0; i < sizeof(commands)/sizeof(commands[0]); i++) {
98
+	const size_t len = commands[i].len;
99
+	if (!strncmp(cmd, commands[i].cmd, len)) {
100
+	    const char *arg = cmd + len;
101
+	    if (commands[i].need_arg) {
102
+		if (!*arg) {/* missing argument */
103
+		    logg("*Command %s missing argument!\n", commands[i].cmd);
104
+		    return COMMAND_UNKNOWN;
105
+		}
106
+		*argument = arg+1;
107
+	    } else {
108
+		if (*arg) {/* extra stuff after command */
109
+		    logg("*Command %s has trailing garbage!\n", commands[i].cmd);
110
+		    return COMMAND_UNKNOWN;
104 111
 		}
112
+		*argument = NULL;
113
+	    }
114
+	    return commands[i].cmdtype;
105 115
 	}
106
-	return ret;
116
+    }
117
+    return COMMAND_UNKNOWN;
107 118
 }
108 119
 
109
-#else
110
-static int recvfd_and_scan(int desc, const struct cl_engine *engine, unsigned int options, const struct optstruct *opts)
120
+int conn_reply_single(const client_conn_t *conn, const char *path, const char *status)
111 121
 {
112
-	mdprintf(desc, "ERROR: FILDES support not compiled in\n");
113
-	return -1;
122
+    if (conn->id) {
123
+	if (path)
124
+	    return mdprintf(conn->sd, "%u: %s: %s%c", conn->id, path, status, conn->term);
125
+	return mdprintf(conn->sd, "%u: %s%c", conn->id, status, conn->term);
126
+    }
127
+    if (path)
128
+	return mdprintf(conn->sd, "%s: %s%c", path, status, conn->term);
129
+    return mdprintf(conn->sd, "%s%c", status, conn->term);
114 130
 }
131
+
132
+int conn_reply(const client_conn_t *conn, const char *path,
133
+	       const char *msg, const char *status)
134
+{
135
+    if (conn->id) {
136
+	if (path)
137
+	    return mdprintf(conn->sd, "%u: %s: %s %s%c", conn->id, path, msg,
138
+			    status, conn->term);
139
+	return mdprintf(conn->sd, "%u: %s %s%c", conn->id, msg, status,
140
+			conn->term);
141
+    }
142
+    if (path)
143
+	return mdprintf(conn->sd, "%s: %s %s%c", path, msg, status, conn->term);
144
+    return mdprintf(conn->sd, "%s %s%c", msg, status, conn->term);
145
+}
146
+
147
+int conn_reply_error(const client_conn_t *conn, const char *msg)
148
+{
149
+    return conn_reply(conn, NULL, msg, "ERROR");
150
+}
151
+
152
+#define BUFFSIZE 1024
153
+int conn_reply_errno(const client_conn_t *conn, const char *path,
154
+		     const char *msg)
155
+{
156
+    char buf[BUFFSIZE + sizeof(". ERROR")];
157
+#ifdef HAVE_STRERROR_R
158
+    strerror_r(errno, buf, BUFFSIZE-1);
159
+    strcat(buf, ". ERROR");
160
+#else
161
+    snprintf(buf, sizeof(buf), "%u. ERROR", errno);
115 162
 #endif
163
+    return conn_reply(conn, path, msg, buf);
164
+}
116 165
 
117
-int command(int desc, const struct cl_engine *engine, unsigned int options, const struct optstruct *opts, int timeout)
166
+/* returns
167
+ *  -1 on fatal error (shutdown)
168
+ *  0 on ok
169
+ *  >0 errors encountered
170
+ */
171
+int command(client_conn_t *conn, int *virus)
118 172
 {
119
-	char buff[1025];
120
-	int bread;
173
+    int desc = conn->sd;
174
+    struct cl_engine *engine = conn->engine;
175
+    unsigned int options = conn->options;
176
+    const struct optstruct *opts = conn->opts;
177
+    int type = -1; /* TODO: make this enum */
178
+    int maxdirrec;
179
+    int ret = 0;
180
+    int flags = CLI_FTW_STD;
121 181
 
122
-    bread = readsock(desc, buff, sizeof(buff)-1, '\n', timeout, 0, 1);
123
-    if(bread == -2) /* timeout */
124
-	return -2;
125
-    if(bread == 0) /* Connection closed */
126
-	return -1;
127
-    if(bread < 0) {
128
-	mdprintf(desc, "ERROR\n");
129
-	logg("!Command: readsock() failed.\n");
130
-	return -1;
131
-    }
182
+    struct scan_cb_data scandata;
183
+    struct cli_ftw_cbdata data;
184
+    unsigned ok, error, total;
185
+    jobgroup_t *group = NULL;
132 186
 
133
-    buff[bread] = 0;
134
-    cli_chomp(buff);
187
+    if (thrmgr_group_need_terminate(conn->group)) {
188
+	logg("^Client disconnected while command was active\n");
189
+	if (conn->scanfd != -1)
190
+	    close(conn->scanfd);
191
+	return 1;
192
+    }
135 193
     thrmgr_setactiveengine(engine);
136 194
 
137
-    if(!strncmp(buff, CMD1, strlen(CMD1))) { /* SCAN */
138
-	thrmgr_setactivetask(NULL, CMD1);
139
-	if(scan(buff + strlen(CMD1) + 1, NULL, engine, options, opts, desc, TYPE_SCAN) == -2)
140
-	    if(optget(opts, "ExitOnOOM")->enabled)
141
-		return COMMAND_SHUTDOWN;
142
-
143
-    } else if(!strncmp(buff, CMD3, strlen(CMD3))) { /* QUIT */
144
-	thrmgr_setactivetask(NULL, CMD3);
145
-	return COMMAND_SHUTDOWN;
146
-
147
-    } else if(!strncmp(buff, CMD4, strlen(CMD4))) { /* RELOAD */
148
-	thrmgr_setactivetask(NULL, CMD4);
149
-	/* we'll reload, hide the engine, if we are the last
150
-	 * holding a ref to the engine it'll be freed,
151
-	 * we don't want STATS command to access it */
152
-	thrmgr_setactiveengine(NULL);
153
-	mdprintf(desc, "RELOADING\n");
154
-	return COMMAND_RELOAD;
155
-
156
-    } else if(!strncmp(buff, CMD5, strlen(CMD5))) { /* PING */
157
-	thrmgr_setactivetask(NULL, CMD5);
158
-	mdprintf(desc, "PONG\n");
159
-
160
-    } else if(!strncmp(buff, CMD6, strlen(CMD6))) { /* CONTSCAN */
161
-	thrmgr_setactivetask(NULL, CMD6);
162
-	if(scan(buff + strlen(CMD6) + 1, NULL, engine, options, opts, desc, TYPE_CONTSCAN) == -2)
163
-	    if(optget(opts, "ExitOnOOM")->enabled)
164
-		return COMMAND_SHUTDOWN;
165
-
166
-    } else if(!strncmp(buff, CMD7, strlen(CMD7))) { /* VERSION */
167
-	    uint32_t ver;
168
-
169
-	thrmgr_setactivetask(NULL, CMD7);
170
-	cl_engine_get(engine, CL_ENGINE_DB_VERSION, &ver);
171
-	if(ver) {
172
-		char timestr[32];
173
-		time_t t;
174
-
175
-	    cl_engine_get(engine, CL_ENGINE_DB_TIME, &t);
176
-	    mdprintf(desc, "ClamAV %s/%u/%s", get_version(), (unsigned int) ver, cli_ctime(&t, timestr, sizeof(timestr)));
177
-	} else {
178
-	    mdprintf(desc, "ClamAV %s\n", get_version());
179
-	}
195
+    data.data = &scandata;
196
+    memset(&scandata, 0, sizeof(scandata));
197
+    scandata.id = conn->id;
198
+    scandata.group = conn->group;
199
+    scandata.odesc = desc;
200
+    scandata.conn = conn;
201
+    scandata.options = options;
202
+    scandata.engine = engine;
203
+    scandata.opts = opts;
204
+    scandata.thr_pool = conn->thrpool;
205
+    scandata.toplevel_path = conn->filename;
180 206
 
181
-    } else if(!strncmp(buff, CMD8, strlen(CMD8))) { /* STREAM */
182
-	thrmgr_setactivetask(NULL, CMD8);
183
-	if(scanstream(desc, NULL, engine, options, opts) == CL_EMEM)
184
-	    if(optget(opts, "ExitOnOOM")->enabled)
185
-		return COMMAND_SHUTDOWN;
186
-
187
-    } else if(!strncmp(buff, CMD9, strlen(CMD9))) { /* SESSION */
188
-	thrmgr_setactivetask(NULL, CMD9);
189
-	return COMMAND_SESSION;
190
-
191
-    } else if(!strncmp(buff, CMD10, strlen(CMD10))) { /* END */
192
-	thrmgr_setactivetask(NULL, CMD10);
193
-	return COMMAND_END;
194
-
195
-    } else if(!strncmp(buff, CMD11, strlen(CMD11))) { /* SHUTDOWN */
196
-	thrmgr_setactivetask(NULL, CMD11);
197
-	return COMMAND_SHUTDOWN;
198
-
199
-    } else if(!strncmp(buff, CMD13, strlen(CMD13))) { /* MULTISCAN */
200
-	thrmgr_setactivetask(buff+strlen(CMD13)+1, CMD13);
201
-	if(scan(buff + strlen(CMD13) + 1, NULL, engine, options, opts, desc, TYPE_MULTISCAN) == -2)
202
-	    if(optget(opts, "ExitOnOOM")->enabled)
203
-		return COMMAND_SHUTDOWN;
204
-
205
-    } else if(!strncmp(buff, CMD14, strlen(CMD14))) { /* FILDES */
206
-	thrmgr_setactivetask(NULL, CMD14);
207
-	if(recvfd_and_scan(desc, engine, options, opts) == -2)
208
-	    if(optget(opts, "ExitOnOOM")->enabled)
209
-		return COMMAND_SHUTDOWN;
210
-    } else if(!strncmp(buff, CMD15, strlen(CMD15))) { /* STATS */
211
-	    thrmgr_setactivetask(NULL, CMD15);
207
+    switch (conn->cmdtype) {
208
+	case COMMAND_SCAN:
209
+	    thrmgr_setactivetask(NULL, "SCAN");
210
+	    type = TYPE_SCAN;
211
+	    break;
212
+	case COMMAND_CONTSCAN:
213
+	    thrmgr_setactivetask(NULL, "CONTSCAN");
214
+	    type = TYPE_CONTSCAN;
215
+	    break;
216
+	case COMMAND_MULTISCAN:
217
+	    flags &= ~CLI_FTW_NEED_STAT;
218
+	    thrmgr_setactivetask(NULL, "MULTISCAN");
219
+	    type = TYPE_MULTISCAN;
220
+	    scandata.group = group = thrmgr_group_new();
221
+	    break;
222
+	case COMMAND_MULTISCANFILE:
223
+	    thrmgr_setactivetask(NULL, "MULTISCANFILE");
224
+	    scandata.group = NULL;
225
+	    scandata.type = TYPE_SCAN;
226
+	    scandata.thr_pool = NULL;
227
+	    /* TODO: check ret value */
228
+	    scan_callback(NULL, conn->filename, conn->filename, visit_file, &data);
229
+	    /* callback freed it */
230
+	    conn->filename = NULL;
231
+	    *virus = scandata.infected;
232
+	    return 0;
233
+	case COMMAND_FILDES:
234
+	    thrmgr_setactivetask(NULL, "FILDES");
235
+#ifdef HAVE_FD_PASSING
236
+	    if (conn->scanfd == -1)
237
+		conn_reply_error(conn, "FILDES: didn't receive file descriptor.");
238
+	    else {
239
+		ret = scanfd(conn->scanfd, conn, NULL, engine, options, opts, desc, 0);
240
+		if (ret == CL_VIRUS)
241
+		    *virus = 1;
242
+		if (ret == CL_EMEM) {
243
+		    if(optget(opts, "ExitOnOOM")->enabled)
244
+			ret = -1;
245
+		} else
246
+		    ret = 0;
247
+	    }
248
+	    logg("*SESSION: closed fd %d\n", conn->scanfd);
249
+	    close(conn->scanfd);
250
+	    return ret;
251
+#else
252
+	    conn_reply_error(conn, "FILDES support not compiled in.");
253
+	    close(conn->scanfd);
254
+	    return 0;
255
+#endif
256
+	case COMMAND_STATS:
257
+	    thrmgr_setactivetask(NULL, "STATS");
212 258
 	    thrmgr_printstats(desc);
259
+	    return 0;
260
+	case COMMAND_STREAM:
261
+	    thrmgr_setactivetask(NULL, "STREAM");
262
+	    ret = scanstream(desc, NULL, engine, options, opts, conn->term);
263
+	    if (ret == CL_VIRUS)
264
+		*virus = 1;
265
+	    if (ret == CL_EMEM) {
266
+		if(optget(opts, "ExitOnOOM")->enabled)
267
+		    return -1;
268
+	    }
269
+	    return 0;
270
+	case COMMAND_INSTREAMSCAN:
271
+	    ret = scanfd(conn->scanfd, conn, NULL, engine, options, opts, desc, 1);
272
+	    if (ret == CL_VIRUS)
273
+		*virus = 1;
274
+	    if (ret == CL_EMEM) {
275
+		if(optget(opts, "ExitOnOOM")->enabled)
276
+		    ret = -1;
277
+	    } else
278
+		ret = 0;
279
+	    if (ftruncate(conn->scanfd, 0) == -1) {
280
+		/* not serious, we're going to close it and unlink it anyway */
281
+		logg("*ftruncate failed: %d\n", errno);
282
+	    }
283
+	    close(conn->scanfd);
284
+	    conn->scanfd = -1;
285
+	    cli_unlink(conn->filename);
286
+	    return ret;
287
+    }
288
+
289
+    scandata.type = type;
290
+    maxdirrec = optget(opts, "MaxDirectoryRecursion")->numarg;
291
+    if (optget(opts, "FollowDirectorySymlinks")->enabled)
292
+	flags |= CLI_FTW_FOLLOW_DIR_SYMLINK;
293
+    if (optget(opts, "FollowFileSymlinks")->enabled)
294
+	flags |= CLI_FTW_FOLLOW_FILE_SYMLINK;
295
+    if (cli_ftw(conn->filename, flags,  maxdirrec ? maxdirrec : INT_MAX, scan_callback, &data) == CL_EMEM) 
296
+	if(optget(opts, "ExitOnOOM")->enabled)
297
+	    return -1;
298
+    if (scandata.group && conn->cmdtype == COMMAND_MULTISCAN) {
299
+	thrmgr_group_waitforall(group, &ok, &error, &total);
213 300
     } else {
214
-	mdprintf(desc, "UNKNOWN COMMAND\n");
301
+	error = scandata.errors;
302
+	total = scandata.total;
303
+	ok = total - error - scandata.infected;
215 304
     }
216 305
 
217
-    return 0; /* no error and no 'special' command executed */
306
+    if (ok + error == total && (error != total)) {
307
+	conn_reply_single(conn, conn->filename, "OK");
308
+    }
309
+    *virus = total - (ok + error);
310
+    return error;
311
+}
312
+
313
+static int dispatch_command(client_conn_t *conn, enum commands cmd, const char *argument)
314
+{
315
+    int ret = 0;
316
+    client_conn_t *dup_conn = (client_conn_t *) malloc(sizeof(struct client_conn_tag));
317
+
318
+    if(!dup_conn) {
319
+	logg("!Can't allocate memory for client_conn\n");
320
+	return -1;
321
+    }
322
+    memcpy(dup_conn, conn, sizeof(*conn));
323
+    dup_conn->cmdtype = cmd;
324
+    if(cl_engine_addref(dup_conn->engine)) {
325
+	logg("!cl_engine_addref() failed\n");
326
+	free(dup_conn);
327
+	return -1;
328
+    }
329
+    dup_conn->scanfd = -1;
330
+    switch (cmd) {
331
+	case COMMAND_FILDES:
332
+	    if (conn->scanfd == -1) {
333
+		conn_reply_error(dup_conn, "No file descriptor received.");
334
+		ret = 1;
335
+	    }
336
+	    dup_conn->scanfd = conn->scanfd;
337
+	    /* consume FD */
338
+	    conn->scanfd = -1;
339
+	    break;
340
+	case COMMAND_SCAN:
341
+	case COMMAND_CONTSCAN:
342
+	case COMMAND_MULTISCAN:
343
+	    dup_conn->filename = strdup(argument);
344
+	    if (!dup_conn->filename) {
345
+		logg("!Failed to allocate memory for filename\n");
346
+		ret = -1;
347
+	    }
348
+	    break;
349
+	case COMMAND_INSTREAMSCAN:
350
+	    dup_conn->scanfd = conn->scanfd;
351
+	    conn->scanfd = -1;
352
+	    break;
353
+	case COMMAND_STREAM:
354
+	case COMMAND_STATS:
355
+	    /* just dispatch the command */
356
+	    break;
357
+    }
358
+    if(!ret && !thrmgr_group_dispatch(dup_conn->thrpool, dup_conn->group, dup_conn)) {
359
+	logg("!thread dispatch failed\n");
360
+	ret = -2;
361
+    }
362
+    if (ret) {
363
+	cl_engine_free(dup_conn->engine);
364
+	free(dup_conn);
365
+    }
366
+    return ret;
367
+}
368
+
369
+/* returns:
370
+ *  <0 for error
371
+ *     -1 out of memory
372
+ *     -2 other
373
+ *   0 for async dispatched
374
+ *   1 for command completed (connection can be closed)
375
+ */
376
+int execute_or_dispatch_command(client_conn_t *conn, enum commands cmd, const char *argument)
377
+{
378
+    int desc = conn->sd;
379
+    char term = conn->term;
380
+    const struct cl_engine *engine = conn->engine;
381
+    /* execute commands that can be executed quickly on the recvloop thread,
382
+     * these must:
383
+     *  - not involve any operation that can block for a long time, such as disk
384
+     *  I/O
385
+     *  - send of atomic message is allowed.
386
+     * Dispatch other commands */
387
+    if (conn->group) {
388
+	switch (cmd) {
389
+	    case COMMAND_FILDES:
390
+	    case COMMAND_SCAN:
391
+	    case COMMAND_END:
392
+	    case COMMAND_INSTREAM:
393
+	    case COMMAND_INSTREAMSCAN:
394
+		/* These commands are accepted inside IDSESSION */
395
+		break;
396
+	    default:
397
+		/* these commands are not recognized inside an IDSESSION */
398
+		conn_reply_error(conn, "Command invalid inside IDSESSION.");
399
+		logg("*SESSION: command is not valid inside IDSESSION: %d\n", cmd);
400
+		conn->group = NULL;
401
+		return 1;
402
+	}
403
+    }
404
+
405
+    switch (cmd) {
406
+	case COMMAND_SHUTDOWN:
407
+	    pthread_mutex_lock(&exit_mutex);
408
+	    progexit = 1;
409
+	    pthread_mutex_unlock(&exit_mutex);
410
+	    return 1;
411
+	case COMMAND_RELOAD:
412
+	    pthread_mutex_lock(&reload_mutex);
413
+	    reload = 1;
414
+	    pthread_mutex_unlock(&reload_mutex);
415
+	    mdprintf(desc, "RELOADING%c", term);
416
+	    /* we set reload flag, and we'll reload before closing the
417
+	     * connection */
418
+	    return 1;
419
+	case COMMAND_PING:
420
+	    mdprintf(desc, "PONG%c", term);
421
+	    return 1;
422
+	case COMMAND_VERSION:
423
+	    {
424
+		uint32_t ver;
425
+		cl_engine_get(engine, CL_ENGINE_DB_VERSION, &ver);
426
+		if(ver) {
427
+		    char timestr[32];
428
+		    const char *tstr;
429
+		    time_t t;
430
+		    cl_engine_get(engine, CL_ENGINE_DB_TIME, &t);
431
+		    tstr = cli_ctime(&t, timestr, sizeof(timestr));
432
+		    /* cut trailing \n */
433
+		    timestr[strlen(tstr)-1] = '\0';
434
+		    mdprintf(desc, "ClamAV %s/%u/%s%c", get_version(), (unsigned int) ver, tstr, term);
435
+		} else {
436
+		    mdprintf(desc, "ClamAV %s%c", get_version(), conn->term);
437
+		}
438
+		return 1;
439
+	    }
440
+	case COMMAND_INSTREAM:
441
+	    {
442
+#if 0
443
+		if (!conn->group) {
444
+		    /* only valid inside IDSESSION */
445
+		    conn_reply_single(conn, NULL, "UNKNOWN COMMAND");
446
+		    return 1;
447
+		}
448
+#endif
449
+		int rc = cli_gentempfd(NULL, &conn->filename, &conn->scanfd);
450
+		if (rc != CL_SUCCESS)
451
+		    return rc;
452
+		conn->quota = optget(conn->opts, "StreamMaxLength")->numarg;
453
+		conn->mode = MODE_STREAM;
454
+		return 0;
455
+	    }
456
+	case COMMAND_STREAM:
457
+	case COMMAND_MULTISCAN:
458
+	case COMMAND_CONTSCAN:
459
+	case COMMAND_STATS:
460
+	case COMMAND_FILDES:
461
+	case COMMAND_SCAN:
462
+	case COMMAND_INSTREAMSCAN:
463
+	    return dispatch_command(conn, cmd, argument);
464
+	case COMMAND_IDSESSION:
465
+	    conn->group = thrmgr_group_new();
466
+	    if (!conn->group)
467
+		return CL_EMEM;
468
+	    return 0;
469
+	case COMMAND_END:
470
+	    if (!conn->group) {
471
+		/* end without idsession? */
472
+		conn_reply_single(conn, NULL, "UNKNOWN COMMAND");
473
+		return 1;
474
+	    }
475
+	    /* need to close connection  if we were last in group */
476
+	    return 1;
477
+	/*case COMMAND_UNKNOWN:*/
478
+	default:
479
+	    conn_reply_single(conn, NULL, "UNKNOWN COMMAND");
480
+	    return 1;
481
+    }
218 482
 }
... ...
@@ -19,10 +19,6 @@
19 19
 #ifndef __SESSION_H
20 20
 #define __SESSION_H
21 21
 
22
-#define COMMAND_SHUTDOWN 1
23
-#define COMMAND_RELOAD 2
24
-#define COMMAND_END 3
25
-#define COMMAND_SESSION 4
26 22
 
27 23
 #define CMD1 "SCAN"
28 24
 /* #define CMD2 "RAWSCAN" */
... ...
@@ -32,17 +28,66 @@
32 32
 #define CMD6 "CONTSCAN"
33 33
 #define CMD7 "VERSION"
34 34
 #define CMD8 "STREAM"
35
-#define CMD9 "SESSION"
35
+/*#define CMD9 "SESSION"*/
36 36
 #define CMD10 "END"
37 37
 #define CMD11 "SHUTDOWN"
38 38
 /* #define CMD12 "FD" */
39 39
 #define CMD13 "MULTISCAN"
40 40
 #define CMD14 "FILDES"
41 41
 #define CMD15 "STATS"
42
+#define CMD16 "IDSESSION"
43
+#define CMD17 "INSTREAM"
42 44
 
43 45
 #include "libclamav/clamav.h"
44 46
 #include "shared/optparser.h"
47
+#include "server.h"
48
+#include "others.h"
45 49
 
46
-int command(int desc, const struct cl_engine *engine, unsigned int options, const struct optstruct *opts, int timeout);
50
+enum commands {
51
+    COMMAND_UNKNOWN = 0,
52
+    COMMAND_SHUTDOWN = 1,
53
+    COMMAND_RELOAD,
54
+    COMMAND_END,
55
+    COMMAND_SESSION,
56
+    COMMAND_SCAN,
57
+    COMMAND_PING,
58
+    COMMAND_CONTSCAN,
59
+    COMMAND_VERSION,
60
+    COMMAND_STREAM,
61
+    COMMAND_MULTISCAN,
62
+    COMMAND_FILDES,
63
+    COMMAND_STATS,
64
+    /* new proto commands */
65
+    COMMAND_IDSESSION,
66
+    COMMAND_INSTREAM,
67
+    /* internal commands */
68
+    COMMAND_MULTISCANFILE,
69
+    COMMAND_INSTREAMSCAN
70
+};
47 71
 
72
+typedef struct client_conn_tag {
73
+    enum commands cmdtype;
74
+    char *filename;
75
+    int scanfd;
76
+    int sd;
77
+    unsigned int options;
78
+    const struct optstruct *opts;
79
+    struct cl_engine *engine;
80
+    time_t engine_timestamp;
81
+    char term;
82
+    threadpool_t *thrpool;
83
+    int id;
84
+    long quota;
85
+    jobgroup_t *group;
86
+    enum mode mode;
87
+} client_conn_t;
88
+
89
+int command(client_conn_t *conn, int *virus);
90
+enum commands parse_command(const char *cmd, const char **argument);
91
+int execute_or_dispatch_command(client_conn_t *conn, enum commands command, const char *argument);
92
+
93
+int conn_reply(const client_conn_t *conn, const char *path, const char *msg, const char *status);
94
+int conn_reply_single(const client_conn_t *conn, const char *path, const char *status);
95
+int conn_reply_error(const client_conn_t *conn, const char *msg);
96
+int conn_reply_errno(const client_conn_t *conn, const char *path, const char *msg);
48 97
 #endif
... ...
@@ -25,12 +25,14 @@
25 25
 #include <pthread.h>
26 26
 #include <time.h>
27 27
 #include <errno.h>
28
+#include <string.h>
28 29
 
29 30
 #include "shared/output.h"
30 31
 
31 32
 #include "thrmgr.h"
32 33
 #include "others.h"
33 34
 #include "mpool.h"
35
+#include "server.h"
34 36
 
35 37
 #if defined(C_LINUX)
36 38
 #include <malloc.h>
... ...
@@ -55,6 +57,7 @@ static work_queue_t *work_queue_new(void)
55 55
 
56 56
 	work_q->head = work_q->tail = NULL;
57 57
 	work_q->item_count = 0;
58
+	work_q->popped = 0;
58 59
 	return work_q;
59 60
 }
60 61
 
... ...
@@ -152,6 +155,38 @@ static void remove_frompools(threadpool_t *t)
152 152
 	pthread_mutex_unlock(&pools_lock);
153 153
 }
154 154
 
155
+static void print_queue(int f, work_queue_t *queue, struct timeval *tv_now)
156
+{
157
+    long umin=~0UL, umax=0, usum=0;
158
+    unsigned invalids = 0, cnt = 0;
159
+    work_item_t *q;
160
+
161
+    if(!queue->head)
162
+	return;
163
+    for(q=queue->head;q;q=q->next) {
164
+	long delta;
165
+	delta = tv_now->tv_usec - q->time_queued.tv_usec;
166
+	delta += (tv_now->tv_sec - q->time_queued.tv_sec)*1000000;
167
+	if(delta < 0) {
168
+	    invalids++;
169
+	    continue;
170
+	}
171
+	if(delta > umax)
172
+	    umax = delta;
173
+	if(delta < umin)
174
+	    umin = delta;
175
+	usum += delta;
176
+	++cnt;
177
+    }
178
+    mdprintf(f," min_wait: %.6f max_wait: %.6f avg_wait: %.6f",
179
+	     umin/1e6, umax/1e6, usum /(1e6*cnt));
180
+    if(invalids)
181
+	mdprintf(f," (INVALID timestamps: %u)", invalids);
182
+    if(cnt + invalids != (unsigned)queue->item_count)
183
+	mdprintf(f," (ERROR: %u != %u)", cnt + invalids,
184
+		 (unsigned)queue->item_count);
185
+}
186
+
155 187
 int thrmgr_printstats(int f)
156 188
 {
157 189
 	struct threadpool_list *l;
... ...
@@ -166,10 +201,7 @@ int thrmgr_printstats(int f)
166 166
 	for(l= pools;l && !error_flag;l = l->nxt) {
167 167
 		threadpool_t *pool = l->pool;
168 168
 		const char *state;
169
-		work_item_t *q;
170 169
 		struct timeval tv_now;
171
-		long umin=~0UL, umax=0, usum=0;
172
-		int invalids=0;
173 170
 		struct task_desc *task;
174 171
 		cnt = 0;
175 172
 
... ...
@@ -199,32 +231,11 @@ int thrmgr_printstats(int f)
199 199
 		mdprintf(f, "THREADS: live %u  idle %u max %u idle-timeout %u\n"
200 200
 				,pool->thr_alive, pool->thr_idle, pool->thr_max,
201 201
 				pool->idle_timeout);
202
-		mdprintf(f,"QUEUE: %u items", pool->queue->item_count);
202
+		/* TODO: show both queues */
203
+		mdprintf(f,"QUEUE: %u items", pool->single_queue->item_count + pool->bulk_queue->item_count);
203 204
 		gettimeofday(&tv_now, NULL);
204
-		if(pool->queue->head) {
205
-			for(q=pool->queue->head;q;q=q->next) {
206
-				long delta;
207
-				delta = tv_now.tv_usec - q->time_queued.tv_usec;
208
-				delta += (tv_now.tv_sec - q->time_queued.tv_sec)*1000000;
209
-				if(delta < 0) {
210
-					invalids++;
211
-					continue;
212
-				}
213
-				if(delta > umax)
214
-					umax = delta;
215
-				if(delta < umin)
216
-					umin = delta;
217
-				usum += delta;
218
-				++cnt;
219
-			}
220
-			mdprintf(f," min_wait: %.6f max_wait: %.6f avg_wait: %.6f",
221
-					umin/1e6, umax/1e6, usum /(1e6*cnt));
222
-			if(invalids)
223
-				mdprintf(f," (INVALID timestamps: %u)", invalids);
224
-		}
225
-		if(cnt + invalids != (unsigned)pool->queue->item_count)
226
-			mdprintf(f," (ERROR: %u != %u)", cnt + invalids,
227
-					(unsigned)pool->queue->item_count);
205
+		print_queue(f, pool->bulk_queue, &tv_now);
206
+		print_queue(f, pool->single_queue, &tv_now);
228 207
 		mdprintf(f, "\n");
229 208
 		for(task = pool->tasks; task; task = task->nxt) {
230 209
 			long delta;
... ...
@@ -331,15 +342,17 @@ void thrmgr_destroy(threadpool_t *threadpool)
331 331
 	}
332 332
 
333 333
 	pthread_mutex_destroy(&(threadpool->pool_mutex));
334
-	pthread_cond_destroy(&(threadpool)->idle_cond);
334
+	pthread_cond_destroy(&(threadpool->idle_cond));
335
+	pthread_cond_destroy(&(threadpool->queueable_cond));
335 336
 	pthread_cond_destroy(&(threadpool->pool_cond));
336 337
 	pthread_attr_destroy(&(threadpool->pool_attr));
337
-	free(threadpool->queue);
338
+	free(threadpool->single_queue);
339
+	free(threadpool->bulk_queue);
338 340
 	free(threadpool);
339 341
 	return;
340 342
 }
341 343
 
342
-threadpool_t *thrmgr_new(int max_threads, int idle_timeout, void (*handler)(void *))
344
+threadpool_t *thrmgr_new(int max_threads, int idle_timeout, int max_queue, void (*handler)(void *))
343 345
 {
344 346
 	threadpool_t *threadpool;
345 347
 #if defined(C_BIGSTACK)
... ...
@@ -350,16 +363,30 @@ threadpool_t *thrmgr_new(int max_threads, int idle_timeout, void (*handler)(void
350 350
 		return NULL;
351 351
 	}
352 352
 
353
+	if (2*max_threads > max_queue) {
354
+	    logg("!Configuration error: MaxQueue should be at least twice MaxThreads\n");
355
+	    return NULL;
356
+	}
357
+
353 358
 	threadpool = (threadpool_t *) malloc(sizeof(threadpool_t));
354 359
 	if (!threadpool) {
355 360
 		return NULL;
356 361
 	}
357 362
 
358
-	threadpool->queue = work_queue_new();
359
-	if (!threadpool->queue) {
363
+	threadpool->single_queue = work_queue_new();
364
+	if (!threadpool->single_queue) {
360 365
 		free(threadpool);
361 366
 		return NULL;
362 367
 	}
368
+	threadpool->bulk_queue = work_queue_new();
369
+	if (!threadpool->bulk_queue) {
370
+		free(threadpool->single_queue);
371
+		free(threadpool);
372
+		return NULL;
373
+	}
374
+
375
+	threadpool->queue_max = max_queue;
376
+
363 377
 	threadpool->thr_max = max_threads;
364 378
 	threadpool->thr_alive = 0;
365 379
 	threadpool->thr_idle = 0;
... ...
@@ -368,41 +395,58 @@ threadpool_t *thrmgr_new(int max_threads, int idle_timeout, void (*handler)(void
368 368
 	threadpool->tasks = NULL;
369 369
 
370 370
 	if(pthread_mutex_init(&(threadpool->pool_mutex), NULL)) {
371
-		free(threadpool->queue);
371
+		free(threadpool->single_queue);
372
+		free(threadpool->bulk_queue);
372 373
 		free(threadpool);
373 374
 		return NULL;
374 375
 	}
375 376
 
376 377
 	if (pthread_cond_init(&(threadpool->pool_cond), NULL) != 0) {
377 378
 		pthread_mutex_destroy(&(threadpool->pool_mutex));
378
-		free(threadpool->queue);
379
+		free(threadpool->single_queue);
380
+		free(threadpool->bulk_queue);
381
+		free(threadpool);
382
+		return NULL;
383
+	}
384
+
385
+	if (pthread_cond_init(&(threadpool->queueable_cond), NULL) != 0) {
386
+		pthread_cond_destroy(&(threadpool->pool_cond));
387
+		pthread_mutex_destroy(&(threadpool->pool_mutex));
388
+		free(threadpool->single_queue);
389
+		free(threadpool->bulk_queue);
379 390
 		free(threadpool);
380 391
 		return NULL;
381 392
 	}
382 393
 
383 394
 	if (pthread_cond_init(&(threadpool->idle_cond),NULL) != 0)  {
395
+		pthread_cond_destroy(&(threadpool->queueable_cond));
384 396
 		pthread_cond_destroy(&(threadpool->pool_cond));
385 397
 		pthread_mutex_destroy(&(threadpool->pool_mutex));
386
-		free(threadpool->queue);
398
+		free(threadpool->single_queue);
399
+		free(threadpool->bulk_queue);
387 400
 		free(threadpool);
388 401
 		return NULL;
389 402
 	}
390 403
 
391 404
 	if (pthread_attr_init(&(threadpool->pool_attr)) != 0) {
405
+		pthread_cond_destroy(&(threadpool->queueable_cond));
392 406
 		pthread_cond_destroy(&(threadpool->idle_cond));
393 407
 		pthread_cond_destroy(&(threadpool->pool_cond));
394 408
 		pthread_mutex_destroy(&(threadpool->pool_mutex));
395
-		free(threadpool->queue);
409
+		free(threadpool->single_queue);
410
+		free(threadpool->bulk_queue);
396 411
 		free(threadpool);
397 412
 		return NULL;
398 413
 	}
399 414
 
400 415
 	if (pthread_attr_setdetachstate(&(threadpool->pool_attr), PTHREAD_CREATE_DETACHED) != 0) {
416
+		pthread_cond_destroy(&(threadpool->queueable_cond));
401 417
 		pthread_attr_destroy(&(threadpool->pool_attr));
402 418
 		pthread_cond_destroy(&(threadpool->idle_cond));
403 419
 		pthread_cond_destroy(&(threadpool->pool_cond));
404 420
 		pthread_mutex_destroy(&(threadpool->pool_mutex));
405
-		free(threadpool->queue);
421
+		free(threadpool->single_queue);
422
+		free(threadpool->bulk_queue);
406 423
 		free(threadpool);
407 424
 		return NULL;
408 425
 	}
... ...
@@ -431,7 +475,7 @@ static void stats_tls_key_alloc(void)
431 431
 static const char *IDLE_TASK = "IDLE";
432 432
 
433 433
 /* no mutex is needed, we are using  thread local variable */
434
-void thrmgr_setactivetask(const char *filename, const char* command)
434
+void thrmgr_setactivetask(const char *filename, const char* cmd)
435 435
 {
436 436
 	struct task_desc *desc;
437 437
 	pthread_once(&stats_tls_key_once, stats_tls_key_alloc);
... ...
@@ -439,10 +483,10 @@ void thrmgr_setactivetask(const char *filename, const char* command)
439 439
 	if(!desc)
440 440
 		return;
441 441
 	desc->filename = filename;
442
-	if(command) {
443
-		if(command == IDLE_TASK && desc->command == command)
442
+	if(cmd) {
443
+		if(cmd == IDLE_TASK && desc->command == cmd)
444 444
 			return;
445
-		desc->command = command;
445
+		desc->command = cmd;
446 446
 		gettimeofday(&desc->tv, NULL);
447 447
 	}
448 448
 }
... ...
@@ -490,6 +534,55 @@ static void stats_destroy(threadpool_t *pool)
490 490
 	pthread_setspecific(stats_tls_key, NULL);
491 491
 }
492 492
 
493
+static inline int thrmgr_contended(threadpool_t *pool)
494
+{
495
+    return pool->bulk_queue->item_count + pool->single_queue->item_count
496
+	+ pool->thr_alive - pool->thr_idle >= pool->queue_max;
497
+}
498
+
499
+/* when both queues have tasks, it will pick 4 items from the single queue,
500
+ * and 1 from the bulk */
501
+#define SINGLE_BULK_RATIO 4
502
+#define SINGLE_BULK_SUM (SINGLE_BULK_RATIO + 1)
503
+
504
+/* must be called with pool_mutex held */
505
+static void *thrmgr_pop(threadpool_t *pool)
506
+{
507
+    void *task;
508
+    work_queue_t *first, *second;
509
+    int ratio;
510
+
511
+    if (pool->single_queue->popped < SINGLE_BULK_RATIO) {
512
+	first = pool->single_queue;
513
+	second = pool->bulk_queue;
514
+	ratio = SINGLE_BULK_RATIO;
515
+    } else {
516
+	second = pool->single_queue;
517
+	first = pool->bulk_queue;
518
+	ratio = SINGLE_BULK_SUM - SINGLE_BULK_RATIO;
519
+    }
520
+
521
+    task = work_queue_pop(first);
522
+    if (task) {
523
+	if (++first->popped == ratio)
524
+	    second->popped = 0;
525
+    } else {
526
+	task = work_queue_pop(second);
527
+	if (task) {
528
+	    if (++second->popped == ratio)
529
+		first->popped = 0;
530
+	}
531
+    }
532
+
533
+    if (!thrmgr_contended(pool)) {
534
+	logg("*THRMGR: queue crossed low threshold -> signaling\n");
535
+	pthread_cond_signal(&pool->queueable_cond);
536
+    }
537
+
538
+    return task;
539
+}
540
+
541
+
493 542
 static void *thrmgr_worker(void *arg)
494 543
 {
495 544
 	threadpool_t *threadpool = (threadpool_t *) arg;
... ...
@@ -512,7 +605,7 @@ static void *thrmgr_worker(void *arg)
512 512
 		timeout.tv_sec = time(NULL) + threadpool->idle_timeout;
513 513
 		timeout.tv_nsec = 0;
514 514
 		threadpool->thr_idle++;
515
-		while (((job_data=work_queue_pop(threadpool->queue)) == NULL)
515
+		while (((job_data=thrmgr_pop(threadpool)) == NULL)
516 516
 				&& (threadpool->state != POOL_EXIT)) {
517 517
 			/* Sleep, awaiting wakeup */
518 518
 			pthread_cond_signal(&threadpool->idle_cond);
... ...
@@ -557,8 +650,9 @@ static void *thrmgr_worker(void *arg)
557 557
 	return NULL;
558 558
 }
559 559
 
560
-int thrmgr_dispatch(threadpool_t *threadpool, void *user_data)
560
+static int thrmgr_dispatch_internal(threadpool_t *threadpool, void *user_data, int bulk)
561 561
 {
562
+	int ret = TRUE;
562 563
 	pthread_t thr_id;
563 564
 
564 565
 	if (!threadpool) {
... ...
@@ -571,35 +665,179 @@ int thrmgr_dispatch(threadpool_t *threadpool, void *user_data)
571 571
 		return FALSE;
572 572
 	}
573 573
 
574
-	if (threadpool->state != POOL_VALID) {
575
-		if (pthread_mutex_unlock(&(threadpool->pool_mutex)) != 0) {
576
-			logg("!Mutex unlock failed\n");
577
-		}
578
-		return FALSE;
579
-	}
580
-	if (!work_queue_add(threadpool->queue, user_data)) {
581
-		if (pthread_mutex_unlock(&(threadpool->pool_mutex)) != 0) {
582
-			logg("!Mutex unlock failed\n");
583
-			return FALSE;
584
-		}
585
-		return FALSE;
586
-	}
574
+	do {
575
+	    work_queue_t *queue;
576
+	    int items;
577
+
578
+	    if (threadpool->state != POOL_VALID) {
579
+		ret = FALSE;
580
+		break;
581
+	    }
582
+
583
+	    if (bulk)
584
+		queue = threadpool->bulk_queue;
585
+	    else
586
+		queue = threadpool->single_queue;
587 587
 
588
-	if ((threadpool->thr_idle < threadpool->queue->item_count) &&
589
-			(threadpool->thr_alive < threadpool->thr_max)) {
588
+	    while (thrmgr_contended(threadpool)) {
589
+		logg("*THRMGR: contended, sleeping\n");
590
+		pthread_cond_wait(&threadpool->queueable_cond, &threadpool->pool_mutex);
591
+		logg("*THRMGR: contended, woken\n");
592
+	    }
593
+
594
+	    if (!work_queue_add(queue, user_data)) {
595
+		ret = FALSE;
596
+		break;
597
+	    }
598
+
599
+	    items = threadpool->single_queue->item_count + threadpool->bulk_queue->item_count;
600
+	    if ((threadpool->thr_idle < items) &&
601
+		(threadpool->thr_alive < threadpool->thr_max)) {
590 602
 		/* Start a new thread */
591 603
 		if (pthread_create(&thr_id, &(threadpool->pool_attr),
592
-				thrmgr_worker, threadpool) != 0) {
593
-			logg("!pthread_create failed\n");
604
+				   thrmgr_worker, threadpool) != 0) {
605
+		    logg("!pthread_create failed\n");
594 606
 		} else {
595
-			threadpool->thr_alive++;
607
+		    threadpool->thr_alive++;
596 608
 		}
597
-	}
598
-	pthread_cond_signal(&(threadpool->pool_cond));
609
+	    }
610
+	    pthread_cond_signal(&(threadpool->pool_cond));
611
+
612
+	} while (0);
599 613
 
600 614
 	if (pthread_mutex_unlock(&(threadpool->pool_mutex)) != 0) {
601
-		logg("!Mutex unlock failed\n");
602
-		return FALSE;
615
+	    logg("!Mutex unlock failed\n");
616
+	    return FALSE;
603 617
 	}
604
-	return TRUE;
618
+	return ret;
619
+}
620
+
621
+int thrmgr_dispatch(threadpool_t *threadpool, void *user_data)
622
+{
623
+    return thrmgr_dispatch_internal(threadpool, user_data, 0);
624
+}
625
+
626
+int thrmgr_group_dispatch(threadpool_t *threadpool, jobgroup_t *group, void *user_data)
627
+{
628
+    int ret;
629
+    if (group) {
630
+	pthread_mutex_lock(&group->mutex);
631
+	group->jobs++;
632
+	pthread_mutex_unlock(&group->mutex);
633
+    }
634
+    if (!(ret = thrmgr_dispatch_internal(threadpool, user_data, 1)) && group) {
635
+	pthread_mutex_lock(&group->mutex);
636
+	group->jobs--;
637
+	pthread_mutex_unlock(&group->mutex);
638
+    }
639
+    return ret;
640
+}
641
+
642
+/* returns
643
+ *   0 - this was not the last thread in the group
644
+ *   1 - this was last thread in group, group freed
645
+ */
646
+int thrmgr_group_finished(jobgroup_t *group, enum thrmgr_exit exitc)
647
+{
648
+    int ret = 0;
649
+    if (!group) {
650
+	/* there is no group, we are obviously the last one */
651
+	return 1;
652
+    }
653
+    pthread_mutex_lock(&group->mutex);
654
+    logg("*THRMGR: group_finished: %p, %d\n", group, group->jobs);
655
+    group->exit_total++;
656
+    switch (exitc) {
657
+	case EXIT_OK:
658
+	    group->exit_ok++;
659
+	    break;
660
+	case EXIT_ERROR:
661
+	    group->exit_error++;
662
+	    break;
663
+    }
664
+    if (group->jobs) {
665
+	if (!--group->jobs) {
666
+	    ret = 1;
667
+	}
668
+	if (group->jobs == 1)
669
+	    pthread_cond_signal(&group->only);
670
+    }
671
+    pthread_mutex_unlock(&group->mutex);
672
+    if (ret) {
673
+	logg("*THRMGR: group_finished: freeing %p\n", group);
674
+	free(group);
675
+    }
676
+    return ret;
677
+}
678
+
679
+void thrmgr_group_waitforall(jobgroup_t *group, unsigned *ok, unsigned *error, unsigned *total)
680
+{
681
+    int needexit = 0, needfree = 0;
682
+    struct timespec timeout;
683
+    pthread_mutex_lock(&group->mutex);
684
+    while (group->jobs > 1) {
685
+	pthread_mutex_lock(&exit_mutex);
686
+	needexit = progexit;
687
+	pthread_mutex_unlock(&exit_mutex);
688
+	if (needexit)
689
+	    break;
690
+	/* wake to check progexit */
691
+	timeout.tv_sec = time(NULL) + 5;
692
+	timeout.tv_nsec = 0;
693
+	pthread_cond_timedwait(&group->only, &group->mutex, &timeout);
694
+    }
695
+    *ok = group->exit_ok;
696
+    *error = group->exit_error + needexit;
697
+    *total = group->exit_total;
698
+    if(!--group->jobs)
699
+	needfree = 1;
700
+    pthread_mutex_unlock(&group->mutex);
701
+    if (needfree) {
702
+	logg("*THRMGR: freeing %p\n", group);
703
+	free(group);
704
+    }
705
+}
706
+
707
+#define JOBGROUP_INITIALIZER  { PTHREAD_MUTEX_INITIALIZER, PTHREAD_COND_INITIALIZER, 1, 0, 0, 0, 0 };
708
+jobgroup_t *thrmgr_group_new(void)
709
+{
710
+    jobgroup_t *group;
711
+    jobgroup_t dummy = JOBGROUP_INITIALIZER;
712
+
713
+    group = malloc(sizeof(*group));
714
+    if (!group)
715
+	return NULL;
716
+    memcpy(group, &dummy, sizeof(dummy));
717
+    return group;
718
+}
719
+
720
+int thrmgr_group_need_terminate(jobgroup_t *group)
721
+{
722
+    int ret;
723
+    if (group) {
724
+	pthread_mutex_lock(&group->mutex);
725
+	ret = group->force_exit;
726
+	pthread_mutex_unlock(&group->mutex);
727
+    } else
728
+	ret = 0;
729
+    pthread_mutex_lock(&exit_mutex);
730
+    ret |= progexit;
731
+    pthread_mutex_unlock(&exit_mutex);
732
+    return ret;
733
+}
734
+
735
+/* returns
736
+ *  0 - flag set, jobs still active
737
+ *  1 - last job exited */
738
+int thrmgr_group_terminate(jobgroup_t *group)
739
+{
740
+    if (thrmgr_group_finished(group, EXIT_ERROR))
741
+	return 1;
742
+    /* we are not last active job, now
743
+     * the last active job will free resources */
744
+    pthread_mutex_lock(&group->mutex);
745
+    group->force_exit = 1;
746
+    pthread_mutex_unlock(&group->mutex);
747
+
748
+    return 0;
605 749
 }
... ...
@@ -36,6 +36,7 @@ typedef struct work_queue_tag {
36 36
 	work_item_t *head;
37 37
 	work_item_t *tail;
38 38
 	int item_count;
39
+	int popped;
39 40
 } work_queue_t;
40 41
 
41 42
 typedef enum {
... ...
@@ -59,22 +60,47 @@ typedef struct threadpool_tag {
59 59
 	pthread_attr_t pool_attr;
60 60
 
61 61
 	pthread_cond_t  idle_cond;
62
+	pthread_cond_t  queueable_cond;
62 63
 	
63 64
 	pool_state_t state;
64 65
 	int thr_max;
66
+	int queue_max;
65 67
 	int thr_alive;
66 68
 	int thr_idle;
67 69
 	int idle_timeout;
68 70
 	struct task_desc *tasks;
69 71
 	
70 72
 	void (*handler)(void *);
71
-	
72
-	work_queue_t *queue;
73
+
74
+	work_queue_t *bulk_queue;
75
+	work_queue_t *single_queue;
73 76
 } threadpool_t;
74 77
 
75
-threadpool_t *thrmgr_new(int max_threads, int idle_timeout, void (*handler)(void *));
78
+typedef struct jobgroup {
79
+    pthread_mutex_t mutex;
80
+    pthread_cond_t only;
81
+    unsigned	jobs;
82
+    unsigned	exit_ok;
83
+    unsigned	exit_error;
84
+    unsigned	exit_total;
85
+    int		force_exit;
86
+} jobgroup_t;
87
+
88
+enum thrmgr_exit {
89
+    EXIT_OK,
90
+    EXIT_ERROR,
91
+    EXIT_OTHER
92
+};
93
+
94
+threadpool_t *thrmgr_new(int max_threads, int idle_timeout, int max_queue, void (*handler)(void *));
76 95
 void thrmgr_destroy(threadpool_t *threadpool);
77 96
 int thrmgr_dispatch(threadpool_t *threadpool, void *user_data);
97
+int thrmgr_group_dispatch(threadpool_t *threadpool, jobgroup_t *group, void *user_data);
98
+void thrmgr_group_waitforall(jobgroup_t *group, unsigned *ok, unsigned *error, unsigned *total);
99
+int thrmgr_group_finished(jobgroup_t *group, enum thrmgr_exit exitc);
100
+int thrmgr_group_need_terminate(jobgroup_t *group);
101
+int thrmgr_group_terminate(jobgroup_t *group);
102
+jobgroup_t *thrmgr_group_new(void);
78 103
 int thrmgr_printstats(int outfd);
79 104
 void thrmgr_setactivetask(const char *filename, const char* command);
80 105
 void thrmgr_setactiveengine(const struct cl_engine *engine);
... ...
@@ -188,6 +188,8 @@ const struct clam_option clam_options[] = {
188 188
 
189 189
     { "ReadTimeout", NULL, 0, TYPE_NUMBER, MATCH_NUMBER, 120, NULL, 0, OPT_MILTER, "Waiting for data from clamd will timeout after this time (seconds).\nValue of 0 disables the timeout.", "300" },
190 190
 
191
+    { "MaxQueue", NULL, 0, TYPE_NUMBER, MATCH_NUMBER, 100, NULL, 0, OPT_CLAMD, "Maximum number of queued items (including those being processed)\nWARNING: you shouldn't increase this beyond 512, since you may run out of File Descriptors (usual max is 1024)\n", "200" },
192
+
191 193
     { "IdleTimeout", NULL, 0, TYPE_NUMBER, MATCH_NUMBER, 30, NULL, 0, OPT_CLAMD, "This option specifies how long (in seconds) the process should wait\nfor a new job.", "60" },
192 194
 
193 195
     { "ExcludePath", NULL, 0, TYPE_STRING, NULL, -1, NULL, FLAG_MULTIPLE, OPT_CLAMD, "Don't scan files/directories whose names match the provided\nregular expression. This option can be specified multiple times.", "^/proc/\n^/sys/" },