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
34 extern "C" {
35 #include <unistd.h>
36 }
37
38 #include <cstdarg>
39 #include <cstdio>
40 #include <cstdlib>
41 #include <cstring>
42 #include <iostream>
43
44 extern "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)
53 namespace std {
54 using ::vsnprintf;
55 }
56 #endif // !defined(HAVE_VSNPRINTF_IN_STD)
57
58 namespace impl = atf::application;
59 #define IMPL_NAME "atf::application"
60
61 // ------------------------------------------------------------------------
62 // The "usage_error" class.
63 // ------------------------------------------------------------------------
64
usage_error(const char * fmt,...)65 impl::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
~usage_error(void)76 impl::usage_error::~usage_error(void)
77 throw()
78 {
79 }
80
81 const char*
what(void) const82 impl::usage_error::what(void)
83 const throw()
84 {
85 return m_text;
86 }
87
88 // ------------------------------------------------------------------------
89 // The "application" class.
90 // ------------------------------------------------------------------------
91
option(char ch,const std::string & a,const std::string & desc)92 impl::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
101 bool
operator <(const impl::option & o) const102 impl::option::operator<(const impl::option& o)
103 const
104 {
105 return m_character < o.m_character;
106 }
107
app(const std::string & description,const std::string & manpage,const std::string & global_manpage,const bool use_ui)108 impl::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
~app(void)123 impl::app::~app(void)
124 {
125 }
126
127 bool
inited(void)128 impl::app::inited(void)
129 {
130 return m_argc != -1;
131 }
132
133 impl::app::options_set
options(void)134 impl::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
143 std::string
specific_args(void) const144 impl::app::specific_args(void)
145 const
146 {
147 return "";
148 }
149
150 impl::app::options_set
specific_options(void) const151 impl::app::specific_options(void)
152 const
153 {
154 return options_set();
155 }
156
157 void
process_option(int ch ATF_DEFS_ATTRIBUTE_UNUSED,const char * arg ATF_DEFS_ATTRIBUTE_UNUSED)158 impl::app::process_option(int ch ATF_DEFS_ATTRIBUTE_UNUSED,
159 const char* arg ATF_DEFS_ATTRIBUTE_UNUSED)
160 {
161 }
162
163 void
process_options(void)164 impl::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
217 void
usage(std::ostream & os)218 impl::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
262 int
run(int argc,char * const * argv)263 impl::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