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