Archive

Archive for the ‘Mainframe’ Category

Backup a bit…

April 11, 2020 Leave a comment

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

 

 

 

 

 

Categories: Mainframe

Conditional Assembly Language…

May 10, 2016 Leave a comment

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.

Categories: Coding, Mainframe

Update to the Enhanced 3270 User Interface Security Requirements

December 17, 2013 Leave a comment

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…?

December 3, 2013 Leave a comment

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

October 16, 2013 2 comments

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.

Categories: E3270UI, ITM, Mainframe Tags: ,

Using the SDSF REXX interface – Part 2

October 12, 2013 Leave a comment

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
Categories: Mainframe, REXX Tags: ,

Using the SDSF REXX interface – Part 1

September 24, 2013 Leave a comment

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
Categories: Mainframe, REXX Tags: ,