I wanted to retrieve the defaults for a number of IBM commands, so I could compare them to those in another IBM i partition. I found an API, QCDRCMDD, would give me a XML string of the command's definition, that I could then write to a file. This file could then be transferred to the other partition and used when comparing the two sets of commands' information.
The output file, OUTFILE, would contain one field, for the XML string, but would need to big enough to contain the longest possible XML string. My guess is that a field of 10,000 characters would be long enough to cover most commands.
01 A R OUTFILER 02 A FIELD 10000A |
When I was creating the RPG program it made sense to have the API in a subprocedure that could be called multiple times. If I placed all the definitions for the API in the subprocedure it makes the "main line" very simple.
01 **free 02 ctl-opt option(*nodebugio:*srcstmt) dftactgrp(*no) ; 03 dcl-f OUTFILE usage(*output) ; 04 GetData('WRKJOB *LIBL ') ; 05 GetData('DSPJOB *LIBL ') ; 06 GetData('CHGJOB *LIBL ') ; 07 GetData('WRKSPLF *LIBL ') ; 08 GetData('CHGSPLFA *LIBL ') ; 09 GetData('WRKACTJOB *LIBL ') ; 10 GetData('CRTDTAARA *LIBL ') ; 11 GetData('DSPDTAARA *LIBL ') ; 12 GetData('CHGDTAARA *LIBL ') ; 13 GetData('DLTDTAARA *LIBL ') ; 14 GetData('CRTPF *LIBL ') ; 15 GetData('CHGPF *LIBL ') ; 16 GetData('DLTF *LIBL ') ; 17 GetData('CRTPRTF *LIBL ') ; 18 GetData('WRKQRY *LIBL ') ; 19 GetData('RUNQRY *LIBL ') ; 20 GetData('STRSQL *LIBL ') ; 21 GetData('GRTOBJAUT *LIBL ') ; 22 GetData('EDTOBJAUT *LIBL ') ; 23 GetData('Not_A_Cmd *LIBL ') ; 24 *inlr = *on ; |
Line 1: This should come as no surprise to you as all the RPG I have written for the past few years has been totally free RPG (RPG for i!).
Line 2: My favorite control options. I need to have the default activation group as this program contains a subprocedure.
Line 3: Definition for the output file I am using. It is defined just for output.
Lines 4 - 23: These are the commands I will be retrieving the definition of. Notice that line 23 contains a command that does not exist.
And onto the code for the subprocedure, GetData, that contains the call to the QCDRCMDD API.
25 dcl-proc GetData ; 26 dcl-pi *n ; 27 Parm char(20) const ; 28 end-pi ; 29 dcl-ds CMDD0100data ; 30 BytesReturned int(10) ; 31 ByteAvailable int(10) ; 32 XMLdata char(10240) ccsid(1208) ; 33 end-ds ; 34 dcl-pr QCDRCMDD extpgm ; 35 *n char(20) const ; //Command & library 36 *n int(10) const ; //Size of returned DS 37 *n char(8) const ; //Destination format name 38 *n char(32767) options(*varsize) ; //Returned DS 39 *n char(8) const ; //Receiver format name 40 *n likeds(QUSEC) options(*varsize) ; //API error DS 41 end-pr ; 42 /include qsysinc/qrpglesrc,qusec //API error DS 43 XMLdata = ' ' ; 44 QCDRCMDD(Parm : 45 %size(CMDD0100data) : 46 'DEST0100' : 47 CMDD0100data : 48 'CMDD0100' : 49 QUSEC) ; 50 if (XMLdata = ' ') ; 51 FIELD = '* ERROR * ' + Parm ; 52 else ; 53 FIELD = XMLdata ; 54 endif ; 55 write OUTFILER ; 56 return ; 57 end-proc ; |
Lines 26 - 28: When I am defining the procedure interface I do not have to give it name, I use *N instead.
Lines 29 - 33: This data structure is returned by QCDRCMDD. The first two parameters tell me how long the returned XML string is. The third is the XML string. The API returns the XML data in CCSID 1208, therefore, I use the CCSID keyword in the data structure subfield's definition so I can see the data there. I will explain later why I need to do this.
Lines 34 - 41: This is the procedure definition to call the API program. The EXTPGM shows it is a program. I never bother to give the parameters names, therefore, I name them all *N. The parameters that are passed to the program and not returned are all constants, CONST. The two returned parameters are variable in length.
Line 42: I am including the code for the QUSEC standard API data structure from the QSYSINC library. I used it in a LIKEDS definition on line 40.
Line 43: I am clearing the data structure subfield that the XML is moved to. If the API cannot retrieve the command's definition it does not change this subfield. Therefore, I can use this to tell if the API was successful or not.
Lines 44 - 49: This is the call to the API program. I have placed each parameter used by the API on a separate line to make it easier to understand what is going on.
Line 44: This variable contains the command, and the library it is in.
Line 45: The size of the data structure the results will be placed into.
Line 46: There are two different ways the results are returned. By using format DEST0100 informs the API to place the results in the next parameter.
Line 47: The name of the variable the results are placed into.
Line 48: There are two sets of results that can be returned. In this program I want the commands' definition returned.
Line 49: If the API encounters an error the information is returned into the standard API data structure.
Lines 50 - 54: Error checking. If the returned value in the XML subfield is blank then the API was unable to perform, and an error value is moved to the output file's field. If it was successful the XML string is moved to the field.
Line 55: The output file is written to.
Line 56: Return from the subprocedure to the "main line" of the program.
I mentioned previously that I need to use the CCSID(1208) with the XML subfield. If I use debug to see the data in the subfield I see:
EVAL XMLdata XMLDATA ....5...10...15...20...25...30...35...40...45...50 1 '<QcdCLCmd DTDVersion="1.0"><Cmd CmdName="WRKJOB" C |
If I removed the CCSID keyword from line 32.
32 XMLdata char(10240) ; |
The XML would be displayed in CCSID 1208, which in the IBM i I am using looks like:
EVAL XMLdata XMLDATA ....5...10...15...20...25...30...35...40...45...50 1 '█éÄÀä<ä_À█àèàîÁÊËÑ?>████████ä_À█ä_À+/_Á██ïê.¢|â██ä |
What do the results look like? I am not going to display the entire XML statements for all the commands I used, but here is the beginning of each record from the output file.
FIELD ------------------------------------------------------- 01 <QcdCLCmd DTDVersion="1.0"><Cmd CmdName="WRKJOB" CmdLib 02 <QcdCLCmd DTDVersion="1.0"><Cmd CmdName="DSPJOB" CmdLib 03 * ERROR * CHGJOB *LIBL 04 <QcdCLCmd DTDVersion="1.0"><Cmd CmdName="WRKSPLF" CmdLi 05 * ERROR * CHGSPLFA *LIBL 06 <QcdCLCmd DTDVersion="1.0"><Cmd CmdName="WRKACTJOB" Cmd 07 * ERROR * CRTPRTF *LIBL 08 <QcdCLCmd DTDVersion="1.0"><Cmd CmdName="CRTDTAARA" Cmd 09 <QcdCLCmd DTDVersion="1.0"><Cmd CmdName="DSPDTAARA" Cmd 10 <QcdCLCmd DTDVersion="1.0"><Cmd CmdName="CHGDTAARA" Cmd 11 <QcdCLCmd DTDVersion="1.0"><Cmd CmdName="DLTDTAARA" Cmd 12 * ERROR * CRTPF *LIBL 13 <QcdCLCmd DTDVersion="1.0"><Cmd CmdName="DLTF" CmdLib=" 14 <QcdCLCmd DTDVersion="1.0"><Cmd CmdName="WRKQRY" CmdLib 15 <QcdCLCmd DTDVersion="1.0"><Cmd CmdName="RUNQRY" CmdLib 16 <QcdCLCmd DTDVersion="1.0"><Cmd CmdName="STRSQL" CmdLib 17 * ERROR * GRTOBJAUT *LIBL 18 <QcdCLCmd DTDVersion="1.0"><Cmd CmdName="EDTOBJAUT" Cmd 19 <QcdCLCmd DTDVersion="1.0"><Cmd CmdName="WRKJOB" CmdLib 20 <QcdCLCmd DTDVersion="1.0"><Cmd CmdName="DSPJOB" CmdLi 21 * ERROR * CHGJOB *LIBL 22 <QcdCLCmd DTDVersion="1.0"><Cmd CmdName="WRKSPLF" CmdL 23 * ERROR * CHGSPLFA *LIBL 24 <QcdCLCmd DTDVersion="1.0"><Cmd CmdName="WRKACTJOB" Cm 25 <QcdCLCmd DTDVersion="1.0"><Cmd CmdName="CRTDTAARA" Cm 26 <QcdCLCmd DTDVersion="1.0"><Cmd CmdName="DSPDTAARA" Cm 27 <QcdCLCmd DTDVersion="1.0"><Cmd CmdName="CHGDTAARA" Cm 28 <QcdCLCmd DTDVersion="1.0"><Cmd CmdName="DLTDTAARA" Cm 29 * ERROR * CRTPF *LIBL 30 * ERROR * CHGPF *LIBL 31 <QcdCLCmd DTDVersion="1.0"><Cmd CmdName="DLTF" CmdLib= 32 * ERROR * CRTPRTF *LIBL 33 <QcdCLCmd DTDVersion="1.0"><Cmd CmdName="WRKQRY" CmdLi 34 <QcdCLCmd DTDVersion="1.0"><Cmd CmdName="RUNQRY" CmdLi 35 <QcdCLCmd DTDVersion="1.0"><Cmd CmdName="STRSQL" CmdLi 36 * ERROR * GRTOBJAUT *LIBL 37 <QcdCLCmd DTDVersion="1.0"><Cmd CmdName="EDTOBJAUT" Cm |
A couple of things I noticed:
- When the API tried to execute the invalid command the program errored, therefore, no record was written to the output file
- All of the commands that change, create, or grant failed. I hope this is just something due to my authority on the IBM i partition I was using.
I cannot show all the XML data from all of the records written as there is not enough room on this page. But I can show all of the XML data from the first record just as an example:
<QcdCLCmd DTDVersion="1.0"><Cmd CmdName="WRKJOB" C mdLib="__LIBL" CCSID="37" HlpPnlGrp="QHWCCMD" HlpP nlGrpLib="__LIBL" HlpID="WRKJOB" MaxPos="2" Prompt ="Work with Job" MsgF="QCPFMSG" MsgFLib="__LIBL" E xecBatch="YES" ChgCmdExit="NO" RtvCmdExit="NO"><Pa rm Kwd="JOB" PosNbr="1" KeyParm="NO" Type="QUAL" M in="0" Max="1" Prompt="Job name" Rstd="NO" Dft="*" AlwUnprt="YES" AlwVar="YES" Expr="YES" Full="NO" DspInput="YES" Choice="*" ><SngVal><Value Val="*" MapTo="*"/></SngVal><Qual Type="NAME" Min="1" Max= "1" Len="10" Rstd="NO" AlwUnprt="YES" AlwVar="YES" Expr="YES" Full="NO" DspInput="YES" Choice="Name" ></Qual><Qual Type="NAME" Min="0" Max="1" Prompt= "User" Len="10" Rstd="NO" AlwUnprt="YES" AlwVar="Y ES" Expr="YES" Full="NO" DspInput="YES" Choice="Na me" ></Qual><Qual Type="CHAR" Min="0" Max="1" Prom pt="Number" Len="6" Rstd="NO" RangeMinVal="000000" RangeMaxVal="999999" AlwUnprt="YES" AlwVar="YES" Expr="YES" Full="YES" DspInput="YES" Choice="00000 0-999999" ></Qual></Parm><Parm Kwd="OUTPUT" PosNbr ="2" KeyParm="NO" Type="CHAR" Min="0" Max="1" Prom pt="Output" Len="1" Rstd="YES" Dft="*" AlwUnprt="Y ES" AlwVar="YES" Expr="YES" Full="NO" DspInput="YE S" Choice="*, *PRINT" ><SpcVal><Value Val="*" MapT o="*"/><Value Val="*PRINT" MapTo="L"/></SpcVal></P arm><Parm Kwd="OPTION" PosNbr="3" KeyParm="NO" Typ e="INT2" Min="0" Max="1" Prompt="Option" Rstd="YES " Dft="*SELECT" AlwUnprt="YES" AlwVar="YES" Expr=" YES" Full="NO" DspInput="YES" Choice="*SELECT, *ST SA, *DFNA..." ><SpcVal><Value Val="*SELECT" MapTo= "13"/><Value Val="*STSA" MapTo="1"/><Value Val="*D FNA" MapTo="2"/><Value Val="*RUNA" MapTo="3"/><Val ue Val="*SPLF" MapTo="4"/><Value Val="*JOBLOG" Map To="5"/><Value Val="*PGMSTK" MapTo="6"/><Value Val ="*JOBLCK" MapTo="7"/><Value Val="*LIBL" MapTo="8" /><Value Val="*OPNF" MapTo="9"/><Value Val="*FILOV R" MapTo="10"/><Value Val="*CMTCTL" MapTo="11"/><V alue Val="*CMNSTS" MapTo="14"/><Value Val="*ACTGRP " MapTo="15"/><Value Val="*MUTEX" MapTo="16"/><Val ue Val="*THREAD" MapTo="17"/><Value Val="*MLBA" Ma pTo="18"/><Value Val="*ENVVAR" MapTo="19"/><Value Val="*ALL" MapTo="12"/></SpcVal></Parm><Parm Kwd=" DUPJOBOPT" PosNbr="5" KeyParm="NO" PmtCtl="PMTRQS" Type="CHAR" Min="0" Max="1" Prompt="Duplicate job option" Len="10" Rstd="YES" Dft="*SELECT" AlwUnpr t="YES" AlwVar="YES" Expr="YES" Full="NO" DspInput ="YES" Choice="*SELECT, *MSG" ><SpcVal><Value Val= "*SELECT" MapTo="*SELECT"/><Value Val="*MSG" MapTo ="*MSG"/></SpcVal></Parm><Parm Kwd="SLTTHD" PosNbr ="4" KeyParm="NO" PmtCtl="PMTCTL" Type="CHAR" Min= "0" Max="20" Prompt="Thread ID to include" Len="8" Rstd="NO" Dft="*INITIAL" AlwUnprt="YES" AlwVar="Y ES" Expr="NO" Full="YES" DspInput="YES" Choice="Ch aracter value" ><SngVal><Value Val="*ALL" MapTo="* ALL"/><Value Val="*SELECT" MapTo="*SELECT"/></SngV al><SpcVal><Value Val="*INITIAL" MapTo="*INITIAL"/ ><Value Val="*CURRENT" MapTo="*CURRENT"/></SpcVal> <PmtCtl CtlKwd="OUTPUT" NbrTrueRel="EQ" NbrTrue="1 " LglRel="AND" ><PmtCtlCond Rel="EQ" CmpVal="L"/>< /PmtCtl><PmtCtl CtlKwd="OPTION" NbrTrueRel="EQ" Nb rTrue="1" ><PmtCtlCond Rel="EQ" CmpVal="6"/></PmtC tl></Parm><Dep CtlKwdRel="SPCFD" CtlKwd="SLTTHD" N brTrueRel="EQ" NbrTrue="0" MsgID="CPD0977"><DepPar m Kwd="OPTION" Rel="NE" CmpVal="6" /><DepParm Kwd= "OUTPUT" Rel="NE" CmpVal="L" /></Dep></Cmd></QcdCL Cmd> |
What next?
I am going to copy this file to the other IBM i partition. Run this same program on that partition into a different file. Then I will compare the data within the two files using a hash of the record.
You can learn more about the QCDRCMDD API from the IBM website here.
This article was written for IBM i 7.4, and should work for some earlier releases too.
Addendum
I found a way to get the command defaults string for all commands using a CLOB, read about it here.
I tried your program and the same commands fail for me also and I have sufficient authority to run them. No error reported from API.
ReplyDelete-Matt
Wow. I have no idea what rules are used with this API.
Deletedear simon, thank you for this smart code template.
ReplyDeleteNot an authority problem, 10240 is not long enough for those commands, then BytesReturned is 0 and ByteAvailable is what's needed to get something
ReplyDeleteSimon, i think the answer is in documentation "If the receiver variable is not large enough to hold all of the generated CDML source, no CDML source is returned."
ReplyDeletei made a test with XMLdata char(65535) ccsid(1208); and i have everything.
-Christian.