tyarn.c (18025B)
1 /* tyarn: minimal yarn implementation 2 * 3 * Copyright © 2019 - 2021 Richard Ipsum 4 * 5 * This program is free software: you can redistribute it and/or modify 6 * it under the terms of the GNU General Public License as published by 7 * the Free Software Foundation, version 3 of the License. 8 * 9 * This program is distributed in the hope that it will be useful, 10 * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 * GNU General Public License for more details. 13 * 14 * You should have received a copy of the GNU General Public License 15 * along with this program. If not, see <http://www.gnu.org/licenses/>. 16 * 17 */ 18 19 /* This program is dedicated to Terry A Davis, 20 * the smartest programmer that ever lived, 21 * may he rest in peace. 22 */ 23 24 #ifdef __linux__ 25 #define _XOPEN_SOURCE 500 26 #endif 27 28 #define _POSIX_C_SOURCE 200809L 29 30 #include <lua.h> 31 #include <lauxlib.h> 32 33 #include <stdio.h> 34 #include <stdlib.h> 35 #include <stdbool.h> 36 #include <string.h> 37 #include <sys/wait.h> 38 #include <unistd.h> 39 #include <errno.h> 40 #include <string.h> 41 #include <ftw.h> 42 #include <sys/types.h> 43 #include <sys/select.h> 44 #include <regex.h> 45 46 size_t strlcpy(char *, const char *, size_t); 47 48 struct pipe_state { 49 int fd; 50 char *p; 51 bool eof; 52 char buf[1 * 1024 * 1024]; 53 }; 54 55 static void *xmalloc(size_t size) 56 { 57 void *p = malloc(size); 58 if (p == NULL) { 59 fprintf(stderr, "out of memory\n"); 60 exit(1); 61 } 62 return p; 63 } 64 65 static char *xstrdup(const char *s) 66 { 67 char *t = strdup(s); 68 if (t == NULL) { 69 fprintf(stderr, "out of memory\n"); 70 exit(1); 71 } 72 return t; 73 } 74 75 /* concepts stolen from 76 * https://stackoverflow.com/questions/6137684/iterate-through-lua-table 77 */ 78 static const char **arr_from_lua_str_table(lua_State *L, int index, int *len_out) 79 { 80 int n = 0; 81 const char **arr = xmalloc(1 * sizeof (char *)); 82 const char **p; 83 84 if (arr == NULL) { 85 return NULL; 86 } 87 88 lua_pushvalue(L, index); 89 lua_pushnil(L); 90 91 while (lua_next(L, -2)) { 92 lua_pushvalue(L, -2); 93 94 lua_tostring(L, -1); 95 const char *value = lua_tostring(L, -2); 96 97 arr[n++] = value; 98 99 p = realloc(arr, (n + 1) * sizeof (char *)); 100 if (p == NULL) { 101 free(arr); 102 return NULL; 103 } 104 105 arr = p; 106 lua_pop(L, 2); 107 } 108 109 lua_pop(L, 1); 110 111 arr[n] = NULL; 112 *len_out = n; 113 114 return arr; 115 } 116 117 static int tyarn_path_exists(lua_State *L) 118 { 119 struct stat s; 120 const char *path = luaL_checkstring(L, 1); 121 122 if (stat(path, &s) == -1) { 123 if (errno != ENOENT) { 124 fprintf(stderr, "stat `%s': %s\n", path, strerror(errno)); 125 exit(1); 126 } 127 lua_pushboolean(L, false); /* ENOENT */ 128 return 1; 129 } 130 131 lua_pushboolean(L, true); 132 return 1; 133 } 134 135 static int tyarn_mkdtemp(lua_State *L) 136 { 137 const char *path; 138 char *pathbuf; 139 size_t pathbuf_size; 140 141 path = luaL_checkstring(L, 1); 142 pathbuf_size = strlen(path) + 1; 143 pathbuf = xmalloc(pathbuf_size); 144 strlcpy(pathbuf, path, pathbuf_size); 145 146 lua_pushstring(L, mkdtemp(pathbuf)); 147 lua_pushinteger(L, errno); 148 free(pathbuf); 149 return 2; 150 } 151 152 static int tyarn_strerror(lua_State *L) 153 { 154 lua_pushstring(L, strerror(luaL_checkinteger(L, -1))); 155 return 1; 156 } 157 158 static void child_fn(const char **args, const char **env, const char *dir) 159 { 160 /* child, do exec */ 161 if (dir != NULL && chdir(dir) == -1) { 162 fprintf(stderr, "chdir: %s\n", strerror(errno)); 163 exit(1); 164 } 165 166 if (execve(args[0], (char * const *) args, (char * const *) env) == -1) { 167 fprintf(stderr, "exeve: %s\n", strerror(errno)); 168 exit(1); 169 } 170 } 171 172 static int parent_fn(struct pipe_state *stdo, struct pipe_state *stde) 173 { 174 fd_set rfds; 175 int maxfds, n, status; 176 struct timeval timeout = {.tv_sec = 1, .tv_usec = 0}; 177 struct pipe_state *readers[2] = {stdo, stde}; 178 char buf[8192]; 179 ssize_t nread; 180 size_t remaining; 181 182 maxfds = readers[0]->fd > readers[1]->fd ? readers[0]->fd : readers[1]->fd; 183 184 for (;;) { 185 FD_ZERO(&rfds); 186 FD_SET(readers[0]->fd, &rfds); 187 FD_SET(readers[1]->fd, &rfds); 188 189 if (readers[0]->eof && readers[1]->eof) { 190 *(readers[0]->p) = '\0'; 191 *(readers[1]->p) = '\0'; 192 if (wait(&status) == -1) { 193 perror("wait"); 194 exit(1); 195 } 196 return status; 197 } 198 199 n = select(maxfds + 1, &rfds, NULL, NULL, &timeout); 200 201 switch (n) { 202 case -1: 203 perror("select"); 204 exit(1); 205 case 0: 206 continue; 207 default: 208 for (size_t i = 0; i < 2; i++) { 209 if (readers[i]->eof || !FD_ISSET(readers[i]->fd, &rfds)) { 210 continue; 211 } 212 213 nread = read(readers[i]->fd, buf, sizeof (buf)); 214 if (nread == -1) { 215 perror("read"); 216 exit(1); 217 } 218 219 if (nread == 0) { 220 readers[i]->eof = true; 221 continue; 222 } 223 224 remaining = (readers[i]->buf + sizeof (readers[i]->buf)) - readers[i]->p; 225 226 if ((size_t) nread >= remaining) { 227 fprintf(stderr, "out of memory\n"); 228 exit(1); 229 } 230 231 memcpy(readers[i]->p, buf, nread); 232 readers[i]->p += nread; 233 } 234 } 235 } 236 } 237 238 static int tyarn_exec(lua_State *L) 239 { 240 int args_len, env_len; 241 int status; 242 const char **args, **env; 243 const char *dir; 244 int stdout_pipefds[2]; 245 int stderr_pipefds[2]; 246 struct pipe_state stdo = {0, .eof = false}; 247 struct pipe_state stde = {0, .eof = false}; 248 stdo.p = stdo.buf; 249 stde.p = stde.buf; 250 251 luaL_checktype(L, 1, LUA_TTABLE); 252 luaL_checktype(L, 2, LUA_TTABLE); 253 dir = luaL_checkstring(L, 3); 254 255 args = arr_from_lua_str_table(L, 1, &args_len); 256 if (args == NULL) { 257 lua_pushinteger(L, -1); 258 lua_pushinteger(L, EINVAL); 259 return 2; 260 } 261 262 env = arr_from_lua_str_table(L, 2, &env_len); 263 if (env == NULL) { 264 lua_pushinteger(L, -1); 265 lua_pushinteger(L, EINVAL); 266 return 2; 267 } 268 269 if (pipe(stdout_pipefds) == -1) { 270 perror("pipe"); 271 exit(1); 272 } 273 274 if (pipe(stderr_pipefds) == -1) { 275 perror("pipe"); 276 exit(1); 277 } 278 279 switch (fork()) { 280 case 0: 281 close(STDOUT_FILENO); 282 if (dup(stdout_pipefds[1]) == -1) { 283 perror("dup"); 284 exit(1); 285 } 286 287 close(STDERR_FILENO); 288 if (dup(stderr_pipefds[1]) == -1) { 289 perror("dup"); 290 exit(1); 291 } 292 293 /* close read ends */ 294 close(stdout_pipefds[0]); 295 close(stderr_pipefds[0]); 296 297 /* close left over descriptors 298 * no longer needed now they've been dup'd to stdout and stderr 299 */ 300 close(stdout_pipefds[1]); 301 close(stderr_pipefds[1]); 302 303 /* child, do the work */ 304 child_fn(args, env, dir); 305 break; 306 case -1: 307 perror("fork"); 308 exit(1); 309 default: 310 close(stdout_pipefds[1]); 311 close(stderr_pipefds[1]); 312 stdo.fd = stdout_pipefds[0]; 313 stde.fd = stderr_pipefds[0]; 314 315 /* parent, wait for child to complete */ 316 status = parent_fn(&stdo, &stde); 317 318 close(stdo.fd); 319 close(stde.fd); 320 321 lua_pushinteger(L, 0); 322 lua_pushinteger(L, WEXITSTATUS(status)); 323 lua_pushstring(L, stdo.buf); 324 lua_pushstring(L, stde.buf); 325 return 4; 326 } 327 328 return 0; 329 } 330 331 static int remove_entry(const char *name, const struct stat *s, int flag, struct FTW *f) 332 { 333 (void) flag; 334 (void) f; 335 336 if (S_ISDIR(s->st_mode)) { 337 if (rmdir(name) == -1) { 338 fprintf(stderr, "rmdir: %s\n", strerror(errno)); 339 return 1; 340 } 341 342 return 0; 343 } 344 345 if (unlink(name) == -1) { 346 fprintf(stderr, "unlink: %s\n", strerror(errno)); 347 return 1; 348 } 349 350 return 0; 351 } 352 353 static int tyarn_rmutil(lua_State *L) 354 { 355 const char *dir = luaL_checkstring(L, -1); 356 357 if (nftw(dir, remove_entry, 100, FTW_DEPTH | FTW_MOUNT | FTW_PHYS) == -1) { 358 fprintf(stderr, "Error traversing %s\n", dir); 359 exit(1); 360 } 361 362 return 0; 363 } 364 365 static void usage(void) 366 { 367 fprintf(stderr, "usage: tyarn [-12CdEehlstv] scenarios implementation...\n"); 368 } 369 370 static int tyarn_usage(lua_State *L) 371 { 372 (void) L; 373 374 usage(); 375 return 0; 376 } 377 378 static void help(void) 379 { 380 usage(); 381 382 const char *s = "\nOptions:\n" 383 " -C Don't cleanup temporary files and directories\n" 384 " -d Enable debug\n" 385 " -E Exit early (exit when first scenario fails)\n" 386 " -e NAME=VALUE\n" 387 " Add an environment variable to the test's environment\n" 388 " (this argument can be repeated to add multiple variables)\n" 389 " -h This help message\n" 390 " -l SHELL_LIBRARY\n" 391 " -1 Show any messages printed to stdout during execution of scenario steps\n" 392 " -2 Show any messages printed to stderr during execution of scenario steps\n" 393 " Shell library to be used by scenario implementations\n" 394 " -s SHELL Execute scenario implementations using SHELL\n" 395 " -t DIR\n" 396 " Use DIR as temporary directory for tests\n" 397 " DIR is a template e.g. /tmp/foo.XXXXXXXX\n" 398 " There must be at least six trailing Xs\n" 399 " -v Verbose\n"; 400 401 fprintf(stderr, "%s", s); 402 } 403 404 static int tyarn_help(lua_State *L) 405 { 406 (void) L; 407 408 help(); 409 return 0; 410 } 411 412 static int tyarn_parse_args(lua_State *L) 413 { 414 const char **args; 415 int args_len; 416 int c = 42; 417 char *shell_lib = NULL; 418 char **buf; 419 bool debug = false; 420 bool no_cleanup = false; 421 bool exit_early = false; 422 bool help = false; 423 int verbose = 0; 424 char *tmpdir = NULL; 425 char *shell = NULL; 426 char *envopts[8192]; 427 size_t envoptsn = 0; 428 bool show_stdout = false; 429 bool show_stderr = false; 430 int bufn = 0; 431 432 luaL_checktype(L, 1, LUA_TTABLE); 433 434 args = arr_from_lua_str_table(L, 1, &args_len); 435 if (args == NULL) { 436 lua_pushinteger(L, -1); 437 lua_pushinteger(L, EINVAL); 438 return 2; 439 } 440 441 buf = xmalloc(args_len * sizeof (char *)); 442 buf[bufn++] = xstrdup("tyarn"); 443 444 for (int i = 0; i < args_len - 2; i++) { 445 buf[bufn++] = xstrdup(args[i]); 446 } 447 448 while ((c = getopt(bufn, buf, ":12l:s:dECht:e:v")) != -1) { 449 switch (c) { 450 case '1': 451 show_stdout = true; 452 break; 453 case '2': 454 show_stderr = true; 455 break; 456 case 'C': 457 no_cleanup = true; 458 break; 459 case 'E': 460 exit_early = true; 461 break; 462 case 'e': 463 envopts[envoptsn++] = optarg; 464 break; 465 case 'l': 466 shell_lib = optarg; 467 break; 468 case 's': 469 shell = optarg; 470 break; 471 case 'd': 472 debug = true; 473 break; 474 case 'h': 475 help = true; 476 break; 477 case 'v': 478 verbose++; 479 break; 480 case 't': 481 tmpdir = optarg; 482 break; 483 case ':': 484 fprintf(stderr, "%s: missing option\n", buf[0]); 485 exit(1); 486 default: 487 usage(); 488 exit(1); 489 } 490 } 491 492 lua_createtable(L, bufn - optind, optind); 493 lua_pushstring(L, shell_lib); 494 lua_setfield(L, -2, "shell_lib"); 495 lua_pushboolean(L, debug); 496 lua_setfield(L, -2, "debug"); 497 lua_pushboolean(L, no_cleanup); 498 lua_setfield(L, -2, "no_cleanup"); 499 lua_pushboolean(L, exit_early); 500 lua_setfield(L, -2, "exit_early"); 501 lua_pushboolean(L, help); 502 lua_setfield(L, -2, "help"); 503 lua_pushstring(L, tmpdir); 504 lua_setfield(L, -2, "tmpdir"); 505 lua_pushboolean(L, verbose); 506 lua_setfield(L, -2, "verbose"); 507 lua_pushstring(L, shell); 508 lua_setfield(L, -2, "shell"); 509 lua_pushboolean(L, show_stdout); 510 lua_setfield(L, -2, "show_stdout"); 511 lua_pushboolean(L, show_stderr); 512 lua_setfield(L, -2, "show_stderr"); 513 514 int tn = 1; 515 for (int i = optind; i < bufn; i++) { 516 lua_pushstring(L, buf[i]); 517 lua_rawseti(L, -2, tn++); 518 } 519 520 lua_createtable(L, 0, envoptsn); 521 522 for (size_t i = 0; i < envoptsn; i++) { 523 char *p = strchr(envopts[i], '='); 524 if (p == NULL) { 525 fprintf(stderr, "%s: invalid argument to --env: %s\n", 526 buf[0], envopts[i]); 527 exit(1); 528 } 529 char *key = envopts[i]; 530 char *value = p + 1; 531 *p = '\0'; 532 lua_pushstring(L, value); 533 lua_setfield(L, -2, key); 534 } 535 536 for (int i = 0; i < bufn; i++) { 537 free(buf[i]); 538 } 539 free(buf); 540 return 2; 541 } 542 543 static int tyarn_unlink(lua_State *L) 544 { 545 lua_pushinteger(L, unlink(luaL_checkstring(L, 1))); 546 lua_pushinteger(L, errno); 547 return 2; 548 } 549 550 static int tyarn_mkstemp(lua_State *L) 551 { 552 const char *path; 553 char *template; 554 int fd; 555 size_t template_size; 556 557 path = luaL_checkstring(L, 1); 558 template_size = strlen(path) + 1; 559 template = xmalloc(template_size); 560 strlcpy(template, path, template_size); 561 fd = mkstemp(template); 562 563 if (fd == -1) { 564 lua_pushnil(L); 565 lua_pushstring(L, strerror(errno)); 566 free(template); 567 return 2; 568 } 569 570 close(fd); 571 /* this is super lame, a nicer way would be to make a luaStream, 572 * http://lua-users.org/lists/lua-l/2014-11/msg00120.html, not supported in 5.1 though 573 */ 574 575 lua_pushstring(L, template); 576 free(template); 577 return 1; 578 } 579 580 static char *str_from_offs(const char *orig, regoff_t start, regoff_t end) 581 { 582 char *p, *buf, *newstr; 583 size_t bufsiz; 584 585 bufsiz = strlen(orig) + 1; 586 buf = xmalloc(bufsiz); 587 strlcpy(buf, orig, bufsiz); 588 589 p = buf + start; 590 buf[end] = '\0'; 591 592 newstr = xstrdup(p); 593 free(buf); 594 return newstr; 595 } 596 597 static int tyarn_rematch(lua_State *L) 598 { 599 const char *string = luaL_checkstring(L, 1); 600 const char *pattern = luaL_checkstring(L, 2); 601 int ret; 602 regex_t r = {.re_nsub = 0}; 603 regmatch_t pmatch[100]; 604 char errstr[8192]; 605 606 ret = regcomp(&r, pattern, REG_EXTENDED); 607 if (ret != 0) { 608 regerror(ret, &r, errstr, sizeof (errstr)); 609 lua_pushnil(L); 610 lua_pushstring(L, errstr); 611 return 2; 612 } 613 614 ret = regexec(&r, string, 100, pmatch, 0); 615 if (ret != 0) { 616 regerror(ret, &r, errstr, sizeof (errstr)); 617 lua_pushnil(L); 618 lua_pushstring(L, errstr); 619 return 2; 620 } 621 622 lua_pushinteger(L, 1); 623 624 lua_createtable(L, r.re_nsub, 0); 625 int tn = 1; 626 for (size_t i = 1; i <= r.re_nsub; i++) { 627 char *s = str_from_offs(string, pmatch[i].rm_so, pmatch[i].rm_eo); 628 lua_pushstring(L, s); 629 lua_rawseti(L, -2, tn++); 630 free(s); 631 } 632 633 return 2; 634 } 635 636 static int tyarn_refind(lua_State *L) 637 { 638 const char *string = luaL_checkstring(L, 1); 639 const char *pattern = luaL_checkstring(L, 2); 640 int ret; 641 regex_t r = {.re_nsub = 0}; 642 regmatch_t pmatch[100]; 643 char errstr[8192]; 644 const char *p = string; 645 bool match = false; 646 647 ret = regcomp(&r, pattern, REG_EXTENDED); 648 if (ret != 0) { 649 regerror(ret, &r, errstr, sizeof (errstr)); 650 lua_pushnil(L); 651 lua_pushstring(L, errstr); 652 return 2; 653 } 654 655 while (*p != '\0') { 656 ret = regexec(&r, p, 100, pmatch, 0); 657 658 switch (ret) { 659 case REG_NOMATCH: 660 p++; 661 continue; 662 case 0: 663 match = true; 664 goto out; 665 default: 666 regerror(ret, &r, errstr, sizeof (errstr)); 667 lua_pushnil(L); 668 lua_pushstring(L, errstr); 669 return 2; 670 } 671 } 672 673 out: 674 if (!match) { 675 lua_pushnil(L); 676 lua_pushnil(L); 677 return 2; 678 } 679 680 lua_pushinteger(L, 1); 681 lua_createtable(L, r.re_nsub, 0); 682 int tn = 1; 683 for (size_t i = 1; i <= r.re_nsub; i++) { 684 char *s = str_from_offs(p, pmatch[i].rm_so, pmatch[i].rm_eo); 685 lua_pushstring(L, s); 686 lua_rawseti(L, -2, tn++); 687 free(s); 688 } 689 690 return 2; 691 } 692 693 static int tyarn_mkdir(lua_State *L) 694 { 695 const char *path = luaL_checkstring(L, 1); 696 697 lua_pushinteger(L, mkdir(path, S_IRWXU | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH)); 698 lua_pushstring(L, strerror(errno)); 699 700 return 2; 701 } 702 703 static int tyarn_getcwd(lua_State *L) 704 { 705 char *path = getcwd(NULL, 0); 706 707 if (path == NULL) { 708 lua_pushnil(L); 709 lua_pushstring(L, strerror(errno)); 710 return 2; 711 } 712 713 lua_pushstring(L, path); 714 free(path); 715 return 1; 716 } 717 718 static const struct luaL_Reg tyarn_functions[] = { 719 {"exec", tyarn_exec}, 720 {"mkdtemp", tyarn_mkdtemp}, 721 {"strerror", tyarn_strerror}, 722 {"rmutil", tyarn_rmutil}, 723 {"parse_args", tyarn_parse_args}, 724 {"unlink", tyarn_unlink}, 725 {"mkstemp", tyarn_mkstemp}, 726 {"re_match", tyarn_rematch}, 727 {"re_find", tyarn_refind}, 728 {"usage", tyarn_usage}, 729 {"help", tyarn_help}, 730 {"path_exists", tyarn_path_exists}, 731 {"mkdir", tyarn_mkdir}, 732 {"getcwd", tyarn_getcwd}, 733 {NULL, NULL} 734 }; 735 736 int luaopen_tyarn(lua_State *L) 737 { 738 luaL_register(L, "tyarn", tyarn_functions); 739 return 1; 740 } 741