Let’s ‘C’ some server messages

We are now starting to see some functionality in the server program so lets enhance that a little more. One of the problems we all experience is accessing the messages in a message queue on the server from a remote device such as a phone so we will add some functionality to get those messages. We are going to start by just pulling the message data which shows most of the information we are interested in, maybe you can expand this to extract the help text in your own code? The API we will use will be capable of extracting all of the data from the message queue (upto 2GB of messages), it will return some of those messages very quickly while working on others in the background. This will ensure the user gets a response quickly and the additional data will be sent as it is built.

The API’s we will use are QGYOLMSG, QGYGTLE and QGYCLST the first is responsible for building the list the second allows the content of the list to be exposed while the third closes the list and frees up the resources. We will use a number of the functions we have already built and add a number of new ones, as per our last posting this builds on the existing code so we are just showing the additional code we have added. 
Because we are adding new functions to the Service Program we will need to update the export listing and generate new signatures so old programs will continue to function without having to re-compile them. The code is commented to show what we do and why but I would suggest you read the API docs from the Knowledge Center online.

Here is the code which is to be added to the WORKER program.
         case 3 :
            if(valid_user == 1) {
               if(handle_MR(accept_sd,a_e_ccsid,e_a_ccsid) != 1) {
                  sprintf(msg_dta,"Failed to retrieve messages");
                  len = strlen(msg_dta);
                  convert_buffer(msg_dta,convBuf,len,_32K,e_a_ccsid);
                  rc = send(accept_sd,convBuf,len,0);
                  }
               }
            else {
               sprintf(msg_dta,"Must be signed on to send request");
               len = strlen(msg_dta);
               convert_buffer(msg_dta,convBuf,len,_32K,e_a_ccsid);
               rc = send(accept_sd,convBuf,len,0);
               }
            break;    
The process will be similar to the Command Server in that as soon as the correct Key is entered the request will be handed off to the processing function, also the function will not be available until after the user has signed on. If you need to get messages from a message queue which is not in the library list you can use the Command processing to add the library list prior to making the call.

This is the handler code
// Function handle_MR()
// purpose: retrieve messages from a message queue
// @parms
//      socket
//      conversion table a-e
//      conversion table e-a
// returns 1 on success

int handle_MR(int accept_sd,
              iconv_t a_e_ccsid,
              iconv_t e_a_ccsid) {
int rc = 0;                                 // return code
int len = 0;                                // counter
int Min_Recs = 50;                          // Async record request
int Num_Recs = 0;                           // number records processed
int Total_Recs = 0;                         // total records in queue
int i = 0;                                  // counter
int more = 0;                               // more messages
char Sort_Info = '0';                       // msg sort (not sorted)
char recv_buf[_32K];                        // recv buffer
char msg_dta[1024];                         // message buffer
char convBuf[_1KB];                         // conversion buffer
char Q_MsgQ[22];                            // qualified message queue
char SPC_Name[20] = "QGYOLMSG  QTEMP     "; // usrspc for data
char ListInfo[80];                          // list info returned by API
char QI[21] = {'1',' '};                    // msgq to list
char QL[44];                                // holder
char Msg_Buf[_1KB];                         // returned message
char MsgQ[20] = "          *LIBL     ";     // message queue
char _CRLF[2] = {0x0d,0x0a};                // CRLF ASCII
char *space;                                // usrspc pointer
char *tmp;                                  // temp ptr
char *Data;                                 // data ptr
SelInf_t SI = {0};                          // selection info
Msg_Ret_t *QIPtr;                           // queue info ptr
time_fmt_t *t;                              // time struct ptr
date_fmt_t *d;                              // date struct ptr
Msg_Dets_t Dets;                            // messages returned
Qgy_Olmsg_ListInfo_t *ret_info;             // returned hdr
Qgy_Olmsg_RecVar_t *ret_msg;                // returned message
Qgy_Olmsg_IDFieldInfo_t *field_data;        // returned msg dta
Os_EC_t Error_Code = {0};                   // Error Code

Error_Code.EC.Bytes_Provided = _ERR_REC;
// first get the message queue name
sprintf(msg_dta,"Please enter the message Queue Name : ");
len = strlen(msg_dta);
convert_buffer(msg_dta,convBuf,len,_1KB,e_a_ccsid);
rc = send(accept_sd,convBuf,len,0);
// make sure the data was sent
if(rc != len) {
   return -1;
   }
// get the comman string to process
rc = recv(accept_sd,recv_buf,_32K,0);
// convert to EBCDIC
convert_buffer(recv_buf,convBuf,rc,_1KB,a_e_ccsid);
// copy with removed A0
memcpy(MsgQ,convBuf,rc-1);
// set up the message queue to retrieve, '1' denotes message queue
memcpy(&QI[1],MsgQ,20);
// get a space pointer for the messages
if(Get_Spc_Ptr(SPC_Name,&space,_1MB) != 1) {
   return -1;
   }
memcpy(SI.osData.List_Direction,"*PRV      ",10);
// return all messages
SI.osData.Severity_Criteria = 0;
// max message length for 0302 key
SI.osData.Max_Msg_Length = 132;
// help length, we do not request
SI.osData.Max_Help_Length = 0;
//offset to selection criteria (44 bytes)
SI.osData.Sel_Criteria_Offset = sizeof(_Packed struct Qgy_Olmsg_MsgSelInfo);
// number of selections
SI.osData.Num_Sel_Criteria = 1;
// offset to keys
SI.osData.Start_Msg_Keys_Offset = 54;
SI.osData.Retd_Fields_IDs_Offset = 58;
SI.osData.Num_Fields = 2;
// select all with key based on last message added
memcpy(SI.Sel_Cri[0],"*ALL      ",10);
memset(SI.Msg_Key[0],0xFF,4);
// fields to return, msg with data and reply status
SI.FieldID[0] = 302;
SI.FieldID[1] = 1001;
// set up the pointer to the message structure
ret_msg = (Qgy_Olmsg_RecVar_t *)Msg_Buf;
// set up the pointer to list info
ret_info = (Qgy_Olmsg_ListInfo_t *)ListInfo;
// date and time conversion
t = (time_fmt_t *)ret_msg->Time_Sent;
d = (date_fmt_t *)ret_msg->Date_Sent;
do {
   more = 0;
   // pull the messages into the user space
   QGYOLMSG(space,
            _1MB,
            ListInfo,
            Min_Recs,
            &Sort_Info,
            &SI,
            sizeof(SI),
            QI,
            QL,
            &Error_Code);
   if(Error_Code.EC.Bytes_Available > 0) {
      snd_error_msg(Error_Code);
      return -1;
      }
   if(ret_info->Records_Retd < ret_info->Total_Records) {
      more == 1;
      }
   Num_Recs = ret_info->Records_Retd;
   //sprintf(msg_dta,"Messages %d - %d",ret_info->Records_Retd,ret_info->Total_Records);
   //snd_msg("GEN0001",msg_dta,strlen(msg_dta));
   // if nothing to do just break
   if(ret_info->Total_Records <= 0) {
      break;
      }
   // loop through and get the messages to be sent
   for(i = 1; i <= Num_Recs; i++) {
      QGYGTLE(Msg_Buf,
              sizeof(Msg_Buf),
              ret_info->Request_Handle,
              &ListInfo,
              1,
              i,
              &Error_Code);
      if(Error_Code.EC.Bytes_Available > 0) {
         // clean up and return to caller
         if(memcmp(Error_Code.EC.Exception_Id,"GUI0006",7) != 0)
            snd_error_msg(Error_Code);
         QGYCLST(ret_info->Request_Handle,&Error_Code);
         return -1;
         }
      // set up the access to the data
      tmp = Msg_Buf;
      tmp += ret_msg->Offset_to_Fields_Retd;
      field_data = (Qgy_Olmsg_IDFieldInfo_t *)tmp;
      Data = tmp;
      Data += sizeof(_Packed struct Qgy_Olmsg_IDFieldInfo);
      // copy the message infor to the buffer
      memcpy(Dets.MsgID,ret_msg->Msg_ID,7);
      Dets.MsgSev = ret_msg->Msg_Severity;
      Cvt_Hex_Buf(ret_msg->Msg_Key,Dets.MsgKey,4);
memset(Dets.MsgDta,' ',132); memcpy(Dets.MsgDta,Data,field_data->Data_Length); sprintf(Dets.Date_Time,"20%.2s-%.2s-%.2s %.2s:%.2s:%.2s",d->Y,d->M,d->D,t->H,t->M,t->S); // convert to ASCII and send convert_buffer((char *)&Dets,convBuf,sizeof(Dets),_1KB,e_a_ccsid); rc = send(accept_sd,convBuf,sizeof(Dets),0); // send a CRLF for ASCII rc = send(accept_sd,_CRLF,2,0); } // set the starting message key memcpy(SI.Msg_Key[0],ret_msg->Msg_Key,4); } while(more == 1); // clean up the resources QGYCLST(ret_info->Request_Handle,&Error_Code); if(Error_Code.EC.Bytes_Available > 0) { snd_error_msg(Error_Code); return -1; } // first get the message queue name sprintf(msg_dta,"End of Messages : "); len = strlen(msg_dta); convert_buffer(msg_dta,convBuf,len,_1KB,e_a_ccsid); rc = send(accept_sd,convBuf,len,0); // make sure the data was sent if(rc != len) { return -1; } return 1; }
The process is pretty simple, first we get the name of the message queue that we are to retrieve the messages from, next we build the selection criteria which will be used to extract the relevant messages. In this instance we have decided to get the latest message first and then walk back up through the messages, you could alternate that depending on your requirements.We are only going to extract the message data (with replacement data) and no other fields, but we need to pass in the reply status as well as per the instructions in the documentation. (We can use that later to do things like only send messages that need a reply etc). We have also set the key to use as the start key as 0xFF (4 bytes set to this value) s o it is the maximum key, all other messages will have a lower key value, and said extract all *PRV (previous) messages. Another important option is the minimum records to return Synchronously while have the rest built Asynchronously, we have set this to 50 and created a user space that will be more than adequate to hold that many entries.

We check the returned information to determine of all available messages have been returned, if there are more messages we set the flag (more ) to 1 and once all of the entries have been processed we copy the key from the last message to the selection information so we only get messages we have not yet seen. We then retrieve each message from the list using the QGYGTLE API and send it back to the user, you will see we also send a CRLF (ASCII version) after each message so the user can see separate lines from each return. (Perhaps you could add a request process for each message with a break command if you have seen enough).

Once all of the current list has been processed we check if we need to extract the next list set and go through the same process again. If there are no more messages we will send a message stating that and close the list using the QGYCLST API. You will notice that we are calling a new function Cvt_Hex_Buf(), this simply converts the hex characters in the message key field to a readable version. It can be omitted and the message key left as hex, but in this instance we are converting simply to remove any strange looking content when displayed in the client interface. Eventually we can leave it as a hex value and use it to retrieve a specific message based on it, here is the code to convert the key.
// function Cvt_Hex_Buf
// Convert Hex to Ascii for output
// @parms
//      char Ptr hex buffero
//      char ptr ascii buffer
//      int length hex buffer
// returns 1

int Cvt_Hex_Buf(char *inbuf,
                char *outbuf,
                int buflen) {
char *tmp;

tmp = inbuf;
while(buflen) {
   sprintf(outbuf,"%02x",*tmp);
   buflen--;
   tmp++;
   outbuf += 2;
   }
return buflen;
}           
If you look at the code online in GitHub you will notice we have added the Crt_Q_Name() function as well which was originally a built in function for the MSGID program, but as we are building more functionality this will be very useful as an exported function. There are a number of changes to be made to the header programs to allow the programs to be built, we are going to leave those out but again they are contained in the GitHub code.

Lets compile all of the modules and programs and give it a run through.
CRTCMOD MODULE(OSLIB/SRVFUNC) SRCFILE(OSLIB/QCSRC) SRCMBR(SRVFUNC) OUTPUT(*PRINT) DBGVIEW(*SOURCE) REPLACE(*YES) TGTRLS(V7R1M0)
RTVBNDSRC MODULE(OSLIB/SRVFUNC) SRCFILE(OSLIB/QSRVSRC) MBROPT(*ADD)
CRTCMOD MODULE(OSLIB/WORKER) SRCFILE(OSLIB/QCSRC) SRCMBR(WORKER) OUTPUT(*PRINT) DBGVIEW(*SOURCE) REPLACE(*YES) TGTRLS(V7R1M0)
CRTPGM PGM(OSPGM/WORKER) MODULE(OSLIB/WORKER) BNDDIR(OSLIB/OS) TGTRLS(V7R1M0)
CRTSRVPGM SRVPGM(OSPGM/SRVFUNC) MODULE(OSLIB/SRVFUNC) SRCFILE(OSLIB/QSRVSRC) BNDDIR(OSLIB/OS) TGTRLS(V7R1M0)
Dont forget to adjust the QSRVSRC member SRVFUNC to show the new exports and set the previous ones to *PRV. Remember *CURRENT should always be at the top.

Now we can do some testing… We are going to extract the messages from QSYSOPR after signing in. We returned over 100 messages but the following only shows 1, we could see how many were returned in each call to the QGYOLMSG API which demonstrated the loop for additional messages actually works.
nc sas4.shield.local 12345
0000
Please Enter your Profile name : CHRISH
Please Enter your Password : *****
0003
Please enter the message Queue name : QSYSOPR
CPIEF0100001760Service Agent is analyzing your system product activity log entries. 2018-05-12 04:14:11
0001
OK Signing off and closing socket :
That’s all we need to do to get the messages, as we have said previously this is the base so there are a lot of exciting functions you could add to this such as extracting only messages that need a reply or extract them all and highlight the ones which need a reply. Building the ability to reply to these messages will be quite easy because you have the message key (make sure you use the 4 character variable not the 8 character we use for the display.

This post we have looked at the use of the Open List API’s which IBM provides to allow processing large lists that wont fit into 16MB (max size for a User Space object) which other list API’s rely on.We have looked at how to manage the signatures when processing service programs so they can be used with programs which knew about an earlier signature. We have implemented a do while loop and a for loop in the correct manner and set up exit points when things go wrong. Mainly we have added a nice new feature to our server program.

NOTE:
While we were experimenting with the QGYOLMSG API we did find what could be a bug! If you set the Min Messages to 0 (documentation states this is possible and should effectively build all of the messages asynchronously) it runs the message build server program but returns with 0 as the total messages available and 0 as the number of messages returned. This should return the total available as the number of messages in the queue and the number returned as 0.

Hope you find the post useful, as always we are going to load the new code up to GitHub so it can be downloaded.

Chris…