#include "config.h" #include #include #include #include #include #include #include #include #include #include #include "formatting.h" #include "indexer.h" #include "mapfile.h" extern char *fetch(char*); extern char *eoln(char*,char*); /* * rebuild $bbsroot/index.html { all articles this month } * $bbsroot/YYYY/MM/index.html { ditto } */ static char art[25]; /* YYYY/MM/DD/###/message.{txt|inf} */ static char dydir[25]; /* YYYY/MM/DD/### */ static char mo[25]; /* YYYY/MM/index.html */ int dirent_is_good(struct dirent *e) { char *p; for (p = e->d_name; *p; ++p) if (!isdigit(*p)) return 0; return 1; } int dirent_nsort(struct dirent **a, struct dirent **b) { return atoi((*a)->d_name) - atoi((*b)->d_name); } char * restofline(char *p, char *q) { char *a; int size = (q-p); a = malloc(size+2); memcpy(a, p, size); a[size] = 0; return a; } static FILE *iFb = 0; void putindex(FILE *f) { register c; static char *text = 0; static long size; char *p, *end, *q; char *url; char *home; char *context; unsigned int comments; unsigned int comments_ok; enum { HOME, POST, ARCHIVE } state = HOME; /* state == home; just the [comment] button * == post; [comment], [edit], [post] * == archive; [comment] */ if (context = fetch("_CONTEXT")) { if (strcmp(context, "post") == 0) state = POST; else if (strcmp(context, "archive") == 0) state = ARCHIVE; } if (state == POST) { fprintf(f, "
\n"); fputs(fmt.post.start, f); fprintf(f, "\n"); fputs(fmt.post.end, f); fprintf(f, "
\n"); fprintf(f, "
\n"); fprintf(f, "\n"); fprintf(f, "
\n"); fputs(fmt.separator, f); } rewind(iFb); if (text = mapfd(fileno(iFb), &size)) { /* context format is: rep [ text^@{settings}^@ ] * where settings are ^Aurl * ^Bwebroot */ /*fwrite(text,size,1,f);*/ p = text; end = p + size; for (; (p < end) && (q = memchr(p, 0, end-p)); p = 1+q) { fwrite(p, (q-p), 1, f); comments_ok = comments = 0; while (*++q) { switch (*q) { case 1: url = 1+q; break; case 2: home = 1+q; break; case 3: comments=atoi(1+q); break; case 4: comments_ok = 1; break; } while (*q) ++q; } if ( (state == POST) && url ) { fprintf(f, "
\n"); fputs(fmt.edit.start, f); fprintf(f, "\n"); fprintf(f, "\n", url); fputs(fmt.edit.end, f); fprintf(f, "
\n"); } if ( !(url && home) ) continue; if (comments > 0) { fputs(fmt.comment.start, f); fprintf(f, "%d comment%s\n", home, url, comments, (comments!=1)?"s":""); fputs(fmt.comment.end,f); } else if (comments_ok) { fprintf(f, "
\n", home); fputs(fmt.comment.start, f); fprintf(f, "\n"); fprintf(f, "\n", url); fputs(fmt.comment.end, f); fprintf(f, "
\n"); } } fwrite(p, end-p, 1, f); munmap(text,size); } else { fprintf(f, "\n", strerror(errno)); syslog(LOG_INFO, "mapfd failed: %m"); } } char * makefile(char *pathname, char *filepart) { char *ret = malloc(strlen(pathname)+20); char *p; if (ret) { strcpy(ret, pathname); if (p = strrchr(ret, '/')) { strcpy(p+1, filepart); return ret; } free(ret); } return 0; } struct article * openart(char *filename) { struct article *ret = calloc(sizeof *ret, 1); char *base; char *p, *eol, *end; char field; char *fname; char *ctl; long size; if ( ret == 0 ) return 0; ret->ctlfile = makefile(filename, "message.ctl"); ret->msgfile = makefile(filename, "message.txt"); ret->cmtfile = makefile(filename, "comment.txt"); if ( !(ret->ctlfile || ret->msgfile || ret->cmtfile) ) { freeart(ret); return 0; } if ( (ctl = mapfile(ret->ctlfile, &size)) == 0 ) { freeart(ret); return 0; } end = ctl + size; for (p = ctl; p < end; p = 1+eol) { eol = eoln(p, end); if ( p[1] == ':' ) { field = p[0]; p += 2; switch (field) { case 'A': /* The (A)uthor */ ret->author = restofline(p,eol); break; case 'W': /* W)hen was it posted */ ret->timeofday = atol(p); break; case 'M': /* when it was (M)odified */ ret->modified = atol(p); break; case 'T': /* T)itle of the article */ ret->title = restofline(p,eol); break; case 'U': /* end of (U)rl */ ret->url = restofline(p,eol); break; case 'C': /* comments ok? */ ret->comments_ok = atoi(p); break; case 'F': /* # of (F)ollowups */ ret->comments = atoi(p); break; case 'P': /* P)review (for rss syndication) */ ret->preview = restofline(p, eol); break; default: break; } } } munmap(ctl, size); if (ret->modified == 0) ret->modified = ret->timeofday; if (ret->body = mapfile(ret->msgfile, &ret->size)) return ret; freeart(ret); return 0; } int newart(struct article *art) { char path[40]; char *end; int seq; struct tm *tm; if (art == 0) return 0; tm = localtime( &art->timeofday ); strftime(path, sizeof path, "%Y", tm); if ( (mkdir(path, 0755) != 0) && (errno != EEXIST) ) return 0; strftime(path, sizeof path, "%Y/%m", tm); if ( (mkdir(path, 0755) != 0) && (errno != EEXIST) ) return 0; strftime(path, sizeof path, "%Y/%m/%d", tm); if ( (mkdir(path, 0755) != 0) && (errno != EEXIST) ) return 0; end = path + strlen(path); for (seq=0; seq< 1000; seq++) { sprintf(end, "/%03d", seq); if (mkdir(path, 0755) == 0) { strcat(path, "/"); art->ctlfile = makefile(path, "message.ctl"); art->msgfile = makefile(path, "message.txt"); art->cmtfile = makefile(path, "comment.txt"); return (art->ctlfile && art->msgfile && art->cmtfile); } } return 0; } void freeartbody(struct article *art) { if (art && art->body) { munmap(art->body, art->size); art->body = 0; art->size = 0; } } void freeart(struct article *art) { if (art) { freeartbody(art); if (art->author) free(art->author); if (art->title) free(art->title); if (art->url) free(art->url); if (art->ctlfile) free(art->ctlfile); if (art->msgfile) free(art->msgfile); if (art->cmtfile) free(art->cmtfile); memset(art,0,sizeof *art); free(art); } } int writectl(struct article *art) { FILE *f; errno = EINVAL; if (art && art->ctlfile && (f = fopen(art->ctlfile, "w")) ) { fprintf(f, "A:%s\n", art->author); fprintf(f, "W:%ld\n", art->timeofday); fprintf(f, "M:%ld\n", art->modified ? art->modified : art->timeofday); fprintf(f, "T:%s\n",art->title); fprintf(f, "U:%s\n", art->url); fprintf(f, "F:%d\n", art->comments); fprintf(f, "C:%d\n", art->comments_ok); fclose(f); return 1; } return 0; } int writemsg(struct article *art, int flags) { FILE *f; errno = EINVAL; if (art && art->msgfile && (f = fopen(art->msgfile, "w")) ) { format(f, art->body, flags|FM_BLOCKED); fclose(f); return 1; } return 0; } static void alink(FILE *f, char *pfx, char *sfx, char *line, char *end) { fprintf(f, "%s', f); if (*line == ':') format(f, ++line, FM_ONELINE); fprintf(f, "%s\n",sfx); } char *lastlast; static void navbar(FILE *f, char *url) { static char *db = 0; static int size, usize; static int hasdb = 1; char *end; char *p, *low, *high; int ret, gap; lastlast = 0; if (fmt.url == 0) return; if ( hasdb && !db ) { db = mapfile("index.db", &size); hasdb = (db != 0); } if ( !hasdb ) return; high = end = db + size; low = db; usize = strlen(url); while (low < high) { gap = high-low; p = low + (gap/2); while (p > low && p[-1] != '\n') --p; ret = strncmp(url, p, usize); if (ret < 0) high = p; else if (ret > 0) { low = p; while (low < high && *low != '\n') ++low; if (low < high) low++; } else { char *next, *last; fprintf(f, "

\n"); for (next=p; next < end && *next != '\n'; ++next) ; if (next < end-1) { ++next; alink(f, "« ", " ", next, end); } else next=0; if (p > db) { last = p-1; while (last > db && last[-1] != '\n') --last; if (next) fprintf(f, " · "); lastlast = last; alink(f, " ", " »", last, p); } fprintf(f, "

\n"); return; } } } static struct article *htmlart = 0; static int puthtml(FILE *f) { char *text; long size; navbar(f, htmlart->url); subject(f, htmlart->title); if (fmt.topsig) byline(f, htmlart, 0); if (text = mapfile(htmlart->msgfile, &size)) { fprintf(f, "\n"); fwrite(text,size,1,f); munmap(text,size); } else fprintf(f, "

Ooops! mmap %s

\n",strerror(errno)); if (!fmt.topsig) byline(f,htmlart, 0); if (text = mapfile(htmlart->cmtfile, &size)) { fprintf(f, "

Comments


\n"); fwrite(text,size,1,f); munmap(text,size); } if (htmlart->comments_ok) { fprintf(f, "\n"); fprintf(f, "
\n", fetch("_ROOT")); fputs(fmt.comment.start, f); if (htmlart->comments > 0) fprintf(f, "\n"); else fprintf(f, "\n"); fprintf(f, "\n", htmlart->url); fputs(fmt.comment.end, f); fprintf(f, "
\n"); } return 1; } int writehtml(struct article *art) { FILE *f; char *url, *root; if ( art && art->url && (f = fopen(art->url, "w")) ) { flock(fileno(f), LOCK_EX); htmlart = art; root = fetch("_ROOT"); url = alloca(strlen(art->url)+strlen(root)+10); sprintf(url, "%s%s", root, art->url); stash("_DOCUMENT", url); stash("_USER", art->author); stash("title", art->title); process("post.theme", puthtml, 1, f); fflush(f); ftruncate(fileno(f), ftell(f)); flock(fileno(f), LOCK_UN); fclose(f); } htmlart = 0; } int reindex(struct tm *tm, char *bbspath, int full_rebuild, int nrposts) { char b1[20], b2[20]; int fd; struct dirent **days, **each; int dmax, emax; struct dirent **dp, **ap; struct tm m; char **files; int nrfiles, count; char scratch[20]; int even = 0; int more = (nrposts > 0) ? 2 : 1; int total = 0; int i; int c; int j, k; int verbose = fetch("_VERBOSE") != 0; char *webroot = fetch("_ROOT"); m = *tm; strftime(mo, sizeof mo, "%Y/%m", &m); files = malloc(sizeof *files * (nrfiles = 1000)); count=0; while ( more-- ) { dmax = scandir(mo, &days, dirent_is_good, dirent_nsort); for (j=dmax; j-- > 0; ) { sprintf(dydir, "%s/%s", mo, days[j]->d_name); if ( (emax=scandir(dydir, &each, dirent_is_good, dirent_nsort)) < 1) continue; strftime(b1, sizeof b1, "@ %b %%s, %Y", &m); sprintf(b2, b1, days[j]->d_name); files[count++] = strdup(b2); if (verbose) printf("T %s\n", b2); for (k = emax; k-- > 0; ) { total ++; sprintf(art, "%s/%s/message.ctl", dydir, each[k]->d_name); if (count >= nrfiles-5) files = realloc(files, sizeof *files * (nrfiles += 1000)); files[count++] = strdup(art); if (verbose) printf("A %s\n", art); /*free(*ap);*/ } /*free(*dp);*/ } if (--m.tm_mon < 1) { m.tm_mon = 11; m.tm_year--; } strftime(mo, sizeof mo, "%Y/%m", &m); } if (iFb) fclose(iFb); iFb = tmpfile(); for (j = i = 0; i < count; i++) { if (files[i][0] == '@') { fprintf(iFb, "%s%s%s\n", fmt.chapter.start, 1+(files[i]), fmt.chapter.end); j=0; } else { struct article *art; if (art = openart(files[i])) { if (full_rebuild) { writehtml(art); if (verbose) printf("H %s\n", files[i]); } if (j++ > 0) fputs(fmt.separator, iFb); fprintf(iFb, fmt.article.start, even ? "even" : "odd"); subject(iFb, art->title); if (fmt.topsig) byline(iFb, art, 1); fwrite(art->body, art->size, 1, iFb); if (!fmt.topsig) byline(iFb, art, 1); /* url fragment to article */ fputc(0, iFb); fputc(1, iFb); fputs(art->url, iFb); /* url root of weblog */ fputc(0, iFb); fputc(2, iFb); fputs(webroot, iFb); if (art->comments > 0) { fputc(0, iFb); fputc(3, iFb); fprintf(iFb, "%d", art->comments); } if (art->comments_ok) { fputc(0, iFb); fputc(4, iFb); fprintf(iFb, "1"); } fputc(0, iFb); fputc(0, iFb); fputs(fmt.article.end, iFb); even = !even; freeart(art); if ( nrposts && !--nrposts ) break; } else perror(files[i]); } free(files[i]); } done: while (i < count) free(files[i++]); /*fflush(iFb);*/ rewind(iFb); free(files); return 1; } int buildpages(struct tm *tm, int which) { char post[80]; char archive_for[80]; FILE *f; struct stat st; char *bbsroot = fetch("_ROOT"); char *archive; archive = alloca(strlen(bbsroot) + 1001); if (which & PG_ARCHIVE) { strftime(post, sizeof post, "%Y/%m/index.html", tm); strftime(archive_for, sizeof archive_for, "Archive for %b %Y", tm); /* set stdout to -> YYYY/MM/index.html */ if ( f = fopen(post, "w") ) { sprintf(archive, "%s%s",bbsroot, post); stash("_DOCUMENT", archive); stash("_CONTEXT", "archive"); stash("title", archive_for); process("page.theme", putindex, 1, f); fclose(f); } } else if (which & PG_HOME) { /* set stdout to -> index.html */ if ( f = fopen("index.html", "w") ) { sprintf(archive, "%sindex.html", bbsroot); stash("_DOCUMENT", archive); stash("_CONTEXT", "home"); stash("title", fmt.name); process("page.theme", putindex, 1, f); fclose(f); } } else if (which & PG_POST) { /* set stdout to -> post/index.html */ if ( f = fopen("post/index.html", "w") ) { sprintf(archive, "%spost/index.html", bbsroot); stash("_DOCUMENT", archive); stash("_CONTEXT", "post"); stash("title", fmt.name); if (stat("post/page.theme", &st) == 0) process("post/page.theme", putindex, 1, f); else process("page.theme", putindex, 1, f); fclose(f); } } return 1; } void generate(struct tm *tm, char *bbspath, int indexflags, int buildflags) { int nrposts = 0; unsigned int flag; for (flag = 0x01; flag; flag <<= 1) if (buildflags & flag) { nrposts = (flag==PG_HOME) ? fmt.nrposts : 0; if (reindex(tm, bbspath, indexflags, nrposts)) buildpages(tm, flag); } }