///////////////////////////////////////////////////////////////////////////////// // // Copyright (C) 2011-2012 Statoil ASA, Ceetron AS // // ResInsight is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // ResInsight is distributed in the hope that it will be useful, but WITHOUT ANY // WARRANTY; without even the implied warranty of MERCHANTABILITY or // FITNESS FOR A PARTICULAR PURPOSE. // // See the GNU General Public License at // for more details. // ///////////////////////////////////////////////////////////////////////////////// #include "RiaApplication.h" #include "RiaArgumentParser.h" #include "RiaMainTools.h" #include "RiaOpenTelemetryManager.h" #include "RiaPreferences.h" #include "RiaQuantityInfoTools.h" #include "RiaStdStringTools.h" #ifdef ENABLE_GRPC #include "RiaGrpcConsoleApplication.h" #include "RiaGrpcGuiApplication.h" #else #include "RiaConsoleApplication.h" #include "RiaGuiApplication.h" #endif #include "cafUiAppearanceSettings.h" #include "cvfProgramOptions.h" #include "cvfqtUtils.h" #include #include #include #ifndef WIN32 #include #include #endif #include #include void manageSegFailure( int signalCode ); void manageTerminate(); #ifndef WIN32 void manageSegFailureSA( int signalCode, siginfo_t* info, void* ucontext ); #endif namespace { QtMessageHandler g_previousMessageHandler = nullptr; //-------------------------------------------------------------------------------------------------- /// Filter out a harmless Qt6 warning seen on some Linux platforms (e.g. RHEL8) where Qt validates an /// empty (all-zero) QColorSpacePrimaries and prints: /// "QColorSpace attempted constructed from invalid primaries: QPointF(0,0) ..." /// The message has no functional impact, so suppress it while forwarding everything else unchanged. /// See https://github.com/OPM/ResInsight/issues/11802 //-------------------------------------------------------------------------------------------------- void riaFilteredMessageHandler( QtMsgType type, const QMessageLogContext& context, const QString& msg ) { if ( msg.startsWith( QLatin1String( "QColorSpace attempted constructed from invalid primaries" ) ) ) { return; } if ( g_previousMessageHandler ) { g_previousMessageHandler( type, context, msg ); } } } // namespace RiaApplication* createApplication( int& argc, char* argv[] ) { for ( int i = 1; i < argc; ++i ) { if ( !qstrcmp( argv[i], "--console" ) || !qstrcmp( argv[i], "--unittest" ) ) { #ifdef ENABLE_GRPC return new RiaGrpcConsoleApplication( argc, argv ); #else return new RiaConsoleApplication( argc, argv ); #endif } } #ifdef ENABLE_GRPC return new RiaGrpcGuiApplication( argc, argv ); #else return new RiaGuiApplication( argc, argv ); #endif } int main( int argc, char* argv[] ) { #ifndef WIN32 // From Qt 5.3 and onwards Qt has a mechanism for checking this automatically // But it only checks user id not group id, so better to do it ourselves. if ( getuid() != geteuid() || getgid() != getegid() ) { std::cerr << "FATAL: The application binary appears to be running setuid or setgid, this is a security hole." << std::endl; return 1; } #endif // Install a message handler to filter out a harmless Qt6 color-space warning (see #11802) g_previousMessageHandler = qInstallMessageHandler( riaFilteredMessageHandler ); RiaMainTools::deleteStaleSettingsLockFiles(); // The Qt::AA_ShareOpenGLContexts setting is needed when we have multiple viz widgets in flight // and we have a setup where these widgets belong to different top-level windows, or end up // belonging to different top-level windows through re-parenting. // See test application QtTestBenchOpenGLWidget QApplication::setAttribute( Qt::AA_ShareOpenGLContexts ); // The visualization framework requires desktop OpenGL with the fixed-function pipeline. Request // this explicitly, as Qt may otherwise create an OpenGL ES or core profile context on some // platforms (e.g. Wayland/EGL), causing all 3D rendering to fail. { QSurfaceFormat surfaceFormat = QSurfaceFormat::defaultFormat(); surfaceFormat.setRenderableType( QSurfaceFormat::OpenGL ); surfaceFormat.setProfile( QSurfaceFormat::CompatibilityProfile ); QSurfaceFormat::setDefaultFormat( surfaceFormat ); } // Create feature manager before the application object is created RiaMainTools::initializeSingletons(); RiaQuantityInfoTools::initializeSummaryKeywords(); // https://www.w3.org/wiki/CSS/Properties/color/keywords caf::UiAppearanceSettings::instance()->setAutoValueEditorColor( "moccasin" ); std::unique_ptr app( createApplication( argc, argv ) ); cvf::ProgramOptions progOpt; bool result = RiaArgumentParser::parseArguments( &progOpt ); const cvf::String usageText = progOpt.usageText( 110, 30 ); app->initialize(); app->setCommandLineHelpText( cvfqt::Utils::toQString( usageText ) ); // Report application startup to OpenTelemetry { std::map attributes; bool isConsoleMode = ( dynamic_cast( app.get() ) == nullptr ); attributes["app.console_mode"] = RiaStdStringTools::boolToString( isConsoleMode ); attributes["app.version"] = RiaApplication::getVersionStringApp( false ); RiaOpenTelemetryManager::instance().reportEventAsync( "app.started", attributes ); } if ( !result ) { std::vector unknownOptions = progOpt.unknownOptions(); QString unknownOptionsText; for ( cvf::String option : unknownOptions ) { unknownOptionsText += QString( "\tUnknown option: %1\n" ).arg( cvfqt::Utils::toQString( option ) ); } app->showFormattedTextInMessageBoxOrConsole( "ERROR: Unknown command line options detected ! \n" + unknownOptionsText + "\n\n" + "The current command line options in ResInsight are:\n" + app->commandLineParameterHelp() ); if ( dynamic_cast( app.get() ) == nullptr ) { return 1; } } QLocale::setDefault( QLocale( QLocale::English, QLocale::UnitedStates ) ); setlocale( LC_NUMERIC, "C" ); // Set up signal handlers for genuine fault/abort signals only. // SIGINT and SIGTERM are graceful termination requests (Ctrl-C, kill, or a // controlling process such as the Python rips client). They must NOT be routed // through the crash handler: doing so captures the stack trace of wherever the // main thread happens to be parked - e.g. inside the multi-second gRPC server // EventEngine teardown during normal shutdown - and reports it as a spurious // "Crash". Leaving them with their default disposition lets the process // terminate cleanly. #ifndef WIN32 // Use SA_SIGINFO on Linux to capture fault address, signal code, and program counter struct sigaction sa{}; sa.sa_sigaction = manageSegFailureSA; sa.sa_flags = SA_SIGINFO; sigemptyset( &sa.sa_mask ); sigaction( SIGILL, &sa, nullptr ); sigaction( SIGFPE, &sa, nullptr ); sigaction( SIGSEGV, &sa, nullptr ); sigaction( SIGABRT, &sa, nullptr ); #else signal( SIGILL, manageSegFailure ); signal( SIGFPE, manageSegFailure ); signal( SIGSEGV, manageSegFailure ); signal( SIGABRT, manageSegFailure ); #endif // Capture uncaught C++ exceptions at the throw site (before the stack unwinds) with full type, // message and stack trace. The SIGABRT signal handler above remains a fallback for direct // abort()/assert() failures that do not go through std::terminate. std::set_terminate( manageTerminate ); // Handle the command line arguments. // Todo: Move to a one-shot timer, delaying the execution until we are inside the event loop. // The complete handling of the resulting ApplicationStatus must be moved along. // The reason for this is: deleteLater() does not work outside the event loop // Make execution of command line stuff operate in identical conditions as interactive operation. RiaApplication::ApplicationStatus status = app->handleArguments( &progOpt ); if ( status == RiaApplication::ApplicationStatus::EXIT_COMPLETED ) { // Make sure project is closed to avoid assert and crash in destruction of widgets app->closeProject(); app.reset(); RiaMainTools::releaseSingletonAndFactoryObjects(); return 0; } else if ( status == RiaApplication::ApplicationStatus::EXIT_WITH_ERROR ) { // Make sure project is closed to avoid assert and crash in destruction of widgets app->closeProject(); return 2; } else if ( status == RiaApplication::ApplicationStatus::KEEP_GOING ) { int exitCode = 0; // No try/catch around the event loop: an uncaught exception must reach std::set_terminate // (manageTerminate) with the stack still intact so the crash report captures the throw site. { #ifdef ENABLE_GRPC auto grpcInterface = dynamic_cast( app.get() ); if ( grpcInterface && grpcInterface->initializeGrpcServer( progOpt ) ) { grpcInterface->launchGrpcServer(); if ( cvf::Option o = progOpt.option( "portnumberfile" ) ) { if ( o.valueCount() == 1 ) { int portNumber = grpcInterface->portNumber(); QString fileName = QString::fromStdString( o.value( 0 ).toStdString() ); // Write port number to the file given file. // Temp file is used to avoid incomplete reads. QString tempFilePath = fileName + ".tmp"; QFile file( tempFilePath ); if ( file.open( QIODevice::WriteOnly | QIODevice::Text ) ) { QTextStream out( &file ); out << portNumber << "\n"; } file.close(); QFile::rename( tempFilePath, fileName ); } } } #endif // Check if max thread count is set either from command line or preferences app->setThreadCount(); exitCode = QCoreApplication::instance()->exec(); } app.reset(); RiaMainTools::releaseSingletonAndFactoryObjects(); return exitCode; } CVF_ASSERT( false && "Unknown ApplicationStatus" ); return -1; }