1 // 2 // Automated Testing Framework (atf) 3 // 4 // Copyright (c) 2008, 2009, 2010 The NetBSD Foundation, Inc. 5 // All rights reserved. 6 // 7 // Redistribution and use in source and binary forms, with or without 8 // modification, are permitted provided that the following conditions 9 // are met: 10 // 1. Redistributions of source code must retain the above copyright 11 // notice, this list of conditions and the following disclaimer. 12 // 2. Redistributions in binary form must reproduce the above copyright 13 // notice, this list of conditions and the following disclaimer in the 14 // documentation and/or other materials provided with the distribution. 15 // 16 // THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND 17 // CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, 18 // INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 19 // MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 20 // IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS BE LIABLE FOR ANY 21 // DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 22 // DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE 23 // GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 24 // INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER 25 // IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR 26 // OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN 27 // IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 // 29 30 extern "C" { 31 #include <sys/types.h> 32 #include <sys/wait.h> 33 34 #include <limits.h> 35 #include <signal.h> 36 } 37 38 #include <cerrno> 39 #include <cstdlib> 40 #include <cstring> 41 #include <fstream> 42 #include <iostream> 43 #include <iterator> 44 #include <list> 45 #include <memory> 46 #include <utility> 47 48 #include "atf-c++/check.hpp" 49 #include "atf-c++/config.hpp" 50 #include "atf-c++/utils.hpp" 51 52 #include "atf-c++/detail/application.hpp" 53 #include "atf-c++/detail/exceptions.hpp" 54 #include "atf-c++/detail/fs.hpp" 55 #include "atf-c++/detail/process.hpp" 56 #include "atf-c++/detail/sanity.hpp" 57 #include "atf-c++/detail/text.hpp" 58 59 // ------------------------------------------------------------------------ 60 // Auxiliary functions. 61 // ------------------------------------------------------------------------ 62 63 namespace { 64 65 enum status_check_t { 66 sc_exit, 67 sc_ignore, 68 sc_signal, 69 }; 70 71 struct status_check { 72 status_check_t type; 73 bool negated; 74 int value; 75 76 status_check(const status_check_t& p_type, const bool p_negated, 77 const int p_value) : 78 type(p_type), 79 negated(p_negated), 80 value(p_value) 81 { 82 } 83 }; 84 85 enum output_check_t { 86 oc_ignore, 87 oc_inline, 88 oc_file, 89 oc_empty, 90 oc_match, 91 oc_save 92 }; 93 94 struct output_check { 95 output_check_t type; 96 bool negated; 97 std::string value; 98 99 output_check(const output_check_t& p_type, const bool p_negated, 100 const std::string& p_value) : 101 type(p_type), 102 negated(p_negated), 103 value(p_value) 104 { 105 } 106 }; 107 108 class temp_file : public std::ostream { 109 std::auto_ptr< atf::fs::path > m_path; 110 int m_fd; 111 112 public: 113 temp_file(const atf::fs::path& p) : 114 std::ostream(NULL), 115 m_fd(-1) 116 { 117 atf::utils::auto_array< char > buf(new char[p.str().length() + 1]); 118 std::strcpy(buf.get(), p.c_str()); 119 120 m_fd = ::mkstemp(buf.get()); 121 if (m_fd == -1) 122 throw atf::system_error("atf_check::temp_file::temp_file(" + 123 p.str() + ")", "mkstemp(3) failed", 124 errno); 125 126 m_path.reset(new atf::fs::path(buf.get())); 127 } 128 129 ~temp_file(void) 130 { 131 close(); 132 try { 133 remove(*m_path); 134 } catch (const atf::system_error&) { 135 // Ignore deletion errors. 136 } 137 } 138 139 const atf::fs::path& 140 get_path(void) const 141 { 142 return *m_path; 143 } 144 145 void 146 write(const std::string& text) 147 { 148 if (::write(m_fd, text.c_str(), text.size()) == -1) 149 throw atf::system_error("atf_check", "write(2) failed", errno); 150 } 151 152 void 153 close(void) 154 { 155 if (m_fd != -1) { 156 flush(); 157 ::close(m_fd); 158 m_fd = -1; 159 } 160 } 161 }; 162 163 } // anonymous namespace 164 165 static int 166 parse_exit_code(const std::string& str) 167 { 168 try { 169 const int value = atf::text::to_type< int >(str); 170 if (value < 0 || value > 255) 171 throw std::runtime_error("Unused reason"); 172 return value; 173 } catch (const std::runtime_error&) { 174 throw atf::application::usage_error("Invalid exit code for -s option; " 175 "must be an integer in range 0-255"); 176 } 177 } 178 179 static struct name_number { 180 const char *name; 181 int signo; 182 } signal_names_to_numbers[] = { 183 { "hup", SIGHUP }, 184 { "int", SIGINT }, 185 { "quit", SIGQUIT }, 186 { "trap", SIGTRAP }, 187 { "abrt", SIGABRT }, 188 { "kill", SIGKILL }, 189 { "segv", SIGSEGV }, 190 { "pipe", SIGPIPE }, 191 { "alrm", SIGALRM }, 192 { "term", SIGTERM }, 193 { "usr1", SIGUSR1 }, 194 { "usr2", SIGUSR2 }, 195 { NULL, INT_MIN }, 196 }; 197 198 static int 199 signal_name_to_number(const std::string& str) 200 { 201 struct name_number* iter = signal_names_to_numbers; 202 int signo = INT_MIN; 203 while (signo == INT_MIN && iter->name != NULL) { 204 if (str == iter->name || str == std::string("sig") + iter->name) 205 signo = iter->signo; 206 else 207 iter++; 208 } 209 return signo; 210 } 211 212 static int 213 parse_signal(const std::string& str) 214 { 215 const int signo = signal_name_to_number(str); 216 if (signo == INT_MIN) { 217 try { 218 return atf::text::to_type< int >(str); 219 } catch (std::runtime_error) { 220 throw atf::application::usage_error("Invalid signal name or number " 221 "in -s option"); 222 } 223 } 224 INV(signo != INT_MIN); 225 return signo; 226 } 227 228 static status_check 229 parse_status_check_arg(const std::string& arg) 230 { 231 const std::string::size_type delimiter = arg.find(':'); 232 bool negated = (arg.compare(0, 4, "not-") == 0); 233 const std::string action_str = arg.substr(0, delimiter); 234 const std::string action = negated ? action_str.substr(4) : action_str; 235 const std::string value_str = ( 236 delimiter == std::string::npos ? "" : arg.substr(delimiter + 1)); 237 int value; 238 239 status_check_t type; 240 if (action == "eq") { 241 // Deprecated; use exit instead. TODO: Remove after 0.10. 242 type = sc_exit; 243 if (negated) 244 throw atf::application::usage_error("Cannot negate eq checker"); 245 negated = false; 246 value = parse_exit_code(value_str); 247 } else if (action == "exit") { 248 type = sc_exit; 249 if (value_str.empty()) 250 value = INT_MIN; 251 else 252 value = parse_exit_code(value_str); 253 } else if (action == "ignore") { 254 if (negated) 255 throw atf::application::usage_error("Cannot negate ignore checker"); 256 type = sc_ignore; 257 value = INT_MIN; 258 } else if (action == "ne") { 259 // Deprecated; use not-exit instead. TODO: Remove after 0.10. 260 type = sc_exit; 261 if (negated) 262 throw atf::application::usage_error("Cannot negate ne checker"); 263 negated = true; 264 value = parse_exit_code(value_str); 265 } else if (action == "signal") { 266 type = sc_signal; 267 if (value_str.empty()) 268 value = INT_MIN; 269 else 270 value = parse_signal(value_str); 271 } else 272 throw atf::application::usage_error("Invalid output checker"); 273 274 return status_check(type, negated, value); 275 } 276 277 static 278 output_check 279 parse_output_check_arg(const std::string& arg) 280 { 281 const std::string::size_type delimiter = arg.find(':'); 282 const bool negated = (arg.compare(0, 4, "not-") == 0); 283 const std::string action_str = arg.substr(0, delimiter); 284 const std::string action = negated ? action_str.substr(4) : action_str; 285 286 output_check_t type; 287 if (action == "empty") 288 type = oc_empty; 289 else if (action == "file") 290 type = oc_file; 291 else if (action == "ignore") { 292 if (negated) 293 throw atf::application::usage_error("Cannot negate ignore checker"); 294 type = oc_ignore; 295 } else if (action == "inline") 296 type = oc_inline; 297 else if (action == "match") 298 type = oc_match; 299 else if (action == "save") { 300 if (negated) 301 throw atf::application::usage_error("Cannot negate save checker"); 302 type = oc_save; 303 } else 304 throw atf::application::usage_error("Invalid output checker"); 305 306 return output_check(type, negated, arg.substr(delimiter + 1)); 307 } 308 309 static 310 std::string 311 flatten_argv(char* const* argv) 312 { 313 std::string cmdline; 314 315 char* const* arg = &argv[0]; 316 while (*arg != NULL) { 317 if (arg != &argv[0]) 318 cmdline += ' '; 319 320 cmdline += *arg; 321 322 arg++; 323 } 324 325 return cmdline; 326 } 327 328 static 329 std::auto_ptr< atf::check::check_result > 330 execute(const char* const* argv) 331 { 332 std::cout << "Executing command [ "; 333 for (int i = 0; argv[i] != NULL; ++i) 334 std::cout << argv[i] << " "; 335 std::cout << "]\n"; 336 337 atf::process::argv_array argva(argv); 338 return atf::check::exec(argva); 339 } 340 341 static 342 std::auto_ptr< atf::check::check_result > 343 execute_with_shell(char* const* argv) 344 { 345 const std::string cmd = flatten_argv(argv); 346 347 const char* sh_argv[4]; 348 sh_argv[0] = atf::config::get("atf_shell").c_str(); 349 sh_argv[1] = "-c"; 350 sh_argv[2] = cmd.c_str(); 351 sh_argv[3] = NULL; 352 return execute(sh_argv); 353 } 354 355 static 356 void 357 cat_file(const atf::fs::path& path) 358 { 359 std::ifstream stream(path.c_str()); 360 if (!stream) 361 throw std::runtime_error("Failed to open " + path.str()); 362 363 stream >> std::noskipws; 364 std::istream_iterator< char > begin(stream), end; 365 std::ostream_iterator< char > out(std::cerr); 366 std::copy(begin, end, out); 367 368 stream.close(); 369 } 370 371 static 372 bool 373 grep_file(const atf::fs::path& path, const std::string& regexp) 374 { 375 std::ifstream stream(path.c_str()); 376 if (!stream) 377 throw std::runtime_error("Failed to open " + path.str()); 378 379 bool found = false; 380 381 std::string line; 382 while (!found && !std::getline(stream, line).fail()) { 383 if (atf::text::match(line, regexp)) 384 found = true; 385 } 386 387 stream.close(); 388 389 return found; 390 } 391 392 static 393 bool 394 file_empty(const atf::fs::path& p) 395 { 396 atf::fs::file_info f(p); 397 398 return (f.get_size() == 0); 399 } 400 401 static bool 402 compare_files(const atf::fs::path& p1, const atf::fs::path& p2) 403 { 404 bool equal = false; 405 406 std::ifstream f1(p1.c_str()); 407 if (!f1) 408 throw std::runtime_error("Failed to open " + p1.str()); 409 410 std::ifstream f2(p2.c_str()); 411 if (!f2) 412 throw std::runtime_error("Failed to open " + p1.str()); 413 414 for (;;) { 415 char buf1[512], buf2[512]; 416 417 f1.read(buf1, sizeof(buf1)); 418 if (f1.bad()) 419 throw std::runtime_error("Failed to read from " + p1.str()); 420 421 f2.read(buf2, sizeof(buf2)); 422 if (f2.bad()) 423 throw std::runtime_error("Failed to read from " + p1.str()); 424 425 if ((f1.gcount() == 0) && (f2.gcount() == 0)) { 426 equal = true; 427 break; 428 } 429 430 if ((f1.gcount() != f2.gcount()) || 431 (std::memcmp(buf1, buf2, f1.gcount()) != 0)) { 432 break; 433 } 434 } 435 436 return equal; 437 } 438 439 static 440 void 441 print_diff(const atf::fs::path& p1, const atf::fs::path& p2) 442 { 443 const atf::process::status s = 444 atf::process::exec(atf::fs::path("diff"), 445 atf::process::argv_array("diff", "-u", p1.c_str(), 446 p2.c_str(), NULL), 447 atf::process::stream_connect(STDOUT_FILENO, 448 STDERR_FILENO), 449 atf::process::stream_inherit()); 450 451 if (!s.exited()) 452 std::cerr << "Failed to run diff(3)\n"; 453 454 if (s.exitstatus() != 1) 455 std::cerr << "Error while running diff(3)\n"; 456 } 457 458 static 459 std::string 460 decode(const std::string& s) 461 { 462 size_t i; 463 std::string res; 464 465 res.reserve(s.length()); 466 467 i = 0; 468 while (i < s.length()) { 469 char c = s[i++]; 470 471 if (c == '\\') { 472 switch (s[i++]) { 473 case 'a': c = '\a'; break; 474 case 'b': c = '\b'; break; 475 case 'c': break; 476 case 'e': c = 033; break; 477 case 'f': c = '\f'; break; 478 case 'n': c = '\n'; break; 479 case 'r': c = '\r'; break; 480 case 't': c = '\t'; break; 481 case 'v': c = '\v'; break; 482 case '\\': break; 483 case '0': 484 { 485 int count = 3; 486 c = 0; 487 while (--count >= 0 && (unsigned)(s[i] - '0') < 8) 488 c = (c << 3) + (s[i++] - '0'); 489 break; 490 } 491 default: 492 --i; 493 break; 494 } 495 } 496 497 res.push_back(c); 498 } 499 500 return res; 501 } 502 503 static 504 bool 505 run_status_check(const status_check& sc, const atf::check::check_result& cr) 506 { 507 bool result; 508 509 if (sc.type == sc_exit) { 510 if (cr.exited() && sc.value != INT_MIN) { 511 const int status = cr.exitcode(); 512 513 if (!sc.negated && sc.value != status) { 514 std::cerr << "Fail: incorrect exit status: " 515 << status << ", expected: " 516 << sc.value << "\n"; 517 result = false; 518 } else if (sc.negated && sc.value == status) { 519 std::cerr << "Fail: incorrect exit status: " 520 << status << ", expected: " 521 << "anything else\n"; 522 result = false; 523 } else 524 result = true; 525 } else if (cr.exited() && sc.value == INT_MIN) { 526 result = true; 527 } else { 528 std::cerr << "Fail: program did not exit cleanly\n"; 529 result = false; 530 } 531 } else if (sc.type == sc_ignore) { 532 result = true; 533 } else if (sc.type == sc_signal) { 534 if (cr.signaled() && sc.value != INT_MIN) { 535 const int status = cr.termsig(); 536 537 if (!sc.negated && sc.value != status) { 538 std::cerr << "Fail: incorrect signal received: " 539 << status << ", expected: " << sc.value << "\n"; 540 result = false; 541 } else if (sc.negated && sc.value == status) { 542 std::cerr << "Fail: incorrect signal received: " 543 << status << ", expected: " 544 << "anything else\n"; 545 result = false; 546 } else 547 result = true; 548 } else if (cr.signaled() && sc.value == INT_MIN) { 549 result = true; 550 } else { 551 std::cerr << "Fail: program did not receive a signal\n"; 552 result = false; 553 } 554 } else { 555 UNREACHABLE; 556 result = false; 557 } 558 559 if (result == false) { 560 std::cerr << "stdout:\n"; 561 cat_file(atf::fs::path(cr.stdout_path())); 562 std::cerr << "\n"; 563 564 std::cerr << "stderr:\n"; 565 cat_file(atf::fs::path(cr.stderr_path())); 566 std::cerr << "\n"; 567 } 568 569 return result; 570 } 571 572 static 573 bool 574 run_status_checks(const std::vector< status_check >& checks, 575 const atf::check::check_result& result) 576 { 577 bool ok = false; 578 579 for (std::vector< status_check >::const_iterator iter = checks.begin(); 580 !ok && iter != checks.end(); iter++) { 581 ok |= run_status_check(*iter, result); 582 } 583 584 return ok; 585 } 586 587 static 588 bool 589 run_output_check(const output_check oc, const atf::fs::path& path, 590 const std::string& stdxxx) 591 { 592 bool result; 593 594 if (oc.type == oc_empty) { 595 const bool is_empty = file_empty(path); 596 if (!oc.negated && !is_empty) { 597 std::cerr << "Fail: " << stdxxx << " not empty\n"; 598 print_diff(atf::fs::path("/dev/null"), path); 599 result = false; 600 } else if (oc.negated && is_empty) { 601 std::cerr << "Fail: " << stdxxx << " is empty\n"; 602 result = false; 603 } else 604 result = true; 605 } else if (oc.type == oc_file) { 606 const bool equals = compare_files(path, atf::fs::path(oc.value)); 607 if (!oc.negated && !equals) { 608 std::cerr << "Fail: " << stdxxx << " does not match golden " 609 "output\n"; 610 print_diff(atf::fs::path(oc.value), path); 611 result = false; 612 } else if (oc.negated && equals) { 613 std::cerr << "Fail: " << stdxxx << " matches golden output\n"; 614 cat_file(atf::fs::path(oc.value)); 615 result = false; 616 } else 617 result = true; 618 } else if (oc.type == oc_ignore) { 619 result = true; 620 } else if (oc.type == oc_inline) { 621 atf::fs::path path2 = atf::fs::path(atf::config::get("atf_workdir")) 622 / "inline.XXXXXX"; 623 temp_file temp(path2); 624 temp.write(decode(oc.value)); 625 temp.close(); 626 627 const bool equals = compare_files(path, temp.get_path()); 628 if (!oc.negated && !equals) { 629 std::cerr << "Fail: " << stdxxx << " does not match expected " 630 "value\n"; 631 print_diff(temp.get_path(), path); 632 result = false; 633 } else if (oc.negated && equals) { 634 std::cerr << "Fail: " << stdxxx << " matches expected value\n"; 635 cat_file(temp.get_path()); 636 result = false; 637 } else 638 result = true; 639 } else if (oc.type == oc_match) { 640 const bool matches = grep_file(path, oc.value); 641 if (!oc.negated && !matches) { 642 std::cerr << "Fail: regexp " + oc.value + " not in " << stdxxx 643 << "\n"; 644 cat_file(path); 645 result = false; 646 } else if (oc.negated && matches) { 647 std::cerr << "Fail: regexp " + oc.value + " is in " << stdxxx 648 << "\n"; 649 cat_file(path); 650 result = false; 651 } else 652 result = true; 653 } else if (oc.type == oc_save) { 654 INV(!oc.negated); 655 std::ifstream ifs(path.c_str(), std::fstream::binary); 656 ifs >> std::noskipws; 657 std::istream_iterator< char > begin(ifs), end; 658 659 std::ofstream ofs(oc.value.c_str(), std::fstream::binary 660 | std::fstream::trunc); 661 std::ostream_iterator <char> obegin(ofs); 662 663 std::copy(begin, end, obegin); 664 result = true; 665 } else { 666 UNREACHABLE; 667 result = false; 668 } 669 670 return result; 671 } 672 673 static 674 bool 675 run_output_checks(const std::vector< output_check >& checks, 676 const atf::fs::path& path, const std::string& stdxxx) 677 { 678 bool ok = true; 679 680 for (std::vector< output_check >::const_iterator iter = checks.begin(); 681 iter != checks.end(); iter++) { 682 ok &= run_output_check(*iter, path, stdxxx); 683 } 684 685 return ok; 686 } 687 688 // ------------------------------------------------------------------------ 689 // The "atf_check" application. 690 // ------------------------------------------------------------------------ 691 692 namespace { 693 694 class atf_check : public atf::application::app { 695 bool m_xflag; 696 697 std::vector< status_check > m_status_checks; 698 std::vector< output_check > m_stdout_checks; 699 std::vector< output_check > m_stderr_checks; 700 701 static const char* m_description; 702 703 bool run_output_checks(const atf::check::check_result&, 704 const std::string&) const; 705 706 std::string specific_args(void) const; 707 options_set specific_options(void) const; 708 void process_option(int, const char*); 709 void process_option_s(const std::string&); 710 711 public: 712 atf_check(void); 713 int main(void); 714 }; 715 716 } // anonymous namespace 717 718 const char* atf_check::m_description = 719 "atf-check executes given command and analyzes its results."; 720 721 atf_check::atf_check(void) : 722 app(m_description, "atf-check(1)", "atf(7)"), 723 m_xflag(false) 724 { 725 } 726 727 bool 728 atf_check::run_output_checks(const atf::check::check_result& r, 729 const std::string& stdxxx) 730 const 731 { 732 if (stdxxx == "stdout") { 733 return ::run_output_checks(m_stdout_checks, 734 atf::fs::path(r.stdout_path()), "stdout"); 735 } else if (stdxxx == "stderr") { 736 return ::run_output_checks(m_stderr_checks, 737 atf::fs::path(r.stderr_path()), "stderr"); 738 } else { 739 UNREACHABLE; 740 return false; 741 } 742 } 743 744 std::string 745 atf_check::specific_args(void) 746 const 747 { 748 return "<command>"; 749 } 750 751 atf_check::options_set 752 atf_check::specific_options(void) 753 const 754 { 755 using atf::application::option; 756 options_set opts; 757 758 opts.insert(option('s', "qual:value", "Handle status. Qualifier " 759 "must be one of: ignore exit:<num> signal:<name|num>")); 760 opts.insert(option('o', "action:arg", "Handle stdout. Action must be " 761 "one of: empty ignore file:<path> inline:<val> match:regexp " 762 "save:<path>")); 763 opts.insert(option('e', "action:arg", "Handle stderr. Action must be " 764 "one of: empty ignore file:<path> inline:<val> match:regexp " 765 "save:<path>")); 766 opts.insert(option('x', "", "Execute command as a shell command")); 767 768 return opts; 769 } 770 771 void 772 atf_check::process_option(int ch, const char* arg) 773 { 774 switch (ch) { 775 case 's': 776 m_status_checks.push_back(parse_status_check_arg(arg)); 777 break; 778 779 case 'o': 780 m_stdout_checks.push_back(parse_output_check_arg(arg)); 781 break; 782 783 case 'e': 784 m_stderr_checks.push_back(parse_output_check_arg(arg)); 785 break; 786 787 case 'x': 788 m_xflag = true; 789 break; 790 791 default: 792 UNREACHABLE; 793 } 794 } 795 796 int 797 atf_check::main(void) 798 { 799 if (m_argc < 1) 800 throw atf::application::usage_error("No command specified"); 801 802 int status = EXIT_FAILURE; 803 804 std::auto_ptr< atf::check::check_result > r = 805 m_xflag ? execute_with_shell(m_argv) : execute(m_argv); 806 807 if (m_status_checks.empty()) 808 m_status_checks.push_back(status_check(sc_exit, false, EXIT_SUCCESS)); 809 else if (m_status_checks.size() > 1) { 810 // TODO: Remove this restriction. 811 throw atf::application::usage_error("Cannot specify -s more than once"); 812 } 813 814 if (m_stdout_checks.empty()) 815 m_stdout_checks.push_back(output_check(oc_empty, false, "")); 816 if (m_stderr_checks.empty()) 817 m_stderr_checks.push_back(output_check(oc_empty, false, "")); 818 819 if ((run_status_checks(m_status_checks, *r) == false) || 820 (run_output_checks(*r, "stderr") == false) || 821 (run_output_checks(*r, "stdout") == false)) 822 status = EXIT_FAILURE; 823 else 824 status = EXIT_SUCCESS; 825 826 return status; 827 } 828 829 int 830 main(int argc, char* const* argv) 831 { 832 return atf_check().run(argc, argv); 833 } 834