1 /* $Id: ovdb.c 10513 2021-01-15 22:34:56Z iulius $
2  *
3  * ovdb.c
4  * ovdb 2.00
5  * Overview storage using Berkeley DB 4.4 or higher
6  *
7  * 2004-02-17 : Need to track search cursors, since it's possible that
8  *              ovdb_closesearch does not get called.  We now close
9  *              any cursors still open in ovdb_close, or they'd be in
10  *              the database indefinitely causing deadlocks.
11  * 2002-08-13 : Change BOOL to bool, remove portability to < 2.4.
12  * 2002-08-11 : Cleaned up use of sprintf and fixed a bunch of warnings.
13  * 2002-02-28 : Update getartinfo for the overview API change in 2.4.  This
14  *              breaks compatibility with INN 2.3.x....
15  * 2000-12-12 : Add support for Berkeley DB DB_SYSTEM_MEM option, controlled
16  *            : by ovdb.conf 'useshm' and 'shmkey'
17  * 2000-11-27 : Update for DB 3.2.x compatibility
18  * 2000-11-13 : New 'readserver' feature
19  * 2000-10-10 : ovdb_search now closes the cursor right after the last
20  *              record is read.
21  * 2000-10-05 : artnum member of struct datakey changed from ARTNUM to u_int32_t.
22  *              OS's where sizeof(long)==8 will have to rebuild their databases
23  *              after this update.
24  * 2000-10-05 : from Dan Riley: struct datakey needs to be zero'd, for
25  *              64-bit OSs where the struct has internal padding bytes.
26  * 2000-09-29 : ovdb_expiregroup can now fix incorrect counts; use new
27  *              inn/version.h so can have same ovdb.c for 2.3.0, 2.3.1, and 2.4
28  * 2000-09-28 : low mark in ovdb_expiregroup still wasn't right
29  * 2000-09-27 : Further improvements to ovdb_expiregroup: restructured the
30  *              loop; now updates groupinfo as it goes along rather than
31  *              counting records at the end, which prevents a possible
32  *              deadlock.
33  * 2000-09-19 : *lo wasn't being set in ovdb_expiregroup
34  * 2000-09-15 : added ovdb_check_user(); tweaked some error msgs; fixed an
35  *              improper use of RENEW
36  * 2000-08-28 : New major release: version 2.00 (beta)
37  *    + "groupsbyname" and "groupstats" databases replaced with "groupinfo".
38  *    + ovdb_recover, ovdb_upgrade, and dbprocs are now deprecated; their
39  *         functionality is now in ovdb_init and ovdb_monitor.
40  *    + ovdb_init can upgrade a database from the old version of ovdb to
41  *         work with this version.
42  *    + Rewrote ovdb_expiregroup(); it can now re-write OV data rather
43  *         than simply deleting old keys (which can leave 'holes' that result
44  *         in inefficient disk-space use).
45  *    + Add "nocompact" to ovdb.conf, which controls whether ovdb_expiregroup()
46  *         rewrites OV data.
47  *    + No longer needs the Berkeley DB tools db_archive, db_checkpoint, and
48  *         db_deadlock.  That functionality is now in ovdb_monitor.
49  *    + ovdb_open() won't succeed if ovdb_monitor is not running.  This will
50  *         prevent the problems that happen if the database is not regularly
51  *         checkpointed and deadlock-tested.
52  *    + Internal group IDs (32-bit ints) are now reused.
53  *    + Add "maxlocks" to ovdb.conf, which will set the DB lk_max parameter.
54  *    + Pull "test" code out into ovdb_stat.  ovdb_stat will also provide
55  *         functionality similar to the Berkeley DB "db_stat" command.
56  *    + Update docs: write man pages for the new ovdb_* commands; update
57  *         ovdb.pod
58  *
59  * 2000-07-11 : fix possible alignment problem; add test code
60  * 2000-07-07 : bugfix: timestamp handling
61  * 2000-06-10 : Modified groupnum() interface; fix ovdb_add() to return false
62  *              for certain groupnum() errors
63  * 2000-06-08 : Added Berkeley DB 3.1.x compatibility
64  * 2000-04-09 : Tweak some default parameters; store aliased group info
65  * 2000-03-29 : Add DB_RMW flag to the 'get' of get-modify-put sequences
66  * 2000-02-17 : Update expire behavior to be consistent with current
67  *              ov3 and buffindexed
68  * 2000-01-13 : Fix to make compatible with unmodified nnrpd/article.c
69  * 2000-01-04 : Added data versioning
70  * 1999-12-20 : Added Berkeley DB 3.x compatibility
71  * 1999-12-06 : First Release -- H. Kehoe <hakehoe@avalon.net>
72  */
73 
74 #include "config.h"
75 #include "clibrary.h"
76 #include "portable/socket.h"
77 #include <errno.h>
78 #include <fcntl.h>
79 #ifdef HAVE_LIMITS_H
80 # include <limits.h>
81 #endif
82 #include <signal.h>
83 #ifdef HAVE_SYS_SELECT_H
84 # include <sys/select.h>
85 #endif
86 #ifdef HAVE_ZLIB
87 # include <zlib.h>
88 # define MAX_UNZIP_SZ 100000
89 # define COMPRESS_MIN 600
90 #endif
91 
92 #ifdef HAVE_SYS_TIME_H
93 # include <sys/time.h>
94 #endif
95 #include <time.h>
96 
97 #include "conffile.h"
98 #include "inn/fdflag.h"
99 #include "inn/innconf.h"
100 #include "inn/libinn.h"
101 #include "inn/messages.h"
102 #include "inn/newsuser.h"
103 #include "inn/paths.h"
104 #include "inn/storage.h"
105 
106 #ifdef HAVE_UNIX_DOMAIN_SOCKETS
107 # include "portable/socket-unix.h"
108 #endif
109 
110 #include "inn/ov.h"
111 #include "ovinterface.h"
112 #include "ovdb.h"
113 #include "ovdb-private.h"
114 #ifndef HAVE_BDB
115 
116 /* Provide stub functions if we don't have db */
117 
ovdb_open(int mode UNUSED)118 bool ovdb_open(int mode UNUSED)
119 {
120     warn("OVDB: ovdb support not enabled");
121     return false;
122 }
123 
ovdb_groupstats(const char * group UNUSED,int * lo UNUSED,int * hi UNUSED,int * count UNUSED,int * flag UNUSED)124 bool ovdb_groupstats(const char *group UNUSED, int *lo UNUSED, int *hi UNUSED, int *count UNUSED, int *flag UNUSED)
125 { return false; }
126 
ovdb_groupadd(const char * group UNUSED,ARTNUM lo UNUSED,ARTNUM hi UNUSED,char * flag UNUSED)127 bool ovdb_groupadd(const char *group UNUSED, ARTNUM lo UNUSED, ARTNUM hi UNUSED, char *flag UNUSED)
128 { return false; }
129 
ovdb_groupdel(const char * group UNUSED)130 bool ovdb_groupdel(const char *group UNUSED)
131 { return false; }
132 
ovdb_add(const char * group UNUSED,ARTNUM artnum UNUSED,TOKEN token UNUSED,char * data UNUSED,int len UNUSED,time_t arrived UNUSED,time_t expires UNUSED)133 bool ovdb_add(const char *group UNUSED, ARTNUM artnum UNUSED, TOKEN token UNUSED, char *data UNUSED, int len UNUSED, time_t arrived UNUSED, time_t expires UNUSED)
134 { return false; }
135 
ovdb_cancel(const char * group UNUSED,ARTNUM artnum UNUSED)136 bool ovdb_cancel(const char *group UNUSED, ARTNUM artnum UNUSED)
137 { return false; }
138 
ovdb_opensearch(const char * group UNUSED,int low UNUSED,int high UNUSED)139 void *ovdb_opensearch(const char *group UNUSED, int low UNUSED, int high UNUSED)
140 { return NULL; }
141 
ovdb_search(void * handle UNUSED,ARTNUM * artnum UNUSED,char ** data UNUSED,int * len UNUSED,TOKEN * token UNUSED,time_t * arrived UNUSED)142 bool ovdb_search(void *handle UNUSED, ARTNUM *artnum UNUSED, char **data UNUSED, int *len UNUSED, TOKEN *token UNUSED, time_t *arrived UNUSED)
143 { return false; }
144 
ovdb_closesearch(void * handle UNUSED)145 void ovdb_closesearch(void *handle UNUSED) { }
146 
ovdb_getartinfo(const char * group UNUSED,ARTNUM artnum UNUSED,TOKEN * token UNUSED)147 bool ovdb_getartinfo(const char *group UNUSED, ARTNUM artnum UNUSED, TOKEN *token UNUSED)
148 { return false; }
149 
ovdb_expiregroup(const char * group UNUSED,int * lo UNUSED,struct history * h UNUSED)150 bool ovdb_expiregroup(const char *group UNUSED, int *lo UNUSED, struct history *h UNUSED)
151 { return false; }
152 
ovdb_ctl(OVCTLTYPE type UNUSED,void * val UNUSED)153 bool ovdb_ctl(OVCTLTYPE type UNUSED, void *val UNUSED)
154 { return false; }
155 
ovdb_close(void)156 void ovdb_close(void) { }
157 
158 #else /* HAVE_BDB */
159 
160 #define EXPIREGROUP_TXN_SIZE 100
161 #define DELETE_TXN_SIZE 500
162 
163 int ovdb_data_ver = 0;
164 struct ovdb_conf ovdb_conf;
165 DB_ENV *OVDBenv = NULL;
166 
167 static int OVDBmode;
168 static bool Cutofflow;
169 static DB **dbs = NULL;
170 static int oneatatime = 0;
171 static int current_db = -1;
172 static time_t eo_start = 0;
173 static int clientmode = 0;
174 
175 static DB *groupinfo = NULL;
176 static DB *groupaliases = NULL;
177 
178 #define OVDBtxn_nosync	1
179 #define OVDBnumdbfiles	2
180 #define OVDBpagesize	3
181 #define OVDBcachesize	4
182 #define OVDBminkey	5
183 #define OVDBmaxlocks	6
184 #define OVDBnocompact	7
185 #define OVDBreadserver	8
186 #define OVDBnumrsprocs	9
187 #define OVDBmaxrsconn	10
188 #define OVDBuseshm	11
189 #define OVDBshmkey	12
190 #define OVDBcompress	13
191 #define OVDBncache	14
192 
193 static CONFTOKEN toks[] = {
194     { OVDBtxn_nosync,   (char *) "txn_nosync"   },
195     { OVDBnumdbfiles,   (char *) "numdbfiles"   },
196     { OVDBpagesize,     (char *) "pagesize"     },
197     { OVDBcachesize,    (char *) "cachesize"    },
198     { OVDBminkey,       (char *) "minkey"       },
199     { OVDBmaxlocks,     (char *) "maxlocks"     },
200     { OVDBnocompact,    (char *) "nocompact"    },
201     { OVDBreadserver,   (char *) "readserver"   },
202     { OVDBnumrsprocs,   (char *) "numrsprocs"   },
203     { OVDBmaxrsconn,    (char *) "maxrsconn"    },
204     { OVDBuseshm,       (char *) "useshm"       },
205     { OVDBshmkey,       (char *) "shmkey"       },
206     { OVDBcompress,     (char *) "compress"     },
207     { OVDBncache,       (char *) "ncache"       },
208     { 0,                NULL                    }
209 };
210 
211 #define _PATH_OVDBCONF "ovdb.conf"
212 
213 /*********** readserver functions ***********/
214 
215 static int clientfd = -1;
216 
217 /* read client send and receive functions. */
218 
219 static int
csend(const void * data,int n)220 csend(const void *data, int n)
221 {
222     ssize_t status;
223 
224     if (n == 0)
225 	return 0;
226     status = xwrite(clientfd, data, n);
227     if (status < 0)
228         syswarn("OVDB: rc: cant write");
229     return status;
230 }
231 
crecv(void * data,int n)232 static int crecv(void *data, int n)
233 {
234     int r, p = 0;
235 
236     if(n == 0)
237 	return 0;
238 
239     while(p < n) {
240 	r = read(clientfd, (char *)data + p, n - p);
241 	if(r <= 0) {
242 	    if(r < 0 && errno == EINTR)
243 		continue;
244             syswarn("OVDB: rc: cant read");
245 	    clientfd = -1;
246 	    exit(1);
247 	}
248 	p+= r;
249     }
250     return p;
251 }
252 
253 /* Attempt to connect to the readserver.  If anything fails, we
254    return -1 so that ovdb_open can open the database directly. */
255 
client_connect(void)256 static int client_connect(void)
257 {
258     ssize_t r;
259     size_t p = 0;
260     char *path;
261 #ifdef HAVE_UNIX_DOMAIN_SOCKETS
262     struct sockaddr_un sa;
263 #else
264     struct sockaddr_in sa;
265 #endif
266     char banner[sizeof(OVDB_SERVER_BANNER)];
267     fd_set fds;
268     struct timeval timeout;
269 
270 #ifdef HAVE_UNIX_DOMAIN_SOCKETS
271     clientfd = socket(AF_UNIX, SOCK_STREAM, 0);
272 #else
273     clientfd = socket(AF_INET, SOCK_STREAM, 0);
274 #endif
275     if(clientfd < 0) {
276         syswarn("OVDB: socket");
277 	return -1;
278     }
279 
280     memset(&sa, 0, sizeof sa);
281 #ifdef HAVE_UNIX_DOMAIN_SOCKETS
282     sa.sun_family = AF_UNIX;
283     path = concatpath(innconf->pathrun, OVDB_SERVER_SOCKET);
284     strlcpy(sa.sun_path, path, sizeof(sa.sun_path));
285     free(path);
286     r = connect(clientfd, (struct sockaddr *) &sa, SUN_LEN(&sa));
287 #else
288     sa.sin_family = AF_INET;
289     sa.sin_port = 0;
290     sa.sin_addr.s_addr = htonl(0x7f000001UL);
291     bind(clientfd, (struct sockaddr *) &sa, sizeof sa);
292     sa.sin_port = htons(OVDB_SERVER_PORT);
293     r = connect(clientfd, (struct sockaddr *) &sa, sizeof sa);
294 #endif
295     if(r != 0) {
296         syswarn("OVDB: rc: cant connect to server");
297 	close(clientfd);
298 	clientfd = -1;
299 	return -1;
300     }
301 
302     while(p < sizeof(OVDB_SERVER_BANNER)) {
303 	FD_ZERO(&fds);
304 	FD_SET(clientfd, &fds);
305 	timeout.tv_sec = 30;
306 	timeout.tv_usec = 0;
307 
308 	r = select(clientfd+1, &fds, NULL, NULL, &timeout);
309 
310 	if(r < 0) {
311 	    if(errno == EINTR)
312 	    	continue;
313             syswarn("OVDB: select");
314 	    close(clientfd);
315 	    clientfd = -1;
316 	    return -1;
317 	}
318 	if(r == 0) {
319             warn("OVDB: rc: timeout waiting for server");
320 	    close(clientfd);
321 	    clientfd = -1;
322 	    return -1;
323     	}
324 
325 	r = read(clientfd, banner + p, sizeof(OVDB_SERVER_BANNER) - p);
326 	if(r <= 0) {
327 	    if(r < 0 && errno == EINTR)
328 		continue;
329             syswarn("OVDB: rc: cant read");
330 	    close(clientfd);
331 	    clientfd = -1;
332 	    return -1;
333 	}
334 	p+= r;
335     }
336 
337     if(memcmp(banner, OVDB_SERVER_BANNER, sizeof(OVDB_SERVER_BANNER))) {
338         warn("OVDB: rc: unknown reply from server");
339 	close(clientfd);
340 	clientfd = -1;
341 	return -1;
342     }
343     return 0;
344 }
345 
346 static void
client_disconnect(void)347 client_disconnect(void)
348 {
349     struct rs_cmd rs;
350 
351     if (clientfd != -1) {
352 	rs.what = CMD_QUIT;
353 	csend(&rs, sizeof(rs));
354     }
355     clientfd = -1;
356 }
357 
358 
359 /*********** internal functions ***********/
360 
conf_bool_val(char * str,bool * value)361 static bool conf_bool_val(char *str, bool *value)
362 {
363     if(strcasecmp(str, "on") == 0
364        || strcasecmp(str, "true") == 0
365        || strcasecmp(str, "yes") == 0) {
366 	*value = true;
367 	return true;
368     }
369     if(strcasecmp(str, "off") == 0
370        || strcasecmp(str, "false") == 0
371        || strcasecmp(str, "no") == 0) {
372 	*value = false;
373 	return true;
374     }
375     return false;
376 }
377 
conf_long_val(char * str,long * value)378 static bool conf_long_val(char *str, long *value)
379 {
380     long v;
381 
382     errno = 0;
383     v = strtol(str, NULL, 10);
384     if(v == 0 && errno != 0) {
385 	return false;
386     }
387     *value = v;
388     return true;
389 }
390 
read_ovdb_conf(void)391 void read_ovdb_conf(void)
392 {
393     static int confread = 0;
394     int done = 0;
395     char *path;
396     CONFFILE *f;
397     CONFTOKEN *tok;
398     bool b;
399     long l;
400 
401     if(confread)
402 	return;
403 
404     /* defaults */
405     ovdb_conf.home = innconf->pathoverview;
406     ovdb_conf.txn_nosync = 1;
407     ovdb_conf.numdbfiles = 32;
408     ovdb_conf.pagesize = 8192;
409     ovdb_conf.cachesize = 8000 * 1024;
410     ovdb_conf.ncache = 1;
411     ovdb_conf.minkey = 0;
412     ovdb_conf.maxlocks = 4000;
413     ovdb_conf.nocompact = 1;
414     ovdb_conf.readserver = 1;
415     ovdb_conf.numrsprocs = 5;
416     ovdb_conf.maxrsconn = 0;
417     ovdb_conf.useshm = 0;
418     ovdb_conf.shmkey = 6400;
419     ovdb_conf.compress = 0;
420 
421     path = concatpath(innconf->pathetc, _PATH_OVDBCONF);
422     f = CONFfopen(path);
423     free(path);
424 
425     if(f) {
426 	while(!done && (tok = CONFgettoken(toks, f))) {
427 	    switch(tok->type) {
428 	    case OVDBtxn_nosync:
429 		tok = CONFgettoken(0, f);
430 		if(!tok) {
431 		    done = 1;
432 		    continue;
433 		}
434 		if(conf_bool_val(tok->name, &b)) {
435 		    ovdb_conf.txn_nosync = b;
436 		}
437 		break;
438 	    case OVDBnumdbfiles:
439 		tok = CONFgettoken(0, f);
440 		if(!tok) {
441 		    done = 1;
442 		    continue;
443 		}
444 		if(conf_long_val(tok->name, &l) && l > 0) {
445 		    ovdb_conf.numdbfiles = l;
446 		}
447 		break;
448 	    case OVDBpagesize:
449 		tok = CONFgettoken(0, f);
450 		if(!tok) {
451 		    done = 1;
452 		    continue;
453 		}
454 		if(conf_long_val(tok->name, &l) && l > 0) {
455 		    ovdb_conf.pagesize = l;
456 		}
457 		break;
458 	    case OVDBcachesize:
459 		tok = CONFgettoken(0, f);
460 		if(!tok) {
461 		    done = 1;
462 		    continue;
463 		}
464 		if(conf_long_val(tok->name, &l) && l > 0) {
465 		    ovdb_conf.cachesize = l * 1024;
466 		}
467 		break;
468 	    case OVDBncache:
469 		tok = CONFgettoken(0, f);
470 		if(!tok) {
471 		    done = 1;
472 		    continue;
473 		}
474 		if(conf_long_val(tok->name, &l) && l > 0) {
475 		    ovdb_conf.ncache = l;
476 		}
477 		break;
478 	    case OVDBminkey:
479 		tok = CONFgettoken(0, f);
480 		if(!tok) {
481 		    done = 1;
482 		    continue;
483 		}
484 		if(conf_long_val(tok->name, &l) && l > 1) {
485 		    ovdb_conf.minkey = l;
486 		}
487 		break;
488 	    case OVDBmaxlocks:
489 		tok = CONFgettoken(0, f);
490 		if(!tok) {
491 		    done = 1;
492 		    continue;
493 		}
494 		if(conf_long_val(tok->name, &l) && l > 0) {
495 		    ovdb_conf.maxlocks = l;
496 		}
497 		break;
498 	    case OVDBnocompact:
499 		tok = CONFgettoken(0, f);
500 		if(!tok) {
501 		    done = 1;
502 		    continue;
503 		}
504 		if(conf_long_val(tok->name, &l) && l >= 0) {
505 		    ovdb_conf.nocompact = l;
506 		}
507 		break;
508 	    case OVDBreadserver:
509 		tok = CONFgettoken(0, f);
510 		if(!tok) {
511 		    done = 1;
512 		    continue;
513 		}
514 		if(conf_bool_val(tok->name, &b)) {
515 		    ovdb_conf.readserver = b;
516 		}
517 		break;
518 	    case OVDBnumrsprocs:
519 		tok = CONFgettoken(0, f);
520 		if(!tok) {
521 		    done = 1;
522 		    continue;
523 		}
524 		if(conf_long_val(tok->name, &l) && l > 0) {
525 		    ovdb_conf.numrsprocs = l;
526 		}
527 		break;
528 	    case OVDBmaxrsconn:
529 		tok = CONFgettoken(0, f);
530 		if(!tok) {
531 		    done = 1;
532 		    continue;
533 		}
534 		if(conf_long_val(tok->name, &l) && l >= 0) {
535 		    ovdb_conf.maxrsconn = l;
536 		}
537 		break;
538 	    case OVDBuseshm:
539 		tok = CONFgettoken(0, f);
540 		if(!tok) {
541 		    done = 1;
542 		    continue;
543 		}
544 		if(conf_bool_val(tok->name, &b)) {
545 		    ovdb_conf.useshm = b;
546 		}
547 		break;
548 	    case OVDBshmkey:
549 		tok = CONFgettoken(0, f);
550 		if(!tok) {
551 		    done = 1;
552 		    continue;
553 		}
554 		if(conf_long_val(tok->name, &l) && l >= 0) {
555 		    ovdb_conf.shmkey = l;
556 		}
557 		break;
558 	    case OVDBcompress:
559 #ifdef HAVE_ZLIB
560 		tok = CONFgettoken(0, f);
561 		if(!tok) {
562 		    done = 1;
563 		    continue;
564 		}
565 		if(conf_bool_val(tok->name, &b)) {
566 		    ovdb_conf.compress = b;
567 		}
568 #endif
569 		break;
570 	    }
571 	}
572 	CONFfclose(f);
573     }
574 
575     /* If user did not specify minkey, choose one based on pagesize */
576     if(ovdb_conf.minkey == 0) {
577 	if(ovdb_conf.compress) {
578 	    ovdb_conf.minkey = ovdb_conf.pagesize / 1500;
579 	} else {
580 	    ovdb_conf.minkey = ovdb_conf.pagesize / 2600;
581 	}
582 	if(ovdb_conf.minkey < 2)
583 	    ovdb_conf.minkey = 2;
584     }
585 
586     confread = 1;
587 }
588 
589 /* Function that db will use to report errors */
OVDBerror(const DB_ENV * dbenv UNUSED,const char * db_errpfx UNUSED,const char * buffer)590 static void OVDBerror(const DB_ENV *dbenv UNUSED, const char *db_errpfx UNUSED, const char *buffer)
591 {
592     warn("OVDB: %s", buffer);
593 }
594 
595 static u_int32_t _db_flags = 0;
596 
open_db_file(int which)597 static int open_db_file(int which)
598 {
599     int ret;
600     char name[16];
601     DB_TXN *tid;
602 
603     if(dbs[which] != NULL)
604 	return 0;
605 
606     snprintf(name, sizeof(name), "ov%05d", which);
607 
608     ret = db_create(&(dbs[which]), OVDBenv, 0);
609     if (ret != 0)
610 	return ret;
611 
612     if(ovdb_conf.minkey > 0)
613 	(dbs[which])->set_bt_minkey(dbs[which], ovdb_conf.minkey);
614     if(ovdb_conf.pagesize > 0)
615 	(dbs[which])->set_pagesize(dbs[which], ovdb_conf.pagesize);
616 
617     TXN_START_NORETRY(t_open_db_file, tid);
618     ret = (dbs[which])->open(dbs[which], tid, name, NULL, DB_BTREE, _db_flags,
619                              0666);
620     if (ret == 0)
621 	TXN_COMMIT(t_open_db_file, tid);
622     if (ret != 0) {
623 	(dbs[which])->close(dbs[which], 0);
624 	dbs[which] = NULL;
625 	return ret;
626     }
627     return 0;
628 }
629 
close_db_file(int which)630 static void close_db_file(int which)
631 {
632     if(which == -1 || dbs[which] == NULL)
633 	return;
634 
635     dbs[which]->close(dbs[which], 0);
636     dbs[which] = NULL;
637 }
638 
which_db(const char * group)639 static int which_db(const char *group)
640 {
641     HASH grouphash;
642     unsigned int i;
643 
644     grouphash = Hash(group, strlen(group));
645     memcpy(&i, &grouphash, sizeof(i));
646     return i % ovdb_conf.numdbfiles;
647 }
648 
get_db_bynum(int which)649 static DB *get_db_bynum(int which)
650 {
651     int ret;
652     if(which >= ovdb_conf.numdbfiles)
653 	return NULL;
654     if(oneatatime) {
655 	if(which != current_db && current_db != -1)
656 	    close_db_file(current_db);
657 
658         ret = open_db_file(which);
659 	if (ret != 0)
660             warn("OVDB: open_db_file failed: %s", db_strerror(ret));
661 
662 	current_db = which;
663     }
664     return(dbs[which]);
665 }
666 
667 
668 int
ovdb_getgroupinfo(const char * group,struct groupinfo * gi,int ignoredeleted,DB_TXN * tid,int getflags)669 ovdb_getgroupinfo(const char *group, struct groupinfo *gi, int ignoredeleted,
670                   DB_TXN *tid, int getflags)
671 {
672     int ret;
673     DBT key, val;
674 
675     if(group == NULL)	/* just in case */
676 	return DB_NOTFOUND;
677 
678     memset(&key, 0, sizeof key);
679     memset(&val, 0, sizeof val);
680 
681     key.data = (char *) group;
682     key.size = strlen(group);
683     val.data = gi;
684     val.ulen = sizeof(struct groupinfo);
685     val.flags = DB_DBT_USERMEM;
686 
687     ret = groupinfo->get(groupinfo, tid, &key, &val, getflags);
688     if (ret != 0)
689 	return ret;
690 
691     if(val.size != sizeof(struct groupinfo)) {
692         warn("OVDB: wrong size for %s groupinfo (%u)", group, val.size);
693 	return DB_NOTFOUND;
694     }
695 
696     if(ignoredeleted && (gi->status & GROUPINFO_DELETED))
697 	return DB_NOTFOUND;
698 
699     return 0;
700 }
701 
702 #define GROUPID_MAX_FREELIST 10240
703 #define GROUPID_MIN_FREELIST 100
704 
705 /* allocate a new group ID and return in gno */
706 /* must be used in a transaction */
groupid_new(group_id_t * gno,DB_TXN * tid)707 static int groupid_new(group_id_t *gno, DB_TXN *tid)
708 {
709     DBT key, val;
710     int ret, n;
711     group_id_t newgno, *freelist, one;
712 
713     memset(&key, 0, sizeof key);
714     memset(&val, 0, sizeof val);
715 
716     key.data = (char *) "!groupid_freelist";
717     key.size = sizeof("!groupid_freelist");
718 
719     ret = groupinfo->get(groupinfo, tid, &key, &val, DB_RMW);
720     if (ret != 0) {
721 	if(ret == DB_NOTFOUND) {
722 	    val.size = sizeof(group_id_t);
723 	    val.data = &one;
724 	    one = 1;
725 	} else {
726 	    return ret;
727 	}
728     }
729 
730     if(val.size % sizeof(group_id_t)) {
731         warn("OVDB: invalid size (%d) for !groupid_freelist", val.size);
732 	return EINVAL;
733     }
734 
735     n = val.size / sizeof(group_id_t);
736     freelist = xmalloc(n * sizeof(group_id_t));
737     memcpy(freelist, val.data, val.size);
738     if(n <= GROUPID_MIN_FREELIST ) {
739 	newgno = freelist[n-1];
740 	(freelist[n-1])++;
741 	val.data = freelist;
742     } else {
743 	newgno = freelist[0];
744 	val.data = &(freelist[1]);
745 	val.size -= sizeof(group_id_t);
746     }
747 
748     ret = groupinfo->put(groupinfo, tid, &key, &val, 0);
749     if (ret != 0) {
750 	free(freelist);
751 	return ret;
752     }
753 
754     free(freelist);
755     *gno = newgno;
756     return 0;
757 }
758 
759 /* mark given group ID as "unused" */
760 /* must be used in a transaction */
groupid_free(group_id_t gno,DB_TXN * tid)761 static int groupid_free(group_id_t gno, DB_TXN *tid)
762 {
763     DBT key, val;
764     int ret, n, i;
765     group_id_t *freelist;
766 
767     memset(&key, 0, sizeof key);
768     memset(&val, 0, sizeof val);
769 
770     key.data = (char *) "!groupid_freelist";
771     key.size = sizeof("!groupid_freelist");
772 
773     ret = groupinfo->get(groupinfo, tid, &key, &val, DB_RMW);
774     if (ret != 0) {
775 	return ret;
776     }
777 
778     if(val.size % sizeof(group_id_t)) {
779         warn("OVDB: invalid size (%d) for !groupid_freelist", val.size);
780 	return EINVAL;
781     }
782 
783     n = val.size / sizeof(group_id_t);
784     if(n > GROUPID_MAX_FREELIST)
785 	return 0;
786     freelist = xmalloc((n + 1) * sizeof(group_id_t));
787     memcpy(freelist, val.data, val.size);
788 
789     if(gno >= freelist[n-1]) {	/* shouldn't happen */
790 	free(freelist);
791 	return 0;
792     }
793     for(i = 0; i < n-1; i++) {
794 	if(gno == freelist[i]) {	/* already on freelist */
795 	    free(freelist);
796 	    return 0;
797 	}
798     }
799 
800     freelist[n] = freelist[n-1];
801     freelist[n-1] = gno;
802     val.data = freelist;
803     val.size += sizeof(group_id_t);
804 
805     ret = groupinfo->put(groupinfo, tid, &key, &val, 0);
806 
807     free(freelist);
808     return ret;
809 }
810 
811 /* Must be called outside of a transaction because it makes its own
812    transactions */
delete_all_records(int whichdb,group_id_t gno)813 static int delete_all_records(int whichdb, group_id_t gno)
814 {
815     DB *db;
816     DBC *dbcursor;
817     DBT key, val;
818     struct datakey dk;
819     int count;
820     int ret = 0;
821     DB_TXN *tid;
822 
823     memset(&key, 0, sizeof key);
824     memset(&val, 0, sizeof val);
825     memset(&dk, 0, sizeof dk);
826 
827     db = get_db_bynum(whichdb);
828     if(db == NULL)
829         return DB_NOTFOUND;
830 
831     dk.groupnum = gno;
832     dk.artnum = 0;
833 
834     while(1) {
835         TXN_START(t_del, tid);
836 
837         /* get a cursor to traverse the ov records and delete them */
838         ret = db->cursor(db, tid, &dbcursor, 0);
839         switch(ret)
840         {
841         case 0:
842             break;
843         case TRYAGAIN:
844             TXN_RETRY(t_del, tid);
845         default:
846             TXN_ABORT(t_del, tid);
847             warn("OVDB: delete_all_records: db->cursor: %s", db_strerror(ret));
848             return ret;
849         }
850 
851         key.data = &dk;
852         key.size = sizeof dk;
853         val.flags = DB_DBT_PARTIAL;
854 
855         for(count = 0; count < DELETE_TXN_SIZE; count++) {
856             ret = dbcursor->c_get(dbcursor, &key, &val,
857                             count ? DB_NEXT : DB_SET_RANGE);
858             switch (ret)
859             {
860             case 0:
861                 break;
862             case DB_NOTFOUND:
863                 dbcursor->c_close(dbcursor);
864                 TXN_COMMIT(t_del, tid);
865                 return 0;
866             case TRYAGAIN:
867                 dbcursor->c_close(dbcursor);
868                 TXN_RETRY(t_del, tid);
869             default:
870                 warn("OVDB: delete_all_records: DBcursor->c_get: %s",
871                      db_strerror(ret));
872                 dbcursor->c_close(dbcursor);
873                 TXN_ABORT(t_del, tid);
874                 return ret;
875             }
876 
877             if(key.size == sizeof dk
878                     && memcmp(key.data, &gno, sizeof gno)) {
879                 break;
880             }
881 
882             ret = dbcursor->c_del(dbcursor, 0);
883             switch (ret) {
884             case 0:
885             case DB_KEYEMPTY:
886                 break;
887             case TRYAGAIN:
888                 dbcursor->c_close(dbcursor);
889                 TXN_RETRY(t_del, tid);
890             default:
891                 warn("OVDB: delete_all_records: DBcursor->c_del: %s",
892                      db_strerror(ret));
893                 dbcursor->c_close(dbcursor);
894                 TXN_ABORT(t_del, tid);
895                 return ret;
896             }
897         }
898         dbcursor->c_close(dbcursor);
899         TXN_COMMIT(t_del, tid);
900         if(count < DELETE_TXN_SIZE) {
901             break;
902         }
903     }
904     return 0;
905 }
906 
907 /* Make a temporary groupinfo key using the given db number and group ID.
908    Must be called in a transaction */
909 static int
mk_temp_groupinfo(int whichdb,group_id_t gno,DB_TXN * tid)910 mk_temp_groupinfo(int whichdb, group_id_t gno, DB_TXN *tid)
911 {
912     char keystr[1 + sizeof gno];
913     DBT key, val;
914     struct groupinfo gi;
915     int ret;
916 
917     memset(&key, 0, sizeof key);
918     memset(&val, 0, sizeof val);
919     memset(&gi, 0, sizeof gi);
920 
921     keystr[0] = 0;
922     memcpy(keystr + 1, &gno, sizeof gno);
923 
924     gi.current_db = whichdb;
925     gi.current_gid = gno;
926     gi.status = GROUPINFO_DELETED;
927 
928     key.data = keystr;
929     key.size = sizeof keystr;
930     val.data = &gi;
931     val.size = sizeof gi;
932 
933     ret = groupinfo->put(groupinfo, tid, &key, &val, 0);
934     switch (ret) {
935     case 0:
936         break;
937     default:
938         warn("OVDB: mk_temp_groupinfo: groupinfo->put: %s", db_strerror(ret));
939         /* fallthrough */
940     case TRYAGAIN:
941         return ret;
942     }
943 
944     return 0;
945 }
946 
947 /* Delete a temporary groupinfo key created by mk_temp_groupid, then
948    frees the group id.
949    Must NOT be called within a transaction. */
950 static int
rm_temp_groupinfo(group_id_t gno)951 rm_temp_groupinfo(group_id_t gno)
952 {
953     char keystr[1 + sizeof gno];
954     DB_TXN *tid;
955     DBT key;
956     int ret = 0;
957 
958     memset(&key, 0, sizeof key);
959 
960     keystr[0] = 0;
961     memcpy(keystr + 1, &gno, sizeof gno);
962 
963     key.data = keystr;
964     key.size = sizeof keystr;
965 
966     TXN_START(t_tmp, tid);
967 
968     ret = groupinfo->del(groupinfo, tid, &key, 0);
969     switch(ret) {
970     case 0:
971     case DB_NOTFOUND:
972         break;
973     case TRYAGAIN:
974         TXN_RETRY(t_tmp, tid);
975     default:
976         TXN_ABORT(t_tmp, tid);
977         warn("OVDB: rm_temp_groupinfo: groupinfo->del: %s", db_strerror(ret));
978         return ret;
979     }
980 
981     switch(groupid_free(gno, tid)) {
982     case 0:
983         break;
984     case TRYAGAIN:
985         TXN_RETRY(t_tmp, tid);
986     default:
987         TXN_ABORT(t_tmp, tid);
988         warn("OVDB: rm_temp_groupinfo: groupid_free: %s", db_strerror(ret));
989         return ret;
990     }
991 
992     TXN_COMMIT(t_tmp, tid);
993     return 0;
994 }
995 
996 /* This function deletes overview records for deleted or forgotton groups */
997 /* argument: 0 = process deleted groups   1 = process forgotton groups */
delete_old_stuff(int forgotton)998 static bool delete_old_stuff(int forgotton)
999 {
1000     DBT key, val;
1001     DBC *cursor;
1002     DB_TXN *tid;
1003     struct groupinfo gi;
1004     char **dellist = NULL;
1005     size_t *dellistsz = NULL;
1006     int listlen, listcount = 0;
1007     int i, ret = 0;
1008 
1009     TXN_START(t_dellist, tid);
1010     if (dellist != NULL) {
1011         for (i = 0; i < listcount; ++i)
1012             free(dellist[i]);
1013         free(dellist);
1014     }
1015     if (dellistsz != NULL)
1016 	free(dellistsz);
1017     listlen = 20;
1018     listcount = 0;
1019     dellist = xmalloc(listlen * sizeof(char *));
1020     dellistsz = xmalloc(listlen * sizeof(size_t));
1021 
1022     memset(&key, 0, sizeof key);
1023     memset(&val, 0, sizeof val);
1024 
1025     val.data = &gi;
1026     val.ulen = val.dlen = sizeof gi;
1027     val.flags = DB_DBT_USERMEM | DB_DBT_PARTIAL;
1028 
1029     /* get a cursor to traverse all of the groupinfo records */
1030     ret = groupinfo->cursor(groupinfo, tid, &cursor, 0);
1031     if (ret != 0) {
1032         warn("OVDB: delete_old_stuff: groupinfo->cursor: %s",
1033              db_strerror(ret));
1034 	free(dellist);
1035 	free(dellistsz);
1036 	return false;
1037     }
1038 
1039     while((ret = cursor->c_get(cursor, &key, &val, DB_NEXT)) == 0) {
1040 	if(key.size == sizeof("!groupid_freelist") &&
1041 		!strcmp("!groupid_freelist", key.data))
1042 	    continue;
1043 	if(val.size != sizeof(struct groupinfo)) {
1044             warn("OVDB: delete_old_stuff: wrong size for groupinfo record");
1045 	    continue;
1046 	}
1047 	if((!forgotton && (gi.status & GROUPINFO_DELETED))
1048 		|| (forgotton && (gi.expired < eo_start))) {
1049 	    dellist[listcount] = xmalloc(key.size);
1050 	    memcpy(dellist[listcount], key.data, key.size);
1051 	    dellistsz[listcount] = key.size;
1052 	    listcount++;
1053 	    if(listcount >= listlen) {
1054 		listlen += 20;
1055                 dellist = xrealloc(dellist, listlen * sizeof(char *));
1056                 dellistsz = xrealloc(dellistsz, listlen * sizeof(size_t));
1057 	    }
1058 	}
1059     }
1060     cursor->c_close(cursor);
1061     switch (ret) {
1062     case 0:
1063     case DB_NOTFOUND:
1064         TXN_COMMIT(t_dellist, tid);
1065         break;
1066     case TRYAGAIN:
1067         TXN_RETRY(t_dellist, tid);
1068     default:
1069         TXN_ABORT(t_dellist, tid);
1070         warn("OVDB: delete_old_stuff: cursor->c_get: %s", db_strerror(ret));
1071         for (i = 0; i < listcount; ++i)
1072             free(dellist[i]);
1073         free(dellist);
1074         free(dellistsz);
1075         return false;
1076     }
1077 
1078     for(i = 0; i < listcount; i++) {
1079 	TXN_START(t_dos, tid);
1080 
1081 	/* Can't use ovdb_getgroupinfo here */
1082 	key.data = dellist[i];
1083 	key.size = dellistsz[i];
1084 	val.data = &gi;
1085 	val.ulen = sizeof(struct groupinfo);
1086 	val.flags = DB_DBT_USERMEM;
1087 
1088 	ret = groupinfo->get(groupinfo, tid, &key, &val, 0);
1089 
1090 	switch (ret)
1091         {
1092 	case 0:
1093 	    break;
1094 	case TRYAGAIN:
1095 	    TXN_RETRY(t_dos, tid);
1096 	case DB_NOTFOUND:
1097 	    TXN_ABORT(t_dos, tid);
1098 	    continue;
1099 	default:
1100             warn("OVDB: delete_old_stuff: groupinfo->get: %s",
1101                  db_strerror(ret));
1102 	    TXN_ABORT(t_dos, tid);
1103 	    continue;
1104 	}
1105 	if(val.size != sizeof(struct groupinfo)) {
1106 	    TXN_ABORT(t_dos, tid);
1107 	    continue;
1108 	}
1109 
1110 	/* This if() is true if this ISN'T a key created by mk_temp_groupinfo */
1111 	if(*((char *)key.data) != 0 || key.size != 1 + sizeof(group_id_t)) {
1112 
1113 	    switch(mk_temp_groupinfo(gi.current_db, gi.current_gid, tid)) {
1114 	    case 0:
1115 		break;
1116 	    case TRYAGAIN:
1117 		TXN_RETRY(t_dos, tid);
1118 	    default:
1119 		TXN_ABORT(t_dos, tid);
1120 		continue;
1121 	    }
1122 	    if(gi.status & GROUPINFO_MOVING) {
1123 		switch(mk_temp_groupinfo(gi.new_db, gi.new_gid, tid)) {
1124 		case 0:
1125 		    break;
1126 		case TRYAGAIN:
1127 		    TXN_RETRY(t_dos, tid);
1128 		default:
1129 		    TXN_ABORT(t_dos, tid);
1130 		    continue;
1131 		}
1132 	    }
1133 
1134 	    /* delete the corresponding groupaliases record (if exists) */
1135 	    switch(groupaliases->del(groupaliases, tid, &key, 0)) {
1136 	    case 0:
1137 	    case DB_NOTFOUND:
1138 		break;
1139 	    case TRYAGAIN:
1140 		TXN_RETRY(t_dos, tid);
1141 	    default:
1142 		TXN_ABORT(t_dos, tid);
1143 		continue;
1144 	    }
1145 
1146 	    switch(groupinfo->del(groupinfo, tid, &key, 0)) {
1147 	    case 0:
1148 	    case DB_NOTFOUND:
1149 		break;
1150 	    case TRYAGAIN:
1151 		TXN_RETRY(t_dos, tid);
1152 	    default:
1153 		TXN_ABORT(t_dos, tid);
1154 		continue;
1155 	    }
1156 	}
1157 
1158 	TXN_COMMIT(t_dos, tid);
1159 
1160 	if(delete_all_records(gi.current_db, gi.current_gid) == 0) {
1161 	    rm_temp_groupinfo(gi.current_gid);
1162 	}
1163 	if(gi.status & GROUPINFO_MOVING) {
1164 	    if(delete_all_records(gi.new_db, gi.new_gid) == 0) {
1165 		rm_temp_groupinfo(gi.new_gid);
1166 	    }
1167 	}
1168     }
1169 
1170     for(i = 0; i < listcount; i++)
1171 	free(dellist[i]);
1172     free(dellist);
1173     free(dellistsz);
1174     return true;
1175 }
1176 
count_records(struct groupinfo * gi)1177 static int count_records(struct groupinfo *gi)
1178 {
1179     int ret;
1180     DB *db;
1181     DBC *cursor;
1182     DBT key, val;
1183     struct datakey dk;
1184     u_int32_t artnum, newlow = 0;
1185 
1186     memset(&key, 0, sizeof key);
1187     memset(&val, 0, sizeof val);
1188     memset(&dk, 0, sizeof dk);
1189 
1190     db = get_db_bynum(gi->current_db);
1191     if(db == NULL)
1192 	return DB_NOTFOUND;
1193 
1194     dk.groupnum = gi->current_gid;
1195     dk.artnum = 0;
1196     key.data = &dk;
1197     key.size = key.ulen = sizeof dk;
1198     key.flags = DB_DBT_USERMEM;
1199     val.flags = DB_DBT_PARTIAL;
1200 
1201     gi->count = 0;
1202 
1203     ret = db->cursor(db, NULL, &cursor, 0);
1204     if (ret)
1205 	return ret;
1206 
1207     ret = cursor->c_get(cursor, &key, &val, DB_SET_RANGE);
1208     switch (ret)
1209     {
1210     case 0:
1211     case DB_NOTFOUND:
1212 	break;
1213     default:
1214 	cursor->c_close(cursor);
1215 	return ret;
1216     }
1217     while(ret == 0 && key.size == sizeof(dk) && dk.groupnum == gi->current_gid) {
1218 	artnum = ntohl(dk.artnum);
1219 	if(newlow == 0 || newlow > artnum)
1220 	    newlow = artnum;
1221 	if(artnum > gi->high)
1222 	    gi->high = artnum;
1223 	gi->count++;
1224 
1225 	ret = cursor->c_get(cursor, &key, &val, DB_NEXT);
1226     }
1227     cursor->c_close(cursor);
1228     if(gi->count == 0)
1229 	gi->low = gi->high + 1;
1230     else
1231 	gi->low = newlow;
1232 
1233     if(ret == DB_NOTFOUND)
1234 	return 0;
1235     return ret;
1236 }
1237 
1238 
1239 /*
1240  * Locking:  OVopen() calls ovdb_getlock(OVDB_LOCK_NORMAL).  This
1241  * aquires a read (shared) lock on the lockfile.  Multiple processes
1242  * can have this file locked at the same time.  That way, if there
1243  * are any processes that are using the database, the lock file will
1244  * have one or more shared locks on it.
1245  *
1246  * ovdb_init, when starting, calls ovdb_getlock(OVDB_LOCK_EXCLUSIVE).
1247  * This tries to get a write (exclusive) lock, which will fail if
1248  * anyone has a shared lock.  This way, ovdb_init can tell if there
1249  * are any processes using the database.  If not, and the excl. lock
1250  * succeeds, ovdb_init is free to do DB_RUNRECOVER.
1251  *
1252  * ovdb_getlock() in the "normal" lock mode calls ovdb_check_monitor,
1253  * which looks for the OVDB_MONITOR_PIDFILE.  If said file does not
1254  * exist, or the PID in it does not exist, it will fail.  This will
1255  * prevent OVopen() from opening the database if ovdb_monitor is not running.
1256  *
1257  * The OVDB_LOCK_ADMIN mode is used by ovdb_monitor to get a shared lock
1258  * without testing the pidfile.
1259  */
1260 static int lockfd = -1;
ovdb_getlock(int mode)1261 bool ovdb_getlock(int mode)
1262 {
1263     if(lockfd == -1) {
1264 	char *lockfn = concatpath(innconf->pathrun, OVDB_LOCKFN);
1265 	lockfd = open(lockfn,
1266 		mode == OVDB_LOCK_NORMAL ? O_RDWR : O_CREAT|O_RDWR, 0660);
1267 	if(lockfd == -1) {
1268 	    free(lockfn);
1269 	    if(errno == ENOENT)
1270                 warn("OVDB: can not open database unless ovdb_monitor is"
1271                      " running");
1272 	    return false;
1273 	}
1274 	fdflag_close_exec(lockfd, true);
1275 	free(lockfn);
1276     } else
1277 	return true;
1278 
1279     if(mode == OVDB_LOCK_NORMAL) {
1280 	if(!ovdb_check_pidfile(OVDB_MONITOR_PIDFILE)) {
1281             warn("OVDB: can not open database unless ovdb_monitor is"
1282                  " running");
1283 	    return false;
1284 	}
1285     }
1286     if(mode == OVDB_LOCK_EXCLUSIVE) {
1287 	if(!inn_lock_file(lockfd, INN_LOCK_WRITE, false)) {
1288 	    close(lockfd);
1289 	    lockfd = -1;
1290 	    return false;
1291 	}
1292 	return true;
1293     } else {
1294 	if(!inn_lock_file(lockfd, INN_LOCK_READ, false)) {
1295 	    close(lockfd);
1296 	    lockfd = -1;
1297 	    return false;
1298 	}
1299 	return true;
1300     }
1301 }
1302 
ovdb_releaselock(void)1303 bool ovdb_releaselock(void)
1304 {
1305     bool r;
1306     if(lockfd == -1)
1307 	return true;
1308     r = inn_lock_file(lockfd, INN_LOCK_UNLOCK, false);
1309     close(lockfd);
1310     lockfd = -1;
1311     return r;
1312 }
1313 
ovdb_check_pidfile(const char * file)1314 bool ovdb_check_pidfile(const char *file)
1315 {
1316     int f, pid;
1317     char buf[SMBUF];
1318     char *pidfn = concatpath(innconf->pathrun, file);
1319 
1320     f = open(pidfn, O_RDONLY);
1321     if(f == -1) {
1322 	if(errno != ENOENT)
1323             syswarn("OVDB: can't open %s", pidfn);
1324 	free(pidfn);
1325 	return false;
1326     }
1327     memset(buf, 0, SMBUF);
1328     if(read(f, buf, SMBUF-1) < 0) {
1329         syswarn("OVDB: can't read from %s", pidfn);
1330 	free(pidfn);
1331 	close(f);
1332 	return false;
1333     }
1334     close(f);
1335     free(pidfn);
1336     pid = atoi(buf);
1337     if(pid <= 1) {
1338 	return false;
1339     }
1340     if(kill(pid, 0) < 0 && errno == ESRCH) {
1341 	return false;
1342     }
1343     return true;
1344 }
1345 
1346 /* Make sure the effective uid is that of the runasuser user. */
ovdb_check_user(void)1347 bool ovdb_check_user(void)
1348 {
1349     static int result = -1;
1350 
1351     if(result == -1) {
1352         int rv;
1353         uid_t uid;
1354 
1355         rv = get_news_uid_gid(&uid, false, false);
1356 
1357         if (rv != 0) {
1358             syswarn("OVDB: can't resolve runasuser user to a UID");
1359             return false;
1360         }
1361         result = (uid == geteuid());
1362     }
1363     return result;
1364 }
1365 
check_version(void)1366 static int check_version(void)
1367 {
1368     int ret;
1369     DB *vdb;
1370     DBT key, val;
1371     u_int32_t dv;
1372 
1373     /* open version db */
1374     ret = db_create(&vdb, OVDBenv, 0);
1375     if (ret != 0) {
1376         warn("OVDB: open: db_create: %s", db_strerror(ret));
1377 	return ret;
1378     }
1379     ret = vdb->open(vdb, NULL, "version", NULL, DB_BTREE, _db_flags, 0666);
1380     if (ret != 0) {
1381 	vdb->close(vdb, 0);
1382         warn("OVDB: open: version->open: %s", db_strerror(ret));
1383 	return ret;
1384     }
1385     memset(&key, 0, sizeof key);
1386     memset(&val, 0, sizeof val);
1387     key.data = (char *) "dataversion";
1388     key.size = sizeof("dataversion");
1389     ret = vdb->get(vdb, NULL, &key, &val, 0);
1390     if (ret != 0) {
1391 	if(ret != DB_NOTFOUND) {
1392             warn("OVDB: open: can't retrieve version: %s", db_strerror(ret));
1393 	    vdb->close(vdb, 0);
1394 	    return ret;
1395 	}
1396     }
1397     if(ret == DB_NOTFOUND || val.size != sizeof dv) {
1398 	dv = ovdb_conf.compress ? DATA_VERSION_COMPRESS : DATA_VERSION;
1399 	if(!(OVDBmode & OV_WRITE)) {
1400 	    vdb->close(vdb, 0);
1401 	    return EACCES;
1402 	}
1403 	val.data = &dv;
1404 	val.size = sizeof dv;
1405         ret = vdb->put(vdb, NULL, &key, &val, 0);
1406 	if (ret != 0) {
1407             warn("OVDB: open: can't store version: %s", db_strerror(ret));
1408 	    vdb->close(vdb, 0);
1409 	    return ret;
1410 	}
1411     } else
1412 	memcpy(&dv, val.data, sizeof dv);
1413 
1414     if(dv > DATA_VERSION_COMPRESS) {
1415         warn("OVDB: can't open database: unknown version %d", dv);
1416 	vdb->close(vdb, 0);
1417 	return EINVAL;
1418     }
1419     if(dv < DATA_VERSION) {
1420         warn("OVDB: database is an old version, please run ovdb_init");
1421 	vdb->close(vdb, 0);
1422 	return EINVAL;
1423     }
1424 #ifndef HAVE_ZLIB
1425     if(dv == DATA_VERSION_COMPRESS) {
1426 	warn("OVDB: database is compressed but INN was not built with zlib");
1427 	vdb->close(vdb, 0);
1428 	return EINVAL;
1429     }
1430 #endif
1431     if(ovdb_conf.compress && dv == DATA_VERSION && (OVDBmode & OV_WRITE)) {
1432 	/* "Upgrade" the database to indicate there may be compressed
1433 	   records */
1434 	dv = DATA_VERSION_COMPRESS;
1435 	val.data = &dv;
1436 	val.size = sizeof dv;
1437 
1438         ret = vdb->put(vdb, NULL, &key, &val, 0);
1439 	if (ret != 0) {
1440 	    warn("OVDB: open: can't store version: %s", db_strerror(ret));
1441 	    vdb->close(vdb, 0);
1442 	    return ret;
1443 	}
1444     }
1445     ovdb_data_ver = dv;
1446 
1447     /* The version db also stores some config values, which will override the
1448        corresponding ovdb.conf setting. */
1449 
1450     key.data = (char *) "numdbfiles";
1451     key.size = sizeof("numdbfiles");
1452     ret = vdb->get(vdb, NULL, &key, &val, 0);
1453     if (ret != 0) {
1454 	if(ret != DB_NOTFOUND) {
1455             warn("OVDB: open: can't retrieve numdbfiles: %s",
1456                  db_strerror(ret));
1457 	    vdb->close(vdb, 0);
1458 	    return ret;
1459 	}
1460     }
1461     if(ret == DB_NOTFOUND || val.size != sizeof(ovdb_conf.numdbfiles)) {
1462 	if(!(OVDBmode & OV_WRITE)) {
1463 	    vdb->close(vdb, 0);
1464 	    return EACCES;
1465 	}
1466 	val.data = &(ovdb_conf.numdbfiles);
1467 	val.size = sizeof(ovdb_conf.numdbfiles);
1468         ret = vdb->put(vdb, NULL, &key, &val, 0);
1469 	if (ret != 0) {
1470             warn("OVDB: open: can't store numdbfiles: %s", db_strerror(ret));
1471 	    vdb->close(vdb, 0);
1472 	    return ret;
1473 	}
1474     } else {
1475 	memcpy(&(ovdb_conf.numdbfiles), val.data, sizeof(ovdb_conf.numdbfiles));
1476     }
1477 
1478     vdb->close(vdb, 0);
1479     return 0;
1480 }
1481 
1482 
ovdb_open_berkeleydb(int mode,int flags)1483 int ovdb_open_berkeleydb(int mode, int flags)
1484 {
1485     int ret;
1486     u_int32_t ai_flags = DB_INIT_LOCK|DB_INIT_LOG|DB_INIT_MPOOL|DB_INIT_TXN;
1487 
1488     OVDBmode = mode;
1489     read_ovdb_conf();
1490 
1491     if(OVDBenv != NULL)
1492 	return 0;	/* already opened */
1493 
1494     if(OVDBmode & OV_WRITE) {
1495 	_db_flags |= DB_CREATE;
1496 	ai_flags |= DB_CREATE;
1497     } else {
1498 	_db_flags |= DB_RDONLY;
1499     }
1500     if(flags & OVDB_RECOVER)
1501 	ai_flags |= DB_RECOVER;
1502 
1503     ret = db_env_create(&OVDBenv, 0);
1504     if (ret != 0) {
1505         warn("OVDB: db_env_create: %s", db_strerror(ret));
1506 	return ret;
1507     }
1508 
1509     if((flags & (OVDB_UPGRADE | OVDB_RECOVER)) == (OVDB_UPGRADE | OVDB_RECOVER))
1510 	ai_flags |= DB_PRIVATE;
1511     if(!(ai_flags & DB_PRIVATE)) {
1512 	if(ovdb_conf.useshm)
1513 	    ai_flags |= DB_SYSTEM_MEM;
1514 	OVDBenv->set_shm_key(OVDBenv, ovdb_conf.shmkey);
1515     }
1516 
1517     OVDBenv->set_errcall(OVDBenv, OVDBerror);
1518     OVDBenv->set_cachesize(OVDBenv, 0, ovdb_conf.cachesize, ovdb_conf.ncache);
1519     OVDBenv->set_lk_max_locks(OVDBenv, ovdb_conf.maxlocks);
1520     OVDBenv->set_lk_max_lockers(OVDBenv, ovdb_conf.maxlocks);
1521     OVDBenv->set_lk_max_objects(OVDBenv, ovdb_conf.maxlocks);
1522 
1523     if(ovdb_conf.txn_nosync)
1524 	OVDBenv->set_flags(OVDBenv, DB_TXN_NOSYNC, 1);
1525 
1526     if((flags & (OVDB_UPGRADE | OVDB_RECOVER)) != OVDB_UPGRADE) {
1527 	ret = OVDBenv->open(OVDBenv, ovdb_conf.home, ai_flags, 0666);
1528 	if (ret != 0) {
1529 	    OVDBenv->close(OVDBenv, 0);
1530 	    OVDBenv = NULL;
1531             warn("OVDB: OVDBenv->open: %s", db_strerror(ret));
1532 	    return ret;
1533 	}
1534     }
1535 
1536     return 0;
1537 }
1538 
ovdb_open(int mode)1539 bool ovdb_open(int mode)
1540 {
1541     int i, ret;
1542     DB_TXN *tid;
1543 
1544     if(OVDBenv != NULL || clientmode) {
1545         warn("OVDB: ovdb_open called more than once");
1546 	return false;
1547     }
1548 
1549     read_ovdb_conf();
1550     if(ovdb_conf.readserver && mode == OV_READ)
1551 	clientmode = 1;
1552 
1553     if(mode & OVDB_SERVER)
1554 	clientmode = 0;
1555 
1556     if(clientmode) {
1557 	if(client_connect() == 0)
1558 	    return true;
1559 	clientmode = 0;
1560     }
1561     if(!ovdb_check_user()) {
1562         warn("OVDB: must be running as runasuser user to access overview");
1563 	return false;
1564     }
1565     if(!ovdb_getlock(OVDB_LOCK_NORMAL)) {
1566         syswarn("OVDB: ovdb_getlock failed");
1567 	return false;
1568     }
1569 
1570     if(ovdb_open_berkeleydb(mode, 0) != 0)
1571 	return false;
1572 
1573     if(check_version() != 0)
1574 	return false;
1575 
1576     if(mode & OV_WRITE || mode & OVDB_SERVER) {
1577 	oneatatime = 0;
1578     } else {
1579 	oneatatime = 1;
1580     }
1581 
1582     dbs = xcalloc(ovdb_conf.numdbfiles, sizeof(DB *));
1583 
1584     if(!oneatatime) {
1585 	for(i = 0; i < ovdb_conf.numdbfiles; i++) {
1586             ret = open_db_file(i);
1587 	    if (ret != 0) {
1588                 warn("OVDB: open_db_file failed: %s", db_strerror(ret));
1589 		return false;
1590 	    }
1591 	}
1592     }
1593 
1594     ret = db_create(&groupinfo, OVDBenv, 0);
1595     if (ret != 0) {
1596         warn("OVDB: open: db_create: %s", db_strerror(ret));
1597 	return false;
1598     }
1599     TXN_START_NORETRY(t_open_groupinfo, tid);
1600     ret = groupinfo->open(groupinfo, tid, "groupinfo", NULL, DB_BTREE,
1601                           _db_flags, 0666);
1602     if (ret == 0)
1603 	TXN_COMMIT(t_open_groupinfo, tid);
1604     if (ret != 0) {
1605 	groupinfo->close(groupinfo, 0);
1606         warn("OVDB: open: groupinfo->open: %s", db_strerror(ret));
1607 	return false;
1608     }
1609     ret = db_create(&groupaliases, OVDBenv, 0);
1610     if (ret != 0) {
1611         warn("OVDB: open: db_create: %s", db_strerror(ret));
1612 	return false;
1613     }
1614     TXN_START_NORETRY(t_open_groupaliases, tid);
1615     ret = groupaliases->open(groupaliases, tid, "groupaliases", NULL, DB_HASH,
1616                              _db_flags, 0666);
1617     if (ret == 0)
1618 	TXN_COMMIT(t_open_groupaliases, tid);
1619     if (ret != 0) {
1620 	groupaliases->close(groupaliases, 0);
1621         warn("OVDB: open: groupaliases->open: %s", db_strerror(ret));
1622 	return false;
1623     }
1624 
1625     Cutofflow = false;
1626     return true;
1627 }
1628 
1629 
1630 bool
ovdb_groupstats(const char * group,int * lo,int * hi,int * count,int * flag)1631 ovdb_groupstats(const char *group, int *lo, int *hi, int *count, int *flag)
1632 {
1633     int ret;
1634     struct groupinfo gi;
1635 
1636     if (clientmode) {
1637 	struct rs_cmd rs;
1638 	struct rs_groupstats repl;
1639 
1640 	rs.what = CMD_GROUPSTATS;
1641 	rs.grouplen = strlen(group)+1;
1642 
1643 	if (csend(&rs, sizeof(rs)) < 0)
1644             return false;
1645 	if (csend(group, rs.grouplen) < 0)
1646             return false;
1647 	crecv(&repl, sizeof(repl));
1648 
1649 	if(repl.status != CMD_GROUPSTATS)
1650 	    return false;
1651 
1652 	/* we don't use the alias yet, but the OV API will be extended
1653 	   at some point so that the alias is returned also */
1654 	if(repl.aliaslen != 0) {
1655 	    char *buf = xmalloc(repl.aliaslen);
1656 	    crecv(buf, repl.aliaslen);
1657 	    free(buf);
1658 	}
1659 
1660 	if(lo)
1661 	    *lo = repl.lo;
1662 	if(hi)
1663 	    *hi = repl.hi;
1664 	if(count)
1665 	    *count = repl.count;
1666 	if(flag)
1667 	    *flag = repl.flag;
1668 	return true;
1669     }
1670 
1671     ret = ovdb_getgroupinfo(group, &gi, true, NULL, 0);
1672     switch (ret)
1673     {
1674     case 0:
1675 	break;
1676     case DB_NOTFOUND:
1677 	return false;
1678     default:
1679         warn("OVDB: ovdb_getgroupinfo failed: %s", db_strerror(ret));
1680 	return false;
1681     }
1682 
1683     if(lo != NULL)
1684 	*lo = gi.low;
1685     if(hi != NULL)
1686 	*hi = gi.high;
1687     if(count != NULL)
1688 	*count = gi.count;
1689     if(flag != NULL)
1690 	*flag = gi.flag;
1691     return true;
1692 }
1693 
1694 bool
ovdb_groupadd(const char * group,ARTNUM lo,ARTNUM hi,char * flag)1695 ovdb_groupadd(const char *group, ARTNUM lo, ARTNUM hi, char *flag)
1696 {
1697     DBT key, val;
1698     struct groupinfo gi;
1699     DB_TXN *tid;
1700     int ret = 0;
1701     int new;
1702 
1703     memset(&key, 0, sizeof key);
1704     memset(&val, 0, sizeof val);
1705 
1706     TXN_START(t_groupadd, tid);
1707 
1708     if(tid==NULL)
1709 	return false;
1710 
1711     new = 0;
1712     ret = ovdb_getgroupinfo(group, &gi, false, tid, DB_RMW);
1713     switch (ret)
1714     {
1715     case DB_NOTFOUND:
1716 	new = 1;
1717     case 0:
1718 	break;
1719     case TRYAGAIN:
1720 	TXN_RETRY(t_groupadd, tid);
1721     default:
1722 	TXN_ABORT(t_groupadd, tid);
1723         warn("OVDB: ovdb_getgroupinfo failed: %s", db_strerror(ret));
1724 	return false;
1725     }
1726 
1727     if(!new && (gi.status & GROUPINFO_DELETED)
1728 		&& !(gi.status & GROUPINFO_EXPIRING)
1729 		&& !(gi.status & GROUPINFO_MOVING)) {
1730 	int s, c = 0;
1731 	char g[MED_BUFFER];
1732 
1733 	strlcpy(g, group, sizeof(g));
1734 	s = strlen(g) + 1;
1735 	key.data = g;
1736 	key.size = s + sizeof(int);
1737 	do {
1738 	    c++;
1739 	    memcpy(g+s, &c, sizeof(int));
1740 	    ret = groupinfo->get(groupinfo, tid, &key, &val, 0);
1741 	} while(ret == 0);
1742 	if(ret == TRYAGAIN) {
1743 	    TXN_RETRY(t_groupadd, tid);
1744 	}
1745 	val.data = &gi;
1746 	val.size = sizeof(gi);
1747         ret = groupinfo->put(groupinfo, tid, &key, &val, 0);
1748 	switch (ret)
1749         {
1750 	case 0:
1751 	    break;
1752 	case TRYAGAIN:
1753 	    TXN_RETRY(t_groupadd, tid);
1754 	default:
1755 	    TXN_ABORT(t_groupadd, tid);
1756             warn("OVDB: groupinfo->put: %s", db_strerror(ret));
1757 	    return false;
1758 	}
1759 	key.data = (char *) group;
1760 	key.size = strlen(group);
1761         ret = groupinfo->del(groupinfo, tid, &key, 0);
1762 	switch (ret)
1763         {
1764 	case 0:
1765 	    break;
1766 	case TRYAGAIN:
1767 	    TXN_RETRY(t_groupadd, tid);
1768 	default:
1769 	    TXN_ABORT(t_groupadd, tid);
1770             warn("OVDB: groupinfo->del: %s", db_strerror(ret));
1771 	    return false;
1772 	}
1773 	new = 1;
1774     }
1775 
1776     if(new) {
1777         ret = groupid_new(&gi.current_gid, tid);
1778 	switch (ret)
1779         {
1780 	case 0:
1781 	    break;
1782 	case TRYAGAIN:
1783 	    TXN_RETRY(t_groupadd, tid);
1784 	default:
1785 	    TXN_ABORT(t_groupadd, tid);
1786             warn("OVDB: groupid_new: %s", db_strerror(ret));
1787 	    return false;
1788 	}
1789 	gi.low = lo ? lo : 1;
1790 	gi.high = hi;
1791 	gi.count = 0;
1792 	gi.flag = *flag;
1793 	gi.expired = time(NULL);
1794 	gi.expiregrouppid = 0;
1795 	gi.current_db = gi.new_db = which_db(group);
1796 	gi.new_gid = gi.current_gid;
1797 	gi.status = 0;
1798     } else {
1799 	gi.flag = *flag;
1800     }
1801 
1802     key.data = (char *) group;
1803     key.size = strlen(group);
1804     val.data = &gi;
1805     val.size = sizeof gi;
1806 
1807     ret = groupinfo->put(groupinfo, tid, &key, &val, 0);
1808     switch (ret) {
1809     case 0:
1810 	break;
1811     case TRYAGAIN:
1812 	TXN_RETRY(t_groupadd, tid);
1813     default:
1814 	TXN_ABORT(t_groupadd, tid);
1815         warn("OVDB: groupadd: groupinfo->put: %s", db_strerror(ret));
1816 	return false;
1817     }
1818 
1819     if(*flag == NF_FLAG_ALIAS) {
1820 	key.data = (char *) group;
1821 	key.size = strlen(group);
1822 	val.data = flag + 1;
1823 	val.size = strcspn(flag, "\n") - 1;
1824 
1825 	switch(ret = groupaliases->put(groupaliases, tid, &key, &val, 0)) {
1826 	case 0:
1827 	    break;
1828 	case TRYAGAIN:
1829 	    TXN_RETRY(t_groupadd, tid);
1830 	default:
1831 	    TXN_ABORT(t_groupadd, tid);
1832             warn("OVDB: groupadd: groupaliases->put: %s", db_strerror(ret));
1833 	    return false;
1834 	}
1835     }
1836 
1837     TXN_COMMIT(t_groupadd, tid);
1838     return true;
1839 }
1840 
1841 bool
ovdb_groupdel(const char * group)1842 ovdb_groupdel(const char *group)
1843 {
1844     DBT key, val;
1845     struct groupinfo gi;
1846     DB_TXN *tid;
1847     int ret = 0;
1848 
1849     memset(&key, 0, sizeof key);
1850     memset(&val, 0, sizeof val);
1851 
1852     /* We only need to set the deleted flag in groupinfo to prevent readers
1853        from seeing this group.  The actual overview records aren't deleted
1854        now, since that could take a significant amount of time (and innd
1855        is who normally calls this function).  The expireover run will
1856        clean up the deleted groups. */
1857 
1858     TXN_START(t_groupdel, tid);
1859 
1860     if(tid==NULL)
1861 	return false;
1862 
1863     ret = ovdb_getgroupinfo(group, &gi, true, tid, DB_RMW);
1864     switch (ret)
1865     {
1866     case DB_NOTFOUND:
1867 	return true;
1868     case 0:
1869 	break;
1870     case TRYAGAIN:
1871 	TXN_RETRY(t_groupdel, tid);
1872     default:
1873         warn("OVDB: ovdb_getgroupinfo failed: %s", db_strerror(ret));
1874 	TXN_ABORT(t_groupdel, tid);
1875 	return false;
1876     }
1877 
1878     gi.status |= GROUPINFO_DELETED;
1879 
1880     key.data = (char *) group;
1881     key.size = strlen(group);
1882     val.data = &gi;
1883     val.size = sizeof gi;
1884 
1885     switch(ret = groupinfo->put(groupinfo, tid, &key, &val, 0)) {
1886     case 0:
1887 	break;
1888     case TRYAGAIN:
1889 	TXN_RETRY(t_groupdel, tid);
1890     default:
1891 	TXN_ABORT(t_groupdel, tid);
1892         warn("OVDB: groupadd: groupinfo->put: %s", db_strerror(ret));
1893 	return false;
1894     }
1895 
1896     switch(ret = groupaliases->del(groupaliases, tid, &key, 0)) {
1897     case 0:
1898     case DB_NOTFOUND:
1899 	break;
1900     case TRYAGAIN:
1901 	TXN_RETRY(t_groupdel, tid);
1902     default:
1903         warn("OVDB: groupdel: groupaliases->del: %s", db_strerror(ret));
1904 	TXN_ABORT(t_groupdel, tid);
1905 	return false;
1906     }
1907 
1908     TXN_COMMIT(t_groupdel, tid);
1909     return true;
1910 }
1911 
1912 bool
ovdb_add(const char * group,ARTNUM artnum,TOKEN token,char * data,int len,time_t arrived,time_t expires)1913 ovdb_add(const char *group, ARTNUM artnum, TOKEN token, char *data, int len,
1914          time_t arrived, time_t expires)
1915 {
1916     static size_t databuflen = 0;
1917     static char *databuf;
1918 #ifdef HAVE_ZLIB
1919     uLong	c_sz = 0;
1920 #else
1921     #define	c_sz 0
1922 #endif
1923     DB		*db;
1924     DBT		key, val;
1925     DB_TXN	*tid;
1926     struct groupinfo gi;
1927     struct datakey dk;
1928     int ret = 0;
1929 
1930     memset(&dk, 0, sizeof dk);
1931 
1932     if(databuflen == 0) {
1933 	databuflen = BIG_BUFFER;
1934 	databuf = xmalloc(databuflen);
1935     }
1936 #ifdef HAVE_ZLIB
1937     if(ovdb_conf.compress) {
1938 	/* Allow for worst-case compression */
1939 	c_sz = len + (len / 1000) + 20;
1940     }
1941 #endif
1942 
1943     if(databuflen < len + sizeof(struct ovdata) + c_sz) {
1944 	databuflen = len + sizeof(struct ovdata) + c_sz;
1945         databuf = xrealloc(databuf, databuflen);
1946     }
1947 
1948     /* Hmm...  Berkeley DB needs something like a 'struct iovec' so that we don't
1949        have to make a new buffer and copy everything in to it. */
1950 
1951     ((struct ovdata *)databuf)->token = token;
1952     ((struct ovdata *)databuf)->arrived = arrived;
1953     ((struct ovdata *)databuf)->expires = expires;
1954 
1955 #ifdef HAVE_ZLIB
1956     if(ovdb_conf.compress && len > COMPRESS_MIN) {
1957 	uint32_t sz;
1958 	c_sz -= sizeof(uint32_t);
1959 	ret = compress(
1960 		(Bytef *)(databuf + sizeof(struct ovdata) + sizeof(uint32_t)),
1961 		&c_sz, (Bytef *)data, (uLong)len);
1962 	if(ret != Z_OK) {
1963 	    memcpy(databuf + sizeof(struct ovdata), data, len);
1964 	    len += sizeof(struct ovdata);
1965 	} else {
1966 	    sz = htonl((uint32_t)len);
1967 	    memcpy(databuf + sizeof(struct ovdata), &sz, sizeof(uint32_t));
1968 
1969 	    /* The following line is mostly paranoia. Just want to make sure
1970 	       that the first byte is 0 (it should be 0 anyway), so ovdb_search
1971 	       recognizes this as compressed data.  */
1972 	    *(databuf + sizeof(struct ovdata)) = 0;
1973 
1974 	    len = c_sz + sizeof(struct ovdata) + sizeof(uint32_t);
1975 	}
1976     } else {
1977 #endif
1978       memcpy(databuf + sizeof(struct ovdata), data, len);
1979       len += sizeof(struct ovdata);
1980 #ifdef HAVE_ZLIB
1981     }
1982 #endif
1983 
1984     memset(&key, 0, sizeof key);
1985     memset(&val, 0, sizeof val);
1986 
1987     /* start the transaction */
1988     TXN_START(t_add, tid);
1989 
1990     if(tid==NULL)
1991 	return false;
1992 
1993     /* first, retrieve groupinfo */
1994     ret = ovdb_getgroupinfo(group, &gi, true, tid, DB_RMW);
1995     switch (ret)
1996     {
1997     case 0:
1998 	break;
1999     case DB_NOTFOUND:
2000 	TXN_ABORT(t_add, tid);
2001 	return true;
2002     case TRYAGAIN:
2003 	TXN_RETRY(t_add, tid);
2004     default:
2005 	TXN_ABORT(t_add, tid);
2006         warn("OVDB: add: ovdb_getgroupinfo: %s", db_strerror(ret));
2007 	return false;
2008     }
2009 
2010     /* adjust groupinfo */
2011     if(Cutofflow && gi.low > artnum) {
2012 	TXN_ABORT(t_add, tid);
2013 	return true;
2014     }
2015     if(gi.low == 0 || gi.low > artnum)
2016 	gi.low = artnum;
2017     if(gi.high < artnum)
2018 	gi.high = artnum;
2019     gi.count++;
2020 
2021     /* store groupinfo */
2022     key.data = (char *) group;
2023     key.size = strlen(group);
2024     val.data = &gi;
2025     val.size = sizeof gi;
2026 
2027     ret = groupinfo->put(groupinfo, tid, &key, &val, 0);
2028     switch (ret)
2029     {
2030     case 0:
2031 	break;
2032     case TRYAGAIN:
2033 	TXN_RETRY(t_add, tid);
2034     default:
2035 	TXN_ABORT(t_add, tid);
2036         warn("OVDB: add: groupinfo->put: %s", db_strerror(ret));
2037 	return false;
2038     }
2039 
2040     /* store overview */
2041     db = get_db_bynum(gi.current_db);
2042     if(db == NULL) {
2043 	TXN_ABORT(t_add, tid);
2044 	return false;
2045     }
2046     dk.groupnum = gi.current_gid;
2047     dk.artnum = htonl((u_int32_t)artnum);
2048 
2049     key.data = &dk;
2050     key.size = sizeof dk;
2051     val.data = databuf;
2052     val.size = len;
2053 
2054     ret = db->put(db, tid, &key, &val, 0);
2055     switch (ret)
2056     {
2057     case 0:
2058 	break;
2059     case TRYAGAIN:
2060 	TXN_RETRY(t_add, tid);
2061     default:
2062 	TXN_ABORT(t_add, tid);
2063         warn("OVDB: add: db->put: %s", db_strerror(ret));
2064 	return false;
2065     }
2066 
2067     if(artnum < gi.high && gi.status & GROUPINFO_MOVING) {
2068 	/* If the GROUPINFO_MOVING flag is set, then expireover
2069 	   is writing overview records under a new groupid.
2070 	   If this overview record is not at the highmark,
2071 	   we need to also store it under the new groupid */
2072 	db = get_db_bynum(gi.new_db);
2073 	if(db == NULL) {
2074 	    TXN_ABORT(t_add, tid);
2075 	    return false;
2076 	}
2077 	dk.groupnum = gi.new_gid;
2078 
2079         ret = db->put(db, tid, &key, &val, 0);
2080 	switch (ret)
2081         {
2082 	case 0:
2083 	    break;
2084 	case TRYAGAIN:
2085 	    TXN_RETRY(t_add, tid);
2086 	default:
2087 	    TXN_ABORT(t_add, tid);
2088             warn("OVDB: add: db->put: %s", db_strerror(ret));
2089 	    return false;
2090 	}
2091     }
2092 
2093     TXN_COMMIT(t_add, tid);
2094     return true;
2095 }
2096 
ovdb_cancel(const char * group UNUSED,ARTNUM artnum UNUSED)2097 bool ovdb_cancel(const char *group UNUSED, ARTNUM artnum UNUSED)
2098 {
2099     return true;
2100 }
2101 
2102 #ifdef HAVE_ZLIB
2103 static char *
myuncompress(char * buf,size_t buflen,size_t * newlen)2104 myuncompress(char *buf, size_t buflen, size_t *newlen)
2105 {
2106     static char *dbuf = NULL;
2107     static uLongf dbuflen = 0, ulen;
2108     uint32_t sz;
2109     int ret;
2110 
2111     memcpy(&sz, buf, sizeof(sz));
2112     sz = ntohl(sz);
2113 
2114     if(sz >= dbuflen) {
2115 	if(dbuflen == 0) {
2116 	    dbuflen = sz + 512;
2117 	    dbuf = xmalloc(dbuflen);
2118 	} else {
2119 	    dbuflen = sz + 512;
2120 	    dbuf = xrealloc(dbuf, dbuflen);
2121 	}
2122     }
2123     ulen = dbuflen - 1;
2124 
2125     ret = uncompress((Bytef *)dbuf, &ulen, (Bytef *)(buf + sizeof(uint32_t)), buflen - sizeof(uint32_t));
2126     if(ret != Z_OK) {
2127 	warn("OVDB: uncompress failed");
2128 	return NULL;
2129     }
2130     dbuf[ulen] = 0;	/* paranoia */
2131     if(newlen)
2132 	*newlen = ulen;
2133     return dbuf;
2134 }
2135 #endif
2136 
2137 struct ovdbsearch {
2138     DBC *cursor;
2139     group_id_t gid;
2140     u_int32_t firstart;
2141     u_int32_t lastart;
2142     int state;
2143 };
2144 
2145 /* Even though nnrpd only does one search at a time, a read server process
2146    could do many concurrent searches; hence we must keep track of an arbitrary
2147    number of open searches */
2148 static struct ovdbsearch **searches = NULL;
2149 static int nsearches = 0;
2150 static int maxsearches = 0;
2151 
2152 void *
ovdb_opensearch(const char * group,int low,int high)2153 ovdb_opensearch(const char *group, int low, int high)
2154 {
2155     DB *db;
2156     struct ovdbsearch *s;
2157     struct groupinfo gi;
2158     int ret;
2159 
2160     if(clientmode) {
2161 	struct rs_cmd rs;
2162 	struct rs_opensrch repl;
2163 
2164 	rs.what = CMD_OPENSRCH;
2165 	rs.grouplen = strlen(group)+1;
2166 	rs.artlo = low;
2167 	rs.arthi = high;
2168 
2169 	if (csend(&rs, sizeof(rs)) < 0)
2170             return NULL;
2171 	if (csend(group, rs.grouplen) < 0)
2172             return NULL;
2173 	crecv(&repl, sizeof(repl));
2174 
2175 	if(repl.status != CMD_OPENSRCH)
2176 	    return NULL;
2177 
2178 	return repl.handle;
2179     }
2180 
2181     ret = ovdb_getgroupinfo(group, &gi, true, NULL, 0);
2182     switch (ret)
2183     {
2184     case 0:
2185 	break;
2186     case DB_NOTFOUND:
2187 	return NULL;
2188     default:
2189         warn("OVDB: ovdb_getgroupinfo failed: %s", db_strerror(ret));
2190 	return NULL;
2191     }
2192 
2193     s = xmalloc(sizeof(struct ovdbsearch));
2194     db = get_db_bynum(gi.current_db);
2195     if(db == NULL) {
2196 	free(s);
2197 	return NULL;
2198     }
2199 
2200     ret = db->cursor(db, NULL, &(s->cursor), 0);
2201     if (ret != 0) {
2202         warn("OVDB: opensearch: s->db->cursor: %s", db_strerror(ret));
2203 	free(s);
2204 	return NULL;
2205     }
2206 
2207     s->gid = gi.current_gid;
2208     s->firstart = low;
2209     s->lastart = high;
2210     s->state = 0;
2211 
2212     if(searches == NULL) {
2213 	nsearches = 0;
2214 	maxsearches = 50;
2215 	searches = xmalloc(sizeof(struct ovdbsearch *) * maxsearches);
2216     }
2217     if(nsearches == maxsearches) {
2218 	maxsearches += 50;
2219 	searches = xrealloc(searches, sizeof(struct ovdbsearch *) * maxsearches);
2220     }
2221     searches[nsearches] = s;
2222     nsearches++;
2223 
2224     return (void *)s;
2225 }
2226 
2227 bool
ovdb_search(void * handle,ARTNUM * artnum,char ** data,int * len,TOKEN * token,time_t * arrived)2228 ovdb_search(void *handle, ARTNUM *artnum, char **data, int *len,
2229             TOKEN *token, time_t *arrived)
2230 {
2231     struct ovdbsearch *s = (struct ovdbsearch *)handle;
2232     DBT key, val;
2233     u_int32_t flags;
2234     struct ovdata ovd;
2235     uint32_t sz = 0;
2236     struct datakey dk;
2237     int ret;
2238     char *dp;
2239 
2240     if (clientmode) {
2241 	struct rs_cmd rs;
2242 	struct rs_srch repl;
2243 	static char *databuf;
2244 	static int buflen = 0;
2245 
2246 	rs.what = CMD_SRCH;
2247 	rs.handle = handle;
2248 
2249 	if (csend(&rs, sizeof(rs)) < 0)
2250             return false;
2251 	if (crecv(&repl, sizeof(repl)) < 0)
2252             return false;
2253 
2254 	if(repl.status != CMD_SRCH)
2255 	    return false;
2256 	if(repl.len > buflen) {
2257 	    if(buflen == 0) {
2258 		buflen = repl.len + 512;
2259 		databuf = xmalloc(buflen);
2260 	    } else {
2261 		buflen = repl.len + 512;
2262                 databuf = xrealloc(databuf, buflen);
2263 	    }
2264 	}
2265 	crecv(databuf, repl.len);
2266 
2267 	if(artnum)
2268 	    *artnum = repl.artnum;
2269 	if(token)
2270 	    *token = repl.token;
2271 	if(arrived)
2272 	    *arrived = repl.arrived;
2273 	if(len)
2274 	    *len = repl.len;
2275 	if(data)
2276 	    *data = databuf;
2277 	return true;
2278     }
2279 
2280     switch(s->state) {
2281     case 0:
2282 	flags = DB_SET_RANGE;
2283 	memset(&dk, 0, sizeof dk);
2284 	dk.groupnum = s->gid;
2285 	dk.artnum = htonl(s->firstart);
2286 	s->state = 1;
2287 	break;
2288     case 1:
2289 	flags = DB_NEXT;
2290 	break;
2291     case 2:
2292 	s->state = 3;
2293 	return false;
2294     default:
2295         warn("OVDB: OVsearch called again after false return");
2296 	return false;
2297     }
2298 
2299     memset(&key, 0, sizeof key);
2300     memset(&val, 0, sizeof val);
2301     key.data = &dk;
2302     key.size = key.ulen = sizeof(struct datakey);
2303     key.flags = DB_DBT_USERMEM;
2304 
2305     if(!data && !len) {
2306 	/* caller doesn't need data, so we don't have to retrieve it all */
2307 	val.flags |= DB_DBT_PARTIAL;
2308 
2309 	if(token || arrived)
2310 	    val.dlen = sizeof(struct ovdata);
2311     }
2312 
2313     switch(ret = s->cursor->c_get(s->cursor, &key, &val, flags)) {
2314     case 0:
2315 	break;
2316     case DB_NOTFOUND:
2317 	s->state = 3;
2318 	s->cursor->c_close(s->cursor);
2319 	s->cursor = NULL;
2320 	return false;
2321     default:
2322         warn("OVDB: search: c_get: %s", db_strerror(ret));
2323 	s->state = 3;
2324 	s->cursor->c_close(s->cursor);
2325 	s->cursor = NULL;
2326 	return false;
2327     }
2328 
2329     if(key.size != sizeof(struct datakey)) {
2330 	s->state = 3;
2331 	s->cursor->c_close(s->cursor);
2332 	s->cursor = NULL;
2333 	return false;
2334     }
2335 
2336     if(dk.groupnum != s->gid || ntohl(dk.artnum) > s->lastart) {
2337 	s->state = 3;
2338 	s->cursor->c_close(s->cursor);
2339 	s->cursor = NULL;
2340 	return false;
2341     }
2342 
2343     if( ((len || data) && val.size <= sizeof(struct ovdata))
2344 	|| ((token || arrived) && val.size < sizeof(struct ovdata)) ) {
2345         warn("OVDB: search: bad value length");
2346 	s->state = 3;
2347 	s->cursor->c_close(s->cursor);
2348 	s->cursor = NULL;
2349 	return false;
2350     }
2351 
2352     if(ntohl(dk.artnum) == s->lastart) {
2353 	s->state = 2;
2354 	s->cursor->c_close(s->cursor);
2355 	s->cursor = NULL;
2356     }
2357 
2358     if(artnum)
2359 	*artnum = ntohl(dk.artnum);
2360 
2361     if(token || arrived)
2362 	memcpy(&ovd, val.data, sizeof(struct ovdata));
2363     if(token)
2364 	*token = ovd.token;
2365     if(arrived)
2366 	*arrived = ovd.arrived;
2367 
2368     dp = (char *)val.data + sizeof(struct ovdata);
2369 #ifdef HAVE_ZLIB
2370     if(val.size >= (sizeof(struct ovdata) + sizeof(uint32_t)) && *dp == 0) {
2371 	/* data is compressed */
2372 	memcpy(&sz, dp, sizeof(uint32_t));
2373 	sz = ntohl(sz);
2374 	if(sz > MAX_UNZIP_SZ) {  /* sanity check */
2375 	    warn("OVDB: search: bogus sz: %d", sz);
2376 	    sz = 0;
2377 	}
2378     }
2379 #endif
2380 
2381     if(len) {
2382 	if(sz) {
2383 	    *len = sz;
2384 	} else {
2385 	    *len = val.size - sizeof(struct ovdata);
2386 	}
2387     }
2388 
2389     if(data) {
2390 #ifdef HAVE_ZLIB
2391 	if(sz && val.size > sizeof(struct ovdata) + sizeof(uint32_t)) {
2392 	    *data = myuncompress(dp, val.size - sizeof(struct ovdata), NULL);
2393 	    if(*data == NULL) {
2394 		s->state = 3;
2395 		s->cursor->c_close(s->cursor);
2396 		s->cursor = NULL;
2397 		return false;
2398 	    }
2399 	} else
2400 #endif
2401 	  *data = dp;
2402     }
2403 
2404     return true;
2405 }
2406 
ovdb_closesearch(void * handle)2407 void ovdb_closesearch(void *handle)
2408 {
2409     int i;
2410     if(clientmode) {
2411 	struct rs_cmd rs;
2412 
2413 	rs.what = CMD_CLOSESRCH;
2414 	rs.handle = handle;
2415 	csend(&rs, sizeof(rs));
2416 	/* no reply is sent for a CMD_CLOSESRCH */
2417     } else {
2418 	struct ovdbsearch *s = (struct ovdbsearch *)handle;
2419 
2420 	if(s->cursor)
2421 	    s->cursor->c_close(s->cursor);
2422 
2423 	for(i = 0; i < nsearches; i++) {
2424 	    if(s == searches[i]) {
2425 		break;
2426 	    }
2427 	}
2428 	nsearches--;
2429 	for( ; i < nsearches; i++) {
2430 	    searches[i] = searches[i+1];
2431 	}
2432 
2433 	free(handle);
2434     }
2435 }
2436 
2437 bool
ovdb_getartinfo(const char * group,ARTNUM artnum,TOKEN * token)2438 ovdb_getartinfo(const char *group, ARTNUM artnum, TOKEN *token)
2439 {
2440     int ret, cdb = 0;
2441     group_id_t cgid = 0;
2442     DB *db;
2443     DBT key, val;
2444     struct ovdata ovd;
2445     struct datakey dk;
2446     struct groupinfo gi;
2447     int pass = 0;
2448 
2449     if(clientmode) {
2450 	struct rs_cmd rs;
2451 	struct rs_artinfo repl;
2452 
2453 	rs.what = CMD_ARTINFO;
2454 	rs.grouplen = strlen(group)+1;
2455 	rs.artlo = artnum;
2456 
2457 	if (csend(&rs, sizeof(rs)) < 0)
2458             return false;
2459 	if (csend(group, rs.grouplen) < 0)
2460             return false;
2461 	crecv(&repl, sizeof(repl));
2462 
2463 	if(repl.status != CMD_ARTINFO)
2464 	    return false;
2465 
2466 	if(token)
2467 	    *token = repl.token;
2468 
2469 	return true;
2470     }
2471 
2472     while(1) {
2473         ret = ovdb_getgroupinfo(group, &gi, true, NULL, 0);
2474 	switch (ret)
2475         {
2476 	case 0:
2477 	    break;
2478 	case DB_NOTFOUND:
2479 	    return false;
2480 	default:
2481             warn("OVDB: ovdb_getgroupinfo failed: %s", db_strerror(ret));
2482 	    return false;
2483 	}
2484 
2485 	if(pass) {
2486 	    /* This was our second groupinfo retrieval; because the article
2487 	       retrieval came up empty.  If the group ID hasn't changed
2488 	       since the first groupinfo retrieval, we can assume the article
2489 	       is definitely not there.  Otherwise, we'll try to retrieve
2490 	       it the article again. */
2491 	    if(cdb == gi.current_db && cgid == gi.current_gid)
2492 		return false;
2493 	}
2494 
2495 	db = get_db_bynum(gi.current_db);
2496 	if(db == NULL)
2497 	    return false;
2498 
2499 	memset(&dk, 0, sizeof dk);
2500 	dk.groupnum = gi.current_gid;
2501 	dk.artnum = htonl((u_int32_t)artnum);
2502 
2503 	memset(&key, 0, sizeof key);
2504 	memset(&val, 0, sizeof val);
2505 
2506 	key.data = &dk;
2507 	key.size = sizeof dk;
2508 
2509         /* caller doesn't need data, so we don't have to retrieve it all */
2510         val.flags = DB_DBT_PARTIAL;
2511 
2512         if(token)
2513             val.dlen = sizeof(struct ovdata);
2514 
2515 	switch(ret = db->get(db, NULL, &key, &val, 0)) {
2516 	case 0:
2517 	case DB_NOTFOUND:
2518 	    break;
2519 	default:
2520             warn("OVDB: getartinfo: db->get: %s", db_strerror(ret));
2521 	    return false;
2522 	}
2523 
2524 	if(ret == DB_NOTFOUND) {
2525 	    /* If the group is being moved (i.e., its group ID is going
2526 	       to change), there's a chance the article is now under the
2527 	       new ID.  So we'll grab the groupinfo again to check for
2528 	       that. */
2529 	    if(!pass && (gi.status & GROUPINFO_MOVING)) {
2530 		cdb = gi.current_db;
2531 		cgid = gi.current_gid;
2532 		pass++;
2533 		continue;
2534 	    }
2535 	    return false;
2536 	}
2537 	break;
2538     }
2539 
2540     if(token && val.size < sizeof(struct ovdata)) {
2541         warn("OVDB: getartinfo: data too short");
2542 	return false;
2543     }
2544 
2545     if(token) {
2546 	memcpy(&ovd, val.data, sizeof(struct ovdata));
2547 	*token = ovd.token;
2548     }
2549     return true;
2550 }
2551 
2552 bool
ovdb_expiregroup(const char * group,int * lo,struct history * h)2553 ovdb_expiregroup(const char *group, int *lo, struct history *h)
2554 {
2555     DB *db, *ndb = NULL;
2556     DBT key, val, nkey, gkey, gval;
2557     DB_TXN *tid;
2558     DBC *cursor = NULL;
2559     int ret = 0, delete, old_db = 0, cleanup;
2560     struct groupinfo gi;
2561     struct ovdata ovd;
2562     struct datakey dk, ndk;
2563     group_id_t old_gid = 0;
2564     ARTHANDLE *ah;
2565     u_int32_t artnum = 0, currentart, lowest;
2566     int i, compact, done, currentcount, newcount;
2567 
2568     if(eo_start == 0) {
2569 	eo_start = time(NULL);
2570 	delete_old_stuff(0);	/* remove deleted groups first */
2571     }
2572 
2573     /* Special case:  when called with NULL group, we're to clean out
2574      * records for forgotton groups (groups removed from the active file
2575      * but not from overview).
2576      * This happens at the end of the expireover run, and only if all
2577      * of the groups in the active file have been processed.
2578      * delete_old_stuff(1) will remove groups that are in ovdb but
2579      * have not been processed during this run.
2580      */
2581 
2582     if(group == NULL)
2583 	return delete_old_stuff(1);
2584 
2585     memset(&key, 0, sizeof key);
2586     memset(&nkey, 0, sizeof nkey);
2587     memset(&val, 0, sizeof val);
2588     memset(&dk, 0, sizeof dk);
2589     memset(&ndk, 0, sizeof ndk);
2590 
2591     TXN_START(t_expgroup_1, tid);
2592 
2593     if(tid==NULL)
2594 	return false;
2595 
2596     cleanup = 0;
2597 
2598     ret = ovdb_getgroupinfo(group, &gi, true, tid, DB_RMW);
2599     switch (ret)
2600     {
2601     case 0:
2602 	break;
2603     case TRYAGAIN:
2604 	TXN_RETRY(t_expgroup_1, tid);
2605     default:
2606         warn("OVDB: expiregroup: ovdb_getgroupinfo failed: %s",
2607              db_strerror(ret));
2608         /* fallthrough */
2609     case DB_NOTFOUND:
2610 	TXN_ABORT(t_expgroup_1, tid);
2611 	return false;
2612     }
2613 
2614     if(gi.status & GROUPINFO_EXPIRING) {
2615 	/* is there another expireover working on this group? */
2616         ret = kill(gi.expiregrouppid, 0);
2617 	switch(ret)
2618         {
2619 	case 0:
2620 	case EPERM:
2621 	    TXN_ABORT(t_expgroup_1, tid);
2622 	    return false;
2623 	}
2624 
2625 	/* a previous expireover run must've died.  We'll clean
2626 	   up after it */
2627 	if(gi.status & GROUPINFO_MOVING) {
2628 	    cleanup = 1;
2629 	    old_db = gi.new_db;
2630 	    old_gid = gi.new_gid;
2631 
2632 	    ret = mk_temp_groupinfo(old_db, old_gid, tid);
2633 	    switch(ret) {
2634 	    case 0:
2635 	    	break;
2636 	    case TRYAGAIN:
2637 	    	TXN_RETRY(t_expgroup_1, tid);
2638 	    default:
2639 	    	TXN_ABORT(t_expgroup_1, tid);
2640 		return false;
2641 	    }
2642 	    gi.status &= ~GROUPINFO_MOVING;
2643 	}
2644     }
2645 
2646     if(gi.count < ovdb_conf.nocompact || ovdb_conf.nocompact == 0)
2647 	compact = 1;
2648     else
2649 	compact = 0;
2650 
2651     if(gi.count == 0)
2652 	compact = 0;
2653 
2654     db = get_db_bynum(gi.current_db);
2655     if(db == NULL) {
2656 	TXN_ABORT(t_expgroup_1, tid);
2657 	return false;
2658     }
2659 
2660     gi.status |= GROUPINFO_EXPIRING;
2661     gi.expiregrouppid = getpid();
2662     if(compact) {
2663 	gi.status |= GROUPINFO_MOVING;
2664 	gi.new_db = gi.current_db;
2665 	ndb = db;
2666         ret = groupid_new(&gi.new_gid, tid);
2667 	switch (ret)
2668         {
2669 	case 0:
2670 	    break;
2671 	case TRYAGAIN:
2672 	    TXN_RETRY(t_expgroup_1, tid);
2673 	default:
2674 	    TXN_ABORT(t_expgroup_1, tid);
2675             warn("OVDB: expiregroup: groupid_new: %s", db_strerror(ret));
2676 	    return false;
2677 	}
2678     }
2679 
2680     key.data = (char *) group;
2681     key.size = strlen(group);
2682     val.data = &gi;
2683     val.size = sizeof gi;
2684 
2685     ret = groupinfo->put(groupinfo, tid, &key, &val, 0);
2686     switch (ret)
2687     {
2688     case 0:
2689 	break;
2690     case TRYAGAIN:
2691 	TXN_RETRY(t_expgroup_1, tid);
2692     default:
2693 	TXN_ABORT(t_expgroup_1, tid);
2694         warn("OVDB: expiregroup: groupinfo->put: %s", db_strerror(ret));
2695 	return false;
2696     }
2697     TXN_COMMIT(t_expgroup_1, tid);
2698 
2699     if(cleanup) {
2700 	if(delete_all_records(old_db, old_gid) == 0) {
2701 	    rm_temp_groupinfo(old_gid);
2702 	}
2703     }
2704 
2705     /*
2706      * The following loop iterates over the OV records for the group in
2707      * "batches", to limit transaction sizes.
2708      *
2709      * loop {
2710      *    start transaction
2711      *    get groupinfo
2712      *    process EXPIREGROUP_TXN_SIZE records
2713      *    write updated groupinfo
2714      *    commit transaction
2715      * }
2716      */
2717     currentart = 0;
2718     lowest = currentcount = 0;
2719 
2720     memset(&gkey, 0, sizeof gkey);
2721     memset(&gval, 0, sizeof gval);
2722     gkey.data = (char *) group;
2723     gkey.size = strlen(group);
2724     gval.data = &gi;
2725     gval.size = sizeof gi;
2726 
2727     while(1) {
2728 	TXN_START(t_expgroup_loop, tid);
2729 	if(tid==NULL)
2730 	    return false;
2731         done = 0;
2732 	newcount = 0;
2733 
2734         ret = ovdb_getgroupinfo(group, &gi, false, tid, DB_RMW);
2735 	switch (ret)
2736         {
2737 	case 0:
2738 	    break;
2739 	case TRYAGAIN:
2740 	    TXN_RETRY(t_expgroup_loop, tid);
2741 	default:
2742 	    TXN_ABORT(t_expgroup_loop, tid);
2743             warn("OVDB: expiregroup: ovdb_getgroupinfo: %s",
2744                  db_strerror(ret));
2745 	    return false;
2746 	}
2747 
2748         ret = db->cursor(db, tid, &cursor, 0);
2749 	switch (ret)
2750         {
2751 	case 0:
2752 	    break;
2753 	case TRYAGAIN:
2754 	    TXN_RETRY(t_expgroup_loop, tid);
2755 	default:
2756 	    TXN_ABORT(t_expgroup_loop, tid);
2757             warn("OVDB: expiregroup: db->cursor: %s", db_strerror(ret));
2758 	    return false;
2759 	}
2760 
2761 	dk.groupnum = gi.current_gid;
2762 	dk.artnum = htonl(currentart);
2763 	key.data = &dk;
2764 	key.size = key.ulen = sizeof dk;
2765 	key.flags = DB_DBT_USERMEM;
2766 
2767 	for(i=0; i < EXPIREGROUP_TXN_SIZE; i++) {
2768             ret = cursor->c_get(cursor, &key, &val,
2769                                 i == 0 ? DB_SET_RANGE : DB_NEXT);
2770 	    switch (ret)
2771             {
2772 	    case 0:
2773 	    case DB_NOTFOUND:
2774 		break;
2775 	    case TRYAGAIN:
2776 		cursor->c_close(cursor);
2777 		TXN_RETRY(t_expgroup_loop, tid);
2778 	    default:
2779 		cursor->c_close(cursor);
2780 		TXN_ABORT(t_expgroup_loop, tid);
2781                 warn("OVDB: expiregroup: c_get: %s", db_strerror(ret));
2782 		return false;
2783 	    }
2784 
2785 	    /* stop if: there are no more keys, an unknown key is reached,
2786 	       or reach a different group */
2787 
2788 	    if(ret == DB_NOTFOUND
2789 		    || key.size != sizeof dk
2790 		    || dk.groupnum != gi.current_gid) {
2791 		done++;
2792 		break;
2793 	    }
2794 
2795 	    artnum = ntohl(dk.artnum);
2796 
2797 	    delete = 0;
2798 	    if(val.size < sizeof ovd) {
2799 		delete = 1;	/* must be corrupt, just delete it */
2800 	    } else {
2801 		char *p = (char *)val.data + sizeof(ovd);
2802 		size_t sz = val.size - sizeof(ovd);
2803 #ifdef HAVE_ZLIB
2804 		if(*p == 0)
2805 		    p = myuncompress(p, sz, &sz);
2806 		if(p == NULL) {
2807 		    p = (char *) "";
2808 		    delete = 1;
2809 		}
2810 #endif
2811 		memcpy(&ovd, val.data, sizeof ovd);
2812 
2813 		ah = NULL;
2814 		if (!SMprobe(EXPENSIVESTAT, &ovd.token, NULL) || OVstatall) {
2815 		    if((ah = SMretrieve(ovd.token, RETR_STAT)) == NULL) {
2816 			delete = 1;
2817 		    } else
2818 			SMfreearticle(ah);
2819 		} else {
2820 		    if (!delete && !OVhisthasmsgid(h, p)) {
2821 			delete = 1;
2822 		    }
2823 		}
2824 		if (!delete && innconf->groupbaseexpiry
2825 			    && OVgroupbasedexpire(ovd.token, group,
2826 				    p, sz, ovd.arrived, ovd.expires)) {
2827 		    delete = 1;
2828 		}
2829 	    }
2830 
2831 	    if(delete) {
2832 		if(!compact) {
2833 		    switch(ret = cursor->c_del(cursor, 0)) {
2834 		    case 0:
2835 		    case DB_NOTFOUND:
2836 		    case DB_KEYEMPTY:
2837 			break;
2838 		    case TRYAGAIN:
2839 			cursor->c_close(cursor);
2840 			TXN_RETRY(t_expgroup_loop, tid);
2841 		    default:
2842 			cursor->c_close(cursor);
2843 			TXN_ABORT(t_expgroup_loop, tid);
2844                         warn("OVDB: expiregroup: c_del: %s",
2845                              db_strerror(ret));
2846 			return false;
2847 		    }
2848 		}
2849 		if(gi.count > 0)
2850 		    gi.count--;
2851 	    } else {
2852 		if(compact) {
2853 		    ndk.groupnum = gi.new_gid;
2854 		    ndk.artnum = dk.artnum;
2855 		    nkey.data = &ndk;
2856 		    nkey.size = sizeof ndk;
2857 
2858 		    switch(ret = ndb->put(ndb, tid, &nkey, &val, 0)) {
2859 		    case 0:
2860 			break;
2861 		    case TRYAGAIN:
2862 			cursor->c_close(cursor);
2863 			TXN_RETRY(t_expgroup_loop, tid);
2864 		    default:
2865 			cursor->c_close(cursor);
2866 			TXN_ABORT(t_expgroup_loop, tid);
2867                         warn("OVDB: expiregroup: ndb->put: %s",
2868                              db_strerror(ret));
2869 			return false;
2870 		    }
2871 		}
2872 		newcount++;
2873                 if (lowest != (u_int32_t) -1)
2874                     if (lowest == 0 || artnum < lowest)
2875                         lowest = artnum;
2876 	    }
2877 	}
2878 	/* end of for loop */
2879 
2880 	if(cursor->c_close(cursor) == TRYAGAIN) {
2881 	    TXN_RETRY(t_expgroup_loop, tid);
2882 	}
2883 
2884 	if(lowest != 0 && lowest != (u_int32_t) -1)
2885 	    gi.low = lowest;
2886 
2887 	if(done) {
2888 	    if(compact) {
2889 		old_db = gi.current_db;
2890 		gi.current_db = gi.new_db;
2891 		old_gid = gi.current_gid;
2892 		gi.current_gid = gi.new_gid;
2893 		gi.status &= ~GROUPINFO_MOVING;
2894 
2895 		ret = mk_temp_groupinfo(old_db, old_gid, tid);
2896 		switch(ret) {
2897 		case 0:
2898 		    break;
2899 		case TRYAGAIN:
2900 		    TXN_RETRY(t_expgroup_loop, tid);
2901 		default:
2902 		    TXN_ABORT(t_expgroup_loop, tid);
2903 		    return false;
2904 		}
2905 	    }
2906 
2907 	    gi.status &= ~GROUPINFO_EXPIRING;
2908 	    gi.expired = time(NULL);
2909 	    if(gi.count == 0 && lowest == 0)
2910 		gi.low = gi.high+1;
2911 	}
2912 
2913         ret = groupinfo->put(groupinfo, tid, &gkey, &gval, 0);
2914 	switch (ret)
2915         {
2916 	case 0:
2917 	    break;
2918 	case TRYAGAIN:
2919 	    TXN_RETRY(t_expgroup_loop, tid);
2920 	default:
2921 	    TXN_ABORT(t_expgroup_loop, tid);
2922             warn("OVDB: expiregroup: groupinfo->put: %s", db_strerror(ret));
2923 	    return false;
2924 	}
2925         TXN_COMMIT(t_expgroup_loop, tid);
2926 
2927 	currentcount += newcount;
2928 	if(lowest != 0)
2929 	    lowest = (u_int32_t) -1;
2930 
2931 	if(done)
2932 	    break;
2933 
2934 	currentart = artnum+1;
2935     }
2936 
2937     if(compact) {
2938 	if(delete_all_records(old_db, old_gid) == 0) {
2939 	    rm_temp_groupinfo(old_gid);
2940 	}
2941     }
2942 
2943     if(currentcount != gi.count) {
2944         notice("OVDB: expiregroup: recounting %s", group);
2945 
2946 	TXN_START(t_expgroup_recount, tid);
2947 	if(tid == NULL)
2948 	    return false;
2949 
2950 	switch(ret = ovdb_getgroupinfo(group, &gi, false, tid, DB_RMW)) {
2951 	case 0:
2952 	    break;
2953 	case TRYAGAIN:
2954 	    TXN_RETRY(t_expgroup_recount, tid);
2955 	default:
2956 	    TXN_ABORT(t_expgroup_recount, tid);
2957             warn("OVDB: expiregroup: ovdb_getgroupinfo: %s",
2958                  db_strerror(ret));
2959 	    return false;
2960 	}
2961 
2962 	if(count_records(&gi) != 0) {
2963 	    TXN_ABORT(t_expgroup_recount, tid);
2964 	    return false;
2965 	}
2966 
2967         ret = groupinfo->put(groupinfo, tid, &gkey, &gval, 0);
2968 	switch (ret)
2969         {
2970 	case 0:
2971 	    break;
2972 	case TRYAGAIN:
2973 	    TXN_RETRY(t_expgroup_recount, tid);
2974 	default:
2975 	    TXN_ABORT(t_expgroup_recount, tid);
2976             warn("OVDB: expiregroup: groupinfo->put: %s", db_strerror(ret));
2977 	    return false;
2978 	}
2979         TXN_COMMIT(t_expgroup_recount, tid);
2980     }
2981 
2982     if(lo)
2983 	*lo = gi.low;
2984     return true;
2985 }
2986 
ovdb_ctl(OVCTLTYPE type,void * val)2987 bool ovdb_ctl(OVCTLTYPE type, void *val)
2988 {
2989     int *i;
2990     float *f;
2991     OVSORTTYPE *sorttype;
2992     bool *boolval;
2993 
2994     switch (type) {
2995     case OVSPACE:
2996         f = (float *)val;
2997         *f = -1.0f;
2998         return true;
2999     case OVSORT:
3000         sorttype = (OVSORTTYPE *)val;
3001         *sorttype = OVNEWSGROUP;
3002         return true;
3003     case OVCUTOFFLOW:
3004         Cutofflow = *(bool *)val;
3005         return true;
3006     case OVSTATICSEARCH:
3007 	i = (int *)val;
3008 	*i = true;
3009 	return true;
3010     case OVCACHEKEEP:
3011     case OVCACHEFREE:
3012         boolval = (bool *)val;
3013         *boolval = false;
3014         return true;
3015     default:
3016         return false;
3017     }
3018 }
3019 
ovdb_close_berkeleydb(void)3020 void ovdb_close_berkeleydb(void)
3021 {
3022     if(OVDBenv) {
3023 	/* close db environment */
3024 	OVDBenv->close(OVDBenv, 0);
3025 	OVDBenv = NULL;
3026     }
3027 }
3028 
ovdb_close(void)3029 void ovdb_close(void)
3030 {
3031     int i;
3032 
3033     if(clientmode) {
3034 	client_disconnect();
3035 	return;
3036     }
3037 
3038     while(searches != NULL && nsearches) {
3039 	ovdb_closesearch(searches[0]);
3040     }
3041     if(searches != NULL) {
3042 	free(searches);
3043 	searches = NULL;
3044     }
3045 
3046     if(dbs) {
3047 	/* close databases */
3048 	for(i = 0; i < ovdb_conf.numdbfiles; i++)
3049 	    close_db_file(i);
3050 
3051 	free(dbs);
3052 	dbs = NULL;
3053     }
3054     if(groupinfo) {
3055 	groupinfo->close(groupinfo, 0);
3056 	groupinfo = NULL;
3057     }
3058     if(groupaliases) {
3059 	groupaliases->close(groupaliases, 0);
3060 	groupaliases = NULL;
3061     }
3062 
3063     ovdb_close_berkeleydb();
3064     ovdb_releaselock();
3065 }
3066 
3067 #endif /* HAVE_BDB */
3068