/* This is a histogram memory driver for the 2005-6 version of the histogram memory software based on RTAI-Linux and an embedded WWW-server for communications. For all http work the ghttp library from the gnome project is used. This HM is meant to be used in conjunction with a counter module chained through the hmcontrol module. No need to handle counters here when hmcontrol can do the chaining. copyright: see file COPYRIGHT Mark Koennecke, January 2005 (original SINQ version) Mark Lesha, 9 October 2006 (ANSTO version) ----------------------------------------------------------------------*/ #include #include #include #include #include #include #include #include extern char *trim(char *); /*=================================================================== The request strings to append to the computer address ====================================================================*/ static char startdaq[] = {"/admin/startdaq.egi"}; static char stopdaq[] = {"/admin/stopdaq.egi"}; static char pausedaq[] = {"/admin/pausedaq.egi"}; static char continuedaq[] = {"/admin/continuedaq.egi"}; static char statusdaq[] = {"/admin/textstatus.egi"}; static char gethm[] = {"/admin/readhmdata.egi"}; static char configure[] = {"/admin/configure.egi"}; static char preset[] = {"/admin/presethm.egi"}; /*==================================================================== error codes ======================================================================*/ #define BADURL -701 #define HTTPERROR -702 #define NOBODY -703 #define BODYSHORT -704 #define NOTIMPLEMENTED -705 #define SERVERERROR -706 #define BADSTATUS -707 #define BADAUTH -708 #define IMMEDFAIL -709 /*===================================================================== our driver private data structure ======================================================================*/ #define MAXSTRLEN 512 typedef struct { ghttp_request *syncRequest; char hmAddress[512]; char userName[132]; char passWord[132]; char hmError[512]; int errorCode; int pause; int failCount; int asyncRunning; }anstoHttp, *pAnstoHttp; /*------------------------------------------------------------------*/ static int anstoHttpGetPrepare(pAnstoHttp self, char *request){ char url[512]; ghttp_status httpStatus; if(self->asyncRunning){ while((httpStatus = ghttp_process(self->syncRequest)) == ghttp_not_done){ } self->asyncRunning = 0; } self->errorCode = 0; ghttp_clean(self->syncRequest); memset(self->hmError,0,512*sizeof(char)); snprintf(url,511,"%s%s",self->hmAddress,request); ghttp_set_type(self->syncRequest,ghttp_type_get); ghttp_set_header(self->syncRequest,"connection","keep-alive"); if(ghttp_set_uri(self->syncRequest,url) < 0){ self->errorCode = BADURL; return 0; } ghttp_set_authinfo(self->syncRequest,self->userName, self->passWord); ghttp_set_sync(self->syncRequest,ghttp_sync); return 1; } /*-------------------------------------------------------------------*/ static int anstoHttpCheckResponse(pAnstoHttp self){ char *pPtr = NULL; int len; self->failCount = 0; pPtr = ghttp_get_body(self->syncRequest); if(pPtr == NULL) // MJL check ghttp_get_body for NULL return return 1; // empty response is okay len = ghttp_get_body_len(self->syncRequest); if(len > 511){ len = 510; } if(strstr(pPtr,"ERROR") != NULL){ memset(self->hmError,0,512*sizeof(char)); strncpy(self->hmError,pPtr, len); self->errorCode = HTTPERROR; return 0; } else if(strstr(pPtr,"Authentication Error") != NULL){ memset(self->hmError,0,512*sizeof(char)); strncpy(self->hmError,pPtr, len); self->errorCode = BADAUTH; return 0; } return 1; } /*-------------------------------------------------------------------*/ static int anstoHttpGet(pAnstoHttp self, char *request){ ghttp_status httpStatus; char *pPtr = NULL; if(!anstoHttpGetPrepare(self,request)){ return 0; } /* * try two times: a reconnect is no error */ ghttp_prepare(self->syncRequest); httpStatus = ghttp_process(self->syncRequest); if(httpStatus != ghttp_done){ ghttp_close(self->syncRequest); if (!anstoHttpGetPrepare(self,request)) return 0; // MJL return here if failed ghttp_prepare(self->syncRequest); // MJL be sure to call ghttp_prepare before each ghttp_process httpStatus = ghttp_process(self->syncRequest); } if(httpStatus != ghttp_done){ strncpy(self->hmError,"Reconnect", 511); self->errorCode = SERVERERROR; return 0; } else { return anstoHttpCheckResponse(self); } return 1; } /*====================================================================*/ static int AnstoHttpConfigure(pHistDriver self, SConnection *pCon, pStringDict pOpt, SicsInterp *pSics){ char hmname[512]; char confCommand[512], url[512]; pAnstoHttp pPriv = NULL; int status, iInit; float fVal; int i; char *confData = NULL; ghttp_status httpStatus; pPriv = (pAnstoHttp)self->pPriv; assert(pPriv != NULL); /* * The HM computer address */ if(StringDictGet(pOpt,"hmaddress",pPriv->hmAddress, 511) != 1){ SCWrite(pCon, "ERROR: required configuration parameter hmaddress not found", eError); return 0; } /* * looser credentials */ if(StringDictGet(pOpt,"username",pPriv->userName, 131) != 1){ SCWrite(pCon, "ERROR: required configuration parameter username not found", eError); return 0; } if(StringDictGet(pOpt,"password",pPriv->passWord, 131) != 1){ SCWrite(pCon, "ERROR: required configuration parameter password not found", eError); return 0; } /* actual configuration. Check for flag INIT in options. We do not need to configure, if the HM has configured itself already. What is does, these days. */ status = StringDictGetAsNumber(pOpt,"init",&fVal); iInit = 0; if(status == 1) { if(fVal > 0.9 ) { iInit = 1; } } /* actually do configure */ if(iInit == 1){ for(i = 0; i < 2; i++){ memset(confCommand,0,512*sizeof(char)); if(StringDictGet(pOpt,"hmconfigscript",confCommand,511) != 1){ SCWrite(pCon, "ERROR: required parameter hmconfigscript not found!", eError); return 0; } status = Tcl_Eval(pSics->pTcl,confCommand); if(status != TCL_OK){ snprintf(confCommand,511, "ERROR: Tcl reported %s while evaluating hmconfigscript", Tcl_GetStringResult(pSics->pTcl)); SCWrite(pCon,confCommand,eError); return 0; } else { /* uplod new configuration to HM */ ghttp_clean(pPriv->syncRequest); snprintf(url,511,"%s%s",pPriv->hmAddress,configure); status = ghttp_set_uri(pPriv->syncRequest,url); if(status < 0){ SCWrite(pCon,"ERROR: invalid URI for HM request",eError); return 0; } status = ghttp_set_type(pPriv->syncRequest,ghttp_type_post); confData = (char *)Tcl_GetStringResult(pSics->pTcl); status = ghttp_set_body(pPriv->syncRequest,confData, strlen(confData)); ghttp_set_authinfo(pPriv->syncRequest, pPriv->userName, pPriv->passWord); ghttp_set_sync(pPriv->syncRequest,ghttp_sync); status = ghttp_prepare(pPriv->syncRequest); httpStatus = ghttp_process(pPriv->syncRequest); confData = (char *)ghttp_get_body(pPriv->syncRequest); if(httpStatus != ghttp_done){ /* we may need to reconnect..... */ if(i == 0){ ghttp_close(pPriv->syncRequest); continue; } /* * no we have a real error */ confData = (char *)ghttp_get_error(pPriv->syncRequest); snprintf(confCommand,511,"ERROR: http error %s occurred", confData); SCWrite(pCon,confCommand,eError); return 0; } else if(confData != NULL){ if(strstr(confData,"ERROR") != NULL){ snprintf(confCommand,511,"%s",confData); SCWrite(pCon,confCommand,eError); return 0; } if(strstr(confData,"Authentication Error") != NULL){ snprintf(confCommand,511,"%s",confData); SCWrite(pCon,confCommand,eError); return 0; } } } } } // MJL NOTE: Added extra init parameters here, these get committed // regardless of whether we are doing the first init or not // (i.e. will get committed even if init=1). // Need to do init on the histogram object (e.g. 'hm init') // in order to commit changed settings during operation. // Specifically, this is important if FAT settings need to be // modified during operation. To do this, the command // 'hm configure FAT_Xxxx vvv' needs to be performed, // where Xxxx is the name of the item in the FAT, and vvv is the desired value. // If the requested FAT variable doesn't exist or cannot be modified // during operation, the set fails but no indication is given. // // Now, to find out what entries are FAT_Xxxx format, /// by traversing the dictionary list. char pValue[256]; const char *pItem=NULL; do { pItem=StringDictGetNext(pOpt,pValue,256); if (pItem) { if (strncasecmp(pItem,"FAT_",4)==0) { // Try committing the setting to the histogram server // Make special http request to set the FAT parameter char modify_FAT_http_request[1024]; sprintf(modify_FAT_http_request, "/admin/selectdynamicfatmodify.egi?dynamicFATmodifyparamname=%s&dynamicFATmodifyparamvalue=%s", pItem+4,pValue); // Send the request. When one doesn't work, drop out of the loop. int status = anstoHttpGet(pPriv,modify_FAT_http_request); if(status != 1) return 0; } } } while(pItem); return 1; } /*--------------------------------------------------------------------*/ static int readStatus(pHistDriver pDriv){ char *pPtr = NULL, *pLinePtr; char line[132]; char name[80], value[80]; pAnstoHttp self = NULL; char *daqPtr = NULL, *daqValPtr = NULL; self = (pAnstoHttp)pDriv->pPriv; assert(self != NULL); pPtr = ghttp_get_body(self->syncRequest); if(pPtr == NULL){ strncpy(self->hmError,"No body in status response",131); self->errorCode = NOBODY; return 0; } pPtr = stptok(pPtr,line,132,"\n"); while(pPtr != NULL){ pLinePtr = line; pLinePtr = stptok(pLinePtr,name,80,":"); pLinePtr = stptok(pLinePtr,value,80,":"); strtolower(name); if(StringDictExists(pDriv->pOption,trim(name)) == 1){ StringDictUpdate(pDriv->pOption,trim(name),trim(value)); } else { StringDictAddPair(pDriv->pOption,trim(name),trim(value)); } pPtr = stptok(pPtr,line,131,"\n"); } return 1; } /*---------------------------------------------------------------------*/ // The status code now allows pCon==NULL to be passed in. // This allows the status to be checked from all the start, stop, pause // and continue callbacks, so we can wait for the server to actually // change to the correct operating state before continuing. static int AnstoHttpStatus(pHistDriver self,SConnection *pCon){ // pCon=NULL allowed pAnstoHttp pPriv = NULL; ghttp_status httpStatus; char daqStatus[20]; int status, len; char *pPtr = NULL; pPriv = (pAnstoHttp)self->pPriv; assert(pPriv != NULL); // MJL for the ANSTO histogram server we STILL need status checking to occur // even when in paused mode (our pause mode has a different functionality). // So the code below is removed. /// if(pPriv->pause == 1){ /// return HWPause; /// } if(pPriv->asyncRunning == 0){ status = anstoHttpGetPrepare(pPriv,statusdaq); ghttp_set_sync(pPriv->syncRequest,ghttp_async); ghttp_prepare(pPriv->syncRequest); if(status != 1){ return HWFault; } pPriv->asyncRunning = 1; } httpStatus = ghttp_process(pPriv->syncRequest); switch(httpStatus){ case ghttp_error: ghttp_close(pPriv->syncRequest); anstoHttpGetPrepare(pPriv,statusdaq); ghttp_prepare(pPriv->syncRequest); httpStatus = ghttp_process(pPriv->syncRequest); if(httpStatus != ghttp_done){ strncpy(pPriv->hmError,"Reconnect", 511); pPriv->errorCode = SERVERERROR; return HWFault; pPriv->asyncRunning = 0; } break; case ghttp_not_done: // MJL this is not a problem, the server is just slow. // return HWRedo not HWBusy (original) or falut return HWRedo; break; case ghttp_done: pPriv->asyncRunning = 0; status = anstoHttpCheckResponse(pPriv); if(status != 1){ return HWFault; } break; } status = readStatus(self); if(status != 1){ return HWFault; } if(StringDictGet(self->pOption,"daq",daqStatus,20) != 1){ pPriv->errorCode = BADSTATUS; strncpy(pPriv->hmError,"ERROR: status does not contain DAQ field",511); return HWFault; } ///if (pCon) SCWrite(pCon,daqStatus,eError); // MJL DEBUG if(strstr(daqStatus,"Started") != NULL){ return HWBusy; } else if(strstr(daqStatus,"Paused") != NULL){ return HWIdle; } else if(strstr(daqStatus,"Stopped") != NULL){ return HWIdle; } else { pPriv->errorCode = BADSTATUS; snprintf(pPriv->hmError,511,"ERROR: invalid DAQ status %s",daqStatus); return HWFault; } return HWFault; // shouldn't get here } #define MAX_STATUS_READ_RETRIES 150 #define STATUS_READ_DELAY_US 200000 // static int AnstoHttpStatusWithRetries(pHistDriver self, int requiredstate,SConnection *pCon) // pCon=NULL allowed { int retries,retcode,initialentry=1; for(retries=0;retriespPriv; assert(pPriv != NULL); status = anstoHttpGet(pPriv,startdaq); if(status != 1){ return HWFault; } AnstoHttpStatusWithRetries(self,HWBusy,pCon); return OKOK; } /*---------------------------------------------------------------------*/ static int AnstoHttpHalt(pHistDriver self){ // hmm, why isn't there a pCon like all the other ones...? pAnstoHttp pPriv = NULL; int status; pPriv = (pAnstoHttp)self->pPriv; assert(pPriv != NULL); status = anstoHttpGet(pPriv,stopdaq); if(status != 1){ return HWFault; } AnstoHttpStatusWithRetries(self,HWIdle,NULL); // no pCon available :( return OKOK; } /*---------------------------------------------------------------------*/ static int AnstoHttpPause(pHistDriver self,SConnection *pCon){ pAnstoHttp pPriv = NULL; int status; pPriv = (pAnstoHttp)self->pPriv; assert(pPriv != NULL); status = anstoHttpGet(pPriv,pausedaq); if(status != 1){ return HWFault; } pPriv->pause = 1; AnstoHttpStatusWithRetries(self,HWIdle,pCon); return OKOK; } /*---------------------------------------------------------------------*/ static int AnstoHttpContinue(pHistDriver self, SConnection *pCon){ pAnstoHttp pPriv = NULL; int status; pPriv = (pAnstoHttp)self->pPriv; assert(pPriv != NULL); status = anstoHttpGet(pPriv,continuedaq); if(status != 1){ return HWFault; } pPriv->pause = 0; AnstoHttpStatusWithRetries(self,HWBusy,pCon); return OKOK; } /*---------------------------------------------------------------------*/ static int AnstoHttpError(pHistDriver self, int *code, char *error, int errLen){ pAnstoHttp pPriv = NULL; int status; pPriv = (pAnstoHttp)self->pPriv; assert(pPriv != NULL); strncpy(error,pPriv->hmError, errLen); *code = pPriv->errorCode; return OKOK; } /*--------------------------------------------------------------------*/ static int AnstoHttpFixIt(pHistDriver self, int code){ pAnstoHttp pPriv = NULL; pPriv = (pAnstoHttp)self->pPriv; assert(pPriv != NULL); /* * not much to fix here: ghttplib already tries hard to fix * connection problems... But abort any pending transactions... */ ghttp_close(pPriv->syncRequest); if(code == SERVERERROR){ pPriv->failCount++; if(pPriv->failCount > 2){ return COTERM; } else { return COREDO; } } return COTERM; } /*--------------------------------------------------------------------*/ static int AnstoHttpGetData(pHistDriver self,SConnection *pCon){ /* * do noting, monitors are with the counter, histogram memory * caching and retrieval is handled via GetHistogram */ return OKOK; } /*-------------------------------------------------------------------*/ /** \brief Reads histogram data from a zipped file produced by the * histogram memory server * * \param self (r) provides access to the histogram data structure * \param bank number n maps to a HDS_Pn file. */ static int GetHMfromFile(pHistDriver self, SConnection *pCon, int bank, int start, int end, HistInt *data){ HistInt *hmdata; pAnstoHttp pPriv = NULL; int status, len, i, size; int errMsgLen; FILE *fp; char command[256]; char hmDataPath[MAXSTRLEN]; char DAQdir[MAXSTRLEN]; char DSfilepath[MAXSTRLEN]; char scanpoint[32]; memset(command, 0, sizeof(command)); memset(hmDataPath, 0, sizeof(hmDataPath)); memset(DAQdir, 0, sizeof(DAQdir)); memset(DSfilepath, 0, sizeof(DSfilepath)); memset(scanpoint, 0, sizeof(scanpoint)); pPriv = (pAnstoHttp)self->pPriv; assert(pPriv != NULL); errMsgLen = sizeof(pPriv->hmError); if (bank < 0) { snprintf(pPriv->hmError,errMsgLen,"Negative bank numbers are not supported when reading from the filesystem"); pPriv->errorCode = IMMEDFAIL; return HWFault; } StringDictGet(self->pOption, "daq_dirname",DAQdir, sizeof(DAQdir)-1); StringDictGet(self->pOption, "hmdatapath",hmDataPath, sizeof(hmDataPath)-1); StringDictGet(self->pOption, "scanpoint",scanpoint, sizeof(scanpoint)-1); snprintf(DSfilepath,sizeof(DSfilepath),"%s/%s/DATASET_%s/HDS_P%d",hmDataPath,DAQdir,scanpoint,bank); if ((fp=gzopen(DSfilepath, "rb")) == NULL) { snprintf(pPriv->hmError,errMsgLen,"Failed to open %s", DSfilepath); pPriv->errorCode = IMMEDFAIL; return HWFault; } size = end - start; gzread(fp, data, size*sizeof(HistInt)); data+=size; start+=size; gzclose(fp); return OKOK; } /**\brief Gets histogram data directly from the histogram memory http server. */ static int GetHMfromSocket(pHistDriver self, SConnection *pCon, int bank, int start, int end, HistInt *data){ char command[256]; HistInt *hmdata; pAnstoHttp pPriv = NULL; int status, len, i; // For the ANSTO Histogram Server, the transfer size may be large // and this can lead to transmission of the data from the server in chunks. // This is apparent from the presence of 'xxxxx; chunk length nnnnnn' in the // data when the transfer size is greater than 128K. // e.g. a request for 64K elements is split into two chunks, // and each has the header '20000; chunk length 131072'. // However for some reason ghttp_get_body_len returns only the data size, // even though there are header(s) added to the data. // So, for the time being we play it safe and divide the transfer // into chunks of less than 128KB. // // MJL 22/11/06: Found that AppWeb 2.1.0 supports HTTP 1.1 chunking, // but this can be turned off by specifying HttpChunking off in the AppWeb // config file, which is what we do currently, so the transfer size limitation code below // is no longer needed. I've left it in with a much larger size restriction, // just in case it's required later on for any reason (to improve transfer reliability?). // Apparently the AppWeb 2.1.0 HTTP chunking feature is broken, because when the // data is opened directly from the web server using an editor like KHexEdit, the open // stalls for large data sizes unless chunking is disabled. This shows it is // an AppWeb problem, not a ghttp problem. // For AppWeb versions before 2.1.0, HTTP chunking is not available anyway. pPriv = (pAnstoHttp)self->pPriv; assert(pPriv != NULL); // How this limit is set in the server is not obvious... anyway, it should not change. // MJL 22/11/06: Turned HTTP 1.1 chunking off at the histogram server, so we no longer // need a size restriction. Leave the code in just in case it's needed for other reasons, // but set a larger maximum request size. //#define MAX_HTTP_REQUEST_BYTES 131072 #define MAX_HTTP_REQUEST_BYTES 1048576 // Do the HTTP data transfer in bite sized pieces while(end>start) { int size=((end-start)>(MAX_HTTP_REQUEST_BYTES/sizeof(int))) ?(MAX_HTTP_REQUEST_BYTES/sizeof(int)):(end-start); snprintf(command,255,"%s?bank=%d&start=%d&end=%d",gethm,bank, start,start+size); status = anstoHttpGet(pPriv,command); if(status != 1){ return HWFault; } len = ghttp_get_body_len(pPriv->syncRequest); if(len < size*sizeof(int)){ pPriv->errorCode = BODYSHORT; strncpy(pPriv->hmError,"Not enough data received from HM",511); return HWFault; } hmdata = (HistInt *)ghttp_get_body(pPriv->syncRequest); if(hmdata == NULL){ // MJL check ghttp_get_body for NULL return pPriv->errorCode = NOBODY; strncpy(pPriv->hmError,"No body in HM data response",511); return HWFault; } // MJL removed the ntohl, our histogram server doesn't apply htonl, // so the data arrives in LE format. (Would have to buffer data // at the server otherwise...) // Doubt this will raise any conpatibility issues. for(i = 0; i < size; i++){ //data[i] = ntohl(hmdata[i]); data[i] = hmdata[i]; } data+=size; start+=size; } return OKOK; } static int AnstoHttpGetHistogram(pHistDriver self, SConnection *pCon, int bank, int start, int end, HistInt *data){ char direct[2]; StringDictGet(self->pOption, "direct",direct, 2); if (strcmp(direct,"1") == 0) return GetHMfromSocket(self, pCon, bank, start, end, data); else return GetHMfromFile(self, pCon, bank, start, end, data); } /*--------------------------------------------------------------------*/ static int AnstoHttpSetHistogram(pHistDriver self, SConnection *pCon, int bank, int start, int end, HistInt *data){ pAnstoHttp pPriv = NULL; pPriv = (pAnstoHttp)self->pPriv; assert(pPriv != NULL); pPriv->errorCode = NOTIMPLEMENTED; strncpy(pPriv->hmError,"Not implemented",511); return HWFault; } /*---------------------------------------------------------------------*/ static long AnstoHttpGetMonitor(pHistDriver self, int i, SConnection *pCon){ return 0; } /*---------------------------------------------------------------------*/ static float AnstoHttpGetTime(pHistDriver self, SConnection *pCon){ return -999.99; } /*---------------------------------------------------------------------*/ static int AnstoHttpPreset(pHistDriver self, SConnection *pCon, int val){ pAnstoHttp pPriv = NULL; int status; char command[512]; pPriv = (pAnstoHttp)self->pPriv; assert(pPriv != NULL); snprintf(command,512,"%s?value=%d",preset,val); status = anstoHttpGet(pPriv,command); if(status != 1){ return HWFault; } return 1; } /*---------------------------------------------------------------------*/ static int AnstoHttpFreePrivate(pHistDriver self){ pAnstoHttp pPriv = NULL; pPriv = (pAnstoHttp)self->pPriv; if(pPriv == NULL){ return 1; } if(pPriv->syncRequest != NULL){ ghttp_request_destroy(pPriv->syncRequest); } free(pPriv); return 1; } /*-------------------------------------------------------------------*/ pHistDriver CreateAnstoHttpDriver(pStringDict pOption){ pHistDriver pNew = NULL; pAnstoHttp pInternal = NULL; /* create the general driver */ pNew = CreateHistDriver(pOption); if(!pNew){ return NULL; } /* add our options */ StringDictAddPair(pOption,"hmaddress","http://unknown.psi.ch:9090"); StringDictAddPair(pOption,"hmconfigurescript","hmconfigure"); /* initialise our private data structure */ pInternal = (pAnstoHttp)malloc(sizeof(anstoHttp)); if(!pInternal){ free(pNew); return NULL; } memset(pInternal,0,sizeof(anstoHttp)); pNew->pPriv = pInternal; pInternal->syncRequest = ghttp_request_new(); memset(pInternal->hmError,0,sizeof(pInternal->hmError)); if(pInternal->syncRequest == NULL){ free(pNew); free(pInternal); return NULL; } /* direct = 0 Get HM from data files * direct = 1 Get HM from http server */ StringDictAddPair(pOption, "direct", "1"); StringDictAddPair(pOption, "hmdatapath", "../HMData"); StringDictAddPair(pOption, "scanpoint", "0"); /* configure all those functions */ pNew->Configure = AnstoHttpConfigure; pNew->Start = AnstoHttpStart; pNew->Halt = AnstoHttpHalt; pNew->GetCountStatus = AnstoHttpStatus; pNew->GetError = AnstoHttpError; pNew->TryAndFixIt = AnstoHttpFixIt; pNew->GetData = AnstoHttpGetData; pNew->GetHistogram = AnstoHttpGetHistogram; pNew->SetHistogram = AnstoHttpSetHistogram; pNew->GetMonitor = AnstoHttpGetMonitor; pNew->GetTime = AnstoHttpGetTime; pNew->Preset = AnstoHttpPreset; pNew->FreePrivate = AnstoHttpFreePrivate; pNew->Pause = AnstoHttpPause; pNew->Continue = AnstoHttpContinue; return pNew; }