/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying file Copyright.txt or https://cmake.org/licensing for details. */ #if !defined(_WIN32) && !defined(__sun) // POSIX APIs are needed // NOLINTNEXTLINE(bugprone-reserved-identifier) # define _POSIX_C_SOURCE 200809L #endif #if defined(__OpenBSD__) || defined(__FreeBSD__) || defined(__NetBSD__) // For isascii // NOLINTNEXTLINE(bugprone-reserved-identifier) # define _XOPEN_SOURCE 700 #endif #include "cmLoadCommandCommand.h" #include #include #include #include #include #include #include "cmCPluginAPI.h" #include "cmCommand.h" #include "cmDynamicLoader.h" #include "cmExecutionStatus.h" #include "cmListFileCache.h" #include "cmLocalGenerator.h" #include "cmMakefile.h" #include "cmState.h" #include "cmStringAlgorithms.h" #include "cmSystemTools.h" // NOLINTNEXTLINE(bugprone-suspicious-include) #include "cmCPluginAPI.cxx" #ifdef __QNX__ # include /* for malloc/free on QNX */ #endif namespace { const char* LastName = nullptr; extern "C" void TrapsForSignals(int sig) { fprintf(stderr, "CMake loaded command %s crashed with signal: %d.\n", LastName, sig); } struct SignalHandlerGuard { explicit SignalHandlerGuard(const char* name) { LastName = name != nullptr ? name : "????"; signal(SIGSEGV, TrapsForSignals); #ifdef SIGBUS signal(SIGBUS, TrapsForSignals); #endif signal(SIGILL, TrapsForSignals); } ~SignalHandlerGuard() { signal(SIGSEGV, nullptr); #ifdef SIGBUS signal(SIGBUS, nullptr); #endif signal(SIGILL, nullptr); } SignalHandlerGuard(SignalHandlerGuard const&) = delete; SignalHandlerGuard& operator=(SignalHandlerGuard const&) = delete; }; struct LoadedCommandImpl : cmLoadedCommandInfo { explicit LoadedCommandImpl(CM_INIT_FUNCTION init) : cmLoadedCommandInfo{ 0, 0, &cmStaticCAPI, 0, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr } { init(this); } ~LoadedCommandImpl() { if (this->Destructor) { SignalHandlerGuard guard(this->Name); this->Destructor(this); } if (this->Error != nullptr) { free(this->Error); } } LoadedCommandImpl(LoadedCommandImpl const&) = delete; LoadedCommandImpl& operator=(LoadedCommandImpl const&) = delete; int DoInitialPass(cmMakefile* mf, int argc, char* argv[]) { SignalHandlerGuard guard(this->Name); return this->InitialPass(this, mf, argc, argv); } void DoFinalPass(cmMakefile* mf) { SignalHandlerGuard guard(this->Name); this->FinalPass(this, mf); } }; // a class for loadabple commands class cmLoadedCommand : public cmCommand { public: cmLoadedCommand() = default; explicit cmLoadedCommand(CM_INIT_FUNCTION init) : Impl(std::make_shared(init)) { } /** * This is a virtual constructor for the command. */ std::unique_ptr Clone() override { auto newC = cm::make_unique(); // we must copy when we clone newC->Impl = this->Impl; return std::unique_ptr(std::move(newC)); } /** * This is called when the command is first encountered in * the CMakeLists.txt file. */ bool InitialPass(std::vector const& args, cmExecutionStatus&) override; private: std::shared_ptr Impl; }; bool cmLoadedCommand::InitialPass(std::vector const& args, cmExecutionStatus&) { if (!this->Impl->InitialPass) { return true; } // clear the error string if (this->Impl->Error) { free(this->Impl->Error); } // create argc and argv and then invoke the command int argc = static_cast(args.size()); char** argv = nullptr; if (argc) { argv = static_cast(malloc(argc * sizeof(char*))); } int i; for (i = 0; i < argc; ++i) { argv[i] = strdup(args[i].c_str()); } int result = this->Impl->DoInitialPass(this->Makefile, argc, argv); cmFreeArguments(argc, argv); if (result) { if (this->Impl->FinalPass) { auto impl = this->Impl; this->Makefile->AddGeneratorAction( [impl](cmLocalGenerator& lg, const cmListFileBacktrace&) { impl->DoFinalPass(lg.GetMakefile()); }); } return true; } /* Initial Pass must have failed so set the error string */ if (this->Impl->Error) { this->SetError(this->Impl->Error); } return false; } } // namespace // cmLoadCommandCommand bool cmLoadCommandCommand(std::vector const& args, cmExecutionStatus& status) { if (args.empty()) { return true; } // Construct a variable to report what file was loaded, if any. // Start by removing the definition in case of failure. std::string reportVar = cmStrCat("CMAKE_LOADED_COMMAND_", args[0]); status.GetMakefile().RemoveDefinition(reportVar); // the file must exist std::string moduleName = cmStrCat( status.GetMakefile().GetRequiredDefinition("CMAKE_SHARED_MODULE_PREFIX"), "cm", args[0], status.GetMakefile().GetRequiredDefinition("CMAKE_SHARED_MODULE_SUFFIX")); // search for the file std::vector path; for (unsigned int j = 1; j < args.size(); j++) { // expand variables std::string exp = args[j]; cmSystemTools::ExpandRegistryValues(exp); // Glob the entry in case of wildcards. cmSystemTools::GlobDirs(exp, path); } // Try to find the program. std::string fullPath = cmSystemTools::FindFile(moduleName, path); if (fullPath.empty()) { status.SetError(cmStrCat("Attempt to load command failed from file \"", moduleName, "\"")); return false; } // try loading the shared library / dll cmsys::DynamicLoader::LibraryHandle lib = cmDynamicLoader::OpenLibrary(fullPath.c_str()); if (!lib) { std::string err = cmStrCat("Attempt to load the library ", fullPath, " failed."); const char* error = cmsys::DynamicLoader::LastError(); if (error) { err += " Additional error info is:\n"; err += error; } status.SetError(err); return false; } // Report what file was loaded for this command. status.GetMakefile().AddDefinition(reportVar, fullPath); // find the init function std::string initFuncName = args[0] + "Init"; CM_INIT_FUNCTION initFunction = reinterpret_cast( cmsys::DynamicLoader::GetSymbolAddress(lib, initFuncName)); if (!initFunction) { initFuncName = cmStrCat('_', args[0], "Init"); initFunction = reinterpret_cast( cmsys::DynamicLoader::GetSymbolAddress(lib, initFuncName)); } // if the symbol is found call it to set the name on the // function blocker if (initFunction) { return status.GetMakefile().GetState()->AddScriptedCommand( args[0], BT( cmLegacyCommandWrapper(cm::make_unique(initFunction)), status.GetMakefile().GetBacktrace()), status.GetMakefile()); } status.SetError("Attempt to load command failed. " "No init function found."); return false; }