1 // Copyright (C) 2007-2023 CEA, EDF, OPEN CASCADE
3 // This library is free software; you can redistribute it and/or
4 // modify it under the terms of the GNU Lesser General Public
5 // License as published by the Free Software Foundation; either
6 // version 2.1 of the License, or (at your option) any later version.
8 // This library is distributed in the hope that it will be useful,
9 // but WITHOUT ANY WARRANTY; without even the implied warranty of
10 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
11 // Lesser General Public License for more details.
13 // You should have received a copy of the GNU Lesser General Public
14 // License along with this library; if not, write to the Free Software
15 // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
17 // See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com
19 // File : SMESHUtils_MGLicenseKeyGen.cxx
20 // Created : Sat Jul 31 18:54:16 2021
21 // Author : Edward AGAPOV (OCC)
23 #include "SMESH_MGLicenseKeyGen.hxx"
25 #include "SMESH_Comment.hxx"
26 #include "SMESH_File.hxx"
27 #include "SMESH_TryCatch.hxx"
29 #include <Basics_DirUtils.hxx>
30 #include <Basics_Utils.hxx>
32 #include <cstdlib> // getenv, system
34 #include <boost/filesystem.hpp>
35 #include <boost/regex.hpp>
36 namespace boofs = boost::filesystem;
43 # define LibHandle HMODULE
44 # define LoadLib( name ) LoadLibrary( name )
45 # define GetProc GetProcAddress
46 # define UnLoadLib( handle ) FreeLibrary( handle );
52 # define LibHandle void*
53 # define LoadLib( name ) dlopen( name, RTLD_LAZY | RTLD_LOCAL )
54 # define GetProc dlsym
55 # define UnLoadLib( handle ) dlclose( handle );
59 // to retrieve description of exception caught by SMESH_TRY
61 #define SMESH_CAUGHT error =
63 constexpr char MESHGEMS_OLD_STYLE[] = "MESHGEMS_OLD_STYLE";
64 constexpr char SPATIAL_LICENSE[] = "SPATIAL_LICENSE";
69 static LibHandle theLibraryHandle = nullptr; //!< handle of a loaded library
71 const char* theEnvVar = "SALOME_MG_KEYGEN_LIB_PATH"; /* var specifies either full file name
72 of libSalomeMeshGemsKeyGenerator or
73 URL to download the library from */
75 const char* theTmpEnvVar = "SALOME_TMP_DIR"; // directory to download the library to
77 //-----------------------------------------------------------------------------------
79 * \brief Remove library file at destruction in case if it was downloaded from server
81 //-----------------------------------------------------------------------------------
85 std::string _name; // full file name
88 LibraryFile(): _isURL( false ) {}
94 if ( theLibraryHandle )
96 UnLoadLib( theLibraryHandle );
97 theLibraryHandle = nullptr;
100 std::string tmpDir; // tmp dir that should not be removed
101 if ( const char* libPath = getenv( theTmpEnvVar ))
104 while (( !tmpDir.empty() ) &&
105 ( tmpDir.back() == '/' || tmpDir.back() == '\\' ))
109 while ( SMESH_File( _name ).remove() )
111 size_t length = _name.size();
112 _name = boofs::path( _name ).parent_path().string(); // goto parent directory
113 if ( _name.size() == length )
114 break; // no more parents
116 if ( _name == tmpDir )
117 break; // don't remove tmp dir
119 if ( !Kernel_Utils::IsEmptyDir( _name ))
127 //================================================================================
129 * \brief Retrieve description of the last error
130 * \param [out] error - return the description
131 * \return bool - true if the description found
133 //================================================================================
135 bool getLastError( std::string& error )
139 if ( const char* text = dlerror() )
148 DWORD dw = GetLastError();
150 DWORD msgLen = FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM,
153 MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), // Default language
159 # if defined( UNICODE )
160 error = Kernel_Utils::encode_s((wchar_t*)cstr);
172 //================================================================================
174 * \brief Adjust file extension according to the platform
176 //================================================================================
178 bool setExtension( std::string& fileName, std::string& error )
180 if ( fileName.empty() )
182 error = "Library file name is empty";
186 std::string ext = ".dll";
187 #elif defined(__APPLE__)
188 std::string ext = ".dylib";
190 std::string ext = ".so";
193 fileName = fileName.substr( 0, fileName.find_last_of('.')) + ext;
197 //================================================================================
199 * \brief Check if library file name looks like an URL
200 * \param [in,out] libraryFile - holds file name and returns result in _isURL member field
201 * \return bool - true if the file name looks like an URL
203 //================================================================================
205 bool isURL( LibraryFile & libraryFile )
208 enum { SCHEME = 2, AUTHORITY = 4, PATH = 5 }; // sub-strings
209 boost::regex urlRegex ( R"(^(([^:\/?#]+):)?(//([^\/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?)",
210 boost::regex::extended );
211 boost::smatch matchResult;
213 libraryFile._isURL = false;
214 if ( boost::regex_match( libraryFile._name, matchResult, urlRegex ))
215 libraryFile._isURL = ( !matchResult.str( SCHEME ).empty() &&
216 !matchResult.str( AUTHORITY ).empty() &&
217 !matchResult.str( PATH ).empty() );
219 if(libraryFile._isURL)
222 enum { HOST = 2, PORT = 3, PATH = 4 }; // sub-strings
223 boost::regex urlRegex ( R"(^(([^:\/?#]+):)?([^/]+)?(/[^#]*))",
224 boost::regex::extended );
225 boost::smatch matchResult;
227 libraryFile._isURL = false;
228 if ( boost::regex_match( libraryFile._name, matchResult, urlRegex ))
229 libraryFile._isURL = ( !matchResult.str( HOST ).empty() &&
230 !matchResult.str( PORT ).empty() &&
231 !matchResult.str( PATH ).empty() );
233 return libraryFile._isURL;
236 //================================================================================
238 * \brief Download libraryFile._name URL to SALOME_TMP_DIR
239 * \param [in,out] libraryFile - holds the URL and returns name of a downloaded file
240 * \param [out] error - return error description
241 * \return bool - is a success
243 //================================================================================
245 bool downloadLib( LibraryFile& libraryFile, std::string & error )
247 // check if can write into SALOME_TMP_DIR
249 std::string tmpDir = Kernel_Utils::GetTmpDirByEnv( theTmpEnvVar );
250 if ( tmpDir.empty() ||
251 !Kernel_Utils::IsExists( tmpDir ))
253 error = "Can't download " + libraryFile._name + " as SALOME_TMP_DIR is not correctly set";
256 if ( !Kernel_Utils::IsWritable( tmpDir ))
258 error = "Can't download " + libraryFile._name + " as '" + tmpDir + "' is not writable. "
259 "Check SALOME_TMP_DIR environment variable";
265 std::string url = libraryFile._name;
269 std::string outFile = tmpDir + "MeshGemsKeyGenerator.dll";
271 // use wget (== Invoke-WebRequest) PowerShell command available since Windows 7
272 std::string psCmd = "wget -Uri " + url + " -OutFile " + outFile;
273 std::string cmd = "powershell.exe " + psCmd;
277 std::string outFile = tmpDir + "libMeshGemsKeyGenerator.so";
279 std::string cmd = "smesh_wget.py " + url + " -O " + outFile;
283 if ( Kernel_Utils::IsExists( outFile )) // remove existing file
285 SMESH_File lib( outFile, /*open=*/false );
295 std::string redirect = tmpDir + "redirect.out";
296 std::ostringstream oss;
297 oss << cmd << " " << redirect;
301 system( cmd.c_str() ); // download
305 std::ifstream infile(redirect);
306 infile.seekg(0, std::ios::end);
307 size_t length = infile.tellg();
308 infile.seekg(0, std::ios::beg);
309 std::unique_ptr<char []> buffer(new char[length+1]);
310 buffer[length] = '\0';
311 infile.read(const_cast<char *>( buffer.get() ),length);
313 MESSAGE( buffer.get() );
316 SMESH_File redirectFile( redirect, /*open=*/false );
317 redirectFile.remove();
321 SMESH_File resultFile( outFile, /*open=*/false );
322 bool ok = ( resultFile.exists() && resultFile.size() > 0 );
325 libraryFile._name = outFile;
327 error = "Can't download file " + url;
332 //================================================================================
334 * \brief Load libMeshGemsKeyGenerator.so
335 * \param [out] error - return error description
336 * \param [out] libraryFile - return library file name and _isURL flag
337 * \return bool - is a success
339 //================================================================================
341 bool loadLibrary( std::string& error, LibraryFile& libraryFile )
343 if ( theLibraryHandle )
346 const char* libPath = getenv( theEnvVar );
349 error = SMESH_Comment( "Environment variable ") << theEnvVar << " is not set";
353 libraryFile._name = libPath;
354 // if ( !setExtension( libraryFile._name, error )) // is it necessary?
357 if ( isURL( libraryFile ))
359 if ( !downloadLib( libraryFile, error ))
361 // try to fix extension
362 std::string url = libraryFile._name;
363 if ( !setExtension( libraryFile._name, error ))
365 if ( url == libraryFile._name )
366 return false; // extension not changed
368 if ( !downloadLib( libraryFile, error ))
373 #if defined( WIN32 ) && defined( UNICODE )
374 std::wstring encodePath = Kernel_Utils::utf8_decode_s( libraryFile._name );
375 const wchar_t* path = encodePath.c_str();
377 const char* path = libraryFile._name.c_str();
380 theLibraryHandle = LoadLib( path );
381 if ( !theLibraryHandle )
383 if ( ! getLastError( error ))
384 error = "Can't load library '" + libraryFile._name + "'";
387 return theLibraryHandle;
390 } // anonymous namespace
393 namespace SMESHUtils_MGLicenseKeyGen // API implementation
395 //================================================================================
398 * \param [in] meshgems_cad - pointer to a MG CAD object (meshgems_cad_t)
399 * \param [out] error - return error description
400 * \return bool - is a success
402 //================================================================================
404 bool SignCAD_After( void* meshgems_cad, std::string& error )
406 LibraryFile libraryFile;
407 if ( !loadLibrary( error, libraryFile ))
411 typedef bool (*SignFun)(void* );
412 SignFun signFun = (SignFun) GetProc( theLibraryHandle, "SignCAD" );
415 if ( ! getLastError( error ))
416 error = SMESH_Comment( "Can't find symbol 'SignCAD' in '") << getenv( theEnvVar ) << "'";
422 ok = signFun( meshgems_cad );
424 SMESH_CATCH( SMESH::returnError );
426 if ( !error.empty() )
429 error = "SignCAD() failed (located in '" + libraryFile._name + "')";
434 //================================================================================
436 * \brief Unlock a specific MeshGems product (for products called as a library)
437 * \param [in] product - product of MeshGems to unlock
438 * \param [out] error - return error description
439 * \return bool - is a success
441 //================================================================================
442 bool UnlockProduct( const std::string& product, std::string& error )
444 MESSAGE("SMESH UnlockProduct: " << product);
445 LibraryFile libraryFile;
446 if ( !loadLibrary( error, libraryFile ))
450 typedef bool (*SignFun)(const std::string& );
452 // specific function to unlock each product
453 std::string function = "UnlockProduct";
455 SignFun signFun = (SignFun) GetProc( theLibraryHandle, function.c_str() );
458 if ( ! getLastError( error ))
459 error = SMESH_Comment( "Can't find symbol '") << function << "' in '" << getenv( theEnvVar ) << "'";
465 ok = signFun( product.c_str() );
467 SMESH_CATCH( SMESH::returnError );
469 if ( !error.empty() )
471 std::cerr << "error: " << error << std::endl;
475 error = "UnlockProduct() failed (located in '" + libraryFile._name + "')";
480 //================================================================================
482 * \brief Sign a CAD (or don't do it if env MESHGEMS_OLD_STYLE is set)
483 * \param [in] meshgems_cad - pointer to a MG CAD object (meshgems_cad_t)
484 * \param [out] error - return error description
485 * \return bool - is a success
487 //================================================================================
488 bool SignCAD( void* meshgems_cad, std::string& error )
490 const char *meshGemsOldStyleEnvVar( getenv( MESHGEMS_OLD_STYLE ) );
491 if ( !meshGemsOldStyleEnvVar || strlen(meshGemsOldStyleEnvVar) == 0 )
493 if (NeedsMGSpatialEnvLicense(error))
494 // SignCAD is only called by cadsurf. Other components call SignMesh
495 return UnlockProduct("cadsurf", error);
497 return SignCAD_After(meshgems_cad, error);
503 //================================================================================
506 * \param [in] meshgems_mesh - pointer to a MG mesh (meshgems_mesh_t)
507 * \param [out] error - return error description
508 * \return bool - is a success
510 //================================================================================
512 bool SignMesh_After( void* meshgems_mesh, std::string& error )
514 LibraryFile libraryFile;
515 if ( !loadLibrary( error, libraryFile ))
519 typedef bool (*SignFun)(void* );
520 SignFun signFun = (SignFun) GetProc( theLibraryHandle, "SignMesh" );
523 if ( ! getLastError( error ))
524 error = SMESH_Comment( "Can't find symbol 'SignMesh' in '") << getenv( theEnvVar ) << "'";
530 ok = signFun( meshgems_mesh );
532 SMESH_CATCH( SMESH::returnError );
534 if ( !error.empty() )
537 error = "SignMesh() failed (located in '" + libraryFile._name + "')";
542 //================================================================================
544 * \brief Sign a mesh (or don't do it if env MESHGEMS_OLD_STYLE is set)
545 * \param [in] meshgems_mesh - pointer to a MG mesh (meshgems_mesh_t)
546 * \param [in] product - product of MeshGems to unlock
547 * \param [out] error - return error description
548 * \return bool - is a success
550 //================================================================================
551 bool SignMesh( void* meshgems_mesh, const std::string& product, std::string& error )
553 const char *meshGemsOldStyleEnvVar( getenv( MESHGEMS_OLD_STYLE ) );
554 if ( !meshGemsOldStyleEnvVar || strlen(meshGemsOldStyleEnvVar) == 0 )
556 if (NeedsMGSpatialEnvLicense(error))
557 // unlock product (MG 2.15)
558 return UnlockProduct(product, error);
560 // sign the mesh (MG 2.13 and 2.14)
561 return SignMesh_After(meshgems_mesh, error);
564 // use DLIM8 server (nothing to do here)
568 //================================================================================
570 * \brief Return a license key to pass as argument to a MG mesher executable
571 * \param [in] gmfFile - path to an input mesh file
572 * \param [in] nb* - nb of entities in the input mesh
573 * \param [out] error - return error description
574 * \return std::string - the key
576 //================================================================================
578 std::string GetKey_After(const std::string& gmfFile,
586 LibraryFile libraryFile;
587 if ( !loadLibrary( error, libraryFile ))
590 typedef std::string (*GetKeyFun)(std::string const &, int, int, int, int );
591 GetKeyFun keyFun = (GetKeyFun) GetProc( theLibraryHandle, "GetKey" );
594 if ( ! getLastError( error ))
595 error = SMESH_Comment( "Can't find symbol 'GetKey' in '") << getenv( theEnvVar ) << "'";
599 key = keyFun( gmfFile, nbVertex, nbEdge, nbFace, nbVol );
602 error = "GetKey() failed (located in '" + libraryFile._name + "')";
607 //================================================================================
609 * \brief Return a license key to pass as argument to a MG mesher executable (>2.15)
610 * \param [out] error - return error description
611 * \return std::string - the key
613 //================================================================================
615 std::string GetKey_After(std::string& error)
618 LibraryFile libraryFile;
619 if ( !loadLibrary( error, libraryFile ))
622 typedef std::string (*GetKeyFun)();
623 GetKeyFun keyFun = (GetKeyFun) GetProc( theLibraryHandle, "GetKey" );
626 if ( ! getLastError( error ))
627 error = SMESH_Comment( "Can't find symbol 'GetKey' in '") << getenv( theEnvVar ) << "'";
634 error = "GetKey() failed (located in '" + libraryFile._name + "')";
639 //================================================================================
641 * \brief Get MeshGems version major/minor/patch from the environment variables
642 * \param [out] error - return error description
643 * \return int - the version
645 //================================================================================
646 int GetMGVersionFromEnv(const char* env_variable)
648 MESSAGE("Entering GetMGVersionFromEnv and calling " << env_variable);
650 if (getenv(env_variable) == nullptr )
652 MESSAGE("Could not find " << env_variable << " from environment");
656 version = std::stoi(std::string(getenv(env_variable)));
660 //================================================================================
662 * \brief Get MeshGems version major/minor/patch from the keygen library and meshgems built-in functions
663 * \param [out] error - return error description
664 * \return int - the function implemented in the library
666 //================================================================================
667 int GetMGVersionFromFunction(const char* function_name)
669 MESSAGE("Entering GetMGVersionFromFunction and calling " << function_name);
671 typedef int (*GetKeyFun)();
672 GetKeyFun keyFun = (GetKeyFun) GetProc( theLibraryHandle, function_name);
675 MESSAGE("Could not find " << function_name << " from library");
684 //================================================================================
686 * \brief Get MeshGems version from the keygen library or meshgems built-in functions
687 * \param [out] error - return error description
688 * \return int - the version
690 //================================================================================
691 int GetMGVersionHex(std::string& error)
693 // load mgkeygen library
695 LibraryFile libraryFile;
696 if ( !loadLibrary( error, libraryFile ))
698 MESSAGE("Extracting MeshGems version");
701 v_min = GetMGVersionFromFunction("meshgems_core_get_version_minor");
703 v_min = GetMGVersionFromFunction("GetVersionMinor");
705 v_min = GetMGVersionFromEnv("MESHGEMS_VERSION_MINOR");
707 error = "could not retrieve minor version (located in '" + libraryFile._name + "')";
708 MESSAGE("MeshGems minor version = " << v_min);
711 int v_maj = GetMGVersionFromFunction("meshgems_core_get_version_major");
713 v_maj = GetMGVersionFromFunction("GetVersionMajor");
715 v_maj = GetMGVersionFromEnv("MESHGEMS_VERSION_MAJOR");
717 error = "could not retrieve major version (located in '" + libraryFile._name + "')";
718 MESSAGE("MeshGems major version = " << v_maj);
721 int v_patch = GetMGVersionFromFunction("meshgems_core_get_version_patch");
723 v_patch = GetMGVersionFromFunction("GetVersionPatch");
725 v_patch = GetMGVersionFromEnv("MESHGEMS_VERSION_PATCH");
727 error = "could not retrieve patch version (located in '" + libraryFile._name + "')";
728 MESSAGE("MeshGems patch version = " << v_patch);
730 int v_hex = (v_maj << 16 | v_min << 8 | v_patch);
731 MESSAGE("v_hex: " << v_hex);
736 //================================================================================
738 * \brief Guess if the Spatial license is needed (if MeshGems is > 2.15.0)
739 * \param [out] error - return error description
740 * \return bool - true if MeshGems is > 2.15.0
742 //================================================================================
743 bool NeedsMGSpatialEnvLicense(std::string& error)
745 // if MeshGems version is > 2.15.0, need to set SPATIAL_LICENSE
746 int v_hex = GetMGVersionHex(error);
747 bool ok = (v_hex > MESHGEMS_215);
749 MESSAGE("MeshGems version is > 2.15.0, need to set SPATIAL_LICENSE");
753 //================================================================================
755 * \brief Set the SPATIAL_LICENSE environment variable
756 * \param [out] error - return error description
757 * \return bool - true in case of success
759 //================================================================================
760 bool SetMGSpatialEnvLicense(std::string& error)
763 std::string key = GetKey(error);
765 ok = setenv(SPATIAL_LICENSE, key.c_str(), 0); // 0 means do not overwrite
767 ok = Kernel_Utils::setenv(SPATIAL_LICENSE, key.c_str(), 0 );
769 MESSAGE("Set SPATIAL_LICENSE");
773 //================================================================================
775 * \brief Get the license key from libMeshGemsKeyGenerator.so or $SPATIAL_LICENSE
776 * Called by plugins calling MG products as executables.
777 * If MESHGEMS_OLD_STYLE is set, return "0", to use old DLIM8 server license
778 * instead of the key.
779 * \param [in] gmfFile - path to an input mesh file
780 * \param [in] nb* - nb of entities in the input mesh
781 * \param [out] error - return error description
782 * \return std::string - the key
784 //================================================================================
785 std::string GetKey(const std::string& gmfFile,
792 // default key if MESHGEMS_OLD_STYLE or SPATIAL_LICENSE is set
793 std::string key("0");
794 const char *meshGemsOldStyleEnvVar( getenv( MESHGEMS_OLD_STYLE ) );
795 if ( !meshGemsOldStyleEnvVar || strlen(meshGemsOldStyleEnvVar) == 0 )
797 const char *spatialLicenseEnvVar( getenv( SPATIAL_LICENSE ) );
798 if ( !spatialLicenseEnvVar || strlen(spatialLicenseEnvVar) == 0 )
800 if (NeedsMGSpatialEnvLicense(error))
802 // if MG version > 2.15, set environment license, don't return it as a key
803 // otherwise it will be printed in the command line
804 MESSAGE("SPATIAL_LICENSE not in env => we add it from MGKeygen .so");
805 SetMGSpatialEnvLicense(error);
809 // generate the key from the mesh info (MG 2.13 and 2.14)
810 MESSAGE("MG < 2.15 => get the key from MGKeygen .so and this mesh info");
811 key = GetKey_After(gmfFile,nbVertex,nbEdge,nbFace,nbVol,error);
815 MESSAGE("SPATIAL_LICENSE already in env => we use it");
822 //================================================================================
824 * \brief Get the license key from libMeshGemsKeyGenerator.so or $SPATIAL_LICENSE
825 * Called for MG 2.15 by CADSurf and MG plugins calling MG products as library,
826 * i.e. compiled as library with -DSALOME_USE_MG_LIBS=ON
827 * \param [out] error - return error description
828 * \return std::string - the key
830 //================================================================================
831 std::string GetKey(std::string& error)
833 // default key if not found in .so or in SPATIAL_LICENSE
834 std::string key("0");
835 const char *meshGemsOldStyleEnvVar( getenv( MESHGEMS_OLD_STYLE ) );
836 if ( !meshGemsOldStyleEnvVar || strlen(meshGemsOldStyleEnvVar) == 0 ){
837 const char *spatialLicenseEnvVar( getenv( SPATIAL_LICENSE ) );
838 if ( !spatialLicenseEnvVar || strlen(spatialLicenseEnvVar) == 0 )
840 MESSAGE("SPATIAL_LICENSE not in env => we add it from MGKeygen .so");
841 // use new style, i.e. key in a library
842 key = GetKey_After(error);
846 MESSAGE("SPATIAL_LICENSE already in env => we use it");
847 key = std::string(spatialLicenseEnvVar);
855 //================================================================================
857 * \brief Return false if libMeshGemsKeyGenerator.so is not functional
858 * \param [out] error - return error description
859 * \return bool - is a success
861 //================================================================================
863 bool CheckKeyGenLibrary( std::string& error )
865 return !GetKey("",4,0,2,0,error ).empty();
868 //================================================================================
870 * \brief Return KeyGenerator library name
872 //================================================================================
874 std::string GetLibraryName()
876 std::string libName, error;
877 if ( const char* libPath = getenv( theEnvVar ))
879 libName = Kernel_Utils::GetBaseName( libPath );
883 libName = "libSalomeMeshGemsKeyGenerator";
885 setExtension( libName, error );