Back to home page

EIC code displayed by LXR

 
 

    


File indexing completed on 2025-01-18 10:17:32

0001 
0002 // Copyright 2012-2025, Jefferson Science Associates, LLC.
0003 // Subject to the terms in the LICENSE file found in the top-level directory.
0004 // Author: David Lawrence
0005 
0006 #include <JANA/JLogger.h>
0007 #include <JANA/Services/JParameterManager.h>
0008 #include <JANA/Calibrations/JResource.h>
0009 
0010 #include <md5.h>
0011 
0012 #include <stdlib.h>
0013 #include <unistd.h>
0014 #include <sys/stat.h>
0015 #include <libgen.h>
0016 
0017 #include <fstream>
0018 
0019 using namespace std;
0020 
0021 #ifdef HAVE_CURL
0022 #include <curl/curl.h>
0023 #endif // HAVE_CURL
0024 
0025 
0026 
0027 static pthread_mutex_t resource_manager_mutex = PTHREAD_MUTEX_INITIALIZER;
0028 static string CURRENT_OUTPUT_FNAME = "";
0029 
0030 static int mkpath(string s, mode_t mode = S_IRWXU | S_IRWXG | S_IRWXO);
0031 
0032 #ifdef HAVE_CURL
0033 static int mycurl_printprogress(void *clientp, double dltotal, double dlnow, double ultotal,  double ulnow);
0034 #endif // HAVE_CURL
0035 
0036 
0037 //---------------------------------
0038 // JResource    (Constructor)
0039 //---------------------------------
0040 JResource::JResource(std::shared_ptr<JParameterManager> params, JCalibration *jcalib, string resource_dir) {
0041     /// Creates a new resource manager. See class description for details
0042 
0043     // Record JCalibration object used to get URLs of resources from calibration DB.
0044     this->jcalib = jcalib;
0045 
0046     // Get list of existing namepaths so we can check if they exist without JCalibration subclass printing errors.
0047     jcalib->GetListOfNamepaths(calib_namepaths);
0048 
0049     // Derive location of resources directory on local system. This can be specified in several ways, given here in
0050     // order of precedence:
0051     //
0052     // 1. Passed as second argument to this constructor
0053     // 2. Specified in JANA:RESOURCE_DIR configuration parameter
0054     // 3. Specified in JANA_RESOURCE_DIR environment variable
0055     // 4. Specified in JANA:RESOURCE_DEFAULT_PATH configuration parameter
0056     // 5. Create a user directory in /tmp called "resources"
0057     //
0058     // Note that in nearly all instances, no second argument should be passed to the constructor so that the value can be changed
0059     // via run time parameters.
0060 
0061     // 5.
0062     string user = (getenv("USER") == NULL ? "jana" : getenv("USER"));
0063     this->resource_dir = "/tmp/" + user + "/resources";
0064 
0065     // 4.
0066     string RESOURCE_DEFAULT_PATH = "";
0067     if (params != nullptr) {
0068         // (make sure param is defined with proper description)
0069         string description = "Colon (:) separated list of potential directories to be used as the resource directory. Only used if not specified explicitly via JANA:RESOURCE_DIR config. param or JANA_RESOURCE_DIR envar. First directory found to exist is used.";
0070         JParameter *p = NULL;
0071 
0072         if (params->Exists("JANA:RESOURCE_DEFAULT_PATH")) {
0073             p = params->GetParameter("JANA:RESOURCE_DEFAULT_PATH", RESOURCE_DEFAULT_PATH);
0074         } else {
0075             p = params->SetParameter("JANA:RESOURCE_DEFAULT_PATH", RESOURCE_DEFAULT_PATH);
0076         }
0077         p->SetDescription(description);
0078     }
0079 
0080     if (RESOURCE_DEFAULT_PATH != "") {
0081         stringstream ss(RESOURCE_DEFAULT_PATH);
0082         string path;
0083         while (getline(ss, path, ':')) {
0084             struct stat sb;
0085             if (stat(path.c_str(), &sb) == 0 && S_ISDIR(sb.st_mode)) {
0086                 this->resource_dir = path;
0087                 break;
0088             }
0089         }
0090     }
0091 
0092     // 3.
0093     const char *JANA_RESOURCE_DIR = getenv("JANA_RESOURCE_DIR");
0094     if (JANA_RESOURCE_DIR) this->resource_dir = JANA_RESOURCE_DIR;
0095 
0096     // 2.
0097     if (params != nullptr) params->SetDefaultParameter("JANA:RESOURCE_DIR", this->resource_dir);
0098 
0099     // 1.
0100     if (resource_dir != "") this->resource_dir = resource_dir;
0101 
0102     // Create a JCalibrationFile file object that uses the
0103     // resource directory
0104     string local_url = "file://" + this->resource_dir;
0105     jcalibfile = new JCalibrationFile(local_url, 1);
0106 
0107     // Try and open the resources file and read it in
0108     ReadResourceInfoFile();
0109 
0110     // Check if user has specified a URL base that will be used to
0111     // override any found in the calib DB
0112     overide_URL_base = false;
0113     if (params != nullptr) {
0114         if (params->Exists("JANA:RESOURCE_URL")) {
0115             overide_URL_base = true;
0116             URL_base = "";
0117             params->SetDefaultParameter("JANA:RESOURCE_URL", URL_base,
0118                                         "Base URL to use for retrieving resources. If set, this will override any URL_base values found in the calib DB.");
0119         }
0120     }
0121 
0122 #ifdef HAVE_CURL
0123     // Initialize CURL system
0124     curl_global_init(CURL_GLOBAL_ALL);
0125     curl_args = "";
0126 #else
0127     // curl_args is only used of the CURL library is not
0128     curl_args = "--insecure";
0129     if (params)
0130         params->SetDefaultParameter("JANA:CURL_ARGS", curl_args,
0131                                     "additional arguments to be passed to the external curl program");
0132 #endif // HAVE_CURL
0133 
0134     // Allow user to turn off checking of md5 checksums
0135     check_md5 = true;
0136     if (params)
0137         params->SetDefaultParameter("JANA:RESOURCE_CHECK_MD5", check_md5,
0138                                     "Set this to 0 to disable checking of the md5 checksum for resource files. You generally want this check left on.");
0139 }
0140 
0141 //---------------------------------
0142 // ~JResource    (Destructor)
0143 //---------------------------------
0144 JResource::~JResource() {
0145     if (jcalibfile) delete jcalibfile;
0146 
0147 #ifdef HAVE_CURL
0148     // Cleanup CURL system
0149     curl_global_cleanup();
0150 #endif // HAVE_CURL
0151 }
0152 
0153 //---------------------------------
0154 // GetResource
0155 //---------------------------------
0156 string JResource::GetResource(string namepath) {
0157     string fullpath = GetLocalPathToResource(namepath);
0158 
0159     // If a calibration object was specified, then use it to check
0160     // for the URL and path to use.
0161     if (jcalib) {
0162         // Get URL and local filename of resource relative to the
0163         // resources directory from the JCalibration object.
0164         map<string, string> info;
0165         jcalib->Get(namepath, info);
0166 
0167         // We provide 2 options here:
0168         //
0169         // Option 1.) The DB provides a "URL_base" string and a "path"
0170         // string. These are combined to make the full URL, and the
0171         // "path" is appended to the resource_dir to generate the local
0172         // path. One may also specify a JANA:RESOURCE_URL configuration
0173         // parameter to override the URL_base found in the calibDB. If
0174         // the JANA:RESOURCE_URL config. param. is present then the
0175         // URL_base parameter in the calib DB is not required.
0176         //
0177         // Option 2.) The DB provides a "URL" string only. This is used
0178         // as the full URL and as a key to the resources map to find
0179         // the relative path. If none exists, this relative path is taken
0180         // to be the namepath specified.
0181         //
0182         // Option 1. takes precedent. If either the "URL_base" or "path"
0183         // strings are present, then the other must be as well or an
0184         // exception is thrown. Note that "URL_base" may be specified via
0185         // configuration parameter in which case it need not be in the
0186         // calib DB. If neither "URL_base" nor "path" is present, then the
0187         // URL string is checked and used. If it also does not exist, an
0188         // exception is thrown.
0189         //
0190         // Once a URL and local path+filename have been determined, the
0191         // local resources map is checked to see if the URL already exists
0192         // here, possibly associated with another filename. If so, the
0193         // other path+filename is used.
0194 
0195         bool has_URL_base = info.find("URL_base") != info.end();
0196         bool has_path = info.find("path") != info.end();
0197         bool has_URL = info.find("URL") != info.end();
0198         bool has_md5 = info.find("md5") != info.end();
0199 
0200         if (overide_URL_base) has_URL_base = true;
0201 
0202         string URL = "";
0203         string path = namepath;
0204 
0205         // Option 1
0206         if (has_URL_base || has_path) {
0207             if (!has_URL_base) {
0208                 jout << "URL_base=\"" << info["URL_base"] << "\" path=\"" << info["path"] << "\"" << endl;
0209                 string mess = string("\"path\" specified for resource \"") + namepath + "\" but not \"URL_base\"!";
0210                 throw JException(mess);
0211             }
0212             if (!has_path) {
0213                 jout << "URL_base=\"" << info["URL_base"] << "\" path=\"" << info["path"] << "\"" << endl;
0214                 string mess = string("\"URL_base\" specified for resource \"") + namepath + "\" but not \"path\"!";
0215                 throw JException(mess);
0216             }
0217 
0218             string my_URL_base = info["URL_base"];
0219             if (overide_URL_base) my_URL_base = URL_base;
0220             if (my_URL_base.length() == 0) {
0221                 my_URL_base += "/";
0222             } else {
0223                 if (my_URL_base[my_URL_base.length() - 1] != '/') my_URL_base += "/";
0224             }
0225 
0226             path = info["path"];
0227             if (path.length() > 0)
0228                 if (path[0] == '/') path.erase(0, 1);
0229 
0230             URL = my_URL_base + path;
0231 
0232 
0233             // Option 2
0234         } else if (has_URL) {
0235 
0236             URL = info["URL"];
0237 
0238 
0239             // Insufficient info for either option
0240         } else {
0241             string mess =
0242                     string("Neither \"URL_base\",\"path\" pair nor \"URL\" exist in DB for resource \"") + namepath +
0243                     "\" !";
0244             throw JException(mess);
0245         }
0246 
0247         // Do we already have this resource?
0248         // (possibly a different namepath uses the same URL)
0249         pthread_mutex_lock(&resource_manager_mutex);
0250         if (resources.find(URL) != resources.end()) {
0251             fullpath = resource_dir + "/" + resources[URL];
0252         }
0253         pthread_mutex_unlock(&resource_manager_mutex);
0254 
0255         // Check if resource file exists.
0256         bool file_exists = false;
0257         ifstream ifs(fullpath.c_str());
0258         if (ifs.is_open()) {
0259             file_exists = true;
0260             ifs.close();
0261         }
0262 
0263         // If file doesn't exist, it could be because
0264         // 1. there was no "resources" file or the file was installed by hand without updating "resources"
0265         // and
0266         // 2. the file name does not match the namepath exactly
0267         //
0268         // In this case we could download the file and the correct entry into "resources"
0269         // will be made automatically EXCEPT, if one is using a JANA_RESOURCE_DIR that
0270         // the user does not have write privileges in. That would also result in two
0271         // copies of the resource file. For this special case, we need to check if a file
0272         // exists that uses the path obtained from jcalib rather than the namepath.
0273         bool check_for_redownload = true;
0274         if ((!file_exists) && has_path) {
0275             string alternate_fullpath = resource_dir + "/" + info["path"];
0276             ifstream ifs(alternate_fullpath.c_str());
0277             if (ifs.is_open()) {
0278                 ifs.close();
0279                 file_exists = true;
0280                 fullpath = alternate_fullpath;
0281                 check_for_redownload = false; // don't try changing anything if we're using this fallback
0282             }
0283         }
0284 
0285         // Flag to decide if we need to rewrite the info file later
0286         bool rewrite_info_file = false;
0287 
0288         // If file doesn't exist, then download it
0289         if (!file_exists) {
0290             GetResourceFromURL(URL, fullpath);
0291 
0292             pthread_mutex_lock(&resource_manager_mutex);
0293             resources[URL] = path;
0294             pthread_mutex_unlock(&resource_manager_mutex);
0295 
0296             rewrite_info_file = true;
0297         } else if (check_for_redownload) {
0298             // If file does exist, but URL is different, we'll need to download it,
0299             // replacing the existing file. The resources map will then need to be updated.
0300             bool redownload_required = false;
0301             string other_URL = "";
0302             map<string, string>::iterator iter;
0303             pthread_mutex_lock(&resource_manager_mutex);
0304             for (iter = resources.begin(); iter != resources.end(); iter++) {
0305                 if (iter->second == path) {
0306                     if (iter->first != URL) {
0307                         other_URL = iter->first;
0308                         redownload_required = true;
0309                         break;
0310                     }
0311                 }
0312             }
0313             pthread_mutex_unlock(&resource_manager_mutex);
0314 
0315             if (redownload_required) {
0316                 // We must redownload the file, replacing the existing one.
0317                 // Remove old file first and warn the user this is happening.
0318                 jout << " Resource \"" << namepath << "\" already exists, but is" << endl;
0319                 jout << " associated with the URL: " << other_URL << endl;
0320                 jout << " Deleting existing file and downloading new version" << endl;
0321                 jout << " from: " << URL << endl;
0322                 unlink(fullpath.c_str());
0323 
0324                 GetResourceFromURL(URL, fullpath);
0325 
0326                 pthread_mutex_lock(&resource_manager_mutex);
0327                 resources[URL] = path;
0328 
0329                 // We need to remove any other entries from the resources
0330                 // map that refer to this file using a different URL since the file
0331                 // may have changed.
0332                 for (iter = resources.begin(); iter != resources.end(); /* don't increment here */) {
0333                     map<string, string>::iterator tmp = iter; // remember iterator since we might erase it
0334                     iter++;
0335                     if (tmp->second == path) {
0336                         if (tmp->first != URL)resources.erase(tmp);
0337                     }
0338                 }
0339                 pthread_mutex_unlock(&resource_manager_mutex);
0340 
0341                 rewrite_info_file = true;
0342             }
0343         }
0344 
0345         // If the md5 checksum is in the calibDB then check that our file is correct
0346         if (has_md5 && check_md5) {
0347             string md5sum = Get_MD5(fullpath);
0348             if (md5sum != info["md5"]) {
0349                 jerr << "-- ERROR: md5 checksum for the following resource file does not match expected" << endl;
0350                 jerr << "-- " << fullpath << endl;
0351                 jerr << "--  for the resource: " << endl;
0352                 if (has_URL_base) jerr << "--   URL_base = " << info["URL_base"] << endl;
0353                 if (has_path) jerr << "--       path = " << info["path"] << endl;
0354                 if (has_URL) jerr << "--        URL = " << info["URL"] << endl;
0355                 if (has_md5) jerr << "--        md5 = " << info["md5"] << endl;
0356                 jerr << "-- The md5sum for the existing file is: " << md5sum << endl;
0357                 jerr << "--" << endl;
0358                 jerr << "-- This can happen if the resource download was previously interrupted." << endl;
0359                 jerr << "-- Try removing the existing file and re-running to trigger a re-download." << endl;
0360                 jerr << "--" << endl;
0361                 jerr << "-- This is a fatal error and the program will stop now. To bypass checking" << endl;
0362                 jerr << "-- the md5sum, set the JANA:RESOURCE_CHECK_MD5 config. parameter to 0." << endl;
0363                 jerr << "--" << endl;
0364                 exit(-1);
0365             }
0366         }
0367 
0368         // Write new resource list to file
0369         if (rewrite_info_file) WriteResourceInfoFile();
0370     }
0371 
0372     return fullpath;
0373 }
0374 
0375 //---------------------------------
0376 // GetLocalPathToResource
0377 //---------------------------------
0378 string JResource::GetLocalPathToResource(string namepath) {
0379     string fullpath = resource_dir + "/" + namepath;
0380     if (jcalib) {
0381         for (unsigned int i = 0; i < calib_namepaths.size(); i++) {
0382             if (calib_namepaths[i] == namepath) {
0383                 map<string, string> info;
0384                 jcalib->Get(namepath, info);
0385 
0386                 if (info.find("path") != info.end()) {
0387                     fullpath = resource_dir + "/" + info["path"];
0388                 }
0389                 break;
0390             }
0391         }
0392     }
0393 
0394     return fullpath;
0395 }
0396 
0397 //---------------------------------
0398 // ReadResourceInfoFile
0399 //---------------------------------
0400 void JResource::ReadResourceInfoFile(void) {
0401     pthread_mutex_lock(&resource_manager_mutex);
0402 
0403     // Clear the resources container so it is empty
0404     // in case we don't find the file
0405     resources.clear();
0406 
0407     // Check if resources file exists
0408     string fname = GetLocalPathToResource("resources");
0409     ifstream ifs(fname.c_str());
0410     if (!ifs.is_open()) {
0411         pthread_mutex_unlock(&resource_manager_mutex);
0412         return; // no resources file so just return
0413     }
0414     ifs.close();
0415 
0416     // The resources file exists. Read it in using the
0417     // JCalibrationFile class to parse it
0418     jcalibfile->Get("resources", resources);
0419 
0420     pthread_mutex_unlock(&resource_manager_mutex);
0421 }
0422 
0423 //---------------------------------
0424 // WriteResourceInfoFile
0425 //---------------------------------
0426 void JResource::WriteResourceInfoFile(void) {
0427     pthread_mutex_lock(&resource_manager_mutex);
0428 
0429     // Get full path to resources file
0430     string fname = GetLocalPathToResource("resources");
0431 
0432     // Open file for writing, discarding any existing contents
0433     ofstream ofs(fname.c_str(), ios_base::out | ios_base::trunc);
0434 
0435     // File header
0436     time_t t = time(NULL);
0437     ofs << "#" << endl;
0438     ofs << "# JANA resources file  Auto-generated DO NOT EDIT" << endl;
0439     ofs << "#" << endl;
0440     ofs << "# " << ctime(&t); // automatically adds endl
0441     ofs << "#" << endl;
0442     ofs << "#% URL  namepath" << endl;
0443 
0444     map<string, string>::iterator iter;
0445     for (iter = resources.begin(); iter != resources.end(); iter++) {
0446         ofs << iter->first << "\t" << iter->second << endl;
0447     }
0448     ofs << endl;
0449 
0450     // Close file
0451     ofs.close();
0452 
0453     pthread_mutex_unlock(&resource_manager_mutex);
0454 }
0455 
0456 //---------------------------------
0457 // GetResourceFromURL
0458 //---------------------------------
0459 void JResource::GetResourceFromURL(const string &URL, const string &fullpath) {
0460     /// Download the specified file and place it in the location specified
0461     /// by fullpath. If unsuccessful, a JException will be thrown with
0462     /// an appropriate error message.
0463 
0464     pthread_mutex_lock(&resource_manager_mutex);
0465 
0466     jout << "Downloading " << URL << " ..." << endl;
0467     CURRENT_OUTPUT_FNAME = fullpath;
0468     if (fullpath.length() > 60) {
0469         CURRENT_OUTPUT_FNAME = string("...") + fullpath.substr(fullpath.length() - 60, 60);
0470     }
0471 
0472     // Create the directory path needed to hold the resource file
0473     char tmp[256];
0474     strcpy(tmp, fullpath.c_str());
0475     char *path_only = dirname(tmp);
0476     mkpath(path_only);
0477 
0478     // Create an empty info.xml file in resources directory
0479     // to avoid warning from JCalibrationFile
0480     string info_xml = resource_dir + "/info.xml";
0481     ofstream ofs(info_xml.c_str());
0482     ofs.close();
0483 
0484 #ifdef HAVE_CURL
0485     // Program has CURL library available
0486 
0487     // Initialize curl transaction
0488     CURL *curl = curl_easy_init();
0489 
0490     // Setup the options for the download
0491     char error[CURL_ERROR_SIZE] = "";
0492     FILE *f = fopen(fullpath.c_str(), "w");
0493     curl_easy_setopt(curl, CURLOPT_VERBOSE, 0);
0494     curl_easy_setopt(curl, CURLOPT_URL, URL.c_str());
0495     curl_easy_setopt(curl, CURLOPT_WRITEDATA, f);
0496     curl_easy_setopt(curl, CURLOPT_NOPROGRESS, 0);
0497     curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0); // allow non-secure SSL connection
0498     curl_easy_setopt(curl, CURLOPT_PROGRESSFUNCTION, mycurl_printprogress);
0499     curl_easy_setopt(curl, CURLOPT_ERRORBUFFER, error);
0500 
0501 
0502     // Download the file
0503     curl_easy_perform(curl);
0504     if(error[0]!=0) cout << error << endl;
0505 
0506     // Close CURL
0507     curl_easy_cleanup(curl);
0508 
0509     // Close the downloaded file
0510     cout << endl;
0511     fclose(f);
0512 
0513 #else // HAVE_CURL
0514     // Program does NOT have CURL library available
0515 
0516     string cmd = "curl " + curl_args + " " + URL + " -o " + fullpath;
0517     cout << cmd << endl;
0518     system(cmd.c_str());
0519 #endif // HAVE_CURL
0520 
0521     // We may want to have an option to automatically un-compress the file here
0522     // if it is in a compressed format. See the bottom of getwebfile.c in the
0523     // Hall-D source code for the hdparsim plugin for an example of how this might
0524     // be done.
0525 
0526     // unlock mutex
0527     pthread_mutex_unlock(&resource_manager_mutex);
0528 }
0529 
0530 //-----------
0531 // Get_MD5
0532 //-----------
0533 string JResource::Get_MD5(string fullpath) {
0534     ifstream ifs(fullpath.c_str());
0535     if (!ifs.is_open()) return string("");
0536 
0537     md5_state_t pms;
0538     md5_init(&pms);
0539 
0540     // allocate 10kB buffer for reading in file
0541     unsigned int buffer_size = 10000;
0542     char *buff = new char[buffer_size];
0543 
0544     // read data as a block:
0545     while (ifs.good()) {
0546         ifs.read(buff, buffer_size);
0547         md5_append(&pms, (const md5_byte_t *) buff, ifs.gcount());
0548     }
0549     ifs.close();
0550 
0551     delete[] buff;
0552 
0553     md5_byte_t digest[16];
0554     md5_finish(&pms, digest);
0555 
0556     const size_t str_len = 16 * 2 + 1;
0557     char hex_output[str_len];
0558     for (int di = 0; di < 16; ++di){
0559         size_t buff_left = str_len -  di * 2;
0560         snprintf(hex_output + di * 2, buff_left, "%02x", digest[di]);
0561     }
0562 
0563     return string(hex_output);
0564 }
0565 
0566 
0567 // The following will make all neccessary sub directories in order for the specified path to exist. It was
0568 // taken from here:
0569 // http://stackoverflow.com/questions/675039/how-can-i-create-directory-tree-in-c-linux
0570 // though it was not the first answer
0571 //----------------------------
0572 // mkpath
0573 //----------------------------
0574 int mkpath(string s, mode_t mode) {
0575     size_t pre = 0, pos;
0576     std::string dir;
0577     int mdret = 0;
0578 
0579     if (s[s.size() - 1] != '/') {
0580         // force trailing / so we can handle everything in loop
0581         s += '/';
0582     }
0583 
0584     while ((pos = s.find_first_of('/', pre)) != string::npos) {
0585         dir = s.substr(0, pos++);
0586         pre = pos;
0587         if (dir.size() == 0) continue; // if leading / first time is 0 length
0588         if ((mdret = mkdir(dir.c_str(), mode)) && errno != EEXIST) {
0589             return mdret;
0590         }
0591     }
0592     return mdret;
0593 }
0594 
0595 #ifdef HAVE_CURL
0596 //----------------------------
0597 // mycurl_printprogress
0598 //----------------------------
0599 int mycurl_printprogress(void *clientp, double dltotal, double dlnow, double ultotal,  double ulnow)
0600 {
0601     unsigned long kB_downloaded = (unsigned long)(dlnow/1024.0);
0602     cout << "  " << kB_downloaded << "kB  " << CURRENT_OUTPUT_FNAME << "\r";
0603     cout.flush();
0604 
0605     return 0;
0606 }
0607 #endif // HAVE_CURL
0608