RPG 101 Call PHP

(click to open)

Quick Page Table of Contents

Scanning…

RPG Call PHP 101

Introduction

In the previous tutorials we were in the business of calling RPG from PHP to get data for web applications. In this tutorial we will call PHP from RPG to get web data into our RPG program.

For this demonstration a simple command line RPG program will call the PHP REST server script we made in an earlier lesson on this machine to get an XML document of Search DVDs back into our RPG program.

WARNING: PHP ships without *PUBLIC access, here is how you fix for RPG popen hacking
I can't get this to work low authority profile, what is happening???
PHP no hacking (shipped by Zend Company)
bash-4.2$ ls -l /usr/local/zendsvr/
drwxr-s---    2 qtmhhttp 0             40960 Aug 28 2012  bin
drwxr-s---   17 qtmhhttp 0             40960 Nov 16 2012  lib

Access only one profile qtmhhttp (Apache web) ... not your profile (maybe) ... 
drwxr-s---    6 qtmhhttp
d             <- directory
.rwx          <- owner  ... all access owner (qtmhhttp)
....r-s       <- group  ... read access
.......---    <- public ... NO public access (except secofr)

How do i fix, so i may hack with RPG popen?
call qp2term
> cd /usr/local
> chmod -R 0755 zendsvr      (or zendsvr6)
where
-R -- recursive directory apply (all child IFS nodes)
0  -- octal input (base eight)
7  -- owner  ... all access (owner not changed)
5  -- group  ... read/execute access
5  -- public ... read/execute access (proceed with RPG popen hacking)

Can it be that easy (PASEMAIN)?

Using the PASESERV SRVPGM included in this lesson (next sections), the RPG call to a PHP script is fairly easy. If you are familiar with any of the PASE shells (qp2term), the PHP command passed through RPG PaseExec32 looks exactly like it would be typed on a command line (i.e. call qp2term), except output from the PHP script is collected in RPG variable paseStdOut.

     D ind             S              1N
     D cmd             S           1024A
     D paseStdOut      S          32766A
     D buf             S             50A
      /free
       ind = PaseStart32();
       cmd = '/usr/local/Zend/Core/bin/php-cli'
           + ' /www/zendcore/htdocs/wiki/uploads/I5/101RPGRestServer.php'
           + ' browsetype=title browse_title=H limit_num=3';
       ind = PaseExec32(cmd:paseStdOut);
       ind = PaseStop();
       buf = %subst(paseStdOut:23:50);
       dsply buf;
       *inlr = *on;
      /end-free

Output:
DSPLY  <result><dvd><PROD_ID>3</PROD_ID><TITLE>Happy Frog

An interface include to the PASESERV SRVPGM provides prototypes to the APIs we use in the main program. The main program uses /copy PASE_H to include these prototypes.

      * errCode=PaseError([msg])
      * return (0=no error, !0=error)
      * no msg return last error, then clear
     D PaseError       PR            10i 0 extproc(*CL:'PaseError')
     D paseMsg                       10i 0 value options(*nopass)

      * rc=PaseStart32
      * return (*ON=good, *OFF=error)
     D PaseStart32     PR             1N   extproc(*CL:'PaseStart32')

      * rc=PaseStop
      * return (*ON=good, *OFF=error)
     D PaseStop        PR             1N   extproc(*CL:'PaseStop')

      * rc=PaseExec32(cmd,out)
      * return (*ON=good, *OFF=error)
     D PaseExec32      PR             1N   extproc(*CL:'PaseExec32')
     D paseCmd                     1024A
     D paseStdOut                 32766A

The PASE APIs are also included in this interface even though they are not expected to be used by client RPG programs (so that you may see the prototypes).

     d Qp2RunPase      pr            10I 0 extproc('Qp2RunPase')
     d  ppathName                      *   value options(*string)
     d  psymName                       *   value options(*string)
     d  psymlDat                       *   value options(*string)
     d  psymDataL                    10U 0 value
     d  pccsid                       10I 0 value
     d  pargv                          *   value
     d  penvp                          *   value

     d Qp2malloc       pr              *   extproc('Qp2malloc')
     d  pmem_size                    20U 0 value
     d  pmem_pase                      *   value
:

What’s the trick?

The trick is to let PASE do all the work using PASE’s popen API. The popen subroutine creates a pipe between the calling program and a shell command to be executed. This means you essentially get the same behavior as using “call qp2term” from a 5250 command line (below), except with PASE popen stdout will be redirected back to our RPG program.

call qp2term
   $
 > /usr/local/Zend/Core/bin/php-cli /www/zendcore/htdocs/wiki/uploads/I5/101RPGR
   estServer.php browsetype=title browse_title=H limit_num=3
   <?xml version="1.0"?>
   <result><dvd><PROD_ID>3</PROD_ID><TITLE>Happy Frog</TITLE><ACTOR>Fred Flimfla
   m</ACTOR><PRICE>6.22</PRICE></dvd><dvd><PROD_ID>4</PROD_ID><TITLE>Happy Toad<
   /TITLE><ACTOR>Fred Flinflam</ACTOR><PRICE>6.22</PRICE></dvd></result>
   $
 > 

Note: The popen subroutine runs only sh shell commands. The results are unpredictable if the Command parameter is not a valid sh shell command.

We took one small(ish) liberty in the site PHP configuration module to enable PHP’s $argv arguments passed from a command line to be parsed into our familiar mandatory $_POST variables used in web callers (command_line_argv).

// ------------------------
// Global: $_POST vars
// ------------------------
function post_mandatory($keys) 
{ command_line_argv($keys);
  foreach ( $keys as $key ) 
  { if (isset ( $_REQUEST [$key] ))
    { $_POST [$key] = $_REQUEST [$key];
    } 
    else if (! isset ( $_POST [$key] ))
    { $_POST [$key] = "";
    }
  }
}

function command_line_argv($keys) 
{ global $argv;
  if (!(count($_REQUEST)==0 && count($argv)>0))
  { return;
  }
  foreach ( $keys as $key )
  { for ($i=0;$i<count($argv);$i++)
    { $a = explode("=",$argv[$i]);
      if (count($a)==2 && $a[0]==$key)
      { $_REQUEST [$key] = $a[1];
      }
    }
  }
} 

What are the key APIs used from PASE (PASESERV)?

All of the PASE APIs used in the example are provided by PASE libc.a, which provides common “Unix” APIs used by countless AIX programs. As you understand the general technique of calling PASE lib APIs, your RPG programming toolbox choices may get a lot wider.

In this example we are going to take advantage of PASE’s ability to fork/exec a child process using popen and collect the output. Key PASE calls

  • filePtr = popen(“cmd”,”r”) - fork process, run command, collect stout
  • buffPtr = fgets(char *fgBuf, int size, FILE *filePtr) - output into buffer
  • rc = pclose(filePtr) - close the file (and process)
       // filePtr = popen("cmd","r")
       dow 1<>2;
         // buffPtr = fgets(char *fgBuf, int size, FILE *filePtr);
         paseStdOut=%trim(paseStdOut)+' '+%trim(fgBuf);
       enddo;
       // rc = pclose(filePtr);
       Translate(%len(%trim(paseStdOut)):paseStdOut:QP2_2_EBCDIC);

What about pure ILE programming?

Unfortunately ILE does not have a suitable popen API. You can emulate PASE’s popen API using ILE pipe() and spawn() along with a bunch of code you need to write (sorry mate).

Note: If I find time I will demonstrate “call PASE” with pure ILE in a later example.

How does PaseStart32 work (PASESERV)?

The first thing we need to do is to wake up PASE in our RPG program job (process). The PASE Qp2RunPase API allows us to call a PASE “main” program along with parameters and environment variables. In this case we are going to call the PASE provided utility:

  • /usr/lib/start32 - start 32 bit PASE.

Normally, calling a PASE main program with Qp2RunPase would “wake up” PASE, then run the program and terminate the PASE environment when the PASE main program returned to our RPG caller. However, start32 utility is a main program with a special PASE return API (_RETURN), that allows the start32 program “wake up” PASE and return control back to our RPG caller without folding up the PASE environment. This allows us access to PASE’s shared libraries like libc.a (like SRVPGMs), so that we can call individual PASE functions even though the main start32 program is essentially complete (dead).

Therefore, our second PASE API Qp2dlopen allows us to “load” any PASE shared libraries we want to use from our RPG program, just as if a PASE main program was in charge (yahoo RPG!). In the case of libc.a (and a few others), these libraries are “commonly” loaded and shared by all PASE programs, so all we really need to do with Qp2dlopen is to get a handle with Qp2dlopen(*NULL).

So the thing to remember as you look at the code below is

  • Qp2RunPase(‘/usr/lib/start32′) - “wake up” 32 bit PASE process and keep alive
  • Qp2dlopen(*NULL) - give me a handle to common PASE shared libraries

With these two things a tremendous amount of PASE functions are just waiting to be called by your RPG program.

     P PaseStart32     B                   export
     D PaseStart32     PI             1N
      * vars
     Diarg             DS
     D ipgm                          30A   inz(QP2_START_PASE_32BIT)
     D iarg1                           *   inz(*NULL)
     D iargend                         *   inz(*NULL)
     Dienv             DS
     D iatt                          30A   inz(QP2_PASE_THREAD_ATTACH)
     D ienv1                           *   inz(*NULL)
     D ienvend                         *   inz(*NULL)
     D rc              S             10I 0
      /free
       // PASE already started
       if sLib > 0;
         return *ON;
       endif;
       // init parms
       iarg1 = %ADDR(ipgm);
       ienv1 = %ADDR(iatt);
       ipgm = %trim(ipgm) + x'00';
       iatt = %trim(iatt) + x'00';
       // return (0==good, -1==bad, -2=exit PASE alive)
       rc=Qp2RunPase(iarg1: *NULL: *NULL: 0: 819: 
                     %ADDR(iarg1): %ADDR(ienv1));
       if rc = -1;
         rc = PaseError(QP2_ERROR_START32_FAIL);
         return *OFF;
       endif;
       // load PASE common libraries libc.a (0=bad, !0=good)
       sLib=Qp2dlopen(*NULL: X'00000002': 0);
       if sLib = 0;
         rc = PaseError(QP2_ERROR_LOAD_LIBC_FAIL);
         return *OFF;
       endif;
       return *ON;
      /end-free
     P                 E

Note: A production version of this sort of StartPase32 function should check to see if PASE is already active in this process. Hopefully a 32 bit PASE active because we would need to change all of our RPG 10u 0 to 20U 0 to hold 64 bit PASE addresses (and may be some other code flow). But this simple demonstration assume the world is “sane” without prior PASE commitment in our RPG process.

How does PaseStop work (PASESERV)?

If you read the previous topic, then you know that Qp2RunPase(‘/usr/lib/start32′) “wakes up” PASE 32 bit, but does not shut PASE back down when start32 returned to our RPG program. So, Qp2EndPase API wipes out all of PASE in our RPG job. The only trick is to remember to call it in our RPG program when we are completely done using PASE functions.

After a Qp2EndPase you will have to start all over with Qp2RunPase to use PASE in this RPG program again (slow), so don’t kill PASE unless you really mean it.

     P PaseStop        B                   export
     D PaseStop        PI             1N
      * vars
     D rc              S             10I 0
     D i               S             10I 0
      /free
       // PASE already stopped
       if sLib = 0;
         return *ON;
       endif;
       sLib = 0;
       // end PASE (!0=bad, 0=good)
       rc=Qp2EndPase();
       if rc <> 0;
         rc = PaseError(QP2_ERROR_STOP_FAIL);
         return *OFF;
       endif;
       return *ON;
      /end-free
     P                 E

How does PaseExec32 work (PASESERV)?

We have arrived at the routine that does all the work to run the PASE command and collect the output back into our RPG variable. At first glance it appears to be a lot of code, but if you look a little closer you will see we are doing the same thing over and over with slightly different parameters calling the PASE libc.a functions

  • popen - fork child process, run PASE command, collect output
  • fgets - get output back into RPG program
  • pclose - close the output and end the child process

For each of the PASE functions we follow the same steps:

  • poToc = Qp2dlsym(sLib: %ADDR(popen): 0: *NULL) - find toc address/handle to function in PASE
  • poParmPtr = Qp2malloc(addr(poPasePtr)) - get PASE memory for in/out parameters (PASE can’t see memory in the RPG program, only memory PASE allocated)
  • setup the PASE addresses - poPasePtr gave us the start address of the parameter area, we just use simple math to calculate where the pointers are suppose to be (it take practice — sorry mate)
  • rc = Qp2CallPase(poToc:poParmPtr:…) - call the pase function

Because Qp2CallPase is used as a generic service to call any PASE function, we need to help Qp2CallPase understand the layout of the parameters heading to the PASE function. Therefore we have a different xxArgSig data structure for each of the PASE functions called to help this function setup machine registers, strorage, etc. to call the PASE function. You will need to look up the values (−5, 0) in the PASE manual to understand all the options, but we mostly “abuse” the −5 signature option because it provides a machine “register” size hole for our PASE pointer/integer to make it to the PASE function.

     P PaseExec32      B                   export
     D PaseExec32      PI             1N
     D paseCmd                     1024A
     D paseStdOut                 32766A
      * vars
     D rc              S             10I 0
      * FILE * popen(const char *cmd, const char *mode);
     D popen           S             10A   inz('popen')
     D poToc           S               *
     D poParmPtr       S               *
     D poPasePtr       S             20U 0 
     DpoArg            DS                  based(poParmPtr)
     D poCmdPtr                      10U 0 
     D poModePtr                     10U 0
     D poCmd                       1024A
     D poMode                         8A
     DpoArgSig         DS
     D poSig1                         5i 0 inz(-5)
     D poSig2                         5i 0 inz(-5)
     D poSig3                         5i 0 inz(0)
     D poRetSig        S             10i 0 inz(-5)
     D paseFilePtr     S             10U 0
      * char *fgets(char *s, int n, FILE *stream);
     D fgets           S             10A   inz('fgets')
     D fgToc           S               *
     D fgParmPtr       S               *
     D fgPasePtr       S             20U 0 
     DfgArg            DS                  based(fgParmPtr)
     D fgBufPtr                      10U 0 
     D fgSize                        10i 0
     D fgFilePtr                     10U 0
     D fgBuf                       4096A
     DfgArgSig         DS
     D fgSig1                         5i 0 inz(-5)
     D fgSig2                         5i 0 inz(-5)
     D fgSig3                         5i 0 inz(-5)
     D fgSig4                         5i 0 inz(0)
     D fgRetSig        S             10i 0 inz(-5)
     D paseBufPtr      S             10U 0
      * int pclose (FILE *stream);
     D pclose          S             10A   inz('pclose')
     D pcToc           S               *
     D pcParmPtr       S               *
     D pcPasePtr       S             20U 0 
     DpcArg            DS                  based(pcParmPtr)
     D pcFilePtr                     10U 0
     DpcArgSig         DS
     D pcSig1                         5i 0 inz(-5)
     D pcSig2                         5i 0 inz(0)
     D pcRetSig        S             10i 0 inz(-5)
     D paseRC          S             10i 0
      /free
       // filePtr = popen("cmd","r")
       popen = %trim(popen) + x'00';
       poToc = Qp2dlsym(sLib: %ADDR(popen): 0: *NULL);
       poParmPtr = Qp2malloc(%size(poArg):%addr(poPasePtr));
       poCmdPtr = poPasePtr + %size(poCmdPtr) + %size(poModePtr);
       poModePtr = poCmdPtr + %size(poCmd);
       poCmd = paseCmd;
       Translate(%len(%trim(poCmd)):poCmd:QP2_2_ASCII);
       poCmd = %trim(poCmd) + x'00';  // ASCII command
       poMode = x'72' + x'00';        // ASCII 'r'
       rc = Qp2CallPase
       ( poToc:poParmPtr:
         %ADDR(poArgSig):poRetSig:
         %addr(paseFilePtr)
       );
       // buffPtr = fgets(char *s, int n, FILE *stream);
       fgets = %trim(fgets) + x'00';
       fgToc = Qp2dlsym(sLib: %ADDR(fgets): 0: *NULL);
       fgParmPtr = Qp2malloc(%size(fgArg):%addr(fgPasePtr));
       fgBufPtr = fgPasePtr + %size(fgBufPtr) 
                  + %size(fgSize) + %size(fgFilePtr);
       fgSize = 4096;
       fgFilePtr = paseFilePtr;
       clear paseStdOut;
       dow 1<>2;
         clear fgBuf;
         rc = Qp2CallPase
         ( fgToc:fgParmPtr:
           %ADDR(fgArgSig):fgRetSig:
           %addr(paseBufPtr)
         );
         if paseBufPtr >= 1;
           fgBuf = %xlate(x'0A00' : '  ' : fgBuf);
           if %len(%trim(paseStdOut)) + %len(%trim(fgBuf)) < 4096;
             paseStdOut=%trim(paseStdOut)+' '+%trim(fgBuf);
           endif;
         else;
           leave;
         endif;
       enddo;
       // rc = pclose(file);
       pclose = %trim(pclose) + x'00';
       pcToc = Qp2dlsym(sLib: %ADDR(pclose): 0: *NULL);
       pcParmPtr = Qp2malloc(%size(pcArg):%addr(pcPasePtr));
       pcFilePtr = paseFilePtr;
       rc = Qp2CallPase
       ( pcToc:pcParmPtr:
         %ADDR(pcArgSig):pcRetSig:
         %addr(paseRC)
       );
       // copy out data and translate
       Translate(%len(%trim(paseStdOut)):paseStdOut:QP2_2_EBCDIC);
       Qp2Free(poParmPtr);
       Qp2Free(fgParmPtr);
       Qp2Free(pcParmPtr);
       // good
       return *ON;
      /end-free
     P                 E

Note: I was extremely lazy writing the PaseExec32 code because I didn’t check the return values of any of the calls. You should provide much better return code feedback to your user if you choose to use this technique in production RPG code (but too much of that clutters up these demonstration programs).

Well that’s about it, feel free to steal this code.