Let’s ‘C’ it work some commands

So far we have created a Server which will allows a user to sign onto the IBM i via an ASCII based terminal, now we are going to look at how to add a bit more functionality. This post is going to show how to add a very simple command checking and processing function, it is not going to be a production level process but something that shows the basics so you can add to it. The security will be managed via the sign on process, if the user has not signed-on we will inform them that is required before the command request, this makes sure the user is authorized to run the command as it will be run under their profile.

As part of the update we are going to be adding a number of new functions, some to replace the existing code and others to implement the new feature. This will require a rebuild of the Service Program so we have to be aware of the limitations when doing that. The Service Program is mapped to the calling program via a signature, to ensure we have the correct signature we will have to update the source file which manages the exports for the Service Program, as we have seen before this allows a Service Program to manage connections from programs that have been built previously which have no idea about the new functions the Service Program now contains.

Note: in this particular case we did not need to go through this exercise because the only program which uses this Service Program is also being re-compiled to accommodate the new features.

A lot of times we are converting from ASCII to EBCDIC and vice versa, when we first started its was only used in a few places so it did not matter that we did each conversion inline as the code was still pretty short. Now we are increasing the code lines we felt that it was a good time to remove the inline conversions and place them into a separate function, this function will be part of the new exports from the SRVFUNC Service Program so if a later program requires the same functionality we can simply bind the new program to the existing Service Program and that functionality will be available.

The following is the new function we are going to add for the conversion from ASCII to EBCDIC or EBCDIC to ASCII.
// Function convert_buffer()
// purpose: Convert buffer to and from ASCII
// @parms
//      input buffer
//      conversion buffer
//      in length
//      out length
//      buffer len
//      conversion table
// returns 1 on success

int convert_buffer(char *inBuf,
                   char *outBuf,
                   int inBufLen,
                   int outBufLen,
                   iconv_t table) {
int ret = 0;                                // return value
size_t insz;                                // input len
size_t outsz;                               // output size
char *out_ptr;                              // buffer ptr
char *in_ptr;                               // buffer ptr

insz = inBufLen;
outsz = outBufLen;
in_ptr = inBuf;
out_ptr = outBuf;
ret = (iconv(table,(char **)&(in_ptr),&insz,(char **)&(out_ptr),&outsz));
return 1;
}        
We are simply passing in 2 buffers one which contains the text to be converted the other will contain the converted data plus a table which will be used to carryout the conversion. the following code
in_ptr = recv_buf;
out_ptr = convBuf;
insz = rc;
outsz = 132;
ret = (iconv(a_e_ccsid,(char **)&(in_ptr),&insz,(char **)&(out_ptr),&outsz));
can now be replace with
convert_buffer(recv_buf,convBuf,rc,132,a_e_ccsid);
This will significantly reduce the number of lines of code in the source member making it a lot easier to maintain later.

The next function we are going to add will manage the command request, as we are going to be checking the eligibility of the command to be run prior to this function being called we simply request the command string to be processed and respond to the user accordingly. This is the code we are going to use.
// Function handle_CM()
// purpose: Handle a command request
// @parms
//      socket
//      conversion table a-e
//      conversion table e-a
// returns 1 on success

int handle_CM(int accept_sd,
              iconv_t a_e_ccsid,
              iconv_t e_a_ccsid) {
volatile int e_count = 0;                   // error flag
int rc = 0;                                 // return code
int len = 0;                                // counter
char recv_buf[_32K];                        // recv buffer
char msg_dta[1024];                         // message buffer
char Cmd[1024] = {'\0'};                    // Command string converted
char convBuf[1024];                         // conversion buffer


// first we need to retrieve the command
sprintf(msg_dta,"Please enter a command : ");
len = strlen(msg_dta);
convert_buffer(msg_dta,convBuf,len,132,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);
// copy to the Profile holder which is initialized with all blanks after conversion
// maximum command size must be less than 1023 based on conversion buffer size
if(rc > 1023) {
   sprintf(msg_dta,"Command string too long");
   convert_buffer(msg_dta,convBuf,len,132,e_a_ccsid);
   rc = send(accept_sd,convBuf,len,0);
   return -1;
   }
// command len is OK so convert the buffer
convert_buffer(recv_buf,convBuf,rc,132,a_e_ccsid);
// strip off the 'A0' byte.
len = rc -1;
memcpy(Cmd,convBuf,len);
// set up the error handler
#pragma exception_handler(cmd_errhdl,e_count,0,_C2_ALL,_CTLA_HANDLE)
// first make sure its correctly formed
e_count = 0;
QCMDCHK(Cmd,len);
if(e_count > 0) {
   sprintf(msg_dta,"Command check failed");
   convert_buffer(msg_dta,convBuf,len,132,e_a_ccsid);
   rc = send(accept_sd,convBuf,len,0);
   return -1;
   }
// issue the command
e_count = 0;
QCMDEXC(Cmd,len);
if(e_count > 0) {
if(e_count == 1) {
sprintf(msg_dta,"Command execution failed as library does not exist");
}
else {
sprintf(msg_dta,"Command execution failed");
} convert_buffer(msg_dta,convBuf,len,132,e_a_ccsid); rc = send(accept_sd,convBuf,len,0); return -1; } // remove the handler #pragma disable_handler return 1; }
As previously the server function takes over the communication as soon as the request is sent in, so the first few lines will request the command from the remote user using the new convert_buffer() function to convert the text as required. Once we have the command we need to verify the command to make sure its correctly formatted and has the correct parameter types etc. But the API’s we use to carry out this check do not have any ability to respond with an error like other API’s we have used in the past, this means we have to wrap the API’s in an exception handler. The exception handler can be used for both requests so we have wrapped it around both the command check and the command execution API’s. As part of the handler set up we describe the handler function, a common data buffer (allows us to pass data back and forth) plus some information about the errors to be handled etc. (check the documentation for more information on what the handler is capable of). This is the handler code which will be run if any exceptions are signaled.
// Function cmd_errhdl()
// purpose: Handle any errors generated by command processing,sets count to value
//          which relates to the error captured
// @parms
//      exception info struct ptr
// returns nothing

static void cmd_errhdl(_INTRPT_Hndlr_Parms_T *excp_info) {
int *count;                                 // error counter
char Msg_Type[10] = "*INFO     ";           // msg type
char QRpy_Q[20] = {' '};                    // reply queue
char Msg_Key[4] = {' '};                    // msg key
Rcv_Msg_t rtv_dta;                          // msg buffer for retrieval
Os_EC_t Error_Code = {0};                   // error code struct

Error_Code.EC.Bytes_Provided = _ERR_REC;
// set the count ptr to exception
count = (int *)(excp_info->Com_Area);
// retrieve the message for the program
QMHRCVPM(&rtv_dta,
         sizeof(rtv_dta),
         "RCVM0200",
         "*         ",
         0,
         "*ANY      ",
         (char *) (&(excp_info->Msg_Ref_Key)),
         0,
         "*SAME     ",
         &Error_Code);
if(Error_Code.EC.Bytes_Available > 0) {
   snd_error_msg(Error_Code);
   return;
   }
// library already in library list
if(memcmp(excp_info->Msg_Id,"CPF2103",7) == 0) {
   // not really a problem so let flow through with no error
   }
else if(memcmp(excp_info->Msg_Id,"CPF2110",7) == 0) {
// library does not exist
*count = 1;
} // handle the message QMHCHGEM(&(excp_info->Target), 0, (char *) (&(excp_info->Msg_Ref_Key)), "*HANDLE ", "", 0, &Error_Code); if(Error_Code.EC.Bytes_Available > 0) { snd_error_msg(Error_Code); } // send an information copy to the message queue QMHSNDM(excp_info->Msg_Id, rtv_dta.msg_struct.Message_File_Name, rtv_dta.msg_data, rtv_dta.msg_struct.Length_Data_Returned, Msg_Type, _DFT_MSGQ, 1, QRpy_Q, Msg_Key, &Error_Code); if(Error_Code.EC.Bytes_Available > 0) { snd_error_msg(Error_Code); } return; }
Once the function is called we will set a link to the common data so we can update, then retrieve the message which was generated by the API call. As we are testing with the ADDLIBLE command I wanted to add a simple check that if its already set (CPF2103 is sent) we can just ignore and move on. However if the library does not exist or the command fails we need to respond accordingly. You will notice that I have set the content of the exception_info->Com_Area (an integer in this case) to 1, this is used to signal that we trapped the missing library. You could use other values to signify other errors.

The Worker program has to be updated to allow the function to be run so here is the updated code for the WORKER program, you will need to update the conversion code to use the new function as well but this is just the code to handle a command request. You will notice the valid_user variable has to be set before we attempt to collect the command, this ensures the command is run under the users profile that is requesting it. We will send the succeded message in this code, but it could equally be sent from the Server Program function.
         case 2 :
            if(valid_user == 1) {
               if(handle_CM(accept_sd,a_e_ccsid,e_a_ccsid) == 1) {
                  sprintf(msg_dta,"Command succeeded");
                  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;       
We have added the relevant header updates for the API’s we are calling plus any additional variables which are not shown in the above, the code which is on GitHub is complete with all of the changes and can be downloaded if required. I would suggest you take the content we have given above and work out how to implement into the existing code from previous posts which should increase your skills and knowledge about ‘C’ programming. Once you have added all of the relevant code you can go ahead and built the new Service Program and program which have been updated. Here are the commands we used and the order they need to be run.
CRTCMOD MODULE(OSLIB/WORKER) SRCFILE(OSLIB/QCSRC) SRCMBR(WORKER) OUTPUT(*PRINT) DBGVIEW(*SOURCE) REPLACE(*YES) TGTRLS(V7R1M0)
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)
CRTSRVPGM SRVPGM(OSPGM/SRVFUNC) MODULE(OSLIB/SRVFUNC) SRCFILE(OSLIB/QSRVSRC) BNDDIR(OSLIB/OS) TGTRLS(V7R1M0)
The RTVBNDSRC is used to update the export listing for the Service Program, once the command has been run you need to update the content of the source file to show the export levels as below.
STRPGMEXP PGMLVL(*CURRENT)
/********************************************************************/
/*   *MODULE      SRVFUNC      OSLIB        05/10/18  11:18:45      */
/********************************************************************/
  EXPORT SYMBOL("handle_CM")
  EXPORT SYMBOL("Handle_SO")
  EXPORT SYMBOL("convert_buffer")
ENDPGMEXP

STRPGMEXP PGMLVL(*PREVIOUS)
/********************************************************************/
/*   *MODULE      SRVFUNC      OSLIB        05/01/18  11:46:14      */
/********************************************************************/
  EXPORT SYMBOL("Handle_SO")
ENDPGMEXP   
The latest content is always placed at the end of the member so we moved it to the top plus we changed the existing export statement to show its the *PREVIOUS level. Now the Service program will have 2 signatures attached.

To test we used the same process as before with a terminal from the Linux Server using netcat. First we start the connection with
nc sas4.shield.local 12345
Now we need to sign on so enter the key ‘0000’ and the server will request your User name and Password.
Please enter your Profile name : CHRISH
Please enter your Password : *******
Once you have entered your information and it has been accepted you can enter the key for a command and the server will request the command to run.
0002
Please enter a command : ADDLIBLE LIB(OSPGM)
Command succeeded
You should now check the WORKER program and verify that the library has infact been aded to its library list (WRKACTJOB option5,13). If its not been set check the joblog for any errors. So lets remove the library list and check again.
0002
Please enter a command : RMVLIBLE LIB(OSPGM)
Command succeeded
End the communication as usual and it will terminate the connection.
0001
OK signing off and closing socket :
Thats it! you now have a method of running commands from your ASCII terminal on the IBM i…. Obviously a lot more needs to go into this to make it worthwhile but the bones are there. Maybe you can check out what happens when you send an incorrect command etc and add some functionality to correct etc?

As promised the code will be updtaed on GitHub so go check it out if needed. In the post we have covered a number of new API’s plus added exception handling. As part of the feature changes we have rebuilt the source member for the exports and seen how to manage the signature for the Service Program. Its important to continuously improve your code so by making the conversion process into a function and removing the existing code we have improved on our existing code base!

Thats it for this post, next we will look at how to open a file and send the content back to the user.

Chris..