saait.c (12892B)
1#include <ctype.h> 2#include <dirent.h> 3#include <errno.h> 4#include <limits.h> 5#include <stdio.h> 6#include <stdint.h> 7#include <stdlib.h> 8#include <string.h> 9 10/* OpenBSD pledge(2) */ 11#ifdef __OpenBSD__ 12#include <unistd.h> 13#else 14#define pledge(p1,p2) 0 15#endif 16 17/* This is the blocksize of my disk, use atleast an equal or higher value and 18 a multiple of 2 for better performance ((struct stat).st_blksize). */ 19#define READ_BUF_SIZ 16384 20#define LEN(s) (sizeof(s)/sizeof(*s)) 21 22enum { BlockHeader = 0, BlockItem, BlockFooter, BlockLast }; 23 24struct variable { 25 char *key, *value; 26 struct variable *next; 27}; 28 29struct block { 30 char *name; /* filename */ 31 char *data; /* content (set at runtime) */ 32}; 33 34struct template { 35 char *name; 36 /* blocks: header, item, footer */ 37 struct block blocks[BlockLast]; 38 /* output FILE * (set at runtime) */ 39 FILE *fp; 40}; 41 42static const char *configfile = "config.cfg"; 43static const char *outputdir = "output"; 44static const char *templatedir = "templates"; 45 46static struct variable *global; /* global config variables */ 47 48char * 49estrdup(const char *s) 50{ 51 char *p; 52 53 if (!(p = strdup(s))) { 54 fprintf(stderr, "strdup: %s\n", strerror(errno)); 55 exit(1); 56 } 57 return p; 58} 59 60void * 61ecalloc(size_t nmemb, size_t size) 62{ 63 void *p; 64 65 if (!(p = calloc(nmemb, size))) { 66 fprintf(stderr, "calloc: %s\n", strerror(errno)); 67 exit(1); 68 } 69 return p; 70} 71 72void * 73erealloc(void *ptr, size_t size) 74{ 75 void *p; 76 77 if (!(p = realloc(ptr, size))) { 78 fprintf(stderr, "realloc: %s\n", strerror(errno)); 79 exit(1); 80 } 81 return p; 82} 83 84FILE * 85efopen(const char *path, const char *mode) 86{ 87 FILE *fp; 88 89 if (!(fp = fopen(path, mode))) { 90 fprintf(stderr, "fopen: %s, mode: %s: %s\n", 91 path, mode, strerror(errno)); 92 exit(1); 93 } 94 return fp; 95} 96 97void 98catfile(FILE *fpin, const char *ifile, FILE *fpout, const char *ofile) 99{ 100 char buf[READ_BUF_SIZ]; 101 size_t r; 102 103 while (!feof(fpin)) { 104 if (!(r = fread(buf, 1, sizeof(buf), fpin))) 105 break; 106 if ((fwrite(buf, 1, r, fpout)) != r) 107 break; 108 if (r != sizeof(buf)) 109 break; 110 } 111 if (ferror(fpin)) { 112 fprintf(stderr, "%s -> %s: error reading data from stream: %s\n", 113 ifile, ofile, strerror(errno)); 114 exit(1); 115 } 116 if (ferror(fpout)) { 117 fprintf(stderr, "%s -> %s: error writing data to stream: %s\n", 118 ifile, ofile, strerror(errno)); 119 exit(1); 120 } 121} 122 123char * 124readfile(const char *file) 125{ 126 FILE *fp; 127 char *buf; 128 size_t n, len = 0, size = 0; 129 130 fp = efopen(file, "rb"); 131 buf = ecalloc(1, size + 1); /* always allocate an empty buffer */ 132 while (!feof(fp)) { 133 if (len + READ_BUF_SIZ + 1 > size) { 134 /* allocate size: common case is small textfiles */ 135 size += READ_BUF_SIZ; 136 buf = erealloc(buf, size + 1); 137 } 138 if (!(n = fread(&buf[len], 1, READ_BUF_SIZ, fp))) 139 break; 140 len += n; 141 buf[len] = '\0'; 142 if (n != READ_BUF_SIZ) 143 break; 144 } 145 if (ferror(fp)) { 146 fprintf(stderr, "fread: file: %s: %s\n", file, strerror(errno)); 147 exit(1); 148 } 149 fclose(fp); 150 151 return buf; 152} 153 154struct variable * 155newvar(const char *key, const char *value) 156{ 157 struct variable *v; 158 159 v = ecalloc(1, sizeof(*v)); 160 v->key = estrdup(key); 161 v->value = estrdup(value); 162 163 return v; 164} 165 166/* uses var->key as key */ 167void 168setvar(struct variable **vars, struct variable *var, int override) 169{ 170 struct variable *p, *v; 171 172 /* new */ 173 if (!*vars) { 174 *vars = var; 175 return; 176 } 177 178 /* search: set or append */ 179 for (p = NULL, v = *vars; v; v = v->next, p = v) { 180 if (!strcmp(var->key, v->key)) { 181 if (!override) 182 return; 183 /* NOTE: keep v->next */ 184 var->next = v->next; 185 if (p) 186 p->next = var; 187 else 188 *vars = var; 189 free(v->key); 190 free(v->value); 191 free(v); 192 return; 193 } 194 /* append */ 195 if (!v->next) { 196 var->next = NULL; 197 v->next = var; 198 return; 199 } 200 } 201} 202 203struct variable * 204getvar(struct variable *vars, char *key) 205{ 206 struct variable *v; 207 208 for (v = vars; v; v = v->next) 209 if (!strcmp(key, v->key)) 210 return v; 211 return NULL; 212} 213 214void 215freevars(struct variable *vars) 216{ 217 struct variable *v, *tmp; 218 219 for (v = vars; v; ) { 220 tmp = v->next; 221 free(v->key); 222 free(v->value); 223 free(v); 224 v = tmp; 225 } 226} 227 228struct variable * 229parsevars(const char *file, const char *s) 230{ 231 struct variable *vars = NULL, *v; 232 const char *keystart, *keyend, *valuestart, *valueend; 233 size_t linenr = 1; 234 235 for (; *s; ) { 236 if (*s == '\r' || *s == '\n') { 237 linenr += (*s == '\n'); 238 s++; 239 continue; 240 } 241 242 /* comment start with #, skip to newline */ 243 if (*s == '#') { 244 s++; 245 s = &s[strcspn(s, "\n")]; 246 continue; 247 } 248 249 /* trim whitespace before key */ 250 s = &s[strspn(s, " \t")]; 251 252 keystart = s; 253 s = &s[strcspn(s, "=\r\n")]; 254 if (*s != '=') { 255 fprintf(stderr, "%s:%zu: error: no variable\n", 256 file, linenr); 257 exit(1); 258 } 259 260 /* trim whitespace at end of key: but whitespace inside names 261 are allowed */ 262 for (keyend = s++; keyend > keystart && 263 (keyend[-1] == ' ' || keyend[-1] == '\t'); 264 keyend--) 265 ; 266 /* no variable name: skip */ 267 if (keystart == keyend) { 268 fprintf(stderr, "%s:%zu: error: invalid variable\n", 269 file, linenr); 270 exit(1); 271 } 272 273 /* trim whitespace before value */ 274 valuestart = &s[strspn(s, " \t")]; 275 s = &s[strcspn(s, "\r\n")]; 276 valueend = s; 277 278 v = ecalloc(1, sizeof(*v)); 279 v->key = ecalloc(1, keyend - keystart + 1); 280 memcpy(v->key, keystart, keyend - keystart); 281 v->value = ecalloc(1, valueend - valuestart + 1); 282 memcpy(v->value, valuestart, valueend - valuestart); 283 284 setvar(&vars, v, 1); 285 } 286 return vars; 287} 288 289struct variable * 290readconfig(const char *file) 291{ 292 struct variable *c; 293 char *data; 294 295 data = readfile(file); 296 c = parsevars(file, data); 297 free(data); 298 299 return c; 300} 301 302/* Escape characters below as HTML 2.0 / XML 1.0. */ 303void 304xmlencode(const char *s, FILE *fp) 305{ 306 for (; *s; s++) { 307 switch (*s) { 308 case '<': fputs("<", fp); break; 309 case '>': fputs(">", fp); break; 310 case '\'': fputs("'", fp); break; 311 case '&': fputs("&", fp); break; 312 case '"': fputs(""", fp); break; 313 default: fputc(*s, fp); 314 } 315 } 316} 317 318void 319writepage(FILE *fp, const char *name, const char *forname, 320 struct variable *c, char *s) 321{ 322 FILE *fpin; 323 struct variable *v; 324 char *key; 325 size_t keylen, linenr = 1; 326 int op, tmpc; 327 328 for (; *s; s++) { 329 op = *s; 330 switch (*s) { 331 case '#': /* insert value non-escaped */ 332 case '$': /* insert value escaped */ 333 case '%': /* insert contents of filename set in variable */ 334 if (*(s + 1) == '{') { 335 s += 2; 336 break; 337 } 338 fputc(*s, fp); 339 continue; 340 case '\n': 341 linenr++; /* FALLTHROUGH */ 342 default: 343 fputc(*s, fp); 344 continue; 345 } 346 347 /* variable case */ 348 for (; *s && isspace((unsigned char)*s); s++) 349 ; 350 key = s; 351 for (keylen = 0; *s && *s != '}'; s++) 352 keylen++; 353 /* trim right whitespace */ 354 for (; keylen && isspace((unsigned char)key[keylen - 1]); ) 355 keylen--; 356 357 /* temporary NUL terminate */ 358 tmpc = key[keylen]; 359 key[keylen] = '\0'; 360 361 /* lookup variable in config, if no config or not found look in 362 global config */ 363 if (!c || !(v = getvar(c, key))) 364 v = getvar(global, key); 365 key[keylen] = tmpc; /* restore NUL terminator to original */ 366 367 if (!v) { 368 fprintf(stderr, "%s:%zu: error: undefined variable: '%.*s'%s%s\n", 369 name, linenr, (int)keylen, key, 370 forname ? " for " : "", forname ? forname : ""); 371 exit(1); 372 } 373 374 switch (op) { 375 case '#': 376 fputs(v->value, fp); 377 break; 378 case '$': 379 xmlencode(v->value, fp); 380 break; 381 case '%': 382 if (!v->value[0]) 383 break; 384 fpin = efopen(v->value, "rb"); 385 catfile(fpin, v->value, fp, name); 386 fclose(fpin); 387 break; 388 } 389 } 390} 391 392void 393usage(const char *argv0) 394{ 395 fprintf(stderr, "%s [-c configfile] [-o outputdir] [-t templatesdir] " 396 "pages...\n", argv0); 397 exit(1); 398} 399 400int 401main(int argc, char *argv[]) 402{ 403 struct template *t, *templates = NULL; 404 struct block *b; 405 struct variable *c, *v; 406 DIR *bdir, *idir; 407 struct dirent *ir, *br; 408 char file[PATH_MAX + 1], contentfile[PATH_MAX + 1], path[PATH_MAX + 1]; 409 char outputfile[PATH_MAX + 1], *p, *filename; 410 size_t i, j, k, templateslen; 411 int argi, r; 412 413 if (pledge("stdio cpath rpath wpath", NULL) == -1) { 414 fprintf(stderr, "pledge: %s\n", strerror(errno)); 415 return 1; 416 } 417 418 for (argi = 1; argi < argc; argi++) { 419 if (argv[argi][0] != '-') 420 break; 421 if (argi + 1 >= argc) 422 usage(argv[0]); 423 switch (argv[argi][1]) { 424 case 'c': configfile = argv[++argi]; break; 425 case 'o': outputdir = argv[++argi]; break; 426 case 't': templatedir = argv[++argi]; break; 427 default: usage(argv[0]); break; 428 } 429 } 430 431 /* global config */ 432 global = readconfig(configfile); 433 434 /* load templates, must start with "header.", "item." or "footer." */ 435 templateslen = 0; 436 if (!(bdir = opendir(templatedir))) { 437 fprintf(stderr, "opendir: %s: %s\n", templatedir, strerror(errno)); 438 exit(1); 439 } 440 441 while ((br = readdir(bdir))) { 442 if (br->d_name[0] == '.') 443 continue; 444 445 r = snprintf(path, sizeof(path), "%s/%s", templatedir, 446 br->d_name); 447 if (r < 0 || (size_t)r >= sizeof(path)) { 448 fprintf(stderr, "path truncated: '%s/%s'\n", 449 templatedir, br->d_name); 450 exit(1); 451 } 452 453 if (!(idir = opendir(path))) { 454 fprintf(stderr, "opendir: %s: %s\n", path, strerror(errno)); 455 exit(1); 456 } 457 458 templateslen++; 459 /* check overflow */ 460 if (SIZE_MAX / templateslen < sizeof(*templates)) { 461 fprintf(stderr, "realloc: too many templates: %zu\n", templateslen); 462 exit(1); 463 } 464 templates = erealloc(templates, templateslen * sizeof(*templates)); 465 t = &templates[templateslen - 1]; 466 memset(t, 0, sizeof(struct template)); 467 t->name = estrdup(br->d_name); 468 469 while ((ir = readdir(idir))) { 470 if (!strncmp(ir->d_name, "header.", sizeof("header.") - 1)) 471 b = &(t->blocks[BlockHeader]); 472 else if (!strncmp(ir->d_name, "item.", sizeof("item.") - 1)) 473 b = &(t->blocks[BlockItem]); 474 else if (!strncmp(ir->d_name, "footer.", sizeof("footer.") - 1)) 475 b = &(t->blocks[BlockFooter]); 476 else 477 continue; 478 479 r = snprintf(file, sizeof(file), "%s/%s", path, 480 ir->d_name); 481 if (r < 0 || (size_t)r >= sizeof(file)) { 482 fprintf(stderr, "path truncated: '%s/%s'\n", 483 path, ir->d_name); 484 exit(1); 485 } 486 b->name = estrdup(file); 487 b->data = readfile(file); 488 } 489 closedir(idir); 490 } 491 closedir(bdir); 492 493 /* open output files for templates and write header, except for "page" */ 494 for (i = 0; i < templateslen; i++) { 495 /* "page" is a special case */ 496 if (!strcmp(templates[i].name, "page")) 497 continue; 498 r = snprintf(file, sizeof(file), "%s/%s", outputdir, 499 templates[i].name); 500 if (r < 0 || (size_t)r >= sizeof(file)) { 501 fprintf(stderr, "path truncated: '%s/%s'\n", outputdir, 502 templates[i].name); 503 exit(1); 504 } 505 templates[i].fp = efopen(file, "wb"); 506 507 /* header */ 508 b = &templates[i].blocks[BlockHeader]; 509 if (b->name) 510 writepage(templates[i].fp, b->name, NULL, NULL, b->data); 511 } 512 513 /* pages */ 514 for (i = argi; i < (size_t)argc; i++) { 515 c = readconfig(argv[i]); 516 517 if ((p = strrchr(argv[i], '.'))) 518 r = snprintf(contentfile, sizeof(contentfile), "%.*s.html", 519 (int)(p - argv[i]), argv[i]); 520 else 521 r = snprintf(contentfile, sizeof(contentfile), "%s.html", argv[i]); 522 if (r < 0 || (size_t)r >= sizeof(contentfile)) { 523 fprintf(stderr, "path truncated for file: '%s'\n", argv[i]); 524 exit(1); 525 } 526 /* set contentfile, but allow to override it */ 527 setvar(&c, newvar("contentfile", contentfile), 0); 528 529 if ((v = getvar(c, "filename"))) { 530 filename = v->value; 531 } else { 532 /* set output filename (with path removed), but allow 533 to override it */ 534 if ((p = strrchr(contentfile, '/'))) 535 filename = &contentfile[p - contentfile + 1]; 536 else 537 filename = contentfile; 538 539 setvar(&c, newvar("filename", filename), 0); 540 } 541 542 /* item blocks */ 543 for (j = 0; j < templateslen; j++) { 544 /* "page" is a special case */ 545 if (!strcmp(templates[j].name, "page")) { 546 r = snprintf(outputfile, sizeof(outputfile), "%s/%s", 547 outputdir, filename); 548 if (r < 0 || (size_t)r >= sizeof(outputfile)) { 549 fprintf(stderr, "path truncated: '%s/%s'\n", 550 outputdir, filename); 551 exit(1); 552 } 553 554 /* "page" template files are opened per item 555 as opposed to other templates */ 556 templates[j].fp = efopen(outputfile, "wb"); 557 for (k = 0; k < LEN(templates[j].blocks); k++) { 558 b = &templates[j].blocks[k]; 559 if (b->name) 560 writepage(templates[j].fp, 561 b->name, argv[i], c, 562 b->data); 563 } 564 fclose(templates[j].fp); 565 } else { 566 b = &templates[j].blocks[BlockItem]; 567 if (b->name) 568 writepage(templates[j].fp, b->name, 569 argv[i], c, b->data); 570 } 571 } 572 freevars(c); 573 } 574 575 /* write footer, except for "page" */ 576 for (i = 0; i < templateslen; i++) { 577 if (!strcmp(templates[i].name, "page")) 578 continue; 579 b = &templates[i].blocks[BlockFooter]; 580 if (b->name) 581 writepage(templates[i].fp, b->name, NULL, NULL, b->data); 582 } 583 584 return 0; 585}