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: ,

Maintaining the OMEGAMON XE for CICS on z/OS Globals with PARMGEN

September 10, 2013 Leave a comment

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…

August 12, 2013 Leave a comment

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:

$GLB$USR

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

July 29, 2013 Leave a comment

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.

Categories: Coding, Mainframe