src/article.c
/* [<][>][^][v][top][bottom][index][help] */
DEFINITIONS
This source file includes following functions.
- rfc822lower
- newsDate
- art_hilo
- crosspost
- emit_follows
- find_header_arg
- authorise_newsgroups
- auth_article
- bad_article
- good_article
- CMDarticle
/* $Id: article.c,v 1.8 2002/03/29 09:47:55 proff Exp $ */
#include "nglobal.h"
#include "mmap.h"
#include "acc.h"
#include "filter.h"
#include "group.h"
#include "history.h"
#include "lock.h"
#include "nlist.h"
#include "xover.h"
#include "article.h"
EXPORT void rfc822lower (char *s)
/* [<][>][^][v][top][bottom][index][help] */
{
for (; *s != '@'; s++)
if (!*s)
return;
for (s++; *s; s++)
*s = tolower (*s);
}
EXPORT char *newsDate (time_t t)
/* [<][>][^][v][top][bottom][index][help] */
{
static char buf[80];
struct tm *tm = localtime (&t);
strftime (buf, sizeof buf, "%d %b %Y %H:%M:%S %Z", tm);
return buf;
}
static void art_hilo(char *path)
/* [<][>][^][v][top][bottom][index][help] */
{
int artno;
struct newsgroup *n;
char *p = strrchr (path, '/');
if (!p)
return;
*p = '\0';
strExchange(path, '/', '.');
n = newsgroupFindAddLock(path, NULL, TRUE);
if (!n)
logd (("art_hilo(): no such group '%s'", path));
strExchange(path, '.', '/');
*p = '/';
if (!n)
return;
artno = strToi (p+1);
if (!n->lo || n->lo > artno)
n->lo = artno;
if (n->hi < artno)
n->hi = artno;
newsgroupUnlockWrite(n);
}
/*
* we must be in the top level of the servers dir
* path should be relative to the above
*/
static bool crosspost (struct strStack *stack, char *path, enum cmds type, bool getbyid)
/* [<][>][^][v][top][bottom][index][help] */
{
char msgid[MAX_MSGID] = "";
struct strList *v, *links = NULL;
char xpathbuf[MAX_HEADER];
char *xpath = NULL;
char *xref = NULL;
char fn[MAX_GROUP + 20];
char *artfile;
char *s;
int fd;
for (s = stack->data; *s; s++)
{
if (*s == '\r' || *s == '\n')
break;
if (strnCaseEq (s, "Message-ID:", 11))
strSnip(s, MAX_MSGID + 11, "<", ">\r\n", msgid, sizeof msgid);
else if (strnCaseEq (s, "Xref:", 5))
xref = s;
s = strchr (s, '\n');
if (!s) /* badly formed art */
{
logw (("badly formed article head %s", path));
return FALSE;
}
}
if (!*msgid)
{
logw (("no Message-ID in article '%s' - not cached", path));
return FALSE;
}
if (!xref && getbyid)
{
log (("no Xref header or article number, trying xpath <%s>", msgid));
Cemitf ("xpath <%s>\r\n", msgid);
Cflush ();
if (Cget (xpathbuf, sizeof xpathbuf) &&
strToi (xpathbuf) == NNTP_NOTHING_FOLLOWS_VAL)
{
xpath = xpathbuf;
strStripEOL (xpath);
CurrentScfg->share->xpath_good++;
} else
CurrentScfg->share->xpath_fail++;
}
if (!getbyid)
{
if (safePath(path))
{
if (type == c_head && !strstr (path, "_head"))
strcat (path, "_head");
links = strListAdd (links, path);
} else
logw (("suspect path: '%.256s'", path));
}
if (xref)
{
char *xrefbuf = Sstrdup (xref);
char *tok;
strStripEOL (xrefbuf);
tok = strtok (xrefbuf, " \t"); /* Xref: */
if (tok)
tok = strtok (NULL, " \t"); /* Xref: foomatic.foo.com */
if (!tok || !(tok = strtok (NULL, " \t"))) /* xref: foomatic.foo.com alt.fan.proff:1649 */
{
logw (("bad Xref %.*s", MAX_SYSLOG-20, xref));
} else
{
do
{
char *p = strchr (tok, ':'); /* alt.fan.proff:1649 */
if (!p || (p && strToi (p + 1) <= 0))
{
logw (("bad Xref %.*s", MAX_SYSLOG-20, xref));
continue;
}
/* does another server normally handly this
* group ?
*/
*p = '\0';
if (getServerGroup(tok) != CurrentScfg)
continue;
*p = '/'; /* alt.fan.proff/1649 */
if (strlen (tok) > (size_t)MAX_GROUP)
{
logw (("xref too long %.*s", MAX_SYSLOG-20, xref));
continue;
}
strExchange (tok, '.', '/'); /* alt/fan/proff/1649 */
if (safePath(tok))
{
if (type == c_head)
{
sprintf (fn, "%.255s_head", tok);
links = strListAdd (links, fn);
} else
links = strListAdd (links, tok);
} else
logw (("suspect path: '%.256s'", tok));
} while ((tok = strtok (NULL, " \t")));
}
free (xrefbuf);
}
if (xpath) /* !xref */
{
char *tok = strtok (xpath, " \t");
if (!tok || !(tok = strtok (NULL, " \t")))
logw (("bad Xref %.*s", MAX_SYSLOG-20, xref));
else
do
{
if (safePath(tok))
{
if (type == c_head)
{
sprintf (fn, "%.255s_head", tok);
links = strListAdd (links, fn);
} else
links = strListAdd (links, tok);
} else
logw (("suspect path: '%.256s'", tok));
} while ((tok = strtok (NULL, " \t")));
}
if (!links)
{
logw (("couldn't work out an article number for <%s>", msgid));
return FALSE;
}
artfile = links->head->data;
fd = open (artfile, O_WRONLY | O_EXCL | O_CREAT, 0000 /* tag for incompleteness */ );
if (fd == -1)
{
if (errno == EEXIST)
{
logw (("unlinking pre-existing article %s", artfile));
unlink (artfile);
Stats->article_unlinked++;
} else
blddir (artfile);
fd = open (artfile, O_WRONLY | O_EXCL | O_CREAT, 0000 );
if (fd<0)
{
loge (("couldn't create article %s", artfile));
strListFree (links);
return FALSE;
}
}
if (write (fd, stack->data, stack->used - 1) != stack->used - 1)
{
loge (("error writing article %.127s", artfile));
unlink (artfile);
close (fd);
strListFree (links);
return FALSE;
}
fchmod(fd, 0664 /* complete */);
close (fd);
art_hilo(artfile);
;{
char buf[MAX_HOST+MAX_GROUP+32];
char *p = strstr (artfile, "_head");
if (p)
*p = '\0';
sprintf(buf, "%.127s/%.287s", CurrentDir, artfile);
if (safePath(buf))
{
bool ok;
rfc822lower (msgid);
ok = hisAdd (msgid, buf);
logd (("adding <%s>:%s to %s (%s)", msgid, buf, con->historyFile, ok ? "suceeded" : "failed"));
} else
logw (("suspect path: '%.256s'", buf));
if (p)
*p = '_';
}
for (v = links->head->next; v; v = v->next)
{
bool linkok;
char *f = v->data;
if (strEq (f, artfile))
continue;
if (link (artfile, f) == -1)
{
if (errno != EEXIST)
blddir (f);
else
{
logw (("unlinking pre-existing article link %s", f));
unlink (f);
Stats->article_unlinked++;
}
if (link (artfile, f) == -1)
{
loge (("couldn't link %s -> %s", artfile, f));
linkok = FALSE;
} else
linkok = TRUE;
} else
linkok = TRUE;
if (!linkok)
continue;
logd (("linked %s -> %s", artfile, f));
Stats->crossposts++;
Stats->crosspostsBytes += stack->used-1;
art_hilo(f);
}
strListFree (links);
return TRUE;
}
static int emit_follows (enum cmds type, int artno, char *msgid, bool getbyid)
/* [<][>][^][v][top][bottom][index][help] */
{
char *m=NULL, *c=NULL; /* stop warning */
switch (type)
{
case c_article:
m=NNTP_ARTICLE_FOLLOWS; c="article";
CurrentGroupArtRead++;
ArtRead++;
break;
case c_head:
m=NNTP_HEAD_FOLLOWS; c="head";
CurrentGroupArtRead++;
ArtRead++;
break;
case c_body:
m=NNTP_BODY_FOLLOWS; c="body";
CurrentGroupArtRead++;
ArtRead++;
break;
case c_stat:
m=NNTP_NOTHING_FOLLOWS; c="status";
break;
default:
loge(("strange flow"));
retire_vm_proc(1);
}
if (getbyid) /* dumb, but that is how inn does it */
return emitf("%s %d %s <%s>\r\n", m, artno, c, msgid);
CurrentGroupArtNum = artno;
return emitf("%s %d <%s> %s\r\n", m, artno, msgid, c);
}
EXPORT char *find_header_arg(char *s, char *hdr, int len)
/* [<][>][^][v][top][bottom][index][help] */
{
char *s_orig=s;
char *p;
for (;;s=p+1)
{
p=len? strnCaseStr(s, hdr, len): strCaseStr(s, hdr);
if (!p)
return NULL;
if (p==s_orig)
goto found;
if (p[-1]=='\n')
goto found;
}
found:
p+=strlen(hdr);
while (isspace(*p))
p++;
return p;
}
static int authorise_newsgroups (char *s)
/* [<][>][^][v][top][bottom][index][help] */
{
char buf[MAX_HEADER];
char *p = strchr (s, '\r');
char *p2 = strchr (s, '\n');
if (!p2)
{
logw (("badly formed newsgroups line"));
return FALSE;
}
if (p)
p = (p<p2)? p: p2;
else
p = p2;
strncpy(buf, s, p-s);
buf[p-s] = '\0';
for (s=strtok(buf, ","); s; s=strtok(NULL, ","))
{
struct authent *auth;
strStripLeftRight(s);
if (!*s)
continue;
auth = authorise(RemoteHosts, s);
if (auth && auth->read && (!auth->auth || AuthState == valid))
return TRUE;
}
return FALSE;
}
static bool auth_article (char *art, int hlen, char *msgid)
/* [<][>][^][v][top][bottom][index][help] */
{
char *p=find_header_arg(art, "Newsgroups:", hlen);
if (!p || !authorise_newsgroups(p))
{
if (!p)
{
logw (("missing newsgroups in <%s> - skipped", msgid));
emitrn (NNTP_BOGUSARTICLE);
} else
{
log (("%s not permitted read access to <%s>", ClientHost, msgid));
emitrn (NNTP_PERM);
}
return FALSE;
}
return TRUE;
}
/* This routine just shoves out a dummy art with the stats in it */
static void bad_article(struct server_cfg *scfg, enum cmds type)
/* [<][>][^][v][top][bottom][index][help] */
{
switch (type)
{
case c_article:
scfg->share->article_fail++;
break;
case c_head:
scfg->share->head_fail++;
break;
case c_body:
scfg->share->body_fail++;
break;
case c_stat:
scfg->share->stat_fail++;
break;
default:
assert("PC" == "never here");
break;
}
}
static void good_article(struct server_cfg *scfg, enum cmds type)
/* [<][>][^][v][top][bottom][index][help] */
{
switch (type)
{
case c_article:
scfg->share->article_good++;
break;
case c_head:
scfg->share->head_good++;
break;
case c_body:
scfg->share->body_good++;
break;
case c_stat:
scfg->share->stat_good++;
break;
default:
assert("PC" == "never here");
break;
}
}
/* handle article, head, body and stat commands. this routine is
* insane. most of the insanity is attempting to handle all the
* differrent msgid, caching, security, filtering,
* body/stat/article/head variations that can occur without simply
* waiting until we have a complete article in memory before speaking
* to the client i.e we try to minimise cache latency as much as is
* possible. This function has "evolved" over time, and needs a
* complete re-write -- however it works, and it's fast,
* so one's game to touch it) */
EXPORT bool CMDarticle (struct command *cmd, char *args, bool dontcache)
/* [<][>][^][v][top][bottom][index][help] */
{
char bfr[MAX_LINE];
char artfile[MAX_PATH]="";
int artno = 0;
int response;
int fd = -1;
char msgid[MAX_MSGID + 20] = "";
enum cmds type=cmd->val;
int bytes = 0;
int cc;
bool real_head_file = FALSE;
struct strStack *art_stack = NULL;
bool getbyid;
bool f_filt = con->contentFilters && CurrentGroupAuth && CurrentGroupAuth->filter_chain;
strStripLeftRight (args);
if (strSnip(args, 0, "<", ">\r\n", msgid, sizeof msgid)<1 &&
sscanf (args, "%*s %d", &artno) != 1)
{
artno = CurrentGroupArtNum;
sprintf(args, "%.32s %d", cmd->cmd, artno);
}
if (type == c_stat)
strncpy (args, "head", 4);
getbyid = *msgid? TRUE: FALSE;
if (getbyid)
CS->by_msgid++;
else
CS->by_artnum++;
if (!getbyid && (!*CurrentGroup || !setGD()))
{
emitrn (NNTP_NOTINGROUP);
return FALSE;
}
if (getbyid)
{
if (CurrentIDScfg)
CurrentScfg = CurrentIDScfg;
} else
CurrentScfg = CurrentGroupScfg;
/*
* we can't immediately tell if the article belongs to a cached
* server or not if we are doing a getbyid()
*/
if (getbyid || CurrentScfg->article_timeout)
{
if (getbyid) /* find <msgid> -> server/group/article in history file */
{
char *p;
rfc822lower (msgid);
/*
* heads are stored as group/artnum_head
*/
p = hisGet (msgid);
if (p)
{
char *p2;
p2 = strchr(p, '/'); /* isolate server */
if (p2)
{
struct server_cfg *scfg;
*p2 = '\0';
scfg = findScfg (p); /* look it up */
if (scfg)
{
CurrentScfg = scfg;
if (scfg->article_timeout) /* this server caches */
{
char *p3 = strrchr(p2+1, '/'); /* extract the article number */
if (p3)
{
artno = strToi(p3+1);
if (artno>0)
{
*p2 = '/'; /* put the / back */
if (type == c_head || type == c_stat)
{
sprintf(artfile, "%.127s/%.384s_head", con->cacheDir, p);
real_head_file = TRUE;
} else
sprintf(artfile, "%.127s/%.384s", con->cacheDir, p);
}
}
}
}
}
}
} else /* ahh, the simple alternative (!getbyid) */
{
if (type == c_head || type == c_stat)
{
sprintf (artfile, "%d_head", artno);
real_head_file = TRUE;
} else
sprintf (artfile, "%d", artno);
}
if (*artfile) /* we have somewhere to look */
{
if ((fd = open (artfile, O_RDONLY)) < 0)
{
if (type == c_head || type == c_stat)
{
/* no _head, try for article */
char *p;
assert ((p = strrchr (artfile, '_')));
*p = '\0';
real_head_file = FALSE;
fd = open (artfile, O_RDONLY);
} else /* c_article or c_body */
{
/* no article, check for the head */
strcat (artfile, "_head");
fd = open (artfile, O_RDONLY);
if (fd >= 0)
{
struct stat st;
if (fstat (fd, &st) == 0)
{
int len = st.st_size;
char *buf = (char *)mmap (0, len, PROT_READ, MAP_PRIVATE, fd, 0); /* slurrrup */
if (buf!=(char *)-1)
{
int len = st.st_size;
real_head_file = TRUE;
art_stack = strnStackAdd (NULL, buf, len);
if (munmap(buf, len)!=0)
loge (("couldn't munmap %s (%d length)", artfile, len));
logd (("fetched %s (%d bytes) from cache", artfile, len));
}
} else
close (fd);
fd = -1;
} /* revert, open _head failed */
*strrchr (artfile, '_') = '\0';
}
}
}
if (fd<0)
{
/* replace 'body' with 'article' unless we pulled in a _head */
if (type == c_body && !real_head_file)
{
if (*msgid)
sprintf (args, "article <%.*s>", MAX_MSGID, msgid);
else
sprintf (args, "article %d", artno);
} else
/* replace 'article' with 'body' if we pulled in a _head */
if ((type == c_article || type == c_body) && real_head_file)
{
if (*msgid)
sprintf (args, "body <%.*s>", MAX_MSGID, msgid);
else
sprintf (args, "body %d", artno);
}
if (Cemitrn (args) <= 0 || Cflush () != 0) {
logd (("couldn't send command to host (down?)"));
if (art_stack)
strStackFree (art_stack);
emitrn (NNTP_SERVERTEMPDOWN);
return FALSE;
}
}
} else /* not cached */
{
if (Cemitrn (args) <= 0 || Cflush () != 0) {
logd (("couldn't send command to host (down?)"));
if (art_stack)
strStackFree (art_stack);
emitrn (NNTP_SERVERTEMPDOWN);
return FALSE;
}
}
if (fd<0) /* cache off or request not in cache */
{
bool body;
int head_len;
if ((!Cget (bfr, sizeof bfr) ||
!(response=strToi (bfr))) ||
(response != NNTP_ARTICLE_FOLLOWS_VAL &&
response != NNTP_HEAD_FOLLOWS_VAL &&
response != NNTP_BODY_FOLLOWS_VAL))
{
int n;
struct server_cfg *cf;
if (!*msgid)
{
emit (bfr);
bad_art:
bad_article(CurrentScfg, type);
if (art_stack)
strStackFree (art_stack);
return FALSE;
}
for (n = 0, cf = ServerList; cf && n<con->maxMsgIDsearch; cf = cf->next)
{
if (CurrentScfg==cf)
continue;
logd (("trying article <%s> on server %s", msgid, cf->host));
n++;
if (!attachServer (cf))
continue;
Cfemitrn (cf, args);
Cfflush (cf);
if (!Cfget (cf, bfr, sizeof (bfr)))
continue;
switch (strToi (bfr))
{
case NNTP_ARTICLE_FOLLOWS_VAL:
case NNTP_HEAD_FOLLOWS_VAL:
case NNTP_BODY_FOLLOWS_VAL:
sscanf(bfr, "%*d %d", &artno);
cf->artno = artno;
CurrentIDScfg = CurrentScfg = cf;
goto found;
}
}
emitrn (NNTP_DONTHAVEIT);
goto bad_art;
found:
;
} else
{
if (*msgid)
sscanf(bfr, "%*d %d", &artno);
else
strSnip(bfr, 0, "<", ">\r\n", msgid, sizeof msgid);
CurrentScfg->artno=artno;
}
/*
* we only do body's when we are asked for an article or body
* and have only a cached _head or caching is off
*/
body = strnCaseEq (args, "body", 4); /* XXX */
if (!body)
{
/* get the head */
/* TODO: Cget this directly into art_stack and
thus avoid copying */
for (; (cc = Cget (bfr, sizeof bfr)) && !EL (bfr) && *bfr != '\r' && *bfr !='\n'; bytes += cc)
{
if (strnCaseEq(bfr, "Xref:", 5))
{
char xref[sizeof bfr];
cc = xrefRewriteCopy(CurrentScfg, bfr, xref, TRUE);
if (strchr(xref+9,':')) { /* are there any articles? */
xref[cc++] = '\r';
xref[cc++] = '\n';
art_stack = strnStackAdd (art_stack, xref, cc);
}
} else
art_stack = strnStackAdd (art_stack, bfr, cc);
}
if (!art_stack || cc<1)
{
if (art_stack)
strStackFree (art_stack);
emitrn (NNTP_BOGUSARTICLE);
bad_article(CurrentScfg, type);
return FALSE;
}
if (getbyid)
{
if (con->groupSecurity && !auth_article(art_stack->data, art_stack->used-1, msgid)) /* can't have already group auth'ed <msgid> */
{
if (type == c_article || type == c_body) /* drain */
while (Cget(bfr, sizeof bfr) && !EL (bfr));
strStackFree (art_stack);
CS->auth_blocked++;
bad_article(CurrentScfg, type);
return FALSE;
}
}
/* if we pulled it from the cache, then this has already been added */
art_stack = strnStackAdd (art_stack, XCACHE, sizeof(XCACHE)-1);
}
head_len = art_stack? art_stack->used-1 : 0;
if (type == c_article || (type == c_body && CurrentScfg->article_timeout))
art_stack = strnStackAdd (art_stack, "\r\n", 2);
if (!f_filt)
{
emit_follows(type, artno, msgid, getbyid); /* 221 52 <839359140.253541@suburbia.net> head etc */
if (type==c_head || type == c_article)
{
emit(art_stack->data); /* emit the head */
}
}
if (type == c_body || type == c_article)
{
for (; (cc = Cget (bfr, sizeof bfr)) && !EL (bfr); bytes += cc) /* suck in rest */
{
if (!f_filt)
emit (bfr);
art_stack = strnStackAdd (art_stack, bfr, cc);
}
if (!art_stack || !cc)
{
if (art_stack)
strStackFree (art_stack);
emitrn (f_filt? NNTP_BOGUSARTICLE: ".");
bad_article(CurrentScfg, type);
return FALSE;
}
}
if (f_filt)
{
/* we now have the whole shoomse and can attempt to truffle trough it
*/
char *blocker;
if (type == c_body && !CurrentScfg->article_timeout) /* non cached filtered body has no head */
blocker = filterText(type,
CurrentGroupAuth,
NULL,
0,
NULL,
0,
art_stack->data, art_stack->used-1);
else
blocker = filterText(type,
CurrentGroupAuth,
art_stack->data,
head_len,
art_stack->data,
art_stack->used-1,
(type == c_body || type == c_article)? art_stack->data+head_len+2: NULL,
(type == c_body || type == c_article)? art_stack->used-1-head_len-2: 0);
if (blocker)
{
logd (("content filter %s blocked %s of %s/%d", blocker, cmd->cmd, CurrentDir, artno));
emitrn (NNTP_PERM);
CS->filter_blocked++;
return FALSE;
}
emit_follows(type, artno, msgid, getbyid);
if (type != c_stat)
{
emit ((type == c_body && CurrentScfg->article_timeout)? art_stack->data+head_len+2: art_stack->data);
}
}
if (type != c_stat)
emitrn (".");
if (CurrentScfg->article_timeout && !dontcache)
{
if (setGroupDir(NULL, CurrentScfg))
{
char buf[MAX_PATH];
if (!getbyid)
{
sprintf(buf, "%.255s/%.32s", CurrentGroup, artfile);
strExchange(buf, '.', '/');
}
crosspost (art_stack, getbyid? artfile: buf, (type==c_stat)? c_head: type, getbyid);
}
}
good_article(CurrentScfg, type);
strStackFree (art_stack);
return TRUE;
} else /* pull it out of the cache. easy */
{
struct stat st;
char *buf = NULL;
char *body = NULL; /* stop -Wall complaining */
char *msgid_header;
int len = 0; /* stop -Wall complaining */
int wsize;
int t;
if (fstat (fd, &st) != 0 || st.st_size <2)
goto err;
len = st.st_size;
buf = (char *)mmap (0, len, PROT_READ, MAP_PRIVATE, fd, 0);
if (buf == (char *)-1)
goto err;
close (fd);
if (buf[len - 1] != '\n') /* truncated file */
goto err;
if (!real_head_file)
{
body = strstr (buf, "\r\n\r\n");
if (!body)
goto err;
body+=4;
}
if (!*msgid)
{
msgid_header = find_header_arg(buf, "Message-ID:", body? body-buf: len);
if (!msgid_header) /* shouldn't be in the body! */
goto err;
/* some sscanf implimentations are REALLY DUMB (e.g OSF/1) - and insist on scanning to the end of
* the string even after the terminators have been reached. We use strSnip instead */
if (strSnip(msgid_header, MIN (body? body-buf: len, MAX_MSGID + sizeof("Message-ID:")), "<", ">\r\n", msgid, sizeof msgid)<1)
{
loge (("strSnip failed"));
goto err;
}
}
if (getbyid && con->groupSecurity && !auth_article(buf, body? body-buf: len, msgid)) /* can't have already group auth'ed <msgid> */
{
if (munmap(buf, len)!=0)
loge (("couldn't munmap %s (%d length)", artfile, len));
CS->auth_blocked++;
return FALSE;
}
if (f_filt)
{
/* head's and stat's use head and ahead filters, while
* article's and body's are use article and ahead filters.
*/
char *blocker = NULL;
switch (type)
{
case c_article:
case c_body:
blocker = filterText(type, CurrentGroupAuth, buf, body-buf-2, buf, len, body, buf+len-body);
break;
case c_head:
case c_stat:
blocker = filterText(type, CurrentGroupAuth, buf, body? body-buf-2: len, NULL, 0, NULL, 0);
break;
default:
break;
}
if (blocker)
{
logd (("content filter %s blocked %s of %s/%d", blocker, cmd->cmd, CurrentDir, artno));
emitrn (NNTP_PERM);
CS->filter_blocked++;
if (munmap(buf, len)!=0)
loge (("couldn't munmap %s (%d length)", artfile, len));
return FALSE;
}
}
/* system calls and context switches have a fair degree of over-head.
* we attempt to minimise both by using different strategies depending
* on the amount of data we are dealing with. if the total amount of
* data is small, then we use stdio to buffer it up, so we can flush()
* it at the end with only one write(). If we are dealing with a longer
* pieces of information (i.e ~>1k), then we write() it directly,
* reducing the amount of memory movement, at the expense of two
* extra (tiny) writes().
*
* TODO: save the .\r\n on the end of each head/article during the caching?
*/
wsize = emit_follows(type, artno, msgid, getbyid);
t = 0;
switch (type)
{
case c_article:
fflush(clientout); /* keep fd and fh in-sync */
/* avoid buffering, push it out in one hit */
t = writeClient (buf, len);
break;
case c_head:
/* head's tend to be small */
fwriteClient (buf, (t = (body? body-2-buf: len)));
break;
case c_body:
/* bodies can be large or small */
if (buf+len-body > 1024)
{
fflush(clientout); /* keep fd and fh in-sync */
/* avoid buffering, push it out in one hit */
writeClient (body, (t = (buf+len-body)));
} else
fwriteClient (body, (t = (buf+len-body)));
break;
case c_stat:
break;
default:
break;
}
wsize+=t;
if (munmap(buf, len)!=0)
loge (("couldn't munmap %s (%d length)", artfile, len));
if (type != c_stat)
emitrn (".");
return TRUE;
err:
logw (("bad article '%s' ... unlinked", artfile));
if (buf && munmap(buf, len)!=0)
loge (("couldn't munmap %s (%d length)", artfile, len));
emitrn (NNTP_DONTHAVEIT); /* we should probably just call ourselves again */
unlink (artfile);
return FALSE;
}
NOTREACHED;
}