1//
2// Automated Testing Framework (atf)
3//
4// Copyright (c) 2007, 2008, 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#if defined(HAVE_CONFIG_H)
31#include "bconfig.h"
32#endif
33
34extern "C" {
35#include <unistd.h>
36}
37
38#include <cstdarg>
39#include <cstdio>
40#include <cstdlib>
41#include <cstring>
42#include <iostream>
43
44#include "application.hpp"
45#include "sanity.hpp"
46#include "ui.hpp"
47
48#if !defined(HAVE_VSNPRINTF_IN_STD)
49namespace std {
50using ::vsnprintf;
51}
52#endif // !defined(HAVE_VSNPRINTF_IN_STD)
53
54namespace impl = atf::application;
55#define IMPL_NAME "atf::application"
56
57// ------------------------------------------------------------------------
58// The "usage_error" class.
59// ------------------------------------------------------------------------
60
61impl::usage_error::usage_error(const char *fmt, ...)
62    throw() :
63    std::runtime_error("usage_error; message unformatted")
64{
65    va_list ap;
66
67    va_start(ap, fmt);
68    std::vsnprintf(m_text, sizeof(m_text), fmt, ap);
69    va_end(ap);
70}
71
72impl::usage_error::~usage_error(void)
73    throw()
74{
75}
76
77const char*
78impl::usage_error::what(void)
79    const throw()
80{
81    return m_text;
82}
83
84// ------------------------------------------------------------------------
85// The "application" class.
86// ------------------------------------------------------------------------
87
88impl::option::option(char ch,
89                     const std::string& a,
90                     const std::string& desc) :
91    m_character(ch),
92    m_argument(a),
93    m_description(desc)
94{
95}
96
97bool
98impl::option::operator<(const impl::option& o)
99    const
100{
101    return m_character < o.m_character;
102}
103
104impl::app::app(const std::string& description,
105               const std::string& manpage,
106               const std::string& global_manpage,
107               const bool use_ui) :
108    m_hflag(false),
109    m_argc(-1),
110    m_argv(NULL),
111    m_prog_name(NULL),
112    m_description(description),
113    m_manpage(manpage),
114    m_global_manpage(global_manpage),
115    m_use_ui(use_ui)
116{
117}
118
119impl::app::~app(void)
120{
121}
122
123bool
124impl::app::inited(void)
125{
126    return m_argc != -1;
127}
128
129impl::app::options_set
130impl::app::options(void)
131{
132    options_set opts = specific_options();
133    if (m_use_ui) {
134        opts.insert(option('h', "", "Shows this help message"));
135    }
136    return opts;
137}
138
139std::string
140impl::app::specific_args(void)
141    const
142{
143    return "";
144}
145
146impl::app::options_set
147impl::app::specific_options(void)
148    const
149{
150    return options_set();
151}
152
153void
154impl::app::process_option(int ch, const char* arg)
155{
156}
157
158void
159impl::app::process_options(void)
160{
161    PRE(inited());
162
163    std::string optstr;
164#if defined(HAVE_GNU_GETOPT)
165    optstr += '+'; // Turn on POSIX behavior.
166#endif
167    optstr += ':';
168    {
169        options_set opts = options();
170        for (options_set::const_iterator iter = opts.begin();
171             iter != opts.end(); iter++) {
172            const option& opt = (*iter);
173
174            optstr += opt.m_character;
175            if (!opt.m_argument.empty())
176                optstr += ':';
177        }
178    }
179
180    int ch;
181    ::opterr = 0;
182    while ((ch = ::getopt(m_argc, m_argv, optstr.c_str())) != -1) {
183        switch (ch) {
184            case 'h':
185                INV(m_use_ui);
186                m_hflag = true;
187                break;
188
189            case ':':
190                throw usage_error("Option -%c requires an argument.",
191                                  ::optopt);
192
193            case '?':
194                throw usage_error("Unknown option -%c.", ::optopt);
195
196            default:
197                process_option(ch, ::optarg);
198        }
199    }
200    m_argc -= ::optind;
201    m_argv += ::optind;
202
203    // Clear getopt state just in case the test wants to use it.
204    optind = 1;
205#if defined(HAVE_OPTRESET)
206    optreset = 1;
207#endif
208}
209
210void
211impl::app::usage(std::ostream& os)
212{
213    PRE(inited());
214
215    std::string args = specific_args();
216    if (!args.empty())
217        args = " " + args;
218    os << ui::format_text_with_tag(std::string(m_prog_name) + " [options]" +
219                                   args, "Usage: ", false) << "\n\n"
220       << ui::format_text(m_description) << "\n\n";
221
222    options_set opts = options();
223    INV(!opts.empty());
224    os << "Available options:\n";
225    size_t coldesc = 0;
226    for (options_set::const_iterator iter = opts.begin();
227         iter != opts.end(); iter++) {
228        const option& opt = (*iter);
229
230        if (opt.m_argument.length() + 1 > coldesc)
231            coldesc = opt.m_argument.length() + 1;
232    }
233    for (options_set::const_iterator iter = opts.begin();
234         iter != opts.end(); iter++) {
235        const option& opt = (*iter);
236
237        std::string tag = std::string("    -") + opt.m_character;
238        if (opt.m_argument.empty())
239            tag += "    ";
240        else
241            tag += " " + opt.m_argument + "    ";
242        os << ui::format_text_with_tag(opt.m_description, tag, false,
243                                       coldesc + 10) << "\n";
244    }
245    os << "\n";
246
247    std::string gmp;
248    if (!m_global_manpage.empty())
249        gmp = " and " + m_global_manpage;
250    os << ui::format_text("For more details please see " + m_manpage +
251                          gmp + ".")
252       << "\n";
253}
254
255int
256impl::app::run(int argc, char* const* argv)
257{
258    PRE(argc > 0);
259    PRE(argv != NULL);
260
261    m_argc = argc;
262    m_argv = argv;
263
264    m_argv0 = m_argv[0];
265
266    m_prog_name = std::strrchr(m_argv[0], '/');
267    if (m_prog_name == NULL)
268        m_prog_name = m_argv[0];
269    else
270        m_prog_name++;
271
272    // Libtool workaround: if running from within the source tree (binaries
273    // that are not installed yet), skip the "lt-" prefix added to files in
274    // the ".libs" directory to show the real (not temporary) name.
275    if (std::strncmp(m_prog_name, "lt-", 3) == 0)
276        m_prog_name += 3;
277
278    const std::string bug =
279        std::string("This is probably a bug in ") + m_prog_name +
280        " or one of the libraries it uses.  Please report this problem to "
281        PACKAGE_BUGREPORT " and provide as many details as possible "
282        "describing how you got to this condition.";
283
284    int errcode;
285    try {
286        int oldargc = m_argc;
287
288        process_options();
289
290        if (m_hflag) {
291            INV(m_use_ui);
292            if (oldargc != 2)
293                throw usage_error("-h must be given alone.");
294
295            usage(std::cout);
296            errcode = EXIT_SUCCESS;
297        } else
298            errcode = main();
299    } catch (const usage_error& e) {
300        if (m_use_ui) {
301            std::cerr << ui::format_error(m_prog_name, e.what()) << "\n"
302                      << ui::format_info(m_prog_name, std::string("Type `") +
303                                         m_prog_name + " -h' for more details.")
304                      << "\n";
305        } else {
306            std::cerr << m_prog_name << ": ERROR: " << e.what() << "\n";
307            std::cerr << m_prog_name << ": See " << m_manpage << " for usage "
308                "details.\n";
309        }
310        errcode = EXIT_FAILURE;
311    } catch (const std::runtime_error& e) {
312        if (m_use_ui) {
313            std::cerr << ui::format_error(m_prog_name, std::string(e.what()))
314                      << "\n";
315        } else {
316            std::cerr << m_prog_name << ": ERROR: " << e.what() << "\n";
317        }
318        errcode = EXIT_FAILURE;
319    } catch (const std::exception& e) {
320        if (m_use_ui) {
321            std::cerr << ui::format_error(m_prog_name, std::string("Caught "
322                "unexpected error: ") + e.what() + "\n" + bug) << "\n";
323        } else {
324            std::cerr << m_prog_name << ": ERROR: Caught unexpected error: "
325                      << e.what() << "\n";
326        }
327        errcode = EXIT_FAILURE;
328    } catch (...) {
329        if (m_use_ui) {
330            std::cerr << ui::format_error(m_prog_name, std::string("Caught "
331                "unknown error\n") + bug) << "\n";
332        } else {
333            std::cerr << m_prog_name << ": ERROR: Caught unknown error\n";
334        }
335        errcode = EXIT_FAILURE;
336    }
337    return errcode;
338}
339