2222#include < TMessage.h>
2323#include < sstream>
2424#include < TFile.h>
25+ #include < TGrid.h>
2526#include < TSystem.h>
2627#include < TStreamerInfo.h>
2728#include < TMemFile.h>
@@ -70,6 +71,12 @@ void CcdbApi::init(std::string const& host)
7071 } else {
7172 curlInit ();
7273 }
74+
75+ // find out if we can can in principle connect to Alien
76+ mHaveAlienToken = checkAlienToken ();
77+ if (!mHaveAlienToken ) {
78+ LOG (WARN) << " CCDB: Did not find an alien token; Cannot serve objects located on alien://" ;
79+ }
7380}
7481
7582/* *
@@ -391,15 +398,16 @@ std::string CcdbApi::generateFileName(const std::string& inp)
391398
392399namespace
393400{
401+ template <typename MapType = std::map<std::string, std::string>>
394402size_t header_map_callback (char * buffer, size_t size, size_t nitems, void * userdata)
395403{
396- auto * headers = static_cast <std::map<std::string, std::string> *>(userdata);
404+ auto * headers = static_cast <MapType *>(userdata);
397405 auto header = std::string (buffer, size * nitems);
398406 std::string::size_type index = header.find (' :' , 0 );
399407 if (index != std::string::npos) {
400- headers-> insert ( std::make_pair (
401- boost::algorithm::trim_copy (header.substr (0 , index)),
402- boost::algorithm::trim_copy (header. substr (index + 1 )) ));
408+ const auto key = boost::algorithm::trim_copy (header. substr ( 0 , index));
409+ const auto value = boost::algorithm::trim_copy (header.substr (index + 1 ));
410+ headers-> insert ( std::make_pair (key, value ));
403411 }
404412 return size * nitems;
405413}
@@ -458,7 +466,7 @@ TObject* CcdbApi::retrieveFromTFile(std::string const& path, std::map<std::strin
458466 // setup curl for headers handling
459467 if (headers != nullptr ) {
460468 list = curl_slist_append (list, (" If-None-Match: " + to_string (timestamp)).c_str ());
461- curl_easy_setopt (curl_handle, CURLOPT_HEADERFUNCTION, header_map_callback);
469+ curl_easy_setopt (curl_handle, CURLOPT_HEADERFUNCTION, header_map_callback<> );
462470 curl_easy_setopt (curl_handle, CURLOPT_HEADERDATA, headers);
463471 }
464472
@@ -643,6 +651,145 @@ void* CcdbApi::extractFromLocalFile(std::string const& filename, TClass const* t
643651 return extractFromTFile (f, tcl);
644652}
645653
654+ bool CcdbApi::checkAlienToken () const
655+ {
656+ // a somewhat weird construction to programmatically find out if we
657+ // have a GRID token; Can be replaced with something more elegant once
658+ // alien-token-info does not ask for passwords interactively
659+ auto returncode = system (" timeout 1s timeout 1s alien-token-info &> /dev/null" );
660+ return returncode == 0 ;
661+ }
662+
663+ bool CcdbApi::initTGrid () const
664+ {
665+ if (!mAlienInstance ) {
666+ if (mHaveAlienToken ) {
667+ mAlienInstance = TGrid::Connect (" alien" );
668+ }
669+ }
670+ return mAlienInstance != nullptr ;
671+ }
672+
673+ void * CcdbApi::downloadAlienContent (std::string const & url, TClass* cl) const
674+ {
675+ if (!initTGrid ()) {
676+ return nullptr ;
677+ }
678+ auto memfile = TMemFile::Open (url.c_str (), " OPEN" );
679+ if (memfile) {
680+ auto content = extractFromTFile (*memfile, cl);
681+ delete memfile;
682+ return content;
683+ }
684+ return nullptr ;
685+ }
686+
687+ void * CcdbApi::interpretAsTMemFileAndExtract (char * contentptr, size_t contentsize, TClass* tcl) const
688+ {
689+ void * result = nullptr ;
690+ Int_t previousErrorLevel = gErrorIgnoreLevel ;
691+ gErrorIgnoreLevel = kFatal ;
692+ TMemFile memFile (" name" , contentptr, contentsize, " READ" );
693+ gErrorIgnoreLevel = previousErrorLevel;
694+ if (!memFile.IsZombie ()) {
695+ result = extractFromTFile (memFile, tcl);
696+ if (!result) {
697+ LOG (ERROR) << o2::utils::concat_string (" Couldn't retrieve object corresponding to " , tcl->GetName (), " from TFile" );
698+ }
699+ memFile.Close ();
700+ }
701+ return result;
702+ }
703+
704+ // navigate sequence of URLs until TFile content is found; object is extracted and returned
705+ void * CcdbApi::navigateURLsAndRetrieveContent (CURL* curl_handle, std::string const & url, TClass* cl, std::map<string, string>* headers) const
706+ {
707+ // let's see first of all if the url is something specific that curl cannot handle
708+ if (url.find (" alien:/" , 0 ) != std::string::npos) {
709+ return downloadAlienContent (url, cl);
710+ }
711+ // add other final cases here
712+ // example root://
713+
714+ // otherwise make an HTTP/CURL request
715+ // specify URL to get
716+ curl_easy_setopt (curl_handle, CURLOPT_URL, url.c_str ());
717+ // some servers don't like requests that are made without a user-agent
718+ // field, so we provide one
719+ curl_easy_setopt (curl_handle, CURLOPT_USERAGENT, " libcurl-agent/1.0" );
720+ // if redirected , we tell libcurl NOT to follow redirection
721+ curl_easy_setopt (curl_handle, CURLOPT_FOLLOWLOCATION, 0L );
722+ curl_easy_setopt (curl_handle, CURLOPT_HEADERFUNCTION, header_map_callback<decltype (mHeaderData )>);
723+ mHeaderData .clear ();
724+ curl_easy_setopt (curl_handle, CURLOPT_HEADERDATA, (void *)&mHeaderData );
725+
726+ MemoryStruct chunk{(char *)malloc (1 ), 0 };
727+
728+ // send all data to this function
729+ curl_easy_setopt (curl_handle, CURLOPT_WRITEFUNCTION, WriteMemoryCallback);
730+ curl_easy_setopt (curl_handle, CURLOPT_WRITEDATA, (void *)&chunk);
731+
732+ auto res = curl_easy_perform (curl_handle);
733+ long response_code = -1 ;
734+ void * content = nullptr ;
735+ bool errorflag = false ;
736+ bool cachingflag = false ;
737+ if (res == CURLE_OK && curl_easy_getinfo (curl_handle, CURLINFO_RESPONSE_CODE, &response_code) == CURLE_OK) {
738+ if (headers) {
739+ for (auto & p : mHeaderData ) {
740+ (*headers)[p.first ] = p.second ;
741+ }
742+ }
743+ if (200 <= response_code && response_code < 300 ) {
744+ // good response and the content is directly provided and should have been dumped into "chunk"
745+ content = interpretAsTMemFileAndExtract (chunk.memory , chunk.size , cl);
746+ } else if (response_code == 304 ) {
747+ // this means the object exist but I am not serving
748+ // it since it's already in your possession
749+
750+ // there is nothing to be done here
751+ cachingflag = true ;
752+ }
753+ // this is a more general redirection
754+ else if (300 <= response_code && response_code < 400 ) {
755+ // we try content locations in order of appearance until one succeeds
756+ // 1st: The "Location" field
757+ // 2nd: Possible "Content-Location" fields
758+ auto tryLocations = [this , &curl_handle, &content, cl](auto range) {
759+ for (auto it = range.first ; it != range.second ; ++it) {
760+ auto nextlocation = it->second ;
761+ LOG (DEBUG) << " Trying content location " << nextlocation;
762+ content = navigateURLsAndRetrieveContent (curl_handle, nextlocation, cl, nullptr );
763+ if (content /* or other success marker in future */ ) {
764+ break ;
765+ }
766+ }
767+ };
768+ tryLocations (mHeaderData .equal_range (" Location" ));
769+ if (content == nullptr ) {
770+ tryLocations (mHeaderData .equal_range (" Content-Location" ));
771+ }
772+ } else if (response_code == 404 ) {
773+ LOG (ERROR) << " Requested resource does not exist" ;
774+ errorflag = true ;
775+ } else {
776+ errorflag = true ;
777+ }
778+ // cleanup
779+ if (chunk.memory != nullptr ) {
780+ free (chunk.memory );
781+ }
782+ } else {
783+ LOG (ERROR) << " Curl request to " << url << " failed " ;
784+ errorflag = true ;
785+ }
786+ // indicate that an error occurred ---> used by caching layers (such as CCDBManager)
787+ if (errorflag && headers) {
788+ (*headers)[" Error" ] = " An error occurred during retrieval" ;
789+ }
790+ return content;
791+ }
792+
646793void * CcdbApi::retrieveFromTFile (std::type_info const & tinfo, std::string const & path,
647794 std::map<std::string, std::string> const & metadata, long timestamp,
648795 std::map<std::string, std::string>* headers, std::string const & etag,
@@ -655,103 +802,32 @@ void* CcdbApi::retrieveFromTFile(std::type_info const& tinfo, std::string const&
655802 return nullptr ;
656803 }
657804
658- // Note : based on https://curl.haxx.se/libcurl/c/getinmemory.html
659- // Thus it does not comply to our coding guidelines as it is a copy paste.
660-
661- // Prepare CURL
662- CURL* curl_handle;
663- CURLcode res;
664- struct MemoryStruct chunk {
665- (char *)malloc(1 ) /* memory*/ , 0 /* size*/
666- };
667-
668- /* init the curl session */
669- curl_handle = curl_easy_init ();
670-
805+ CURL* curl_handle = curl_easy_init ();
671806 string fullUrl = getFullUrlForRetrieval (curl_handle, path, metadata, timestamp);
672807 // if we are in snapshot mode we can simply open the file; extract the object and return
673808 if (mInSnapshotMode ) {
674809 return extractFromLocalFile (fullUrl, tcl);
675810 }
676811
677- /* specify URL to get */
678- curl_easy_setopt (curl_handle, CURLOPT_URL, fullUrl.c_str ());
679-
680- /* send all data to this function */
681- curl_easy_setopt (curl_handle, CURLOPT_WRITEFUNCTION, WriteMemoryCallback);
682-
683- /* we pass our 'chunk' struct to the callback function */
684- curl_easy_setopt (curl_handle, CURLOPT_WRITEDATA, (void *)&chunk);
685-
686- /* some servers don't like requests that are made without a user-agent
687- field, so we provide one */
688- curl_easy_setopt (curl_handle, CURLOPT_USERAGENT, " libcurl-agent/1.0" );
689-
690- /* if redirected , we tell libcurl to follow redirection */
691- curl_easy_setopt (curl_handle, CURLOPT_FOLLOWLOCATION, 1L );
692-
812+ // add some global options to the curl query
693813 struct curl_slist * list = nullptr ;
694814 if (!etag.empty ()) {
695815 list = curl_slist_append (list, (" If-None-Match: " + etag).c_str ());
696816 }
697-
698817 if (!createdNotAfter.empty ()) {
699818 list = curl_slist_append (list, (" If-Not-After: " + createdNotAfter).c_str ());
700819 }
701-
702820 if (!createdNotBefore.empty ()) {
703821 list = curl_slist_append (list, (" If-Not-Before: " + createdNotBefore).c_str ());
704822 }
705-
706- // setup curl for headers handling
707- if (headers != nullptr ) {
823+ if (headers) {
708824 list = curl_slist_append (list, (" If-None-Match: " + to_string (timestamp)).c_str ());
709- curl_easy_setopt (curl_handle, CURLOPT_HEADERFUNCTION, header_map_callback);
710- curl_easy_setopt (curl_handle, CURLOPT_HEADERDATA, headers);
711- }
712-
713- if (list) {
714- curl_easy_setopt (curl_handle, CURLOPT_HTTPHEADER, list);
715- }
716-
717- /* get it! */
718- res = curl_easy_perform (curl_handle);
719- std::string errStr;
720- void * result = nullptr ;
721- if (res == CURLE_OK) {
722- long response_code;
723- res = curl_easy_getinfo (curl_handle, CURLINFO_RESPONSE_CODE, &response_code);
724- if ((res == CURLE_OK) && (response_code != 404 )) {
725- Int_t previousErrorLevel = gErrorIgnoreLevel ;
726- gErrorIgnoreLevel = kFatal ;
727- TMemFile memFile (" name" , chunk.memory , chunk.size , " READ" );
728- gErrorIgnoreLevel = previousErrorLevel;
729- if (!memFile.IsZombie ()) {
730- result = extractFromTFile (memFile, tcl);
731- if (!result) {
732- errStr = o2::utils::concat_string (" Couldn't retrieve the object " , path);
733- LOG (ERROR) << errStr;
734- }
735- memFile.Close ();
736- } else {
737- LOG (DEBUG) << " Object " << path << " is stored in a TMemFile" ;
738- }
739- } else {
740- errStr = o2::utils::concat_string (" Invalid URL : " , fullUrl);
741- LOG (ERROR) << errStr;
742- }
743- } else {
744- errStr = o2::utils::concat_string (" curl_easy_perform() failed: " , curl_easy_strerror (res));
745- fprintf (stderr, " %s" , errStr.c_str ());
746- }
747-
748- if (!errStr.empty () && headers) {
749- (*headers)[" Error" ] = errStr;
750825 }
826+ curl_easy_setopt (curl_handle, CURLOPT_HTTPHEADER, list);
751827
828+ auto content = navigateURLsAndRetrieveContent (curl_handle, fullUrl, tcl, headers);
752829 curl_easy_cleanup (curl_handle);
753- free (chunk.memory );
754- return result;
830+ return content;
755831}
756832
757833size_t CurlWrite_CallbackFunc_StdString2 (void * contents, size_t size, size_t nmemb, std::string* s)
@@ -930,7 +1006,7 @@ std::map<std::string, std::string> CcdbApi::retrieveHeaders(std::string const& p
9301006 /* get us the resource without a body! */
9311007 curl_easy_setopt (curl, CURLOPT_NOBODY, 1L );
9321008 curl_easy_setopt (curl, CURLOPT_FOLLOWLOCATION, 1L );
933- curl_easy_setopt (curl, CURLOPT_HEADERFUNCTION, header_map_callback);
1009+ curl_easy_setopt (curl, CURLOPT_HEADERFUNCTION, header_map_callback<> );
9341010 curl_easy_setopt (curl, CURLOPT_HEADERDATA, &headers);
9351011
9361012 // Perform the request, res will get the return code
0 commit comments