saait.c (13403B)
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 22 enum { BlockHeader = 0, BlockItem, BlockFooter, BlockLast }; 23 24 struct variable { 25 char *key, *value; 26 struct variable *next; 27 }; 28 29 struct block { 30 char *name; /* filename */ 31 char *data; /* content (set at runtime) */ 32 }; 33 34 struct 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 42 static const char *configfile = "config.cfg"; 43 static const char *outputdir = "output"; 44 static const char *templatedir = "templates"; 45 46 static struct variable *global; /* global config variables */ 47 48 char * 49 estrdup(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 60 void * 61 ecalloc(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 72 void * 73 erealloc(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 84 FILE * 85 efopen(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 97 void 98 catfile(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 123 char * 124 readfile(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 154 struct variable * 155 newvar(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 */ 167 void 168 setvar(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 203 struct variable * 204 getvar(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 214 void 215 freevars(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 228 struct variable * 229 parsevars(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 if (valuestart != valueend && *valuestart == '"' && valueend[-1] == '"') { 279 valuestart++; 280 valueend--; 281 } 282 283 v = ecalloc(1, sizeof(*v)); 284 v->key = ecalloc(1, keyend - keystart + 1); 285 memcpy(v->key, keystart, keyend - keystart); 286 v->value = ecalloc(1, valueend - valuestart + 1); 287 memcpy(v->value, valuestart, valueend - valuestart); 288 289 setvar(&vars, v, 1); 290 } 291 return vars; 292 } 293 294 struct variable * 295 readconfig(const char *file) 296 { 297 struct variable *c; 298 char *data; 299 300 data = readfile(file); 301 c = parsevars(file, data); 302 free(data); 303 304 return c; 305 } 306 307 /* Escape characters below as HTML 2.0 / XML 1.0. */ 308 void 309 xmlencode(const char *s, FILE *fp) 310 { 311 for (; *s; s++) { 312 switch (*s) { 313 case '<': fputs("<", fp); break; 314 case '>': fputs(">", fp); break; 315 case '\'': fputs("'", fp); break; 316 case '&': fputs("&", fp); break; 317 case '"': fputs(""", fp); break; 318 default: fputc(*s, fp); 319 } 320 } 321 } 322 323 void 324 writepage(FILE *fp, const char *name, const char *forname, 325 struct variable *c, char *s) 326 { 327 FILE *fpin; 328 struct variable *v; 329 char *key; 330 size_t keylen, linenr = 1; 331 int op, tmpc; 332 333 for (; *s; s++) { 334 op = *s; 335 switch (*s) { 336 case '#': /* insert value non-escaped */ 337 case '$': /* insert value escaped */ 338 case '%': /* insert contents of filename set in variable */ 339 case '?': /* text if variable matches */ 340 if (*(s + 1) == '{') { 341 s += 2; 342 break; 343 } 344 fputc(*s, fp); 345 continue; 346 case '\n': 347 linenr++; /* FALLTHROUGH */ 348 default: 349 fputc(*s, fp); 350 continue; 351 } 352 353 /* variable case */ 354 for (; *s && isspace((unsigned char)*s); s++) 355 ; 356 key = s; 357 keylen = strcspn(s, "=}" + (op != '?')); 358 s += keylen; 359 /* trim right whitespace */ 360 for (; keylen && isspace((unsigned char)key[keylen - 1]); ) 361 keylen--; 362 363 /* temporary NUL terminate */ 364 tmpc = key[keylen]; 365 key[keylen] = '\0'; 366 367 /* lookup variable in config, if no config or not found look in 368 global config */ 369 if (!c || !(v = getvar(c, key))) 370 v = getvar(global, key); 371 key[keylen] = tmpc; /* restore NUL terminator to original */ 372 373 if (!v) { 374 fprintf(stderr, "%s:%zu: error: undefined variable: '%.*s'%s%s\n", 375 name, linenr, (int)keylen, key, 376 forname ? " for " : "", forname ? forname : ""); 377 exit(1); 378 } 379 380 switch (op) { 381 case '#': 382 fputs(v->value, fp); 383 break; 384 case '$': 385 xmlencode(v->value, fp); 386 break; 387 case '%': 388 if (!v->value[0]) 389 break; 390 fpin = efopen(v->value, "rb"); 391 catfile(fpin, v->value, fp, name); 392 fclose(fpin); 393 break; 394 case '?': 395 char *cmp = s + 1; 396 size_t len = strcspn(cmp, ":"); 397 tmpc = cmp[len]; 398 cmp[len] = '\0'; 399 s = cmp + len + 1 + strcspn(cmp + len + 1, "}"); 400 401 if (strcmp(v->value, cmp)) { 402 cmp[len] = tmpc; 403 continue; 404 } 405 cmp[len] = tmpc; 406 tmpc = *s; 407 *s = '\0'; 408 fwrite(cmp + len + 1, 1, s - (cmp + len + 1), fp); 409 *s = tmpc; 410 } 411 } 412 } 413 414 void 415 usage(const char *argv0) 416 { 417 fprintf(stderr, "%s [-c configfile] [-o outputdir] [-t templatesdir] " 418 "pages...\n", argv0); 419 exit(1); 420 } 421 422 int 423 main(int argc, char *argv[]) 424 { 425 struct template *t, *templates = NULL; 426 struct block *b; 427 struct variable *c, *v; 428 DIR *bdir, *idir; 429 struct dirent *ir, *br; 430 char file[PATH_MAX + 1], contentfile[PATH_MAX + 1], path[PATH_MAX + 1]; 431 char outputfile[PATH_MAX + 1], *p, *filename; 432 size_t i, j, k, templateslen; 433 int argi, r; 434 435 if (pledge("stdio cpath rpath wpath", NULL) == -1) { 436 fprintf(stderr, "pledge: %s\n", strerror(errno)); 437 return 1; 438 } 439 440 for (argi = 1; argi < argc; argi++) { 441 if (argv[argi][0] != '-') 442 break; 443 if (argi + 1 >= argc) 444 usage(argv[0]); 445 switch (argv[argi][1]) { 446 case 'c': configfile = argv[++argi]; break; 447 case 'o': outputdir = argv[++argi]; break; 448 case 't': templatedir = argv[++argi]; break; 449 default: usage(argv[0]); break; 450 } 451 } 452 453 /* global config */ 454 global = readconfig(configfile); 455 456 /* load templates, must start with "header.", "item." or "footer." */ 457 templateslen = 0; 458 if (!(bdir = opendir(templatedir))) { 459 fprintf(stderr, "opendir: %s: %s\n", templatedir, strerror(errno)); 460 exit(1); 461 } 462 463 while ((br = readdir(bdir))) { 464 if (br->d_name[0] == '.') 465 continue; 466 467 r = snprintf(path, sizeof(path), "%s/%s", templatedir, 468 br->d_name); 469 if (r < 0 || (size_t)r >= sizeof(path)) { 470 fprintf(stderr, "path truncated: '%s/%s'\n", 471 templatedir, br->d_name); 472 exit(1); 473 } 474 475 if (!(idir = opendir(path))) { 476 fprintf(stderr, "opendir: %s: %s\n", path, strerror(errno)); 477 exit(1); 478 } 479 480 templateslen++; 481 /* check overflow */ 482 if (SIZE_MAX / templateslen < sizeof(*templates)) { 483 fprintf(stderr, "realloc: too many templates: %zu\n", templateslen); 484 exit(1); 485 } 486 templates = erealloc(templates, templateslen * sizeof(*templates)); 487 t = &templates[templateslen - 1]; 488 memset(t, 0, sizeof(struct template)); 489 t->name = estrdup(br->d_name); 490 491 while ((ir = readdir(idir))) { 492 if (!strncmp(ir->d_name, "header.", sizeof("header.") - 1)) 493 b = &(t->blocks[BlockHeader]); 494 else if (!strncmp(ir->d_name, "item.", sizeof("item.") - 1)) 495 b = &(t->blocks[BlockItem]); 496 else if (!strncmp(ir->d_name, "footer.", sizeof("footer.") - 1)) 497 b = &(t->blocks[BlockFooter]); 498 else 499 continue; 500 501 r = snprintf(file, sizeof(file), "%s/%s", path, 502 ir->d_name); 503 if (r < 0 || (size_t)r >= sizeof(file)) { 504 fprintf(stderr, "path truncated: '%s/%s'\n", 505 path, ir->d_name); 506 exit(1); 507 } 508 b->name = estrdup(file); 509 b->data = readfile(file); 510 } 511 closedir(idir); 512 } 513 closedir(bdir); 514 515 /* open output files for templates and write header, except for "page" */ 516 for (i = 0; i < templateslen; i++) { 517 /* "page" is a special case */ 518 if (!strcmp(templates[i].name, "page")) 519 continue; 520 r = snprintf(file, sizeof(file), "%s/%s", outputdir, 521 templates[i].name); 522 if (r < 0 || (size_t)r >= sizeof(file)) { 523 fprintf(stderr, "path truncated: '%s/%s'\n", outputdir, 524 templates[i].name); 525 exit(1); 526 } 527 templates[i].fp = efopen(file, "wb"); 528 529 /* header */ 530 b = &templates[i].blocks[BlockHeader]; 531 if (b->name) 532 writepage(templates[i].fp, b->name, NULL, NULL, b->data); 533 } 534 535 /* pages */ 536 for (i = argi; i < (size_t)argc; i++) { 537 c = readconfig(argv[i]); 538 539 if ((p = strrchr(argv[i], '.'))) 540 r = snprintf(contentfile, sizeof(contentfile), "%.*s.html", 541 (int)(p - argv[i]), argv[i]); 542 else 543 r = snprintf(contentfile, sizeof(contentfile), "%s.html", argv[i]); 544 if (r < 0 || (size_t)r >= sizeof(contentfile)) { 545 fprintf(stderr, "path truncated for file: '%s'\n", argv[i]); 546 exit(1); 547 } 548 /* set contentfile, but allow to override it */ 549 setvar(&c, newvar("contentfile", contentfile), 0); 550 551 if ((v = getvar(c, "filename"))) { 552 filename = v->value; 553 } else { 554 /* set output filename (with path removed), but allow 555 to override it */ 556 if ((p = strrchr(contentfile, '/'))) 557 filename = &contentfile[p - contentfile + 1]; 558 else 559 filename = contentfile; 560 561 setvar(&c, newvar("filename", filename), 0); 562 } 563 564 /* item blocks */ 565 for (j = 0; j < templateslen; j++) { 566 /* "page" is a special case */ 567 if (!strcmp(templates[j].name, "page")) { 568 r = snprintf(outputfile, sizeof(outputfile), "%s/%s", 569 outputdir, filename); 570 if (r < 0 || (size_t)r >= sizeof(outputfile)) { 571 fprintf(stderr, "path truncated: '%s/%s'\n", 572 outputdir, filename); 573 exit(1); 574 } 575 576 /* "page" template files are opened per item 577 as opposed to other templates */ 578 templates[j].fp = efopen(outputfile, "wb"); 579 for (k = 0; k < LEN(templates[j].blocks); k++) { 580 b = &templates[j].blocks[k]; 581 if (b->name) 582 writepage(templates[j].fp, 583 b->name, argv[i], c, 584 b->data); 585 } 586 fclose(templates[j].fp); 587 } else { 588 b = &templates[j].blocks[BlockItem]; 589 if (b->name) 590 writepage(templates[j].fp, b->name, 591 argv[i], c, b->data); 592 } 593 } 594 freevars(c); 595 } 596 597 /* write footer, except for "page" */ 598 for (i = 0; i < templateslen; i++) { 599 if (!strcmp(templates[i].name, "page")) 600 continue; 601 b = &templates[i].blocks[BlockFooter]; 602 if (b->name) 603 writepage(templates[i].fp, b->name, NULL, NULL, b->data); 604 } 605 606 return 0; 607 }