shell.c (18000B)
1 #include <inttypes.h> 2 #include <limits.h> 3 #include <errno.h> 4 #include <stdarg.h> 5 #include <stdbool.h> 6 #include <stdio.h> 7 #include <stdlib.h> 8 #include <string.h> 9 #include <time.h> 10 11 #include "../src/nissy.h" 12 13 #define PRINTCUBE_BUFFER_SIZE UINT64_C(1024) 14 #define SOLUTIONS_BUFFER_SIZE UINT64_C(500000) 15 #define MAX_PATH_LENGTH UINT64_C(10000) 16 17 #define FLAG_CUBE "-cube" 18 #define FLAG_PERM "-perm" 19 #define FLAG_COMMAND "-command" 20 #define FLAG_STR_CUBE "-cubestr" 21 #define FLAG_FORMAT "-format" 22 #define FLAG_FORMAT_IN "-fin" 23 #define FLAG_FORMAT_OUT "-fout" 24 #define FLAG_MOVES "-moves" 25 #define FLAG_TRANS "-trans" 26 #define FLAG_SOLVER "-solver" 27 #define FLAG_NISSTYPE "-nisstype" 28 #define FLAG_MINMOVES "-m" 29 #define FLAG_MAXMOVES "-M" 30 #define FLAG_OPTIMAL "-O" 31 #define FLAG_MAXSOLUTIONS "-n" 32 #define FLAG_THREADS "-t" 33 34 #define INFO_CUBEFORMAT(cube) cube " must be given in B32 format." 35 #define INFO_MOVESFORMAT "The accepted moves are U, D, R, L, F and B, " \ 36 "optionally followed by a 2, a ' or a 3." 37 #define INFO_TRANSFORMAT "The transformation must be given in the format " \ 38 "(rotation|mirrored) (2 letters), for exmple " \ 39 "'rotation UF' or 'mirrored BL'." 40 #define INFO_FORMATS "The available formats are H48, B32 and SRC." 41 42 typedef struct { 43 int command_index; 44 char cube[22]; 45 char cube_perm[22]; 46 char *str_command; 47 char *str_cube; 48 char *str_format; 49 char *str_format_in; 50 char *str_format_out; 51 char *str_moves; 52 char *str_trans; 53 char *str_solver; 54 char *str_nisstype; 55 unsigned minmoves; 56 unsigned maxmoves; 57 unsigned optimal; 58 unsigned maxsolutions; 59 unsigned threads; 60 } args_t; 61 62 static int64_t compose_exec(args_t *); 63 static int64_t inverse_exec(args_t *); 64 static int64_t applymoves_exec(args_t *); 65 static int64_t applytrans_exec(args_t *); 66 static int64_t frommoves_exec(args_t *); 67 static int64_t convert_exec(args_t *); 68 static int64_t randomcube_exec(args_t *); 69 static int64_t solverinfo_exec(args_t *); 70 static int64_t gendata_exec(args_t *); 71 static int64_t solve_exec(args_t *); 72 static int64_t solve_scramble_exec(args_t *); 73 static int64_t countmoves_exec(args_t *); 74 static int64_t help_exec(args_t *); 75 76 static int parse_args(int, char **, args_t *); 77 static bool parse_uint(const char *, unsigned *); 78 static uint8_t parse_nisstype(const char *); 79 80 static bool set_cube(int, char **, args_t *); 81 static bool set_cube_perm(int, char **, args_t *); 82 static bool set_str_command(int, char **, args_t *); 83 static bool set_str_cube(int, char **, args_t *); 84 static bool set_str_format(int, char **, args_t *); 85 static bool set_str_format_in(int, char **, args_t *); 86 static bool set_str_format_out(int, char **, args_t *); 87 static bool set_str_moves(int, char **, args_t *); 88 static bool set_str_trans(int, char **, args_t *); 89 static bool set_str_solver(int, char **, args_t *); 90 static bool set_str_nisstype(int, char **, args_t *); 91 static bool set_minmoves(int, char **, args_t *); 92 static bool set_maxmoves(int, char **, args_t *); 93 static bool set_optimal(int, char **, args_t *); 94 static bool set_maxsolutions(int, char **, args_t *); 95 static bool set_threads(int, char **, args_t *); 96 static bool set_id(int, char **, args_t *); 97 98 static uint64_t rand64(void); 99 100 #define OPTION(N, A, S) { .name = N, .nargs = A, .set = S } 101 struct { 102 char *name; 103 int nargs; 104 bool (*set)(int, char **, args_t *); 105 } options[] = { 106 OPTION(FLAG_CUBE, 1, set_cube), 107 OPTION(FLAG_PERM, 1, set_cube_perm), 108 OPTION(FLAG_COMMAND, 1, set_str_command), 109 OPTION(FLAG_STR_CUBE, 1, set_str_cube), 110 OPTION(FLAG_FORMAT, 1, set_str_format), 111 OPTION(FLAG_FORMAT_IN, 1, set_str_format_in), 112 OPTION(FLAG_FORMAT_OUT, 1, set_str_format_out), 113 OPTION(FLAG_MOVES, 1, set_str_moves), 114 OPTION(FLAG_TRANS, 1, set_str_trans), 115 OPTION(FLAG_SOLVER, 1, set_str_solver), 116 OPTION(FLAG_NISSTYPE, 1, set_str_nisstype), 117 OPTION(FLAG_MINMOVES, 1, set_minmoves), 118 OPTION(FLAG_MAXMOVES, 1, set_maxmoves), 119 OPTION(FLAG_OPTIMAL, 1, set_optimal), 120 OPTION(FLAG_MAXSOLUTIONS, 1, set_maxsolutions), 121 OPTION(FLAG_THREADS, 1, set_threads), 122 OPTION(NULL, 0, NULL) 123 }; 124 125 #define COMMAND(N, S, D, E) { .name = N, .syn = S, .desc = D, .exec = E } 126 struct { 127 char *name; 128 char *syn; 129 char *desc; 130 int64_t (*exec)(args_t *); 131 } commands[] = { 132 /* TODO: add synopsis and description here */ 133 COMMAND( 134 "compose", 135 "compose " FLAG_CUBE " CUBE " FLAG_PERM " PERM", 136 "Apply on CUBE the permutation defined by PERM. " 137 INFO_CUBEFORMAT("CUBE and PERM"), 138 compose_exec 139 ), 140 COMMAND( 141 "inverse", 142 "inverse " FLAG_CUBE " CUBE ", 143 "Compute the inverse of the given CUBE. " 144 INFO_CUBEFORMAT("CUBE"), 145 inverse_exec 146 ), 147 COMMAND( 148 "applymoves", 149 "applymoves " FLAG_CUBE " CUBE " FLAG_MOVES " MOVES", 150 "Apply the given MOVES to the given CUBE. " 151 INFO_CUBEFORMAT("CUBE") " " INFO_MOVESFORMAT, 152 applymoves_exec 153 ), 154 COMMAND( 155 "applytrans", 156 "applytrans " FLAG_CUBE " CUBE " FLAG_TRANS " TRANS", 157 "Apply the single transformation TRANS to the given CUBE. " 158 INFO_CUBEFORMAT("CUBE") " " INFO_TRANSFORMAT, 159 applytrans_exec 160 ), 161 COMMAND( 162 "frommoves", 163 "frommoves " FLAG_MOVES " MOVES", 164 "Return the cube obtained by applying the given MOVES " 165 "to a solved cube. " INFO_MOVESFORMAT, 166 frommoves_exec 167 ), 168 COMMAND( 169 "convert", 170 "convert " FLAG_STR_CUBE " CUBESTR " 171 FLAG_FORMAT_IN " FORMAT_IN " FLAG_FORMAT_OUT " FORMAT_OUT", 172 "Convert the cube described by CUBESTR from FORMAT_IN to " 173 "FORMAT_OUT." 174 INFO_FORMATS " " 175 "CUBESTR must be a valid cube in the FORMAT_IN format.", 176 convert_exec 177 ), 178 COMMAND( 179 "randomcube", 180 "randomcube", 181 "Returns a random cube in B32 format.", 182 randomcube_exec 183 ), 184 COMMAND( 185 "solverinfo", 186 "solverinfo " FLAG_SOLVER " SOLVER", 187 "Return the size in bytes and the id of the data table " 188 "used by SOLVER when called with the given OPTIONS.", 189 solverinfo_exec 190 ), 191 COMMAND( 192 "gendata", 193 "gendata " FLAG_SOLVER " SOLVER", 194 "Generate the data table used by " 195 "SOLVER when called with the given OPTIONS.", 196 gendata_exec 197 ), 198 COMMAND( 199 "solve", 200 "solve " FLAG_SOLVER " SOLVER" 201 "[" FLAG_MINMOVES " n] [" FLAG_MAXMOVES " N] " 202 FLAG_CUBE " CUBE" 203 FLAG_THREADS " T", 204 "Solve the given CUBE using SOLVER, " 205 "using at least n and at most N moves, and T threads. " 206 INFO_CUBEFORMAT("CUBE"), 207 solve_exec 208 ), 209 COMMAND( 210 "solve_scramble", 211 "solve_scramble " FLAG_SOLVER " SOLVER" 212 "[" FLAG_MINMOVES " n] [" FLAG_MAXMOVES " N] " 213 FLAG_MOVES " MOVES", 214 "Solve the given SCRAMBLE using SOLVER, " 215 "using at least n and at most N moves. " 216 INFO_MOVESFORMAT, 217 solve_scramble_exec 218 ), 219 COMMAND( 220 "countmoves", 221 "countmoves " FLAG_MOVES " MOVES", 222 "Count the given MOVES in HTM. " 223 INFO_MOVESFORMAT, 224 countmoves_exec 225 ), 226 COMMAND( 227 "help", 228 "help [" FLAG_COMMAND " COMMAND]", 229 "If no COMMAND is specified, prints some generic information " 230 "and the list of commands. Otherwise it prints detailed " 231 "information about the specified COMMAND.", 232 help_exec 233 ), 234 COMMAND(NULL, NULL, NULL, NULL) 235 }; 236 237 char *tablepaths[] = { 238 "tables/", 239 "", 240 NULL 241 }; 242 243 static uint64_t 244 rand64(void) 245 { 246 uint64_t i, ret; 247 248 for (i = 0, ret = 0; i < 64; i++) 249 ret |= (uint64_t)(rand() % 2) << i; 250 251 return ret; 252 } 253 254 static int64_t 255 compose_exec(args_t *args) 256 { 257 char result[22]; 258 int64_t ret; 259 260 ret = nissy_compose(args->cube, args->cube_perm, result); 261 if (ret == NISSY_OK || ret == NISSY_WARNING_UNSOLVABLE) 262 printf("%s\n", result); 263 264 return ret; 265 } 266 267 static int64_t 268 inverse_exec(args_t *args) 269 { 270 char result[22]; 271 int64_t ret; 272 273 ret = nissy_inverse(args->cube, result); 274 if (ret == NISSY_OK || ret == NISSY_WARNING_UNSOLVABLE) 275 printf("%s\n", result); 276 277 return ret; 278 } 279 280 static int64_t 281 applymoves_exec(args_t *args) 282 { 283 char result[22]; 284 int64_t ret; 285 286 ret = nissy_applymoves(args->cube, args->str_moves, result); 287 if (ret == NISSY_OK || ret == NISSY_WARNING_UNSOLVABLE) 288 printf("%s\n", result); 289 290 return ret; 291 } 292 293 static int64_t 294 applytrans_exec(args_t *args) 295 { 296 char result[22]; 297 int64_t ret; 298 299 ret = nissy_applytrans(args->cube, args->str_trans, result); 300 if (ret == NISSY_OK || ret == NISSY_WARNING_UNSOLVABLE) 301 printf("%s\n", result); 302 303 return ret; 304 } 305 306 static int64_t 307 frommoves_exec(args_t *args) 308 { 309 sprintf(args->cube, NISSY_SOLVED_CUBE); 310 return applymoves_exec(args); 311 } 312 313 static int64_t 314 convert_exec(args_t *args) 315 { 316 char result[PRINTCUBE_BUFFER_SIZE]; 317 int64_t ret; 318 319 ret = nissy_convert(args->str_format_in, args->str_format_out, 320 args->str_cube, PRINTCUBE_BUFFER_SIZE, result); 321 if (ret == NISSY_OK || ret == NISSY_WARNING_UNSOLVABLE) 322 printf("%s\n", result); 323 324 return ret; 325 } 326 327 static int64_t 328 randomcube_exec(args_t *args) 329 { 330 char result[PRINTCUBE_BUFFER_SIZE]; 331 int64_t ret, ep, eo, cp, co; 332 333 ep = rand64(); 334 eo = rand64(); 335 cp = rand64(); 336 co = rand64(); 337 ret = nissy_getcube(ep, eo, cp, co, "fix", result); 338 if (ret == NISSY_OK || ret == NISSY_WARNING_UNSOLVABLE) 339 printf("%s\n", result); 340 341 return ret; 342 } 343 344 static int64_t 345 solverinfo_exec(args_t *args) 346 { 347 int64_t ret; 348 char buf[NISSY_SIZE_DATAID]; 349 350 ret = nissy_solverinfo(args->str_solver, buf); 351 if (ret < 0) 352 fprintf(stderr, "Unknown error (make sure solver is valid)\n"); 353 printf("%" PRId64 "\n%s\n", ret, buf); 354 355 return ret; 356 } 357 358 static int64_t 359 gendata_exec(args_t *args) 360 { 361 int i; 362 FILE *file; 363 char path[MAX_PATH_LENGTH], dataid[NISSY_SIZE_DATAID]; 364 unsigned char *buf; 365 int64_t ret, size; 366 size_t written; 367 368 size = nissy_solverinfo(args->str_solver, dataid); 369 370 if (size < 0) { 371 fprintf(stderr, "gendata: unknown solver %s\n", 372 args->str_solver); 373 return -3; 374 } 375 376 /* TODO: should give warning if overwriting existing file */ 377 for (i = 0; tablepaths[i] != NULL; i++) { 378 strcpy(path, tablepaths[i]); 379 strcat(path, dataid); 380 file = fopen(path, "wb"); 381 if (file != NULL) 382 break; 383 } 384 385 if (tablepaths[i] == NULL) { 386 fprintf(stderr, "Cannot write data to file\n"); 387 fclose(file); 388 return -2; 389 } 390 391 buf = malloc(size); 392 393 ret = nissy_gendata(args->str_solver, size, buf); 394 if (ret < 0) { 395 fprintf(stderr, "Unknown error in generating data\n"); 396 fclose(file); 397 free(buf); 398 return -4; 399 } 400 if (ret != size) { 401 fprintf(stderr, "Unknown error: unexpected data size " 402 "got %" PRId64 ", expected %" PRId64 ")\n", ret, size); 403 fclose(file); 404 free(buf); 405 return -5; 406 } 407 408 written = fwrite(buf, size, 1, file); 409 fclose(file); 410 free(buf); 411 412 if (written != 1) { 413 fprintf(stderr, 414 "Error: data was generated correctly, but could not be " 415 "written to file (generated %" PRId64 " bytes, written " 416 "%zu)\n", size, written); 417 return -6; 418 } 419 420 fprintf(stderr, "Data written to %s\n", path); 421 422 return 0; 423 } 424 425 static int64_t 426 solve_exec(args_t *args) 427 { 428 int i; 429 uint8_t nissflag; 430 FILE *file; 431 char solutions[SOLUTIONS_BUFFER_SIZE], path[MAX_PATH_LENGTH]; 432 unsigned char *buf; 433 char dataid[NISSY_SIZE_DATAID]; 434 long long stats[NISSY_SIZE_SOLVE_STATS]; 435 int64_t ret, gendata_ret, size; 436 size_t read; 437 438 nissflag = parse_nisstype(args->str_nisstype); 439 if (nissflag == UINT8_MAX) { 440 fprintf(stderr, "solve: unknown niss type '%s', use one " 441 "of the following:\nnormal\ninverse\nlinear\nmixed\nall", 442 args->str_nisstype); 443 return -1; 444 } 445 446 size = nissy_solverinfo(args->str_solver, dataid); 447 448 if (size < 0) { 449 fprintf(stderr, "solve: unknown solver '%s'\n", 450 args->str_solver); 451 return size; 452 } 453 454 for (i = 0; tablepaths[i] != NULL; i++) { 455 strcpy(path, tablepaths[i]); 456 strcat(path, dataid); 457 file = fopen(path, "rb"); 458 if (file != NULL) 459 break; 460 } 461 462 if (tablepaths[i] == NULL) { 463 fprintf(stderr, 464 "Cannot read data file, " 465 "generating it (this can take a while)\n"); 466 gendata_ret = gendata_exec(args); 467 if (gendata_ret) 468 return gendata_ret; 469 } 470 471 /* Ugh, this is not elegant TODO */ 472 if (file == NULL) { 473 for (i = 0; tablepaths[i] != NULL; i++) { 474 strcpy(path, tablepaths[i]); 475 strcat(path, dataid); 476 file = fopen(path, "rb"); 477 if (file != NULL) 478 break; 479 } 480 } 481 482 if (tablepaths[i] == NULL) { 483 fprintf(stderr, "Error: data file not found\n"); 484 fclose(file); 485 return -1; 486 } 487 488 if (args->maxsolutions == 0) 489 args->maxsolutions = args->optimal == 20 ? 1 : UINT_MAX; 490 491 buf = malloc(size); 492 read = fread(buf, size, 1, file); 493 fclose(file); 494 if (read != 1) { 495 fprintf(stderr, "Error reading data from file: " 496 "fread() returned %zu instead of 1 when attempting to " 497 "read %" PRId64 " bytes from file %s\n", read, size, path); 498 goto solve_exec_error; 499 } 500 501 ret = nissy_solve( 502 args->cube, args->str_solver, nissflag, args->minmoves, 503 args->maxmoves, args->maxsolutions, args->optimal, args->threads, 504 size, buf, SOLUTIONS_BUFFER_SIZE, solutions, stats); 505 506 free(buf); 507 508 if (ret == NISSY_OK || ret == NISSY_WARNING_UNSOLVABLE) 509 fprintf(stderr, "No solutions found\n"); 510 else 511 printf("%s", solutions); 512 513 return 0; 514 515 solve_exec_error: 516 free(buf); 517 return -2; 518 } 519 520 static int64_t 521 solve_scramble_exec(args_t *args) 522 { 523 nissy_applymoves(NISSY_SOLVED_CUBE, args->str_moves, args->cube); 524 525 return solve_exec(args); 526 } 527 528 static int64_t 529 countmoves_exec(args_t *args) 530 { 531 long long count; 532 533 count = nissy_countmoves(args->str_moves); 534 535 if (count >= 0) 536 printf("%lld\n", count); 537 538 return count >= 0 ? 0 : count; 539 } 540 541 static int64_t 542 help_exec(args_t *args) 543 { 544 int i; 545 546 if (args->str_command == NULL || args->str_command[0] == '\0') { 547 printf("This is a rudimentary shell for the H48 library.\n"); 548 printf("Available commands and usage:\n\n"); 549 for (i = 0; commands[i].name != NULL; i++) 550 printf("%-15s%s\n", commands[i].name, commands[i].syn); 551 printf("\nUse 'help -command COMMAND' for more information.\n"); 552 } else { 553 for (i = 0; commands[i].name != NULL; i++) 554 if (!strcmp(args->str_command, commands[i].name)) 555 break; 556 if (commands[i].name == NULL) { 557 printf("Unknown command %s\n", args->str_command); 558 return 1; 559 } 560 printf("Command %s\n\n", commands[i].name); 561 printf("Synopsis: %s\n\n", commands[i].syn); 562 printf("Description: %s\n", commands[i].desc); 563 } 564 565 return 0; 566 } 567 568 static int 569 parse_args(int argc, char **argv, args_t *args) 570 { 571 int i, j, n; 572 573 *args = (args_t) { 574 .command_index = -1, 575 .cube = "", 576 .cube_perm = "", 577 .str_cube = "", 578 .str_format = "", 579 .str_format_in = "", 580 .str_format_out = "", 581 .str_moves = "", 582 .str_trans = "", 583 .str_solver = "", 584 .str_nisstype = "", 585 .minmoves = 0, 586 .maxmoves = 20, 587 .optimal = 20, 588 .maxsolutions = 0, 589 .threads = 0, 590 }; 591 592 if (argc == 0) { 593 printf("No command given\n"); 594 return 1; 595 } 596 597 for (i = 0; commands[i].name != NULL; i++) { 598 if (!strcmp(argv[0], commands[i].name)) { 599 args->command_index = i; 600 break; 601 } 602 } 603 604 if (commands[i].name == NULL) { 605 fprintf(stderr, "Unknown command %s\n", argv[0]); 606 return 1; 607 } 608 609 for (i = 1; i < argc; i++) { 610 for (j = 0; options[j].name != NULL; j++) { 611 n = argc - i - 1; 612 if (strcmp(argv[i], options[j].name)) 613 continue; 614 if (n < options[j].nargs) { 615 fprintf(stderr, 616 "Too few arguments for option %s\n", 617 options[j].name); 618 return 1; 619 } 620 if (!options[j].set(n, argv+i+1, args)) { 621 fprintf(stderr, 622 "Error parsing arguments for option %s\n", 623 options[j].name); 624 return 1; 625 } 626 i += options[j].nargs; 627 break; 628 } 629 if (options[j].name == NULL) { 630 fprintf(stderr, "Unknown option %s\n", argv[i]); 631 return 1; 632 } 633 } 634 635 return 0; 636 } 637 638 static bool 639 parse_uint(const char *argv, unsigned *result) 640 { 641 *result = strtol(argv, NULL, 10); 642 643 /* TODO: figure out how errno works and use it */ 644 return true; 645 } 646 647 static uint8_t 648 parse_nisstype(const char *arg) 649 { 650 if (!strcmp("normal", arg) || !strcmp("", arg)) 651 return NISSY_NISSFLAG_NORMAL; 652 653 if (!strcmp("inverse", arg)) 654 return NISSY_NISSFLAG_INVERSE; 655 656 if (!strcmp("linear", arg)) 657 return NISSY_NISSFLAG_LINEAR; 658 659 if (!strcmp("mixed", arg)) 660 return NISSY_NISSFLAG_MIXED; 661 662 if (!strcmp("all", arg)) 663 return NISSY_NISSFLAG_ALL; 664 665 return UINT8_MAX; 666 } 667 668 static bool 669 set_cube(int argc, char **argv, args_t *args) 670 { 671 memcpy(args->cube, argv[0], 22); 672 args->cube[21] = 0; 673 674 return true; 675 } 676 677 static bool 678 set_cube_perm(int argc, char **argv, args_t *args) 679 { 680 memcpy(args->cube_perm, argv[0], 22); 681 args->cube_perm[21] = 0; 682 683 return true; 684 } 685 686 static bool 687 set_str_command(int argc, char **argv, args_t *args) 688 { 689 args->str_command = argv[0]; 690 691 return true; 692 } 693 694 static bool 695 set_str_cube(int argc, char **argv, args_t *args) 696 { 697 args->str_cube = argv[0]; 698 699 return true; 700 } 701 702 static bool 703 set_str_format(int argc, char **argv, args_t *args) 704 { 705 args->str_format = argv[0]; 706 707 return true; 708 } 709 710 static bool 711 set_str_format_in(int argc, char **argv, args_t *args) 712 { 713 args->str_format_in = argv[0]; 714 715 return true; 716 } 717 718 static bool 719 set_str_format_out(int argc, char **argv, args_t *args) 720 { 721 args->str_format_out = argv[0]; 722 723 return true; 724 } 725 726 static bool 727 set_str_moves(int argc, char **argv, args_t *args) 728 { 729 args->str_moves = argv[0]; 730 731 return true; 732 } 733 734 static bool 735 set_str_trans(int argc, char **argv, args_t *args) 736 { 737 args->str_trans = argv[0]; 738 739 return true; 740 } 741 742 static bool 743 set_str_solver(int argc, char **argv, args_t *args) 744 { 745 args->str_solver = argv[0]; 746 747 return true; 748 } 749 750 static bool 751 set_str_nisstype(int argc, char **argv, args_t *args) 752 { 753 args->str_nisstype = argv[0]; 754 755 return true; 756 } 757 758 static bool 759 set_minmoves(int argc, char **argv, args_t *args) 760 { 761 return parse_uint(argv[0], &args->minmoves); 762 } 763 764 static bool 765 set_maxmoves(int argc, char **argv, args_t *args) 766 { 767 return parse_uint(argv[0], &args->maxmoves); 768 } 769 770 static bool 771 set_optimal(int argc, char **argv, args_t *args) 772 { 773 return parse_uint(argv[0], &args->optimal); 774 } 775 776 static bool 777 set_maxsolutions(int argc, char **argv, args_t *args) 778 { 779 return parse_uint(argv[0], &args->maxsolutions); 780 } 781 782 static bool 783 set_threads(int argc, char **argv, args_t *args) 784 { 785 return parse_uint(argv[0], &args->threads); 786 } 787 788 void 789 log_stderr(const char *str, void *unused) 790 { 791 fprintf(stderr, "%s", str); 792 } 793 794 int 795 main(int argc, char **argv) 796 { 797 int parse_error; 798 args_t args; 799 800 srand(time(NULL)); 801 nissy_setlogger(log_stderr, NULL); 802 803 parse_error = parse_args(argc-1, argv+1, &args); 804 if (parse_error) 805 return parse_error; 806 807 return (int)commands[args.command_index].exec(&args); 808 }