MPE PROGRAMMING
by Eugene Volokh, VESOFT
Presented at 1983 HPIUG Conference, Montreal, PQ, CANADA
Published by HPIUG Journal, Apr/June 1983 (Vol.6, No.2).
Published in "Thoughts & Discourses on HP3000 Software", 1st-4th ed.
ABSTRACT
MPE, with its limited control structures and small set of commands,
may not at first seem to be a powerful system programming tool.
However, it turns out that MPE can be as powerful as (and easier to
use than) any programming language for certain system programming
tasks. This paper will try to introduce the reader, via a series of
examples, to the art of "MPE programming."
THREE EXAMPLES OF MPE PROGRAMMING IN ACTION
Recently, in my capacity as systems consultant to a large HP
installation, I encountered the following situation:
There is a large system that can operate in one of two modes --
ONLINE or BATCH. The mode in which it operates is indicated by the
presence or absence of a certain file called HOL100. If this file
exists, this means that the system is operating in an ONLINE mode;
if it does not, the system is operating in a BATCH mode.
It is desirable to print at logon time the mode in which the system
is running.
Obviously, since something is to be done at logon time, we should use
a logon UDC. The simplest solution is to have one of the form:
LOGONUDC
OPTION LOGON
RUN CHKSTAT
where CHKSTAT is a program that checks whether HOL100 exists and
prints out an appropriate message. However, this is not the best
solution. For one, I don't feel like writing a custom SPL program
every time a rather simple systems programming task comes around;
those who are not familiar with FOPEN will find this even harder to
do. Furthermore, even if I did write a custom program for this,
either the program or the source file is virtually guaranteed to get
lost. And finally, running a program is a rather long and
resource-consuming task.
But, if not a program, then what? After all, MPE does not even have a
DISPLAY command to print a message, much less a command that will
check whether a file exists and display one message if it does and
another if it doesn't.
At this point, I must make a confession: despite what I said of the
possibilities of MPE as a systems programming language, it was by no
means created to be a systems programming language. In fact, you will
find that most of the techniques that will be described are actually
methods of subverting MPE commands to do tasks that they were never
intended to do in the first place. However, they work, and that's
what counts.
Returning to the problem at hand, let us attack it one step at a time.
For one, as I said, MPE does not provide us with a DISPLAY command.
So, we'll make one!
UDCs are permitted to have a number of options. One of these options,
LIST, instructs MPE to list out the commands in the UDC as they are
executed. Furthermore, there is an MPE command called :COMMENT that
does absolutely nothing. So, what do we get when we cross an OPTION
LIST and a command that does nothing?
DISPLAY !STRING
OPTION LIST
COMMENT !STRING
When the above UDC is invoked via a command of the form 'DISPLAY
"string"', it will execute the command 'COMMENT string' (which in and
of itself will do nothing), but also list this command as it is being
executed! Thus, if we don't mind seeing 'COMMENT' on the screen, we
now have a way of displaying anything we want to on the terminal from
within a UDC.
We've licked one of our problems -- we can now display a message to
the terminal. However, this still does not solve the other problem --
determining whether a file exists or not and printing one message if
it does and another if it doesn't.
Here, we must introduce a very important MPE construct (in fact, its
only control structure) -- the :IF command. With the :IF command and
its two sidekicks, :ELSE and :ENDIF, we can, depending on the value of
a logical expression, execute one of two sets of commands. Thus, our
task can be expressed as follows:
LOGONUDC
OPTION LOGON
Check if HOL100 exists
IF it exists THEN
DISPLAY "USING THE ONLINE SYSTEM"
ELSE
DISPLAY "USING THE BATCH SYSTEM"
ENDIF
However, even before you start to furiously leaf through your MPE
commands manual, you will probably begin to suspect that neither
'Check if HOL100 exists' nor 'it exists' is valid MPE syntax. In
fact, there is no check-if-a-file-exists command in MPE. Or is there?
Well, if there is no command that will explicitly check whether a file
exists, we ought to look for a command that, as a side effect, yields
different results depending on whether a file exists or not.
Furthermore, we would be able to differentiate these results using an
:IF command.
Let us consider the :LISTF command. If we do a ':LISTF filename', the
filename will be listed if the file exists, and a CI error 907 will be
generated if it does not. Since we want as little output to the
terminal as possible, we actually want to do a ':LISTF filename;
$NULL', which will do nothing if the file exists, and print a CI error
907 if it does not. Furthermore, it turns out that the value of the
last CI error is stored in a JCW (Job Control Word) called CIERROR,
which can be interrogated via the :IF command. Thus, instead of
'Check if HOL100 exists' we should say ':LISTF HOL100; $NULL' and
instead of 'it exists' we should say 'CIERROR<>907.' Thus, the
solution to our problem is:
LOGONUDC
OPTION LOGON
SETJCW CIERROR=0
CONTINUE
LISTF HOL100;$NULL
IF CIERROR<>907 THEN
DISPLAY "USING THE ONLINE SYSTEM"
ELSE
DISPLAY "USING THE BATCH SYSTEM"
ENDIF
A few comments: 'SETJCW CIERROR=0' makes sure that CIERROR is cleared
before the :LISTF command. Since this is an OPTION LOGON UDC, it is
guaranteed to be zero anyway, but in general it is conceivable that it
was already 907 before the :LISTF command. More importantly, a
:CONTINUE command was added before the :LISTF command to avoid the UDC
aborting on the first error; a :CONTINUE (either in a UDC or a job
stream) instructs MPE not to abort if the next command fails.
One other point: in addition to the appropriate message, this method
leaves some junk on the screen, namely the LISTF command and the error
message if the file does not exist (and thus the LISTF command failed)
and in either case a 'COMMENT' from the DISPLAY UDC. This is actually
rather easy to take care of -- merely embed in the DISPLAY string some
escape sequences to move the cursor and delete the unwanted lines and
characters on the screen. If you're using printing terminals, though,
you're out of luck.
Thus, we have seen how using MPE alone we can perform some fairly
complex tasks easily and efficiently.
So, from this, we can derive a sort of MPE programming methodology:
1. If you see no direct way of performing a given task, try to find a
way that yields the desired effect as a side effect, with little
or no other direct effects or side effects.
2. If you wish to do two different things depending on some condition
that cannot be straightforwardly expressed with JCWs, try to find
a command or sequence of commands that yields two different JCW
values depending on the condition.
Let us take another example:
One of VESOFT's products, MPEX, is an extended MPE user interface
that provides many desirable features, and is often 'lived in' by
its users -- they run it once when they sign on and stay in it until
they are done, at which time they exit it and immediately sign off.
Some of our users decided to set up an option logon UDC of the form
MPEX
OPTION LOGON
RUN MPEX.PUB.VESOFT
BYE
This way, they would be automatically dropped into MPEX when they
sign on, and would automatically be :BYEd off when they exit it.
However, they do not want this to be done for jobs, but rather only
for sessions. Thus, the task is to determine within a UDC whether
the user is in a job or a session.
In my opinion, in addition to the already existing system-defined JCWs
such as JCW and CIERROR, HP should have provided us with JCWs such as
MODE (to indicate whether we are a session or a job), FSERROR, etc.
However, the fact remains that it did not, and we have to determine
this for ourselves.
Let us apply our rule #2 -- is there a command that yields somewhat
different results for job mode and session mode? In fact, there is.
The :RESUME command, when executed from within session mode (but not
from break mode, since the UDC will never be executed from within
break mode) yields a CIWARN 1686 (COMMAND ONLY ALLOWED IN BREAK);
however, when executed from within job mode, it issues a CIERR 978
(COMMAND NOT ALLOWED IN JOB MODE). Furthermore, since this is an
OPTION LOGON UDC and will therefore never be executed from break mode,
the RESUME command has no other effects! Thus our solution would be:
MPEX
OPTION LOGON
SETJCW CIERROR=0
CONTINUE
RESUME
IF CIERROR<>978 THEN
RUN MPEX.PUB.VESOFT
BYE
ENDIF
As an additional nicety, we may wish to do something like a 'DISPLAY
"PLEASE IGNORE THE FOLLOWING MESSAGE"' before the RESUME command so
that the user will not be puzzled by the warning that the RESUME
command issues in session mode.
So, score another point for UDC programming.
To round out this section, consider one more example:
Before performing a given task, we wish to find out whether a given
file is in use or not. If it is not in use, we should perform the
task; if it is in use, we should print an error message.
Solving this problem requires a substantial amount of knowledge of the
file system. What we really want to do is to try to open the file
with EXCLUSIVE, INPUT access; if the open succeeds, we want to close
the file with SAVE disposition; if it fails, we want to set a flag.
However, we cannot explicitly open and close files in MPE. Rather, we
have to find a command to subvert so that it would do this task for
us. This command's operation should be essentially similar to our
target operation (i.e. it should do an open followed by a close). One
command that pops to mind is the :PURGE command. Unfortunately, it
opens a file with OUT access and closes it with DEL disposition.
But, via the :FILE command, we can force it to open and close the file
with whatever options we please! Thus, our task may be achieved by
doing the following:
FILE F=filename;EXC;ACC=IN;SAVE
SETJCW CIERROR=0
CONTINUE
PURGE *F
IF CIERROR=384 THEN
DISPLAY "ERROR: FILE IS IN USE"
ELSE
the file is not in use; do whatever is necessary
ENDIF
RESET F
Note that any open failure (except 'nonexistent file') during a :PURGE
command causes a CIERR 384; furthermore, the last file system error is
not accessible as a JCW, so we have to assume that no other open
failure will occur.
ADVANCED MPE PROGRAMMING
Consider the following problem:
VESOFT distributes its products on a tape along with an installation
job stream. When a user wishes to install the products, he
:RESTOREs the job stream and streams it. The job stream creates the
appropriate accounting structure, and then :RESTOREs all the
relevant files off the installation tape. However, it is possible
that some files cannot be restored; in this case, we want to send an
appropriate message to the console.
The obvious thing to do here would be to check CIERROR to see if
:RESTORE failed, and if so, do a :TELLOP. But, :RESTORE does NOT set
CIERROR if all files were not restored! It merely prints the
filenames and the count of the files that were not restored to its
list file, and terminates just like all files were restored.
We have run into a problem that we can't really solve with the
techniques outlined above because no MPE command can examine the
contents of a file for us. However, there is one HP utility that is
made explicitly for examining the contents of files -- EDITOR!
Our plan of attack will be as follows: we will redirect the listing of
the :STORE command to a disc file (by setting a file equation for
SYSLIST), massage it with EDITOR, somehow cause EDITOR to set a JCW
depending on the number of files not stored, and then, when we're back
in MPE, examine that JCW.
So, our "program" will look like this:
:FILE SYSLIST,NEW;DEV=DISC;REC=-80,16,F,ASCII;NOCCTL;TEMP
:RESTORE *VESOFT; @.@.VESOFT, @.@.SECURITY; SHOW; OLDDATE
:RESET SYSLIST
:SETJCW FILESNOTRESTORED=0
:EDITOR
TEXT SYSLIST
LIST ALL
CHANGE "FILES NOT RESTORED",":SETJCW FILESNOTRESTORED",ALL
DELETEQ 1/*-1,*+1/LAST
KEEP $NEWPASS,UNNUMBERED
USE $OLDPASS
EXIT
:IF FILESNOTRESTORED<>0 THEN
: TELLOP SOME FILES NOT RESTORED, CHECK SPOOL FILE!
:ENDIF
What does this mess do? Well, the first three lines do a :RESTORE,
redirecting the listing to a disc file. Then, we enter EDITOR and
text in the list file. Now, we have to make EDITOR set a JCW
depending on the number of files not restored. The way that we do
this is by changing the 'FILES NOT RESTORED = xxx' line to ':SETJCW
FILESNOTRESTORED = xxx' with the CHANGE statement, deleting all the
other lines in the file, keeping this as a temporary file, and USEing
this file! The USE command will read the file and execute the :SETJCW
command that we put in it; now, when we exit EDITOR, the
FILESNOTRESTORED JCW is equal to the number of files not restored, and
can now be interrogated.
This kind of trick is a very valuable one, and should be added to our
methodology:
3. If the parameters of an MPE command (in this case :SETJCW) depend
upon the result of another MPE command (in this case :STORE),
redirect the listing of the latter into a disc file, and use
EDITOR to create and execute the former. Similarly, if the input
of a program depends on the result of another program or command,
redirect the listing of the latter into a disc file, and use
EDITOR to create the input file for the former.
This point is best explained by another example:
VINIT, an HP utility, has a '>PDTRACK LDEV' command, which prints
the addresses of all the defective disc tracks on the disc device
indicated by LDEV. However, VINIT has no '>PDTRACK ALL' command.
Implement it.
Applying our methodology, our strategy should be:
A. Find a command that lists all the disc devices in the system, and
redirect its output to a disc file.
B. Using :EDITOR convert this output into input for VINIT.
C. Run VINIT using this newly-generated input file.
For step A, one command that seems to fit the bill is :DSTAT ALL. This
little-known command produces output of the form:
LDEV-TYPE STATUS VOLUME (VOLUME SET-GEN)
----------- --------- -------------------------
1-7925 SYSTEM MH7925U0
2-7925 SYSTEM MH7925U1
3-7925 SYSTEM MH7925U2
As you see, this command displays, among other things, the logical
device numbers of all the discs in the system. However, one problem
comes up immediately: unlike the :STORE command, whose output can
easily be redirected to a disc file, :DSTAT ALL's output always goes
to $STDLIST.
So, how are we going to redirect the output of a command that can only
send its output to $STDLIST? The answer is simple: redirect $STDLIST!
Although we cannot redirect the $STDLIST of a job or of a command, we
can redirect the $STDLIST of a program. So, all we need to do is to
issue the following commands:
:FILE LISTFILE,NEW; REC=-80,,F,ASCII; NOCCTL; TEMP
:RUN FCOPY.PUB.SYS; STDLIST=*LISTFILE
:DSTAT ALL
EXIT
:RESET LISTFILE
What we do is run FCOPY with its $STDLIST redirected to a disc file,
and cause it to do a :DSTAT ALL. :DSTAT ALL will obediently print its
output to $STDLIST, which has been redirected!
So, we have the :DSTAT ALL listing (along with some other stuff
printed by FCOPY) in a temporary disc file called LISTFILE. Now, it is
time for step B -- converting this :DSTAT ALL list file to a VINIT
input file:
:FILE INFILE;TEMP
:EDITOR
TEXT LISTFILE
DELETE 1/6,LAST << delete the various headers >>
FIND FIRST
WHILE
FIND "-" <<delete all after the '-',>>
DELETE *(*)/*(LAST) <<leaving only the LDEV>>
CHANGE 1,"PDTRACK",ALL <<insert PDTRACKs before LDEVs>>
ADD <<add an EXIT command>>
EXIT
//
KEEP *INFILE
EXIT
We now have the VINIT input file; all we need to do is
:FILE INFILE,OLDTEMP
:RUN PVINIT.PUB.SYS;STDIN=*INFILE
and we're done!
We finish off this section with one more example:
VESOFT's installation stream signs on as MANAGER.SYS, builds the
VESOFT and SECURITY accounts and streams two jobs, which sign on as
MANAGER.VESOFT and MANAGER.SECURITY and build the VESOFT and
SECURITY accounts. It is also the duty of the MANAGER.SYS job
stream to restore the VESOFT and SECURITY files; however, it can not
do this until the other two jobs finish. How can we make the
MANAGER.SYS job stream wait for the others to terminate?
The key word in this problem is 'wait.' Again, on the surface it
seems that MPE has no command that permits one to wait for a certain
event to occur. Again, however, a trick exists that saves the day.
This trick uses MESSAGE FILES.
Message files are a kind of file (introduced in MPE IV) that have the
property that if a reader tries to read an empty message file, he does
not get an immediate end of file, but rather suspends until the
message file is no longer empty.
So, even before the two job streams are streamed, we build two message
files in PUB.SYS: MSGVESOF and MSGSECUR. Furthermore, in each of the
two internally streamed job streams, we write a record (via FCOPY) to
the appropriate message file after we are all done. And, in the main
(MANAGER.SYS) job stream, right after we stream the two other job
streams but before we do the :RESTORE, we read the two message files
(again, via FCOPY). The resultant job stream goes like this:
!JOB MANAGER.SYS
!NEWACCT VESOFT
!NEWACCT SECURITY
!BUILD MSGVESOF
!RELEASE MSGVESOF <<so the job stream can write to it>>
!BUILD MSGSECUR
!RELEASE MSGSECUR
!STREAM ,# <<stream the two other job streams>>
#JOB MANAGER.VESOFT
...
#FCOPY FROM;TO=MSGVESOF.PUB.SYS
VESOFT ACCOUNTING STRUCTURE BUILT! <<any message will do>>
#EOJ
#JOB MANAGER.SECURITY
...
#FCOPY FROM;TO=MSGSECUR.PUB.SYS
SECURITY ACCOUNTING STRUCTURE BUILT!
#EOJ
!FCOPY FROM=MSGVESOF;TO <<wait for the VESOFT stream>>
!FCOPY FROM=MSGSECUR;TO <<wait for the SECURITY stream>>
!RESTORE ...
!EOJ
The message file reads cause the job stream to suspend until the
message files are no longer empty, i.e. until the other job streams
have written something to them. Thus, when the :RESTORE is executed,
we are assured that the VESOFT and SECURITY accounting structures have
been built.
CONCLUSION
I have presented some examples and some guidelines that should give
the reader an idea of what MPE programming can do and how it can do
it. It is my belief that with this knowledge and some ingenuity, the
reader can use the art of MPE programming to his advantage.
As another example of MPE programming see our stream LISTAUG:
:JOB LISTAUG,MANAGER.SYS; OUTCLASS=,1; HIPRI
:COMMENT **************************************************
:COMMENT: --LIST OF USERS & GROUPS WITHIN EACH ACCOUNT--
:COMMENT: by Eugene Volokh
:COMMENT: VESOFT, Inc.
:COMMENT: 1135 S. Beverly Dr.
:COMMENT: Los Angeles, CA, 90035-1119 USA
:COMMENT: tel 310-282-0420; fax 310-785-9566
:COMMENT **************************************************
:
:TELLOP DO A :HEADOFF 6, PLEASE...
:FILE LISTAUG;DEV=LP,11,1
:FILE NEWPASS=$NEWPASS;NOCCTL;REC=-80,16,F,ASCII
:REPORT NOGROUP.@,*NEWPASS
:EDITOR
SET QUIET
TEXT $OLDPASS
DELETEQ 1/2
CHANGE 14/80 TO "" IN ALL
FINDQ FIRST
WHILE FLAG
BEGIN
CHANGE 1 TO "LISTACCT "
HOLD *
ADD *,HOLD,NOW
CHANGE "LISTACCT " TO "LISTGROUP @."
ADD *,HOLD,NOW
CHANGE "LISTACCT " TO "LISTUSER @."
FIND *+1
END
CHANGE 40 TO ",*LISTAUG" IN ALL
ADD
EXIT
//
:FILE STDIN;TEMP
KEEP *STDIN
EXIT
:REPORT NOGROUP.@,*LISTAUG
:FILE STDIN,OLDTEMP
:RUN LISTDIR2.PUB.SYS;STDIN=*STDIN
:TELLOP DO A :HEADON 6, PLEASE...
:EOJ
Go to Adager's index of technical papers