Let’s ‘C’ it integrate

We have been thinking about where to take the series of posts about using ‘C’ on the IBM i and have decided to create something that will help integrate the IBM i with the other platforms you run. One of the strengths of the IBM i is the integrated DB2 database and its where most of the data your applications generate resides. We think a Client Server process which provides some level of access to the data and possibly other objects is something most companies would like, especially if it was open source and free!

Our intial interest came about after helping one of our clients update some software that talks to a mainframe, the original code needed some attention and we needed a way to ensure what we had developed would send the correct information to the mainframe (it had to be converted to ASCII). This required a test server that we could send the data to and from without the mainframe being involved. the following code is the start of what we hope will become a very good base for sending and receiving data to and from the IBM i using ASCII. The initial code will simply take a character string and convert it from ASCII to EBCDIC before sending it to a message queue and then send a ASCII based response to the client.

Before we can run the Server code we will need a few environment objects which will allow the jobs to run in batch via a SBMJOB command. We will create all of the objects in the OSPGM library, first thing we will need is a subsystem description:
CRTSBSD SBSD(OSPGM/OSSBS) POOLS((1 *BASE))
Next a Job Queue which is where all of the submitted jobs will run.
CRTJOBQ JOBQ(OSPGM/OSJOBQ) TEXT(‘Job Queue for OSSBS jobs’)
We then add the job queue to the subsystem description, this is how the active jobs show up under the subsystem description when you run a WRKACTJOB command.
ADDJOBQE SBSD(OSPGM/OSSBS) JOBQ(OSPGM/OSJOBQ)
Next we need to route the jobs so they can be started via the QCMD program, but before we can add the routing entry we need to create the class. All of this allows you to finely tune your jobs as they run, for us the defaults work just fine.
CRTCLS CLS(OSPGM/OSCLS) TEXT(‘OS Class’)
ADDRTGE SBSD(OSPGM/OSSBS) SEQNBR(1) CMPVAL(*ANY) PGM(QCMD) CLS(OSPGM/OSCLS)
As with most jobs you will want to create a Job Description which allows control over how the jobs starts and runs, again we just created a simple *JOBD which sets the job queue to be used plus sets the initial library list to our OSPGM library.
CRTJOBD JOBD(OSPGM/OSSVR) JOBQ(OSPGM/OSJOBQ) TEXT(‘OS Server Job Description’) INLLIBL(OSPGM)
As part of the program we will generate for the server side we will need to stop the running jobs, for this we will use a Data Queue where messages can be sent and acted upon by the Server program. We use a keyed Data Queue as we can identify the type of request (will be useful in the future) and filter the actions based on that request.
CRTDTAQ DTAQ(OSPGM/SVRCTLQ) MAXLEN(256) SEQ(*KEYED) KEYLEN(4) AUTORCL(*YES) TEXT(‘Server Control data queue’)
You can go ahead and start the subsystem at this time.
STRSBS SBSD(OSPGM/OSSBS)
We will be creating a couple of programs that run as the server plus a menu which will interface with the programs, the build of the Server and Worker program will use the functionality we have created in the Service programs so make sure you have those available before trying to create these programs. The menu we are going to create is a UIM menu, this will be added to a member called SVRMAIN in the QUIMSRC file. This is the first time we have used the QUIMSRC file so the following command will create it for you.
CRTSRCPF FILE(OSLIB/QUIMSRC) RCDLEN(150) MBR(SVRMENU) TEXT(‘Server Source OSPGM’)
Now add the following code to the member.
.*
.* 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.
.*
:PNLGRP HLPSHELF=list
         DFTMSGF=osmsgf
         SUBMSGF=osmsgf.
.*
:COPYR.
(c)         Copyright Chris Hird 2018
.*
.* -----------------------------------------------------------------
.* UIM Z-variable to be used as the panel identifier
.* -----------------------------------------------------------------
:VAR     NAME=ZMENU.
.*
.* -----------------------------------------------------------------
.* Define keys for the menu
.* -----------------------------------------------------------------
:KEYL     NAME=menukeys
         HELP=keyl.
:KEYI     KEY=F1
         HELP=helpf1
         ACTION=HELP.
:KEYI     KEY=F3
         HELP=exit
         ACTION='EXIT SET'
         VARUPD=NO.
F3=Exit
:KEYI     KEY=F4
         HELP=prompt
         ACTION=PROMPT.
F4=Prompt
:KEYI     KEY=F9
         HELP=retrieve
         ACTION=RETRIEVE.
F9=Retrieve
:KEYI     KEY=F12
         HELP=cancel
         ACTION='CANCEL SET'
         VARUPD=NO.
F12=Cancel
:KEYI     KEY=F24
         HELP=morekeys
         ACTION=MOREKEYS.
F24=More keys
:KEYI     KEY=ENTER
         HELP=enter
         ACTION=ENTER.
:KEYI     KEY=HELP
         HELP=help
         ACTION=HELP.
:KEYI     KEY=HOME
         HELP=home
         ACTION=HOME.
:KEYI     KEY=PAGEDOWN
         HELP=pagedown
         ACTION=PAGEDOWN.
:KEYI     KEY=PAGEUP
         HELP=pageup
         ACTION=PAGEUP.
:KEYI     KEY=PRINT
         HELP=print
         ACTION=PRINT.
:EKEYL.
.*
.* -----------------------------------------------------------------
.* Define Menu panel
.* -----------------------------------------------------------------
:PANEL    NAME=srvmenu
         HELP='menu/help'
         KEYL=menukeys
         ENTER='MSG CPD9817 QCPFMSG'
         PANELID=ZMENU
         TOPSEP=SYSNAM.
Server Menu
.*
.* -------------------------------------
.* Define the menu area
.* -------------------------------------
:MENU     DEPTH='*'
         SCROLL=NO
         BOTSEP=SPACE.
:TOPINST.Select one of the following:
.*
.* -------------------------------------
.* Specify the action to be taken for each option
.* -------------------------------------
.*
:MENUI    OPTION=1
         ACTION='CMD SBMJOB CMD(CALL PGM(OSPGM/TESTSVR)) JOB(MYSERVER) JOBD(OSPGM/OSSVR) JOBQ(*JOBD)'
         HELP='menu/option1'.
Start Server
.*
:MENUI    OPTION=2
         ACTION='CMD CALL SVRSTOP'
         HELP='menu/option2'.
End Server
.*
:MENUI    OPTION=5
         ACTION='CMD WRKACTJOB SBS(OSSBS)'
         HELP='menu/option5'.
Display Server jobs
.*
:MENUI    OPTION=6
         ACTION='CMD DSPMSG OSMSGQ'
         HELP='menu/option6'.
Display messages
.*
:MENUI    OPTION=90
         ACTION='CMD SIGNOFF'
         HELP='menu/option90'.
Signoff
.*
:EMENU.
.*
.* -------------------------------------
.* Use a command line and allow commands and option numbers
.* -------------------------------------
:CMDLINE SIZE=LONG.
Selection or command
.*
:EPANEL.
.*
.*
.* -----------------------------------------------------------------
.* Define help modules for the menu panel
.* -----------------------------------------------------------------
:HELP     NAME=keyl.
Function Keys - Help
:XH3.Function keys
:EHELP.
.*
:HELP     NAME=helpf1.
:PARML.
:PT.F1=Help
:PD.
Provides additional information about using the display or a specific field on the display.
:EPARML.
:EHELP.
.*
:HELP     NAME=exit.
:PARML.
:PT.F3=Exit
:PD.
Ends the current task and returns to the display from which the task was started.
:EPARML.
:EHELP.
.*
:HELP     NAME=prompt.
:PARML.
:PT.F4=Prompt
:PD.
Provides assistance in entering or selecting a command.
:EPARML.
:EHELP.
.*
:HELP     NAME=retrieve.
:PARML.
:PT.F9=Retrieve
:PD.
Displays the last command you entered on the command line and any parameters you included. Pressing this key once, shows the
last command you ran. Pressing this key twice, shows the command you ran before that and so on.
:EPARML.
:EHELP.
.*
:HELP     NAME=cancel.
:PARML.
:PT.F12=Cancel
:PD.
Returns to the previous menu or display.
:EPARML.
:EHELP.
.*
:HELP     NAME=morekeys.
:PARML.
:PT.F24=More keys
:PD.
Shows additional function keys.
:EPARML.
:EHELP.
.*
:HELP     NAME=enter.
:PARML.
:PT.Enter
:PD.
Submits information on the display for processing.
:EPARML.
:EHELP.
.*
:HELP     NAME=help.
:PARML.
:PT.Help
:PD.
Provides additional information about using the display.
:EPARML.
:EHELP.
.*
:HELP     NAME=home.
:PARML.
:PT.Home
:PD.
Goes to the menu that was shown after you signed on the system. This menu is either the initial menu defined in your user
profile or the menu you requested from the Sign-On display.
:EPARML.
:EHELP.
.*
:HELP     NAME=pagedown.
:PARML.
:PT.Page Down (Roll Up)
:PD.
Moves forward to show additional information for this display.
:EPARML.
:EHELP.
.*
:HELP     NAME=pageup.
:PARML.
:PT.Page Up (Roll Down)
:PD.
Moves backward to show additional information for this display.
:EPARML.
:EHELP.
.*
:HELP     NAME=print.
:PARML.
:PT.Print
:PD.
Prints information currently shown on the display.
:EPARML.
:EHELP.
.*
:HELP     NAME='menu/help'.
SaveFile Replication Main Menu - Help
:P.
Provides access to all options related the SVF Replication Process.
:XH3.
How to Use a Menu
:P.
To select a menu option, type the option number and press Enter.
:P.
To run a command, type the command and press Enter. For assistance in selecting a command, press F4 (Prompt) without typing anything.
For assistance in entering a command, type the command and press F4 (Prompt). To see a previous command you entered, press F9
(Retrieve).
:P.
To go to another menu, use the Go to Menu (GO) command. Type GO followed by the menu ID, then press the Enter key.
The menu ID is shown in the upper left corner of the menu.For assistance in entering the GO command, type GO and press F4
(Prompt). If you do not know the entire menu name you can use a generic name.
:EHELP.
.*
:HELP     NAME='menu/option1'.
Option 1 - Help
:XH3.Option 1. Start Server
:P.
Select this option to start the Server ready to receive data.
:EHELP.
.*
:HELP     NAME='menu/option2'.
Option 2 - Help
:XH3.Option 2. End Server
:P.
Select this option to end the server and the worker jobs.
:EHELP.
.*
:HELP     NAME='menu/option5'.
Option 5 - Help
:XH3.Option 5. WRKACTJOB
:P.
Display the active jobs in OSSBS subsystem.
:EHELP.
.*
:HELP     NAME='menu/option6'.
Option 6 - Help
:XH3.Option 6. Display messages
:P.
DPisplay the messages logged to the message queue.
:EHELP.
.*
:HELP     NAME='menu/option90'.
Option 90 - Help
:XH3.Option 90. Signoff
:P.
Select this option to SIGNOFF the current session.
:EHELP.
.*
.* -----------------------------------------------------------------
.* End of menu source
.* -----------------------------------------------------------------
:EPNLGRP.                                    
As this post is about ‘C’ programming we are not going to expand too much into what the above code does and why, the important thing is that using UIM ensures that any output that is generated is complient with the standards. Should you wish to run this through the modernization tools provided in the market place it will result in very few conflicts if any when displaying the interface (depends on the quality of the conversion tool). This produces a menu that allows you to start and stop the Server programs plus view any messages sent to the OSPGM/OSMSGQ message queue. Take note that we are simply running a SBMJOB command for option 1, in the future we will change that to ensure the subsystem is running as part of the start up request.

The next program sends a stop request to the controlling data queue which the Server job will listen to. Again it will be improved in the future adding error management for the data queue request as Data Queue API’s do not have the Error Code parameter many of the other API’s do. More on that in a later post but for now here is the code you need, simply create a new member called SVRSTOP in the OSLIB/QCSRC file and add the following code.
/**
  *
  *  Revision log.:
  *  Date     Author    Revision
  *  2018     Chris H   1.0
  *
  *  Copyright (C) <2018>  <Chris Hird>
  *
  *  This program is free software; you can redistribute it and/or modify
  *  it under the terms of the GNU General Public License as published by
  *  the Free Software Foundation; either version 2 of the License, or
  *  (at your option) any later version.
  *
  *  This program 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.  See the
  *  GNU General Public License for more details.
  *
  *  You should have received a copy of the GNU General Public License
  *  with this program; if not, write to the Free Software Foundation,
  *  Inc.,51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
  *  You can contact the author via Email chrish@shieldadvanced.com
  */                    
#include <stdio.h>                          // stdio header
#include <string.h>                         // string header
#include <stdlib.h>                         // stdlib header
#include <decimal.h>                        // decimal header
#include <qsnddtaq.h>                       // send dataq header

void main(int argc, char **argv) {
decimal(3,0)  KeyLength = 4.0d;             // length of key
char Key[4] = "0000";                       // key used retrieval
char QueueData[7] = "STOP   ";              // Message to quit
char DQName[10] = "SVRCTLQ   ";             // data queue name
char DQLib[10] = "OSPGM     ";              // data queue lib
decimal(5,0)  Data_Len = 7.0d;              // data length

QSNDDTAQ(DQName,
         DQLib,
         Data_Len,
         QueueData,
         KeyLength,
         &Key);
exit(0);
} 

First we need a Server program, this is where the initial start is performed. It will then spawn the worker jobs where the communication between the client and the IBM i will be performed. The stop message will be sent to the data queue this program is watching so when a stop message is received it will go through and send a kill signal to all of the worker jobs it spawned.
Add a new member QCSRC/TESTSVR and add the following code.
/**
  *
  *  Revision log.:
  *  Date     Author    Revision
  *  2018     Chris H   1.0
  *
  *  Copyright (C) <2018>  <Chris Hird>
  *
  *  This program is free software; you can redistribute it and/or modify
  *  it under the terms of the GNU General Public License as published by
  *  the Free Software Foundation; either version 2 of the License, or
  *  (at your option) any later version.
  *
  *  This program 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.  See the
  *  GNU General Public License for more details.
  *
  *  You should have received a copy of the GNU General Public License
  *  with this program; if not, write to the Free Software Foundation,
  *  Inc.,51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
  *  You can contact the author via Email chrish@shieldadvanced.com
  */
#include <H/COMMON>                         // common header
#include <H/MSGFUNC>                        // message functions
#include <qrcvdtaq.h>                       // Receive Data Q Msg
#include <qclrdtaq.h>                       // Clear Data Q Msgs
#include <sys/socket.h>                     // socket
#include <netinet/in.h>                     // net addr
#include <spawn.h>                          // spawn job
#include <errno.h>                          // error number
#include <signal.h>                         // Exception signals


typedef _Packed struct pid_list_x{
         int pid_num[50];
         }pid_list_t;

#define _QSIZE 256
#define _CPYRGHT "Copyright @ Shield Advanced Solutions Ltd 1997-2018"
#pragma comment(copyright,_CPYRGHT)

main(int argc, char *argv[]) {
int on = 1;                                 // on flag
int result = 0;                             // result flag
int stop = 0;                               // stop flag
int i, pid,rc,j;                            // counters
int listen_sd, accept_sd = 0;               // socket descriptors
int num_srv = 1;                            // number of servers
int Server_Port = 12345;                    // server port
int type = 0;                               // Switch key
decimal(5,0)  DataLength = 0.0d;            // Number of bytes returned
decimal(5,0)  WaitTime = -1.0d;             // wait for data <0 = forever
decimal(3,0)  SInfLength = 9.0d;            // length of Sender inf
decimal(3,0)  KeyLength = 4.0d;             // length of key
char *spawn_argv[3];                        // Spawn arg
char *spawn_envp[1];                        // Spawn Environment
char *recptr;                               // pointer to data
char buffer[80];                            // Buffer
char DQKey[4] = "0000";                     // key data used for retvl
char QueueData[_QSIZE];                     // Data from Data queue
char DQueue1[10] = "SVRCTLQ   ";            // Master Q
char DQLib[10] = "OSPGM     ";              // Master Q Lib
char msg_dta[100] = {'\0'};                 // msg buffer
char SpawnStr[50];                          // spawn string
char Key[5];                                // Switch Key
pid_list_t pid_list;                        // Process List struct
pid_list_t ss_pid_list;                     // Process List struct
Qmhq_Sender_Information_t SInfo;            // Sender Inf struct
struct inheritance inherit;                 // inheritance Struct
struct sockaddr_in addr;                    // socket struct
Os_EC_t Error_Code = {0};                   // Error code data

Error_Code.EC.Bytes_Provided = _ERR_REC;
// set up the worker program spawn string
strcpy(SpawnStr,"/QSYS.LIB/OSPGM.LIB/WORKER.PGM");
// Clear out any existing stop messages in control queue
QCLRDTAQ(DQueue1,
         DQLib,
         "EQ",
         KeyLength,
         DQKey,
         &Error_Code);
if(Error_Code.EC.Bytes_Available) {
   sprintf(msg_dta,"Unable to clear the data queue %.7s",Error_Code.EC.Exception_Id);
   snd_msg("GEN0001",msg_dta,strlen(msg_dta));
   }
// Set up the listening sockets Non Secure
listen_sd = socket(AF_INET, SOCK_STREAM, 0);
if(listen_sd < 0) {
   sprintf(msg_dta," socket() failed %s",strerror(errno));
   snd_msg("GEN0001",msg_dta,strlen(msg_dta));
   close(accept_sd);
   exit(-1);
   }
setsockopt(listen_sd,SOL_SOCKET,SO_REUSEADDR,(char *)&on,sizeof(on));
setsockopt(listen_sd,SOL_SOCKET,SO_RCVBUF,CHAR_32K,sizeof(CHAR_32K));
memset(&addr, 0, sizeof(addr));
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = htonl(INADDR_ANY);
addr.sin_port = htons(Server_Port);
rc = bind(listen_sd,(struct sockaddr *) &addr, sizeof(addr));
if(rc < 0)  {
   sprintf(msg_dta," bind() failed %s",strerror(errno));
   snd_msg("GEN0001",msg_dta,strlen(msg_dta));
   close(listen_sd);
   exit(-1);
   }
rc = listen(listen_sd, 5);
if(rc < 0) {
   sprintf(msg_dta," bind() failed %s",strerror(errno));
   snd_msg("GEN0001",msg_dta,strlen(msg_dta));
   close(listen_sd);
   exit(-1);
   }
memset(&inherit, 0, sizeof(inherit));
sprintf(buffer, "%d", listen_sd);
spawn_argv[0] = "";
spawn_argv[1] = buffer;
spawn_argv[2] = NULL;
spawn_envp[0] = NULL;
// load the listening jobs Non Secure
for(i = 0; i < num_srv; i++)  {
   pid = spawn(SpawnStr,listen_sd + 1, NULL, &inherit,spawn_argv, spawn_envp);
   if(pid < 0)  {
      sprintf(msg_dta," spawn() failed %s",strerror(errno));
      snd_msg("GEN0001",msg_dta,strlen(msg_dta));
      close(listen_sd);
      exit(-1);
      }
   pid_list.pid_num[i] = pid;
   }
// Check for signal to end
do {
   recptr = QueueData;
   QRCVDTAQ(DQueue1,
            DQLib,
            &DataLength,
            &QueueData,
            WaitTime,
            "LE",
            KeyLength,
            &DQKey,
            SInfLength,
            &SInfo);
   memset(Key,'\0',5);
   memcpy(Key,DQKey,4);
   type = atoi(Key);
   switch(type)  {
      case  0     :   {  // End the process */
         for(i = 0; i < num_srv; i++) {
            kill(pid_list.pid_num[i], SIGTERM);
            }
         stop = 1;
         break;
         }
      Default :  { // do nothing
         break;
         }
      }
   }while(!stop);
close(listen_sd);
exit(0);
}
 
The above program sets up the socket which is to be listened on and sets a number of options for that socket before binding to it. The worker jobs are actually programs we are going to create next and will be called WORKER. The program sets up the call information plus any parameters that are to be sent to the programs, in this case the socket is an important parameter. We then loop through and load the worker jobs storing the pid for each job so we can send it a kill signal once we receive the relevant data queue entry. Now the program sits on the data queue looking for the stop message, when it is received it will loop through the pid’s for the jobs it spawned and send the kill signal to the.

Finally we have the code which is where all of the real work is done, this is where the actual conversation with the remote client will take place. Add a new member QCSRC/WORKER and add the following code.
/**
  *
  *  Revision log.:
  *  Date     Author    Revision
  *  2018     Chris H   1.0
  *
  *  Copyright (C) <2018>  <Chris Hird>
  *
  *  This program is free software; you can redistribute it and/or modify
  *  it under the terms of the GNU General Public License as published by
  *  the Free Software Foundation; either version 2 of the License, or
  *  (at your option) any later version.
  *
  *  This program 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.  See the
  *  GNU General Public License for more details.
  *
  *  You should have received a copy of the GNU General Public License
  *  with this program; if not, write to the Free Software Foundation,
  *  Inc.,51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
  *  You can contact the author via Email chrish@shieldadvanced.com
  */
#include <H/MSGFUNC>                        // message functions
#include <H/COMMON>                         // common header
#include <netdb.h>                          // Socket DB
#include <sys/socket.h>                     // Sockets
#include <netinet/in.h>                     // Socket net
#include <arpa/inet.h>                      // ARPA structs
#include <errno.h>                          // Error return header
#include <netdb.h>                          // net DB header
#include <sys/errno.h>                      // sys error header
#include <arpa/inet.h>                      // inet header
#include <signal.h>                         // signal funcs
#include <except.h>                         // exception funcs
#include <iconv.h>                          // conversion header
#include <qtqiconv.h>                       // iconv header

#define _QSIZE 256
#define _CPYRGHT "Copyright @ Shield Advanced Solutions Ltd 1997-2018"
#pragma comment(copyright,_CPYRGHT)

// Signal catcher

void catcher(int sig) {
// you can do some messaging etc in this function, it is called
// when the kill signal is received from the TESTSVR program
exit(0);
}

int main(int argc, char **argv) {
int listen_sd, accept_sd = 0;               // file descriptors
int rc = 0;                                 // return codes
int ret = 0;                                // return vals
int Stop = 0;                               // stop flag
int i = 0;                                  // counter
int j = 0;                                  // counter
int offset = 0;                             // offset marker
int total_bytes = 0;                        // counter
int bytes_processed = 0;                    // counter
size_t insz;                                // input len
size_t outsz = _32K;                        // outbuf size
char recv_buf[_32K];                        // recv buffer
char convbuf[_32K];                         // conversion buffer
char msg_dta[_MAX_MSG];                     // message buffer
char *out_ptr;                              // buffer ptr
char *in_ptr;                               // buffer ptr
struct sockaddr_in addr;                    // socket struct
struct sigaction sigact;                    // Signal Action Struct
QtqCode_T jobCode = {0,0,0,0,0,0};          // (Job) CCSID to struct
QtqCode_T asciiCode = {819,0,0,0,0,0};      // (ASCII) CCSID from struct
iconv_t a_e_ccsid;                          // convert table struct
iconv_t e_a_ccsid;                          // convert table struct

// Set up the signal handler
sigemptyset(&sigact.sa_mask);
sigact.sa_flags = 0;
sigact.sa_handler = catcher;
sigaction(SIGTERM, &sigact, NULL);
// create the conversion tables
// ASCII to EBCDIC
a_e_ccsid = QtqIconvOpen(&jobCode,&asciiCode);
if(a_e_ccsid.return_value == -1) {
   sprintf(msg_dta,"QtqIconvOpen Failed %s",strerror(errno));
   snd_msg("GEN0001",msg_dta,strlen(msg_dta));
   exit(-1);
   }
// EBCDIC to ASCII
e_a_ccsid = QtqIconvOpen(&asciiCode,&jobCode);
if(e_a_ccsid.return_value == -1) {
   iconv_close(a_e_ccsid);
   sprintf(msg_dta,"QtqIconvOpen Failed %s",strerror(errno));
   snd_msg("GEN0001",msg_dta,strlen(msg_dta));
   exit(-1);
   }
// connect to the socket passed as argv[1]
listen_sd = atoi(argv[1]);
do {
   accept_sd = accept(listen_sd, NULL, NULL);
   setsockopt(accept_sd,SOL_SOCKET,SO_RCVBUF,CHAR_32K,sizeof(CHAR_32K));
   if(accept_sd < 0) {
      sprintf(msg_dta,"accept() failed",strerror(errno));
      snd_msg("GEN0001",msg_dta,strlen(msg_dta));
      close(listen_sd);
      exit(-1);
      }
   memset(recv_buf,'\0',_32K);
   rc = recv(accept_sd, recv_buf, _32K, 0);
   sprintf(msg_dta,"Bytes received %d %s",rc,recv_buf);
   snd_msg("GEN0001",msg_dta,strlen(msg_dta));
   if(rc < 0) {
      sprintf(msg_dta,"recv() failed",strerror(errno));
      snd_msg("GEN0001",msg_dta,strlen(msg_dta));
      close(listen_sd);
      close(accept_sd);
      exit(-1);
      }
   // convert the input
   memset(convbuf,'\0',_32K);
   in_ptr = recv_buf;
   out_ptr = convbuf;
   insz = rc;
   outsz = _32K;
   ret = (iconv(a_e_ccsid,(char **)&(in_ptr),&insz,(char **)&(out_ptr),&outsz));
   // send the data we just received to a message queue
   snd_msg("GEN0001",convbuf,strlen(convbuf));
   // send a reply to the client
   sprintf(msg_dta,"Yep got that now closing");
   in_ptr = msg_dta;
   out_ptr = convbuf;
   insz = strlen(msg_dta);
   outsz = _32K;
   ret = (iconv(e_a_ccsid,(char **)&(in_ptr),&insz,(char **)&(out_ptr),&outsz));
   rc = send(accept_sd,convbuf,strlen(msg_dta),0);
   close(accept_sd);
   } while(Stop == 0);
}
 
Before we go ahead and compile the programs lets take a look at what we are doing in this program. Once we have decalred all of the variables we need to set up the signal handler that will be caled whenever a signal is trapped by this program, remember we will stop the running processes by the Server Program sending a kill request on receipt of a specific data queue entry. The handler code  (a function called catcher just before the main() statement) we have does not do a lot other than simply returning, we can add more to that later. The next few lines of code is where we will set up the conversion for the data this program will receive, we have already decided that this program will be sent requests in ASCII (ISO8859-1). A couple of things happen as part of the conversion set up, first we create the to and from code arrays (jobCode and acsiiCode) which define the CCSID’s that we need translating. we will pass these into the  QtqIconvOpen API which will return a mapped table for doing the conversion, if it fails we exit as not much point in going any further at tha point. We end up with 2 tables, one to convert from ASCII to EBCDIC and one to convert the other way, we have named these accordingly.

The Server program will map the socket that is to be listed on, this program needs to set a listening state to that socket and then loop through a process of receive and respond against that socket. The worker program will issue an accept request which is actioned once the client has sent its request. Each request will be terminated by the worker program after it has sent its response, we could have the worker wait until a specific set of data is sent to signify that the client has finished the conversation but in this instance we are simply going to stop accepting any further data (close(accept_sd)). 

When the data is received from the client we will convert it to EBCDIC and simply send it onto the message queue, we could do other things like check if its a command and run itr or maybe if required run it as an SQL query? In the future we intend to show how some of that can be done.

The following commands will create the required objects from the above code.
CRTMNU MENU(OSPGM/SVRMAIN) TYPE(*UIM) SRCFILE(OSLIB/QUIMSRC) SRCMBR(SVRMAIN)
CRTCMOD MODULE(OSLIB/SVRSTOP) SRCFILE(OSLIB/QCSRC) SRCMBR(SVRSTOP) OUTPUT(*PRINT)  DBGVIEW(*SOURCE) TGTRLS(V7R1M0)
CRTPGM PGM(OSPGM/SVRSTOP) MODULE(OSLIB/SVRSTOP) BNDDIR(OSLIB/OS) TGTRLS(V7R1M0)
CRTCMOD MODULE(OSLIB/TESTSVR) SRCFILE(OSLIB/QCSRC) SRCMBR(TESTSVR) OUTPUT(*PRINT) DBGVIEW(*SOURCE) TGTRLS(V7R1M0)
CRTPGM PGM(OSPGM/TESTSVR) MODULE(OSLIB/TESTSVR) BNDDIR(OSLIB/OS) TGTRLS(V7R1M0)
You should now have all of the objects required to start up and run the Server programs. You can use the SVRMAIN menu to start (option 1) the programs and show them running (option 5). If option 5 shows no running programs make sure you have started the subsystem.

Now  to test the program actually works. We use Linux a lot so for us we can use the NetCat program (there is a Windows version but not sure how well maintained it is these days plus there are notes about it conflicting with the Anti Virus programs due to port scanning capabilities built in). We simply start a terminal session and issue the following commands:
nc sas4.shield.local 12345
This will start the session between the client and the IBM i which is listening on port 12345. Next we will send the data which will be sent to the mesage queue on the IBM i
Hello from the Linux Box Hope you get this.
On pressing enter you should see a response from the IBM i which states ‘Yep got that now closing’. Here is a screen shot of our terminal.
Linux webserver 4.13.16-2-pve #1 SMP PVE 4.13.16-47 (Mon, 9 Apr 2018 09:58:12 +0200) x86_64

The programs included with the Debian GNU/Linux system are free software;
the exact distribution terms for each program are described in the
individual files in /usr/share/doc/*/copyright.

Debian GNU/Linux comes with ABSOLUTELY NO WARRANTY, to the extent
permitted by applicable law.
Last login: Thu Apr 26 14:20:24 2018 from 192.168.200.149
chrish@webserver:~$ nc sas4.shield.local 12345
Hello from the Linux Box Hope you get this.
Yep got that now closingchrish@webserver:~$
So when you look in the message queue on the IBM i you should now see a message like so.
Bytes received 44 çÁ%%?■ÃÊ?_■ÈÇÁ■<Ñ>ÍÌ■â?Ì■ç?øÁ■`?Í■ÅÁÈ■ÈÇÑË■■
Hello from the Linux Box Hope you get this.■
This is what we expect, the strange text is the actual ASCII characters we received which when you look at the Hex data correlates with the text string that was sent and the second message is after the conversion using the translation tables.

Thats all it takes, we will continue to build on this in future posts so if you have any problems with the code to date get in touch and we will help figure out what is going wrong. In this post we have covered quite a bit, some of it may not make sense at first so please ensure you go through and understand what we do and why in the programs, this is going to be very important once we get into the future stages.

This post we have looked at.
  1. TCP/IP ports and using them to communicate
  2. Conversion for ASCII and EBCDIC
  3. Use of the spawn process to launch worker jobs
  4. Using signal handling to stop spawned jobs
  5. UIM menus
  6. Building a subsystem and setting it up to run jobs.

That’s all for todays post, any questions or concerns get in touch and we will try to help.

Chris..


Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.