1 /*
2 * (c) Copyright 1990, Kim Fabricius Storm. All rights reserved.
3 * Copyright (c) 1996-2005 Michael T Pins. All rights reserved.
4 *
5 * Database access and update
6 */
7
8 #include <unistd.h>
9 #include <stdlib.h>
10 #include <string.h>
11 #include <strings.h>
12 #include <ctype.h>
13 #include <errno.h>
14 #include "config.h"
15 #include "global.h"
16 #include "answer.h"
17 #include "articles.h"
18 #include "db.h"
19 #include "libnov.h"
20 #include "nntp.h"
21 #include "nn_term.h"
22 #include "pack_date.h"
23 #include "pack_name.h"
24 #include "pack_subject.h"
25
26 #ifdef NOV
27 #include "hash.h"
28 #include "newsoverview.h"
29 #endif /* NOV */
30
31 /* db.c */
32
33 #ifndef NOV
34 static char *mk_archive_file(register group_header *, register char *, int);
35 static void db_read_master(void);
36 static void db_read_group(register group_header *);
37 #endif /* !NOV */
38
39 static int sort_gh(group_header ** g1, group_header ** g2);
40 struct novgroup *nntp_get_overview(group_header *, article_number, article_number);
41
42 #ifdef NOV
43 static void db_init_group(group_header * gh, int num);
44 static void db_init_active(group_header * gh);
45 static void readtimfile(void);
46 static void readactfile(void);
47 static void
48 db_fixup_cross_postings(data_header * dhp,
49 data_dynamic_data * ddp,
50 struct novart * artp);
51
52 static struct novgroup *ngovp;
53 static struct novart *allarts;
54 static char **actlist, **grplist;
55 static HASHTABLE *timtbl;
56 #endif /* NOV */
57
58 #ifdef CACHE_PURPOSE
59
60 struct purp_list {
61 char *group_name;
62 char *purpose;
63 } *purp_list;
64
65 static void cache_purpose(void);
66 static int purp_cnt;
67 #endif /* CACHE_PURPOSE */
68
69 /*
70 * String pool allocation
71 */
72 /* Badly broken. Fix ASAP */
73
74 typedef struct stlisthdr {
75 struct stlist *next;
76 } stlisthdr_t;
77
78 typedef struct stlist {
79 stlisthdr_t n;
80 char str[1];
81 } stlist_t;
82
83 #if defined(NOV) || defined(CACHE_PURPOSE)
84 static char *strkeep(char *s, int hdr, int poolid);
85 static char *pmalloc(int size, int poolid);
86 static void pfree(int poolid);
87 #endif
88
89 #define POOL_GRP 0
90 #define POOL_PUR 1
91 #define POOL_ACT 2
92 #define POOL_TIM 3
93 #define POOL_MAX 3
94 #define POOL_TMP 4
95 #define RCX_NEVER 0
96
97 extern char
98 *master_directory, *db_directory, *db_data_directory, *news_directory, *news_lib_directory;
99 extern int db_data_subdirs, new_group_action;
100
101 master_header master;
102
103 #ifdef MALLOC_64K_LIMITATION
104 group_header **active_groups = NULL;
105 #else
106 group_header *active_groups = NULL;
107 #endif /* MALLOC_64K_LIMITATION */
108
109 group_header **sorted_groups = NULL;
110
111 int reread_groups_file = 0; /* nnmaster -G */
112
113 int check_group_access = 0;
114
115 data_header db_hdr;
116 data_dynamic_data db_data;
117
118 int32 db_read_counter = 0; /* articles read by db_read_art */
119 int32 db_write_counter = 0; /* articles written by db_write_art */
120
121 /*
122 * Init access to a group
123 */
124
125 group_header *current_group = NULL;
126
127 char group_path_name[FILENAME];
128 char *group_file_name = NULL;
129
130 static char *group_position = NULL;
131 static article_number current_digest_article = 0;
132
133 int
init_group(register group_header * gh)134 init_group(register group_header * gh)
135 {
136 register char *p, *q;
137
138 current_digest_article = 0;
139
140 if (gh == NULL)
141 return 0;
142 /* if (gh->master_flag & M_IGNORE_GROUP) return 0; *//* OBS */
143 if (gh == current_group)
144 return 1;
145
146 current_group = gh;
147
148 if (gh->group_flag & G_FOLDER) {
149 group_position = NULL;
150 group_file_name = NULL;
151 strcpy(group_path_name, gh->archive_file);
152 return 1;
153 }
154
155 #ifdef NNTP
156 if (use_nntp && nntp_set_group(gh) < 0)
157 return 0;
158 #endif /* NNTP */
159
160 if (group_position == NULL) {
161 if (who_am_i == I_AM_MASTER || who_am_i == I_AM_EXPIRE) {
162 group_position = group_path_name;
163 } else {
164 strcpy(group_path_name, news_directory);
165 group_position = group_path_name + strlen(group_path_name);
166 *group_position++ = '/';
167 }
168 }
169 for (p = group_position, q = gh->group_name; *q; q++)
170 *p++ = (*q == '.') ? '/' : *q;
171
172 if (who_am_i == I_AM_MASTER) {
173
174 /* The master will chdir to the group's directory to get */
175 /* better performance (can use relative path names). */
176
177 *p++ = NUL;
178
179 if (!use_nntp) {
180 if (chdir(news_directory) < 0)
181 sys_error(news_directory);
182
183 if (chdir(group_path_name) < 0)
184 return 0;
185 }
186 group_file_name = group_path_name;
187 return 1;
188 }
189 /* client */
190 if (gh->master_flag & M_NO_DIRECTORY)
191 return 0;
192
193 if (check_group_access && !use_nntp) {
194 *p = NUL;
195 if (file_exist(group_path_name, "dxr") == 0)
196 return 0;
197 }
198 *p++ = '/';
199 *p = NUL;
200 group_file_name = p;
201 return 1;
202 }
203
204 #ifndef NOV
205 /*
206 * Open master & group file; read it in if first open.
207 *
208 * GROUPS file format is:
209 *
210 * One line per group:
211 * <group name> [<space><timestamp>] [<space><options>] <NL>
212 * If <timestamp> is omitted, a timestamp of 0 is used (very old group).
213 * If <group name> is "@", the master entry is ignored.
214 *
215 * <options>:
216 * D Digest all articles in the group
217 * N Never digest articles
218 * @ Bogus group, ignore completely
219 * ! Don't collect this group
220 *
221 * Do not edit the GROUPS file while nnmaster is running.
222 * After editing the groups file (options), run nnmaster -G.
223 */
224
225 static FILE *group_file = NULL;
226
227 int
open_groups(int mode)228 open_groups(int mode)
229 {
230 group_file = open_file(relative(db_directory, "GROUPS"), mode);
231
232 if (group_file != NULL && (mode & OPEN_CREATE)) {
233 fprintf(group_file,
234 "#\n#\tNEVER MODIFY THIS FILE WHILE nnmaster IS RUNNING\n");
235 fprintf(group_file,
236 "#\n#\tRUN 'nnmaster -G' AFTER MODIFYING THIS FILE\n");
237 fprintf(group_file,
238 "#\n#\tDO NOT REMOVE OR REORDER ANY LINES IN THIS FILE\n#\n");
239 }
240 return group_file != NULL;
241 }
242
243 void
close_groups(void)244 close_groups(void)
245 {
246 if (group_file != NULL) {
247 fclose(group_file);
248 group_file = NULL;
249 }
250 }
251
252 static void
db_append_group(register group_header * gh)253 db_append_group(register group_header * gh)
254 {
255 char flags[16], *fp;
256
257 if (gh->group_name[0] == NUL) {
258 fputc('@', group_file);
259 goto out;
260 }
261 fprintf(group_file, "%s", gh->group_name);
262 if (gh->creation_time > 0)
263 fprintf(group_file, " %ld", (long) (gh->creation_time));
264
265 fp = flags;
266
267 if (gh->master_flag & M_IGNORE_G)
268 *fp++ = '!';
269 if (gh->master_flag & M_ALWAYS_DIGEST)
270 *fp++ = 'D';
271 if (gh->master_flag & M_NEVER_DIGEST)
272 *fp++ = 'N';
273 if (gh->master_flag & M_INCLUDE_OLD)
274 *fp++ = 'O';
275 if (gh->master_flag & M_AUTO_RECOLLECT)
276 *fp++ = 'R';
277 if (gh->archive_file != NULL)
278 *fp++ = '>';
279
280 if (fp != flags) {
281 *fp++ = NUL;
282 fprintf(group_file, " %s%s", flags,
283 gh->archive_file != NULL ? gh->archive_file : "");
284 }
285 out:
286 fputc(NL, group_file);
287 }
288
289 void
db_rewrite_groups(int save_groups,group_header * group_list)290 db_rewrite_groups(int save_groups, group_header * group_list)
291 {
292 register group_header *gh;
293
294 if (save_groups)
295 if (save_old_file(relative(db_directory, "GROUPS"), "~") < 0)
296 sys_error("Cannot rename GROUPS file");
297
298 open_groups(OPEN_CREATE | MUST_EXIST);
299
300 if (group_list != NULL) {
301 for (gh = group_list->next_group; gh != NULL; gh = gh->next_group)
302 db_append_group(gh);
303 } else {
304 Loop_Groups_Header(gh) {
305 db_append_group(gh);
306 }
307 }
308 close_groups();
309 }
310
311 static char *
mk_archive_file(register group_header * gh,register char * cp,int logerr)312 mk_archive_file(register group_header * gh, register char *cp, int logerr)
313 {
314 char *name;
315
316 while (*cp && (*cp == '>' || isspace(*cp)))
317 cp++;
318
319 name = cp;
320 while (*cp && !isspace(*cp))
321 cp++;
322 if (*cp)
323 *cp++ = NUL;
324
325 if (*name) {
326 gh->archive_file = copy_str(name);
327
328 if (*name == '/')
329 gh->master_flag |= M_AUTO_ARCHIVE;
330 else {
331 gh->master_flag &= ~M_AUTO_ARCHIVE;
332 if (who_am_i == I_AM_MASTER) {
333 if (logerr)
334 log_entry('E', "GROUPS %s >%s: Full path required",
335 gh->group_name, name);
336 else
337 printf("Error in GROUPS: %s >%s: Full path required\n",
338 gh->group_name, name);
339 }
340 }
341 }
342 return cp;
343 }
344
345 int
db_parse_group(register group_header * gh,int trust_master)346 db_parse_group(register group_header * gh, int trust_master)
347 /* trust_master trust what is in the master file */
348 {
349 char line[256];
350 register char *cp, *name;
351 int ignore;
352
353 do {
354 if (fgets(line, 256, group_file) == NULL)
355 return 0;
356 for (cp = line; *cp && isspace(*cp); cp++);
357 } while (*cp == NUL || *cp == '#');
358
359 gh->archive_file = NULL;
360
361 name = cp;
362
363 if (trust_master) {
364 if (gh->group_name_length == 0) {
365 gh->group_name = "";
366 return 1;
367 }
368 cp = name + gh->group_name_length;
369 if (*cp == NUL || !isspace(*cp))
370 sys_error("MASTER/GROUPS conflict: %d/%s", gh->group_num, line);
371 } else {
372 /* parse GROUPS line */
373
374 if (*cp == '@') {
375 ignore = 1;
376 gh->group_name_length = 0;
377 gh->group_name = "";
378 goto ignore_group;
379 }
380 if (gh->group_name_length == 0) {
381 while (*cp && !isspace(*cp))
382 cp++;
383 gh->group_name_length = cp - name;
384 } else {
385 cp = name + gh->group_name_length;
386 if (*cp == NUL || !isspace(*cp)) {
387 sys_error("MASTER/GROUPS conflict: %d/%s", gh->group_num, line);
388 }
389 }
390 }
391
392 if (*cp)
393 *cp++ = NUL;
394 if (gh->group_name_length > 0)
395 gh->group_name = copy_str(name);
396 else
397 gh->group_name = "";
398
399 if (trust_master) {
400 if (gh->master_flag & M_AUTO_ARCHIVE) {
401 while (*cp && *cp != '>')
402 cp++;
403 if (*cp == '>')
404 mk_archive_file(gh, cp, 1);
405 }
406 return 1;
407 }
408 while (*cp && isspace(*cp))
409 cp++;
410
411 if (*cp && isdigit(*cp)) {
412 gh->creation_time = atol(cp);
413 while (*cp && isdigit(*cp))
414 cp++;
415 } else
416 gh->creation_time = 0;
417
418 while (*cp && isspace(*cp))
419 cp++;
420
421 ignore = 0;
422 gh->master_flag &= ~(M_ALWAYS_DIGEST | M_NEVER_DIGEST |
423 M_AUTO_RECOLLECT | M_AUTO_ARCHIVE);
424
425 while (*cp) {
426 switch (*cp++) {
427 case ' ':
428 case '\t':
429 case NL:
430 case CR:
431 continue;
432
433 case 'D': /* Collect this group, digest all articles */
434 gh->master_flag |= M_ALWAYS_DIGEST;
435 continue;
436
437 case 'N': /* Collect this group, never digest articles */
438 gh->master_flag |= M_NEVER_DIGEST;
439 continue;
440
441 case 'O': /* Ignore -O option for this group */
442 gh->master_flag |= M_INCLUDE_OLD;
443 continue;
444
445 case 'R': /* Recollect this group when new articles
446 * arrive */
447 gh->master_flag |= M_AUTO_RECOLLECT;
448 continue;
449
450 case '>': /* Archive all new articles in
451 * gh->archive_file */
452 cp = mk_archive_file(gh, cp, 0);
453 continue;
454
455 case '@': /* Bogus GROUP -- ignore completely */
456 ignore = 1;
457 gh->group_name_length = 0;
458 break;
459
460 case '!': /* Do not collect this group */
461 case 'X':
462 ignore = 1;
463 break;
464
465 case '#': /* comment */
466 *cp = NUL;
467 break;
468
469 default:
470 printf("Bad GROUPS flag for %s: `%c'\n", gh->group_name, *--cp);
471 break;
472 }
473 break;
474 }
475
476 ignore_group:
477
478 /* G_DONE indicates to master that the group must be cleaned */
479
480 if (ignore) {
481 if ((gh->master_flag & M_IGNORE_GROUP) == 0) {
482 gh->master_flag |= M_MUST_CLEAN;
483 log_entry('X', "Group %s ignored", gh->group_name);
484 }
485 gh->master_flag |= M_IGNORE_G;
486 } else { /* was group ignored in GROUPS, but not
487 * active before? */
488 if ((gh->master_flag & M_IGNORE_GROUP) == M_IGNORE_G) {
489 gh->master_flag &= ~M_NO_DIRECTORY;
490 log_entry('X', "Group %s activated", gh->group_name);
491 }
492 gh->master_flag &= ~M_IGNORE_G;
493 }
494
495 return 1;
496 }
497
498 static FILE *master_file = NULL;
499 static int db_sequential = 0;
500
501 #ifdef APOLLO_DOMAIN_OS
502 /* make copy of master file to CLIENT, but only once every 100 passes or */
503 /* if forced */
504 static int
make_master_copy(int force_copy)505 make_master_copy(int force_copy)
506 {
507 char client_path[FILENAME];
508 static int pass_count = 0;
509 int n;
510
511 if (!force_copy) { /* if not forced copy, only do copy every
512 * %100 passes */
513 pass_count++;
514 if (pass_count < 100) {
515 return;
516 }
517 }
518 pass_count = 0;
519
520 strcpy(client_path, relative(db_directory, "CLIENT"));
521 unlink(client_path);
522 if ((n = copy_file(relative(db_directory, "MASTER"), client_path, 0)) < 0)
523 log_entry('R', "Copy of MASTER to CLIENT failed (err=%d)", n);
524 }
525
526 #endif /* APOLLO_DOMAIN_OS */
527
528 #endif /* !NOV */
529
530 #ifdef NOV
531 /*
532 * Init the groups data from active file.
533 */
534 void
open_master(int mode)535 open_master(int mode)
536 {
537 register group_header *gh;
538
539 freeobj(sorted_groups);
540 freeobj(active_groups);
541 active_groups = NULL;
542 sorted_groups = NULL;
543
544 readactfile(); /* Read in the active file - count groups */
545 readtimfile(); /* Read the newsgroup creation time file */
546
547 db_expand_master(); /* uses count from readact() call! */
548
549 Loop_Groups_Header(gh) {
550 /* db_read_group opens a file per call; use db_init_group instead */
551 db_init_group(gh, l_g_index);
552 }
553
554 sort_groups();
555
556 Loop_Groups_Header(gh) {
557 db_init_active(gh);
558 }
559
560 /*
561 * Free actlist and timtbl space. Don't free grplist, because
562 * active_groups[] has pointers thereto.
563 */
564 pfree(POOL_ACT);
565 free(actlist);
566
567 pfree(POOL_TIM);
568 free(timtbl);
569
570 #ifdef CACHE_PURPOSE
571 cache_purpose(); /* cache sorted newsgroups and descriptions */
572 #endif /* CACHE_PURPOSE */
573 }
574
575 #else /* NOV */
576
577 /*
578 * Open master & group files; read then in if first open.
579 */
580
581 void
open_master(int mode)582 open_master(int mode)
583 {
584 register group_header *gh;
585 int trust_master;
586
587 close_master();
588
589 #ifdef APOLLO_DOMAIN_OS
590 if (who_am_i != I_AM_MASTER && who_am_i != I_AM_ADMIN)
591 master_file = open_file(relative(db_directory, "CLIENT"), mode | MUST_EXIST);
592 else
593 #endif /* APOLLO_DOMAIN_OS */
594
595 master_file = open_file(relative(db_directory, "MASTER"), mode | MUST_EXIST);
596
597 db_sequential = 0;
598 if (mode == OPEN_CREATE)
599 db_sequential = 1;
600
601 if (mode != OPEN_READ)
602 return;
603
604 db_read_master();
605
606 if (who_am_i != I_AM_MASTER && master.db_lock[0])
607 nn_exitmsg(88, "DATABASE LOCKED.\n%s\n", master.db_lock);
608
609 freeobj(sorted_groups);
610
611 #ifdef MALLOC_64K_LIMITATION
612 if (active_groups)
613 Loop_Groups_Header(gh) freeobj(gh);
614 #endif /* MALLOC_64K_LIMITATION */
615
616 freeobj(active_groups);
617 active_groups = NULL;
618 sorted_groups = NULL;
619
620 db_expand_master();
621
622 open_groups(OPEN_READ | MUST_EXIST);
623
624 trust_master = (who_am_i != I_AM_MASTER || !reread_groups_file);
625
626 db_sequential = 1;
627
628 #ifdef MALLOC_64K_LIMITATION
629 Loop_Groups_Number(l_g_index) {
630 gh = newobj(group_header, 1);
631 active_groups[l_g_index] = gh;
632 #else
633 Loop_Groups_Header(gh) {
634 #endif /* MALLOC_64K_LIMITATION */
635
636 gh->group_num = l_g_index;
637 db_read_group(gh);
638 db_parse_group(gh, trust_master);
639 }
640 db_sequential = 0;
641
642 close_groups();
643
644 sort_groups();
645
646 #ifdef CACHE_PURPOSE
647 cache_purpose(); /* cache sorted newsgroups and descriptions */
648 #endif /* CACHE_PURPOSE */
649 }
650
651 #endif /* NOV */
652
653 void
654 db_expand_master(void)
655 {
656 master.free_groups = 20;
657
658 #ifndef NOV
659
660 #ifdef MALLOC_64K_LIMITATION
661 active_groups = resizeobj(active_groups, group_header *,
662 master.number_of_groups + master.free_groups);
663 #else /* MALLOC_64K_LIMITATION */
664 active_groups = resizeobj(active_groups, group_header,
665 master.number_of_groups + master.free_groups);
666 clearobj(active_groups + master.number_of_groups, group_header,
667 master.free_groups);
668 #endif /* MALLOC_64K_LIMITATION */
669
670 #else /* !NOV */
671 active_groups = resizeobj(active_groups, group_header,
672 master.number_of_groups + master.free_groups);
673 clearobj(active_groups + master.number_of_groups, group_header,
674 master.free_groups);
675 #endif /* !NOV */
676
677 sorted_groups = resizeobj(sorted_groups, group_header *, master.number_of_groups + master.free_groups);
678 }
679
680 #if 0
681
682 #ifdef NOV
683 static void
684 freeup(char *key, char *data, char *hook)
685 {
686 hashdelete((HASHTABLE *) data, key);
687 free(key);
688 free(data);
689 }
690
691 #endif /* NOV */
692
693 #endif /* 0 */
694
695 void
696 close_master(void)
697 {
698
699 #ifdef NOV
700
701 #if 0
702 if (ngovp) {
703 novclose(ngovp);
704 ngovp = NULL;
705 }
706 #endif /* 0 */
707
708 #else /* NOV */
709 if (master_file != NULL) {
710 fclose(master_file);
711 master_file = NULL;
712
713 #ifdef APOLLO_DOMAIN_OS
714 if (who_am_i == I_AM_MASTER)
715 make_master_copy(1);
716 #endif /* APOLLO_DOMAIN_OS */
717 }
718 #endif /* NOV */
719 }
720
721 int
722 update_group(group_header * gh)
723 {
724
725 #ifndef NOV
726 group_number numg;
727
728 numg = master.number_of_groups;
729
730 db_read_master();
731 master.number_of_groups = numg;
732 if (master.db_lock[0])
733 return -3;
734 #endif /* !NOV */
735
736 #ifndef NOV
737 db_read_group(gh);
738 #else
739 db_read_group(gh, gh->first_a_article, gh->last_a_article); /* XXX */
740 #endif /* !NOV */
741
742 if (gh->master_flag & M_IGNORE_GROUP)
743 return 0;
744 if (gh->master_flag & M_BLOCKED)
745 return -1;
746
747 return 1;
748 }
749
750
751 static int
752 sort_gh(group_header ** g1, group_header ** g2)
753 {
754 return strcmp((*g1)->group_name, (*g2)->group_name);
755 }
756
757
758 void
759 sort_groups(void)
760 {
761 register group_header *gh;
762
763 Loop_Groups_Header(gh)
764 sorted_groups[l_g_index] = gh;
765
766 quicksort(sorted_groups, master.number_of_groups, group_header *, sort_gh);
767
768 s_g_first = 0;
769 Loop_Groups_Sorted(gh)
770 if (gh->group_name[0] != NUL) {
771 s_g_first = l_g_index;
772 break;
773 }
774 }
775
776
777 group_header *
778 lookup_no_alias(char *name)
779 {
780 register int i, j, k, t;
781
782 i = s_g_first;
783 j = master.number_of_groups - 1;
784
785 while (i <= j) {
786 k = (i + j) / 2;
787
788 if ((t = strcmp(name, sorted_groups[k]->group_name)) > 0)
789 i = k + 1;
790 else if (t < 0)
791 j = k - 1;
792 else
793 return sorted_groups[k];
794 }
795
796 return NULL;
797 }
798
799 group_header *
800 lookup(char *name)
801 {
802 register group_header *gh;
803 group_header *gh_na;
804 register int32 n, x;
805
806 gh = lookup_no_alias(name);
807 if (gh == NULL || (gh->master_flag & M_ALIASED) == 0)
808 return gh;
809
810 gh_na = gh;
811 x = 16;
812 do {
813 if (--x == 0) {
814 log_entry('R', "Possible alias loop: %s", name);
815 return gh_na;
816 }
817 n = (int32) gh->data_write_offset;
818
819 /*
820 * if alias info is unreliable, return original group which will be
821 * ignored anyway
822 */
823 if (n < 0 || n >= master.number_of_groups) {
824 log_entry('R', "Bad aliasing of %s -> %d", gh->group_name, n);
825 return gh_na;
826 }
827 gh = ACTIVE_GROUP(n);
828 } while (gh->master_flag & M_ALIASED);
829
830 return gh;
831 }
832
833 int
834 art_collected(group_header * gh, article_number art_num)
835 {
836 return gh->first_db_article <= art_num && gh->last_db_article >= art_num;
837 }
838
839 #ifndef NOV
840 char *
841 db_data_path(char *namebuf, group_header * gh, char d_or_x)
842 {
843 register char *cp, *np;
844
845 if (db_data_directory != NULL) {
846
847 #ifdef DB_LONG_NAMES
848 sprintf(namebuf, "%s/%s.%c", db_data_directory, gh->group_name, d_or_x);
849 #else
850 if (db_data_subdirs)
851 sprintf(namebuf, "%s/%ld/%ld.%c", db_data_directory,
852 gh->group_num / 100, gh->group_num, d_or_x);
853 else
854 sprintf(namebuf, "%s/%ld.%c", db_data_directory, gh->group_num, d_or_x);
855 #endif /* DB_LONG_NAMES */
856 } else {
857 np = namebuf;
858 /* master chdir to the group's directory */
859 if (who_am_i != I_AM_MASTER) {
860 for (cp = news_directory; (*np = *cp++); np++);
861 *np++ = '/';
862 for (cp = gh->group_name; *cp; cp++)
863 *np++ = *cp == '.' ? '/' : *cp;
864 *np++ = '/';
865 }
866 *np++ = '.';
867 *np++ = 'n';
868 *np++ = 'n';
869 *np++ = d_or_x;
870 *np++ = NUL;
871 }
872
873 return namebuf;
874 }
875
876 /* STUB */
877 FILE *
878 open_data_file(group_header * gh, char d_or_x, int mode)
879 {
880 FILE *f;
881 char data_file[FILENAME];
882
883 db_data_path(data_file, gh, d_or_x);
884
885 if (mode == -1) {
886 if (unlink(data_file) < 0 && errno != ENOTDIR && errno != ENOENT)
887 log_entry('E', "Cannot unlink %s (errno=%d)", data_file, errno);
888 f = NULL;
889 } else {
890 again:
891 f = open_file(data_file, (mode & ~MUST_EXIST));
892 if (f != NULL)
893 return f;
894
895 #ifndef DB_LONG_NAMES
896 if (db_data_subdirs && (mode & 0xf) == OPEN_CREATE && errno == ENOENT) {
897 char *s;
898 s = strrchr(data_file, '/');
899 *s = NUL;
900 if (!file_exist(data_file, "dx")) {
901 if (mkdir(data_file, 0755) < 0)
902 sys_error("Cannot create directory %s", data_file);
903 log_entry('C', "Created directory %s", data_file);
904 *s = '/';
905 goto again;
906 }
907 *s = '/';
908 errno = ENOENT;
909 }
910 #endif /* DB_LONG_NAMES */
911
912 if (mode & MUST_EXIST)
913 sys_error("%s (%d): cannot open '%c' file (mode=%x, errno=%d)",
914 gh->group_name, (int) (gh->group_num), d_or_x,
915 mode, errno);
916 }
917 return f;
918 }
919
920 #ifdef NETWORK_DATABASE
921 #define MASTER_FIELDS 5 /* + DB_LOCK_MESSAGE bytes */
922 #define GROUP_FIELDS 9
923 #define ARTICLE_FIELDS 10
924
925
926 typedef int32 net_long;
927
928
929 #ifdef NETWORK_BYTE_ORDER
930 #define net_to_host(buf, n)
931 #define host_to_net(buf, n)
932
933 #else
934
935 static int
936 net_to_host(register net_long * buf, int lgt)
937 {
938 while (--lgt >= 0) {
939 *buf = ntohl(*buf);
940 buf++;
941 }
942 }
943
944 static int
945 host_to_net(register net_long * buf, int lgt)
946 {
947 while (--lgt >= 0) {
948 *buf = htonl(*buf);
949 buf++;
950 }
951 }
952
953 #endif /* not NETWORK_BYTE_ORDER */
954
955 #endif /* NETWORK_DATABASE */
956
957 #define NWDB_MAGIC 0x00190000 /* NN#n <-> NW#n */
958
959
960 static void
961 db_read_master(void)
962 {
963
964 #ifdef NETWORK_DATABASE
965 net_long buf[MASTER_FIELDS];
966
967 rewind(master_file);
968 if (fread((char *) buf, sizeof(net_long), MASTER_FIELDS, master_file)
969 != MASTER_FIELDS)
970 goto err;
971 if (fread(master.db_lock, sizeof(char), DB_LOCK_MESSAGE, master_file)
972 != DB_LOCK_MESSAGE)
973 goto err;
974
975 net_to_host(buf, MASTER_FIELDS);
976
977 master.db_magic = buf[0] ^ NWDB_MAGIC;
978 master.last_scan = buf[1];
979 master.last_size = buf[2];
980 master.number_of_groups = buf[3];
981 master.db_created = buf[4];
982 #else
983 rewind(master_file);
984 if (fread((char *) &master, sizeof(master_header), 1, master_file) != 1)
985 goto err;
986 #endif /* NETWORK_DATABASE */
987
988 if (master.db_magic != NNDB_MAGIC)
989 sys_error("Database magic number mismatch");
990 return;
991
992 err:
993 sys_error("Incomplete MASTER file");
994 }
995
996 #endif /* NOV */
997
998
999 #ifdef NOV
1000 static void
1001 readactfile(void)
1002 {
1003 char actline[512];
1004 int count = 0;
1005 int i;
1006 FILE *actfp;
1007 stlist_t *sthead, *stp = NULL;
1008
1009 if (actlist != NULL)
1010 return;
1011
1012 #ifdef NNTP
1013 if (use_nntp) {
1014 actfp = nntp_fopen_list("LIST");
1015 } else
1016 #endif /* NNTP */
1017
1018 actfp = fopen(relative(news_lib_directory, "active"), "r");
1019
1020 if (actfp == NULL) {
1021 nn_exitmsg(1, "could not fetch active file\n");
1022 }
1023
1024 /*
1025 * Snarf all of active up in first pass. This gives us a count of the
1026 * groups we can use for internal tables.
1027 */
1028 sthead = NULL;
1029
1030 #ifdef NNTP
1031 while (use_nntp ? nntp_fgets(actline, sizeof actline)
1032 : fgets(actline, sizeof actline, actfp))
1033 #else
1034 while (fgets(actline, sizeof actline, actfp))
1035 #endif /* NNTP */
1036
1037 {
1038 stlist_t *stnew = (stlist_t *) strkeep(actline, sizeof(stlisthdr_t), POOL_ACT);
1039 if (stnew == NULL) {
1040 nn_exitmsg(1, "out of mem for active file (at line %d)\n", count + 1);
1041 }
1042 if (sthead != NULL) {
1043 stp->n.next = stnew;
1044 stp = stnew;
1045 } else {
1046 sthead = stnew;
1047 stp = sthead;
1048 }
1049 count++;
1050 }
1051 stp->n.next = NULL;
1052
1053 if (!use_nntp)
1054 (void) fclose(actfp);
1055
1056 actlist = (char **) calloc(count + 1, sizeof(char *));
1057 grplist = (char **) calloc(count + 1, sizeof(char *));
1058 if (grplist == NULL) {
1059 nn_exitmsg(1, "can't create active or group list (%d entries)\n",
1060 count + 1);
1061 }
1062
1063 /*
1064 * Second pass (in core): Put active lines and group names into string
1065 * arrays.
1066 */
1067 for (i = 0, stp = sthead; stp && i < count; stp = stp->n.next, i++) {
1068 char *p = strchr(stp->str, ' ');
1069
1070 if (p == NULL)
1071 actlist[i] = NULL;
1072 else {
1073 *p++ = NUL;
1074 actlist[i] = p;
1075 }
1076 grplist[i] = strkeep(stp->str, 0, POOL_GRP);
1077 }
1078 actlist[count] = NULL;
1079 grplist[count] = NULL;
1080
1081 /* init the master struct */
1082 clearobj(&master, sizeof(master), 1);
1083 master.number_of_groups = count;
1084
1085 }
1086
1087 #endif /* NOV */
1088
1089
1090
1091 #ifndef NOV
1092 void
1093 db_write_master(void)
1094 {
1095
1096 #ifdef NETWORK_DATABASE
1097 net_long buf[MASTER_FIELDS];
1098
1099 buf[0] = master.db_magic ^ NWDB_MAGIC;
1100 buf[1] = master.last_scan;
1101 buf[2] = master.last_size;
1102 buf[3] = master.number_of_groups;
1103 buf[4] = master.db_created;
1104
1105 host_to_net(buf, MASTER_FIELDS);
1106 rewind(master_file);
1107 if (fwrite((char *) buf, sizeof(net_long), MASTER_FIELDS, master_file)
1108 != MASTER_FIELDS)
1109 goto err;
1110 if (fwrite(master.db_lock, sizeof(char), DB_LOCK_MESSAGE, master_file)
1111 != DB_LOCK_MESSAGE)
1112 goto err;
1113 #else
1114 rewind(master_file);
1115 if (fwrite((char *) &master, sizeof(master_header), 1, master_file) != 1)
1116 goto err;
1117 #endif /* NETWORK_DATABASE */
1118
1119 fflush(master_file);
1120
1121 #ifdef APOLLO_DOMAIN_OS
1122 if (who_am_i == I_AM_MASTER)
1123 make_master_copy(0);
1124 #endif /* APOLLO_DOMAIN_OS */
1125
1126 return;
1127
1128 err:
1129 sys_error("Write to MASTER failed");
1130 }
1131
1132
1133 static void
1134 db_read_group(register group_header * gh)
1135 {
1136
1137 #ifdef NETWORK_DATABASE
1138 net_long buf[GROUP_FIELDS];
1139
1140 if (!db_sequential)
1141 fseek(master_file,
1142 (off_t) (MASTER_FIELDS * sizeof(net_long) + DB_LOCK_MESSAGE +
1143 GROUP_FIELDS * sizeof(net_long) * gh->group_num), 0);
1144
1145 if (fread((char *) buf, sizeof(net_long), GROUP_FIELDS, master_file) != GROUP_FIELDS)
1146 goto err;
1147
1148 net_to_host(buf, GROUP_FIELDS);
1149
1150 gh->first_db_article = buf[0];
1151 gh->last_db_article = buf[1];
1152 gh->index_write_offset = buf[2];
1153 gh->data_write_offset = buf[3];
1154 gh->group_name_length = buf[4];
1155 gh->master_flag = buf[5];
1156 gh->first_a_article = buf[6];
1157 gh->last_a_article = buf[7];
1158 gh->creation_time = buf[8];
1159 #else
1160 if (!db_sequential)
1161 fseek(master_file,
1162 sizeof(master_header) + SAVED_GROUP_HEADER_SIZE(*gh) * gh->group_num, 0);
1163
1164 if (fread((char *) gh, SAVED_GROUP_HEADER_SIZE(*gh), 1, master_file) != 1)
1165 goto err;
1166 #endif /* NETWORK_DATABASE */
1167
1168 return;
1169
1170 err:
1171 sys_error("Read GROUPS failed");
1172 }
1173
1174
1175 void
1176 db_write_group(register group_header * gh)
1177 {
1178
1179 #ifdef NETWORK_DATABASE
1180 net_long buf[GROUP_FIELDS];
1181
1182 if (!db_sequential)
1183 fseek(master_file,
1184 (off_t) (MASTER_FIELDS * sizeof(net_long) + DB_LOCK_MESSAGE +
1185 GROUP_FIELDS * sizeof(net_long) * gh->group_num), 0);
1186
1187 buf[0] = gh->first_db_article;
1188 buf[1] = gh->last_db_article;
1189 buf[2] = gh->index_write_offset;
1190 buf[3] = gh->data_write_offset;
1191 buf[4] = gh->group_name_length;
1192 buf[5] = gh->master_flag;
1193 buf[6] = gh->first_a_article;
1194 buf[7] = gh->last_a_article;
1195 buf[8] = gh->creation_time;
1196
1197 host_to_net(buf, GROUP_FIELDS);
1198 if (fwrite((char *) buf, sizeof(net_long), GROUP_FIELDS, master_file) != GROUP_FIELDS)
1199 goto err;
1200 #else /* NETWORK_DATABASE */
1201 if (!db_sequential)
1202 fseek(master_file, sizeof(master_header) + SAVED_GROUP_HEADER_SIZE(*gh) * gh->group_num, 0);
1203
1204
1205 if (fwrite((char *) gh, SAVED_GROUP_HEADER_SIZE(*gh), 1, master_file) != 1)
1206 goto err;
1207 #endif /* NETWORK_DATABASE */
1208
1209 fflush(master_file);
1210
1211 #ifdef APOLLO_DOMAIN_OS
1212 if (who_am_i == I_AM_MASTER)
1213 make_master_copy(0);
1214 #endif /* APOLLO_DOMAIN_OS */
1215
1216 return;
1217
1218 err:
1219 sys_error("Write GROUPS failed");
1220 }
1221
1222 #endif /* !NOV */
1223
1224
1225 #ifdef NOV
1226 static void
1227 readtimfile(void)
1228 {
1229 char timline[512];
1230 FILE *timfp;
1231 unsigned hsize;
1232
1233 if (timtbl != NULL)
1234 return;
1235 hsize = master.number_of_groups | 0x1ff;
1236 timtbl = hashcreate(hsize, (unsigned (*) ()) NULL);
1237 if (timtbl == NULL) {
1238 nn_exitmsg(1, "can't create time hash (%d entries)\n", hsize);
1239 }
1240
1241 if (new_group_action == RCX_NEVER)
1242 return; /* we don't need it */
1243 #ifdef NNTP
1244 if (use_nntp)
1245 timfp = nntp_fopen_list("LIST active.times");
1246 else
1247 #endif /* NNTP */
1248
1249 timfp = fopen(relative(news_lib_directory, "active.times"), "r");
1250
1251 if (timfp == NULL)
1252 return; /* no great shakes if its missing */
1253
1254 /* alt.fan.marla-thrift 736668095 netnews@ccc.amdahl.com */
1255
1256 #ifdef NNTP
1257 while (use_nntp ? nntp_fgets(timline, sizeof timline)
1258 : fgets(timline, sizeof timline, timfp))
1259 #else /* NNTP */
1260 while (fgets(timline, sizeof timline, timfp))
1261 #endif /* NNTP */
1262
1263 {
1264 char *line = strkeep(timline, 0, POOL_TIM);
1265 char *p = strchr(line, ' ');
1266
1267 if (p == NULL)
1268 continue;
1269 *p++ = NUL;
1270 if (!hashstore(timtbl, line, p)) {
1271 nn_exitmsg(1, "nn: time hashstore failed\n");
1272 }
1273 }
1274
1275 if (!use_nntp)
1276 (void) fclose(timfp);
1277
1278 }
1279
1280 #endif /* NOV */
1281
1282 #ifdef CACHE_PURPOSE
1283 static int
1284 purpcmp(const void *p0, const void *p1)
1285 {
1286 return strcmp(((struct purp_list *) p0)->group_name,
1287 ((struct purp_list *) p1)->group_name);
1288 }
1289
1290 /*
1291 * Open purpose file and cache sorted list of group purposes
1292 */
1293 static void
1294 cache_purpose(void)
1295 {
1296 char buf[512];
1297 register char *p;
1298 FILE *fp;
1299 int i, ngrp;
1300 stlist_t *sthead, *stp, *stnew;
1301 struct purp_list *plp;
1302
1303 if ((fp = open_purpose_file()) == NULL
1304 || (ngrp = master.number_of_groups) == 0
1305 || purp_list != NULL) {
1306 return;
1307 }
1308 ngrp = 0;
1309 stp = sthead = NULL;
1310 while (fgets(buf, sizeof(buf), fp) != NULL) {
1311 if ((p = strchr(buf, NL)) != NULL) {
1312 *p = NUL;
1313 }
1314 stnew = (stlist_t *) strkeep(buf, sizeof(stlisthdr_t), POOL_PUR);
1315 if (stnew == NULL) {
1316 /* tough cookies. we'll just do without. */
1317 pfree(POOL_PUR);
1318 return;
1319 }
1320 if (sthead != NULL) {
1321 stp->n.next = stnew;
1322 stp = stnew;
1323 } else {
1324 sthead = stnew;
1325 stp = sthead;
1326 }
1327 stnew->n.next = NULL;
1328 ngrp++;
1329 }
1330
1331 purp_list = (struct purp_list *) calloc(ngrp + 1, sizeof(struct purp_list));
1332 if (purp_list == NULL) {
1333 pfree(POOL_PUR);
1334 return;
1335 }
1336 for (i = 0, plp = purp_list, stp = sthead; stp; stp = stp->n.next) {
1337 p = stp->str;
1338 while (!isspace(*p)) { /* skip newsgroup name */
1339 if (*++p == NUL)
1340 goto next;
1341 }
1342 *p++ = NUL;
1343
1344 while (isspace(*p)) { /* skip to group description */
1345 if (*++p == NUL)
1346 goto next;
1347 }
1348 plp->group_name = stp->str;
1349 plp->purpose = p;
1350 plp++;
1351 i++;
1352 next:
1353 continue;
1354 }
1355 plp->group_name = NULL;
1356 plp->purpose = NULL;
1357 purp_cnt = i;
1358
1359 qsort(purp_list, purp_cnt, sizeof(struct purp_list), purpcmp);
1360
1361 }
1362
1363 char *
1364 purp_lookup(char *group)
1365 {
1366 register int i, j, k, t;
1367
1368 i = 0;
1369 j = purp_cnt - 1;
1370
1371 while (i <= j) {
1372 k = (i + j) / 2;
1373
1374 if ((t = strcmp(group, purp_list[k].group_name)) > 0)
1375 i = k + 1;
1376 else if (t < 0)
1377 j = k - 1;
1378 else
1379 return purp_list[k].purpose;
1380 }
1381
1382 return "";
1383 }
1384
1385 #endif /* CACHE_PURPOSE */
1386
1387
1388 #ifndef NOV
1389 off_t
1390 db_read_art(FILE * f)
1391 {
1392 off_t bytes;
1393
1394 #ifdef NETWORK_DATABASE
1395 net_long buf[ARTICLE_FIELDS];
1396
1397 if (fread((char *) buf, sizeof(net_long), ARTICLE_FIELDS, f) != ARTICLE_FIELDS)
1398 return 0;
1399 bytes = sizeof(net_long) * ARTICLE_FIELDS;
1400
1401 net_to_host(buf, ARTICLE_FIELDS);
1402
1403 db_hdr.dh_number = buf[0];
1404 db_hdr.dh_date = buf[1];
1405 db_hdr.dh_hpos = buf[2];
1406 db_hdr.dh_lpos = buf[3];
1407 db_hdr.dh_fpos = buf[4];
1408 db_hdr.dh_lines = buf[5];
1409 db_hdr.dh_replies = buf[6];
1410 db_hdr.dh_cross_postings = buf[7];
1411 db_hdr.dh_subject_length = buf[8];
1412 db_hdr.dh_sender_length = buf[9];
1413 #else /* NETWORK_DATABASE */
1414 if (fread((char *) &db_hdr, sizeof(data_header), 1, f) != 1)
1415 return 0;
1416 bytes = sizeof(data_header);
1417 #endif /* NETWORK_DATABASE */
1418
1419 if (db_hdr.dh_number < 0) {
1420 current_digest_article = db_hdr.dh_number = -db_hdr.dh_number;
1421 db_data.dh_type = DH_DIGEST_HEADER;
1422 } else if (db_hdr.dh_number == 0) {
1423 db_hdr.dh_number = current_digest_article;
1424 db_data.dh_type = DH_SUB_DIGEST;
1425 } else {
1426 current_digest_article = 0;
1427 db_data.dh_type = DH_NORMAL;
1428 }
1429
1430 if (db_hdr.dh_cross_postings) {
1431 if (fread((char *) db_data.dh_cross, sizeof(cross_post_number),
1432 (int) db_hdr.dh_cross_postings, f)
1433 != (int) db_hdr.dh_cross_postings)
1434 return -1;
1435 bytes += sizeof(cross_post_number) * (int) db_hdr.dh_cross_postings;
1436 }
1437 if (db_hdr.dh_sender_length) {
1438 if (fread(db_data.dh_sender, sizeof(char),
1439 (int) db_hdr.dh_sender_length, f)
1440 != db_hdr.dh_sender_length)
1441 return -1;
1442 bytes += sizeof(char) * (int) db_hdr.dh_sender_length;
1443 }
1444 db_data.dh_sender[db_hdr.dh_sender_length] = NUL;
1445
1446 if (db_hdr.dh_subject_length) {
1447 if (fread(db_data.dh_subject, sizeof(char),
1448 (int) db_hdr.dh_subject_length, f)
1449 != db_hdr.dh_subject_length)
1450 return -1;
1451 bytes += sizeof(char) * (int) db_hdr.dh_subject_length;
1452 }
1453 db_data.dh_subject[db_hdr.dh_subject_length] = NUL;
1454
1455 db_read_counter++;
1456
1457 return bytes;
1458 }
1459
1460 #endif /* !NOV */
1461
1462
1463 #ifdef NOV
1464 /*
1465 * initialise *gh; this is much cheaper than calling db_read_group.
1466 */
1467 static void
1468 db_init_group(register group_header * gh, int num)
1469 {
1470 register char *p;
1471
1472 /* tidy up the struct */
1473 clearobj(gh, sizeof(struct group_header), 1);
1474
1475 gh->group_num = num;
1476 if (gh->group_name == NULL) {
1477 gh->group_name = grplist[num];
1478 if (gh->group_name == NULL) {
1479 nn_exitmsg(1, "can't map group %d to name\n", num);
1480 }
1481 gh->group_name_length = strlen(gh->group_name);
1482 }
1483 gh->master_flag = M_VALID;
1484 /* control.newgrp, etc are control groups */
1485 if (strncmp(gh->group_name, "control", 7) == 0)
1486 gh->master_flag |= M_CONTROL;
1487 /* these next two are subtle and we need to lie below */
1488 /* gh->first_db_article = 0; *//* lowest # in ov. data */
1489 /* gh->last_db_article = 0; *//* highest # in ov. data */
1490 gh->first_a_article = 1; /* lowest # in active */
1491 /* gh->last_a_article = 0; *//* highest number in active */
1492 /* gh->index_write_offset = 0; *//* dunno */
1493 /* gh->data_write_offset = 0; *//* dunno */
1494
1495 /* set the creation time */
1496 gh->creation_time = 1; /* group creation date (~epoch) */
1497 p = hashfetch(timtbl, gh->group_name);
1498 if (p != NULL) {
1499 gh->creation_time = atol(p);
1500 }
1501 }
1502
1503 /*
1504 * further initialise *gh; to be done after sort_groups(void)
1505 */
1506 static void
1507 db_init_active(register group_header * gh)
1508 {
1509 group_header *gh1;
1510 register char *p;
1511
1512 p = actlist[gh->group_num];
1513 if (p != NULL) {
1514 while (isspace(*p))
1515 ++p;
1516 gh->last_a_article = atol(p);
1517 p = strchr(p, ' ');
1518 if (p == NULL)
1519 return;
1520 gh->first_a_article = atol(++p);
1521 p = strchr(p, ' ');
1522 if (*++p == '=') { /* an alias */
1523 gh1 = lookup_no_alias(++p);
1524 if (gh1 == NULL) {
1525 log_entry('R', "Group %s aliased to unknown group (%s)",
1526 gh->group_name, p);
1527 /* ah well! leave it be */
1528 } else {
1529 gh->master_flag |= M_ALIASED;
1530 gh->data_write_offset = (long) gh1->group_num;
1531 }
1532 }
1533 }
1534 gh->first_db_article = gh->first_a_article; /* lowest # in ov. data */
1535 gh->last_db_article = gh->last_a_article; /* highest # in ov. data */
1536 }
1537
1538 /*
1539 * slurp up the overview data for this group into *gh.
1540 * this costs a file open and so should not be done frivolously.
1541 */
1542 void
1543 db_read_group(register group_header * gh, article_number first, article_number last)
1544 {
1545 register struct novart *artp, *lastartp;
1546
1547 /* db_init_group(gh, group_num?? ); already done early at init time */
1548
1549 if (ngovp != NULL)
1550 novclose(ngovp); /* trash last group's data */
1551
1552 #ifdef NNTP
1553 if (use_nntp) {
1554 ngovp = nntp_get_overview(gh, first, last);
1555 } else
1556 #endif /* NNTP */
1557
1558 ngovp = novopen(gh->group_name);
1559
1560 if (ngovp == NULL) {
1561 printf("no overview data for group `%s'\n", gh->group_name);
1562 return;
1563 }
1564 allarts = novall(ngovp, first, last);
1565 if (allarts == NULL) {
1566
1567 /*
1568 * printf("overview data inaccessible for group `%s'\n",
1569 * gh->group_name);
1570 */
1571 return;
1572 }
1573 if (!use_nntp || first == gh->first_a_article)
1574 gh->first_db_article = atol(allarts->a_num); /* lowest # */
1575
1576 if (!use_nntp || last == gh->last_a_article) {
1577
1578 lastartp = allarts;
1579 for (artp = allarts; artp != NULL; artp = artp->a_nxtnum)
1580 if (atol(artp->a_num) != 0)
1581 lastartp = artp;
1582
1583 gh->last_db_article = atol(lastartp->a_num); /* highest # */
1584 }
1585 /* Handle Digest flag */
1586 if (gh->first_db_article < 0)
1587 gh->first_db_article = -gh->first_db_article;
1588 if (gh->last_db_article < 0)
1589 gh->last_db_article = -gh->last_db_article;
1590 }
1591
1592
1593 /*
1594 * fill in db_hdr and db_data from the overview data for the next
1595 * article in this group. does weirdo nn encodings of header fields.
1596 */
1597 off_t
1598 db_read_art(FILE * f)
1599 {
1600 register data_header *dhp = &db_hdr;
1601 register data_dynamic_data *ddp = &db_data;
1602 register struct novart *artp;
1603 int recnt = 0;
1604
1605 if (ngovp == NULL || ngovp->g_first == NULL)
1606 return 0;
1607 if (ngovp->g_first == NULL) /* XXX */
1608 return 0;
1609 artp = novnext(ngovp);
1610 if (artp == NULL)
1611 return 0; /* group exhausted */
1612
1613 dhp->dh_number = atol(artp->a_num);
1614 /* printf("article #%ld\n", dhp->dh_number); *//* DEBUG */
1615 dhp->dh_date = pack_date(artp->a_date); /* "encoded Date: filed" */
1616 dhp->dh_hpos = 0; /* 1st hdr byte */
1617 dhp->dh_lpos = 1L << 30; /* last article byte */
1618 dhp->dh_fpos = 0; /* 1st article text byte */
1619 dhp->dh_lines = -1; /* -1 == "unknown" */
1620 if (isascii(artp->a_lines[0]) && isdigit(artp->a_lines[0]))
1621 dhp->dh_lines = atoi(artp->a_lines);
1622 dhp->dh_replies = 0; /* # of References: */
1623 if (artp->a_refs != NULL) {
1624 register char *p;
1625
1626 for (p = artp->a_refs; *p != '\0'; p++)
1627 if (*p == '<')
1628 dhp->dh_replies++;
1629 }
1630 db_fixup_cross_postings(dhp, ddp, artp);
1631
1632 if (dhp->dh_number < 0) {
1633 current_digest_article = dhp->dh_number = -dhp->dh_number;
1634 ddp->dh_type = DH_DIGEST_HEADER;
1635 } else if (dhp->dh_number == 0) {
1636
1637 #ifdef DO_NOV_DIGEST
1638 char *cp;
1639 if (artp->a_bytes && (cp = strchr(artp->a_bytes, ':'))) {
1640 dhp->dh_hpos = atol(++cp);
1641 if ((cp = strchr(cp, ':')) != NULL) {
1642 dhp->dh_fpos = atol(++cp);
1643 if ((cp = strchr(cp, ':')) != NULL) {
1644 dhp->dh_lpos = atol(++cp);
1645 }
1646 }
1647 }
1648 #endif /* DO_NOV_DIGEST */
1649
1650 dhp->dh_number = current_digest_article;
1651 ddp->dh_type = DH_SUB_DIGEST;
1652 } else {
1653 current_digest_article = 0;
1654 ddp->dh_type = DH_NORMAL;
1655 }
1656
1657 dhp->dh_sender_length = pack_name(ddp->dh_sender, artp->a_from, Name_Length);
1658 dhp->dh_subject_length = pack_subject(ddp->dh_subject, artp->a_subj, &recnt, DBUF_SIZE);
1659
1660 if (recnt) /* 5/3/93 wolfgang@wsrcc.com */
1661 dhp->dh_replies |= 0x80;
1662
1663 db_read_counter++;
1664 return 1;
1665 }
1666
1667 static void
1668 db_fixup_cross_postings(data_header * dhp, data_dynamic_data * ddp, struct novart * artp)
1669 {
1670 char *curg, *tmp;
1671 int numgrps = 0;
1672
1673 dhp->dh_cross_postings = 0; /* assume none as default until we can show
1674 * otherwise */
1675
1676 /*
1677 * If no "other" header lines are in NOV database, we're out of luck, can
1678 * only assume no crosspostings, so return.
1679 */
1680 if ((artp->a_others) == NULL)
1681 return;
1682
1683 /* Scan until we find a Xref: header line. */
1684 for (curg = artp->a_others;; ++curg) {
1685 if (strncmp("Xref: ", curg, 6) == 0 ||
1686 strncmp("xref: ", curg, 6) == 0) {
1687 break;
1688 }
1689 curg = strchr(curg, '\t'); /* Not this header, skip to the next */
1690 if (curg == NULL)
1691 return;
1692 }
1693
1694 curg += 6; /* Skip over "Xref: " */
1695
1696 while (*curg == ' ')
1697 ++curg; /* Skip to the hostname field after Xref: */
1698
1699 /* Skip over the hostname to the space following hostname */
1700 if ((curg = strchr(curg, ' ')) == NULL) {
1701 return; /* header is malformed, punt. */
1702 }
1703
1704 /*
1705 * Start reading the entries one at a time. Each entry is of the form
1706 * "newsgroup:number", and entries are separated by spaces. Algorithm
1707 * loosely based on the orignal one in collect.c for setting up the
1708 * crosspost information.
1709 */
1710 while (*curg == ' ' && numgrps < DBUF_SIZE) {
1711 group_header *gh;
1712
1713 while (*curg == ' ')
1714 ++curg; /* Skip spaces to the next entry */
1715
1716 /* Zap colon at end of current entry. */
1717 for (tmp = curg;; ++tmp) {
1718 if (*tmp == ':' || *tmp == '\0' || *tmp == '\t')
1719 break;
1720 }
1721 if (*tmp != ':')
1722 break; /* malformed entry, punt. */
1723 *tmp = '\0';
1724
1725 /* Find gh struct for the group. */
1726 if ((gh = lookup(curg)) != NULL) {
1727 /* and add group number to the crosspost list. */
1728 ddp->dh_cross[numgrps++] = gh->group_num;
1729 }
1730 curg = tmp + 1;
1731 while (isdigit(*curg))
1732 ++curg; /* Skip over the article number */
1733 }
1734 if (numgrps > 1) {
1735
1736 /*
1737 * Note: if # of groups is only 1, we leave dh_cross_postings at its
1738 * original value of zero.
1739 */
1740 dhp->dh_cross_postings = numgrps;
1741 }
1742 return;
1743 }
1744
1745 #endif /* NOV */
1746
1747
1748 #ifndef NOV
1749 int
1750 db_write_art(FILE * f)
1751 {
1752
1753 #ifdef NETWORK_DATABASE
1754 net_long buf[ARTICLE_FIELDS];
1755 #endif /* NETWORK_DATABASE */
1756
1757 article_number art_num = db_hdr.dh_number;
1758
1759 switch (db_data.dh_type) {
1760 case DH_NORMAL:
1761 break;
1762 case DH_SUB_DIGEST:
1763 db_hdr.dh_number = 0;
1764 break;
1765 case DH_DIGEST_HEADER:
1766 db_hdr.dh_number = -art_num;
1767 break;
1768 }
1769
1770 #ifdef NETWORK_DATABASE
1771 buf[0] = db_hdr.dh_number;
1772 buf[1] = db_hdr.dh_date;
1773 buf[2] = db_hdr.dh_hpos;
1774 buf[3] = db_hdr.dh_lpos;
1775 buf[4] = db_hdr.dh_fpos;
1776 buf[5] = db_hdr.dh_lines;
1777 buf[6] = db_hdr.dh_replies;
1778 buf[7] = db_hdr.dh_cross_postings;
1779 buf[8] = db_hdr.dh_subject_length;
1780 buf[9] = db_hdr.dh_sender_length;
1781
1782 host_to_net(buf, ARTICLE_FIELDS);
1783
1784 if (fwrite((char *) buf, sizeof(net_long), ARTICLE_FIELDS, f) != ARTICLE_FIELDS)
1785 return -1;
1786 #else /* NETWORK_DATABASE */
1787
1788 if (fwrite((char *) &db_hdr, sizeof(data_header), 1, f) != 1)
1789 return -1;
1790 #endif /* NETWORK_DATABASE */
1791
1792 if (db_hdr.dh_cross_postings)
1793 if (fwrite((char *) db_data.dh_cross, sizeof(cross_post_number),
1794 (int) db_hdr.dh_cross_postings, f)
1795 != (int) db_hdr.dh_cross_postings)
1796 return -1;
1797
1798 if (db_hdr.dh_sender_length)
1799 if (fwrite(db_data.dh_sender, sizeof(char),
1800 (int) db_hdr.dh_sender_length, f)
1801 != db_hdr.dh_sender_length)
1802 return -1;
1803
1804 if (db_hdr.dh_subject_length)
1805 if (fwrite(db_data.dh_subject, sizeof(char),
1806 (int) db_hdr.dh_subject_length, f)
1807 != db_hdr.dh_subject_length)
1808 return -1;
1809
1810 db_hdr.dh_number = art_num;
1811
1812 db_write_counter++;
1813
1814 return 1;
1815 }
1816
1817
1818 long
1819 get_index_offset(group_header * gh, article_number art_num)
1820 {
1821
1822 #ifdef NETWORK_DATABASE
1823 return (off_t) ((art_num - gh->first_db_article) * sizeof(net_long));
1824 #else /* NETWORK_DATABASE */
1825 return (off_t) ((art_num - gh->first_db_article) * sizeof(off_t));
1826 #endif /* NETWORK_DATABASE */
1827 }
1828
1829 long
1830 get_data_offset(group_header * gh, article_number art_num)
1831 {
1832 FILE *index;
1833 long data_offset;
1834
1835 if (gh->first_db_article == art_num)
1836 return (off_t) 0;
1837
1838 index = open_data_file(gh, 'x', OPEN_READ);
1839 if (index == NULL)
1840 return (off_t) (-1);
1841
1842 fseek(index, get_index_offset(gh, art_num), 0);
1843 if (!db_read_offset(index, &data_offset))
1844 data_offset = -1;
1845
1846 fclose(index);
1847
1848 return data_offset;
1849 }
1850
1851
1852 int
1853 db_read_offset(FILE * f, long *offset)
1854 {
1855
1856 #ifdef NETWORK_DATABASE
1857 net_long temp;
1858
1859 if (fread((char *) &temp, sizeof(net_long), 1, f) != 1)
1860 return 0;
1861
1862 #ifndef NETWORK_BYTE_ORDER
1863 temp = ntohl(temp);
1864 #endif /* !NETWORK_BYTE_ORDER */
1865
1866 *offset = temp;
1867 #else /* NETWORK_DATABASE */
1868
1869 if (fread((char *) offset, sizeof(off_t), 1, f) != 1)
1870 return 0;
1871 #endif /* NETWORK_DATABASE */
1872
1873 return 1;
1874 }
1875
1876 int
1877 db_write_offset(FILE * f, long *offset)
1878 {
1879
1880 #ifdef NETWORK_DATABASE
1881 net_long temp;
1882
1883 temp = *offset;
1884
1885 #ifndef NETWORK_BYTE_ORDER
1886 temp = htonl(temp);
1887 #endif /* !NETWORK_BYTE_ORDER */
1888
1889 if (fwrite((char *) &temp, sizeof(net_long), 1, f) != 1)
1890 return 0;
1891
1892 #else /* NETWORK_DATABASE */
1893
1894 if (fwrite((char *) offset, sizeof(off_t), 1, f) != 1)
1895 return 0;
1896 #endif /* NETWORK_DATABASE */
1897
1898 return 1;
1899 }
1900
1901 #endif /* !NOV */
1902
1903
1904 #ifdef NOV
1905 /* These are strictly temporary. They will go away. */
1906 char *
1907 db_data_path(char *namebuf, group_header * gh, char d_or_x)
1908 {
1909 nn_exitmsg(50, "STUB ROUTINE CALLED: db_data_path\n");
1910 return NULL;
1911 }
1912
1913 int
1914 db_read_offset(FILE * f, long *offset)
1915 {
1916 nn_exitmsg(50, "STUB ROUTINE CALLED: db_read_offset\n");
1917 return -1;
1918 }
1919
1920 void
1921 db_write_group(group_header * gh)
1922 {
1923 nn_exitmsg(50, "STUB ROUTINE CALLED: db_write_group\n");
1924 return;
1925 }
1926
1927 FILE *
1928 open_data_file(group_header * gh, char d_or_x, int mode)
1929 {
1930 nn_exitmsg(50, "STUB ROUTINE CALLED: open_data_dile\n");
1931 return NULL;
1932 }
1933
1934 long
1935 get_index_offset(group_header * gh, article_number art_num)
1936 {
1937 nn_exitmsg(50, "STUB ROUTINE CALLED: get_index_offset\n");
1938 return -1;
1939 }
1940
1941 #endif /* NOV */
1942
1943 #if defined(NOV) || defined(CACHE_PURPOSE)
1944 /*
1945 * pmalloc()/pfree():
1946 * A scheme to avoid malloc()/free() overhead; handles memory in
1947 * STRCHUNK increments (deliberately same as STR_THUNK_SIZE).
1948 * Unlike mark_str()/alloc_str()/release_str(), pfree()'d memory
1949 * returns to the malloc() pool, which is arguably more social.
1950 * More important, the alloc_str() family assumes only one active
1951 * use at a time; interleaving uses or a misplaced release_str() has
1952 * the potential to leak memory or corrupt the current_str_t pool.
1953 *
1954 * Perhaps should be globally available; move to global.c?
1955 */
1956 #define STRCHUNK ((1<<14) - 32) /* leave room for malloc header */
1957
1958 typedef struct stpool {
1959 stlist_t *sthead;
1960 char *pool;
1961 int pfree;
1962 int pslop;
1963 } stpool_t;
1964
1965 static stpool_t stpool[POOL_MAX + 1];
1966
1967 static char *
1968 pmalloc(int size, int poolid)
1969 {
1970 register stpool_t *pp;
1971 register stlist_t *stnew;
1972 register char *ret;
1973
1974 if (poolid < 0 || poolid > POOL_MAX)
1975 return NULL;
1976
1977 pp = &stpool[poolid];
1978 if (size <= pp->pfree) {
1979 /* Usually short; fits into current chunk */
1980 ret = pp->pool;
1981 } else if (size <= STRCHUNK) {
1982 /* Sometimes chunk is exhausted; chain new one to pool */
1983 stnew = (stlist_t *) malloc(sizeof(stlisthdr_t) + STRCHUNK);
1984 if (stnew == NULL)
1985 return NULL;
1986 pp->pslop += pp->pfree;
1987 stnew->n.next = pp->sthead;
1988 pp->sthead = stnew;
1989 pp->pfree = STRCHUNK;
1990 ret = stnew->str;
1991 } else {
1992
1993 /*
1994 * Last resort: allocate oversize chunk and chain to pool behind
1995 * current chunk.
1996 */
1997 stnew = (stlist_t *) malloc(sizeof(stlisthdr_t) + size);
1998 if (stnew == NULL)
1999 return NULL;
2000 if (pp->sthead != NULL) {
2001 stnew->n.next = pp->sthead->n.next;
2002 pp->sthead->n.next = stnew;
2003 } else {
2004 stnew->n.next = NULL;
2005 pp->sthead = stnew;
2006 }
2007 return stnew->str;
2008 /* NOTREACHED */
2009 }
2010
2011 pp->pool = ret + size;
2012 pp->pfree -= size;
2013 return ret;
2014 }
2015
2016 static void
2017 pfree(int poolid)
2018 {
2019 register stpool_t *pp;
2020 register stlist_t *stp, *stnext;
2021
2022 if (poolid < 0 || poolid > POOL_MAX)
2023 return;
2024
2025 pp = &stpool[poolid];
2026 for (stp = pp->sthead; stp; stp = stnext) {
2027 stnext = stp->n.next;
2028 free(stp);
2029 }
2030 pp->sthead = NULL;
2031 pp->pool = NULL;
2032 pp->pfree = 0;
2033 pp->pslop = 0;
2034 }
2035
2036
2037 /*
2038 * strkeep(void)
2039 * Save a string, allowing space for a header.
2040 */
2041 static char *
2042 strkeep(char *s, int hdr, int poolid)
2043 {
2044 register int size;
2045 register char *ret;
2046
2047 if (poolid == POOL_TMP) {
2048 size = hdr + strlen(s);
2049 ret = alloc_str(size);
2050 } else {
2051 size = (hdr + strlen(s) + sizeof(long)) & ~(sizeof(long) - 1);
2052 ret = pmalloc(size, poolid);
2053 }
2054
2055 if (ret)
2056
2057 #ifdef NO_MEMMOVE
2058 bcopy(s, ret + hdr, size - hdr);
2059 #else
2060 memmove(ret + hdr, s, size - hdr);
2061 #endif /* NO_MEMMOVE */
2062
2063 return ret;
2064 }
2065
2066 #endif /* NOV || CACHE_PURPOSE */
2067