Backup a bit…
About a million years ago, actually 1976, I started in the computer industry as an operator. My job interview was with the managing director of the ‘computer department’ because it wasn’t ‘Information Technology’ yet! One thing that I have always remembered from that interview is him saying that I shouldn’t worry about making mistakes because it can always be fixed. I think he probably said it as much to reassure my novice self as anything else, but it always stuck with me.
And for the most part, he was right. But I have to qualify the general statement by saying that fixing it is a lot easier if you’ve got a backup of everything!
This comes to mind because a backup saved my bacon a few days ago.
I’ve been working on a little side project for a good while now, just a fun little thing I’ve been playing around with and while it’s not a lot of code as such, it is pretty involved. Now I still work quite a bit on a good old green screen in ISPF where I tend to use the 3.4 data set list option because I can easily switch from browse of a member to edit and back without having to switch ISPF screens.
The one thing I have never liked about the data set list function and the resultant member list display for a data set though, is that you can easily enter a D (for delete!) against either a data set or member. While it does prompt you to confirm the action (unless you’ve turned it off) it is pretty easy to be not paying attention, press enter on the confirm screen and it’s gone.
At this point you go ‘Oh F***k’ or words to that effect.
I know this because that’s exactly what happened to me the other day. Not paying attention, something popped up, I hit enter and it was only when the word ‘deleted’ appeared beside the member name that I realized I’d screwed up!
Now I’ve been working on this project for a while. In fact I last touched it a few years ago, got to a ‘sticky’ point where the coding was neither fun nor easy and put it aside until recently when I decided to pick it up again.
In all that time I kept thinking I really should start taking backups of the work. Fortunately for me, that’s exactly what I had done the previous day. I spent some time to write some jobs to back everything up so when I screwed up, thankfully I had a backup that I was able to restore the missing member from.
Had this happened a day earlier and it would have been a whole different story.
An after effect of this is that I wrote my own little data set list function that does NOT allow me to do a delete either against the data set or a data set member. Again, this was something I’ve often thought about, just never gotten around to until this happened and I got ‘motivated’ as it were.
My ISPF 3.4 alternative can be down loaded from ===> here. <===
Unzip the file and upload the exec and panel to your ISPF REXX and ISPPLIB panel library using a text transfer to convert back to EBCDIC. Edit the exec to specify the name of the panel library or remove the LIBDEFs for the panel library if it is already allocated to your ISPF ISPPLIB DD.
Then just run the exec, @34. It takes two parameters:
- A mask to specify the datasets to list, just like the real 3.4
- One or more exclude strings. Any data set name in the result list that matches one of these strings will not be show.
Line commands against data sets are:
- B or S – Browse
- E – Edit
- Z – Compress
Line commands against a member in a member list are:
- S – Whatever what entered against the data set, E or B.
- E – Edit
- B – Browse
- SUB – Submit the member
- = Repeat the previous command
It’s very basic. A lot of the error messages are just issued as say statements. If you use it, feel free to modify it to your needs.
This is the exec source:
/* rexx */ /* Custom ISPF 3.4 function */ /* pfx is dataset prefix to list */ /* excl is one or more exclude masks */ /* David E Ellis April 2020 */ parse arg pfx excl if pfx = '' then pfx = sysvar(syspref) address ISPEXEC /* "CONTROL ERRORS RETURN"*/ "LIBDEF ISPPLIB DATASET ID('DAVE.ISPPLIB')" "TBCREATE @34 NAMES(DSNAME DSTYPE) NOWRITE" if rc <> 0 then do say "TBCREATE RC="rc signal exit end rc=Build_list(pfx) if rc <> 0 then signal exit do forever 'TBTOP @34' 'TBDISPL @34 PANEL(@34)' , 'AUTOSEL(NO)' , 'ROWID(ROW)' if rc > 7 then leave 'TBQUERY @34 POSITION(row)' if row = 0 then row=1 do ztdsels if sel <> '' then do upper sel select when sel='S' then do rc=doit('B') /* default */ end when sel='B' then do rc=doit('B') end when sel='E' then do rc=doit('E') end when sel='Z' then do rc=compress(dsname,dstype) end otherwise do zerrsm = 'Invalid option' zerrlm = 'Invalid option for this line' zerralrm = 'NO' 'SETMSG MSG(ISRZ002)' end sel='' end end /* if sel <> '' */ if ztdsels = 1 then iterate 'TBDISPL @34' end /* do ztdsels */ end /*----------------------------------------------------*/ exit: "CONTROL ERRORS RETURN" "TBEND @34" "LIBDEF ISPPLIB" return 0 /*----------------------------------------------------*/ Build_list: parse arg pfx myrc=0 "LMDINIT LISTID(DSID) LEVEL("pfx")" if rc <> 0 then do say "LMDINIT RC="rc myrc=4 signal Build_list_exit end DSN = '' do forever "LMDLIST LISTID("dsid") OPTION(LIST) DATASET(DSN)" , "STATS(YES)" if rc = 4 then do say 'Nothing found for 'pfx myrc=4 leave end else if rc = 8 then leave /* end of list */ else if rc <> 0 then do say "LMDLIST RC="rc myrc=4 leave end exclrc=0 /* RC=0 means do NOT exclude */ if excl<>'' then do exclrc=CheckExcl(dsn,excl) end if exclrc=0 then do dsname=dsn dstype=zdldsntp "TBADD @34" end end Build_list_exit: "LMDLIST LISTID("dsid") OPTION(FREE)" "LMDFREE LISTID("dsid")" return myrc /*----------------------------------------------------*/ doit: myrc=0 parse arg option if strip(dstype) = "" then do /* unknow org, let ispf handle */ option" DATASET('"dsname"')" signal doit_exit end else myrc=mlist(option) doit_exit: return myrc /*----------------------------------------------------*/ mlist: myrc=0 parse arg mlistopt /* mlistopt just controls default action if S entered for mbr */ "LMINIT DATAID(MLID) DATASET('"dsname"') ENQ(SHR)" if rc<>0 then do say "LMINIT Rc="rc say ZERRLM myrc=4 signal mlist_exit end "LMOPEN DATAID("mlid") OPTION(INPUT)" if rc<>0 then do say "LMOPEN Rc="rc myrc=4 signal mlist_exit end mbr="" do forever prevcmd="" "LMMDISP DATAID("mlid") OPTION (DISPLAY) COMMANDS(ANY)" , "TOP("mbr") FIELD(9)" if rc=0 then nop else if rc=4 then do say " Empty dataset" leave end else if rc=8 then leave else do say "LMMDISP DISPLAY RC="rc say ZERRLM leave end mbr=strip(zlmember) rc=procmbr(mbr,zllcmd) do forever "LMMDISP DATAID("mlid") OPTION(GET)" getrc=rc if getrc=0 then nop else if getrc=8 then leave else do say "LMMDISP GET RC="getrc say ZERRLM leave end mbr=strip(zlmember) rc=procmbr(mbr,zllcmd) end "LMMDISP DATAID("mlid") OPTION(FREE)" end mlist_exit: "CONTROL ERRORS RETURN" "LMCLOSE DATAID("mlid")" "LMFREE DATAID("mlid")" return myrc /*----------------------------------------------------*/ procmbr: Procedure Expose mlistopt dsname prevcmd myrc=0 parse arg mbr,cmd if cmd = "S" then do prevcmd=mlistopt myrc=procmbr(mbr,mlistopt) end else if cmd = "B" then do prevcmd="B" "BROWSE DATASET('"dsname"("mbr")')" end else if cmd = "E" then do prevcmd="E" "EDIT DATASET('"dsname"("mbr")')" end else if cmd = "SUB" then do prevcmd="SUB" address TSO "SUBMIT ('"dsname"("mbr")')" end else if cmd = "=" then do myrc=procmbr(mbr,prevcmd) end else do prevcmd=cmd say "Invalid command "cmd end return 0 /*----------------------------------------------------*/ CheckExcl: Procedure /* If any of the string in excl are in the dsn return rc=4 */ /* else returns rc=0 (excl strigs NOT in dsn) */ parse arg dsn , excl upper dsn upper excl do i = 1 to words(excl) if pos(word(excl,i),dsn) > 0 then return 4 end return 0 /*----------------------------------------------------*/ compress: Procedure parse arg dsname, dstype if dstype <> 'PDS' then do say 'Invalid dataset type' signal compress_exit end "LMINIT DATAID(LMIID) DATASET('"dsname"') ENQ(EXCLU)" if rc <> 0 then do say "LMINIT RC="rc say ZERRLM signal compress_exit end "LMCOMP DATAID("lmiid")" if rc=0 then do say "Compressed" end else do say "LMCOMP RC="rc say ZERRLM end "LMFREE DATAID("lmiid")" compress_exit: return 0
And this is the panel definition:
)PANEL KEYLIST(ISRSPBC,ISR) )ATTR @ TYPE(OUTPUT) INTENS(LOW) COLOR(YELLOW) )BODY EXPAND(//) %--/-/-- Dataset List --/-/-- %Command ==> _ZCMD / / +Scroll ===>_ZAMT+ +DSLIST - Data Sets Matching@Z + )MODEL _Z+ @Z + )INIT .ZVARS = '(PFX SEL DSNAME)' &zamt=csr &zcmd='' &sel='' .HELP=ISR00003 )REINIT IF (.MSG = ' ') &SEL = ' ' REFRESH (SEL) )PROC IF (&ZTDSELS ^= 0000) VER (&SEL, LIST, B, b, E , e , S , s, Z, z) )END
Conditional Assembly Language…
If you’ve ever read or written a macro you have no doubt at least seen conditional assembly language. It’s all that AIF and AGO stuff that forms a sort of ‘program’ within the macro so that it can generate code or whatever depending on whatever the input parameters are.
What’s really cool though is that it is not just limited to macros, you can use it within open code as well. So you might ask ‘why would you need to do that?’ but even if you don’t ask, here’s one interesting situation that came up recently.
I had some code that used a macro to generate a DSECT to map a control block. However we were switching version of the product that supplied the macro and a field within the macro had changed names even though it’s content had not. The result was that my code would only assemble with one version of the macro since with the other one it would get a not found error for the changed label. Since I did not want to have to co-ordinate my source code change with a build tool change the problem I had was how to make my source code support both versions of the macro and DSECT that it generated?
In case you have not guessed, the answer is conditional assembly language.
Here’s an example.
The old macro/DSECT:
MYMACRO SOMENAME DSECT MYFIELD DS CL8
The new version of the macro/DSECT
MYMACRO SOMENAME DSECT NEWNAME DS CL8
So my code originally looked something like this:
USING SOMENAME,R2 CLC MYFIELD,=CL8'AAAAAAAA'
Obviously if I switch to the new macro library, my assembly will fail since the field ‘MYFIELD’ is no longer defined within the DSECT.
However, what you can do is to test to see if the variable ‘MYFIELD’ is defined and if not then conditionally change the code that gets assembled. Thus:
USING SOMENAME,R2 AIF (T'MYFIELD EQ 'U').NEWMAC CLC MYFIELD,=CL8'AAAAAAAA' AGO .CONT .NEWMAC ANOP CLC NEWNAME,=CL8'AAAAAAAA' .CONT ANOP
The AIF tests to see if the ‘type’ specification for the field MYFIELD is ‘U’, that is undefined. If it is undefined that means it has not been seen by the assembler (yet) so jump to the label .NEWMAC and continue to generate the code from there, which of course generates the code using the new field label of NEWNAME.
If the field MYFIELD is not ‘undefined’ then the assembler generates the code using the old field name, MYFIELD and then jumps (AGO) to the label .CONT to continue the assembly.
As a result, no matter which version of the macro library I am using, my code still assembles and works correctly.
There are other ways of achieving the same effect; For example by using the conditional assembly language to control the redefining of the old or renamed symbol to a common name and using that common name in the open code.
One gotcha though to be aware of. The macro/DSECT has to be defined in the source code BEFORE the conditional assembly code. If it is defined after the conditional code then, since the assembler has not seen either field at the time it encounters the test for the field being defined, it will always treat it as being undefined which would cause an assembly error when using the old macro/DSECT library because it would generate the code to use the new field name.
Update to the Enhanced 3270 User Interface Security Requirements
In this post I mentioned that the recent OB700 IF1 update had added additional security checking for KOBASE and 04SRV resources and that without suitable RACF (or other security product) profiles to give the user read access to these resources, users would not be able to log on to the Enhanced 3270 UI.
PTF UA71750 is now available and removes the security checking on these resources. As a result, these additional security profiles are no longer required.
New to PARMGEN…?
If you need to know more about the PARMGEN configuration tool for the ITM z/OS environment, here are a couple of videos about it.
What is PARMGEN
Convert an ICAT RTE to PRMGEN and Upgrade the Products
Securing the Enhanced 3270 User Interface with RACF and OB 700 IF1
In this post I talked about securing the enhanced 3270 User Interface with RACF
Since then a new level of the base code (FMID HKOB700) called Interim Feature One (IF1) has arrived in the form of PTF UA69877. But before you go off and apply that, don’t! Instead apply UA70618 which fixes some issues with the original code that may impact certain users.
In the hold doc (you do read all the hold doc don’t you!) for UA70618 are instructions on setting up new RACF profiles that may be needed if you are using security to protect the Enhanced 3270 User Interface environment. These are the new resources being checked:
KOBUI.USER.COMMAND.<command_name> KOBUI.ADMIN.PREFS.AUTOUPDATE KOBUI.ADMIN.LISTUSERS KOBUI.ADMIN.TRACE.UI.<trace_type> KOBUI.ADMIN.TRACE.INTERNAL.<trace_type> KOBUI.ADMIN.USEHUB.<hub_name> KOBUI.ADMIN.MEMBER.WRITE.<dd_name>.<member_name> KOBUI.ADMIN.ITM.<hub_name>.SERVICEINDEX KOBUI.ADMIN.ITM.<hub_name>.<servicepoint_name>.SERVICECONSOLE KOBUI.ADMIN.ITM.<hub_name>.<servicepoint_name>.SOAPCONSOLE SYSTEM.<managed_system_name>.<table_name>
You could protect these with the following RACF profiles:
PERMIT KOBUI.USER.** PERMIT KOBUI.ADMIN.** PERMIT SYSTEM.**
Recently I came across a problem where a customer needed additional RACF profiles setting up in order to log on to the Enhanced 3270 UI. These are:
KOBASE.** O4SRV.**
The easiest way to add these would be with a UACC or READ but your installation standards may require a different implementation. I believe a tech note will be forthcoming on the issue soon.
This particular user had a default profile of * in the RACF class with a UACC of NONE so anything that was not specifically permitted was rejected. If you do not have such a profile in the RACF class used by the Enhanced 3270 UI then the default action is to allow the request if a profile does not exist which basically allows anyone to do anything unless you specifically lock it down. That approach results in the least amount of work to secure the Enhanced 3270 UI environment.
Using the SDSF REXX interface – Part 2
Here’s another REXX exec using the SDSF REXX interface. This one will issue a command to the local system.
/**REXX**************************************************************/ /* */ /* ISSUCMD - Issue a command. */ /* */ /* Input args: */ /* */ /* Delay - # secs to wait for command completion */ /* Command - Command to issue (do not need leading /) */ /* */ /* Output: return code from the command. */ /* */ /********************************************************************/ parse arg delay, command Address 'TSO' IsfRC = isfcalls( "ON" ) saved_delay=ISFDELAY /* save curr delay */ ISFDELAY=delay /* Set delay time */ address SDSF "ISFEXEC '/"command"'" saverc=rc ISFDELAY=saved_delay /* restore delay */ IsfRC = isfcalls( "OFF" ) return saverc
Using the above command to start a started task on the local system:
/* REXX */ rc=ISSUCMD(2,"S CICS1") return 0
Using the SDSF REXX interface – Part 1
I thought I’d post some of the REXX execs I have written that use the SDSF REXX interface. This first one determines if a job is executing, either on the specified lpar if passed in or anywhere in the sysplex if not (subject to shared JES spool limits). It returns a return code and, if executing, the lpar the job (or stc) is executing on separated by a space which you can then parse out.
/**REXX**************************************************************/ /* */ /* JOBSTAT - Determine if a job is executing or not */ /* */ /* Input args: */ /* */ /* Jobname - Required. */ /* sysname - Optional. If specified will only look for job on that */ /* system. */ /* */ /* Output: */ /* 0 - Job is not executing (on requested system if specified) */ /* 1 - Job is executing + sysname of system job is running on */ /* */ /********************************************************************/ parse arg jobn , sysname upper jobn upper sysname Address 'TSO' IsfRC = isfcalls( "ON" ) saved_delay=ISFDELAY ISFDELAY=0 executing=0 executing_on="" address SDSF "ISFEXEC ST "jobn if sysname <> "" then do do i = 1 to jname.0 if queue.i = "EXECUTION" & actsys.i = sysname then do executing=1 executing_on=actsys.i end end end else do do i = 1 to jname.0 if queue.i = "EXECUTION" then do executing=1 executing_on=actsys.i end end end ISFDELAY=saved_delay IsfRC = isfcalls( "OFF" ) return executing" "executing_on
Using the above exec to determine if a job is executing on the local lpar:
/**REXX**************************************************************/ /* */ /* Call jobstat to see if job is running in the local system */ /* */ /********************************************************************/ sysname=MVSVAR('SYSNAME') /* local sysname */ parse value jobstat("MYJOB",sysname) with executing lpar if executing then do /* do 'executing' stuff here */ say 'Job is executing' end else do /* Do 'Not executing' stuff here */ say 'Job is not executing' end return 0
Using the above exec to determine if a job is executing in the sysplex and on which lpar:
/**REXX**************************************************************/ /* */ /* Call jobstat to see if job is running anywhere in the sysplex */ /* */ /********************************************************************/ parse value jobstat("MYJOB") with executing lpar if executing then do /* do 'executing' stuff here */ say 'Job is executing on '||lpar end else do /* Do 'Not executing' stuff here */ say 'Job is not executing' end return 0
Maintaining the OMEGAMON XE for CICS on z/OS Globals with PARMGEN
Global settings selection
May aspects of the operation of OMEGAMON XE for CICS on z/OS data collection and operation are controlled by global settings. These settings are held in a PDS member of the library allocated to the RKC2GLBL DD in the ‘classic’ collector address space (also known as the OCCI). Typically the hlq.RKANPARU library is allocated to the RKC2GLBL DD
Global members have member names of the form KC2GLBxx where xx is a two character suffix. The global member to be used by the OCCI when monitoring a given CICS region is selected by including a KOCGLBxx dummy DD in the CICS startup JCL, where the xx in the DD name matches the suffix of a global member in the library allocated to the RK2GLBL DD in the OCCI address space.
If you do not add a KOCGLBxx DD to the CICS region, then xx defaults to ’00’ (zero zero). If the selected global member is not found by the OCCI address space then it uses a default set of values.
PARMGEN support for the CICS globals
Currently PARMGEN support for the CICS globals is limited to copying global members from a source PDS into the run time environment’s hlq.RKANPARU library.
The source PDS to be used is specified by the KC2_CLASSIC_KC2GLB_SRCLIB parameter in the rte member name in the PARMGEN WCONFIG library and defaults to the ICAT INSTDATA dataset.
If you are not converting from an ICAT install for the RTE you can point this parameter at any library you chose and place your completed global members in there. During the deployment phase of the RTE, the deployment jobs will copy the global members from this library to the run time’s hlq.RKANPARU library.
If you are NOT using the default global member for a CICS region you will still need to manually add a KOCGLBxx dummy DD card to the CICS startup JCL in order to select the corresponding global member.
PARMGEN and System Libraries…
The deployment phase (step 11 on the menu) of creating and loading an RTE with PARMGEN copies all the run time data to the run time libraries. Some of these members need to go into system libraries such as SYS1.PROCLIB and SYS1.VTAMLST or your user versions of same.
Rather than overwrite my current live run time procedures and VTAMLST members I configure PARMGEN to write them to ‘staging’ libraries. From there I can double check them against the current live members before committing them.
To configure PARMGEN to write to your own system data sets, edit the $GBL$USR member in WCONFIG by selecting option 8 on the main menu and then option 2 and change the highlighted lines shown in this screen shot:
You have to manually create these data sets yourself but that is easy enough using ISPF option 3,2.
After the initial deployment, most of the time you will only need to copy run time JCL from the proclib and possibly vtam list members from the vtamlst staging libraries to your actual live system data sets.
Mixing it up with C and assembler with METALC (Part 2)…
Following on from my earlier post, here’s a version of the reverse string program that is ‘closer’ (but still no cigar yet):
#include <stdio.h> #include <stdlib.h> #include <string.h> char * reverse( char * in) { int l = strlen(in); char * work; if (l>0) { work = malloc(l); // get a temp work area __asm( " PUSH USING \n" " DROP , \n" " BASR 4,0 \n" " USING *,4 \n" " LR 1,%1 LENGTH \n" " LA 2,%0 TARGET \n" " LA 3,%2 SOURCE \n" " BCTR 1,0 \n" " LA 3,0(1,3) LAST CHAR of SRC \n" " EX %1,MVCIN \n" " J PASTMOVE \n" "MVCIN MVCIN 0(0,2),0(3) \n" "PASTMOVE DS 0H \n" " POP USING \n" :"=m"(work) : "r"(l), "m"(in) :"r1", "r2","r3","r4" ); memcpy(in,work,l); // copy reversed string in work to orig free(work); // release work area } // input area return in; // return same string back to caller } int main() { char c[21]="12345"; // String to reverse printf("input=($s)",c); // print input string reverse(c); // reverse it in place printf("output=($s)",c); // print revered string return 0; }
At Least this compiles although it will not link edit because it seems there is NO printf function in Metal C!
You can see that my little one line of assembler has become somewhat longer as a result of the way you have to pass parameters into the embedded code and the fact that since the MVCIN instruction has the move length encoded in the instruction at assembly time, the only way to make it variable is to use an EXECUTE instruction. I also had to mess around with code base registers and assign this little bit of code it’s own base register so that the execute instruction has addressability to the MVCIN instruction.
At least I added some length checking code so that it did not try to do with move with a zero length which would of course result in a 256 character move and resultant storage overlay.
Still an interesting exercise though.