/*
* (c) Copyright 1990, Kim Fabricius Storm. All rights reserved.
* Copyright (c) 1996-2005 Michael T Pins. All rights reserved.
*
* Database access and update
*/
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <strings.h>
#include <ctype.h>
#include <errno.h>
#include "config.h"
#include "global.h"
#include "answer.h"
#include "articles.h"
#include "db.h"
#include "libnov.h"
#include "nntp.h"
#include "nn_term.h"
#include "pack_date.h"
#include "pack_name.h"
#include "pack_subject.h"
#ifdef NOV
#include "hash.h"
#include "newsoverview.h"
#endif /* NOV */
/* db.c */
#ifndef NOV
static char *mk_archive_file(register group_header *, register char *, int);
static void db_read_master(void);
static void db_read_group(register group_header *);
#endif /* !NOV */
static int sort_gh(group_header ** g1, group_header ** g2);
struct novgroup *nntp_get_overview(group_header *, article_number, article_number);
#ifdef NOV
static void db_init_group(group_header * gh, int num);
static void db_init_active(group_header * gh);
static void readtimfile(void);
static void readactfile(void);
static void
db_fixup_cross_postings(data_header * dhp,
data_dynamic_data * ddp,
struct novart * artp);
static struct novgroup *ngovp;
static struct novart *allarts;
static char **actlist, **grplist;
static HASHTABLE *timtbl;
#endif /* NOV */
#ifdef CACHE_PURPOSE
struct purp_list {
char *group_name;
char *purpose;
} *purp_list;
static void cache_purpose(void);
static int purp_cnt;
#endif /* CACHE_PURPOSE */
/*
* String pool allocation
*/
/* Badly broken. Fix ASAP */
typedef struct stlisthdr {
struct stlist *next;
} stlisthdr_t;
typedef struct stlist {
stlisthdr_t n;
char str[1];
} stlist_t;
#if defined(NOV) || defined(CACHE_PURPOSE)
static char *strkeep(char *s, int hdr, int poolid);
static char *pmalloc(int size, int poolid);
static void pfree(int poolid);
#endif
#define POOL_GRP 0
#define POOL_PUR 1
#define POOL_ACT 2
#define POOL_TIM 3
#define POOL_MAX 3
#define POOL_TMP 4
#define RCX_NEVER 0
extern char
*master_directory, *db_directory, *db_data_directory, *news_directory, *news_lib_directory;
extern int db_data_subdirs, new_group_action;
master_header master;
#ifdef MALLOC_64K_LIMITATION
group_header **active_groups = NULL;
#else
group_header *active_groups = NULL;
#endif /* MALLOC_64K_LIMITATION */
group_header **sorted_groups = NULL;
int reread_groups_file = 0; /* nnmaster -G */
int check_group_access = 0;
data_header db_hdr;
data_dynamic_data db_data;
int32 db_read_counter = 0; /* articles read by db_read_art */
int32 db_write_counter = 0; /* articles written by db_write_art */
/*
* Init access to a group
*/
group_header *current_group = NULL;
char group_path_name[FILENAME];
char *group_file_name = NULL;
static char *group_position = NULL;
static article_number current_digest_article = 0;
int
init_group(register group_header * gh)
{
register char *p, *q;
current_digest_article = 0;
if (gh == NULL)
return 0;
/* if (gh->master_flag & M_IGNORE_GROUP) return 0; *//* OBS */
if (gh == current_group)
return 1;
current_group = gh;
if (gh->group_flag & G_FOLDER) {
group_position = NULL;
group_file_name = NULL;
strcpy(group_path_name, gh->archive_file);
return 1;
}
#ifdef NNTP
if (use_nntp && nntp_set_group(gh) < 0)
return 0;
#endif /* NNTP */
if (group_position == NULL) {
if (who_am_i == I_AM_MASTER || who_am_i == I_AM_EXPIRE) {
group_position = group_path_name;
} else {
strcpy(group_path_name, news_directory);
group_position = group_path_name + strlen(group_path_name);
*group_position++ = '/';
}
}
for (p = group_position, q = gh->group_name; *q; q++)
*p++ = (*q == '.') ? '/' : *q;
if (who_am_i == I_AM_MASTER) {
/* The master will chdir to the group's directory to get */
/* better performance (can use relative path names). */
*p++ = NUL;
if (!use_nntp) {
if (chdir(news_directory) < 0)
sys_error(news_directory);
if (chdir(group_path_name) < 0)
return 0;
}
group_file_name = group_path_name;
return 1;
}
/* client */
if (gh->master_flag & M_NO_DIRECTORY)
return 0;
if (check_group_access && !use_nntp) {
*p = NUL;
if (file_exist(group_path_name, "dxr") == 0)
return 0;
}
*p++ = '/';
*p = NUL;
group_file_name = p;
return 1;
}
#ifndef NOV
/*
* Open master & group file; read it in if first open.
*
* GROUPS file format is:
*
* One line per group:
* <group name> [<space><timestamp>] [<space><options>] <NL>
* If <timestamp> is omitted, a timestamp of 0 is used (very old group).
* If <group name> is "@", the master entry is ignored.
*
* <options>:
* D Digest all articles in the group
* N Never digest articles
* @ Bogus group, ignore completely
* ! Don't collect this group
*
* Do not edit the GROUPS file while nnmaster is running.
* After editing the groups file (options), run nnmaster -G.
*/
static FILE *group_file = NULL;
int
open_groups(int mode)
{
group_file = open_file(relative(db_directory, "GROUPS"), mode);
if (group_file != NULL && (mode & OPEN_CREATE)) {
fprintf(group_file,
"#\n#\tNEVER MODIFY THIS FILE WHILE nnmaster IS RUNNING\n");
fprintf(group_file,
"#\n#\tRUN 'nnmaster -G' AFTER MODIFYING THIS FILE\n");
fprintf(group_file,
"#\n#\tDO NOT REMOVE OR REORDER ANY LINES IN THIS FILE\n#\n");
}
return group_file != NULL;
}
void
close_groups(void)
{
if (group_file != NULL) {
fclose(group_file);
group_file = NULL;
}
}
static void
db_append_group(register group_header * gh)
{
char flags[16], *fp;
if (gh->group_name[0] == NUL) {
fputc('@', group_file);
goto out;
}
fprintf(group_file, "%s", gh->group_name);
if (gh->creation_time > 0)
fprintf(group_file, " %ld", (long) (gh->creation_time));
fp = flags;
if (gh->master_flag & M_IGNORE_G)
*fp++ = '!';
if (gh->master_flag & M_ALWAYS_DIGEST)
*fp++ = 'D';
if (gh->master_flag & M_NEVER_DIGEST)
*fp++ = 'N';
if (gh->master_flag & M_INCLUDE_OLD)
*fp++ = 'O';
if (gh->master_flag & M_AUTO_RECOLLECT)
*fp++ = 'R';
if (gh->archive_file != NULL)
*fp++ = '>';
if (fp != flags) {
*fp++ = NUL;
fprintf(group_file, " %s%s", flags,
gh->archive_file != NULL ? gh->archive_file : "");
}
out:
fputc(NL, group_file);
}
void
db_rewrite_groups(int save_groups, group_header * group_list)
{
register group_header *gh;
if (save_groups)
if (save_old_file(relative(db_directory, "GROUPS"), "~") < 0)
sys_error("Cannot rename GROUPS file");
open_groups(OPEN_CREATE | MUST_EXIST);
if (group_list != NULL) {
for (gh = group_list->next_group; gh != NULL; gh = gh->next_group)
db_append_group(gh);
} else {
Loop_Groups_Header(gh) {
db_append_group(gh);
}
}
close_groups();
}
static char *
mk_archive_file(register group_header * gh, register char *cp, int logerr)
{
char *name;
while (*cp && (*cp == '>' || isspace(*cp)))
cp++;
name = cp;
while (*cp && !isspace(*cp))
cp++;
if (*cp)
*cp++ = NUL;
if (*name) {
gh->archive_file = copy_str(name);
if (*name == '/')
gh->master_flag |= M_AUTO_ARCHIVE;
else {
gh->master_flag &= ~M_AUTO_ARCHIVE;
if (who_am_i == I_AM_MASTER) {
if (logerr)
log_entry('E', "GROUPS %s >%s: Full path required",
gh->group_name, name);
else
printf("Error in GROUPS: %s >%s: Full path required\n",
gh->group_name, name);
}
}
}
return cp;
}
int
db_parse_group(register group_header * gh, int trust_master)
/* trust_master trust what is in the master file */
{
char line[256];
register char *cp, *name;
int ignore;
do {
if (fgets(line, 256, group_file) == NULL)
return 0;
for (cp = line; *cp && isspace(*cp); cp++);
} while (*cp == NUL || *cp == '#');
gh->archive_file = NULL;
name = cp;
if (trust_master) {
if (gh->group_name_length == 0) {
gh->group_name = "";
return 1;
}
cp = name + gh->group_name_length;
if (*cp == NUL || !isspace(*cp))
sys_error("MASTER/GROUPS conflict: %d/%s", gh->group_num, line);
} else {
/* parse GROUPS line */
if (*cp == '@') {
ignore = 1;
gh->group_name_length = 0;
gh->group_name = "";
goto ignore_group;
}
if (gh->group_name_length == 0) {
while (*cp && !isspace(*cp))
cp++;
gh->group_name_length = cp - name;
} else {
cp = name + gh->group_name_length;
if (*cp == NUL || !isspace(*cp)) {
sys_error("MASTER/GROUPS conflict: %d/%s", gh->group_num, line);
}
}
}
if (*cp)
*cp++ = NUL;
if (gh->group_name_length > 0)
gh->group_name = copy_str(name);
else
gh->group_name = "";
if (trust_master) {
if (gh->master_flag & M_AUTO_ARCHIVE) {
while (*cp && *cp != '>')
cp++;
if (*cp == '>')
mk_archive_file(gh, cp, 1);
}
return 1;
}
while (*cp && isspace(*cp))
cp++;
if (*cp && isdigit(*cp)) {
gh->creation_time = atol(cp);
while (*cp && isdigit(*cp))
cp++;
} else
gh->creation_time = 0;
while (*cp && isspace(*cp))
cp++;
ignore = 0;
gh->master_flag &= ~(M_ALWAYS_DIGEST | M_NEVER_DIGEST |
M_AUTO_RECOLLECT | M_AUTO_ARCHIVE);
while (*cp) {
switch (*cp++) {
case ' ':
case '\t':
case NL:
case CR:
continue;
case 'D': /* Collect this group, digest all articles */
gh->master_flag |= M_ALWAYS_DIGEST;
continue;
case 'N': /* Collect this group, never digest articles */
gh->master_flag |= M_NEVER_DIGEST;
continue;
case 'O': /* Ignore -O option for this group */
gh->master_flag |= M_INCLUDE_OLD;
continue;
case 'R': /* Recollect this group when new articles
* arrive */
gh->master_flag |= M_AUTO_RECOLLECT;
continue;
case '>': /* Archive all new articles in
* gh->archive_file */
cp = mk_archive_file(gh, cp, 0);
continue;
case '@': /* Bogus GROUP -- ignore completely */
ignore = 1;
gh->group_name_length = 0;
break;
case '!': /* Do not collect this group */
case 'X':
ignore = 1;
break;
case '#': /* comment */
*cp = NUL;
break;
default:
printf("Bad GROUPS flag for %s: `%c'\n", gh->group_name, *--cp);
break;
}
break;
}
ignore_group:
/* G_DONE indicates to master that the group must be cleaned */
if (ignore) {
if ((gh->master_flag & M_IGNORE_GROUP) == 0) {
gh->master_flag |= M_MUST_CLEAN;
log_entry('X', "Group %s ignored", gh->group_name);
}
gh->master_flag |= M_IGNORE_G;
} else { /* was group ignored in GROUPS, but not
* active before? */
if ((gh->master_flag & M_IGNORE_GROUP) == M_IGNORE_G) {
gh->master_flag &= ~M_NO_DIRECTORY;
log_entry('X', "Group %s activated", gh->group_name);
}
gh->master_flag &= ~M_IGNORE_G;
}
return 1;
}
static FILE *master_file = NULL;
static int db_sequential = 0;
#ifdef APOLLO_DOMAIN_OS
/* make copy of master file to CLIENT, but only once every 100 passes or */
/* if forced */
static int
make_master_copy(int force_copy)
{
char client_path[FILENAME];
static int pass_count = 0;
int n;
if (!force_copy) { /* if not forced copy, only do copy every
* %100 passes */
pass_count++;
if (pass_count < 100) {
return;
}
}
pass_count = 0;
strcpy(client_path, relative(db_directory, "CLIENT"));
unlink(client_path);
if ((n = copy_file(relative(db_directory, "MASTER"), client_path, 0)) < 0)
log_entry('R', "Copy of MASTER to CLIENT failed (err=%d)", n);
}
#endif /* APOLLO_DOMAIN_OS */
#endif /* !NOV */
#ifdef NOV
/*
* Init the groups data from active file.
*/
void
open_master(int mode)
{
register group_header *gh;
freeobj(sorted_groups);
freeobj(active_groups);
active_groups = NULL;
sorted_groups = NULL;
readactfile(); /* Read in the active file - count groups */
readtimfile(); /* Read the newsgroup creation time file */
db_expand_master(); /* uses count from readact() call! */
Loop_Groups_Header(gh) {
/* db_read_group opens a file per call; use db_init_group instead */
db_init_group(gh, l_g_index);
}
sort_groups();
Loop_Groups_Header(gh) {
db_init_active(gh);
}
/*
* Free actlist and timtbl space. Don't free grplist, because
* active_groups[] has pointers thereto.
*/
pfree(POOL_ACT);
free(actlist);
pfree(POOL_TIM);
free(timtbl);
#ifdef CACHE_PURPOSE
cache_purpose(); /* cache sorted newsgroups and descriptions */
#endif /* CACHE_PURPOSE */
}
#else /* NOV */
/*
* Open master & group files; read then in if first open.
*/
void
open_master(int mode)
{
register group_header *gh;
int trust_master;
close_master();
#ifdef APOLLO_DOMAIN_OS
if (who_am_i != I_AM_MASTER && who_am_i != I_AM_ADMIN)
master_file = open_file(relative(db_directory, "CLIENT"), mode | MUST_EXIST);
else
#endif /* APOLLO_DOMAIN_OS */
master_file = open_file(relative(db_directory, "MASTER"), mode | MUST_EXIST);
db_sequential = 0;
if (mode == OPEN_CREATE)
db_sequential = 1;
if (mode != OPEN_READ)
return;
db_read_master();
if (who_am_i != I_AM_MASTER && master.db_lock[0])
nn_exitmsg(88, "DATABASE LOCKED.\n%s\n", master.db_lock);
freeobj(sorted_groups);
#ifdef MALLOC_64K_LIMITATION
if (active_groups)
Loop_Groups_Header(gh) freeobj(gh);
#endif /* MALLOC_64K_LIMITATION */
freeobj(active_groups);
active_groups = NULL;
sorted_groups = NULL;
db_expand_master();
open_groups(OPEN_READ | MUST_EXIST);
trust_master = (who_am_i != I_AM_MASTER || !reread_groups_file);
db_sequential = 1;
#ifdef MALLOC_64K_LIMITATION
Loop_Groups_Number(l_g_index) {
gh = newobj(group_header, 1);
active_groups[l_g_index] = gh;
#else
Loop_Groups_Header(gh) {
#endif /* MALLOC_64K_LIMITATION */
gh->group_num = l_g_index;
db_read_group(gh);
db_parse_group(gh, trust_master);
}
db_sequential = 0;
close_groups();
sort_groups();
#ifdef CACHE_PURPOSE
cache_purpose(); /* cache sorted newsgroups and descriptions */
#endif /* CACHE_PURPOSE */
}
#endif /* NOV */
void
db_expand_master(void)
{
master.free_groups = 20;
#ifndef NOV
#ifdef MALLOC_64K_LIMITATION
active_groups = resizeobj(active_groups, group_header *,
master.number_of_groups + master.free_groups);
#else /* MALLOC_64K_LIMITATION */
active_groups = resizeobj(active_groups, group_header,
master.number_of_groups + master.free_groups);
clearobj(active_groups + master.number_of_groups, group_header,
master.free_groups);
#endif /* MALLOC_64K_LIMITATION */
#else /* !NOV */
active_groups = resizeobj(active_groups, group_header,
master.number_of_groups + master.free_groups);
clearobj(active_groups + master.number_of_groups, group_header,
master.free_groups);
#endif /* !NOV */
sorted_groups = resizeobj(sorted_groups, group_header *, master.number_of_groups + master.free_groups);
}
#if 0
#ifdef NOV
static void
freeup(char *key, char *data, char *hook)
{
hashdelete((HASHTABLE *) data, key);
free(key);
free(data);
}
#endif /* NOV */
#endif /* 0 */
void
close_master(void)
{
#ifdef NOV
#if 0
if (ngovp) {
novclose(ngovp);
ngovp = NULL;
}
#endif /* 0 */
#else /* NOV */
if (master_file != NULL) {
fclose(master_file);
master_file = NULL;
#ifdef APOLLO_DOMAIN_OS
if (who_am_i == I_AM_MASTER)
make_master_copy(1);
#endif /* APOLLO_DOMAIN_OS */
}
#endif /* NOV */
}
int
update_group(group_header * gh)
{
#ifndef NOV
group_number numg;
numg = master.number_of_groups;
db_read_master();
master.number_of_groups = numg;
if (master.db_lock[0])
return -3;
#endif /* !NOV */
#ifndef NOV
db_read_group(gh);
#else
db_read_group(gh, gh->first_a_article, gh->last_a_article); /* XXX */
#endif /* !NOV */
if (gh->master_flag & M_IGNORE_GROUP)
return 0;
if (gh->master_flag & M_BLOCKED)
return -1;
return 1;
}
static int
sort_gh(group_header ** g1, group_header ** g2)
{
return strcmp((*g1)->group_name, (*g2)->group_name);
}
void
sort_groups(void)
{
register group_header *gh;
Loop_Groups_Header(gh)
sorted_groups[l_g_index] = gh;
quicksort(sorted_groups, master.number_of_groups, group_header *, sort_gh);
s_g_first = 0;
Loop_Groups_Sorted(gh)
if (gh->group_name[0] != NUL) {
s_g_first = l_g_index;
break;
}
}
group_header *
lookup_no_alias(char *name)
{
register int i, j, k, t;
i = s_g_first;
j = master.number_of_groups - 1;
while (i <= j) {
k = (i + j) / 2;
if ((t = strcmp(name, sorted_groups[k]->group_name)) > 0)
i = k + 1;
else if (t < 0)
j = k - 1;
else
return sorted_groups[k];
}
return NULL;
}
group_header *
lookup(char *name)
{
register group_header *gh;
group_header *gh_na;
register int32 n, x;
gh = lookup_no_alias(name);
if (gh == NULL || (gh->master_flag & M_ALIASED) == 0)
return gh;
gh_na = gh;
x = 16;
do {
if (--x == 0) {
log_entry('R', "Possible alias loop: %s", name);
return gh_na;
}
n = (int32) gh->data_write_offset;
/*
* if alias info is unreliable, return original group which will be
* ignored anyway
*/
if (n < 0 || n >= master.number_of_groups) {
log_entry('R', "Bad aliasing of %s -> %d", gh->group_name, n);
return gh_na;
}
gh = ACTIVE_GROUP(n);
} while (gh->master_flag & M_ALIASED);
return gh;
}
int
art_collected(group_header * gh, article_number art_num)
{
return gh->first_db_article <= art_num && gh->last_db_article >= art_num;
}
#ifndef NOV
char *
db_data_path(char *namebuf, group_header * gh, char d_or_x)
{
register char *cp, *np;
if (db_data_directory != NULL) {
#ifdef DB_LONG_NAMES
sprintf(namebuf, "%s/%s.%c", db_data_directory, gh->group_name, d_or_x);
#else
if (db_data_subdirs)
sprintf(namebuf, "%s/%ld/%ld.%c", db_data_directory,
gh->group_num / 100, gh->group_num, d_or_x);
else
sprintf(namebuf, "%s/%ld.%c", db_data_directory, gh->group_num, d_or_x);
#endif /* DB_LONG_NAMES */
} else {
np = namebuf;
/* master chdir to the group's directory */
if (who_am_i != I_AM_MASTER) {
for (cp = news_directory; (*np = *cp++); np++);
*np++ = '/';
for (cp = gh->group_name; *cp; cp++)
*np++ = *cp == '.' ? '/' : *cp;
*np++ = '/';
}
*np++ = '.';
*np++ = 'n';
*np++ = 'n';
*np++ = d_or_x;
*np++ = NUL;
}
return namebuf;
}
/* STUB */
FILE *
open_data_file(group_header * gh, char d_or_x, int mode)
{
FILE *f;
char data_file[FILENAME];
db_data_path(data_file, gh, d_or_x);
if (mode == -1) {
if (unlink(data_file) < 0 && errno != ENOTDIR && errno != ENOENT)
log_entry('E', "Cannot unlink %s (errno=%d)", data_file, errno);
f = NULL;
} else {
again:
f = open_file(data_file, (mode & ~MUST_EXIST));
if (f != NULL)
return f;
#ifndef DB_LONG_NAMES
if (db_data_subdirs && (mode & 0xf) == OPEN_CREATE && errno == ENOENT) {
char *s;
s = strrchr(data_file, '/');
*s = NUL;
if (!file_exist(data_file, "dx")) {
if (mkdir(data_file, 0755) < 0)
sys_error("Cannot create directory %s", data_file);
log_entry('C', "Created directory %s", data_file);
*s = '/';
goto again;
}
*s = '/';
errno = ENOENT;
}
#endif /* DB_LONG_NAMES */
if (mode & MUST_EXIST)
sys_error("%s (%d): cannot open '%c' file (mode=%x, errno=%d)",
gh->group_name, (int) (gh->group_num), d_or_x,
mode, errno);
}
return f;
}
#ifdef NETWORK_DATABASE
#define MASTER_FIELDS 5 /* + DB_LOCK_MESSAGE bytes */
#define GROUP_FIELDS 9
#define ARTICLE_FIELDS 10
typedef int32 net_long;
#ifdef NETWORK_BYTE_ORDER
#define net_to_host(buf, n)
#define host_to_net(buf, n)
#else
static int
net_to_host(register net_long * buf, int lgt)
{
while (--lgt >= 0) {
*buf = ntohl(*buf);
buf++;
}
}
static int
host_to_net(register net_long * buf, int lgt)
{
while (--lgt >= 0) {
*buf = htonl(*buf);
buf++;
}
}
#endif /* not NETWORK_BYTE_ORDER */
#endif /* NETWORK_DATABASE */
#define NWDB_MAGIC 0x00190000 /* NN#n <-> NW#n */
static void
db_read_master(void)
{
#ifdef NETWORK_DATABASE
net_long buf[MASTER_FIELDS];
rewind(master_file);
if (fread((char *) buf, sizeof(net_long), MASTER_FIELDS, master_file)
!= MASTER_FIELDS)
goto err;
if (fread(master.db_lock, sizeof(char), DB_LOCK_MESSAGE, master_file)
!= DB_LOCK_MESSAGE)
goto err;
net_to_host(buf, MASTER_FIELDS);
master.db_magic = buf[0] ^ NWDB_MAGIC;
master.last_scan = buf[1];
master.last_size = buf[2];
master.number_of_groups = buf[3];
master.db_created = buf[4];
#else
rewind(master_file);
if (fread((char *) &master, sizeof(master_header), 1, master_file) != 1)
goto err;
#endif /* NETWORK_DATABASE */
if (master.db_magic != NNDB_MAGIC)
sys_error("Database magic number mismatch");
return;
err:
sys_error("Incomplete MASTER file");
}
#endif /* NOV */
#ifdef NOV
static void
readactfile(void)
{
char actline[512];
int count = 0;
int i;
FILE *actfp;
stlist_t *sthead, *stp = NULL;
if (actlist != NULL)
return;
#ifdef NNTP
if (use_nntp) {
actfp = nntp_fopen_list("LIST");
} else
#endif /* NNTP */
actfp = fopen(relative(news_lib_directory, "active"), "r");
if (actfp == NULL) {
nn_exitmsg(1, "could not fetch active file\n");
}
/*
* Snarf all of active up in first pass. This gives us a count of the
* groups we can use for internal tables.
*/
sthead = NULL;
#ifdef NNTP
while (use_nntp ? nntp_fgets(actline, sizeof actline)
: fgets(actline, sizeof actline, actfp))
#else
while (fgets(actline, sizeof actline, actfp))
#endif /* NNTP */
{
stlist_t *stnew = (stlist_t *) strkeep(actline, sizeof(stlisthdr_t), POOL_ACT);
if (stnew == NULL) {
nn_exitmsg(1, "out of mem for active file (at line %d)\n", count + 1);
}
if (sthead != NULL) {
stp->n.next = stnew;
stp = stnew;
} else {
sthead = stnew;
stp = sthead;
}
count++;
}
stp->n.next = NULL;
if (!use_nntp)
(void) fclose(actfp);
actlist = (char **) calloc(count + 1, sizeof(char *));
grplist = (char **) calloc(count + 1, sizeof(char *));
if (grplist == NULL) {
nn_exitmsg(1, "can't create active or group list (%d entries)\n",
count + 1);
}
/*
* Second pass (in core): Put active lines and group names into string
* arrays.
*/
for (i = 0, stp = sthead; stp && i < count; stp = stp->n.next, i++) {
char *p = strchr(stp->str, ' ');
if (p == NULL)
actlist[i] = NULL;
else {
*p++ = NUL;
actlist[i] = p;
}
grplist[i] = strkeep(stp->str, 0, POOL_GRP);
}
actlist[count] = NULL;
grplist[count] = NULL;
/* init the master struct */
clearobj(&master, sizeof(master), 1);
master.number_of_groups = count;
}
#endif /* NOV */
#ifndef NOV
void
db_write_master(void)
{
#ifdef NETWORK_DATABASE
net_long buf[MASTER_FIELDS];
buf[0] = master.db_magic ^ NWDB_MAGIC;
buf[1] = master.last_scan;
buf[2] = master.last_size;
buf[3] = master.number_of_groups;
buf[4] = master.db_created;
host_to_net(buf, MASTER_FIELDS);
rewind(master_file);
if (fwrite((char *) buf, sizeof(net_long), MASTER_FIELDS, master_file)
!= MASTER_FIELDS)
goto err;
if (fwrite(master.db_lock, sizeof(char), DB_LOCK_MESSAGE, master_file)
!= DB_LOCK_MESSAGE)
goto err;
#else
rewind(master_file);
if (fwrite((char *) &master, sizeof(master_header), 1, master_file) != 1)
goto err;
#endif /* NETWORK_DATABASE */
fflush(master_file);
#ifdef APOLLO_DOMAIN_OS
if (who_am_i == I_AM_MASTER)
make_master_copy(0);
#endif /* APOLLO_DOMAIN_OS */
return;
err:
sys_error("Write to MASTER failed");
}
static void
db_read_group(register group_header * gh)
{
#ifdef NETWORK_DATABASE
net_long buf[GROUP_FIELDS];
if (!db_sequential)
fseek(master_file,
(off_t) (MASTER_FIELDS * sizeof(net_long) + DB_LOCK_MESSAGE +
GROUP_FIELDS * sizeof(net_long) * gh->group_num), 0);
if (fread((char *) buf, sizeof(net_long), GROUP_FIELDS, master_file) != GROUP_FIELDS)
goto err;
net_to_host(buf, GROUP_FIELDS);
gh->first_db_article = buf[0];
gh->last_db_article = buf[1];
gh->index_write_offset = buf[2];
gh->data_write_offset = buf[3];
gh->group_name_length = buf[4];
gh->master_flag = buf[5];
gh->first_a_article = buf[6];
gh->last_a_article = buf[7];
gh->creation_time = buf[8];
#else
if (!db_sequential)
fseek(master_file,
sizeof(master_header) + SAVED_GROUP_HEADER_SIZE(*gh) * gh->group_num, 0);
if (fread((char *) gh, SAVED_GROUP_HEADER_SIZE(*gh), 1, master_file) != 1)
goto err;
#endif /* NETWORK_DATABASE */
return;
err:
sys_error("Read GROUPS failed");
}
void
db_write_group(register group_header * gh)
{
#ifdef NETWORK_DATABASE
net_long buf[GROUP_FIELDS];
if (!db_sequential)
fseek(master_file,
(off_t) (MASTER_FIELDS * sizeof(net_long) + DB_LOCK_MESSAGE +
GROUP_FIELDS * sizeof(net_long) * gh->group_num), 0);
buf[0] = gh->first_db_article;
buf[1] = gh->last_db_article;
buf[2] = gh->index_write_offset;
buf[3] = gh->data_write_offset;
buf[4] = gh->group_name_length;
buf[5] = gh->master_flag;
buf[6] = gh->first_a_article;
buf[7] = gh->last_a_article;
buf[8] = gh->creation_time;
host_to_net(buf, GROUP_FIELDS);
if (fwrite((char *) buf, sizeof(net_long), GROUP_FIELDS, master_file) != GROUP_FIELDS)
goto err;
#else /* NETWORK_DATABASE */
if (!db_sequential)
fseek(master_file, sizeof(master_header) + SAVED_GROUP_HEADER_SIZE(*gh) * gh->group_num, 0);
if (fwrite((char *) gh, SAVED_GROUP_HEADER_SIZE(*gh), 1, master_file) != 1)
goto err;
#endif /* NETWORK_DATABASE */
fflush(master_file);
#ifdef APOLLO_DOMAIN_OS
if (who_am_i == I_AM_MASTER)
make_master_copy(0);
#endif /* APOLLO_DOMAIN_OS */
return;
err:
sys_error("Write GROUPS failed");
}
#endif /* !NOV */
#ifdef NOV
static void
readtimfile(void)
{
char timline[512];
FILE *timfp;
unsigned hsize;
if (timtbl != NULL)
return;
hsize = master.number_of_groups | 0x1ff;
timtbl = hashcreate(hsize, (unsigned (*) ()) NULL);
if (timtbl == NULL) {
nn_exitmsg(1, "can't create time hash (%d entries)\n", hsize);
}
if (new_group_action == RCX_NEVER)
return; /* we don't need it */
#ifdef NNTP
if (use_nntp)
timfp = nntp_fopen_list("LIST active.times");
else
#endif /* NNTP */
timfp = fopen(relative(news_lib_directory, "active.times"), "r");
if (timfp == NULL)
return; /* no great shakes if its missing */
/* alt.fan.marla-thrift 736668095 netnews@ccc.amdahl.com */
#ifdef NNTP
while (use_nntp ? nntp_fgets(timline, sizeof timline)
: fgets(timline, sizeof timline, timfp))
#else /* NNTP */
while (fgets(timline, sizeof timline, timfp))
#endif /* NNTP */
{
char *line = strkeep(timline, 0, POOL_TIM);
char *p = strchr(line, ' ');
if (p == NULL)
continue;
*p++ = NUL;
if (!hashstore(timtbl, line, p)) {
nn_exitmsg(1, "nn: time hashstore failed\n");
}
}
if (!use_nntp)
(void) fclose(timfp);
}
#endif /* NOV */
#ifdef CACHE_PURPOSE
static int
purpcmp(const void *p0, const void *p1)
{
return strcmp(((struct purp_list *) p0)->group_name,
((struct purp_list *) p1)->group_name);
}
/*
* Open purpose file and cache sorted list of group purposes
*/
static void
cache_purpose(void)
{
char buf[512];
register char *p;
FILE *fp;
int i, ngrp;
stlist_t *sthead, *stp, *stnew;
struct purp_list *plp;
if ((fp = open_purpose_file()) == NULL
|| (ngrp = master.number_of_groups) == 0
|| purp_list != NULL) {
return;
}
ngrp = 0;
stp = sthead = NULL;
while (fgets(buf, sizeof(buf), fp) != NULL) {
if ((p = strchr(buf, NL)) != NULL) {
*p = NUL;
}
stnew = (stlist_t *) strkeep(buf, sizeof(stlisthdr_t), POOL_PUR);
if (stnew == NULL) {
/* tough cookies. we'll just do without. */
pfree(POOL_PUR);
return;
}
if (sthead != NULL) {
stp->n.next = stnew;
stp = stnew;
} else {
sthead = stnew;
stp = sthead;
}
stnew->n.next = NULL;
ngrp++;
}
purp_list = (struct purp_list *) calloc(ngrp + 1, sizeof(struct purp_list));
if (purp_list == NULL) {
pfree(POOL_PUR);
return;
}
for (i = 0, plp = purp_list, stp = sthead; stp; stp = stp->n.next) {
p = stp->str;
while (!isspace(*p)) { /* skip newsgroup name */
if (*++p == NUL)
goto next;
}
*p++ = NUL;
while (isspace(*p)) { /* skip to group description */
if (*++p == NUL)
goto next;
}
plp->group_name = stp->str;
plp->purpose = p;
plp++;
i++;
next:
continue;
}
plp->group_name = NULL;
plp->purpose = NULL;
purp_cnt = i;
qsort(purp_list, purp_cnt, sizeof(struct purp_list), purpcmp);
}
char *
purp_lookup(char *group)
{
register int i, j, k, t;
i = 0;
j = purp_cnt - 1;
while (i <= j) {
k = (i + j) / 2;
if ((t = strcmp(group, purp_list[k].group_name)) > 0)
i = k + 1;
else if (t < 0)
j = k - 1;
else
return purp_list[k].purpose;
}
return "";
}
#endif /* CACHE_PURPOSE */
#ifndef NOV
off_t
db_read_art(FILE * f)
{
off_t bytes;
#ifdef NETWORK_DATABASE
net_long buf[ARTICLE_FIELDS];
if (fread((char *) buf, sizeof(net_long), ARTICLE_FIELDS, f) != ARTICLE_FIELDS)
return 0;
bytes = sizeof(net_long) * ARTICLE_FIELDS;
net_to_host(buf, ARTICLE_FIELDS);
db_hdr.dh_number = buf[0];
db_hdr.dh_date = buf[1];
db_hdr.dh_hpos = buf[2];
db_hdr.dh_lpos = buf[3];
db_hdr.dh_fpos = buf[4];
db_hdr.dh_lines = buf[5];
db_hdr.dh_replies = buf[6];
db_hdr.dh_cross_postings = buf[7];
db_hdr.dh_subject_length = buf[8];
db_hdr.dh_sender_length = buf[9];
#else /* NETWORK_DATABASE */
if (fread((char *) &db_hdr, sizeof(data_header), 1, f) != 1)
return 0;
bytes = sizeof(data_header);
#endif /* NETWORK_DATABASE */
if (db_hdr.dh_number < 0) {
current_digest_article = db_hdr.dh_number = -db_hdr.dh_number;
db_data.dh_type = DH_DIGEST_HEADER;
} else if (db_hdr.dh_number == 0) {
db_hdr.dh_number = current_digest_article;
db_data.dh_type = DH_SUB_DIGEST;
} else {
current_digest_article = 0;
db_data.dh_type = DH_NORMAL;
}
if (db_hdr.dh_cross_postings) {
if (fread((char *) db_data.dh_cross, sizeof(cross_post_number),
(int) db_hdr.dh_cross_postings, f)
!= (int) db_hdr.dh_cross_postings)
return -1;
bytes += sizeof(cross_post_number) * (int) db_hdr.dh_cross_postings;
}
if (db_hdr.dh_sender_length) {
if (fread(db_data.dh_sender, sizeof(char),
(int) db_hdr.dh_sender_length, f)
!= db_hdr.dh_sender_length)
return -1;
bytes += sizeof(char) * (int) db_hdr.dh_sender_length;
}
db_data.dh_sender[db_hdr.dh_sender_length] = NUL;
if (db_hdr.dh_subject_length) {
if (fread(db_data.dh_subject, sizeof(char),
(int) db_hdr.dh_subject_length, f)
!= db_hdr.dh_subject_length)
return -1;
bytes += sizeof(char) * (int) db_hdr.dh_subject_length;
}
db_data.dh_subject[db_hdr.dh_subject_length] = NUL;
db_read_counter++;
return bytes;
}
#endif /* !NOV */
#ifdef NOV
/*
* initialise *gh; this is much cheaper than calling db_read_group.
*/
static void
db_init_group(register group_header * gh, int num)
{
register char *p;
/* tidy up the struct */
clearobj(gh, sizeof(struct group_header), 1);
gh->group_num = num;
if (gh->group_name == NULL) {
gh->group_name = grplist[num];
if (gh->group_name == NULL) {
nn_exitmsg(1, "can't map group %d to name\n", num);
}
gh->group_name_length = strlen(gh->group_name);
}
gh->master_flag = M_VALID;
/* control.newgrp, etc are control groups */
if (strncmp(gh->group_name, "control", 7) == 0)
gh->master_flag |= M_CONTROL;
/* these next two are subtle and we need to lie below */
/* gh->first_db_article = 0; *//* lowest # in ov. data */
/* gh->last_db_article = 0; *//* highest # in ov. data */
gh->first_a_article = 1; /* lowest # in active */
/* gh->last_a_article = 0; *//* highest number in active */
/* gh->index_write_offset = 0; *//* dunno */
/* gh->data_write_offset = 0; *//* dunno */
/* set the creation time */
gh->creation_time = 1; /* group creation date (~epoch) */
p = hashfetch(timtbl, gh->group_name);
if (p != NULL) {
gh->creation_time = atol(p);
}
}
/*
* further initialise *gh; to be done after sort_groups(void)
*/
static void
db_init_active(register group_header * gh)
{
group_header *gh1;
register char *p;
p = actlist[gh->group_num];
if (p != NULL) {
while (isspace(*p))
++p;
gh->last_a_article = atol(p);
p = strchr(p, ' ');
if (p == NULL)
return;
gh->first_a_article = atol(++p);
p = strchr(p, ' ');
if (*++p == '=') { /* an alias */
gh1 = lookup_no_alias(++p);
if (gh1 == NULL) {
log_entry('R', "Group %s aliased to unknown group (%s)",
gh->group_name, p);
/* ah well! leave it be */
} else {
gh->master_flag |= M_ALIASED;
gh->data_write_offset = (long) gh1->group_num;
}
}
}
gh->first_db_article = gh->first_a_article; /* lowest # in ov. data */
gh->last_db_article = gh->last_a_article; /* highest # in ov. data */
}
/*
* slurp up the overview data for this group into *gh.
* this costs a file open and so should not be done frivolously.
*/
void
db_read_group(register group_header * gh, article_number first, article_number last)
{
register struct novart *artp, *lastartp;
/* db_init_group(gh, group_num?? ); already done early at init time */
if (ngovp != NULL)
novclose(ngovp); /* trash last group's data */
#ifdef NNTP
if (use_nntp) {
ngovp = nntp_get_overview(gh, first, last);
} else
#endif /* NNTP */
ngovp = novopen(gh->group_name);
if (ngovp == NULL) {
printf("no overview data for group `%s'\n", gh->group_name);
return;
}
allarts = novall(ngovp, first, last);
if (allarts == NULL) {
/*
* printf("overview data inaccessible for group `%s'\n",
* gh->group_name);
*/
return;
}
if (!use_nntp || first == gh->first_a_article)
gh->first_db_article = atol(allarts->a_num); /* lowest # */
if (!use_nntp || last == gh->last_a_article) {
lastartp = allarts;
for (artp = allarts; artp != NULL; artp = artp->a_nxtnum)
if (atol(artp->a_num) != 0)
lastartp = artp;
gh->last_db_article = atol(lastartp->a_num); /* highest # */
}
/* Handle Digest flag */
if (gh->first_db_article < 0)
gh->first_db_article = -gh->first_db_article;
if (gh->last_db_article < 0)
gh->last_db_article = -gh->last_db_article;
}
/*
* fill in db_hdr and db_data from the overview data for the next
* article in this group. does weirdo nn encodings of header fields.
*/
off_t
db_read_art(FILE * f)
{
register data_header *dhp = &db_hdr;
register data_dynamic_data *ddp = &db_data;
register struct novart *artp;
int recnt = 0;
if (ngovp == NULL || ngovp->g_first == NULL)
return 0;
if (ngovp->g_first == NULL) /* XXX */
return 0;
artp = novnext(ngovp);
if (artp == NULL)
return 0; /* group exhausted */
dhp->dh_number = atol(artp->a_num);
/* printf("article #%ld\n", dhp->dh_number); *//* DEBUG */
dhp->dh_date = pack_date(artp->a_date); /* "encoded Date: filed" */
dhp->dh_hpos = 0; /* 1st hdr byte */
dhp->dh_lpos = 1L << 30; /* last article byte */
dhp->dh_fpos = 0; /* 1st article text byte */
dhp->dh_lines = -1; /* -1 == "unknown" */
if (isascii(artp->a_lines[0]) && isdigit(artp->a_lines[0]))
dhp->dh_lines = atoi(artp->a_lines);
dhp->dh_replies = 0; /* # of References: */
if (artp->a_refs != NULL) {
register char *p;
for (p = artp->a_refs; *p != '\0'; p++)
if (*p == '<')
dhp->dh_replies++;
}
db_fixup_cross_postings(dhp, ddp, artp);
if (dhp->dh_number < 0) {
current_digest_article = dhp->dh_number = -dhp->dh_number;
ddp->dh_type = DH_DIGEST_HEADER;
} else if (dhp->dh_number == 0) {
#ifdef DO_NOV_DIGEST
char *cp;
if (artp->a_bytes && (cp = strchr(artp->a_bytes, ':'))) {
dhp->dh_hpos = atol(++cp);
if ((cp = strchr(cp, ':')) != NULL) {
dhp->dh_fpos = atol(++cp);
if ((cp = strchr(cp, ':')) != NULL) {
dhp->dh_lpos = atol(++cp);
}
}
}
#endif /* DO_NOV_DIGEST */
dhp->dh_number = current_digest_article;
ddp->dh_type = DH_SUB_DIGEST;
} else {
current_digest_article = 0;
ddp->dh_type = DH_NORMAL;
}
dhp->dh_sender_length = pack_name(ddp->dh_sender, artp->a_from, Name_Length);
dhp->dh_subject_length = pack_subject(ddp->dh_subject, artp->a_subj, &recnt, DBUF_SIZE);
if (recnt) /* 5/3/93 wolfgang@wsrcc.com */
dhp->dh_replies |= 0x80;
db_read_counter++;
return 1;
}
static void
db_fixup_cross_postings(data_header * dhp, data_dynamic_data * ddp, struct novart * artp)
{
char *curg, *tmp;
int numgrps = 0;
dhp->dh_cross_postings = 0; /* assume none as default until we can show
* otherwise */
/*
* If no "other" header lines are in NOV database, we're out of luck, can
* only assume no crosspostings, so return.
*/
if ((artp->a_others) == NULL)
return;
/* Scan until we find a Xref: header line. */
for (curg = artp->a_others;; ++curg) {
if (strncmp("Xref: ", curg, 6) == 0 ||
strncmp("xref: ", curg, 6) == 0) {
break;
}
curg = strchr(curg, '\t'); /* Not this header, skip to the next */
if (curg == NULL)
return;
}
curg += 6; /* Skip over "Xref: " */
while (*curg == ' ')
++curg; /* Skip to the hostname field after Xref: */
/* Skip over the hostname to the space following hostname */
if ((curg = strchr(curg, ' ')) == NULL) {
return; /* header is malformed, punt. */
}
/*
* Start reading the entries one at a time. Each entry is of the form
* "newsgroup:number", and entries are separated by spaces. Algorithm
* loosely based on the orignal one in collect.c for setting up the
* crosspost information.
*/
while (*curg == ' ' && numgrps < DBUF_SIZE) {
group_header *gh;
while (*curg == ' ')
++curg; /* Skip spaces to the next entry */
/* Zap colon at end of current entry. */
for (tmp = curg;; ++tmp) {
if (*tmp == ':' || *tmp == '\0' || *tmp == '\t')
break;
}
if (*tmp != ':')
break; /* malformed entry, punt. */
*tmp = '\0';
/* Find gh struct for the group. */
if ((gh = lookup(curg)) != NULL) {
/* and add group number to the crosspost list. */
ddp->dh_cross[numgrps++] = gh->group_num;
}
curg = tmp + 1;
while (isdigit(*curg))
++curg; /* Skip over the article number */
}
if (numgrps > 1) {
/*
* Note: if # of groups is only 1, we leave dh_cross_postings at its
* original value of zero.
*/
dhp->dh_cross_postings = numgrps;
}
return;
}
#endif /* NOV */
#ifndef NOV
int
db_write_art(FILE * f)
{
#ifdef NETWORK_DATABASE
net_long buf[ARTICLE_FIELDS];
#endif /* NETWORK_DATABASE */
article_number art_num = db_hdr.dh_number;
switch (db_data.dh_type) {
case DH_NORMAL:
break;
case DH_SUB_DIGEST:
db_hdr.dh_number = 0;
break;
case DH_DIGEST_HEADER:
db_hdr.dh_number = -art_num;
break;
}
#ifdef NETWORK_DATABASE
buf[0] = db_hdr.dh_number;
buf[1] = db_hdr.dh_date;
buf[2] = db_hdr.dh_hpos;
buf[3] = db_hdr.dh_lpos;
buf[4] = db_hdr.dh_fpos;
buf[5] = db_hdr.dh_lines;
buf[6] = db_hdr.dh_replies;
buf[7] = db_hdr.dh_cross_postings;
buf[8] = db_hdr.dh_subject_length;
buf[9] = db_hdr.dh_sender_length;
host_to_net(buf, ARTICLE_FIELDS);
if (fwrite((char *) buf, sizeof(net_long), ARTICLE_FIELDS, f) != ARTICLE_FIELDS)
return -1;
#else /* NETWORK_DATABASE */
if (fwrite((char *) &db_hdr, sizeof(data_header), 1, f) != 1)
return -1;
#endif /* NETWORK_DATABASE */
if (db_hdr.dh_cross_postings)
if (fwrite((char *) db_data.dh_cross, sizeof(cross_post_number),
(int) db_hdr.dh_cross_postings, f)
!= (int) db_hdr.dh_cross_postings)
return -1;
if (db_hdr.dh_sender_length)
if (fwrite(db_data.dh_sender, sizeof(char),
(int) db_hdr.dh_sender_length, f)
!= db_hdr.dh_sender_length)
return -1;
if (db_hdr.dh_subject_length)
if (fwrite(db_data.dh_subject, sizeof(char),
(int) db_hdr.dh_subject_length, f)
!= db_hdr.dh_subject_length)
return -1;
db_hdr.dh_number = art_num;
db_write_counter++;
return 1;
}
long
get_index_offset(group_header * gh, article_number art_num)
{
#ifdef NETWORK_DATABASE
return (off_t) ((art_num - gh->first_db_article) * sizeof(net_long));
#else /* NETWORK_DATABASE */
return (off_t) ((art_num - gh->first_db_article) * sizeof(off_t));
#endif /* NETWORK_DATABASE */
}
long
get_data_offset(group_header * gh, article_number art_num)
{
FILE *index;
long data_offset;
if (gh->first_db_article == art_num)
return (off_t) 0;
index = open_data_file(gh, 'x', OPEN_READ);
if (index == NULL)
return (off_t) (-1);
fseek(index, get_index_offset(gh, art_num), 0);
if (!db_read_offset(index, &data_offset))
data_offset = -1;
fclose(index);
return data_offset;
}
int
db_read_offset(FILE * f, long *offset)
{
#ifdef NETWORK_DATABASE
net_long temp;
if (fread((char *) &temp, sizeof(net_long), 1, f) != 1)
return 0;
#ifndef NETWORK_BYTE_ORDER
temp = ntohl(temp);
#endif /* !NETWORK_BYTE_ORDER */
*offset = temp;
#else /* NETWORK_DATABASE */
if (fread((char *) offset, sizeof(off_t), 1, f) != 1)
return 0;
#endif /* NETWORK_DATABASE */
return 1;
}
int
db_write_offset(FILE * f, long *offset)
{
#ifdef NETWORK_DATABASE
net_long temp;
temp = *offset;
#ifndef NETWORK_BYTE_ORDER
temp = htonl(temp);
#endif /* !NETWORK_BYTE_ORDER */
if (fwrite((char *) &temp, sizeof(net_long), 1, f) != 1)
return 0;
#else /* NETWORK_DATABASE */
if (fwrite((char *) offset, sizeof(off_t), 1, f) != 1)
return 0;
#endif /* NETWORK_DATABASE */
return 1;
}
#endif /* !NOV */
#ifdef NOV
/* These are strictly temporary. They will go away. */
char *
db_data_path(char *namebuf, group_header * gh, char d_or_x)
{
nn_exitmsg(50, "STUB ROUTINE CALLED: db_data_path\n");
return NULL;
}
int
db_read_offset(FILE * f, long *offset)
{
nn_exitmsg(50, "STUB ROUTINE CALLED: db_read_offset\n");
return -1;
}
void
db_write_group(group_header * gh)
{
nn_exitmsg(50, "STUB ROUTINE CALLED: db_write_group\n");
return;
}
FILE *
open_data_file(group_header * gh, char d_or_x, int mode)
{
nn_exitmsg(50, "STUB ROUTINE CALLED: open_data_dile\n");
return NULL;
}
long
get_index_offset(group_header * gh, article_number art_num)
{
nn_exitmsg(50, "STUB ROUTINE CALLED: get_index_offset\n");
return -1;
}
#endif /* NOV */
#if defined(NOV) || defined(CACHE_PURPOSE)
/*
* pmalloc()/pfree():
* A scheme to avoid malloc()/free() overhead; handles memory in
* STRCHUNK increments (deliberately same as STR_THUNK_SIZE).
* Unlike mark_str()/alloc_str()/release_str(), pfree()'d memory
* returns to the malloc() pool, which is arguably more social.
* More important, the alloc_str() family assumes only one active
* use at a time; interleaving uses or a misplaced release_str() has
* the potential to leak memory or corrupt the current_str_t pool.
*
* Perhaps should be globally available; move to global.c?
*/
#define STRCHUNK ((1<<14) - 32) /* leave room for malloc header */
typedef struct stpool {
stlist_t *sthead;
char *pool;
int pfree;
int pslop;
} stpool_t;
static stpool_t stpool[POOL_MAX + 1];
static char *
pmalloc(int size, int poolid)
{
register stpool_t *pp;
register stlist_t *stnew;
register char *ret;
if (poolid < 0 || poolid > POOL_MAX)
return NULL;
pp = &stpool[poolid];
if (size <= pp->pfree) {
/* Usually short; fits into current chunk */
ret = pp->pool;
} else if (size <= STRCHUNK) {
/* Sometimes chunk is exhausted; chain new one to pool */
stnew = (stlist_t *) malloc(sizeof(stlisthdr_t) + STRCHUNK);
if (stnew == NULL)
return NULL;
pp->pslop += pp->pfree;
stnew->n.next = pp->sthead;
pp->sthead = stnew;
pp->pfree = STRCHUNK;
ret = stnew->str;
} else {
/*
* Last resort: allocate oversize chunk and chain to pool behind
* current chunk.
*/
stnew = (stlist_t *) malloc(sizeof(stlisthdr_t) + size);
if (stnew == NULL)
return NULL;
if (pp->sthead != NULL) {
stnew->n.next = pp->sthead->n.next;
pp->sthead->n.next = stnew;
} else {
stnew->n.next = NULL;
pp->sthead = stnew;
}
return stnew->str;
/* NOTREACHED */
}
pp->pool = ret + size;
pp->pfree -= size;
return ret;
}
static void
pfree(int poolid)
{
register stpool_t *pp;
register stlist_t *stp, *stnext;
if (poolid < 0 || poolid > POOL_MAX)
return;
pp = &stpool[poolid];
for (stp = pp->sthead; stp; stp = stnext) {
stnext = stp->n.next;
free(stp);
}
pp->sthead = NULL;
pp->pool = NULL;
pp->pfree = 0;
pp->pslop = 0;
}
/*
* strkeep(void)
* Save a string, allowing space for a header.
*/
static char *
strkeep(char *s, int hdr, int poolid)
{
register int size;
register char *ret;
if (poolid == POOL_TMP) {
size = hdr + strlen(s);
ret = alloc_str(size);
} else {
size = (hdr + strlen(s) + sizeof(long)) & ~(sizeof(long) - 1);
ret = pmalloc(size, poolid);
}
if (ret)
#ifdef NO_MEMMOVE
bcopy(s, ret + hdr, size - hdr);
#else
memmove(ret + hdr, s, size - hdr);
#endif /* NO_MEMMOVE */
return ret;
}
#endif /* NOV || CACHE_PURPOSE */
syntax highlighted by Code2HTML, v. 0.9.1