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