Friday, July 29, 2011

Fin

To my few and loyal readers,

I will no longer be updating this blog. I have enjoyed writing and learning all about mainframes, but both the universe and IBM decided it was time for me to move onto bigger and better things. I, like many other IBMers, have been 'resource action-ed' (ie, laid off). Rather than get another job in the industry and thus expose myself to future resource actions, I have decided to go back to school and get my phd, with a focus on the social and ethical implications of the creation of artificial intelligence.

I truly hope this blog saves you some of the pain and trouble I experienced when learning about z/OS, and please feel free to post or contact me if you have questions about z.

cheers

David

Wednesday, April 27, 2011

HOW TO GET A WORKSTATION APPLICATION TO INTERFACE WITH Z/OS

I hope this post saves you some aggravation. If you are creating an application and need to interface with z/OS via TCP/IP, there IS an API for that. The book you want is called "IP Sockets Application Programming Interface Guide and Reference" and can be found here. Note - depending on the age of this post, you may want to find a more recent version of the pub. The link takes you to communication pubs for z/OS v1r12.

Tuesday, September 28, 2010

HOW TO SERIALIZE A JAVA TABLE THAT HAS ROWS OF VARIABLE HEIGHT

Not strictly a mainframe topic, I know, but this has been such a pain in the ass to figure out that I want to save others from the nonsense I had to go through to work this out.

-=THE SITUATION=-

You have a Java table with a custom renderer that allows for the text in cells to word-wrap, thus altering the height of some of the table rows. You want to serialize this table model and you get a java error regarding a sizeSequence.


-=WHAT'S GOING ON=-

Basically, the problem is that Java is dumb. When you execute the setRowHeight method for the table in question, it should be updating the SizeSequence (the thing that keeps track of where one row ends and another begins), but it's not. Not a problem until you try to serialize the table and Java freaks out because there is a discrepancy.


-=THE FIX=-

Put this line:

[table model].fireTableDataChanged();

just prior to your attempt to serialize. It'll force Java to do what it should've done in the first place, which is update SizeSequence, and allow you to serialize your table.

Sunday, August 29, 2010

HOW TO CLEAR THE SCREEN IN TSO, CLIST, OR REXX

I did some digging and found a forum thread with the answer. It seems that you can't do it from TSO. You can, however, write a simple assembler module that will do it for you.

here is the thread with the source for the asm module


here is a link to the IBM pub that describes the STLINENO macro

Thursday, August 12, 2010

HOW TO REMOVE SYSTEM MESSAGES FROM THE CONSOLE

You can use PF1 to remove them one at a time. Does someone know how to clear them all at once?

Thursday, August 5, 2010

HOW TO COMPILE AND LINK EDIT MULTIPLE ASSEMBLER MODULES AT THE SAME TIME

This is the best way I could come up with. If someone has something better (a way to do it from within the JCL perhaps?) please let me know. We are going to create three files:

1) a text file that contains the names and locations of the modules you are going to work with

2) JCL that will compile and link edit the modules

3) a REXX exec that will read data from the environment file, edit the JCL accordingly, then submit the JCL for each member in the environment file. Essentially, you are going to submit a job for each module you want compiled, and the REXX will do that for you automatically via the parameters you specify in the environment file.

Here is the environment file sample:

/*********************************************************************/
/* ASMCLENV - ENVIRIONMENT FILE USED BY ASMCLREX */
/* */
/* 04AUG10 */
/* */
/*********************************************************************/

*1
[location of source module to be compiled]
[where to put the compiled module]
[where to put the link edited load module]

*2
[same format as above]



The REXX exec is going to look for an '*' in column one as it reads this file to tell it where the data it's looking for is. Also, make sure to include the high-level qualifier in the data set name.


Here is the JCL:


//ASMCL JOB 'COMPILE AND BIND ASSY',MSGLEVEL=(1,1),
// NOTIFY=&SYSUID,MSGCLASS=H,CLASS=1
//*
//*** HLASMCL
//*
//* THIS PROCEDURE RUNS THE HIGH LEVEL ASSEMBLER
//* AND LINK-EDITS THE NEWLY ASSEMBLED PROGRAM
//*
//*********************************************************************
//* COMPILE STEP *
//*********************************************************************
//C EXEC PGM=ASMA90,PARM=(OBJECT,NODECK)
//SYSLIB DD DSN=SYS1.MACLIB,DISP=SHR
//*
//SYSUT1 DD DSN=&&SYSUT1,SPACE=(4096,(120,120),,,ROUND),UNIT=SYSDA,
// DCB=BUFNO=1
//*
//SYSIN DD DSN= LOCATION OF SOURCE ASSY PGM **
//*
//SYSLIN DD DSN= WHERE OBJECT MODULE SHOULD BE WRITTEN **
//*
//SYSPRINT DD SYSOUT=*
//*********************************************************************
//* LINK STEP *
//*********************************************************************
//L EXEC PGM=HEWL,COND=(8,LT,C),
// PARM='NOMAP,NOLET,NOLIST,NCAL'
//*
//SYSLIN DD DSN= LOCATION OF MODULE TO BE LINKED **
//*
//SYSLMOD DD DSN= WHERE LINKED MODULE SHOULD BE WRITTEN **
//*
//SYSUT1 DD DSN=&&SYSUT1,SPACE=(1024,(120,120),,,ROUND),UNIT=SYSDA,
// DCB=BUFNO=1
//*
//SYSPRINT DD SYSOUT=*



This you shouldn't have to do anything with, just cut and paste, but be careful! JCL is very picky about operands being in the correct row/column. Make sure the lines that are continuations (the part of the statement that is continued on the next line) begins in column 16 or you'll get a big fat error!


Here is the REXX that drives the whole thing:

/* REXX */
/*********************************************************************/
/* CREATED 04AUG10 */
/* */
/* THE PURPOSE OF THIS EXEC IS TO DRIVE ASMCL (JCL TO COMPILE AND */
/* LINK EDIT AN ASSEMBLER MODULE). ASMCLREX WILL READ ASMCLENV TO */
/* GET A LIST OF ASSEMBLER SOURCE FILES TO COMPILE AND LINK. IT WILL */
/* THEN UPDATE ASMCL WITH THE DATA FROM ASMCLENV, SUBMIT THE JOB, */
/* AND REPEAT FOR EACH ENTRY IN ASMCLENV. THIS WAY WE CAN COMPILE AND*/
/* LINK MULTIPLE SOURCE FILES AT A TIME. */
/* */
/*********************************************************************/

ADDRESS TSO


/*READ ENVIRONMENT DATA***********************************************/

"ALLOC DD (INDDENV) DA('LOCATION OF ENVIRONMENT FILE') SHR REUSE"
'EXECIO * DISKR INDDENV (STEM ENVDATA. FINIS'
"FREE DD(INDDENV)"
ENVSIZE = ENVDATA.0
SYSIN = "//SYSIN DD DSN="
SYSLIN = "//SYSLIN DD DSN="
SYSLMOD = "//SYSLMOD DD DSN="
DISP = ",DISP=SHR"


/*LOOP THROUGH ENVIRONMENT DATA TO PULL OUT RELEVANT INFORMATION*****/

DO I = 1 TO ENVSIZE

/*IF ENTRY MARKER IS FOUND, UPDATE ASMCL AND SUBMIT THE JOB**********/

IF(SUBSTR(ENVDATA.I,1,1)) = '*' THEN DO
"ALLOC DD (INDDACL) DA('LOCATION OF TEMPLATE JCL') SHR REUSE"
'EXECIO * DISKR INDDACL (STEM ACLDATA. FINIS'
"FREE DD(INDDACL)"

ACLSIZE = ACLDATA.0

K = I + 1
ENVDATA.K = STRIP(ENVDATA.K)
ACLDATA.18 = SYSIN || ENVDATA.K || DISP
K = K + 1
ENVDATA.K = STRIP(ENVDATA.K)
ACLDATA.20 = SYSLIN || ENVDATA.K || DISP
ACLDATA.29 = SYSLIN || ENVDATA.K || DISP
K = K + 1
ENVDATA.K = STRIP(ENVDATA.K)
ACLDATA.31 = SYSLMOD || ENVDATA.K || DISP

"ALLOC DD (OUTDD) DA('LOCATION OF TEMPLATE JCL') SHR REUSE"

'EXECIO * DISKW OUTDD (FINIS STEM ACLDATA.'
"FREE DD(OUTDD)"

"SUBMIT 'LOCATION OF TEMPLATE JCL'"

END

END

Friday, July 30, 2010

A QUICK WAY TO CREATE A MEMBER IN AN EMPTY DATASET

Normally, if you are working with a PDS that is populated with members, you can create a new member by going to ISPF option 3.4, entering the data set name, then typing S [new member name] and the command line. Unfortunately, this doesn't work if the PDS is empty. To get around this, go to ISPF option 2 (EDIT), and enter the data set name with the name of the member you want to create like this:
OTHER PARTITIONED OR SEQUENTIAL DATA SET:
DATA SET NAME ===> 'data.set.name(newmemb)'

I found this gem and some other neat ISPF tricks here.

Tuesday, June 22, 2010

HOW TO TRANSFER AN ENTIRE PDS FROM ONE SYSTEM TO ANOTHER WHEN THEY AREN'T DIRECTLY CONNECTED

Ran into this issue the other day:

I had a need to move a PDS from one mainframe to another and the two systems were not connected. I tried a simple FTP to my workstation then up to the other system, but the PDS members became malformed. A coworker suggested I XMIT (see this post if you need help on how to XMIT) the PDS to myself, FTP the XMITed PDS to my workstation, up to the target system, then unload it there.

I know that's a bit confusing so let's break this down:

1) XMIT the PDS from the source system to a new dataset on the source system. This will package the PDS into a format that can be transported. Let's say you called it (DSX)

2) FTP the packaged PDS from the source mainframe to your workstation (your PC, MAC, LCARS, whatever)

3) FTP the packaged PDS from your workstation to the target mainframe.

4) issue a RECEIVE INDS(dsx) command on the target system.


That should do it, hope it helps :-)

Tuesday, February 16, 2010

Friday, August 28, 2009

2009 Master the Mainframe Contest!

The 2009 contest is just around the corner. If you are a high school or college kid and want to participate (no experience necessary, I promise!), click here.

Wednesday, July 22, 2009

Notes on How to Create a RACF Database

First, here is a quick list of some of the RACF utilities and what they do:

IRRMIN00 RACF database initialization utility
IRRUT400 RACF database split/merge/extend utility
IRRDBU00 RACF database unload utility
IRRUT200 RACF database verification utility
IRRUT100 RACF cross-reference utility
IRRRID00 RACF remove ID utility
IRRADU00 RACF SMF data unload utility

To create a new RACF database, you're going to use IRRMIN00. Just whip up some JCL (check out 'z/OS Security Server RACF System Programmer's Guide' if nobody in your shop has some canned JCL you can cut and paste) and the utility will create a fresh database for you to use. Note that you have to reIPL before you can use this new database, as it is completely empty. At IPL time, a user entry for IBMUSER will be added so you can log in and start populating your new database.

After you've done this, there are two commands you probably want to issue against your new database. They are:

SETR GENERIC(DATASET)

and

SETR EGN

The first activates generic profile checking (see this post for a bit more on RACF profiles) and the second activates Enhanced Generic Naming. "When you activate this option, RACF allows you to specify the generic character ** (in addition to the generic characters * and %) when you define data set profile names and entries in the global access checking table. " (Security Server RACF Command Language Reference).

Tuesday, April 7, 2009

Some Random Goodies

I've picked up a few tricks I thought I'd share:

1) In a previous post I talk about sending messages and data sets to other users. The command requires you to know the node name of the system the recipient is on. You can find out what the node name is by going into SDSF and typing NODE on the command line.

2) From within SDSF, after you've run a job and you are reading the results, if you want to edit and or reissue the same JCL, you can do so from within SDSF. When you are looking at the job output, type SJ at the command field to access your job. From there you can edit and resubmit. Here are a few screen shots of what to put where:





3) Recalling migrated data sets can be a pain in the arse, especially when you need several of them to perform a particular task. If you want to recall a bunch of stuff at once, you can do so from within the ISPF data set listing (option 3.4) by typing HRECALL on the command field then = at every subsequent data set you want recalled. The equals sign tells ISPF that you want to repeat the previous command you've entered. Here's another screen shot that illustrates what I'm talking about.

Thursday, February 19, 2009

Quick and Dirty Guide to Adding Users and Groups to the RACF Database

On occasion you may have the need to give a new user access to your system. As with anything else on the mainframe, there are about a million options but thankfully you really only need to concern yourself with a few of them. The first thing you need to understand is the concept of groups. RACF groups are a collection of users, grouped together to allow the system programmer (that's you!) an easy way to manage access lists. In other words, if you have 50 users that require access to a data set, rather than grant them access individually, you can put them in a group and give the group access to the data set. Cool, huh?

When creating a group, there are two attributes that you need to think about. They are

1) who the group owner is (can be another group)

2) whether or not this is a Unix System Services group (if it is, you may need to specify a GID)

It should be noted that it is not required that a group is created when a new user is added to the RACF database. Use the ADDGROUP command to add a RACF group. Here are a few examples from the RACF Security Administrator's Guide (ch 3, pg 59)

For example, to create a group for Department A called DEPTA whose owner and superior group is to be a group called ALLDEPT, enter:

ADDGROUP DEPTA OWNER(ALLDEPT) SUPGROUP(ALLDEPT)

To then connect users to that group, use the CONNECT command. For example, to connect department members SUE, LIZ, and GENE to the DEPTA group and also give LIZ and SUE authority to add new users to the group, enter:

CONNECT (SUE LIZ) GROUP(DEPTA) OWNER(DEPTA) AUTHORITY(CONNECT)

CONNECT GENE GROUP(DEPTA) OWNER(DEPTA)

If the group is to own group data sets create a top generic profile for the group data sets in the DATASET class. For example:

ADDSD ’DEPTA.**’ UACC(NONE)

If the group requires access to RACF-protected resources, give the group the required access using the PERMIT command. For example:

PERMIT ’RACF.PROTECT.DATA’ ID(DEPTA) ACCESS(READ)

If the group requires access to z/OS UNIX resources, alter the profile to include an OMVS segment with an z/OS UNIX group identifier (GID). For example:

ALTGROUP DEPTA OMVS(GID(100))

The next thing you need to dig is the concept of profiles. RACF is made up of profiles, and profiles are composed of segments. The base segment is composed of RACF specific stuff. Products also have segments in the profile. When you define a user to the RACF database, you also can define segments of that profile that specify what kind of access that user has to various products that are installed on the system. For example, when you define a new user to RACF, you may also want to define a TSO segment so TSO knows to use RACF (as opposed to its own UADS (User Attribute Dataset) dataset) to authenticate said user at login time. Use the ADDUSER command to add a user. Here are some things to remember when adding a new user:

1) Unless you specify a default password, the password for the new user will be the name of the group to which you add the user make sure you use a valid logon proc (IKJACCNT and ISPFPROC are good basic ones to start with)

2) You can apply the attributes SPECIAL, OPERATIONS, and AUDIT to a new user to give them access to protected system resources.
- SPECIAL does not automatically give the user access to data, but does give him/her the ability to grant him/herself permission to said data. Another way to look at the SPECIAL user is someone who has the ability to execute protected system commands.
- OPERATIONS has access to data, but not to protected system commands
- AUDIT gives the user the ability to view logs, and specify logging options

Here's more of the ADDUSER command, again from RACF Security Administrator's Guide (ch3 pg 92)

To create the user profile, you can use any of the following methods:

1) Issuing the ADDUSER command.
2) Enrolling the user through the TSO/E Information Center Facility (ICF) panels.

Here is an example of using the ADDUSER command to create a user profile. Suppose you want to create a user profile for user Steve H., a member of Department A. You want to assign the following values: STEVEH for the user ID DEPTA for the default connect group DEPTA for the owner of the STEVEH user profile R3I5VQX for the initial password Steve H. for the user’s name Steve H. does not require any of the user profile segments except TSO. The TSO segment values that you want to set to start with are 123456 for the account number and PROC01 for the logon procedure. To create a user profile with these values, enter:

ADDUSER STEVEH DFLTGRP(DEPTA) OWNER(DEPTA) NAME(’Steve H.’) PASSWORD(R315VQX) TSO(ACCTNUM(123456) PROC(PROC01))

You then want to create a top generic profile for the user in the DATASET class using the ADDSD command. For example, if the user’s user ID is STEVEH, enter:

ADDSD ’STEVEH.**’ UACC(NONE)

Well, that's about it. Note that you can use generic RACF profiles to protect more than one resource. This can be done with the use of the '*' wildcard (also known as the splat). Generic profiles saves you the trouble of having to create a unique profile for every little thing on the system. Last but not least, if you want RACF to protect all non-defined system resources, issue the command:

SETROPS PROTECTALL(FAIL)

What Does ABEND 414-04 Mean?

Ran into this one recently and I thought I'd share. Essentially, a volume can be set to READ ONLY. This is something outside the scope of any security product that might be running on the system, meaning you may have RACF permissions to a data set, but if the volume on which that data set resides is READ ONLY, you'll get the 414 abend. The solution is to get your system programmer to un-read only the volume so you can write to it.

RANDOM APF AUTH GOODNESS

From the console (or from SDSF), use this command to display a list of data sets that are currently APF authorized:

D PROG,APF

From the console (or from SDSF), to dynamically APF authorize a data set:

SETPROG APF,ADD,DSNAME=dsname,VOLUME=volser

Note that if you are using the TSO 'CALL' command to execute your compiled programs, you'll get an error if the program you're attempting to execute is APF authorized. To rectify this, you need to either execute the program via JCL, or add the module to TSO Parmlib member IKSTSOxx and re IPL.

Wednesday, February 18, 2009

How to set AC=1 when using ISPF foreground processing


When you are writing APF authorized code (see this post for more details on what APF is), you may want to link-edit the module using the handy-dandy ISPF panels (option 4.7). Unfortunately, the pubs are not as clear as they could be as to the syntax of how to set the AC = 1 option when using the panels. Well, here's a screen shot of what you need to put there and what it looks like.

Thursday, February 12, 2009

APF AUTHORIZED, SUPERVISOR STATE, AND KEY 0

Understanding how the mainframe manages authorized and non-authorized code is crucial for anyone performing system-level tasks. The concepts are simple, but understanding how they relate to one another can get dicey. This post is geared towards someone who needs to write authorized mainframe code.

The system considers a task authorized when the executing program has the following characteristics:

  • It runs in supervisor state (bit 15 of the program status word (PSW) is zero).

  • It runs with PSW key 0 to 7 (bits 8 through 11 of the PSW contain a value in the range 0 to 7).

  • All previous programs executed in the same task were APF programs.


Here are the three things you need to know about:

APF AUTHORIZED: APF stands for A.uthorized P.rogram F.acility. It allows for the system programmer (for those of you who are new to the field, in mainframe-land a system programmer is like a system-administrator) to identify data sets and programs that are allowed to perform sensitive system functions. There are two components to APF authorization. The first is link-editing a module (program) with the AC 1 option set. By using the AC 1 option, we are making the module eligible to be APF authorized. The second component is to place the module into an APF authorized library. APF-authorized programs must reside in one of the following authorized libraries (data set):

  • SYS1.LINKLIB

  • SYS1.SVCLIB

  • SYS1.LPALIB

  • Authorized libraries specified by your installation.


Now that we've got our module properly link-edited and placed in an authorized library, we can move onto PSW key and system state. Normally, the system will run in what's referred to as “problem state”. This means there is a set of instructions that are unavailable. Only when the user is in supervisor state are these privileged instructions available. APF authorized programs are permitted to put the system into supervisor state. THIS IS IMPORTANT ---> APF AUTHORIZED PROGRAMS DO NOT RUN IN SUPERVISOR STATE AUTOMATICALLY! APF AUTHORIZATION ONLY ALLOWS FOR THE SYSTEM TO BE PLACED SUPERVISOR STATE.

So now that we're in supervisor state, there's one more thing we need to think about. Every page of storage (a page is 4 kilobytes) has a key associated with it. Keys 0-7 are considered protected, and 8-15 are considered unprotected. If a page of storage is protected, module attempting to access it must be authorized. The system needs to be in supervisor state in order to change the default PSW key from 8 to one that is authorized.

So, let's review. To create a program that is capable of executing privileged instructions and accessing protected storage, it needs to be APF authorized (link edited with the AC 1 option and placed in an APF authorized library), it needs to use the MODESET macro to place the system in supervisor state, and it needs to use the SPKA instruction to change the PSW key to 0-7 (whatever is appropriate).

Well, that's about it. I hope it helps. Here's a link that provides a bit more info if you need it.

Monday, January 26, 2009

Random TSO Goodness

Lately I've been missing MS-DOS. Not because DOS was amazing, but because I know where things are and how to get things done. The PATH command is a good example. In DOS, you could use the PATH command to help DOS find programs you wanted to run. It created a list of directories to search through before it gave up and said something like BAD COMMAND OR FILENAME. You could put this command inside a file called AUTOEXEC.BAT, which was the name of a program that would get run every time the computer started. This way, you could save time as you didn't have to type out or go to the directory in which the program you wanted to execute resided.

In mainframe land, however, things are a bit more complicated.

In mainframe land, the equivalent of a BAT file (short for BATCH) is something called a CLIST. CLIST stands for Command Listing, and is a lot like a DOS batch file in that it provides a user the means to execute several commands at once. In other words, instead of issuing ten commands separately, you could make a list and all you had to do was type in the name of the list. So how do we save ourselves time like we did on our old DOS system?

There are two commands that you can issue on the mainframe that are roughly equivalent to the DOS PATH command. They are TSOLIB and ALTLIB.

TSOLIB is used for load modules, which are programs that have been compiled and link-edited. We issue this command against load modules to have it added to the STEPLIB data set. STEPLIB is a library that will be at the head of a load module search. So, when we want to run our "hello world!" program we lovingly wrote in C, we add it to the STEPLIB so the mainframe knows where to find it, thus saving us the trouble of pecking out it's location in the file system.

ALTLIB will do basically the same thing, but is used for CLISTs and uncompiled REXX programs. These are scripting languages and thus don't have load modules. By default, the mainframe will look in a dataset called SYSPROC for CLISTs. ALTLIB will add other data sets to that search, thus saving us time.

Now, it's important to remember that, like the PATH command in DOS, these changes all go away the minute you log off (or in the case of DOS, reboot the system). So, we need to find a way to have the system re-implement these changes every time we log in. We need a mainframe equivalent to AUTOEXEC.BAT.

On the logon screen , there is a field labeled COMMAND. From there you can issue any TSO command you want, including a CLIST that contains all your ALTLIB and TSOLIB statements in it.

There ya go. Hope it helps :-)

Thursday, August 21, 2008

How to search for PDS members in ISPF

I've just discovered a really simple way to find stuff on the mainframe. Let's say you are looking for a file called INDEX and you only know the high level qualifier of the dataset it's in. After you do your DLIST (ISPF option 3.4) of the HLQ (hlq.** for example), you type

member index

at the command line. The system will show you all the data sets where a file called INDEX resides. You can also use wildcards. So, for example, let's say there are lots of files you want to find, all starting with INDEX (INDEX00, INDEX01, INDEX02, etc). Then you'd type

member index*

and you'll be shown all the files that begin with the word index. Cool huh? The only thing I've noticed is that if your data set has been migrated, this won't work. You'll have to recall the data set before you do your search.

Hope you find it useful :-)

Wednesday, August 6, 2008

Master the Mainframe and GDDM

The Master the Mainframe contest is just around the corner and as I'm on the contest team this year, I was asked to come up with a coding challenge. The one I came up with last year was a simple ISPF macro challenge I ripped from the pubs (ISRBOX) and added a few bugs too. This year I wanted to do something spectacular so I dug deep and dove into IBMs Graphical Data Display Manager, or GDDM for short.

What's so cool about GDDM? Well my friends, it allows the mainframe to display graphics and interact with things like mice and light pens and all sorts of other cool peripherals. ISPF can be made to act like a simple GUI with a point and click interface. It is also the means by which OS/2 displayed it's windows and such. With the advent of the web, however, this feature became somewhat depricated and nobody, to my knowledge, really uses it anymore.

So what am I doing messing around with this thing? Well, as fun as looking at endless streams of green text can be (and believe me, it's a hoot), I figured some colorful graphics might be a nice change of pace. I decided to create an implementation of Mandelbrot Set. The man who discovered this method, BenoƮt Mandelbrot, was an IBM fellow so it seemed fitting.

So I got to coding, pestered my co-workers when I got stuck or when the math got over my head, and finally I was able to get the thing working. It should be noted that I got some help, especially with the zooming bit, from here.

So I get the stupid thing working,


it looks amazing, it zooms (sortof) at the click of a mouse, I get my ooohs and aaaahs. After all that, it can't be included in the contest because none of the TN3270 emulators on the market (save the IBM one) can handle host graphics. No host graphics means no GGDM. The contestants won't be using the IBM TN3270 program so they won't be able to see the image.

...probably should've looked into that before I spent all that time coding the thing.

Oh well. As there isn't much good information on the web about GDDM I thought I'd post the code, written in C, for the Mandelbrot Generator. In order to use GDDM, you're going to have to play with your JCL and include some libraries so this compiles/links properly.

*NOTE* There are [] around the includes so Blogger doesn't think it's some sort of tag

#include [stdio.h]
#include [string.h]
#include [admucina.h]
#include [admtstrc.h]
#include [admucinf.h]
#include [admucing.h]
#include [admucins.h]


#pragma linkage(asread,OS)
#pragma linkage(chhatt,OS)
#pragma linkage(chhead,OS)
#pragma linkage(gsuwin,OS)
#pragma linkage(gschar,OS)
#pragma linkage(gsseg,OS)
#pragma linkage(gssati,OS)
#pragma linkage(gsscls,OS)
#pragma linkage(gsenda,OS)
#pragma linkage(gsarea,OS)
#pragma linkage(gsqcho,OS)
#pragma linkage(gsqloc,OS)
#pragma linkage(gspat,OS)
#pragma linkage(gsenab,OS)
#pragma linkage(gssaga,OS)
#pragma linkage(gsmove,OS)
#pragma linkage(gscol,OS)
#pragma linkage(gsline,OS)
#pragma linkage(gsview,OS)
#pragma linkage(gslw,OS)
#pragma linkage(gsflw,OS)
#pragma linkage(gsarc,OS)
#pragma linkage(fsinit,OS)
#pragma linkage(fsterm,OS)

#define width 320
#define height 200

main ()
{

/*SETUP THE GDDM ENVIRONMENT**********************/
float xoff, yoff, oldcx, oldcy;
int temp;
float scale;
int flag;
int number;
float cx = -0.5;
float cy = 0;
int inwin;
int type;
int val;
int count;
int id_type;
int id_id;
double x,y;
double xstart,xstep,ystart,ystep;
double xend, yend;
double z,zi,newz,newzi;
double colour;
int iter,input;
long col;
int i,j,k;
int inset;
int fd;

fsinit();
flag = 0;
gsenab (1,0,1);
gsenab (1,1,1);
gsenab (2,1,1);
gsuwin(0,640,0,480);
gsms(9);

xstart = -2;
xend = 1;
ystart = -1;
yend = 1;
xoff = 0;
yoff = 0;
iter = 200;
scale = 1;
xstep = ((xend-xstart)/width) * scale;
ystep = ((yend-ystart)/height) * scale;

/*DISPLAY AND ZOOM LOOP***************************/

while (flag == 0)
{
flag = 1;

x = xstart;
y = ystart;

for (i=0; i
{

for (j=0; j
{
z = 0;
zi = 0;
inset = 1;

for (k=0; k
{
/* z^2 = (a+bi)(a+bi) = a^2 + 2abi - b^2 */
newz = (z*z)-(zi*zi) + x;
newzi = 2*z*zi + y;
z = newz;
zi = newzi;

if(((z*z)+(zi*zi)) > 4)
{
inset = 0;
colour = k;
k = iter;
}

}

if (inset)
{
gscol(-1);
}
else
{

while (colour > 7)
{
colour = colour / 8;
}

gscol(colour);
}

x += xstep;

/*DRAW YOUR FRACTAL!******************************/
gsmark (j,i);

}

y += ystep;
x = xstart;

}

/*SEND THE GDDM DATA TO THE SCREEN****************/

gsread(1,&id_type,&id_id);

if (id_type == 2)
{

scale = scale * 0.75;
flag = 0;
oldcx = cx;
oldcy = cy;

gsqloc(&inwin,&cx,&cy);


if (cx > 320)
{
cx = 320;
}

if (cy > 200)
{
cy = 200;
}

cx = (cx * xstep) - 2 + xoff;
cy = (cy * ystep) - 1 + yoff;

xoff = (xoff + (cx - oldcx));
yoff = (yoff + (cy - oldcy));

xstart = cx + (-1.5 * scale);
xend = cx + 1.5 * scale;
ystart = cy + (-1 * scale);
yend = cy + 1 * scale;

xstep = xstep * scale;
ystep = ystep * scale;

}

}

fsterm();
}

Enjoy (and good luck to this years Master the Mainframe contestants!)