MVS TOOLS AND TRICKS OF THE TRADE AUGUST 1999 Sam Golob MVS Systems Programmer P.O. Box 906 Tallman, New York 10982 Sam Golob is a Senior Systems Programmer. He also participates in library tours and book signings with his wife, author Courtney Taylor. Sam can be contacted at sbgolob@cbttape.org. Information about the CBT MVS Tapes can be found on the web, at http://www.cbttape.org. WRITING YOUR OWN TSO COMMAND PROCESSOR - Part 2 This month, we'll continue to build on last month's topic, about how you can write your own TSO command processor. We mentioned that nowadays, with the great potential power of REXX execs under TSO, most people might want to write their handy utilities in REXX. However, there is still much cause to write your own TSO command processors, or even to just understand how they work. If you understand how TSO command processors work, you might be able to easily alter someone else's TSO command processor, so it'll do what you need. You might not have to exert the effort of completely writing your own program. Where can you find samples of TSO command processor source code? The free collection of MVS goodies, including the CBT MVS Utilities Tape with the CBT Overflow Tape, contains large numbers of working TSO programs which you can study and change. You can download files from the CBT Tape materials, by going to the NaSPA home page, www.naspa.net, and clicking on "Online CBT Tape". As we mentioned last month, a TSO command processor is a program that was designed to run under TSO. The load module name, or one of its aliases, is what you execute. To execute a TSO command, you type the load module name and its operands on the command line of an ISPF session (prefixed by "TSO"). Or under "TSO ready mode", you type the command's load module name, together with its operands. A TSO command has potential access to all system functions, especially if it runs authorized. And it runs fast--usually far faster than an equivalent REXX exec. A load module that's designed to run as a command under TSO, is a bit different from other load modules in one respect. Upon entry, Register 1 is expected to point to a TSO control block (created by your TSO session) called the CPPL, or Command Processor Parameter List. The CPPL contains four address pointers: The first pointer points to a buffer which contains your command and its operands, known as the "command buffer", and the other three point to three TSO control blocks: the UPT (the User Profile Table), the PSCB (Protected Step Control Block), and the ECT (Environment Control Table). The command buffer contains a four-byte prefix. The first two bytes contain the length of the entire command buffer, and the second two bytes contain the offset from the beginning of the buffer where any operands start, if there are any. Last time, we showed how to use this command buffer prefix information to parse the operands ourselves. This time, we'll show you how to start looking into some of the "official tools" that IBM has provided. We'll look into the IBM parsing tool, IKJPARS, in some detail, and that will indicate the kind of work necessary to set up some of the other "official IBM tools". However, IBM manuals tend to complicate matters unnecessarily, and scare you into thinking you have to do too much preparation. And there is also another big "saving factor", in that once you've set up the IBM facilities in one program, you can copy them over to any other program, almost without change. Do it once, and that's enough! Let's Get Going with Examples. I think that when you study TSO commands, you should have a coding example, printed out in front of you, to look at. If you see "how the other guy did it", it'll be much quicker to learn how to do it yourself. Start out by looking at Figure 3, which roughly shows the steps that are required to write a TSO program. Then start looking for some code samples to learn from. Before talking about coding examples, I'd just like to mention the simple fact, that using the IBM facilities like IKJPARS and PUTLINE and IKJDAIR (the TSO dynamic allocation interface) might appear to be frightening and complicated at first. But the truth is that once you've done it once, you can copy almost the same code into all your other programs. You can even copy this code from a coding example, straight into your program. It helps, however, to have some understanding of what you're doing, so you don't get caught by some obscure bug. A good IBM example that comes with source code, is the PRINTOFF program that used to be distributed with CBIPOs from IBM. (I think the PRINTOFF source code was in IPO1.SAMPLIB.) PRINTOFF is a TSO command that will format a dataset for printing at a printer. If you can't find a copy of PRINTOFF to study, because (I think) it's no longer distributed with the OS/390 installation materials, there are several examples of PRINTOFF source code on the CBT Tape, that have been modified from IBM's distributed version. One example of PRINTOFF source is on CBT Tape File 300, and a better example is on File 325. PRINTOFF, although it uses most of the IBM-supplied TSO functions, is a bit complicated for a beginner, so I'd suggest a program like LPSCB (List the PSCB) from CBT Tape File 300 to start studying. LPSCB only uses the IBM Parser (IKJPARS) and the IBM Putline Service (IKJPUTL or its CVT pointer), so it's simpler than PRINTOFF, which also uses IBM's allocation service and error code handling. PRINTOFF also has a far more complicated operand structure than LPSCB, so we'll look at LPSCB as our introductory example. However, if you don't like either PRINTOFF or LPSCB, or if you're having trouble understanding them, there are several hundred other TSO programs to look at, on CBT Tape File 300 alone. What does LPSCB do? LPSCB is a TSO command which displays the contents of the three "other CPPL control blocks": the PSCB (its default), and optionally, the UPT, and the ECT. If you invoke the LPSCB command (as I've modified it) with the operand ALL, you get a formatted display of all these three control blocks for your own TSO session. If you code the optional keyword ECT, you get the PSCB and ECT only. If you code the keyword UPT, you get the PSCB and UPT only. Please look at Figure 1, to see the macros you have to code, to tell the TSO Parser program, IKJPARS, the structure of permissible operands for LPSCB. These are only keyword parameters. LPSCB has no positional parameters. Now look at Figure 2, which shows the macros that were coded to show the permissible operands for the PRINTOFF command. See how much more complicated the PRINTOFF operands are, than the LPSCB operands? The key to using the IBM Parser, IKJPARS, is to decide beforehand, what all the possible operands for your command processor program will be. Then, you can figure out how to code all the macros between the IKJPARM macro and the IKJENDP macro, so you can easily parse for the operands that your command will need. Before calling the IKJPARS routine, your program will have to perform some routine setup, which is similar for all TSO commands that use the IKJPARS service. Most TSO programs that use IKJPARS, perform this setup procedure near the beginning, because one of the first things they have to do, is to examine what operands were coded in the command invocation, and act accordingly. Therefore, they set up for IKJPARS, and then invoke it with a LINK or CALLTSSR macro, not far from the program's entry point. After return from IKJPARS, the program examines which operand fields were coded, and goes on intelligently with the rest of its processing. To set up for using IKJPARS, you have to create an 8-fullword area, and fill at least 7 fullwords of it. This is called the PPL, or Parse Parameter List. The Parse Parameter List is mapped by the IKJPPL macro in SYS1.MACLIB. Before you call IKJPARS, you have to point Register 1 to the Parse Parameter List. The seven addresses of the Parse Parameter List are (in order): The address of the UPT, the address of the ECT, the address of an empty ECB (a fullword of hex zeros), the address of the PCL (the Parameter Control List which is your IKJPARM thru IKJENDP macros), the address of the "Answer Place"--a location to where IKJPARS will return its results, a pointer to the Command Buffer, and finally, a pointer to the User Work Area, which could be the program save area pointed to by Register 13. IKJPARS returns the address of its results to the Answer Place, and you inquire whether a parameter is present or not, by examining keyword flags. Look at Figure 1, for example. If you want to see if the keyword CPPL was specified with an invocation of LPSCB, just code the instruction CLI REALKW+1,1 . To see if REAL was invoked, since IKJNAME 'REAL' was coded after IKJNAME 'CPPL' under the same REALKW IKJKEYWD macro, you code the instruction CLI REALKW+1,2 . A Branch on Equal (BE) indicates the presence of the keyword. Obviously, if you have two IKJNAME keywords coded under the same IKJKEYWD macro, it's "either-or". Only one of the two Branch on Equal statements will be true. Keeping Things Simple. I don't have space to discuss IKJPARS any further right now. The other formal IBM constructs GETLINE, PUTLINE, and PUTGET are just as complicated to use. To simplify matters, I'd suggest looking into the LPSCB code (or some other sample code), and then looking at IBM's explanations in the TSO Programming Guide manual, which are quite clear. Practically speaking, once you've used these facilities one time in a program, you can copy them over and over. Just get them straight once, and use them again and again. In my opinion, the IBM manuals tend to frighten people by making them think you have to use all of the IBM facilities. Much of the time, you can get away without using most of them. I'll show you: For a simple question that you want to ask a terminal user, it's certainly easier to code a TPUT and a TGET macro rather than a PUTGET macro. For a simple informative message, you can code TPUT instead of PUTLINE. The downside of using the simpler TPUT macro, is that it can't write output to a non-terminal, such as when you're running your program under TSO-in-batch. A lot of times, as I've indicated last month, you can avoid using IKJPARS, if your requirements are very simple. You can also avoid using IKJDAIR or dynamic allocation, by putting out a message, when the file open fails, saying that you have to allocate the proper ddname to the program beforehand. Error handling can also be made very simple, with informative messages being sent to the terminal via TPUT. Really, in many cases, your TSO program can be made quite uncomplicated, and it'll still do its job. So my job this month was to make you less scared to try writing a TSO command for yourself. My personal experience tells me that if you try and fix another TSO command that has everything already coded, it's a far less daunting experience, and you can learn the structures more gradually. One thing I can assure you: If you break through, and learn about TSO command processors, you'll get a very rewarding and satisfying feeling about the "wonders" you can do. Good luck. See you next month. * * * * * * * * * * * * * * * * * * * * * * * Figure 1. IKJPARS macros that describe the permissible operands in the LPSCB TSO command. Note that LPSCB has only keyword operands defined. You can code either CPPL or REAL as a keyword. Other optional keywords are: UPT, ECT, or ALL. This defines a very simple operand structure. MYPCL IKJPARM REALKW IKJKEYWD IKJNAME 'CPPL' IKJNAME 'REAL' UPTKW IKJKEYWD IKJNAME 'UPT' ECTKW IKJKEYWD IKJNAME 'ECT' ALLKW IKJKEYWD IKJNAME 'ALL' IKJENDP PRINT GEN * * * * * * * * * * * * * * * * * * * * * * * Figure 2. IKJPARS macros coded in the PRINTOFF command, from File 300 of the CBT Tape. Note that there are both positional and keyword operands defined, and some of the keywords have possible subfields defined, to be coded between parentheses after the keyword statement. For example, you can code COPIES(nnn), where nnn can be a 1- to 3-digit number. The PRINTOFF command obviously needs a far more complex operand structure than the LPSCB command does. Note the optional HELP and PROMPT fields. These tell IKJPARS what to inform the invoker, if there's a mistake in entering some of the operands. PARMTAB IKJPARM DSECT=IKJPARMD DSNAMES IKJPOSIT DSNAME,LIST,USID, X PROMPT='DSNAME', X HELP='DSNAME TO BE PRINTED' PCLASS IKJKEYWD IKJNAME 'CLASS',SUBFLD=CLASSUB PDEST IKJKEYWD IKJNAME 'DEST',SUBFLD=DESTSUB PHOLD IKJKEYWD IKJNAME 'HOLD' IKJNAME 'NOHOLD' PCOPIES IKJKEYWD IKJNAME 'COPIES',SUBFLD=COPYSUB PPRINT IKJKEYWD IKJNAME 'PRINT' IKJNAME 'NOPRINT' PLIST IKJKEYWD IKJNAME 'LIST' IKJNAME 'NOLIST' PHEAD IKJKEYWD IKJNAME 'NOHEADING' .GTE. IKJNAME 'HEADING' .GTE. PVOL IKJKEYWD IKJNAME 'VOLUME',SUBFLD=VOLSUB PFOLD IKJKEYWD IKJNAME 'FOLD',ALIAS=('CAPS') IKJNAME 'NOFOLD',ALIAS=('ASIS') PFORMS IKJKEYWD IKJNAME 'FORMS',SUBFLD=SFORMS .GTE. PTRAIN IKJKEYWD IKJNAME 'TRAIN',SUBFLD=STRAIN,ALIAS=('UCS') .GTE. PFCB IKJKEYWD IKJNAME 'FCB',SUBFLD=SFCB .GTE. PPROG IKJKEYWD IKJNAME 'PROG',SUBFLD=SPROG .GTE. PTEXT IKJKEYWD IKJNAME 'TEXT',INSERT='UCS(TN) ASIS' .GTE. ASAKW IKJKEYWD IKJNAME 'ASA' .PRC. SNUMKW IKJKEYWD IKJNAME 'SNUM' .PRC. MSGKW IKJKEYWD IKJNAME 'NOMSG' .PRC. * B E G I N S U B F I E L D S CLASSUB IKJSUBF SCLASS IKJIDENT 'CLASSNAME',LIST,FIRST=NONATNUM,MAXLNTH=1, X PROMPT='CLASS NAME' DESTSUB IKJSUBF *-----------------------------------------------------------------.SAD. * CHANGE THE DEST ID FROM YOUR TSO ID TO ANY VALUE. USERID .SAD. * IS A MAX OF 7 CHARACTERS AND DEST ID'S CAN BE 8 CHARACTERS .SAD. *-----------------------------------------------------------------.SAD. SDEST IKJIDENT 'DESTINATION',MAXLNTH=8, X FIRST=ALPHANUM,OTHER=ALPHANUM, X PROMPT='REMOTE DESTINATION IDENTIFY FOR PRINTED FILE', X HELP=('DEST ID OF YOUR PRINTER ASSIGNED BY SADSC') COPYSUB IKJSUBF SCOPIES IKJIDENT 'COPIES',MAXLNTH=3, X FIRST=NUMERIC,OTHER=NUMERIC, X PROMPT='1-3 DIGITS - NUMBER OF COPIES OF OUTPUT', X HELP=('NUMBER OF COPIES OF PRINTOUT DESIRED') VOLSUB IKJSUBF SVOL IKJIDENT 'VOLUME',MAXLNTH=8, X FIRST=ALPHANUM,OTHER=ALPHANUM, X PROMPT='VOLUME SERIAL FOR DATA SETS TO BE PRINTED', X HELP=('VOLUME SERIAL WILL BE ASSUMED FOR ALL DATA SETS') SFORMS IKJSUBF RFORMS IKJIDENT 'FORMS',MAXLNTH=4,FIRST=ALPHANUM, .GTE.X OTHER=ALPHANUM, .GTE.X PROMPT='FORMS DESIGNATION FOR PRINTED OUTPUT', .GTE.X HELP=('AN ALPANUMERIC STRING TO SPECIFY THE FORM') .GTE. STRAIN IKJSUBF RTRAIN IKJIDENT 'UCS',MAXLNTH=4,FIRST=ALPHANUM, .GTE.X OTHER=ALPHANUM, .GTE.X PROMPT='UCS DESIGNATION FOR PRINTED OUTPUT', .GTE.X HELP=('AN ALPANUMERIC STRING TO SPECIFY THE TRAIN') GTE. SFCB IKJSUBF RFCB IKJIDENT 'FCB',MAXLNTH=4,FIRST=ALPHANUM, .GTE.X OTHER=ALPHANUM, .GTE.X PROMPT='FCB DESIGNATION FOR PRINTED OUTPUT', .GTE.X HELP=('AN ALPANUMERIC STRING TO SPECIFY THE FCB') .GTE. SPROG IKJSUBF RPROG IKJIDENT 'PROG',MAXLNTH=8,FIRST=ALPHA, .GTE.X OTHER=ALPHANUM, .GTE.X PROMPT='PROG DESIGNATION FOR PRINTED OUTPUT', .GTE.X HELP=('AN ALPANUMERIC STRING TO SPECIFY THE PROG') .GTE. IKJENDP * * * * * * * * * * * * * * * * * * * * * * * Figure 3. General steps to be taken in writing a TSO command processor program. In any particular program, not all of these steps may be necessary. This information is roughly paraphrased from the TSO/E Programmer's Guide. 1. Access the command processor parameter list (CPPL). Copy the four addresses, and the CPPL address, into a save area in your program. 2. Validate any operands entered with the command. You can code this yourself, or you can use the IBM Parser service, IKJPARS. 3. Communicate with the user at the terminal. You can use the TGET-TPUT combination of services, or you can use GETLINE-PUTLINE-PUTGET. 4. Perform the function of the command according to any operands the user specified. A TSO command is like any program. It does what you design it to do. Now go do it! 5. Recognize and pass control to any subcommands. If your program's design requires subcommand prompting, use PUTGET to issue a message and return a line of input from the terminal. You get a buffer similar to the command buffer, which you have to parse for valid operands. Then you ATTACH a subprogram to complete the subcommand's task. 6. Intercept and process abnormal terminations. You can use the STAE or ESTAE macros to intercept abends and keep the program up. But you don't have to do this, in many cases. 7. Respond to and process attention interruptions entered from the terminal. You can use the STAX macro instruction, to allow the program to take a meaningful action when the ATTENTION key is pressed by the user. 8. Set the return code in Register 15 and return control to the TMP, which called your program. This way, you'll be able to show, in a general way, if your program worked properly or not.