1 /* $Id: ncbiapp.cpp 620642 2020-11-25 17:54:32Z lavr $
2 * ===========================================================================
3 *
4 * PUBLIC DOMAIN NOTICE
5 * National Center for Biotechnology Information
6 *
7 * This software/database is a "United States Government Work" under the
8 * terms of the United States Copyright Act. It was written as part of
9 * the author's official duties as a United States Government employee and
10 * thus cannot be copyrighted. This software/database is freely available
11 * to the public for use. The National Library of Medicine and the U.S.
12 * Government have not placed any restriction on its use or reproduction.
13 *
14 * Although all reasonable efforts have been taken to ensure the accuracy
15 * and reliability of the software and data, the NLM and the U.S.
16 * Government do not and cannot warrant the performance or results that
17 * may be obtained by using this software or data. The NLM and the U.S.
18 * Government disclaim all warranties, express or implied, including
19 * warranties of performance, merchantability or fitness for any particular
20 * purpose.
21 *
22 * Please cite the author in any work or product based on this material.
23 *
24 * ===========================================================================
25 *
26 * Authors: Vsevolod Sandomirskiy, Denis Vakatov
27 *
28 * File Description:
29 * CNcbiApplication -- a generic NCBI application class
30 * CCgiApplication -- a NCBI CGI-application class
31 *
32 */
33
34 #include <ncbi_pch.hpp>
35 #include <common/ncbi_source_ver.h>
36 #include <corelib/ncbiapp.hpp>
37 #undef CNcbiApplication
38 #undef CComponentVersionInfo
39 #undef CVersion
40
41 #include <corelib/ncbifile.hpp>
42 #include <corelib/ncbi_system.hpp>
43 #include <corelib/ncbi_param.hpp>
44 #include <corelib/syslog.hpp>
45 #include <corelib/error_codes.hpp>
46 #include <corelib/ncbi_safe_static.hpp>
47 #include <corelib/request_ctx.hpp>
48 #include "ncbisys.hpp"
49
50 #if defined(NCBI_OS_MSWIN)
51 # include <corelib/ncbi_os_mswin.hpp>
52 # include <corelib/ncbidll.hpp>
53 # include <io.h>
54 # include <fcntl.h>
55 #endif
56
57 #if defined(NCBI_OS_UNIX)
58 # include <unistd.h>
59 #endif
60
61
62 #define NCBI_USE_ERRCODE_X Corelib_App
63
64
65 BEGIN_NCBI_SCOPE
66
67
68 /////////////////////////////////////////////////////////////////////////////
69 // Constants
70 //
71
72 static const char* s_ArgLogFile = "-logfile";
73 static const char* s_ArgCfgFile = "-conffile";
74 static const char* s_ArgVersion = "-version";
75 static const char* s_ArgFullVersion = "-version-full";
76 static const char* s_ArgFullVersionXml = "-version-full-xml";
77 static const char* s_ArgFullVersionJson = "-version-full-json";
78 static const char* s_ArgDryRun = "-dryrun";
79
80
81 /////////////////////////////////////////////////////////////////////////////
82 // Global variables
83 //
84
85 static bool s_IsApplicationStarted = false;
86
87
88 ///////////////////////////////////////////////////////
89 // CNcbiApplication
90 //
91
CNcbiApplicationGuard(CNcbiApplicationAPI * app)92 CNcbiApplicationGuard::CNcbiApplicationGuard(CNcbiApplicationAPI* app) : m_App(app)
93 {
94 if (m_App) {
95 m_AppLock = make_shared<CReadLockGuard>(m_App->GetInstanceLock());
96 }
97 }
98
99
~CNcbiApplicationGuard(void)100 CNcbiApplicationGuard::~CNcbiApplicationGuard(void)
101 {
102 }
103
104
105 DEFINE_STATIC_MUTEX(s_InstanceMutex);
106
GetInstanceMutex(void)107 SSystemMutex& CNcbiApplicationAPI::GetInstanceMutex(void)
108 {
109 return s_InstanceMutex;
110 }
111
112
113 static CSafeStatic<CRWLock> s_InstanceRWLock(CSafeStaticLifeSpan(CSafeStaticLifeSpan::eLifeSpan_Long, 1));
114
GetInstanceLock(void)115 CRWLock& CNcbiApplicationAPI::GetInstanceLock(void)
116 {
117 return s_InstanceRWLock.Get();
118 }
119
120
121 CNcbiApplicationAPI* CNcbiApplicationAPI::m_Instance = nullptr;
122
Instance(void)123 CNcbiApplicationAPI* CNcbiApplicationAPI::Instance(void)
124 {
125 return m_Instance;
126 }
127
128
InstanceGuard(void)129 CNcbiApplicationGuard CNcbiApplicationAPI::InstanceGuard(void)
130 {
131 return CNcbiApplicationGuard(m_Instance);
132 }
133
134
CNcbiApplicationAPI(const SBuildInfo & build_info)135 CNcbiApplicationAPI::CNcbiApplicationAPI(const SBuildInfo& build_info)
136 : m_ConfigLoaded(false),
137 m_LogFile(0),
138 m_LogOptions(0)
139 {
140 CThread::InitializeMainThreadId();
141 // Initialize UID and start timer
142 GetDiagContext().GetUID();
143 GetDiagContext().InitMessages(size_t(-1));
144 GetDiagContext().SetGlobalAppState(eDiagAppState_AppBegin);
145
146 // Verify CPU compatibility
147 // First check. Print critical error only. See second check in x_TryInit() below.
148 {{
149 string err_message;
150 if (!VerifyCpuCompatibility(&err_message)) {
151 ERR_POST_X(22, Critical << err_message);
152 }
153 }}
154
155 m_DisableArgDesc = 0;
156 m_HideArgs = 0;
157 m_StdioFlags = 0;
158 m_CinBuffer = 0;
159 m_ExitCodeCond = eNoExits;
160
161 {
162 CWriteLockGuard guard(GetInstanceLock());
163 // Register the app. instance
164 if (m_Instance) {
165 NCBI_THROW(CAppException, eSecond,
166 "Second instance of CNcbiApplication is prohibited");
167 }
168 m_Instance = this;
169 }
170
171 // Create empty version info
172 m_Version.Reset(new CVersionAPI(build_info));
173
174 // Set version equal to package one if still empty (might have TeamCity build number)
175 if (m_Version->GetVersionInfo().IsAny()) {
176 auto package_info = m_Version->GetPackageVersion();
177 m_Version->SetVersionInfo(new CVersionInfo(package_info));
178 }
179
180 #if NCBI_SC_VERSION_PROXY != 0
181 m_Version->AddComponentVersion("NCBI C++ Toolkit",
182 NCBI_SC_VERSION_PROXY, 0, NCBI_SUBVERSION_REVISION_PROXY,
183 NCBI_TEAMCITY_PROJECT_NAME_PROXY, NCBI_APP_SBUILDINFO_DEFAULT());
184 #endif
185 // Create empty application arguments & name
186 m_Arguments.reset(new CNcbiArguments(0,0));
187
188 // Create empty application environment
189 m_Environ.reset(new CNcbiEnvironment);
190
191 // Create an empty registry
192 m_Config.Reset(new CNcbiRegistry);
193
194 m_DryRun = false;
195 }
196
ExecuteOnExitActions()197 void CNcbiApplicationAPI::ExecuteOnExitActions()
198 {
199 m_OnExitActions.ExecuteActions();
200 }
201
202
~CNcbiApplicationAPI(void)203 CNcbiApplicationAPI::~CNcbiApplicationAPI(void)
204 {
205 CThread::sm_IsExiting = true;
206
207 // Execute exit actions before waiting for all threads to stop.
208 // NOTE: The exit actions may already be executed by higher-level
209 // destructors. This is a final fail-safe place for this.
210 ExecuteOnExitActions();
211
212 #if defined(NCBI_THREADS)
213 CThread::WaitForAllThreads();
214 #endif
215
216 {
217 CWriteLockGuard guard(GetInstanceLock());
218 m_Instance = 0;
219 }
220 FlushDiag(0, true);
221 if (m_CinBuffer) {
222 delete [] m_CinBuffer;
223 }
224
225 #if defined(NCBI_COMPILER_WORKSHOP)
226 // At least under these conditions:
227 // 1) WorkShop 5.5 on Solaris 10/SPARC, Release64MT, and
228 // 2) when IOS_BASE::sync_with_stdio(false) is called, and
229 // 3) the contents of 'cout' is not flushed
230 // some applications crash on exit() while apparently trying to
231 // flush 'cout' and getting confused by its own guts, with error:
232 // "*** libc thread failure: _thread_setschedparam_main() fails"
233 //
234 // This forced pre-flush trick seems to fix the problem.
235 NcbiCout.flush();
236 #endif
237 }
238
239
Instance(void)240 CNcbiApplication* CNcbiApplication::Instance(void)
241 {
242 return dynamic_cast<CNcbiApplication*>(CNcbiApplicationAPI::Instance());
243 }
244
245
CNcbiApplication(const SBuildInfo & build_info)246 CNcbiApplication::CNcbiApplication(const SBuildInfo& build_info)
247 : CNcbiApplicationAPI(build_info)
248 {
249 }
250
251
~CNcbiApplication()252 CNcbiApplication::~CNcbiApplication()
253 {
254 // This earlier execution of the actions allows a safe use of
255 // CNcbiApplication::Instance() from the exit action functions. Instance()
256 // can return NULL pointer if called as part of CNcbiApplicationAPI dtor
257 // when the CNcbiApplication dtor already finished.
258 ExecuteOnExitActions();
259 }
260
261
Init(void)262 void CNcbiApplicationAPI::Init(void)
263 {
264 return;
265 }
266
267
DryRun(void)268 int CNcbiApplicationAPI::DryRun(void)
269 {
270 ERR_POST_X(1, Info << "DryRun: default implementation does nothing");
271 return 0;
272 }
273
274
Exit(void)275 void CNcbiApplicationAPI::Exit(void)
276 {
277 return;
278 }
279
280
GetArgs(void) const281 const CArgs& CNcbiApplicationAPI::GetArgs(void) const
282 {
283 if ( !m_Args.get() ) {
284 NCBI_THROW(CAppException, eUnsetArgs,
285 "Command-line argument description is not found");
286 }
287 return *m_Args;
288 }
289
290
FlushDiag(CNcbiOstream * os,bool)291 SIZE_TYPE CNcbiApplicationAPI::FlushDiag(CNcbiOstream* os, bool /*close_diag*/)
292 {
293 if ( os ) {
294 SetDiagStream(os, true, 0, 0, "STREAM");
295 }
296 GetDiagContext().FlushMessages(*GetDiagHandler());
297 GetDiagContext().DiscardMessages();
298 return 0;
299 }
300
301
302 #if defined(NCBI_OS_DARWIN)
s_MacArgMunging(CNcbiApplicationAPI & app,int * argcPtr,const char * const ** argvPtr,const string & exepath)303 static void s_MacArgMunging(CNcbiApplicationAPI& app,
304 int* argcPtr,
305 const char* const** argvPtr,
306 const string& exepath)
307 {
308
309 // Sometimes on Mac there will be an argument -psn which
310 // will be followed by the Process Serial Number, e.g. -psn_0_13107201
311 // this is in situations where the application could have no other
312 // arguments like when it is double clicked.
313 // This will mess up argument processing later, so get rid of it.
314 static const char* s_ArgMacPsn = "-psn_";
315
316 if (*argcPtr == 2 &&
317 NStr::strncmp((*argvPtr)[1], s_ArgMacPsn, strlen(s_ArgMacPsn)) == 0) {
318 --*argcPtr;
319 }
320
321 if (*argcPtr > 1)
322 return;
323
324 // Have no arguments from the operating system -- so use the '.args' file
325
326 // Open the args file.
327 string exedir;
328 CDir::SplitPath(exepath, &exedir);
329 string args_fname = exedir + app.GetProgramDisplayName() + ".args";
330 CNcbiIfstream in(args_fname.c_str());
331
332 if ( !in.good() ) {
333 ERR_POST_X(2, Info << "Mac arguments file not found: " << args_fname);
334 return;
335 }
336
337 vector<string> v;
338
339 // remember or fake the executable name.
340 if (*argcPtr > 0) {
341 v.push_back((*argvPtr)[0]); // preserve the original argv[0].
342 } else {
343 v.push_back(exepath);
344 }
345
346 // grab the rest of the arguments from the file.
347 // arguments are separated by whitespace. Can be on
348 // more than one line.
349 string arg;
350 while (in >> arg) {
351 v.push_back(arg);
352 }
353
354 // stash them away in the standard argc and argv places.
355 *argcPtr = v.size();
356
357 char** argv = new char*[v.size()];
358 int c = 0;
359 ITERATE(vector<string>, vp, v) {
360 argv[c++] = strdup(vp->c_str());
361 }
362 *argvPtr = argv;
363 }
364 #endif /* NCBI_OS_DARWIN */
365
366
367 NCBI_PARAM_DECL(bool, Debug, Catch_Unhandled_Exceptions);
368 NCBI_PARAM_DEF_EX(bool, Debug, Catch_Unhandled_Exceptions, true,
369 eParam_NoThread, DEBUG_CATCH_UNHANDLED_EXCEPTIONS);
370 typedef NCBI_PARAM_TYPE(Debug, Catch_Unhandled_Exceptions) TParamCatchExceptions;
371
s_HandleExceptions(void)372 bool s_HandleExceptions(void)
373 {
374 return TParamCatchExceptions::GetDefault();
375 }
376
377
378 NCBI_PARAM_DECL(bool, NCBI, TerminateOnCpuIncompatibility);
379 NCBI_PARAM_DEF_EX(bool, NCBI, TerminateOnCpuIncompatibility, false,
380 eParam_NoThread, NCBI_CONFIG__TERMINATE_ON_CPU_INCOMPATIBILITY);
381
382
x_TryInit(EAppDiagStream diag,const char * conf)383 void CNcbiApplicationAPI::x_TryInit(EAppDiagStream diag, const char* conf)
384 {
385 // Load registry from the config file
386 if ( conf ) {
387 string x_conf(conf);
388 LoadConfig(*m_Config, &x_conf);
389 } else {
390 LoadConfig(*m_Config, NULL);
391 }
392 m_ConfigLoaded = true;
393
394 CDiagContext::SetupDiag(diag, m_Config, eDCM_Flush, m_LogFile);
395 CDiagContext::x_FinalizeSetupDiag();
396
397 // Setup the standard features from the config file.
398 // Don't call till after LoadConfig()
399 // NOTE: this will override environment variables,
400 // except DIAG_POST_LEVEL which is Set*Fixed*.
401 x_HonorStandardSettings();
402
403 // Application start
404 AppStart();
405
406 // Verify CPU compatibility
407 // Second check. Print error message and allow to terminate program depends on configuration parameters.
408 // Also, see first check in CNcbiApplicationAPI() constructor.
409 {{
410 string err_message;
411 if (!VerifyCpuCompatibility(&err_message)) {
412 bool fatal = NCBI_PARAM_TYPE(NCBI, TerminateOnCpuIncompatibility)::GetDefault();
413 ERR_POST_X(22, (fatal ? Fatal : Critical) << err_message);
414 }
415 }}
416
417 // Do init
418 #if (defined(NCBI_COMPILER_ICC) && NCBI_COMPILER_VERSION < 900)
419 // ICC 8.0 have an optimization bug in exceptions handling,
420 // so workaround it here
421 try {
422 Init();
423 }
424 catch (const CArgHelpException&) {
425 throw;
426 }
427 #else
428 Init();
429 #endif
430
431 // If the app still has no arg description - provide default one
432 if (!m_DisableArgDesc && !m_ArgDesc.get()) {
433 unique_ptr<CArgDescriptions> arg_desc(new CArgDescriptions);
434 arg_desc->SetUsageContext
435 (GetArguments().GetProgramBasename(),
436 "This program has no mandatory arguments");
437 SetupArgDescriptions(arg_desc.release());
438 }
439 }
440
441 // Macro to define a logging parameter
442 #define NCBI_LOG_PARAM(type,Name,NAME) \
443 NCBI_PARAM_DECL (type, Log, LogApp ## Name); \
444 NCBI_PARAM_DEF_EX(type, Log, LogApp ## Name, false, eParam_NoThread, DIAG_LOG_APP_ ## NAME); \
445 typedef NCBI_PARAM_TYPE(Log, LogApp ## Name) TLogApp ## Name;
446
447 NCBI_LOG_PARAM(bool, Environment, ENVIRONMENT)
448 NCBI_LOG_PARAM(bool, EnvironmentOnStop, ENVIRONMENT_ON_STOP)
449 NCBI_LOG_PARAM(bool, Registry, REGISTRY)
450 NCBI_LOG_PARAM(bool, RegistryOnStop, REGISTRY_ON_STOP)
451 NCBI_LOG_PARAM(bool, Arguments, ARGUMENTS)
452 NCBI_LOG_PARAM(bool, Path, PATH)
453 NCBI_LOG_PARAM(bool, RunContext, RUN_CONTEXT)
454 NCBI_LOG_PARAM(bool, ResUsageOnStop, RESUSAGE_ON_STOP)
455
456
457 enum ELogOptionsEvent {
458 eStartEvent = 0x01, ///< right before AppMain()
459 eStopEvent = 0x02, ///< right after AppMain()
460 eOtherEvent = 0x03 ///< any case is fine
461 };
462
463
464 /// Flags to switch what to log
465 enum ELogOptions {
466 fLogAppEnvironment = 0x01, ///< log app environment on app start
467 fLogAppEnvironmentStop = 0x02, ///< log app environment on app stop
468 fLogAppRegistry = 0x04, ///< log app registry on app start
469 fLogAppRegistryStop = 0x08, ///< log app registry on app stop
470 fLogAppArguments = 0x10, ///< log app arguments
471 fLogAppPath = 0x20, ///< log app executable path
472 fLogAppResUsageStop = 0x40, ///< log resource usage on app stop
473 };
474
475
x_ReadLogOptions()476 void CNcbiApplicationAPI::x_ReadLogOptions()
477 {
478 // Log all
479 if ( TLogAppRunContext::GetDefault() ) {
480 m_LogOptions = 0x7f; // all on
481 return;
482 }
483
484 // Log registry
485 m_LogOptions |= TLogAppRegistry::GetDefault() ? fLogAppRegistry : 0;
486 m_LogOptions |= TLogAppRegistryOnStop::GetDefault() ? fLogAppRegistryStop : 0;
487
488 // Log environment
489 m_LogOptions |= TLogAppEnvironment::GetDefault() ? fLogAppEnvironment : 0;
490 m_LogOptions |= TLogAppEnvironmentOnStop::GetDefault() ? fLogAppEnvironmentStop : 0;
491
492 // Log arguments
493 m_LogOptions |= TLogAppArguments::GetDefault() ? fLogAppArguments : 0;
494
495 // Log path
496 m_LogOptions |= TLogAppPath::GetDefault() ? fLogAppPath : 0;
497
498 // Log resources usage
499 m_LogOptions |= TLogAppResUsageOnStop::GetDefault() ? fLogAppResUsageStop : 0;
500 }
501
502
s_RoundResUsageSize(Uint8 value_in_bytes,string & suffix,Uint8 & value)503 void s_RoundResUsageSize(Uint8 value_in_bytes, string& suffix, Uint8& value)
504 {
505 const Uint8 limit = 1000;
506
507 // KB by default
508 suffix = "_KB";
509 value = value_in_bytes / 1024;
510
511 // Round to MB if value is too big
512 if (value / 1024 > limit) {
513 suffix = "_MB";
514 value /= 1024;
515 }
516 }
517
518 #define RES_SIZE_USAGE(name, value_in_bytes) \
519 { \
520 string suffix; \
521 Uint8 value; \
522 s_RoundResUsageSize(value_in_bytes, suffix, value); \
523 extra.Print(name + suffix, value); \
524 }
525
526 #define RES_TIME_USAGE(name, value) \
527 if (value >= 0 ) \
528 extra.Print(name, (Uint8)value)
529
530
x_LogOptions(int event)531 void CNcbiApplicationAPI::x_LogOptions(int /*ELogOptionsEvent*/ event)
532 {
533 const bool start = (event & eStartEvent) != 0;
534 const bool stop = (event & eStopEvent) != 0;
535
536 // Print environment values
537 if ( (m_LogOptions & fLogAppEnvironment && start) ||
538 (m_LogOptions & fLogAppEnvironmentStop && stop) ) {
539 CDiagContext_Extra extra = GetDiagContext().Extra();
540 extra.Print("LogAppEnvironment", "true");
541 list<string> env_keys;
542 const CNcbiEnvironment& env = GetEnvironment();
543 env.Enumerate(env_keys);
544 ITERATE(list<string>, it, env_keys) {
545 const string& val = env.Get(*it);
546 extra.Print(*it, val);
547 }
548 }
549
550 // Print registry values
551 if ( (m_LogOptions & fLogAppRegistry && start) ||
552 (m_LogOptions & fLogAppRegistryStop && stop) ) {
553 CDiagContext_Extra extra = GetDiagContext().Extra();
554 extra.Print("LogAppRegistry", "true");
555 list<string> reg_sections;
556 const CNcbiRegistry& reg = GetConfig();
557 reg.EnumerateSections(®_sections);
558 ITERATE(list<string>, it, reg_sections) {
559 string section, name;
560 list<string> section_entries;
561 reg.EnumerateEntries(*it, §ion_entries);
562 ITERATE(list<string>, it_entry, section_entries) {
563 const string& val = reg.Get(*it, *it_entry);
564 string path = "[" + *it + "]" + *it_entry;
565 extra.Print(path, val);
566 }
567 }
568 }
569
570 // Print arguments
571 if ( m_LogOptions & fLogAppArguments && start) {
572 CDiagContext_Extra extra = GetDiagContext().Extra();
573 extra.Print("LogAppArguments", "true");
574 string args_str;
575 extra.Print("Arguments", GetArgs().Print(args_str));
576 }
577
578 // Print app path
579 if ( m_LogOptions & fLogAppPath && start) {
580 CDiagContext_Extra extra = GetDiagContext().Extra();
581 extra.Print("LogAppPath", "true");
582 extra.Print("Path", GetProgramExecutablePath());
583 }
584
585 // Print resource usage
586 if ( m_LogOptions & fLogAppResUsageStop && stop) {
587 CDiagContext_Extra extra = GetDiagContext().Extra();
588 extra.Print("LogAppResUsage", "true");
589 // Memory usage
590 CProcess::SMemoryUsage mem_usage;
591 if ( CCurrentProcess::GetMemoryUsage(mem_usage) ) {
592 RES_SIZE_USAGE("mem_total", mem_usage.total );
593 RES_SIZE_USAGE("mem_total_peak", mem_usage.total_peak );
594 RES_SIZE_USAGE("rss_mem", mem_usage.resident );
595 RES_SIZE_USAGE("rss_peak_mem", mem_usage.resident_peak);
596 RES_SIZE_USAGE("shared.mem", mem_usage.shared );
597 RES_SIZE_USAGE("data.mem", mem_usage.data );
598 RES_SIZE_USAGE("stack.mem", mem_usage.stack );
599 }
600 // CPU time usage
601 double real, user, sys;
602 if ( CCurrentProcess::GetTimes(&real, &user, &sys, CProcess::eProcess) ) {
603 RES_TIME_USAGE("real.proc.cpu", real);
604 RES_TIME_USAGE("user.proc.cpu", user);
605 RES_TIME_USAGE("sys.proc.cpu", sys);
606 }
607 if ( CCurrentProcess::GetTimes(&real, &user, &sys, CProcess::eChildren) ) {
608 RES_TIME_USAGE("user.child.cpu", user);
609 RES_TIME_USAGE("sys.child.cpu", sys);
610 }
611 if ( CCurrentProcess::GetTimes(&real, &user, &sys, CProcess::eThread) ) {
612 RES_TIME_USAGE("user.thread.cpu", user);
613 RES_TIME_USAGE("sys.thread.cpu", sys);
614 }
615 }
616 }
617
618
x_TryMain(EAppDiagStream diag,const char * conf,int * exit_code,bool * got_exception)619 void CNcbiApplicationAPI::x_TryMain(EAppDiagStream diag,
620 const char* conf,
621 int* exit_code,
622 bool* got_exception)
623 {
624 // Initialize the application
625 try {
626 if ( s_HandleExceptions() ) {
627 try {
628 x_TryInit(diag, conf);
629 }
630 catch (const CArgHelpException&) {
631 // This exceptions will be caught later regardless of the
632 // handle-exceptions flag.
633 throw;
634 }
635 catch (const CArgException&) {
636 // NCBI_RETHROW_SAME(e, "Application's initialization failed");
637 throw;
638 }
639 catch (const CException& e) {
640 NCBI_REPORT_EXCEPTION_X(15,
641 "Application's initialization failed", e);
642 *got_exception = true;
643 *exit_code = 2;
644 }
645 catch (const exception& e) {
646 ERR_POST_X(6, "Application's initialization failed: " << e.what());
647 *got_exception = true;
648 *exit_code = 2;
649 }
650 }
651 else {
652 x_TryInit(diag, conf);
653 }
654 }
655 catch (const CArgHelpException& e) {
656 x_AddDefaultArgs();
657 // Print USAGE
658 if (e.GetErrCode() == CArgHelpException::eHelpXml) {
659 m_ArgDesc->PrintUsageXml(cout);
660 } else {
661 string str;
662 m_ArgDesc->PrintUsage
663 (str, e.GetErrCode() == CArgHelpException::eHelpFull);
664 cout << str;
665 }
666 *exit_code = e.GetErrCode() == CArgHelpException::eHelpErr ? 2 : 0;
667 }
668 x_ReadLogOptions();
669 x_LogOptions(eStartEvent);
670 // Run application
671 if (*exit_code == 1) {
672 GetDiagContext().SetGlobalAppState(eDiagAppState_AppRun);
673 if ( s_HandleExceptions() ) {
674 try {
675 *exit_code = m_DryRun ? DryRun() : Run();
676 }
677 catch (CArgException& e) {
678 NCBI_RETHROW_SAME(e, "Application's execution failed");
679 }
680 catch (const CException& e) {
681 CRef<CRequestContext> cur_ctx(&GetDiagContext().GetRequestContext());
682 if (cur_ctx != &e.GetRequestContext()) {
683 GetDiagContext().SetRequestContext(&e.GetRequestContext());
684 NCBI_REPORT_EXCEPTION_X(16,
685 "CException thrown", e);
686 GetDiagContext().SetRequestContext(cur_ctx);
687 }
688 NCBI_REPORT_EXCEPTION_X(16,
689 "Application's execution failed", e);
690 *got_exception = true;
691 *exit_code = 3;
692 }
693 catch (const exception& e) {
694 ERR_POST_X(7, "Application's execution failed: " << e.what());
695 *got_exception = true;
696 *exit_code = 3;
697 }
698 }
699 else {
700 *exit_code = m_DryRun ? DryRun() : Run();
701 }
702 }
703 x_LogOptions(eStopEvent);
704 GetDiagContext().SetGlobalAppState(eDiagAppState_AppEnd);
705
706 // Close application
707 if ( s_HandleExceptions() ) {
708 try {
709 Exit();
710 }
711 catch (CArgException& e) {
712 NCBI_RETHROW_SAME(e, "Application's cleanup failed");
713 }
714 catch (const CException& e) {
715 NCBI_REPORT_EXCEPTION_X(17, "Application's cleanup failed", e);
716 *got_exception = true;
717 }
718 catch (const exception& e) {
719 ERR_POST_X(8, "Application's cleanup failed: "<< e.what());
720 *got_exception = true;
721 }
722 }
723 else {
724 Exit();
725 }
726 }
727
728 #if defined(NCBI_OS_MSWIN) && defined(_UNICODE)
729 static
s_Create_ArgsOrEnvW(vector<string> & storage,AutoArray<const char * > & pointers,const TXChar * const * begin)730 void s_Create_ArgsOrEnvW(
731 vector<string>& storage,
732 AutoArray<const char*>& pointers,
733 const TXChar* const* begin)
734 {
735 const TXChar* const* arg = begin;
736 size_t count = 0;
737 while( *(arg++) )
738 ++count;
739
740 const char** args = new const char*[count+1];
741 if ( !args ) {
742 NCBI_THROW(CCoreException, eNullPtr, kEmptyStr);
743 }
744 pointers = args;
745
746 arg = begin;
747 size_t i=0;
748 for (i=0; i<count; ++i) {
749 storage.push_back( _T_STDSTRING( *(arg++) ) );
750 }
751
752 for (i=0; i < storage.size(); ++i) {
753 args[i] = storage[i].c_str();
754 }
755 args[i] = NULL;
756 }
757
AppMain(int argc,const TXChar * const * argv,const TXChar * const * envp,EAppDiagStream diag,const TXChar * conf,const TXString & name)758 int CNcbiApplicationAPI::AppMain
759 (int argc,
760 const TXChar* const* argv,
761 const TXChar* const* envp,
762 EAppDiagStream diag,
763 const TXChar* conf,
764 const TXString& name)
765 {
766 vector< string> argv_storage;
767 AutoArray<const char*> argv_pointers;
768 if (argv) {
769 s_Create_ArgsOrEnvW(argv_storage, argv_pointers, argv);
770 }
771
772 vector< string> envp_storage;
773 AutoArray<const char*> envp_pointers;
774 if (envp) {
775 s_Create_ArgsOrEnvW(envp_storage, envp_pointers, envp);
776 }
777
778 return AppMain(argc,
779 argv == NULL ? NULL : argv_pointers.get(),
780 envp == NULL ? NULL : envp_pointers.get(),
781 diag,
782 conf == NULL ? NULL : (conf == NcbiEmptyXCStr ? NcbiEmptyCStr : _T_CSTRING(conf)),
783 name == NcbiEmptyXString ? NcbiEmptyString : _T_STDSTRING(name));
784 }
785 #endif
786
AppMain(int argc,const char * const * argv,const char * const * envp,EAppDiagStream diag,const char * conf,const string & name)787 int CNcbiApplicationAPI::AppMain
788 (int argc,
789 const char* const* argv,
790 const char* const* envp,
791 EAppDiagStream diag,
792 const char* conf,
793 const string& name)
794 {
795 if (conf) {
796 m_DefaultConfig = conf;
797 }
798 x_SetupStdio();
799
800 // Check if logfile is set in the args.
801 m_LogFile = 0;
802 if (!m_DisableArgDesc && argc > 1 && argv && diag != eDS_User) {
803 for (int i = 1; i < argc; i++) {
804 if ( !argv[i] ) {
805 continue;
806 }
807 if ( NStr::strcmp(argv[i], s_ArgLogFile) == 0 ) {
808 if (!argv[++i]) {
809 continue;
810 }
811 m_LogFile = argv[i];
812 } else if (NStr::StartsWith(argv[i], s_ArgLogFile)) {
813 const char *a = argv[i] + strlen(s_ArgLogFile);
814 if (*a == '=') {
815 m_LogFile = ++a;
816 }
817 }
818 }
819 }
820 // Setup logging as soon as possible.
821 // Setup for diagnostics
822 try {
823 CDiagContext::SetupDiag(diag, 0, eDCM_NoChange, m_LogFile);
824 } catch (const CException& e) {
825 NCBI_RETHROW(e, CAppException, eSetupDiag,
826 "Application diagnostic stream's setup failed");
827 } catch (const exception& e) {
828 NCBI_THROW(CAppException, eSetupDiag,
829 "Application diagnostic stream's setup failed: " +
830 string(e.what()));
831 }
832
833 // Get program executable's name & path.
834 string exepath = FindProgramExecutablePath(argc, argv, &m_RealExePath);
835 m_ExePath = exepath;
836
837 // Get program display name
838 string appname = name;
839 if (appname.empty()) {
840 if (!exepath.empty()) {
841 CDirEntry::SplitPath(exepath, NULL, &appname);
842 } else if (argc > 0 && argv[0] != NULL && *argv[0] != '\0') {
843 CDirEntry::SplitPath(argv[0], NULL, &appname);
844 } else {
845 appname = "ncbi";
846 }
847 }
848 if ( m_ProgramDisplayName.empty() ) {
849 SetProgramDisplayName(appname);
850 }
851
852 // Make sure we have something as our 'real' executable's name.
853 // though if it does not contain a full path it won't be much use.
854 if ( exepath.empty() ) {
855 ERR_POST_X(3, Warning
856 << "Warning: Could not determine this application's "
857 "file name and location. Using \""
858 << appname << "\" instead.\n"
859 "Please fix FindProgramExecutablePath() on this platform.");
860 exepath = appname;
861 }
862
863 #if defined(NCBI_OS_DARWIN)
864 // We do not know standard way of passing arguments to C++ program on Mac,
865 // so we will read arguments from special file having extension ".args"
866 // and name equal to display name of program (name argument of AppMain).
867 s_MacArgMunging(*this, &argc, &argv, exepath);
868 #endif
869
870 CDiagContext& diag_context = GetDiagContext();
871
872 // Preparse command line
873 if (PreparseArgs(argc, argv) == ePreparse_Exit) {
874 diag_context.DiscardMessages();
875 return 0;
876 }
877
878 // Check command line for presence special arguments
879 // "-logfile", "-conffile", "-version"
880 if (!m_DisableArgDesc && argc > 1 && argv) {
881 const char** v = new const char*[argc];
882 v[0] = argv[0];
883 int real_arg_index = 1;
884 for (int i = 1; i < argc; i++) {
885 if ( !argv[i] ) {
886 continue;
887 }
888 // Log file - ignore if diag is eDS_User - the user wants to
889 // take care about logging.
890 if ( diag != eDS_User &&
891 NStr::strcmp(argv[i], s_ArgLogFile) == 0 ) {
892 if (!argv[++i]) {
893 continue;
894 }
895 v[real_arg_index++] = argv[i - 1];
896 v[real_arg_index++] = argv[i];
897 // Configuration file
898 } else if ( NStr::strcmp(argv[i], s_ArgCfgFile) == 0 ) {
899 if (!argv[++i]) {
900 continue;
901 }
902 v[real_arg_index++] = argv[i - 1];
903 v[real_arg_index++] = argv[i];
904 conf = argv[i];
905
906 }
907 else if (NStr::StartsWith(argv[i], s_ArgCfgFile)) {
908 v[real_arg_index++] = argv[i];
909 const char* a = argv[i] + strlen(s_ArgCfgFile);
910 if (*a == '=') {
911 conf = ++a;
912 }
913
914 // Version
915 } else if ( NStr::strcmp(argv[i], s_ArgVersion) == 0 ) {
916 delete[] v;
917 // Print VERSION
918 cout << GetFullVersion().Print( appname, CVersionAPI::fVersionInfo | CVersionAPI::fPackageShort );
919 diag_context.DiscardMessages();
920 return 0;
921
922 // Full version
923 } else if ( NStr::strcmp(argv[i], s_ArgFullVersion) == 0 ) {
924 delete[] v;
925 // Print full VERSION
926 cout << GetFullVersion().Print( appname );
927 diag_context.DiscardMessages();
928 return 0;
929 } else if ( NStr::strcmp(argv[i], s_ArgFullVersionXml) == 0 ) {
930 delete[] v;
931 // Print full VERSION in XML format
932 cout << GetFullVersion().PrintXml( appname );
933 diag_context.DiscardMessages();
934 return 0;
935 } else if ( NStr::strcmp(argv[i], s_ArgFullVersionJson) == 0 ) {
936 delete[] v;
937 // Print full VERSION in JSON format
938 cout << GetFullVersion().PrintJson( appname );
939 diag_context.DiscardMessages();
940 return 0;
941
942 // Dry run
943 } else if ( NStr::strcmp(argv[i], s_ArgDryRun) == 0 ) {
944 m_DryRun = true;
945
946 // Save real argument
947 } else {
948 v[real_arg_index++] = argv[i];
949 }
950 }
951 if (real_arg_index == argc ) {
952 delete[] v;
953 } else {
954 argc = real_arg_index;
955 argv = v;
956 }
957 }
958
959 // Reset command-line args and application name
960 m_Arguments->Reset(argc, argv, exepath, m_RealExePath);
961
962 // Reset application environment
963 m_Environ->Reset(envp);
964
965 // Setup some debugging features from environment variables.
966 if ( !m_Environ->Get(DIAG_TRACE).empty() ) {
967 SetDiagTrace(eDT_Enable, eDT_Enable);
968 }
969 string post_level = m_Environ->Get(DIAG_POST_LEVEL);
970 if ( !post_level.empty() ) {
971 EDiagSev sev;
972 if (CNcbiDiag::StrToSeverityLevel(post_level.c_str(), sev)) {
973 SetDiagFixedPostLevel(sev);
974 }
975 }
976 if ( !m_Environ->Get(ABORT_ON_THROW).empty() ) {
977 SetThrowTraceAbort(true);
978 }
979
980 // Clear registry content
981 m_Config->Clear();
982
983 // Call: Init() + Run() + Exit()
984 int exit_code = 1;
985 bool got_exception = false;
986 s_IsApplicationStarted = true;
987
988 try {
989 if ( s_HandleExceptions() ) {
990 try {
991 x_TryMain(diag, conf, &exit_code, &got_exception);
992 }
993 catch (const CArgException&) {
994 // This exceptions will be caught later regardless of the
995 // handle-exceptions flag.
996 throw;
997 }
998 #if defined(NCBI_COMPILER_MSVC) && defined(_DEBUG)
999 // Microsoft promotes many common application errors to exceptions.
1000 // This includes occurrences such as dereference of a NULL pointer and
1001 // walking off of a dangling pointer. The catch-all is lifted only in
1002 // debug mode to permit easy inspection of such error conditions, while
1003 // maintaining safety of production, release-mode applications.
1004 catch (...) {
1005 ERR_POST_X(10, Warning <<
1006 "Application has thrown an exception of unknown type");
1007 throw;
1008 }
1009 #endif
1010 }
1011 else {
1012 #ifdef NCBI_OS_MSWIN
1013 if ( !IsDebuggerPresent() ) {
1014 SuppressSystemMessageBox(fSuppress_Exception);
1015 }
1016 #endif
1017 x_TryMain(diag, conf, &exit_code, &got_exception);
1018 }
1019 }
1020 catch (const CArgException& e) {
1021 // Print USAGE and the exception error message
1022 if ( e.GetErrCode() != CArgException::eNoValue && m_ArgDesc.get() ) {
1023 x_AddDefaultArgs();
1024 string str;
1025 if ( !m_ArgDesc->IsSetMiscFlag(CArgDescriptions::fNoUsage) ) {
1026 m_ArgDesc->PrintUsage(str);
1027 cerr << str;
1028 }
1029 if ( !m_ArgDesc->IsSetMiscFlag(CArgDescriptions::fDupErrToCerr) ) {
1030 CStreamDiagHandler* errh =
1031 dynamic_cast<CStreamDiagHandler*>(GetDiagHandler());
1032 if (!errh || errh->GetStream() != &cerr) {
1033 cerr << "Error in command-line arguments. "
1034 "See error logs for more details." << endl;
1035 }
1036 }
1037 else {
1038 cerr << "Error in command-line arguments." << endl;
1039 cerr << e.what() << endl;
1040 }
1041 cerr << string(72, '=') << endl << endl;
1042 }
1043 SetDiagPostAllFlags(eDPF_Severity);
1044 NCBI_REPORT_EXCEPTION_X(18, "", e);
1045 got_exception = true;
1046 exit_code = 1;
1047 }
1048
1049 if (!diag_context.IsSetExitCode()) {
1050 diag_context.SetExitCode(exit_code);
1051 }
1052
1053 if (m_ExitCodeCond == eAllExits
1054 || (got_exception && m_ExitCodeCond == eExceptionalExits)) {
1055 _TRACE("Overriding exit code from " << exit_code
1056 << " to " << m_ExitCode);
1057 exit_code = m_ExitCode;
1058 }
1059
1060 // Application stop
1061 AppStop(exit_code);
1062
1063 if ((m_AppFlags & fSkipSafeStaticDestroy) == 0) {
1064 // Destroy short-lived statics
1065 CSafeStaticGuard::Destroy(CSafeStaticLifeSpan::eLifeLevel_AppMain);
1066 }
1067
1068 // Exit
1069 return exit_code;
1070 }
1071
1072
SetEnvironment(const string & name,const string & value)1073 void CNcbiApplicationAPI::SetEnvironment(const string& name, const string& value)
1074 {
1075 SetEnvironment().Set(name, value);
1076 }
1077
1078 #if 0
1079 void CNcbiApplicationAPI::SetVersionByBuild(int major)
1080 {
1081 SetVersion(major, NStr::StringToInt(m_Version->GetBuildInfo().GetExtraValue(SBuildInfo::eStableComponentsVersion, "0")));
1082 }
1083
1084 void CNcbiApplicationAPI::SetVersion(int major, int minor)
1085 {
1086 SetVersion(major, minor, NStr::StringToInt(m_Version->GetBuildInfo().GetExtraValue(SBuildInfo::eTeamCityBuildNumber, "0")));
1087 }
1088
1089 void CNcbiApplicationAPI::SetVersion(int major, int minor, int patch)
1090 {
1091 m_Version->SetVersionInfo(major, minor, patch,
1092 m_Version->GetBuildInfo().GetExtraValue(SBuildInfo::eTeamCityProjectName));
1093 }
1094 #else
SetVersionByBuild(int major)1095 void CNcbiApplicationAPI::SetVersionByBuild(int major)
1096 {
1097 m_Version->SetVersionInfo(major, NCBI_SC_VERSION_PROXY, NCBI_TEAMCITY_BUILD_NUMBER_PROXY);
1098 }
1099 #endif
1100
1101
SetVersion(const CVersionInfo & version)1102 void CNcbiApplicationAPI::SetVersion(const CVersionInfo& version)
1103 {
1104 if ( s_IsApplicationStarted ) {
1105 ERR_POST_X(19, "SetVersion() should be used from constructor of " \
1106 "CNcbiApplication derived class, see description");
1107 }
1108 m_Version->SetVersionInfo(new CVersionInfo(version));
1109 }
1110
SetVersion(const CVersionInfo & version,const SBuildInfo & build_info)1111 void CNcbiApplicationAPI::SetVersion(const CVersionInfo& version,
1112 const SBuildInfo& build_info)
1113 {
1114 if ( s_IsApplicationStarted ) {
1115 ERR_POST_X(19, "SetVersion() should be used from constructor of " \
1116 "CNcbiApplication derived class, see description");
1117 }
1118 m_Version->SetVersionInfo(new CVersionInfo(version), build_info);
1119 }
1120
SetFullVersion(CRef<CVersionAPI> version)1121 void CNcbiApplicationAPI::SetFullVersion( CRef<CVersionAPI> version)
1122 {
1123 if ( s_IsApplicationStarted ) {
1124 ERR_POST_X(19, "SetFullVersion() should be used from constructor of "\
1125 "CNcbiApplication derived class, see description");
1126 }
1127 m_Version.Reset( version );
1128 }
1129
1130
GetVersion(void) const1131 CVersionInfo CNcbiApplicationAPI::GetVersion(void) const
1132 {
1133 return m_Version->GetVersionInfo();
1134 }
1135
GetFullVersion(void) const1136 const CVersionAPI& CNcbiApplicationAPI::GetFullVersion(void) const
1137 {
1138 return *m_Version;
1139 }
1140
1141
SetupArgDescriptions(CArgDescriptions * arg_desc)1142 void CNcbiApplicationAPI::SetupArgDescriptions(CArgDescriptions* arg_desc)
1143 {
1144 m_ArgDesc.reset(arg_desc);
1145
1146 if ( arg_desc ) {
1147 if ( !m_DisableArgDesc ) {
1148 for(CArgDescriptions* desc : m_ArgDesc->GetAllDescriptions()) {
1149 // Add logfile and conffile arguments
1150 if (!desc->Exist(s_ArgLogFile + 1) ) {
1151 desc->AddOptionalKey
1152 (s_ArgLogFile+1, "File_Name",
1153 "File to which the program log should be redirected",
1154 CArgDescriptions::eOutputFile);
1155 }
1156 if (!desc->Exist(s_ArgCfgFile + 1) ) {
1157 if (m_DefaultConfig.empty()) {
1158 desc->AddOptionalKey
1159 (s_ArgCfgFile + 1, "File_Name",
1160 "Program's configuration (registry) data file",
1161 CArgDescriptions::eInputFile);
1162 } else {
1163 desc->AddDefaultKey
1164 (s_ArgCfgFile + 1, "File_Name",
1165 "Program's configuration (registry) data file",
1166 CArgDescriptions::eInputFile,
1167 m_DefaultConfig);
1168 }
1169 }
1170 }
1171 }
1172 m_Args.reset(arg_desc->CreateArgs(GetArguments()));
1173 } else {
1174 m_Args.reset();
1175 }
1176 }
1177
1178
SetupDiag(EAppDiagStream diag)1179 bool CNcbiApplicationAPI::SetupDiag(EAppDiagStream diag)
1180 {
1181 CDiagContext::SetupDiag(diag, 0, eDCM_Flush, m_LogFile);
1182 return true;
1183 }
1184
1185
SetupDiag_AppSpecific(void)1186 bool CNcbiApplicationAPI::SetupDiag_AppSpecific(void)
1187 {
1188 CDiagContext::SetupDiag(eDS_ToStderr, 0, eDCM_Flush, m_LogFile);
1189 return true;
1190 }
1191
1192
LoadConfig(CNcbiRegistry & reg,const string * conf,CNcbiRegistry::TFlags reg_flags)1193 bool CNcbiApplicationAPI::LoadConfig(CNcbiRegistry& reg,
1194 const string* conf,
1195 CNcbiRegistry::TFlags reg_flags)
1196 {
1197 string basename (m_Arguments->GetProgramBasename(eIgnoreLinks));
1198 string basename2(m_Arguments->GetProgramBasename(eFollowLinks));
1199 CMetaRegistry::SEntry entry;
1200
1201 if ( !conf ) {
1202 if (reg.IncludeNcbircIfAllowed(reg_flags)) {
1203 m_ConfigPath = CMetaRegistry::FindRegistry
1204 ("ncbi", CMetaRegistry::eName_RcOrIni);
1205 }
1206 m_ConfigLoaded = true;
1207 return false;
1208 } else if (conf->empty()) {
1209 entry = CMetaRegistry::Load(basename, CMetaRegistry::eName_Ini, 0,
1210 reg_flags, ®);
1211 if ( !entry.registry && basename2 != basename ) {
1212 entry = CMetaRegistry::Load(basename2, CMetaRegistry::eName_Ini, 0,
1213 reg_flags, ®);
1214 }
1215 m_DefaultConfig = CDirEntry(entry.actual_name).GetName();
1216 } else {
1217 entry = CMetaRegistry::Load(*conf, CMetaRegistry::eName_AsIs, 0,
1218 reg_flags, ®);
1219 }
1220 if ( !entry.registry ) {
1221 // failed; complain as appropriate
1222 string dir;
1223 CDirEntry::SplitPath(*conf, &dir, 0, 0);
1224 if (dir.empty()) {
1225 ERR_POST_X(11, Info <<
1226 "Registry file of application \"" << basename
1227 << "\" is not found");
1228 } else {
1229 NCBI_THROW(CAppException, eNoRegistry,
1230 "Registry file \"" + *conf + "\" cannot be opened");
1231 }
1232 // still consider pulling in defaults from .ncbirc
1233 if (reg.IncludeNcbircIfAllowed(reg_flags)) {
1234 m_ConfigPath = CMetaRegistry::FindRegistry
1235 ("ncbi", CMetaRegistry::eName_RcOrIni);
1236 }
1237 m_ConfigLoaded = true;
1238 return false;
1239 } else if (entry.registry != static_cast<IRWRegistry*>(®)) {
1240 // should be impossible with new CMetaRegistry interface...
1241 if (® == m_Config && reg.Empty()) {
1242 m_Config.Reset(dynamic_cast<CNcbiRegistry*>
1243 (entry.registry.GetPointer()));
1244 } else {
1245 // copy into reg
1246 CNcbiStrstream str;
1247 entry.registry->Write(str);
1248 str.seekg(0);
1249 reg.Read(str);
1250 }
1251 }
1252 m_ConfigPath = entry.actual_name;
1253 m_ConfigLoaded = true;
1254 return true;
1255 }
1256
1257
LoadConfig(CNcbiRegistry & reg,const string * conf)1258 bool CNcbiApplicationAPI::LoadConfig(CNcbiRegistry& reg,
1259 const string* conf)
1260 {
1261 return LoadConfig(reg, conf, IRegistry::fWithNcbirc);
1262 }
1263
1264
1265 CNcbiApplicationAPI::EPreparseArgs
PreparseArgs(int,const char * const *)1266 CNcbiApplicationAPI::PreparseArgs(int /*argc*/,
1267 const char* const* /*argv*/)
1268 {
1269 return ePreparse_Continue;
1270 }
1271
1272
DisableArgDescriptions(TDisableArgDesc disable)1273 void CNcbiApplicationAPI::DisableArgDescriptions(TDisableArgDesc disable)
1274 {
1275 m_DisableArgDesc = disable;
1276 }
1277
1278
HideStdArgs(THideStdArgs hide_mask)1279 void CNcbiApplicationAPI::HideStdArgs(THideStdArgs hide_mask)
1280 {
1281 m_HideArgs = hide_mask;
1282 }
1283
1284
SetStdioFlags(TStdioSetupFlags stdio_flags)1285 void CNcbiApplicationAPI::SetStdioFlags(TStdioSetupFlags stdio_flags)
1286 {
1287 // do not call this function more than once
1288 // and from places other than App constructor
1289 _ASSERT(m_StdioFlags == 0);
1290 m_StdioFlags = stdio_flags;
1291 }
1292
1293
x_SetupStdio(void)1294 void CNcbiApplicationAPI::x_SetupStdio(void)
1295 {
1296 if ((m_StdioFlags & fNoSyncWithStdio) != 0) {
1297 IOS_BASE::sync_with_stdio(false);
1298 }
1299
1300 if ((m_StdioFlags & fDefault_CinBufferSize) == 0
1301 #ifdef NCBI_OS_UNIX
1302 && !isatty(0)
1303 #endif
1304 ) {
1305 #if defined(NCBI_COMPILER_GCC) && defined(NCBI_OS_SOLARIS)
1306 _ASSERT(!m_CinBuffer);
1307 // Ugly workaround for ugly interaction between g++ and Solaris C RTL
1308 const size_t kCinBufSize = 5120;
1309 m_CinBuffer = new char[kCinBufSize];
1310 cin.rdbuf()->pubsetbuf(m_CinBuffer, kCinBufSize);
1311 #endif
1312 }
1313 #ifdef NCBI_OS_MSWIN
1314 if ((m_StdioFlags & fBinaryCin) != 0) {
1315 NcbiSys_setmode(NcbiSys_fileno(stdin), O_BINARY);
1316 }
1317 if ((m_StdioFlags & fBinaryCout) != 0) {
1318 NcbiSys_setmode(NcbiSys_fileno(stdout), O_BINARY);
1319 }
1320 #endif
1321 }
1322
x_AddDefaultArgs(void)1323 void CNcbiApplicationAPI::x_AddDefaultArgs(void)
1324 {
1325 if ( !m_DisableArgDesc ) {
1326 for(CArgDescriptions* desc : m_ArgDesc->GetAllDescriptions())
1327 {
1328 if (desc->IsAutoHelpEnabled()) {
1329 if ((m_HideArgs & fHideHelp) != 0) {
1330 if (desc->Exist("h")) {
1331 desc->Delete("h");
1332 }
1333 }
1334 }
1335 if ((m_HideArgs & fHideFullHelp) != 0) {
1336 if (desc->Exist("help")) {
1337 desc->Delete("help");
1338 }
1339 }
1340 if ((m_HideArgs & fHideXmlHelp) != 0) {
1341 if (desc->Exist("xmlhelp")) {
1342 desc->Delete("xmlhelp");
1343 }
1344 }
1345 if ((m_HideArgs & fHideLogfile) != 0) {
1346 if (desc->Exist(s_ArgLogFile + 1)) {
1347 desc->Delete(s_ArgLogFile + 1);
1348 }
1349 } else {
1350 if (!desc->Exist(s_ArgLogFile + 1)) {
1351 desc->AddOptionalKey
1352 (s_ArgLogFile+1, "File_Name",
1353 "File to which the program log should be redirected",
1354 CArgDescriptions::eOutputFile);
1355 }
1356 }
1357 if ((m_HideArgs & fHideConffile) != 0) {
1358 if (desc->Exist(s_ArgCfgFile + 1)) {
1359 desc->Delete(s_ArgCfgFile + 1);
1360 }
1361 } else {
1362 if (!desc->Exist(s_ArgCfgFile + 1)) {
1363 desc->AddOptionalKey
1364 (s_ArgCfgFile + 1, "File_Name",
1365 "Program's configuration (registry) data file",
1366 CArgDescriptions::eInputFile);
1367 }
1368 }
1369 if ((m_HideArgs & fHideVersion) != 0) {
1370 if (desc->Exist(s_ArgVersion + 1)) {
1371 desc->Delete(s_ArgVersion + 1);
1372 }
1373 } else {
1374 if (!desc->Exist(s_ArgVersion + 1)) {
1375 desc->AddFlag
1376 (s_ArgVersion + 1,
1377 "Print version number; ignore other arguments");
1378 }
1379 }
1380 if ((m_HideArgs & fHideFullVersion) != 0) {
1381 if (desc->Exist(s_ArgFullVersion + 1)) {
1382 desc->Delete(s_ArgFullVersion + 1);
1383 }
1384 if (desc->Exist(s_ArgFullVersionXml+ 1)) {
1385 desc->Delete(s_ArgFullVersionXml + 1);
1386 }
1387 if (desc->Exist(s_ArgFullVersionJson + 1)) {
1388 desc->Delete(s_ArgFullVersionJson + 1);
1389 }
1390 } else {
1391 if (!desc->Exist(s_ArgFullVersion + 1)) {
1392 desc->AddFlag
1393 (s_ArgFullVersion + 1,
1394 "Print extended version data; ignore other arguments");
1395 }
1396 if (!desc->Exist(s_ArgFullVersionXml + 1)) {
1397 desc->AddFlag
1398 (s_ArgFullVersionXml + 1,
1399 "Print extended version data in XML format; ignore other arguments");
1400 }
1401 if (!desc->Exist(s_ArgFullVersionJson + 1)) {
1402 desc->AddFlag
1403 (s_ArgFullVersionJson + 1,
1404 "Print extended version data in JSON format; ignore other arguments");
1405 }
1406 }
1407 if ((m_HideArgs & fHideDryRun) != 0) {
1408 if (desc->Exist(s_ArgDryRun + 1)) {
1409 desc->Delete(s_ArgDryRun + 1);
1410 }
1411 } else {
1412 if (!desc->Exist(s_ArgDryRun + 1)) {
1413 desc->AddFlag
1414 (s_ArgDryRun + 1,
1415 "Dry run the application: do nothing, only test all preconditions");
1416 }
1417 }
1418 }
1419 }
1420 }
1421
SetProgramDisplayName(const string & app_name)1422 void CNcbiApplicationAPI::SetProgramDisplayName(const string& app_name)
1423 {
1424 if (app_name.empty()) return;
1425 m_ProgramDisplayName = app_name;
1426 // Also set app_name in the diag context
1427 if ( GetDiagContext().GetAppName().empty() ) {
1428 GetDiagContext().SetAppName(app_name);
1429 }
1430 }
1431
1432
GetAppName(EAppNameType name_type,int argc,const char * const * argv)1433 string CNcbiApplicationAPI::GetAppName(EAppNameType name_type, int argc,
1434 const char* const* argv)
1435 {
1436 CNcbiApplicationGuard instance = InstanceGuard();
1437 string app_name;
1438
1439 switch (name_type) {
1440 case eBaseName:
1441 if (instance) {
1442 app_name = instance->GetProgramDisplayName();
1443 } else {
1444 string exe_path = FindProgramExecutablePath(argc, argv);
1445 CDirEntry::SplitPath(exe_path, NULL, &app_name);
1446 }
1447 break;
1448
1449 case eFullName:
1450 if (instance) {
1451 app_name = instance->GetProgramExecutablePath(eIgnoreLinks);
1452 } else {
1453 app_name = FindProgramExecutablePath(argc, argv);
1454 }
1455 break;
1456
1457 case eRealName:
1458 if (instance) {
1459 app_name = instance->GetProgramExecutablePath(eFollowLinks);
1460 } else {
1461 FindProgramExecutablePath(argc, argv, &app_name);
1462 }
1463 break;
1464 }
1465
1466 return app_name;
1467 }
1468
1469
FindProgramExecutablePath(int argc,const char * const * argv,string * real_path)1470 string CNcbiApplicationAPI::FindProgramExecutablePath
1471 (int argc,
1472 const char* const* argv,
1473 string* real_path)
1474 {
1475 CNcbiApplicationGuard instance = InstanceGuard();
1476 string ret_val;
1477 _ASSERT(argc >= 0); // formally signed for historical reasons
1478 _ASSERT(argv != NULL || argc == 0);
1479 if (argc > 0 && argv[0] != NULL && argv[0][0] != '\0') {
1480 ret_val = argv[0];
1481 } else if (instance) {
1482 ret_val = instance->GetArguments().GetProgramName();
1483 }
1484
1485 #if defined(NCBI_OS_MSWIN) || defined(NCBI_OS_UNIX)
1486
1487 # ifdef NCBI_OS_MSWIN
1488 // MS Windows: Try more accurate method of detection
1489 // XXX - use this only for real_path?
1490 try {
1491 // Load PSAPI dynamic library -- it should exist on MS-Win NT/2000/XP
1492 CDll dll_psapi("psapi.dll", CDll::eLoadNow, CDll::eAutoUnload);
1493
1494 // Get function entry-point from DLL
1495 BOOL (STDMETHODCALLTYPE FAR * dllEnumProcessModules)
1496 (HANDLE hProcess, // handle to process
1497 HMODULE *lphModule, // array of module handles
1498 DWORD cb, // size of array
1499 LPDWORD lpcbNeeded // number of bytes required
1500 ) = NULL;
1501
1502 dllEnumProcessModules =
1503 dll_psapi.GetEntryPoint_Func("EnumProcessModules",
1504 &dllEnumProcessModules);
1505 if ( !dllEnumProcessModules ) {
1506 NCBI_THROW(CException, eUnknown, kEmptyStr);
1507 }
1508
1509 // Find executable file in the midst of all loaded modules
1510 HANDLE process = GetCurrentProcess();
1511 HMODULE module = 0;
1512 DWORD needed = 0;
1513
1514 // Get first module of current process (it should be .exe file)
1515 if ( dllEnumProcessModules(process,
1516 &module, sizeof(HMODULE), &needed) ) {
1517 if ( needed && module ) {
1518 TXChar buf[MAX_PATH + 1];
1519 DWORD ncount = GetModuleFileName(module, buf, MAX_PATH);
1520 if (ncount > 0) {
1521 ret_val = _T_STDSTRING(buf);
1522 if (real_path) {
1523 *real_path = CDirEntry::NormalizePath(ret_val,
1524 eFollowLinks);
1525 }
1526 return ret_val;
1527 }
1528 }
1529 }
1530 }
1531 catch (const CException&) {
1532 ; // Just catch an all exceptions from CDll
1533 }
1534 // This method didn't work -- use standard method
1535 # endif
1536
1537 # ifdef NCBI_OS_LINUX
1538 // Linux OS: Try more accurate method of detection for real_path
1539 if (ret_val.empty() && !real_path) {
1540 real_path = &ret_val;
1541 }
1542 if (real_path) {
1543 char buf[PATH_MAX + 1];
1544 string procfile = "/proc/" + NStr::IntToString(getpid()) + "/exe";
1545 int ncount = (int)readlink((procfile).c_str(), buf, PATH_MAX);
1546 if (ncount > 0) {
1547 real_path->assign(buf, ncount);
1548 if (real_path == &ret_val || ret_val.empty()) {
1549 // XXX - could also parse /proc/self/cmdline.
1550 return *real_path;
1551 }
1552 real_path = 0;
1553 }
1554 }
1555 # endif
1556
1557 if (ret_val.empty()) {
1558 // nothing to go on :-/
1559 if (real_path) {
1560 real_path->erase();
1561 }
1562 return kEmptyStr;
1563 }
1564 string app_path = ret_val;
1565
1566 if ( !CDirEntry::IsAbsolutePath(app_path) ) {
1567 # ifdef NCBI_OS_MSWIN
1568 // Add default ".exe" extention to the name of executable file
1569 // if it running without extension
1570 string dir, title, ext;
1571 CDirEntry::SplitPath(app_path, &dir, &title, &ext);
1572 if ( ext.empty() ) {
1573 app_path = CDirEntry::MakePath(dir, title, "exe");
1574 }
1575 # endif
1576 if ( CFile(app_path).Exists() ) {
1577 // Relative path from the the current directory
1578 app_path = CDir::GetCwd() + CDirEntry::GetPathSeparator()+app_path;
1579 if ( !CFile(app_path).Exists() ) {
1580 app_path = kEmptyStr;
1581 }
1582 } else {
1583 // Running from some path from PATH environment variable.
1584 // Try to determine that path.
1585 string env_path;
1586 if (instance) {
1587 env_path = instance->GetEnvironment().Get("PATH");
1588 } else {
1589 env_path = _T_STDSTRING(NcbiSys_getenv(_TX("PATH")));
1590 }
1591 list<string> split_path;
1592 # ifdef NCBI_OS_MSWIN
1593 NStr::Split(env_path, ";", split_path,
1594 NStr::fSplit_MergeDelimiters | NStr::fSplit_Truncate);
1595 # else
1596 NStr::Split(env_path, ":", split_path,
1597 NStr::fSplit_MergeDelimiters | NStr::fSplit_Truncate);
1598 # endif
1599 string base_name = CDirEntry(app_path).GetBase();
1600 ITERATE(list<string>, it, split_path) {
1601 app_path = CDirEntry::MakePath(*it, base_name);
1602 if ( CFile(app_path).Exists() ) {
1603 break;
1604 }
1605 app_path = kEmptyStr;
1606 }
1607 }
1608 }
1609 ret_val = CDirEntry::NormalizePath
1610 ((app_path.empty() && argv != NULL && argv[0] != NULL) ? argv[0]
1611 : app_path);
1612
1613 #else // defined (NCBI_OS_MSWIN) || defined(NCBI_OS_UNIX)
1614
1615 # error "Unsupported platform, sorry -- please contact NCBI"
1616 #endif
1617 if (real_path) {
1618 *real_path = CDirEntry::NormalizePath(ret_val, eFollowLinks);
1619 }
1620 return ret_val;
1621 }
1622
1623
x_HonorStandardSettings(IRegistry * reg)1624 void CNcbiApplicationAPI::x_HonorStandardSettings( IRegistry* reg)
1625 {
1626 if (reg == 0) {
1627 reg = m_Config.GetPointer();
1628 if (reg == 0)
1629 return;
1630 }
1631
1632 CStackTrace::s_HonorSignalHandlingConfiguration();
1633
1634 // [NCBI.MEMORY_FILL]
1635 CObject::SetAllocFillMode(reg->Get("NCBI", "MEMORY_FILL"));
1636
1637 {{
1638 CSysLog* syslog = dynamic_cast<CSysLog*>(GetDiagHandler());
1639 if (syslog) {
1640 syslog->HonorRegistrySettings(reg);
1641 }
1642 }}
1643
1644 // Debugging features
1645
1646 // [DEBUG.DIAG_TRACE]
1647 if ( !reg->Get("DEBUG", DIAG_TRACE).empty() ) {
1648 SetDiagTrace(eDT_Enable, eDT_Enable);
1649 }
1650
1651 // [DEBUG.ABORT_ON_THROW]
1652 if ( !reg->Get("DEBUG", ABORT_ON_THROW).empty() ) {
1653 SetThrowTraceAbort(true);
1654 }
1655
1656 // [DEBUG.DIAG_POST_LEVEL]
1657 string post_level = reg->Get("DEBUG", DIAG_POST_LEVEL);
1658 if ( !post_level.empty() ) {
1659 EDiagSev sev;
1660 if (CNcbiDiag::StrToSeverityLevel(post_level.c_str(), sev)) {
1661 SetDiagFixedPostLevel(sev);
1662 }
1663 }
1664
1665 // [DEBUG.MessageFile]
1666 string msg_file = reg->Get("DEBUG", DIAG_MESSAGE_FILE);
1667 if ( !msg_file.empty() ) {
1668 CDiagErrCodeInfo* info = new CDiagErrCodeInfo();
1669 if ( !info || !info->Read(msg_file) ) {
1670 if ( info ) {
1671 delete info;
1672 }
1673 ERR_POST_X(12, Warning << "Applications message file \""
1674 << msg_file
1675 << "\" is not found");
1676 } else {
1677 SetDiagErrCodeInfo(info);
1678 }
1679 }
1680
1681 // [DEBUG.GuardAgainstThreadsOnStaticDataDestruction]
1682 if ( !reg->GetBool("DEBUG", "GuardAgainstThreadsOnStaticDataDestruction", true, 0, IRegistry::eErrPost) ) {
1683 CSafeStaticGuard::DisableChildThreadsCheck();
1684 }
1685
1686 // CPU and memory limitations
1687
1688 // [NCBI.HeapSizeLimit]
1689 if ( !reg->Get("NCBI", "HeapSizeLimit").empty() ) {
1690 ERR_POST_X(13, Warning
1691 << "Config param [NCBI.HeapSizeLimit] is deprecated,"
1692 << "please use [NCBI.MemorySizeLimit] instead.");
1693 int mem_size_limit = reg->GetInt("NCBI", "HeapSizeLimit", 0);
1694 if (mem_size_limit < 0) {
1695 NCBI_THROW(CAppException, eLoadConfig,
1696 "Configuration file error: [NCBI.HeapSizeLimit] < 0");
1697 }
1698 SetMemoryLimit(size_t(mem_size_limit) * 1024 * 1024);
1699 }
1700 // [NCBI.MemorySizeLimit]
1701 if ( !reg->Get("NCBI", "MemorySizeLimit").empty() ) {
1702 size_t mem_size_limit = 0;
1703 string s = reg->GetString("NCBI", "MemorySizeLimit", kEmptyStr);
1704 size_t pos = s.find("%");
1705 if (pos != NPOS) {
1706 // Size in percents of total memory
1707 size_t percents = NStr::StringToUInt(CTempString(s, 0, pos));
1708 if (percents > 100) {
1709 NCBI_THROW(CAppException, eLoadConfig,
1710 "Configuration file error: [NCBI.HeapSizeLimit] > 100%");
1711 }
1712 mem_size_limit = (size_t)(CSystemInfo::GetTotalPhysicalMemorySize() * percents / 100);
1713 } else {
1714 try {
1715 // Size is specified in MiB by default if no suffixes
1716 // (converted without exception)
1717 mem_size_limit = NStr::StringToSizet(s) * 1024 * 1024;
1718 }
1719 catch (const CStringException&) {
1720 // Otherwise, size have suffix (MiB, G, GB, etc)
1721 Uint8 bytes = NStr::StringToUInt8_DataSize(s);
1722 if ( bytes > get_limits(mem_size_limit).max() ) {
1723 NCBI_THROW(CAppException, eLoadConfig,
1724 "Configuration file error: [NCBI.MemorySizeLimit] is too big");
1725 }
1726 mem_size_limit = (size_t)bytes;
1727 }
1728 }
1729 SetMemoryLimit(mem_size_limit);
1730 }
1731
1732 // [NCBI.CpuTimeLimit]
1733 if ( !reg->Get("NCBI", "CpuTimeLimit").empty() ) {
1734 int cpu_time_limit = reg->GetInt("NCBI", "CpuTimeLimit", 0);
1735 if (cpu_time_limit < 0) {
1736 NCBI_THROW(CAppException, eLoadConfig,
1737 "Configuration file error: [NCBI.CpuTimeLimit] < 0");
1738 }
1739 SetCpuTimeLimit((unsigned int)cpu_time_limit, 5, NULL, NULL);
1740 }
1741
1742 // TRACE and POST filters
1743
1744 try {
1745 // [DIAG.TRACE_FILTER]
1746 string trace_filter = reg->Get("DIAG", "TRACE_FILTER");
1747 if ( !trace_filter.empty() )
1748 SetDiagFilter(eDiagFilter_Trace, trace_filter.c_str());
1749 } NCBI_CATCH_X(20,
1750 "Failed to load and set diag. filter for traces");
1751
1752 try {
1753 // [DIAG.POST_FILTER]
1754 string post_filter = reg->Get("DIAG", "POST_FILTER");
1755 if ( !post_filter.empty() )
1756 SetDiagFilter(eDiagFilter_Post, post_filter.c_str());
1757 } NCBI_CATCH_X(21,
1758 "Failed to load and set diag. filter for regular errors");
1759 }
1760
1761
AppStart(void)1762 void CNcbiApplicationAPI::AppStart(void)
1763 {
1764 string cmd_line = GetProgramExecutablePath();
1765 if ( m_Arguments.get() ) {
1766 if ( cmd_line.empty() ) {
1767 cmd_line = (*m_Arguments)[0];
1768 }
1769 for (SIZE_TYPE arg = 1; arg < m_Arguments->Size(); ++arg) {
1770 cmd_line += " ";
1771 cmd_line += NStr::ShellEncode((*m_Arguments)[arg]);
1772 }
1773 }
1774
1775 // Print application start message
1776 if ( !CDiagContext::IsSetOldPostFormat() ) {
1777 GetDiagContext().PrintStart(cmd_line);
1778 }
1779 }
1780
1781
AppStop(int exit_code)1782 void CNcbiApplicationAPI::AppStop(int exit_code)
1783 {
1784 CDiagContext& ctx = GetDiagContext();
1785 if ( !ctx.IsSetExitCode() ) {
1786 ctx.SetExitCode(exit_code);
1787 }
1788 }
1789
1790
SetExitCode(int exit_code,EExitMode when)1791 void CNcbiApplicationAPI::SetExitCode(int exit_code, EExitMode when)
1792 {
1793 m_ExitCode = exit_code;
1794 m_ExitCodeCond = when;
1795 }
1796
GetErrCodeString(void) const1797 const char* CAppException::GetErrCodeString(void) const
1798 {
1799 switch (GetErrCode()) {
1800 case eUnsetArgs: return "eUnsetArgs";
1801 case eSetupDiag: return "eSetupDiag";
1802 case eLoadConfig: return "eLoadConfig";
1803 case eSecond: return "eSecond";
1804 case eNoRegistry: return "eNoRegistry";
1805 default: return CException::GetErrCodeString();
1806 }
1807 }
1808
1809
Idle(void)1810 void CDefaultIdler::Idle(void)
1811 {
1812 DiagHandler_Reopen();
1813 }
1814
1815
1816 class CIdlerWrapper
1817 {
1818 public:
CIdlerWrapper(void)1819 CIdlerWrapper(void) : m_Idler(new CDefaultIdler()) {}
~CIdlerWrapper(void)1820 ~CIdlerWrapper(void) {}
1821
1822 INcbiIdler* GetIdler(EOwnership own);
1823 void SetIdler(INcbiIdler* idler, EOwnership own);
1824 void RunIdler(void);
1825
1826 private:
1827 CMutex m_Mutex;
1828 AutoPtr<INcbiIdler> m_Idler;
1829 };
1830
1831
1832 inline
GetIdler(EOwnership own)1833 INcbiIdler* CIdlerWrapper::GetIdler(EOwnership own)
1834 {
1835 CMutexGuard guard(m_Mutex);
1836 m_Idler.reset(m_Idler.release(), own);
1837 return m_Idler.get();
1838 }
1839
1840
1841 inline
SetIdler(INcbiIdler * idler,EOwnership own)1842 void CIdlerWrapper::SetIdler(INcbiIdler* idler, EOwnership own)
1843 {
1844 CMutexGuard guard(m_Mutex);
1845 m_Idler.reset(idler, own);
1846 }
1847
1848
1849 inline
RunIdler(void)1850 void CIdlerWrapper::RunIdler(void)
1851 {
1852 if ( m_Idler.get() ) {
1853 CMutexGuard guard(m_Mutex);
1854 if ( m_Idler.get() ) {
1855 m_Idler->Idle();
1856 }
1857 }
1858 }
1859
1860
1861 CSafeStatic<CIdlerWrapper> s_IdlerWrapper;
1862
GetIdler(EOwnership ownership)1863 INcbiIdler* GetIdler(EOwnership ownership)
1864 {
1865 return s_IdlerWrapper.Get().GetIdler(ownership);
1866 }
1867
1868
SetIdler(INcbiIdler * idler,EOwnership ownership)1869 void SetIdler(INcbiIdler* idler, EOwnership ownership)
1870 {
1871 s_IdlerWrapper.Get().SetIdler(idler, ownership);
1872 }
1873
1874
RunIdler(void)1875 void RunIdler(void)
1876 {
1877 s_IdlerWrapper.Get().RunIdler();
1878 }
1879
1880
1881 END_NCBI_SCOPE
1882