Some Interesting and Useful Routines for RT-11 and TSX-Plus Bob Schor Eye and Ear Institute 203 Lothrop St. Pittsburgh, PA 15213 (412) 647-2116 Over the past ten years, I have developed a series of useful support routines and programs which help me manage and program in an RT-11 / TSX-Plus environment. These routines fall into several catagories -- programming aids, RT-11 and TSX system tools, system utilities, and "useful" programs. They will be presented, according to this arbitrary grouping, below. Programming Aids For about ten years, I have been doing the bulk of my program development using the language Pascal (the Oregon Software Pascal-2 implementation, which supports the full ISO standard) -- I have found that the strong type checking of this language, along with run-time range tests and other error routines, have gone a long way to helping me generate "correct" code. However, occasionally one needs to do some coding in Macro-11, which has little in the way of "good programming practices" (as outlined so well by Milt Campbell at the Spring '89 DECUS Symposium) to aid in the generation of correct code. I have developed a set of macros, embedded in the macro library SUMAC.MLB, to provide Structured User MACros (hence, the name). These macros, in effect, define a "new" macro language, one with IF/ELSE/ENDIF, REPEAT/UNTIL, WHILE/ENDWHILE, PROCED/ENDPROC, and other block and structuring macro definitions. All of the macro routines I am submitting are written using SUMAC. Having mentioned Milt's talk on programming practices, I must confess that using a macro library such as SUMAC directly violates one of his principles, to keep things simple, with few defined macros, to avoid having to learn new "macro languages". My counter-argument is that (a) these macros have been extensively tested (by me and my colleagues) for many years, with few bugs surfacing, (b) their use is relatively intuitive, and (c) they make writing "structured" Macro code (that is, code following the principles of "structured programming") easier. The use of the macro package is documented in SUMAC.DOC. The actual macro library is contained in SUMAC.MLB, which is the only file that needs to reside on disk. The actual macro, for those who want to poke into the internals, is in SUMAC.MAC. Note that the most recent change in the - 1 - package arose after hearing Milt extol the virtues of psects. In the Macro programs I am submitting which utilize SUMAC, all include a standardized header, with the following lines: ;define our .psects before calling SUMAC to force ours low .psect $code ro,i,lcl,rel,con .psect $data rw,d,lcl,rel,con ;uses SUMAC for structured macro code .library /lib:sumac/ ;use external definition file .mcall SUMAC SUMAC ;initialization macro Note the use of the logical device LIB: in the .library statement. This is ordinarily assigned to SY: in the start-up files (see STARTS.COM), but can be redefined by the user to test out new versions of SUMAC, without disturbing a common resource file. TSX/RT-11 Start up, initialization routines When RT-11 starts, it looks for an initialization file, START%.COM. I have added several features to this file to help maintain the system date and time, ease transitions with TSX, perform needed assignments, and initialize certain devices (such as VM:). The file SETDAT.IND is a command file which handles both date-setting and automatic restarting of TSX. First, if no date is present, a date is obtained, either by executing a file SY:NOW.TSX (if it exists), or by asking the user to enter the date on the system console. Then a file SY:GOTSX.COM is built and executed, which starts TSX if the file SY:NOW.TSX is present. The logic behind SETDAT.IND is to allow a user to halt TSX and automatically cause it to restart, without needing access to the system console, merely by having a file NOW.TSX present on SY:. The TSX shut-down routine HALT automatically creates NOW.TSX when the user requests automatic restarting of TSX, for example (see below). Another common task to run at start-up time is initialization of certain devices, most commonly VM:, the "memory disk". This start-up initialization is special, however -- you only want to initialize devices which have not yet been initialized, to avoid losing files already present on the device. The IND file, INIT.IND, when called with a device name, attempts to perform a directory listing, and if it fails (implying that no legal directory exists), will perform an initialization. This has an esthetic flaw, in that an RT-11 error message is produced (and should be - 2 - ignored) -- one could replace this simple method with actually reading the file and deciding if the directory were present, but I took the easy way out. The file INITVM.COM simply calls this routine, specifying device VM:. The TSX start-up routine is contained in DETACH.TSX. Unlike STARTS.COM, it assumes that date and time are correctly entered, and leaves common assignments to other routines automatically invoked for each user when LOGIN runs. It does run INITVM, however, to initialize the common VM: once, when the system starts up. In my system, before exiting from DETACH.TSX, I detach three more jobs. One, WATCH.TSX (described below), is a watch-dog timer, which monitors all my time-sharing lines checking for inactivity, and logs off inactive lines (in case the user has walked away without logging off). Another routine, INIMOD (also described below), contains code to initialize my Hayes-compatible modem for later use. The final activity of DETACH.TSX is to start a routine, ONLINE, which is a modification of a routine published several years ago in the TSX newsletter. This routine sends a message (Line X -- TSX is now running) to each time-sharing line in the system, thus notifying users that they can attempt to log on to the system. In certain systems and configurations (usually when two TSX systems are directly connected to each other via a serial line, one of which gets "grabbed" by CL), ONLINE can hang (I suspect both systems are trying to "talk", and neither is "listening" to the "TSX is running" message). Despite efforts in ONLINE to reset CL, clear buffers, etc., I'm not sure I've eliminated the problem, so I've written another routine, SUICID, which I detach (via OFFLIN) to kill the ONLINE job after 30 seconds. SUICID is a routine which takes two parameters -- the name of a program, and a time (specified as NNs XXm YYh, for seconds, minutes, and hours, i.e. 1m 30s means one-and-a-half minutes). SUICID "sleeps" for the requested length of time, then kills all copies of the named program associated with your time-sharing line or its sub-processes. Note that if you are on your primary line, SUICID KMON (no time parameter means "do it now") will kill all your idle sub-processes. HALT, TSX Shut-down routine There are times when it is necessary to stop TSX: either to return to RT-11 (in order, for example, to handle maximum rates of real-time interrupts) or to re-start TSX (in those rare instances when something bombs, or gets "stuck"). Issuing $SHUTDOWN or $STOP can interfere drastically with other time-sharing activity; ordinarily, the system provides protection from arbitrary use of these commands by requiring that their issuer have "system privileges". I have written a routine, HALT, which provides further protection by instituting an orderly shut-down procedure, with built-in safeguards for active users. - 3 - HALT is normally run by logging in to a special privileged accounted, named RT11, whose only task is to run HALT and log off (see RT11.COM, the account's start-up file). Thus, any user can stop the system, without needing privilege, merely by logging on as RT11. When HALT runs, it first examines all active lines and sub-processes, killing all idle lines (those running KMON), except for idle primary lines supporting active sub-processes. After informing its user of the current system status (i.e. who is still running), it inquires whether a shut-down is really requested. If so, it issues a $SHUTDOWN, to prevent further logons. If users are still active on the system, it requests a timer (from 2 to 15 minutes) be set, and starts broadcasting periodic messages ("TSX will shut down in X minutes") to active users. Once all users log off, or the timer runs out, HALT issues a $STOP, and exits, causing TSX to stop. Starting with TSX Version 6.4, HALT also monitors the spooler queue, and waits for the queue to empty. In addition to stopping TSX, HALT also asks if the user desires to automatically restart TSX when RT-11 boots up. An affirmative answer causes the file SY:NOW.TSX to be created, whose presence is recognized by SETDAT.IND (invoked by STARTS.COM), causing RT-11 to automatically start TSX (see TSX/RT-11 start-up, above). This is useful when you do not have convenient access to the console terminal, quite common in a distributed time-sharing environment. TSX Watch-dog Once I had written HALT, I realized I had most of the code needed to provide a monitoring program to log off users who walk away from their terminals. WATCH periodically monitors all active lines, and keeps a running account of the CPU time. If the CPU time does not increase between watches, the line is assumed to have been idle, and "idle time" is accumulated. Once sufficient time has built up, the line is a candidate for being killed. Note, however, that WATCH will not kill an idle primary line which supports an active sub-process -- as long as any sub-process remains active, the primary line is left alone. The program, at present, treats KED as "special" -- in the "kill" procedure, if the idle program's name is KED, the user is spared (I assume no sane person walks away in the middle of an edit, unless it is urgent). One could easily add other "sparing" conditions, if desired, using code similar to that shown for KED. When WATCH starts, it expects a time parameter, in the nnH xxM yyS format, to tell it how much idle time to allow. During TSX start-up (see above), WATCH.TSX is detached, which currently runs WATCH with an idle time of 3 hours (we're pretty permissive). - 4 - Modem Initialization My TSX systems have a line connected to a Hayes-compatible modem (actually, a MultiTech Multi-Modem 224EH, a 2400-baud MNP Class 5 model). When TSX starts (see above), INIMOD is detached, which runs INIMOD.IND. This routine takes over a communication line, connects it to the modem's time-sharing line, and sends a series of escape sequences to ensure that the modem is properly initialized for accepting incoming and sending out-going traffic. Two auxiliary routines are used by INIMOD (and by PHONE, a routine I use to do my dialing, and then start VTCOM, KERMIT, or the TSX "set host" mode). One, XLBUSY, attempts to allocate XL (to which, for VTCOM compatibility, the CL line of the modem is assigned). If it cannot, it sets appropriate condition codes, which INIMOD interprets as meaning that XL is busy with another user. The other routine, SENDXL, is used to echo its arguments (along with control characters in the form <15><12>) to XL. TSX Internal Mail routine After several years of using TSX, my colleagues asked "Why can't we send messages to each other (other than via SEND,n), some kind of electronic mail?". Seemed like a good question, so I set about designing an electronic mail system for internal TSX usage. I wanted to have a system which guaranteed mail "privacy" (i.e. only the recipient could read the mail, yet anyone could write, or send, mail), yet be easy and flexible to use. I decided to enforce privacy by embedding all of the "mail" in a single file, SY:MAIL.TSX, which, since it had the .TSX extension, could not be mounted by regular users. I could then install the mail program(s) with sufficient privilege ("install/add sy:mail.com/priv=bypass") that it could mount the mail file and read/write inside it. MAIL uses three types of files, all of which contain ASCII text. To facilitate the addressing of mail, a file MAIL.ADR should be created by the system manager containing names and aliases of system users, along with the unique PPN identification used by TSX (this is conveniently maintained at the same time one adds new users to the system with TSAUTH. MAIL knows to ignore internal tabs in this file, which allows the system manager to format the file. Thus a typical file would have entries like Master 1 1 Chief 1 2 Bob Schor 200 1 Bob 200 1 BS 200 1 Schor 200 1 Note the use of multiple aliases. As presently constituted, if a name is not found, then MAIL can provide a list of legal users, along with their aliases. - 5 - MAIL keeps its messages in files called MAIL.001, MAIL.002, etc., reusing numbers and "rolling over" so that 000 follows 999 (thus, you cannot have more than 1000 messages still "active" within the system, as presently constituted). Each message resides within a single file, regardless of the number of users to whom it is sent (thus, if you send a message to everyone, by specifying "*" as the recipient), only a single copy is kept by the mail system. The file MAIL.BOX keeps track of sender, recipient, and mail status for each message. It consists of four fields: the PPN of the intended recipient, the PPN of the sender, the number of the message (i.e. the message file extension), and the message status, typically "N" for new, unread mail, "O" for mail read, but not yet removed from the system. The easiest way to activate MAIL is to include the command "MAIL" in your account TSX start-up file. When MAIL is activated, it informs you how many new and old messages you have, and then provides you with a set of options: read messages (this option only appears if you have mail to read), send mail, check the messages you've sent which are still in the system (used if you send mail by mistake, allows you to identify which message is yours), unmail (allows you to retract mail you've sent), and quit. If you choose to send mail, you will be asked to whom to send it. You can specify one person (or alias) or multiple people (separated by commas "," or ampersands "&"). Spaces (as in "Bob Schor") are assumed to be part of the name. Wild card characters ("*" and "%") have their usual meaning, and are useful in sending messages to multiple people. In addition, any name can carry a switch, of which the only one implemented at present is "/Query" (which can be abbreviated as "/q" -- only the first letter, case-insensitive, is tested), which has the expected effect of echoing the matched name and asking Yes or No (default). The main program, MAIL, is written in Pascal, using Oregon Software's Pascal-2 compiler. Care has been taken to use "vanilla Pascal", so porting the code to other compilers should be fairly simple. The program requires three external routines, written in Macro (using Oregon Software's Pascal Macro package, PASMAC, as well as my SUMAC routines). CTRLC is used to inhibit ^C during execution of the program, as it is necessary to ensure the program exits properly to maintain the integrity of MAIL.BOX, its "directory". FILSTA is used to obtain file information, most notably the date and time of mail file creation, so the recipient can know when mail was sent to him (without requiring time-stamping within MAIL.BOX, which is another possibility). To ensure that only one person is updating the mail boxes at a time, FLOCK is used to lock the file MAIL.BOX to the current user -- if this fails, because someone else is using MAIL, the program will exit with a warning message. MAIL is invoked from MAIL.COM, which is installed with /bypass privilege to allow it to access SY:MAIL.TSX. It consists of the following: mount ld6 sy:mail.tsx box flush r mail set ld6 free - 6 - In my system, most users use low number LD units for typical work, so I've used LD6 for mail. MAIL uses the logical device BOX:, so the mount command makes the assignment while mounting SY:MAIL.TSX. The routine FLUSH is run to empty the user's keyboard buffer, before running the MAIL program. I added this because I have a habit of typing multiple carriage returns while my system is being started, and due to the fact that TSX "remembers" these characters, I can fly right past MAIL, doing who-knows-what to the system. With FLUSH in place, type-ahead gets "eaten-up", forcing the user to attend to the questions asked by MAIL. Clearly, FLUSH can be used in other situations where user input must be synchronized with the start of a program. Miscellaneous System and User Utilities CLEAR is designed to write information (default -- all nulls) onto all unused blocks on a device. There are at least two situations where this is helpful. One is data security -- simply deleting a file (partially) removes its directory entry, leaving the data on the device until it is overwritten; CLEAR will explicitly perform such overwriting. The other situation is to aid in managing devices, particularly if you need to perform data recovery (due to a damaged directory) -- if you know most unused areas of your device have blocks which are all nulls, then you need only concern yourself with blocks having something else as their contents (see SNOOP, below). When CLEAR is invoked, it asks what device you wish cleared, and what pattern you wish (choices are all nulls, all "e" characters, as though the device was just formatted, or the current date, allowing you to tell when you ran the program. The program basically creates temporary files in each free space, writes the pattern in them, then deletes its files. MEMMAP is designed to provide a snap-shot of the i/o page, telling you which addresses (or range of addresses) exist up there. This can help resolve peculiar address conflicts when adding a new piece of hardware ("Oh, I forgot that the clock lived at that address"), or locating a dead board ("Oops, what happened to the controller at 167030?"). It can be run from either RT-11 or TSX (it checks to see which system is running) -- if running on TSX, it must be run with MEMMAP privilege (either by "R MEMMAP/IOPAGE" or from a suitably-privileged account). SHOWDU is a simple program to show you how the DU handler is currently set, i.e. to what unit, port, and partition corresponds to each DU device. SNOOP is designed to help recover from disasters which involve loss of directory. It looks block-by-block at each device, attempting to identify files of various types. At present, it can be told to look for sub-device (.DSK) files, probable text files (containing only ASCII text), .SAV files, and .SYS files. It does best with .DSK files -- if your device consists entirely of .DSK sub-device files, you can wipe out the device directory, and SNOOP will tell you where each .DSK files starts, its size, and its device name and owner, making it a trivial exercise to re-CREATE them. - 7 - VERIFY (VERIFY.COM, VERIF1.SAV, and VERIF2.SAV) perform whole-device file verifications. VERIF1 basically builds a sorted directory of two user-specified devices, then hands control over to VERIF2, which produces a list of files unique to each device, and for those files common to both devices, shows which are identical and which differ. This is handy for file maintenance, for example, to allow you to see "what's new" when you get updates of programs, or to permit you to see what's changed since you did your last back-up onto your scratch disk. I refuse to pay inflated prices for routines I can write myself. When I was asked to purchase a set of RT/TSX utilities at an inflated price, I looked and saw what routines I didn't already have, and which ones I needed. I really didn't need TYPBAK, a routine to type out a file backwards (this was described as useful if you want to see information at the end of a long listing), but thought it'd be fun to try coding such a routine. Enjoy. Date/Calendar Routines One of the earliest utilities I wrote for myself were a set of routines to help me remember things, both events I had to do at specific times, and annual events (like birthdays and anniversaries). I wrote REMIND (and its support routine, JULIAN) to look at two text files, REMIND.REM and ANNUAL.REM, which consist of a date (in DD-MMM-YY format), , and a line of text. The information in REMIND.REM is considered to be one-time information, that is, information you want to know on a specific date, while ANNUAL.REM contains information for which you wish annual reminders (e.g. birthdays). The initial version of the program required, as the first entry of these data files, a line starting with a number, representing the number of days-in-advance you wish to know about the specified event. I recently modified the program to allow two numeric parameters to be entered when the program is invoked, representing the number of days-in-advance for the REMIND and ANNUAL files, respectively. Thus my TSX account start-up file contains remind 10 15 which means "Tell me 10 days in advance all entries I made in REMIND.REM, which represent specific appointments, etc., but warn me 15 days in advance of birthdays, anniversaries, etc., so I have time to shop". The routine UCLSUB provides the importation of run-time parameters. REMIND needs to calculate days-in-advance, both regarding and disregarding years. For this purpose, it uses routines in JULIAN, which translate dates into "Julian dates", the number of days since a long time ago (December 31, 6703 years ago). As a side benefit, it can calculate the day of the week, given any (reasonable) date. Note that the present calendar, the Gregorian calendar, was adopted in 1752, so earlier dates require some adjustment (this is why George Washington's birthday is, or used to be, celebrated on a day other than the day recorded as his birth). - 8 - Having written REMIND, I realized I needed an additional "tickler" program that I could leave little reminder notes to myself, which would pop up and say "Three days ago, you wrote a note saying Finish the documentation". So I created REMEMB, which looks at a file called REMEMB.REM, in the same format as the other .REM files. Here, however, the date is the date the reminder was entered into the file -- REMEMB persistently shows you the entire file (allowing you to add and delete entries, of course), with the date of entry. I provided these programs to a colleague, who complained that it was tedious to constantly edit REMIND.REM in order to update his appointment calendar. So I wrote CALEND to handle the maintenance of REMIND.REM -- this program allows you to create or delete entries in this file, and to mark the entries with their appropriate dates. Spelling Routines I write a lot, using RT-11 (actually, using KED and formatters such as RUNOFF). I wanted something to help me catch spelling errors. I wrote SPELL, a fairly simple-minded speller, which creates a sorted list of words from a text file, compares it with a dictionary file, then provides you a list of words it does not find in the dictionary. It runs a bit slow, most of the time being taken up with word-parsing and sorting, but it works pretty well. I offer it in part because it contains two external sort routines, a heap sort (which is used during the initial parsing phase, since the heap can serve as a sorting "filter" to produce long runs of sorted output) and a poly-phase external sort routine, which is used to do the final sort/merge process. SPELL produces a list of words which are not in the dictionary. Since I did not have my own machine-readable copy of Webster's, I created my own _________ dictionary. Initially, most of the words SPELL did not recognize were, in fact, correctly-spelled words not yet in its dictionary. I wrote ADDICT (which means ADd to DICTionary) to take the edited version of the list produced by SPELL (after you weeded out the words truly mis-spelled) and add them to the dictionary. SPELL (and ADDICT) can accept multiple dictionaries. I typically use two, the default dictionary DIXION.DCT, and a dictionary of names, NAMES.DCT. After I run a file through SPELL using the default dictionary, it produces a list of "mis-spelled" words, which often contain proper names which I recognize, and have in NAMES. By specifying the mis-spelled list to SPELL and using the NAMES dictionary, I can get it to "filter out" the names. Note that the output lists from SPELL contain special headers, which SPELL recognizes as meaning "this file is sorted", so the second pass through the NAMES dictionary runs very fast, as the entire sorting step can be omitted. The current version of SPELL accepts as defaults files with the extension ".DOC" or ".PRS" -- the latter is the file type expected by the formatter routine I use most commonly. This formatter also uses the - 9 - vertical bar, "|", as a hyphenation flag, so SPELL has been customized to ignore this character. Matrox QC-640 Display Controller At the Spring Symposium, Dave Evans presented a nice review of graphics software and products for RT-11. He mentioned that an anonymous person had submitted software designed for a Matrox display controller. Matrox is a Canadian company who produce a line of graphic controller boards for the Q-bus and Unibus. I have their most recent product, the QC-640, a board which accepts ASCII text, and can produce lines, filled areas, symbols, text, and so on in a variety of colors (256 out of a 24-bit palette), as well as perform such transformations as scaling, rotation (in 3-D, yet), clipping, and so on of the image. I had written a handler for this device, called MX, and offered to provide it to the SIG, along with a utility program, MATEST, which exercises some of the features of the board. The handler is fairly straight-forward. The device is basically a character-oriented device (like a terminal), although it does have a FIFO and assorted flags to allow it to buffer data from the CPU. I did implement one .SPFUN, to allow the user to perform a "cold-reset", if necessary. As long as you are only using the device for output, everything is easy. However, the board also allows you to interrogate it, either to return status or flag information, or to allow you to read out the stored image. Since the size of the returned data could not be readily known, providing sensible .READ logic was tricky. I adopted the policy of satisfying every read, by filling the block with nulls if the Matrox board did not provide the characters fast enough, leaving it to the user to decide if more data were required (note the somewhat convoluted logic in MATEST). DYDS -- Handler Modifications for Double-Sided 8" Floppies This may be old-hat to many, but for those of us still using 8" floppies, especially those from non-DEC third-party sources, these devices have an inherent capability to handle twice as much data, simply by using two read/write heads, and double-sided media. Rumor even has it that DEC was all set to release an RX03 as the double-sided double-density member of the RX01/RX02 family. Based on code originally developed for previous versions of RT-11 and TSX-Plus, I have written an article describing how to modify the DEC DY handlers to allow double-sided operation. These modifications are somewhat hardware-specific, so you may need to make additional alterations. I have also discovered some new and exciting "bugs" in the earlier handlers which usually surface when running TSX on a Q-Bus system (especially when using a 22-bit backplane -- funny things can happen if your program straddles an 18-bit memory boundary). The descriptions in the article DYDS.DOC have been tested with RT-11 Version 5.4 and a pre-release version of RT-11 Version 5.5, as well as the latest version of TSX-Plus, version 6.4. - 10 - - 11 -