As I noted in an earlier column, WordStar Release 4 was a very excitingì
event for the CP/M world in general and the Z-System world in particular. ìèIt was the first major commercial program to recognize Z System and to makeì
use of its features. Unfortunately, the Z System code in WS4 was notì
adequately tested, and many errors, some quite serious, slipped through. ì
Some of the most significant errors concern WS4's operation as a ZCPR3ì
shell.
Let's begin with a little background on the concept of a shell in ZCPR. ì
Normally, during Z System operation the user is prompted for command lineì
input. This input may consist of a string of commands separated byì
semicolons. When the entire sequence of commands has been completed and theì
command line buffer is again empty, the user would be prompted again forì
input.
This prompting is performed by the ZCPR command processor, which,ì
because it is limited in size to 2K, is correspondingly limited in itsì
power. Richard Conn, creator of ZCPR, had the brilliant idea of including aì
facility in ZCPR3 for, in effect, replacing -- or, perhaps better said,ì
augmenting -- the command processor as a source of commands for the system. ì
This is the shell facility.
Under ZCPR3, when the command processor finds that there are no moreì
commands in the command line buffer for it to perform, before it prompts theì
user for input, it first checks a memory buffer called the shell stack. Ifì
it finds a command line there, it executes that command immediately, withoutì
prompting the user for input. The program run in that way is called aì
shell, because it is like a shell around the command processor kernel. Theì
shell is what the user sees instead of the command processor, and the shellì
will normally get commands from the user and pass them to the commandì
processor. In effect, the outward appearance of the operating system can beì
changed completely when a shell is selected.
A perfect example of a shell is the EASE history shell. To the user itì
looks rather like the command processor. But there are two very importantì
differences. First of all, the command line editing facilities are greatlyì
augmented. One can move the cursor left or right by characters, words, orì
commands; one can insert new characters or enter new characters on top ofì
existing characters; characters or words can be deleted. One has, in a way,ì
a wordprocessor at one's disposal in creating the command line.
The second feature is the ability to record and recall commands in aì
history file. Many users find that they execute the same or similarì
commands repeatedly. The history feature of EASE makes this veryì
convenient. These two command generation features require far too much codeì
to include in the command processor itself, so it is very convenient to haveì
the shell capability.
Programs designed to run as shells have to include special code toì
distinguish when they have been invoked by the user and when they have beenì
invoked by the command processor. ZCPR3 makes this information available toì
such programs. When invoked by the user, they simply write the appropriateì
command line into the shell stack so that the next time the commandì
processor is ready for new input, the shell will be called on. After that,ìèthe user sees only the shell. Shells normally have a command that the userì
can enter to turn the shell off.
ZCPR3 goes beyond having just a single shell; it has a stack of shells. ì
A typical configuration allows four shell commands in the stack. When theì
user invokes a command designed to run as a shell, it pushes its name ontoì
the stack. When the user cancels that shell, any shell that had beenì
running previously comes back into force. Only when the last shell commandì
has been cancelled (popped from the shell stack) does the user see theì
command processor again.
Let's look at some of the shells that are available under Z System. Weì
have already mentioned the EASE history shell. There is also the HSHì
history shell, which offers similar capabilities. It was written in C andì
cannot be updated to take advantage of innovations like type-3 and type-4 commands. I would say that EASE is the history shell of choice today. Thisì
is especially true because EASE can do double service as an error handler asì
well, with the identical command line editing interface.
Then there are the menu shells, programs that allow the user with justì
a few keystrokes to initiate desired command sequences. They come inì
several flavors. MENU stresses the on-screen menu of command choicesì
associated with single keystrokes. VFILER and ZFILER stress the on-screenì
display of the files on which commands will operate; the commands associatedì
with keys are not normally visible. Z/VFILER offer many internal fileì
FMANAGER are inbetween. Both the files in the directory and the menu ofì
possible commands are shown on the screen.
What Kind of Programs Should be Shells?
Not all programs should be shells. From a strict conceptual viewpoint,ì
only programs that are intended to take over the command input function fromì
the command processor on a semipermanent basis should be shells. Theì
history shells and the MENU and VMENU type shells clearly qualify. Oneì
generally enters those environments for the long haul, not just for a quickì
command or two.
ZFILER and VFILER are marginal from this viewpoint. One generallyì
enters them to perform some short-term file maintenance operations, afterì
which one exits to resume normal operations. It is rare, I believe, toì
reside inside ZFILER or VFILER for extended periods of time, though I amì
sure there are some users who do so.
Many people (I believe mistakenly) try to set up as shells any programì
from which they would like to run other tasks and automatically return. ì
This is the situation with WordStar. No one will claim that the mainì
function of WordStar is to generate command lines! Clearly it is intendedì
to be a file editor. Why, then, was it made into a ZCPR3 shell in the firstì
place? I'm really not sure.
WordStar's 'R' command really does not offer very much. In neither theìèZCPR nor the CP/M configuration does any information about the operatingì
environment seem to be retained. For example, one might expect on return toì
WordStar that the control-r function would be able to recall the mostì
recently specified file name. But this does not seem to be the case,ì
although it could easily have been done. In the ZCPR version, the nameì
could be assigned to one of the four system file names in the environmentì
descriptor; in the CP/M version it could be kept in the RSX code at the topì
of the TPA that enables WordStar to be reinvoked after a command isì
executed.
The WordStar 'R' command does not save any time, either. Essentiallyì
no part of WordStar remains in memory. The user could just as well use theì
'X' command to leave WordStar, run whatever other programs he wished, andì
then reinvoke WS. Nevertheless, I can understand why users would enjoy theì
convenience of a command like the 'R' command that automatically brings oneì
back to WordStar. Shells, however, are not the way to do this, at least notì
shells in the ZCPR3 sense.
ZCPR2-Style Shells
In ZCPR2 Richard Conn had already implemented an earlier version of theì
shell concept which, interestingly enough, would be the appropriate way forì
WordStar and perhaps even ZFILER/VFILER to operate. He did not have a shellì
stack, but he did have programs like MENU that, when they generatedì
commands, always appended their own invocation to the end of the commandì
line. Thus if the menu command script associated with the 'W' key was "WSì
fn2", where fn2 represents system file name #2, then the actual commandì
placed into the command line buffer would be "WS fn2;MENU". In this way,ì
after the user's command ran, the MENU program would come back.
Let's compare how the two shell schemes would have worked withì
WordStar. Suppose we want to edit the file MYTEXT.DOC and then copy it toì
our archive disk with the command "PPIP ARCHIVE:=MYTEXT.DOC". We might haveì
created the following alias script for such operations:
WSWORK ws $1;ppip archive:=$1
Then we just enter the command "WSWORK MYTEXT.DOC" when we want to work on theì
file and have it backed up automatically when we are done.
Here is what WS4 does as a ZCPR3-type shell. The command line starts outì
as:
WSWORK MYTEXT.DOC
When the alias WSWORK is expanded the command line becomes:
WS MYTEXT.DOC;PPIP ARCHIVE:=MYTEXT.DOC
When WordStar runs, it pushes its name onto the shell stack so that it will beì
invoked the next time the command line is empty. Noting that the command lineìèis not empty, it returns control to the command processor. Then the PPIPì
command is executed, backing up our unmodified file (horrors!!!) Finally theì
command line is empty and WS, as the current shell, starts running. Since itì
was invoked as a shell, it prompts the user to press any key before it clearsì
the screen to start editing. By this time it has forgotten all about the fileì
we designated and it presents us with the main menu. All in all, a ratherì
foolish and useless way to go about things.
You might think that the problem would be solved if WS did not check forì
pending commands but went ahead immediately with its work. Indeed, this wouldì
work fine until the 'R' command was used. Then either the pending PPIP commandì
would be lost (replaced by the command generated by the 'R' operation) orì
executed (if the 'R' command appended it to the command it generated). Inì
either case we have disaster!
Now suppose WS4 had used the ZCPR2-style shell concept. After the aliasì
had been expanded, the "WS MYTEXT.DOC" command would run, and we would edit ourì
file. While in WS4, suppose we want to find where on our disks we have filesì
with names starting with OLDTEXT. We use the 'R' command to enter the commandì
line "FF OLDTEXT". The 'R' command would append ";WS" to the end the commandì
we entered and insert it into the command line buffer before the currentì
pointer, leaving the following string in the buffer:
FF OLDTEXT;WS;PPIP ARCHIVE:=MYTEXT.DOC
After the FF command was finished, WordStar would be executed again. Just whatì
we wanted.
In fact, under ZCPR3 WS could be much cleverer than this. First of all,ì
it could determine from the external file control block the name (and under Z33ì
the directory) used to invoke WordStar in the first place. There would be noì
need, as there is now, to configure WS to know its own name and to make sureì
that the directory with WS is on the command search path. The 'R' commandì
could have appended "B4:WSNEW" if WSNEW had been its name and it had beenì
loaded from directory B4.
There is one problem, however. We would really like WS to wait beforeì
clearing the screen and obliterating the results of the FF command. With theì
ZCPR3-type shell, WS can determine from a flag in the ZCPR3 message bufferì
whether it was invoked as a shell. For the ZCPR2-style shell we would have toì
include an option on the command line. WS could, for example, recognize theì
command form "WS /S" as a signal that WS was running as a shell. It would thenì
wait for a key to be pressed before resuming, just as under a ZCPR3-styleì
shell. Of course, you would not be able to specify an edit file with the nameì
"/S" from the command line in this case, but that is not much of a sacrifice orì
restriction.
We could continue to work this way as long as we liked. Only when weì
finally exited WS with the 'X' command would the PPIP command run. This, ofì
course, is just the right way to operate!
èZCPR2 vs ZCPR3 Shell Tradeoffs
Once I started thinking about the old ZCPR2-type shells, I began to wonderì
why one would ever want a ZCPR3-type shell. At first I thought that Z2-styleì
shells could not be nested, but that does not seem to be the case. Suppose weì
run MENU and select the 'V' option to run VFILER. The command line at thatì
point would be
VFILER;MENU /S
where we have assumed that a "/S" option is used to indicate invocation as aì
shell. While in VFILER we might run a macro to crunch the file we are pointingì
to. The macro could spawn the command line "CRUNCH FN.FT". The command lineì
buffer would then contain
CRUNCH FN.FT;VFILER /S;MENU /S
After the crunch is complete, VFILER would be reentered. On exit from VFILERì
with the 'X' command, MENU would start to run. Thus nesting is not onlyì
possible with Z2-type shelling, it is not limited by a fixed number of elementsì
in the shell stack as in ZCPR3 (the standard limit is 4). Only the size of theì
command line buffer would set a limit.
What disadvantages are there to the Z2-style shell? Well, I'm afraid thatì
I cannot come up with much in the way of substantial reasons. The shell stackì
provides a very convenient place to keep status information for a program. Iì
do that in ZFILER so that it can remember option settings made with the 'O'ì
command. On the other hand, this information could be kept as additional flagsì
on the command line, as with the "/S" option flag. There is no reason why theì
information could not be stored even in binary format, except that the nullì
byte (00 hex) would have to be avoided.
If the 128 bytes currently set aside for the shell stack were added to theì
multiple command line buffer, the use of memory would be more efficient than itì
is now with Z3-style shells. Z3 shells use shell stack memory in fixed blocks;ì
with Z2 shells the space would be used only as needed. I rarely have more thanì
one shell running, which means that most of the time 96 bytes of shell stackì
space are totally wasted. Of course, with the present setup of ZCPR3, theì
multiple command line buffer cannot be longer than 255 bytes, because the sizeì
value is stored in the environment descriptor as a byte rather than as a word. ì
The command line pointer, however, is a full word, and so extension to longerì
command lines would be quite possible (I'll keep that in mind for Z35!).
Following this line of reasoning, I am coming to the conclusion that onlyì
programs like history shells and true menu shells should be implemented asì
ZCPR3-style shells. Other programs, like ZFILER and WordStar should use theì
ZCPR2 style. If I am missing some important point here, I hope that readersì
will write in to enlighten me.
Forming a Synthesis
è So long as the command line buffer is fixed at its present length and soì
long as 128 bytes are set aside as a shell stack, one should make the best ofì
the situation. Rob Wood has come up with a fascinating concept that does justì
that.
Rob was working on Steve Cohen's W (wildcard) shell. He recognized thatì
on many occasions one wants to perform a wildcarded operation followed by someì
additional commands (just as with the WordStar example followed by PPIP). As aì
ZCPR3-type shell, W could not do this. It always executed what it was supposedì
to do after the wild operation before the wild operation!
Rob came up with a brilliant way to combine the ZCPR2 and ZCPR3 shellì
concepts. When his version of W is invoked manually by the user, it pushes itsì
name, as a good ZCPR3 shell does, onto the shell stack. But it does not thenì
return to the command processor to execute commands pending in the commandì
line. It starts running immediately, doing the thing it was asked to do andì
using the shell stack entry to maintain needed data.
In the course of operation, however, it does one unusual thing. Afterì
each command that it generates and passes to the command line buffer, itì
appends its own name, as a good ZCPR2 shell does. This command serves as aì
separator between the shell-generated commands and those that were on theì
original command line after the W command. After the shell-generated commandsì
have run, W starts to run. It checks the top of the shell stack, and if itì
finds its own name there, it says "Aha, I'm a shell," and proceeds to use theì
information in the shell stack to generate the next set of commands. Thisì
process continues until W has no more work to do. Then it pops its name offì
the shell stack and returns to the command processor. The commands originallyì
included after the W command are still there and now execute exactly asì
intended. Beautiful!
WordStar Shell Bugs
It is bad enough that WordStar's conceptual implementation as a shell isì
flawed. On top of that, the shell code was not even written correctly. Theì
person who wrote the code (not MicroPro's fault, I would like to add) tried toì
take a short cut and flubbed it. When a shell installs itself, it shouldì
always -- I repeat, always -- push itself onto the stack. WordStar tries toì
take the following shortcut. If it sees that the shell stack is currentlyì
empty, it just writes its name into the first entry, leaving the other entriesì
as they were.
When WordStar terminates, however, it pops the stack. At this pointì
whatever junk was in the second shell stack entry becomes the currently runningì
shell. The coding shortcut (which I would think took extra code rather thanì
less code, but that is beside the point) assumed that if the current shellì
stack entry was null, all the others would be, too. But this need not be theì
case at all. And in many cases it has not in fact been the case, and veryì
strange behavior has been observed with WordStar. Some users have reportedì
that WordStar works on their computers only if invoked from a shell! That isì
because WordStar properly pushes itself onto the stack in that case.
è
There are basically two strategies one can take for dealing with the shellì
problems in WordStar. One is to fix the above problem and live with the otherì
anomalies (just don't ever put commands after WS in a multiple command line). ì
The other is to disable the shell feature entirely.
To fix the bug described above, Rick Charnes wrote a program calledì
SHELLINI to initialize the shell stack before using WordStar. On bulletinì
boards in the past both Rick and I presented aliases that one can use toì
disable the shell stack while WS is running and to reenable it after WS hasì
finished. I will now describe patches that can be made directly to WordStarì
itself. First I will explain what the patches do; later I will discuss how toì
install them.
Listing 2 shows a patch I call WSSHLFIX that will fix the bug justì
described. The code assumes that you do not already have any initialization orì
termination patches installed. If you do, you will have to add the routinesì
here to the ones you are already using.
The patch works as follows. When WS starts running, the initializationì
routine is called. It extracts the shell stack address from the ENV descriptorì
and goes there to see if a shell command is on the stack. If there is, noì
further action is required, since WS already works correctly in this case. If,ì
on the other hand, the first shell entry is null, then the routine calculatesì
the address of the beginning of the second shell entry and places a zero byteì
there. When this stack entry is popped later, it will be inactive.
Listing 3 shows a patch I call WSSHLOFF that will completely disable theì
shell feature of ZCPR3 while WS is running. It works as follows. When WSì
starts running, the initialization routine is called. It gets the number ofì
shell stacks defined for the user's system in the ENV descriptor and saves itì
away in the termination code for later restoration. Then it sets the value toì
0. WordStar later checks this value to see if the shell feature is enabled inì
ZCPR3. Since WordStar thinks that there is no shell facility, it operates theì
'R' command as it would under CP/M. Later, on exit from WS, the terminationì
routine restores the shell-stack-number so that normal shell operation willì
continue upon exit from WS.
The easiest way to install these patches is to assemble them to HEX filesì
and use the following MLOAD command (MLOAD is a very useful program availableì
from remote access systems such as Z Nodes):
MLOAD WS=WS.COM,WSSHLxxx
Substitute the name you use for your version of WordStar and the name of theì
patch you want to install. That's it; you're all done.
If you do not have MLOAD, you can install the patches using the patchingì
feature in WSCHANGE. From the main menu select item C (Computer), and fromì
that menu select item F (Computer Patches). From that menu, work through itemsì
C (initialization subroutine), D (un-initialization subroutine), and E (generalì
patch area), installing the appropriate bytes listed in Table 1.
è
Summary
We have covered a lot of material this time. The issue of shells is aì
very tricky one, and I hope to hear from readers with their comments. I wouldì
also enjoy learning about interesting ARUNZ aliases that you have created.