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::__anon3445e4990111::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