Let’s ‘C’ how (part II)

In the last post we created the program which retrieved source code lines with a certain filter string. We are going to modify the program to verify that all of the messages we have in our message file are actually being used within our source code. We purposely leave the temporary file in QTEMP as we don’t need to keep a record of the file once we are finished with it, QTEMP objects are cleaned up once you sign off so its a nice way to clean up without having to search through lots of objects to find those which are ‘temporary’ in nature.

Side note: 
I did mention that we would create a separate program to create the temporary source file by exclusion instead of inclusion which is how it works today. In reality it is such as simple change it not really worth a whole post to explain so if you want to build the temp file by exclusion simply change the following line in the source code.
if(strstr(buf,filter)) {
to 
if(!strstr(buf,filter)) {
Why would you need this? In our initial design we wanted to exclude any lines which started with ‘//’ or ‘#’ (these are commented lines and special include lines such a #pragma so we know there are no messages in them.) Our second design works better with inclusion only because we are sure the ONLY time we send messages is via the xxx_msg() functions we have built.

End of Side Note:

The new program will use a temporary, file that we create, to walk through all the messages in a message file and check if we see use the message ID in the source files. First lets create a couple of new source members to hold the code. 
ADDPFM FILE(OSLIB/QCSRC) MBR(CHKMSGID) SRCTYPE(C) TEXT(‘Check source for MSGID’)
ADDPFM FILE(OSLIB/QCMDSRC) MBR(CHKMSGID) SRCTYPE(CMD) TEXT(‘Check source for MSGID’)
The source for the command is almost identical to that we used in the COPYSRCA command, you can copy that command and make the required alterations if you want but here it is in all its glory..
/*===================================================================*/
/*  COMMAND name :  CHKMSGID                                         */
/*  Author name..:  Chris Hird                                       */
/*  Date created :  April 2018                                       */
/*                                                                   */
/*                                                                   */
/*  Purpose......:  Check message file content against source        */
/*  CPP..........:  CHKMSGID                                         */
/*  Revision log.:                                                   */
/*  Date     Author    Revision                                      */
/*                                                                   */
/*  @Copyright Chris Hird 2018                                       */
/*===================================================================*/

             CMD        PROMPT('Check message IDs')

             PARM       KWD(MSGF) TYPE(QUAL1) MIN(1) PROMPT('Message File')
             PARM       KWD(SRCF) TYPE(QUAL1) MIN(1) PROMPT('Source File')
             PARM       KWD(TGTF) TYPE(QUAL1) MIN(1) PROMPT('Target File')
             PARM       KWD(INCSTR) TYPE(*CHAR) LEN(50) MIN(1) VARY(*YES *INT2) PROMPT('Include String')

 QUAL1:      QUAL       TYPE(*NAME) LEN(10)
             QUAL       TYPE(*NAME) LEN(10) DFT(*LIBL) SPCVAL((*LIBL)) PROMPT('Library name:')  

Now we can add the following code to the QCSRC/CHKMSGID member. If you are using cut and paste be aware some of the code will be corrupted due to the use of the ‘< >’ symbols which WordPress does not like. we replace them with &gt; and &lt; so that is what your code will contain when copied and pasted.
//
// Copyright (c) 2018 Chris Hird
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions
// are met:
// 1. Redistributions of source code must retain the above copyright
//    notice, this list of conditions and the following disclaimer.
// 2. Redistributions in binary form must reproduce the above copyright
//    notice, this list of conditions and the following disclaimer in the
//    documentation and/or other materials provided with the distribution.
//
// Disclaimer :
// This code is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

#include <H/MSGFUNC>                        // Message headers
#include <H/SPCFUNC>                        // User Space functions
#include <H/COMMON>                         // common header file
#include <qmhrtvm.h>                        // retrieve message api header
#include <recio.h>                          // record IO
#include <errno.h>                          // error number

#pragma linkage (COPYSRCA,OS,nowiden)
void COPYSRCA(char *,char *,char *);

// function Crt_Q_Name()
// Purpose: To create a qualified object name. LIB/OBJ
// @parms
//      string object
//      string q name
// returns 1 success

int Crt_Q_Name(char *Object,
               char *Q_Name) {
int i,j = 0;                                // counters

for(i = 10,j = 0; i < 20; i++,j++) {
   Q_Name[j] = (Object[i] == ' ') ? '\0' : Object[i];
   }
Q_Name[j] = '\0';
strcat(Q_Name,"/");
j = strlen(Q_Name);
for(i = 0;i < 10;i++,j++) {
   Q_Name[j] = (Object[i] == ' ') ? '\0' : Object[i];
   }
Q_Name[j] = '\0';
return 1;
}

// function check_recs()
// Purpose: Read through the records looking for string
// @parms
//      string to find
// returns 1 success

int check_recs(_RFILE *fp,
               char *MsgId,
               char *MsgF) {
_RIOFB_T *fdbk;                             // feed back
int found = 0;                              // counter
int rec_len = 150;                          // record length
char buf[151] = {'\0'};                     // read buffer
char cmd[256];                              // command string

do {
   fdbk = _Rreadn(fp,buf,rec_len,__NO_LOCK);
   if(strstr(buf,MsgId)) {
      found = 1;
      break;
      }
   }while(fdbk->num_bytes != EOF);
// if we did not find the message ID in the source we could delete it
// we prompt the command so we could cancel if required
if(found == 0) {
   // remove the messaid from the file
   sprintf(cmd,"?RMVMSGD MSGID(%s) MSGF(%s)",MsgId,MsgF);
   system(cmd);
   }
return 1;
}

int main(int argc, char** argv) {
_RFILE *fp;                                 // file ptr
_RIOFB_T *fdbk;                             // feed back
int rec_len = 150;                          // record length
int Rep_Dta_Len = 0;                        // length of replacement data passed
char UsrSpc[20] = "RTVMSG    QTEMP     ";   // Userspace returned data
char Rep_Dta[16] = {'0'};                   // replacement data
char Rep_Sub_Vals[10] = "*NO       ";       // replace substitution values
char Ret_Fmt_Ctl[10] = "*NO       ";        // return format control chars
char Rtv_Opt[10] = "*FIRST    ";            // retrieval option
char Msg_Id[7] = {' '};                     // returned message ID
char msg_id[8] = {'\0'};                    // message id to look for
char msg_dta[_MAX_MSG];                     // message buffer
char NewSrc[22];                            // new srource file
char MsgF[22];                              // message file
char *space;                                // pointer to userspace
Qmh_Rtvm_RTVM0300_t *MsgInfo;               // pointer to returned structure
Os_EC_t Error_Code;                         // error Code

Error_Code.EC.Bytes_Provided = sizeof(Error_Code);
// create the source file names etc and copy the source
Crt_Q_Name(argv[1],MsgF);
Crt_Q_Name(argv[3],NewSrc);
// create the temporary source file and populate
COPYSRCA(argv[2],argv[3],argv[4]);
// open the file
if((fp = _Ropen(NewSrc,"rr")) == NULL)  {
   sprintf(msg_dta,"Open failed %s: %s\n",NewSrc,strerror(errno));
   snd_msg("GEN0001",msg_dta,strlen(msg_dta));
   exit(1);
   }
// get ptr to usrspc for messages
if(Get_Spc_Ptr(UsrSpc,&space,_4MB) != 1) {
_Rclose(fp); exit(-1); } // get the first message from message file QMHRTVM(space, _4MB, "RTVM0300", Msg_Id, argv[1], Rep_Dta, Rep_Dta_Len, Rep_Sub_Vals, Ret_Fmt_Ctl, &Error_Code, Rtv_Opt, 0, 0); if(Error_Code.EC.Bytes_Available > 0) { snd_error_msg(Error_Code);
_Rclose(fp); exit(-1); } MsgInfo = (Qmh_Rtvm_RTVM0300_t *)space; if(MsgInfo->Bytes_Available <= 0) { sprintf(msg_dta,"no message found\n"); snd_msg("GEN0001",msg_dta,strlen(msg_dta));
_Rclose(fp); exit(0); } memcpy(Rtv_Opt,"*NEXT ",10); do { memcpy(msg_id,MsgInfo->Message_ID,7); //printf("Checking %s\n",msg_id); // set to start of file each time _Rlocate(fp,NULL,0,__START); check_recs(fp,msg_id,MsgF); // get the next message memcpy(Msg_Id,MsgInfo->Message_ID,7); //printf("%.7s\n",Msg_Id); QMHRTVM(space, _4MB, "RTVM0300", Msg_Id, argv[1], Rep_Dta, Rep_Dta_Len, Rep_Sub_Vals, Ret_Fmt_Ctl, &Error_Code, Rtv_Opt, 0, 0); if(Error_Code.EC.Bytes_Available > 0) { snd_error_msg(Error_Code);
_Rclose(fp); exit(-1); } }while(MsgInfo->Bytes_Available > 0);
_Rclose(fp); exit(0); }
This is an enhancement to the program FNDMSGCNT, the big change is we are going to use the content of the message file to look for each of the message ID’s in the temporary source file, if we don’t find the message ID in the temporary source file we will prompt up the command to remove the message from the message file (safer than just deleting as its just a piece of dumb software, pressing F3 or F12 stops the request). A number of new features have been added into the program which we will go through in a bit more detail.

This program is going to use the COPYSRCA program to copy the source records over to the temporary file, to do this we will tell the compiler that we are going to use that program and pass parameters with the following lines of code.
#pragma linkage (COPYSRCA,OS,nowiden)
void COPYSRCA(char *,char *,char *);
That’s all we need to be able to call the program by name in this program, obviously the parameters need to match up with what is expected etc. and you could qualify the name, but you should get the ghist…

Next we have created a couple of functions that we use within the program, an important point here is we are using the Crt_Q_Name() function which we have already used in the COPYSRCA program. Ideally we should remove the function from both programs and put it in its own Service Program, but for now we will just have 2 separate copies of the code to maintain.
The next function is where we read through each line of the temporary source file to look for the MsgID we are interested in, if we find it we flag it as found and break from the loop (once we have one use, we are not interested if its used again elsewhere). At the end of the function we check if the found flag was set, if not (found == 0) we prompt the command to remove it from the message file (? in front of the command when run through the system function prompts the command to the calling user).  You will notice that we have not had to pre-declare the functions in the file, this is because the compiler knows about the functions and how they are called before they get used (they are coded before they are used) If we had added the functions after the main() function we would have had to create a couple of pre-declarations before the main function such as :
int Crt_Q_Name(char *,char *);
int check_recs(_RFILE *,char *,char *);
Within the main function, we create the qualified names for the source file and the message file. Some functions require they are passed in as LIBRARY/NAME strings so we create them from the 20 character strings passed in by the command (you will have seen that previously if you have been following this series). Next we call the COPYSRCA program passing it the same parameters we are passed, we know they are formatted correctly in this instance so no need to change, this will create the temporary source file for us in QTEMP. 

Once the file is created we can open it ready to be used for the message search.
if((fp = _Ropen(NewSrc,”rr”)) == NULL) {
   sprintf(msg_dta,”Open failed %s: %s\n”,NewSrc,strerror(errno));
   snd_msg(“GEN0001”,msg_dta,strlen(msg_dta));
   exit(1);
}  
We are only interested in reading through the file so the “rr” option sets that on the open request. It is important to note that while any open file pointers will be closed once the program has ended and cleaned up, it is always good practice to close all open files before you exit. We then read through the message file and send the message ID from each message into the function that will check the temporary source file, you will notice that we always set the file pointer to the start of the file, otherwise each time it gets passed to the function it will be at the same place it was left the last time it went through. 
_Rlocate(fp,NULL,0,__START);
The rest of the program is pretty self explanatory.

That is all we have to do for this particular tool so once you have all of the code entered run the following commands to build the required objects.
(REMEMBER this is a series of posts so we have already built objects that these programs rely on, if you have problems with the compiles look at the previous posts.)
CRTCMOD MODULE(OSLIB/CHKMSGID) SRCFILE(OSLIB/QCSRC) SRCMBR(CHKMSGID) OUTPUT(*PRINT) DBGVIEW(*SOURCE) TGTRLS(V7R1M0)
CRTPGM PGM(OSPGM/CHKMSGID) MODULE(OSLIB/CHKMSGID) BNDDIR(OSLIB/OS) TGTRLS(V7R1M0)
CRTCMD CMD(OSPGM/CHKMSGID) PGM(*LIBL/CHKMSGID) SRCFILE(OSLIB/QCMDSRC) SRCMBR(CHKMSGID)
You are now ready to give it a spin, in our test we added a new message to the message file OSPGM/OSMSGF with an ID of ‘TST0001’. We know that we have not used that Message ID in any of our programs so its should flag as being extra to requirements in our program.

Run the following command:
CHKMSGID MSGF(OSPGM/OSMSGF) SRCF(OSLIB/QCSRC) TGTF(QTEMP/QCSRC) INCSTR(‘snd_msg(‘)
You should see something similar to the following :
                      Remove Message Description (RMVMSGD)                      
                                                                                
 Type choices, press Enter.                                                     
                                                                                
 Message identifier . . . . . . . > TST0001       Name                          
 Message file . . . . . . . . . . > OSMSGF        Name                          
   Library  . . . . . . . . . . . >   OSPGM       Name, *LIBL, *CURLIB          
                                                                                
                                                                                
                                                                                
                                                                                
                                                                                
                                                                                
                                                                                
                                                                                
                                                                                
                                                                                
                                                                                
                                                                                
                                                                                
                                                                         Bottom 
 F3=Exit   F4=Prompt   F5=Refresh   F12=Cancel   F13=How to use this display    
 F24=More keys                                                                  

This shows that we have identified a message ID which is not used within our source code. If you delete the message and run the request again you should simply be returned to the command line where you ran the command.

In this post we have covered the following:
  1. Setting up the C program to directly call another program passing parameters
  2. Using functions
  3. File functions for working with source files
  4. Calling API’s within your programs  
If you have any questions about this post contact us via the form provided on our website, we will add the changes made here to our GitHub repository before the end of the day.

The next project will look at how to create a Server Program that will listen for connections against a particular port on your IBM i.  As part of the process we will show how to convert the incoming content from ASCII to EBCDIC and vice versa using API’s plus how you can test the connection using a Linux terminal. 

Chris…