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