1 //
2 // Copyright (c) 2004-2017 Benjamin Kaufmann
3 //
4 // Permission is hereby granted, free of charge, to any person obtaining a copy
5 // of this software and associated documentation files (the "Software"), to
6 // deal in the Software without restriction, including without limitation the
7 // rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
8 // sell copies of the Software, and to permit persons to whom the Software is
9 // furnished to do so, subject to the following conditions:
10 //
11 // The above copyright notice and this permission notice shall be included in
12 // all copies or substantial portions of the Software.
13 //
14 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15 // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16 // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17 // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18 // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
19 // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
20 // IN THE SOFTWARE.
21 //
22 //
23 // NOTE: ProgramOptions is inspired by Boost.Program_options
24 // see: www.boost.org/libs/program_options
25 //
26 #include <potassco/application.h>
27 #include <potassco/program_opts/typed_value.h>
28 #include <cctype>
29 #include <limits.h>
30 #include <cstring>
31 #ifdef _MSC_VER
32 #pragma warning (disable : 4996)
33 #endif
34 #include <stdio.h>
35 #include <stdlib.h>
36 #include <signal.h>
37 #if !defined(SIGALRM)
38 #define SIGALRM 14
39 #endif
40 #if !defined(_WIN32)
41 #include <unistd.h> // for _exit
fetch_and_inc(volatile long & x)42 static long fetch_and_inc(volatile long& x) {
43 return __sync_fetch_and_add(&x, 1);
44 }
fetch_and_dec(volatile long & x)45 static long fetch_and_dec(volatile long& x) {
46 return __sync_fetch_and_sub(&x, 1);
47 }
48 #else
49 #define WIN32_LEAN_AND_MEAN // exclude APIs such as Cryptography, DDE, RPC, Shell, and Windows Sockets.
50 #if !defined(NOMINMAX)
51 #define NOMINMAX // do not let windows.h define macros min and max
52 #endif
53 #include <windows.h>
54 #include <process.h>
fetch_and_inc(volatile long & x)55 static long fetch_and_inc(volatile long& x) {
56 return InterlockedIncrement(&x) - 1;
57 }
fetch_and_dec(volatile long & x)58 static long fetch_and_dec(volatile long& x) {
59 return InterlockedDecrement(&x) + 1;
60 }
61 #endif
62 using namespace Potassco::ProgramOptions;
63 using namespace std;
64 namespace Potassco {
65 /////////////////////////////////////////////////////////////////////////////////////////
66 // Application
67 /////////////////////////////////////////////////////////////////////////////////////////
68 Application* Application::instance_s = 0;
Application()69 Application::Application() : exitCode_(EXIT_FAILURE), timeout_(0), verbose_(0), fastExit_(false), blocked_(0), pending_(0) {}
~Application()70 Application::~Application() { resetInstance(*this); }
initInstance(Application & app)71 void Application::initInstance(Application& app) {
72 instance_s = &app;
73 }
resetInstance(Application & app)74 void Application::resetInstance(Application& app) {
75 if (instance_s == &app) { instance_s = 0; }
76 }
77 #if !defined(_WIN32)
setAlarm(unsigned sec)78 int Application::setAlarm(unsigned sec) {
79 if (sec) { signal(SIGALRM, &Application::sigHandler); }
80 alarm(sec);
81 return 1;
82 }
83 #else
setAlarm(unsigned sec)84 int Application::setAlarm(unsigned sec) {
85 static HANDLE alarmEvent = CreateEvent(0, TRUE, TRUE, TEXT("Potassco::Application::AlarmEvent"));
86 static HANDLE alarmThread = INVALID_HANDLE_VALUE;
87 if (alarmEvent == INVALID_HANDLE_VALUE) { return 0; }
88 if (alarmThread != INVALID_HANDLE_VALUE) {
89 // wakeup any existing alarm
90 SetEvent(alarmEvent);
91 WaitForSingleObject(alarmThread, INFINITE);
92 CloseHandle(alarmThread);
93 alarmThread = INVALID_HANDLE_VALUE;
94 }
95 if (sec > 0) {
96 struct THUNK {
97 static unsigned __stdcall run(void* p) {
98 unsigned ms = static_cast<unsigned>(reinterpret_cast<std::size_t>(p));
99 if (WaitForSingleObject(alarmEvent, ms) == WAIT_TIMEOUT) {
100 Application::getInstance()->processSignal(SIGALRM);
101 }
102 return 0;
103 }
104 };
105 ResetEvent(alarmEvent);
106 alarmThread = (HANDLE)_beginthreadex(0, 0, &THUNK::run, reinterpret_cast<void*>(static_cast<std::size_t>(sec) * 1000), 0, 0);
107 }
108 return 1;
109 }
110 #endif
111 // Application entry point.
main(int argc,char ** argv)112 int Application::main(int argc, char** argv) {
113 initInstance(*this); // singleton instance used for signal handling
114 exitCode_ = EXIT_FAILURE;
115 blocked_ = pending_ = 0;
116 if (getOptions(argc, argv)) {
117 // install signal handlers
118 for (const int* sig = getSignals(); sig && *sig; ++sig) {
119 if (signal(*sig, &Application::sigHandler) == SIG_IGN) {
120 signal(*sig, SIG_IGN);
121 }
122 }
123 if (timeout_) {
124 if (setAlarm(timeout_) == 0) { warn("Could not set time limit!"); }
125 }
126 exitCode_ = EXIT_SUCCESS;
127 try { setup(); run(); shutdown(false); }
128 catch (...) { shutdown(true); }
129 }
130 if (fastExit_) { exit(exitCode_); }
131 fflush(stdout);
132 fflush(stderr);
133 return exitCode_;
134 }
135
getInstance()136 Application* Application::getInstance() {
137 return instance_s;
138 }
139
onUnhandledException()140 void Application::onUnhandledException() {
141 try { throw; }
142 catch (const std::exception& e) { error(e.what()); }
143 catch (...) { error("Unknown exception"); }
144 exit(EXIT_FAILURE);
145 }
146
setExitCode(int n)147 void Application::setExitCode(int n) {
148 exitCode_ = n;
149 }
150
getExitCode() const151 int Application::getExitCode() const {
152 return exitCode_;
153 }
154
155 // Called on application shutdown
shutdown(bool hasError)156 void Application::shutdown(bool hasError) {
157 // ignore signals/alarms during shutdown
158 fetch_and_inc(blocked_);
159 killAlarm();
160 if (hasError) { onUnhandledException(); }
161 shutdown();
162 }
163
shutdown()164 void Application::shutdown() {}
165
166 // Force exit without calling destructors.
exit(int status) const167 void Application::exit(int status) const {
168 fflush(stdout);
169 fflush(stderr);
170 _exit(status);
171 }
172
173 // Temporarily disable delivery of signals.
blockSignals()174 int Application::blockSignals() {
175 return fetch_and_inc(blocked_);
176 }
177
178 // Re-enable signal handling and deliver any pending signal.
unblockSignals(bool deliverPending)179 void Application::unblockSignals(bool deliverPending) {
180 if (fetch_and_dec(blocked_) == 1) {
181 int pend = pending_;
182 pending_ = 0;
183 // directly deliver any pending signal to our sig handler
184 if (pend && deliverPending) { processSignal(pend); }
185 }
186 }
sigHandler(int sig)187 void Application::sigHandler(int sig) {
188 // On Windows and original Unix, a handler once invoked is set to SIG_DFL.
189 // Instead, we temporarily ignore signals and reset our handler once it is done.
190 struct ScopedSig {
191 ScopedSig(int s) : sig(s) { signal(sig, SIG_IGN); Application::getInstance()->processSignal(sig); }
192 ~ScopedSig() { signal(sig, sigHandler); }
193 int sig;
194 } scoped(sig); (void)scoped;
195 }
196
197 // Called on timeout or signal.
processSignal(int sig)198 void Application::processSignal(int sig) {
199 if (fetch_and_inc(blocked_) == 0) {
200 if (!onSignal(sig)) { return; } // block further signals
201 }
202 else if (pending_ == 0) { // signals are currently blocked because output is active
203 info("Queueing signal...");
204 pending_ = sig;
205 }
206 fetch_and_dec(blocked_);
207 }
208
onSignal(int x)209 bool Application::onSignal(int x) {
210 info("INTERRUPTED by signal!");
211 exit(EXIT_FAILURE | (128+x));
212 return false;
213 }
214
215 // Kill any pending alarm.
killAlarm()216 void Application::killAlarm() {
217 if (timeout_ > 0) { setAlarm(0); }
218 }
219
220 namespace {
221 struct HelpParser {
222 static unsigned maxValue_s;
parsePotassco::__anon3d8b4e0d0111::HelpParser223 static bool parse(const std::string& v, unsigned& out) {
224 return string_cast(v, out) && out > 0 && out <= maxValue_s;
225 }
226 };
227 unsigned HelpParser::maxValue_s = 0;
228 } // namespace
229
230 // Process command-line options.
getOptions(int argc,char ** argv)231 bool Application::getOptions(int argc, char** argv) {
232 using namespace ProgramOptions;
233 unsigned help = 0;
234 bool version = false;
235 try {
236 ParsedOptions parsed; // options found in command-line
237 OptionContext allOpts(std::string("<").append(getName()).append(">"));
238 HelpOpt helpO = getHelpOption();
239 if (helpO.second == 0) { error("Invalid help option!"); exit(EXIT_FAILURE); }
240 OptionGroup basic("Basic Options");
241 HelpParser::maxValue_s = helpO.second;
242 Value* hv = helpO.second == 1 ? storeTo(help)->flag() : storeTo(help, &HelpParser::parse)->arg("<n>")->implicit("1");
243 basic.addOptions()
244 ("help,h" , hv , helpO.first)
245 ("version,v" , flag(version) , "Print version information and exit")
246 ("verbose,V" , storeTo(verbose_ = 0)->implicit("-1")->arg("<n>"), "Set verbosity level to %A")
247 ("time-limit" , storeTo(timeout_ = 0)->arg("<n>"), "Set time limit to %A seconds (0=no limit)")
248 ("fast-exit,@1", flag(fastExit_ = false) , "Force fast exit (do not call dtors)")
249 ;
250 allOpts.add(basic);
251 initOptions(allOpts);
252 ParsedValues values = parseCommandLine(argc, argv, allOpts, false, getPositional());
253 parsed.assign(values);
254 allOpts.assignDefaults(parsed);
255 if (help || version) {
256 exitCode_ = EXIT_SUCCESS;
257 if (help) {
258 DescriptionLevel x = (DescriptionLevel)(help-1);
259 allOpts.setActiveDescLevel(x);
260 printHelp(allOpts);
261 }
262 else {
263 printVersion();
264 }
265 return false;
266 }
267 validateOptions(allOpts, parsed, values);
268 }
269 catch(const std::exception& e) {
270 error(e.what());
271 info("Try '--help' for usage information");
272 return false;
273 }
274 return true;
275 }
276
printHelp(const OptionContext & root)277 void Application::printHelp(const OptionContext& root) {
278 printf("%s version %s\n", getName(), getVersion());
279 printUsage();
280 ProgramOptions::FileOut out(stdout);
281 root.description(out);
282 printf("\n");
283 printUsage();
284 printf("Default command-line:\n%s %s\n", getName(), root.defaults(strlen(getName())+1).c_str());
285 fflush(stdout);
286 }
printVersion()287 void Application::printVersion() {
288 printf("%s version %s\n", getName(), getVersion());
289 printf("Address model: %d-bit\n", (int)(sizeof(void*)*CHAR_BIT));
290 fflush(stdout);
291 }
292
printUsage()293 void Application::printUsage() {
294 printf("usage: %s %s\n", getName(), getUsage());
295 }
296
verbose() const297 unsigned Application::verbose() const {
298 return verbose_;
299 }
setVerbose(unsigned v)300 void Application::setVerbose(unsigned v) {
301 verbose_ = v;
302 }
303
304 } // namespace Potassco
305