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