null+****@clear*****
null+****@clear*****
2010年 6月 25日 (金) 14:21:36 JST
Nobuyoshi Nakada 2010-06-25 05:21:36 +0000 (Fri, 25 Jun 2010) New Revision: 2f315b3e348de346d2dcbd6aba5c8fedda768a51 Log: check argv boundary Modified files: lib/str.c test/unit/command/test-option.rb test/unit/lib/ruby/groonga-test-utils.rb Modified: lib/str.c (+18 -11) =================================================================== --- lib/str.c 2010-06-25 06:41:25 +0000 (0bae6e3) +++ lib/str.c 2010-06-25 05:21:36 +0000 (badcb0f) @@ -1735,9 +1735,9 @@ grn_str_tok(const char *str, size_t str_len, char delim, const char **tokbuf, in return tok - tokbuf; } -inline static void +inline static int op_getopt_flag(int *flags, const grn_str_getopt_opt *o, - int argc, char * const argv[], int *i, const char *optvalue) + int argc, char * const argv[], int i, const char *optvalue) { switch (o->op) { case getopt_op_none: @@ -1752,19 +1752,18 @@ op_getopt_flag(int *flags, const grn_str_getopt_opt *o, *flags = o->flag; break; default: - return; + return i; } if (o->arg) { if (optvalue) { *o->arg = (char *)optvalue; + } else if (++i < argc) { + *o->arg = argv[i]; } else { - if (++(*i) < argc) { - *o->arg = argv[*i]; - } else { - /* TODO: error */ - } + return -1; } } + return i; } int @@ -1787,8 +1786,12 @@ grn_str_getopt(int argc, char * const argv[], const grn_str_getopt_opt *opts, for (o = opts; o->opt != '\0' || o->longopt != NULL; o++) { if (o->longopt && strlen(o->longopt) == len && !memcmp(v, o->longopt, len)) { - op_getopt_flag(flags, o, argc, argv, &i, - (*eq == '\0' ? NULL : eq + 1)); + i = op_getopt_flag(flags, o, argc, argv, i, + (*eq == '\0' ? NULL : eq + 1)); + if (i < 0) { + fprintf(stderr, "%s: option '--%s' needs argument.\n", argv[0], o->longopt); + return -1; + } found = 1; break; } @@ -1800,7 +1803,11 @@ grn_str_getopt(int argc, char * const argv[], const grn_str_getopt_opt *opts, found = 0; for (o = opts; o->opt != '\0' || o->longopt != NULL; o++) { if (o->opt && *p == o->opt) { - op_getopt_flag(flags, o, argc, argv, &i, NULL); + i = op_getopt_flag(flags, o, argc, argv, i, NULL); + if (i < 0) { + fprintf(stderr, "%s: option '-%c' needs argument.\n", argv[0], *p); + return -1; + } found = 1; break; } Modified: test/unit/command/test-option.rb (+14 -3) =================================================================== --- test/unit/command/test-option.rb 2010-06-25 06:41:25 +0000 (9127be8) +++ test/unit/command/test-option.rb 2010-06-25 05:21:36 +0000 (8f9edd2) @@ -42,9 +42,20 @@ class OptionTest < Test::Unit::TestCase assert_path_not_exist(pid_file) end - def test_help_option - usage = run_groonga("--help") + def test_help + assert_run_groonga(/\AUsage: groonga \[options\.\.\.\] \[dest\]$/, + "", + [CONFIG_ENV, "--help"]) assert_predicate($?, :success?) - assert_match(/\AUsage: groonga \[options\.\.\.\] \[dest\]$/, usage) + end + + def test_mandatory_argument_missing + usage = 'Usage: groonga \[options\.\.\.\] \[dest\]$' + %w[-e -l -a -p -i -t + --admin-html-path --protocol --log-path + --query-log-path --pid-file --config-path].each do |option| + status = assert_run_groonga("", /: option '#{option}' needs argument\.$/, option) + assert_not_predicate(status, :success?) + end end end Modified: test/unit/lib/ruby/groonga-test-utils.rb (+86 -0) =================================================================== --- test/unit/lib/ruby/groonga-test-utils.rb 2010-06-25 06:41:25 +0000 (e3d6375) +++ test/unit/lib/ruby/groonga-test-utils.rb 2010-06-25 05:21:36 +0000 (8897529) @@ -137,4 +137,90 @@ module GroongaTestUtils string.force_encoding("UTF-8") if string.respond_to?(:force_encoding) string end + + LANG_ENVS = %w"LANG LC_ALL LC_CTYPE" + + def invoke_groonga(args, stdin_data="", capture_stdout=false, capture_stderr=false, opt={}) + @groonga ||= guess_groonga_path + args = [args] unless args.kind_of?(Array) + begin + in_child, in_parent = IO.pipe + out_parent, out_child = IO.pipe if capture_stdout + err_parent, err_child = IO.pipe if capture_stderr + pid = fork do + c = "C" + LANG_ENVS.each {|lc| ENV[lc] = c} + case args.first + when Hash + ENV.update(args.shift) + end + STDIN.reopen(in_child) + in_parent.close + if capture_stdout + STDOUT.reopen(out_child) + out_parent.close + end + if capture_stderr + STDERR.reopen(err_child) + err_parent.close + end + Process.setrlimit(Process::RLIMIT_CORE, 0) rescue nil + exec(@groonga, *args) + end + in_child.close + out_child.close if capture_stdout + err_child.close if capture_stderr + th_stdout = Thread.new { out_parent.read } if capture_stdout + th_stderr = Thread.new { err_parent.read } if capture_stderr + in_parent.write stdin_data.to_str + in_parent.close + if (!capture_stdout || th_stdout.join(10)) && (!capture_stderr || th_stderr.join(10)) + stdout = th_stdout.value if capture_stdout + stderr = th_stderr.value if capture_stderr + else + raise Timeout::Error + end + Process.wait pid + status = $? + ensure + [in_child, in_parent, out_child, out_parent, err_child, err_parent].each do |io| + io.close if io && !io.closed? + end + [th_stdout, th_stderr].each do |th| + (th.kill; th.join) if th + end + end + return stdout, stderr, status + end + + def assert_run_groonga(test_stdout, test_stderr, args, *rest) + argnum = rest.size + 3 + opt = (Hash === rest.last ? rest.pop : {}) + message = (rest.pop if String === rest.last) + if String === rest.last + stdin = rest.pop + else + stdin = opt.delete(:stdin) || "" + end + unless rest.empty? + raise ArgumentError, "wrong number of arguments (#{argnum} for 3)" + end + stdout, stderr, status = invoke_groonga(args, stdin, true, true, opt) + assert_not_predicate(status, :signaled?) + if block_given? + yield(stdout, stderr) + else + if test_stderr.is_a?(Regexp) + assert_match(test_stderr, stderr, message) + else + assert_equal(test_stderr, stderr, message) + end + if test_stdout.is_a?(Regexp) + assert_match(test_stdout, stdout, message) + else + assert_equal(test_stdout, stdout, message) + end + status + end + end end