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