/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "platform/win/launcher_win.h" #include "core/crash_reports.h" #include "core/update_checker.h" #include #include #include #include #include namespace Platform { namespace { // Turn a CRT/STL assertion (or pure-virtual / invalid-parameter) into a real, // crash-reported failure instead of a modal Abort/Retry/Ignore dialog that // would block a headless test agent forever. [[noreturn]] void TestAgentReportCrash( const char *kind, const QString &message) { if (!message.isEmpty()) { CrashReports::SetAnnotation("CrtAssert", message); } // Be loud on stderr too (captured when the launch redirects it to a file). fprintf(stderr, "\n[testagent] %s: %s\n", kind, message.toLocal8Bit().constData()); fflush(stderr); // Crashes with an access violation -> caught by the crash reporter, dumped // to tdata/working + tdata/dumps, and the process exits non-zero. No dialog. Unexpected("Test agent: CRT/STL assertion violation."); } #ifdef _DEBUG int TestAgentReportHook(int reportType, char *message, int *returnValue) { if (returnValue) { *returnValue = 0; // Do not break into the (absent) debugger. } if (reportType == _CRT_ASSERT || reportType == _CRT_ERROR) { TestAgentReportCrash( "assert", QString::fromLocal8Bit(message ? message : "")); } else if (message) { fputs(message, stderr); fflush(stderr); } return TRUE; // Handled -> suppress the default message box. } int TestAgentReportHookW(int reportType, wchar_t *message, int *returnValue) { if (returnValue) { *returnValue = 0; } if (reportType == _CRT_ASSERT || reportType == _CRT_ERROR) { TestAgentReportCrash( "assert", QString::fromWCharArray(message ? message : L"")); } else if (message) { fputws(message, stderr); fflush(stderr); } return TRUE; } #endif // _DEBUG void InstallTestAgentCrashHandling() { // No Windows Error Reporting / general-protection / abort() message boxes — // each would block the test agent waiting on a button it cannot press. SetErrorMode(SEM_FAILCRITICALERRORS | SEM_NOGPFAULTERRORBOX | SEM_NOOPENFILEERRORBOX); _set_abort_behavior(0, _WRITE_ABORT_MSG | _CALL_REPORTFAULT); // Pure-virtual call / invalid CRT parameter -> reported crash, not a dialog. _set_purecall_handler([] { TestAgentReportCrash("purecall", u"Pure virtual function call"_q); }); _set_invalid_parameter_handler([]( const wchar_t *expression, const wchar_t *, const wchar_t *, unsigned int, uintptr_t) { TestAgentReportCrash( "invalid-parameter", QString::fromWCharArray( expression ? expression : L"CRT invalid parameter")); }); #ifdef _DEBUG // assert()/_ASSERTE and STL bounds/iterator checks (_STL_VERIFY) report via // _CrtDbgReport[W]; route them to stderr (never a window) and convert any // assertion into a reported crash through the hooks above. for (const auto type : { _CRT_WARN, _CRT_ERROR, _CRT_ASSERT }) { _CrtSetReportMode(type, _CRTDBG_MODE_FILE); _CrtSetReportFile(type, _CRTDBG_FILE_STDERR); } _CrtSetReportHook2(_CRT_RPTHOOK_INSTALL, TestAgentReportHook); _CrtSetReportHookW2(_CRT_RPTHOOK_INSTALL, TestAgentReportHookW); #endif // _DEBUG } } // namespace Launcher::Launcher(int argc, char *argv[]) : Core::Launcher(argc, argv) { } void Launcher::initHook() { if (cTestAgent()) { InstallTestAgentCrashHandling(); } } std::optional Launcher::readArgumentsHook( int argc, char *argv[]) const { auto count = 0; if (const auto list = CommandLineToArgvW(GetCommandLine(), &count)) { const auto guard = gsl::finally([&] { LocalFree(list); }); if (count > 0) { auto result = QStringList(); result.reserve(count); for (auto i = 0; i != count; ++i) { result.push_back(QString::fromWCharArray(list[i])); } return result; } } return std::nullopt; } bool Launcher::launchUpdater(UpdaterLaunch action) { if (cExeName().isEmpty()) { return false; } const auto operation = (action == UpdaterLaunch::JustRelaunch) ? QString() : (cWriteProtected() ? u"runas"_q : QString()); const auto binaryPath = (action == UpdaterLaunch::JustRelaunch) ? (cExeDir() + cExeName()) : (cWriteProtected() ? (cWorkingDir() + u"tupdates/temp/Updater.exe"_q) : (cExeDir() + u"Updater.exe"_q)); auto argumentsList = QStringList(); const auto pushArgument = [&](const QString &argument) { argumentsList.push_back(argument.trimmed()); }; if (cLaunchMode() == LaunchModeAutoStart) { pushArgument(u"-autostart"_q); } if (Logs::DebugEnabled()) { pushArgument(u"-debug"_q); } if (cStartInTray()) { pushArgument(u"-startintray"_q); } if (customWorkingDir()) { pushArgument(u"-workdir"_q); pushArgument('"' + cWorkingDir() + '"'); } if (cDataFile() != u"data"_q) { pushArgument(u"-key"_q); pushArgument('"' + cDataFile() + '"'); } if (action == UpdaterLaunch::JustRelaunch) { pushArgument(u"-noupdate"_q); if (cRestartingToSettings()) { pushArgument(u"-tosettings"_q); } } else { pushArgument(u"-update"_q); pushArgument(u"-exename"_q); pushArgument('"' + cExeName() + '"'); if (cWriteProtected()) { pushArgument(u"-writeprotected"_q); pushArgument('"' + cExeDir() + '"'); } } return launch(operation, binaryPath, argumentsList); } bool Launcher::launch( const QString &operation, const QString &binaryPath, const QStringList &argumentsList) { const auto convertPath = [](const QString &path) { return QDir::toNativeSeparators(path).toStdWString(); }; const auto nativeBinaryPath = convertPath(binaryPath); const auto nativeWorkingDir = convertPath(cWorkingDir()); const auto arguments = argumentsList.join(' '); DEBUG_LOG(("Application Info: executing %1 %2" ).arg(binaryPath ).arg(arguments )); Logs::closeMain(); CrashReports::Finish(); const auto hwnd = HWND(0); const auto result = ShellExecute( hwnd, operation.isEmpty() ? nullptr : operation.toStdWString().c_str(), nativeBinaryPath.c_str(), arguments.toStdWString().c_str(), nativeWorkingDir.empty() ? nullptr : nativeWorkingDir.c_str(), SW_SHOWNORMAL); if (int64(result) < 32) { DEBUG_LOG(("Application Error: failed to execute %1, working directory: '%2', result: %3" ).arg(binaryPath ).arg(cWorkingDir() ).arg(int64(result) )); return false; } return true; } } // namespace Platform