MVS TOOLS AND TRICKS OF THE TRADE JULY 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 1 Sometimes we surprise ourselves. Lately, I've done it a lot. I'm finding that I know how to code in assembler language, far better than I thought I could. When I was first starting out in this business, I used to look at many amazing programs that other people wrote, especially those of my teacher, Jeff Broido. I wondered whether I would ever produce similar pieces of work, later on in my own career. Even when I broke in (many years ago), MVS Systems Programming no longer required the writing of customized assembler code all day. It was already a job that largely consisted of installing and tweaking the operating system, to make sure everything was working properly. Sure, I went to assembler school and managed to code a few exits here and there. But writing major utilities that were hitherto unavailable, was no longer part of the job description. So I didn't get extensive assembler coding practice on a regular basis. If it weren't for my involvement in handling free code from the CBT MVS Tape, and looking at a lot of source code examples from there, I feel that I'd never achieve the satisfaction of building my assembler skills to the point where I myself could do something major. Today, we're going to talk about how to create an assembler program which does a useful task under TSO. Nowadays, we have REXX available, and most of us try to create our handy little personal tools in REXX. But despite the large capabilities of the REXX language under TSO, I feel it is still a worthwhile skill to write assembler programs too. Assembler programs can execute faster. And assembler programs have full access to all facilities of the operating system. When you code in assembler, you are not subject to any artificial restrictions as to what your programs can do. When I first started, I thought that writing a "TSO Program", or more exactly, a "TSO Command Processor", was something that was quite difficult. IBM's manual on the subject, which was called, "Writing a TSO Command Processor or TMP" (Terminal Monitor Program), looked very difficult to get through. Actually, I found later, that I could write many working programs, without having deep knowledge. Knowing a few fundamental principles was enough. Today, I want to help you "cut through the ice", so you too, can start doing this stuff yourself. IBM's current manual on this subject, called TSO/E Programming Guide, is much easier to read than the old IBM manual was. How is a TSO Command Processor Different? A TSO Command Processor, or (in other words) a program that is designed to be executed during a TSO session, is not much different from any program that's designed to run in batch. When you enter the program, you have to save the registers using the normal conventions, and when you leave the program, you have to restore the registers in the usual way. Register 15 comes into the program, pointing to the entry point, and Register 14 contains the return address of the caller. The main difference is Register 1, which points to a TSO control block called the CPPL, or Command Processor Parameter List. We'll talk about the CPPL in the next section. But first, let's backtrack a bit. Let's ask ourselves the question: What exactly is a TSO command? The answer is that a TSO command is a load module. When you type a TSO command on the screen during a TSO session, you're actually executing a load module, which has that command name. For example, if you execute a TSO SEND command, to send a message to another TSO user or to the console operator, you're actually executing a load module whose name is "SEND". The way IBM ships this command, SEND is a load module in SYS1.CMDLIB, a library that is filled with many essential TSO commands, such as DELETE, ALLOCATE, ACCOUNT, DEFINE, CALL, and so forth. Each of those commands is a separate load module in SYS1.CMDLIB. Your TSO session has access to these commands because SYS1.CMDLIB is in the system link list, and all programs executing under MVS, have access to programs in the system link list. Now suppose you had access to another program whose load module name was SEND, and you copied or linkedited its load module into another load library that was pointed to as a STEPLIB in your TSO Logon Proc. Since the STEPLIB library is accessed in your TSO session before the link list libraries are accessed, when you type a SEND command in TSO, you'll execute your own personal SEND program, and not IBM's SEND program. If you wanted access to IBM's SEND program again, you'd have to either rename your own load module to another name, like SENDX, or you'd have to delete it from your STEPLIB library. The rename route is probably the more useful one to take, because if you want your own program instead of IBM's, you can still issue the SENDX command under your TSO session, whereas if you deleted your own version, you could no longer execute it at all. There's one last topic we have to cover, before we can go back to talking about actually writing the program. That's the question of APF authorization. MVS provides a "safety net" for most programs, so they don't change essential system control blocks in such a way that they'd damage the system. However, if a program, or in our case a TSO command, has to perform a system internals function that's normally protected, we can "authorize" it to do so. The program authorization facility in MVS is called "APF Authorization" (for Authorized Program Facility). IBM's SEND command is an example of an authorized program. Why does SEND need to be authorized? Many installations allow the average TSO user only READ access to SYS1.BRODCAST, a dataset which contains TSO user messages and system notifications to TSO users. You certainly wouldn't want to give an average user RACF UPDATE access to SYS1.BRODCAST. By mistake, the user could then overwrite SYS1.BRODCAST with IEBGENER, or do some other stupid thing, and we want to protect against that. But we also want all TSO users to be able to send messages to other TSO users, even if the other users aren't logged on. These messages would go to the SYS1.BRODCAST dataset, normally (if TSO Userlogs aren't invoked). Therefore, every TSO user has to be able to update the SYS1.BRODCAST dataset in this special way, by using the SEND command. So the problem gets solved by "authorizing" the SEND command using the APF facility, and when the SEND command is authorized, it can circumvent the normal TSO user's "read only" restriction to SYS1.BRODCAST. The newly empowered SEND command, can then do the writing of the message to SYS1.BRODCAST, on behalf of the normally unempowered TSO user. To authorize the SEND command, or any other TSO command, we have to mark its load module (in the pds directory of the load library) as APF authorized, and the library itself has to be made known to the system, through the APF authorization table, as an APF authorized library. In former releases of MVS, all link list libraries would always be APF authorized by default, but now, through the IEASYSnn parameter, LNKAUTH, this doesn't have to be the case. The system authorization table is defined (in modern MVS systems) by the PROGnn member of PARMLIB. There's one more catch though. In the IKJTSOnn PARMLIB member, a TSO command has to be listed among the programs in the AUTHCMD program list. Otherwise TSO (and not the MVS system itself) will block the APF authorization of the program execution. So to authorize a TSO command, we need all three conditions to be fulfilled: its load module has to be marked APF authorized through the SETCODE AC(1) linkedit statement, it has to reside in an APF authorized load library, and it has to be listed in the AUTHCMD list of the IKJTSOnn member of PARMLIB. I'd like to relate a personal technique I've discovered, for obtaining what I call "personal APF authorization". The AUTHCMD and AUTHPGM program lists, used to be made known to TSO in two csects, called IKJEFTE2 and IKJEFTE8 respectively. Once upon a time, those csects were part of the terminal monitor program IKJEFT02, but now they still exist, in a separate small load module called IKJTABLS. You can find the default IKJTABLS in SYS1.LPALIB, and it doesn't have much in it. My trick is that if you put your own (greatly expanded) version of IKJTABLS in an authorized STEPLIB library in your personal TSO Logon Proc, it will completely override all the settings in the active IKJTSOnn member of PARMLIB. This trick is intended for experienced systems programmers only, but it's very useful when you're working on a large system, where you can't disturb the global PARMLIB settings that everybody else uses. WRITING YOUR OWN TSO PROGRAM Now that we've gone through these preliminaries, we'll talk about how to use the CPPL, how to use IBM's TSO facilities, and how to write handy TSO utilities that will enhance your working life for years to come. We'll have to continue this topic next month too, because there's a lot to say. Take a look at Figure 1, which is the IKJCPPL macro that maps the Command Processor Parameter List, that is passed to your program via Register 1. The CPPL contains four address pointers, and when your program gains control, the first thing you should do, is to copy these address pointers into program storage so you can refer to them later. At this point, we're most concerned with the contents of the command buffer. But the other pointers, to the UPT, the PSCB, and the ECT, will come into play later, when we might need to examine the contents of these 3 important control blocks. As its name implies, the Command Buffer contains everything you entered into a TSO command when you invoked its command processor program under TSO. In addition, the Command Buffer starts with a 4-byte header that is divided into 2 halfwords. The first halfword contains the binary numeric length of the entire command buffer, including the header. The second halfword contains a numeric value known as the "offset". This offset field conveniently points you to the beginning of any command operands. Or it contains the number of characters in the command itself, if there aren't any extra operands. I'll explain. Suppose I entered a TSO command: RENAME DATASET.A DATASET.B . This command (RENAME), with its operands (the dataset names), contains 26 characters in all. If I entered more than one space between the operands, the command would be longer. Now if I entered this command at the beginning of a line on my TSO terminal, the command buffer would then be 30 bytes long--4 bytes for the header, and 26 bytes for the command with all its text that follows. In this scenario, the offset would contain the number 7, which is the number of text bytes before the first operand: DATASET.A . So to point to DATASET.A , so I can parse it, I'd do the following: Please refer to Figure 2 for details. I'm assuming I've saved the pointer containing the address of the command buffer, that was in Register 1. I'll do a Load Register instruction, LR R11,R1 , to move the address of the command buffer from the (changeable) Register 1 to the less-used Register 11. First, I point to the address of the command buffer, which is at an offset of 0 bytes past Register 1. I pick up the number of bytes in the command buffer by a Load Halfword instruction: LH R3,0(,R1) , to place the total number of bytes in the command buffer in a register--in this case, Register 3. Then I do a Store Halfword instruction to save this value in my program. That is: STH R3,CMBLENG , where CMBLENG is a 2-byte field in my program. In our case, CMBLENG will now contain the binary value of 30, or hex 001E. Before changing the value of Register 3, it's advisable at this point, to check to see that its value is greater than 4, just to make sure that a command was entered. We issue a Compare Halfword instruction: CH R3,=H'4' , and a BNH (Branch Not High) instruction afterwards, to point to an error-handling routine, or to get out of the program with a warning message. To pick up the value of the offset, which is a halfword, two bytes past Register 1, we issue a LH R3,2(,R1) , and then issue STH R3,OFFSETT , where OFFSETT is another 2-byte field in our program. In our case, OFFSETT will now contain the hex value 0007. Now, we can utilize the condition code capabilities of the Subtract instructions, to determine if we have any operands or not, and if we have them, we can take the appropriate action. Load the length of the command buffer into work Register 5, using LH R5,CMBLENG . Load the offset into Register 3 if it isn't already there: LH R3,OFFSETT . Issue the Subtract Register instruction: SR R5,R3 , to subtract the offset value from the total length of the command buffer. Since the command buffer has a 4-byte header, we can now issue a Subtract Halfword instruction: SH R5,=H'4' , to subtract 4 more bytes from the offset difference, to account for the length of the header record. If the resulting value is zero, there were no operands entered after the command. If the resulting value is less than zero, there's an error condition, and if the resulting value is greater than zero, we've got operands to parse. We can check all of this, using the return codes from the Subtract Halfword instruction. By looking up the Subtract instructions in the Principles of Operation Manual, we see that a zero result gives a condition code of 8 (byte 0 is one). A negative result gives a condition code of 4 (byte 1 is one), and an overflow result gives a condition code of 1 (byte 3 is one). Thus, to test for these conditions, we issue the Branch on Condition (BC) instruction as follows. First to test for the zero result (no command operands), we issue: BC 8,ERRMSG1 , to issue a message that the command should be entered with operands. Then, we test for the negative or overflow conditions with the instruction: BC 5,GETOUT1 , to handle that case, and get out of the program. Now, we're ready to parse our operands. Register 11 contains the address of the command buffer, that was copied from Register 1 originally. So we point to the contents of the command buffer in Register 1 as follows: LA R1,0(,R11) , add the offset value to it: AR R1,R3 , (possibly after LH R3,OFFSETT), bump the value up by 4, to compensate for the Command Buffer header: LA R1,4(,R1) , and now R1 is pointing to the location in storage where the operands begin. We now have a choice. We can either examine our command's operands ourselves, by writing our own instructions, or we can use IBM's official TSO command parsing interface called IKJPARS. I don't have the space to continue the discussion past this point now, so we'll pick it up here, next month. See Figure 2 for the instructions we've executed so far. If you're fairly new to assembler, I hope you've gotten a realization that "I can really do this". You really can. It's quite easy, once you've gotten the hang of it. The CBT MVS Utilities Tape, which you can access online over the Internet, has very many coding examples of TSO commands, which you can learn from. I'd especially recommend File 300 of the CBT Tape, which you can access directly at ftp://ftp.cbttape.org/pub/cbttape/cbt/CBT300.zip (case sensitive). To see the CBT Tape offerings in general, go to www.naspa.net , and click on "Download CBT". You'll have a lot to look at. Good luck. I'm looking forward to seeing you next month. * * * * * * * * * * * * * * * * * * * * * * * Figure 1. Mapping macro for the CPPL (Command Processor Parameter List). At entry to any TSO command invoked by the TSO Terminal Monitor Program, Register 1 points to the address of the CPPL. A principal part of writing a TSO command, is to exploit this information to the extent that's necessary. MACRO IKJCPPL ****************************************************************** * THE COMMAND PROCESSOR PARAMETER LIST (CPPL) IS A LIST OF * * ADDRESSES PASSED FROM THE TMP TO THE CP VIA REGISTER 1 * ****************************************************************** CPPL DSECT CPPLCBUF DS A PTR TO COMMAND BUFFER CPPLUPT DS A PTR TO UPT CPPLPSCB DS A PTR TO PSCB CPPLECT DS A PTR TO ECT MEND * * * * * * * * * * * * * * * * * * * * * * * Figure 2. Coding example to point to where the operands of your TSO Command start, so you can parse them to determine their values. We assume you've established your base registers and saved the caller's registers. LR R11,R1 Preserve the pointer to the CPPL LTR R11,R11 Any CPPL? No. Not a TSO command. BZ ERRORA Not TSO. Get out. L R1,0(,R11) Point to the command buffer. LTR R1,R1 Any command buffer? BZ ERRORB No. Handle the error. LH R3,0(,R1) Load command buffer length STH CMBLENG Save value in the program CH R3,=H'4' Is there a command there? BNH ERROR1 No command or error, handle it. LR R5,R3 Length of command + 4 into R5 LH R3,2(,R1) Load value of offset into R3 STH OFFSETT Save offset value in the program SR R5.R3 Subtract offset from length + 4 SH R5,=H'4' Correct for length of CMDBUFR header BC 8,MESSAGE1 Zero, send 'no operands' message BC 5,ERROR2 Negative or overflow, get out. * ------------------------------------------------------------------ LA R1,0(,R11) Load address of command buffer LA R1,4(,R1) Point past the header LH R3,OFFSETT Reload offset quantity into R3 AR R1,R3 Point to the first command operand * ------------------------------------------------------------------ * At this point, R1 is pointing to the address of the first * operand of our command, so we can parse it for its values. * ------------------------------------------------------------------ CMBLENG DS H Store length of command buffer OFFSETT DS H Store value of offset