TRS-80 DOS - LDOS v5.3.1 for the Model I - BACKUP/CMD Disassembled
Page Customization
Page Index
BACKUP/CMD
Other Navigation
Summary:
LDOS 5.3.1 BACKUP/CMD Disassembly - The Disk Backup Utility (Model I)
BACKUP/CMD duplicates the data on one diskette onto another. It is a transient utility: DOS loads it, it runs, and it exits through @EXIT at 402DH, leaving nothing resident behind it. The command syntax is BACKUP :s [TO] :d (parm,parm) or BACKUP [-]partspec w/wcc:s TO :d (parm,parm), and the parameters are DATE, INV, MOD, MPW, NEW, OLD, QUERY, SYS and X.
The manual describes three kinds of backup, and the code implements exactly three. A mirror image is a cylinder-for-cylinder copy, attempted when the size, density and number of sides match on the two disks. A backup by class is a file-for-file copy, chosen by the operator whenever a partspec or any parameter other than X or MPW appears on the command line. A backup reconstruct is the same file-for-file copy, chosen by the utility itself when the two disks are not compatible enough for a mirror image.
What makes this file unusual is that those are not three code paths through one program. They are three separate programs carried in one load module, and two of them are stored at addresses they never execute at. The command-line front end is assembled to run at 5A00H, which is where the transfer address points, and both backup engines are also assembled to run at 5A00H. The mirror-image engine is stored at 5E00H and the file-by-file engine at 6300H. Once the front end has finished parsing and has decided which kind of backup is required, the selector at 6C00H block-moves the chosen engine down over the front end that has just finished with it, and jumps to 5A00H. The engine sections of this page are therefore annotated at the addresses the code actually runs at, with the load address stated in each section lead-in.
Everything above the moved engine is work space. After the block move, the selector rounds the end address up to the next page boundary and stores it at 5285H, and from that point on 5285H is the base of the buffers and HIGH$ at 4049H is their top. The two Granule Allocation Table copies and the general sector buffer sit at fixed addresses inside the file's own unloaded gap at 55F8H-59FFH.
BACKUP does almost all of its disk work itself rather than through the file system. It copies the drive's ten-byte Drive Control Table entry through the resident GETDCT routine at 478FH, points Register Pair IY at it, and reaches the floppy driver with a JP (IY) through the jump instruction at DCT+00, exactly as FORMAT/CMD does. Only two supervisor requests are issued, both as three-byte tail-call stubs: code 0E4H to hash a master password and code 84H to make SYS2/SYS resident before the copying starts.
The utility also suppresses the real-time clock. The mirror-image transfer is timed against the drive, so the engine saves the byte at 4012H inside the resident interrupt vector and writes a RET over it for the duration of the copy. That one byte is the entire reason the utility signs off with Note: Real time clock no longer accurate.
File Format and Load Map
BACKUP/CMD is 5880 bytes on disk: one type-1FH copyright record carrying Copyright 1991 MISOSYS, Inc., All rights reserved, then 27 type-01 load records, then a type-02 transfer address of 5A00H. The records place 5717 bytes into memory in six runs separated by five unloaded gaps. The gaps are working storage and are represented on this page as reserved space.
| Address Range | Bytes | Contents |
|---|---|---|
| 5200H-521CH | 29 | Parameter result cells, the assembled selection flags and the partspec masks |
| 521DH-5284H | 104 | Not loaded. The filespec build area and the date scratch fields |
| 5285H-55F7H | 883 | The free-space pointer word, the shared subroutine block and the common message block |
| 55F8H-59FFH | 1032 | Not loaded. The file control block, the two Granule Allocation Table copies and the sector buffer |
| 5A00H-5D51H | 850 | The command-line front end. The transfer address points here |
| 5D52H-5DFFH | 174 | Not loaded |
| 5E00H-6208H | 1033 | The mirror-image engine as loaded. It runs at 5A00H-5E08H |
| 6209H-62FFH | 247 | Not loaded |
| 6300H-6A47H | 1864 | The file-by-file engine as loaded. It runs at 5A00H-6147H |
| 6A48H-6BFFH | 440 | Not loaded |
| 6C00H-7021H | 1058 | The engine selector, the two supervisor-request stubs, the drive-geometry reader, the @PARAM table and the front-end message block |
The two engine blocks are moved by a fixed amount, so every address inside them is the loaded address less a constant. The mirror-image engine is moved down by 0400H and the file-by-file engine by 0900H.
| Loaded At | Runs At | Moved By |
|---|---|---|
| 5E00H-6208H | 5A00H-5E08H | Down 0400H by the LDIR at 6C0FH, which copies 0500H bytes |
| 6300H-6A47H | 5A00H-6147H | Down 0900H by the LDIR at 6C5BH, which copies 0900H bytes |
Variable and Buffer List
| Address Range | Purpose |
|---|---|
| 5200H 1 byte | Date-selection mode. Bit 7 is set when a lower bound was given, bit 0 when an upper bound was given. Both clear means the DATE parameter was not used |
| 5201H-5202H 2 bytes | The lower date bound, packed into the two-byte form an LDOS directory record uses |
| 5203H-5204H 2 bytes | The upper date bound, in the same packed form |
| 5205H-520CH 8 bytes | The eight-character filename mask taken from the partspec, blank filled. A 24H dollar sign in any position matches any character |
| 520DH-520FH 3 bytes | The three-character extension mask, blank filled, with the same wildcard rule |
| 5210H 1 byte | The attribute selection byte assembled from the switches. Bit 6 is set by SYS, bit 4 by MOD and bit 3 by INV, so that it can be tested directly against the directory record's own attribute byte |
| 5211H 1 byte | Non-zero when the partspec was prefixed with a minus sign, which inverts the sense of the filename and extension match |
| 5212H 1 byte | Counts the destination diskettes used, incremented each time the operator is asked for a fresh formatted disk |
| 5213H-5214H 2 bytes | The NEW parameter result cell, filled by @PARAM. Non-zero selects only files that are not already on the destination |
| 5215H-5216H 2 bytes | The OLD parameter result cell. Non-zero selects only files that already exist on the destination |
| 5217H-5218H 2 bytes | The MOD parameter result cell. Non-zero selects only files whose modification flag is set |
| 5219H-521AH 2 bytes | The QUERY parameter result cell. 521AH is also cleared at run time by a reply of C to the query prompt, which turns querying off for the rest of the backup |
| 521BH-521CH 2 bytes | The SYS parameter result cell. Non-zero includes system files and makes the destination a SYSTEM disk |
| 521DH-523CH 32 bytes | Not loaded. The filespec is built here for display and for @INIT, as name, slash, extension, terminated by 03H |
| 523DH-525CH 32 bytes | Not loaded. The destination file control block |
| 525DH-525FH 3 bytes | Not loaded. The keyboard reply buffer, and the three date fields the DATE parser builds downwards as year, day, month |
| 527DH-5284H 8 bytes | Not loaded. The system date read from @DATE, used to stamp the destination disk |
| 5285H-5286H 2 bytes | The free-space pointer. Ships as 0000H and is filled in by the selector at 6C5DH with the first page boundary above the moved engine. Every buffer the engines allocate is measured from here up to HIGH$ at 4049H |
| 52C1H 1 byte | The saved copy of the resident interrupt vector byte from 4012H, put back when the backup ends |
| 52CAH-52CBH 2 bytes | The X parameter result cell, held inside the shared subroutine block. Non-zero allows a backup with no system disk in drive 0 |
| 531BH 1 byte | The source drive number, planted as the operand of an LD A instruction |
| 5388H 1 byte | The destination drive number, planted the same way |
| 53D5H 1 byte | The current drive cell. Bits 2-0 are the drive number, bit 7 records that the source disk is believed to be mounted and bit 6 that the destination disk is |
| 53DCH 1 byte | The source drive number exclusive-ORed with the destination drive number. Zero means a single-drive backup, and that one byte governs every prompt-and-swap decision the utility makes |
| 5487H-55F7H 369 bytes | The shared message block, holding the clock note, the write-protect messages, the three insert-disk prompts, the two ALERT messages and the two closing messages |
| 55F8H-56FFH 264 bytes | Not loaded. Geometry scratch and the source file control block |
| 5700H-57FFH 256 bytes | Not loaded. The source disk's Granule Allocation Table sector, addressed at true GAT offsets throughout: 5760H is the lock-out map, 57CCH the configuration word, 57CEH the master-password hash and 57D0H the eight-character disk name |
| 5800H-58FFH 256 bytes | Not loaded. The destination disk's Granule Allocation Table sector, at the same offsets |
| 5900H-59FFH 256 bytes | Not loaded. The general sector buffer, used for directory sectors, the Hash Index Table, the boot sector and collected keyboard replies |
| 6C01H 1 byte | The backup-by-class flag, planted by the front end at 5B2DH as the operand of the LD A that opens the selector. Non-zero means a file-by-file backup |
| 6D25H 1 byte | The second byte of the CB-prefixed instruction at 6D24H, computed at run time so that one instruction body can set or clear bit [drive] of the resident dating flag at 475DH for any of eight drives |
| 6D72H 1 byte | The second byte of the CB-prefixed instruction at 6D71H, computed the same way to test bit [drive] of 475DH |
| 6D74H-6DF3H 128 bytes | The @PARAM keyword table: sixteen six-character blank-padded keywords, each followed by the two-byte address of its result cell, terminated by 00H |
| 6DF4H-7021H 558 bytes | The front-end message block, the sign-on banner, the maximum-days-per-month table at 7006H and the date-format error message |
The Parameter Table
The table at 6D74H is the standard LDOS @PARAM form: a six-character blank-padded keyword followed by the address of a two-byte result cell, repeated until a 00H byte ends the list. A long form and its abbreviation point at the same cell, and the cells are not all in one place - some are in the small data run at 5200H and some are the operand fields of instructions out in the front end.
| Keyword | Result Cell | Effect |
|---|---|---|
| MPW | 5C94H | Passes the source disk's master password so that the prompt is not issued |
| SYS, S | 521BH | Includes system files, and reserves the fifteen directory slots a SYSTEM disk needs, the two FORMAT creates plus thirteen for the /SYS overlays |
| INV, I | 5AE4H | Includes files that are invisible in the directory |
| MOD, M | 5217H | Selects only files modified since the last backup |
| QUERY, Q | 5219H | Asks before each file is copied, showing its date and modification flag |
| X | 52CAH | Allows a backup with no system disk in drive 0, subject to the residency test at 6C22H |
| DATE, D | 5AA2H | Selects files by modification date, in any of the four forms the manual lists |
| NEW, N | 5213H | Copies only files that are not already on the destination |
| OLD, O | 5215H | Copies only files that already exist on the destination |
Major Routine List
| Address | Routine |
|---|---|
| 5287H | Normal Ending Displays the real-time-clock note and Backup complete, then leaves through @EXIT at 402DH. Every successful path ends here |
| 529BH | Disk Error Exit Restores the interrupt vector, reports the error code left in Register A through @ERROR at 4409H, and falls into the abort ending |
| 52ACH | Abort Ending Displays Backup aborted and leaves through @ABORT at 4030H |
| 52C0H | Restore The Interrupt Vector Puts the saved byte back at 4012H, re-enables interrupts, and clears the work space from the free-space pointer up to HIGH$ |
| 5327H | Mount The Source Disk Returns at once if bit 7 of the current drive cell already says the source is in place. Otherwise it prompts, waits, re-reads the Granule Allocation Table and compares the ten-byte Pack ID against the saved copy, refusing with the source ALERT message on a mismatch |
| 5394H | Mount The Destination Disk The mirror of the routine above, using bit 6 of the current drive cell, and verifying that the first byte of the directory sector is still 76H |
| 53E0H | Flashing Prompt And Wait Displays a prompt and alternates it against a delay, calling the resident keyboard poller at 4DA6H each pass. BREAK or ENTER ends the wait; the PAUSE bit discards the loop's own return address so that the routine returns out of the whole prompt |
| 544CH | Clear The Keyboard Flags Masks KFLAG$ at 4423H with 0F8H. The resident poller only ever sets bits 0 to 2, so the caller has to clear them |
| 5455H-5470H | Disk Operation Stubs Eight two-byte entries loading the operation code for present-test, select, restore, controller wait, write, deleted-mark write, read and verify, each falling through a jump into the common tail |
| 5472H | Disk Operation Tail Puts the operation in Register B and the drive from the current drive cell in Register C and in LDRV$ at 4308H, loads Register A with 20H and reaches the floppy driver with the JP (IY) at 5485H |
| 5A00H | Front End Entry The transfer address. Sets the stack to 41E0H, displays the sign-on, parses the partspec, the two drive numbers and the parameters, decides mirror image against reconstruct, and hands over to the selector |
| 5C72H | Ask About A Cylinder Count Mismatch Displays the question, reads one character and returns with the Z flag set when the reply was Y |
| 5C89H | Master Password Check Accepts the disk at once when its master-password hash is 42E0H, the hash of PASSWORD. Otherwise it prompts, hashes the reply through the 0E4H stub, and accepts either the bypass constant 113DH or the disk's own hash |
| 5CB1H | Filespec Character Filter Accepts a dollar sign as the wildcard, folds lower case to upper case, and stops the field at any character outside the digits and the upper-case letters |
| 5CCCH | Date Parameter Parser Splits the quoted date string on the minus sign into a lower and an upper bound, validates each against the maximum-days table, and packs the result into the directory date form |
| 5D20H | Split A Date Group Breaks month, day and year apart on the slash and stores them downwards at 525FH, 525EH and 525DH |
| 5D35H | Collect A Two-Digit Number Builds a decimal value from up to two ASCII digits, returning with the carry flag clear on a bad character |
| 6C00H | Engine Selector Reads the backup-by-class flag planted in its own first instruction, block-moves the mirror-image engine or the file-by-file engine down to 5A00H, records the free-space pointer and jumps to 5A00H |
| 6C22H | X Parameter Residency Test Reads the word at 4BDFH inside the resident supervisor-call dispatcher. If it still holds 4BF5H, nothing has been made resident and the backup is refused. Otherwise four bytes below that address are tested, one for each of the four overlays the file-by-file path needs |
| 6C68H | Collect And Hash A Password Calls the string collector and falls into the 0E4H supervisor stub |
| 6C6BH | Password Hash Stub LD A,0E4H followed by RST 28H, with no RET. The dispatcher discards the address the restart pushed, so this three-byte stub is a complete subroutine |
| 6C6EH | SYS2 Pre-Load Stub LD A,84H followed by RST 28H. Sub-function 00H is rejected by SYS2 with a RET, so the only effect is that the overlay becomes resident |
| 6C71H | Collect Eight Characters Takes the field either from the command line or, when none was given, from the keyboard, blank fills it to eight characters and folds it to upper case |
| 6CBFH | Read A Drive's Geometry Tests that the unit is fitted, restores and seeks the head, waits a full index-hole revolution against TIMER$ for readiness, reads the Granule Allocation Table through DIRCYL and RDSSEC, and unpacks the configuration word into the Drive Control Table copy |
| 6D4FH | Poll For The Index Hole Reads the controller status through RSELCT and gives up when TIMER$ reaches the deadline in Register D |
| 6D61H | Test The Extended Dating Flag Computes the second byte of a BIT instruction from the drive number, plants it at 6D72H and executes it against 475DH, so that one instruction body serves all eight drives |
Cross-Reference Notes
BACKUP is a transient utility, so nothing calls into it. It calls out to the resident core and to the ROM. From the Model I ROM it uses 002BH to test the keyboard, 0033H to display one character, 0040H to collect a line and 0060H to delay.
From the resident core it uses the supervisor vectors @EXIT 402DH, @ABORT 4030H, @ERROR 4409H, @INIT 4420H, @OPEN 4424H, @CLOSE 4428H, @KILL 442CH, @READ 4436H, @VER 443CH, @REW 443FH, @POSN 4442H, @DSPLY 4467H, @DATE 4470H, @PARAM 4476H, @LOGOT 447BH, @CKDRV 44B8H and @MULT 44C1H, together with the disk primitives RSELCT 4759H, SEEK 475EH and GETDCT 478FH, the directory helpers RDSSEC 4B45H and DIRCYL 4B65H, and the keyboard-status poller at 4DA6H. All of these are documented on the SYS0/SYS page.
It reads five resident cells and writes two. It reads SFLAG$ at 430FH bit 5 to find out whether a job is in effect, KFLAG$ at 4423H bits 0 to 2 for BREAK, PAUSE and ENTER, HIGH$ at 4049H for the top of memory, TIMER$ at 4040H for the drive-ready timeout, the month cell at 4046H to see whether a date has been established, and the word at 4BDFH inside the supervisor dispatcher for the X parameter residency test. It writes the drive number into LDRV$ at 4308H before each disk operation, the per-drive extended-dating flag at 475DH once a pack's Granule Allocation Table has been read, and the interrupt vector byte at 4012H for the duration of a mirror-image transfer.
Two supervisor requests are issued, both documented on the main LDOS disassembly page: code 0E4H, the password hash serviced by SYS2/SYS, and code 84H, the pre-load that makes the same overlay resident. The file-by-file engine additionally depends on SYS3/SYS for @CLOSE, SYS8/SYS for granule allocation and SYS10/SYS for @KILL, reached through the resident vectors rather than directly, which is why the X parameter insists that those four overlays are resident before it will run.
The disk layout BACKUP works against is the one FORMAT/CMD lays down, and the two files are the two halves of one story about the Hash Index Table: FORMAT reserves two directory slots for BOOT/SYS and DIR/SYS, and BACKUP claims the remaining thirteen, for SYS0/SYS through SYS12/SYS, when it turns a formatted DATA disk into a SYSTEM disk, checking all fifteen as one table. The per-drive extended-dating flag at 475DH that BACKUP writes is the same flag DATECONV/CMD exists to make meaningful.
Disassembly:
5200H - Parameter Result Cells and Selection Flags
The first load record places twenty-nine bytes of data, not code, at 5200H. Five of them are the result cells the @PARAM service at 4476H writes into when it recognises a keyword on the command line, and the rest are the selection criteria the front end assembles from the partspec. The whole block ships as blanks and zeroes and is filled in during parsing, so the listing's rendering of these bytes as instructions is a misreading of data. Every byte here is read by the file-by-file engine when it decides whether a given directory record is to be copied.
Bit 7 is set when the DATE parameter supplied a lower bound and bit 0 when it supplied an upper bound. Both bits clear means no date filtering. The two bits are set by the date parser at 5AADH and 5ACBH and tested by the file-by-file engine at 649BH and 64AFH.
The earlier of the two dates from a DATE range, already packed into the two-byte form an LDOS directory record uses at offsets 01H and 02H, so that it can be compared against a record without unpacking either value. Written by the front end at 5AB5H.
The later of the two dates, in the same packed form, written by the front end at 5AD3H. When only one date was given the parser stores it in whichever of the two cells the form of the parameter calls for.
The eight-character file name taken from the partspec, blank filled. A 24H dollar sign in any position is the wildcard character and matches anything. Blanks throughout mean no name was given, which is how the front end at 5AF9H decides whether a partspec was present at all.
The three-character extension from the partspec, blank filled, with the same dollar-sign wildcard rule. Filled in only when a 2FH slash was seen on the command line, by the loop at 5A26H.
Assembled at 5AF6H from the three switches so that it can be tested directly against a directory record's own attribute byte at offset 00H. Bit 6 is set by SYS, bit 4 by MOD and bit 3 by INV, which are the same bit positions those meanings occupy in the record.
Set to the 2DH minus sign itself by the front end at 5A1AH when the command line began with one. Non-zero inverts the sense of the filename and extension match, so that the files which do not match are the ones copied.
Ships as 0FEH and reaches zero only when the source pack is in the old date format and the destination pack is in the x.3 extended format. The front end at 5BBFH sets it to 0FFH when the test at 6D61H says bit [drive] of the resident dating flag at 475DH is clear for the source, and 5C4AH increments it when the same test says the bit is set for the destination. A value of 00H is therefore the exact case the manual describes, in which the old access password is dropped and x.3 date and time information is established on the copy, and in which system files may not be copied by class.
Written by @PARAM at 4476H when the NEW or N keyword appears. Non-zero means copy only those files which are not already present on the destination, which the engine determines from the error code @INIT returns.
Non-zero means copy only those files which already exist on the destination. NEW and OLD are tested together at 64F5H, and one of them being set is what makes the engine care about the result of @INIT rather than ignoring it.
Non-zero selects only files whose modification flag, bit 6 of directory offset 01H, is set. Folded into the attribute selection byte at 5B15H and tested against the record at 6439H.
Non-zero asks before each file is copied. The high byte at 521AH is also cleared at run time by 65EFH when the operator answers the query prompt with C, which turns querying off for the rest of the backup exactly as the manual describes.
Non-zero includes system files in the copy and makes the file-by-file engine build a SYSTEM disk, claiming the thirteen further directory slots the /SYS overlays occupy on top of the two FORMAT/CMD already created. Tested at 5AD9H when the attribute byte is assembled and again at 6354H when the engine decides whether to read the Hash Index Table.
Unloaded Storage
The image resumes at 5285H. The 104 bytes from 521DH to 5284H are not written by any load record; they are the filespec build area at 521DH, the destination file control block at 523DH, the keyboard reply and date scratch fields at 525DH and the system date buffer at 527DH. They are working storage and hold whatever was in memory when the utility was loaded.
5285H - Endings, Disk Mounting and the Disk Operation Stubs
The second load record opens with a two-byte cell and then runs into the block of subroutines that both backup engines share. Everything here survives the block move that brings an engine down to 5A00H, which is why the endings, the prompt machinery and the disk operation stubs live in this range rather than inside either engine. The block holds the three ways the utility can finish, the routine that puts the resident interrupt vector back and clears the work space, the two routines that mount and verify the source and destination disks, the flashing prompt loop, and the eight two-byte stubs that reach the floppy driver.
The base of every buffer the engines use. It ships as 0000H and is filled in by the engine selector at 6C61H with the first page boundary above the engine that was just moved down to 5A00H. The top of the same region is HIGH$ at 4049H. The routine at 52DFH clears the whole of it, and the file-by-file engine reads it whenever it needs a sector buffer.
Entered from 529BH these three bytes are a harmless load of Register Pair BC that leaves Register A holding 11H. Entered at 529EH the same bytes are a two-byte load of Register A with 20H. Either way execution continues at 52A0H with an error number in Register A.
Overlapping Code
The three bytes 01 3E 20 at 529DH are read two ways. Entered from 529BH the leading 01H makes them a load of Register Pair BC, which changes nothing and leaves the error number 11H that 529BH put in Register A. Entered at 529EH the 01H is skipped and the remaining two bytes load Register A with 20H. One three-byte sequence therefore provides two error exits, and both of them fall into the common handler at 52A0H.
Load Register A from the operand byte at 52C1H, which is the saved copy of the resident interrupt vector byte from 4012H. The mirror-image engine writes the original byte there at 5AD6H before it overwrites 4012H with a RET; the operand ships as 00H, meaning nothing has been saved.
Store zero at 53DCH, the operand of the load at 53DBH inside the prompt routine. That cell normally holds the source drive number exclusive-ORed with the destination drive number, and forcing it to zero makes the prompt routine behave as though this were a single-drive backup, so that it will actually stop and ask.
Load Register A from the operand at 5302H, which is the drive number the system-disk prompt uses and which the routine below writes. A non-zero value means the system disk is already known to be mounted.
Store the current drive cell at 5314H, the operand of the load at 5313H. That is where the routine will find the value to put back once the operator has finished with the system disk.
Load Register A from the operand at 5302H, the system drive number, which is drive 0 on an ordinary machine. This is also the cell the test at 52F6H reads.
Load Register A from the operand at 5314H, the current drive cell that was saved at 52FEH. The drive check failed, so the state is put back and the prompt is issued again.
Load Register A from the operand at 531BH, which the front end filled in at 5A4EH with the source drive number taken from the command line.
Load Register A from the operand at 5388H, which the front end filled in at 5A7CH with the destination drive number taken from the command line.
RETurn if the Z FLAG has been set, the marker byte matching and the right destination disk being in place. The file-by-file engine writes 0C9H over this byte at 6311H, turning the instruction into an unconditional RET, because a class backup never writes the 76H marker and must not test for it.
Compare Register A, the requested drive state, against the operand byte at 53D5H, which is the current drive cell itself. Making the cell the immediate operand of the comparison is what lets one instruction test the request against the live state. If they are equal, the Z FLAG is set.
Load Register A from the operand at 53DCH, the source drive number exclusive-ORed with the destination drive number, which the front end computed at 5A91H. Zero means both drivespecs named the same unit.
5487H - The Shared Message Block
The remainder of the second load record is text, not code. These ten messages are the ones both backup engines need, which is why they sit below 5A00H where the block move that brings an engine down cannot reach them. Three terminators are in use and they are not interchangeable. A 0DH ends the line. A 03H leaves the cursor where it is, which is what a prompt needs. A leading 1DH returns the cursor to the start of the current line and a following 1EH clears to the end of it, so a message that begins with that pair overwrites the line it is on rather than scrolling the screen, and that is how the flashing prompt loop at 53FEH alternates a prompt against a blank line without disturbing anything above it.
Unloaded Storage
The image resumes at 5A00H. The 1032 bytes from 55F8H to 59FFH are not written by any load record and are the largest gap in the file. They are the geometry scratch and the source file control block at 55F8H, the saved copy of the source disk's Granule Allocation Table sector at 5700H, the destination's at 5800H, and the general 256-byte sector buffer at 5900H. The two Granule Allocation Table copies are addressed at true Granule Allocation Table offsets throughout the file, so 5760H and 5860H are the lock-out maps, 57CCH and 58CCH the configuration words, 57CEH and 58CEH the master-password hashes and 57D0H and 58D0H the eight-character disk names.
5A00H - Front End Entry and Command Line Parsing
The transfer address named by the type-02 load record is 5A00H, so this is the first instruction BACKUP executes. The front end sets up its stack, signs on, and takes the command line apart: the optional leading minus sign, the file name, the extension, and the two drivespecs, prompting for either drivespec that the command line did not supply. Note that this whole block is temporary. Once the front end has decided which kind of backup to run, the selector at 6C00H moves one of the two engines down over it, and every address in this range becomes part of that engine.
Store Register A, the source drive number, at 531BH, which is the operand of the load at 531AH inside the source-mount routine. From here on that routine builds its drive-state byte from this cell.
Store Register A, the destination drive number, at 5388H, which is the operand of the load at 5387H inside the destination-mount routine.
5A7FH - Parameters, the Single Drive Test and the DATE Range
The parameter list in brackets is handed to the resident @PARAM service, which writes each value into the cell the table at 6D74H names. The two drive numbers are then exclusive-ORed together into 53DCH, the byte that tells the rest of the utility whether this is a single-drive backup, and a single-drive backup under a job is refused outright because there is nobody at the keyboard to swap disks. The DATE string is parsed into the two packed bounds at 5201H and 5203H, and the SYS, INV and MOD switches are folded into the attribute selection byte at 5210H in the bit positions a directory record uses.
Load Register A from 531BH, the source drive number planted at 5A4EH.
Store the result at 53DCH, the operand of the load at 53DBH inside the prompt routine, and the single byte on which every prompt-and-swap decision in the file turns. Zero from here on means a single-drive backup.
Load Register Pair HL from the operand at 5AA2H, which is the DATE parameter result cell. @PARAM stores the address of the quoted date string there, or leaves it at 0000H when the parameter was not used.
Load Register Pair DE from the operand at 5AE4H, which is the INV parameter result cell that @PARAM fills when the INV or I keyword appears.
Load Register Pair DE from the operand at 5AEDH, which is a second result cell for the modification switch.
5AF9H - The Backup By Class Decision and Mounting the Source
The manual says a backup by class is done when the user specifies a partspec or any parameter except X or MPW. That rule is implemented literally, as one chain of ORs over the ten cells that are neither X nor MPW, and the answer is planted in the first instruction of the engine selector. The source disk is then mounted: SYS2/SYS is pre-loaded so that the open and initialise services are resident before any disk swapping begins, and the drive is selected, restored and read.
Load Register A from 5AE4H, the low byte of the INV parameter result cell, which is the operand of the load at 5AE3H.
Load Register A from 5AEDH, the low byte of the second modification-switch result cell, the operand of the load at 5AECH.
Load Register A from 5AA2H, the low byte of the DATE parameter result cell, the operand of the load at 5AA1H.
Store the total at 6C01H, which is the operand of the load at 6C00H, the first instruction of the engine selector. The selector therefore begins by reading the answer to the question this chain just settled.
If the Z FLAG has been set no date has been set on this machine, so GOSUB to the resident @LOGOT vector at 447BH to warn the operator that whatever the backup stamps on the copy will be meaningless. The mirror-image engine overwrites the operand of this instruction at 5EB4H with the granules-per-cylinder count once the block move has brought it down to this address.
Load Register A from 531BH, the source drive number.
OR Register A with itself to test the source drive number for zero, which means the source is the system drive. The mirror-image engine overwrites the operand region here at 5ECFH with the count of whole cylinders that will fit in the work space.
5B73H - Reading the Source Geometry and Mounting the Destination
The source disk's boot sector gives up its directory cylinder and its geometry, its Granule Allocation Table is read into 5700H, and its master password is checked. The destination is then mounted and put through the same sequence, with two extra refusals that only apply to it: a rigid drive can never be a mirror-image destination, and a write-protected destination cannot be written at all. The write-protect test is deliberately made twice over, from the status the controller returned and from bit 7 of the Drive Control Table specification byte, so that either source of the information is enough to stop the backup.
Store the eight-inch flag at 5C14H, which is the operand of the exclusive-OR at 5C13H in the mirror-image decision. That comparison therefore tests the destination's eight-inch bit against the source's without needing a second Drive Control Table pointer.
Point Register Pair HL at 5700H, the buffer that holds the source disk's Granule Allocation Table for the rest of the run. The mirror-image engine overwrites the operand at 5BA6H with the destination directory cylinder at 5E83H once it has been moved down to this address.
Load Register A from 5388H, the destination drive number planted at 5A7CH. The mirror-image engine overwrites this region at 5EC7H with the granules-per-cylinder count once it has been moved down.
OR Register A with itself to test the destination drive number for zero, meaning the destination is the system drive.
5C0EH - The Mirror Image Decision
This is where the manual's three kinds of backup are chosen between. A mirror image needs the size, the density and the number of sides to match, and all three live in the high byte of the Granule Allocation Table configuration word, so one exclusive-OR and one mask settle the question. A difference in cylinder count alone is not fatal and the operator is asked about it. Anything else, and any rigid destination, produces a backup reconstruct, which is reached by entering the selector one instruction past its class test so that the file-by-file engine runs whatever the class flag says.
Exclusive-OR Register A with the operand at 5C14H, which is the source drive's eight-inch flag saved at 5B78H. A non-zero result means one drive is eight inch and the other is not.
Load Register D with Register L, which is 00H, giving cylinder 0. The mirror-image engine overwrites this instruction at 5EBAH with the granules-per-cylinder count once it has been moved down to this address.
Load Register A from 5902H, the destination disk's directory cylinder as recorded in its boot sector. The mirror-image engine writes its own cylinder counter over the operand region here at 5F61H and 6031H once it has been moved down.
Load Register A from 6C01H, the backup-by-class flag that was assembled at 5B2DH.
5C72H - Ask About a Cylinder Count Mismatch
A short routine that displays the question, collects a reply and reports whether it was Y. It is the only question in the front end whose answer changes which engine runs.
5C89H - The Master Password Check
A disk still carrying the default master password is accepted without a word. Otherwise the password is taken from the MPW parameter if it was given and from the keyboard if it was not, hashed by SYS2/SYS through the supervisor stub at 6C6BH, and compared twice: once against the reserved bypass constant that SYS2 itself honours, and once against the hash stored in the disk's own Granule Allocation Table. A wrong password ends the backup with no retry.
Load Register Pair DE from the operand at 5C94H, which is the MPW parameter result cell. @PARAM stores the address of the quoted password there, or leaves it at 0000H when MPW was not given.
Load Register Pair HL from 57CEH, the source disk's own master-password hash. The mirror-image engine overwrites the operand region here at 5EAFH with the sectors-per-cylinder count once it has been moved down to this address.
5CB1H - The Filespec Character Filter
One routine fills both the file name mask and the extension mask. It accepts the digits, the letters in either case and the dollar sign that is the LDOS wildcard, folds lower case to upper so that the mask matches the form a directory record stores, and stops at the first character that is none of those, handing that character back to the caller so that the caller can see whether the field ended in a slash, a colon or something else.
5CCCH - The DATE Parameter Parser
The DATE parameter accepts four forms and this block handles all of them. A group of month, day and year is split on the slash into three fields stored downwards, each field is collected as exactly two digits, the year is moved into the right century and tested for a leap year, the month is range checked and the day is checked against the maximum-days table at 7006H, and the validated result is packed into the same two bytes a directory record uses at its offsets 01H and 02H so that the selection test later is a plain sixteen-bit comparison. Note that the leap-year adjustment modifies the February entry of that table in place and never puts it back, which is harmless because at most two dates are parsed in one run of the utility.
Rotate Register A left circularly, the first of three shifts that move the year up beside the month. The file-by-file engine overwrites the operand region here at 642CH with the directory attribute byte of the record it is examining, once it has been moved down to this address.
Unloaded Storage
The image resumes at 5E00H. The 174 bytes from 5D52H to 5DFFH are not written by any load record. Nothing in the front end refers to them, and they exist only because the next load record begins on a page boundary.
5E00H - Mirror Image Engine, Pack ID and Lock Out Map Tests
The mirror-image engine is loaded here at 5E00H but is assembled to run at 5A00H, over the front end. The selector at 6C06H moves it down before it is entered, so every address in the mnemonic column of these sections is 0400H below the address in the leftmost column, and the links follow the operand to the place the instruction is shown rather than the place it runs. The engine begins with the two tests that can refuse the whole backup before a single byte moves: the Pack IDs must match, and no cylinder that carries data on the source may be locked out on the destination.
Relocated Block
Everything from 5E00H to 6208H is loaded where it is shown and executed 0400H lower. An operand such as the jump at 5F54H reads 5AEEH because that is where the instruction it targets will be when the block move has been done; the link on that row goes to 5EEEH, which is where the same instruction is shown on this page. The two are the same instruction seen before and after the move. Only JP and CALL carry an absolute operand and therefore only those show a run-time address; the disassembler resolves JR and DJNZ from their displacements, so those operands are already the addresses shown in the leftmost column. Throughout these sections the descriptions cite the address as it appears on this page, which is also where the link goes. Note also that eight of the addresses in this range are written into by the front end before the move and mean something else entirely until then, which is why several rows below are marked as self-modifying in both directions.
5E4AH - Preparing the Destination and Marking It
The destination boot sector is read, given the source disk's directory cylinder and geometry if the disk is a dual-density one, and written back with a marker byte in its first position. That marker is how the destination is recognised for the rest of the run whenever the operator is asked to swap disks, and it is cleared again at 60B2H when the backup finishes.
Load Register Pair DE with 0000H, cylinder 0 and sector 0. The file-by-file engine writes the top of memory over the operand at 5E51H when it is the engine that has been moved down to this address.
Point Register Pair DE at 5909H, offset 09H of the destination boot sector image in the buffer. The file-by-file engine writes the top of memory over the operand at 5E66H when it occupies this address.
5E7DH - Computing the Geometry and Planting It
The number of sectors in a cylinder, the number of sectors on a single-density cylinder 0, and the number of whole cylinders that will fit in the work space are all worked out once, here, and then written into eight separate immediate operands scattered through the three transfer loops. That is why the loops themselves contain no memory references for geometry: every constant they need has already been assembled into the instruction stream.
Store the directory cylinder at 5BA6H, which after the block move is the operand of the comparison at 5FA5H. The write pass therefore tests each cylinder against the directory cylinder without a memory reference, and uses a deleted data address mark on that one cylinder alone.
Store the sectors per cylinder at 5B41H, which after the block move is the operand of the comparison at 5F40H that ends the read pass on each cylinder.
Store the same value at 5BCBH, the operand of the comparison at 5FCAH that ends the write pass.
Store it again at 5C2BH, the operand of the comparison at 602AH that ends the verify pass.
Store it again at 5D42H, the operand of the comparison at 6141H that ends the pass which clears the modification flags on the source directory.
Store it once more at 5CA4H, the operand of the comparison at 60A3H that ends the pass which clears the modification flags on the destination directory. Five copies of one number, each planted as an immediate operand, is what lets the three transfer loops run without a memory reference per sector.
Store it at 5B3DH, the operand of the comparison at 5F3CH, which is the read pass's alternative end condition for cylinder 0 on a dual-density disk.
Store it at 5BC7H, the operand of the comparison at 5FC6H, the same condition in the write pass.
Store it at 5C27H, the operand of the comparison at 6026H, the same condition in the verify pass.
SUBtract Register D, the bottom page, from Register A, giving the number of whole pages of buffer available. Each sector occupies one page. The front end used the operand region here as the destination drive number at 5BC4H before the block move.
Store it at 5B4CH, the operand of the comparison at 5F4BH, which is what decides how many cylinders the read pass takes before it stops and hands over to the write pass.
5ED2H - Suppressing the Clock
A sector transfer on a Model I is timed against the drive, and a clock interrupt in the middle of one loses data. The engine therefore saves the byte at 4012H inside the resident interrupt handler and writes a RET over it. Everything on the way out of the utility puts it back, and the note about the clock is displayed unconditionally because of it.
Store it at 52C1H, the operand of the load at 52C0H, which is the routine that puts it back on every path out of the utility.
Store zero at 5B45H, the operand of the load at 5F44H, which counts the cylinders sitting in the buffer. Each group of cylinders starts with the count at zero.
5EE7H - The Read Pass
Cylinders are copied in groups as large as the work space will hold. For each cylinder the engine merges the source allocation byte with the two lock-out maps, which both decides whether the cylinder is worth copying at all and builds the destination Granule Allocation Table entry at the same time. A cylinder with nothing on it is skipped entirely, and the two boot sectors on cylinder 0 are never read, so that the destination keeps its own bootstrap.
Store it at 5B68H, the operand of the load at 5F67H, so that the write pass will start at the same cylinder the read pass started at.
PUSH the buffer base in Register Pair HL onto the stack while the Granule Allocation Table bytes for this cylinder are worked out.
Load Register A with 0FEH, a mask that clears the bottom bit of a sector number and leaves everything else.
Compare Register A against the operand at 5F3DH, which the setup planted at 5EB4H with the number of sectors on a single-density cylinder. Cylinder 0 of a dual-density system disk holds fewer sectors than the rest of the disk.
Compare Register A against the operand at 5F41H, the sectors per cylinder planted at 5EA3H.
Load Register A from the operand at 5F45H, the count of cylinders read into the buffer so far. It ships as zero and is reset to zero at 5EE2H before each group.
Store the count back at 5B45H, which is the operand of the load above and is also read by the write pass and by the finish.
Load Register A from the operand at 5F4CH, the number of whole cylinders the work space can hold, computed at 5ECFH.
Load Register A from 5B45H, the count of cylinders sitting in the buffer.
5F5EH - The Write Pass
The same cylinders are written out from the buffer, with the same merge repeated so that the pass knows which ones to skip. Only the directory cylinder is written with a deleted data address mark; everything else gets a normal one, which is the arrangement FORMAT/CMD lays down and which the file system depends on.
Load Register A from 5B45H, the number of cylinders now in the buffer, which is what the write pass and the verify pass both have to work through.
Store it at 5C2FH, the operand of the load at 602FH, which is the verify pass's own copy of the count.
Load Register D from the operand at 5F68H, the cylinder the read pass started at, planted there at 5EEBH. The write pass therefore covers exactly the same cylinders in the same order.
PUSH the buffer base in Register Pair HL onto the stack while the Granule Allocation Table bytes for this cylinder are worked out.
PUSH Register Pair DE, the cylinder and sector, onto the stack. The file-by-file engine writes the top of memory over this address at 630CH when it is the engine occupying it.
Load Register A with 0FEH, the mask that clears the bottom bit of a sector number.
Compare Register A, the cylinder number, against the operand at 5FA6H, which was planted at 5E83H with the destination disk's directory cylinder.
Compare Register A against the operand at 5FC7H, the single-density sector count planted at 5EB7H, which is how many sectors cylinder 0 holds on a dual-density system disk.
Compare Register A against the operand at 5FCBH, the sectors per cylinder planted at 5EA6H.
Load Register A from 5B45H, the number of cylinders still to be written out of the buffer.
Load Register A from 5B45H, the remaining count, to decide whether the write pass has finished.
5FDCH - The Verify Pass
The third pass reads every sector back and checks its cyclic redundancy check without transferring anything, which is why it needs no buffer pointer. When it finishes the engine jumps back to the read pass to start the next group of cylinders.
Load Register A from 5B68H, the cylinder the group started at, planted at 5EEBH.
Load Register A with 0FEH, the boot-sector mask.
Compare Register A against the operand at 6027H, the single-density sector count planted at 5EBAH.
Compare Register A against the operand at 602BH, the sectors per cylinder planted at 5EA9H.
Load Register A from the operand at 602FH, the verify pass's own count of cylinders, planted at 5F61H.
Store the count back at 5C2FH, which is the operand of the load above.
Load Register A from 5C2FH, the remaining count.
603FH - Finishing the Mirror Image
With every cylinder copied, the destination is given the source's identity and the current system date, its Granule Allocation Table is written and verified, the modification flags are cleared throughout both directories, the two boot sectors are patched to name the new directory cylinder, and the destination's old directory cylinder is erased back to the state FORMAT/CMD left it in if the new directory has landed elsewhere. Every one of these steps corresponds to a sentence in the manual.
Load Register A from 5BA6H, the destination directory cylinder planted at 5E83H.
Load Register A from 5BA6H, the destination directory cylinder.
Load Register E with 02H. Sectors 0 and 1 of the directory cylinder are the Granule Allocation Table and the Hash Index Table, so the directory records themselves begin at sector 2.
Test bit 7 of the byte at the address in Register Pair HL, offset 00H of a directory record, which marks a secondary extent entry rather than a primary one.
Compare Register A against the operand at 60A4H, the sectors per cylinder planted at 5EAFH, which is how many sectors the directory cylinder holds.
Load Register A from 5BA6H, the source disk's directory cylinder.
Load Register A from 5BA6H, the directory cylinder, again.
Load Register A from 5B41H, the sectors per cylinder planted at 5EA3H.
GOSUB to 5464H, the disk-operation stub for operation 0DH, to write one sector of 0E5H bytes over the old directory cylinder.
Load Register E with 02H, the first sector of directory records.
Test bit 7 of the byte at the address in Register Pair HL, offset 00H of a directory record, which marks a secondary extent entry.
Compare Register A against the operand at 6142H, the sectors per cylinder planted at 5EACH.
6148H - The Three Digit Number Formatter
A small routine that writes a value into three characters by repeated subtraction, presetting the hundreds and tens to blanks so that leading zeros are suppressed and the units to the character zero so that a value of zero still displays. It is what keeps the cylinder number on the progress line readable as it counts up.
SUBtract Register B, the current power of ten, from Register A.
616EH - The Not Mirror Image Exit
Reached only from the lock-out map walk, before any data has been moved.
6174H - The Mirror Image Engine Message Block
The last 149 bytes of the third load record are the engine's own text. They sit above the code rather than below it because the block move that brings the engine down to 5A00H carries them with it, so the engine reaches them at 5D74H onwards once it is running. The three progress lines all begin with 1DH, which is what lets the cylinder number be rewritten in place instead of the screen scrolling once per cylinder.
Unloaded Storage
The image resumes at 6300H. The 247 bytes from 6209H to 62FFH are not written by any load record. The block move at 6C0FH copies 0500H bytes rather than the 0409H the engine actually occupies, so part of this gap is carried down with the engine and lands harmlessly above it.
6300H - File By File Engine, Setup and the System Disk Slots
The file-by-file engine serves both a backup by class and a backup reconstruct, which the manual says function identically. It is loaded here at 6300H and assembled to run at 5A00H, so every address in the mnemonic column of these sections is 0900H below the address in the leftmost column, and the links follow the operand to where the instruction is shown on this page. Only JP and CALL carry an absolute operand and therefore show a run-time address; JR and DJNZ are resolved from their displacements and already read as the addresses in the leftmost column. The engine begins by working out how much the destination disk holds, disabling a check that only a mirror image needs, and, when the SYS parameter was given, reserving the fifteen directory slots the system files must occupy.
Store it at 5E51H, the operand of the load at 6751H, which is one of the three places the engine tests whether its file buffer has run to the top of memory.
Store it at 5E66H, the operand of the comparison at 6766H, the same test in the verify loop.
Store it at 5F8FH, the operand of the comparison at 688FH, the same test in the routine that verifies a whole buffer.
Store it at 53BDH, over the conditional return inside the destination-verification routine, turning it into an unconditional one. A file-by-file backup never writes the 76H marker byte that a mirror image uses to identify its destination, so that test has to be disabled or every destination swap would be rejected.
Store it at 5DF4H, the operand of the load at 66F4H, which is what a file's length is compared against to decide whether it will fit on the destination at all.
Fetch the current Hash Index Table byte from the address in Register Pair DE into Register A.
Ten bytes of data, jumped over by the instruction above and reached only by the block move at 681FH, which appends them to every file specification the engine builds. In an LDOS file specification a full stop introduces the PASSWORD field and a colon introduces the drivespec, so every file is opened as NAME/EXT.RS0LT0FF:d. SYS2/SYS hashes the password it is given and compares the result against the constant 113DH at its own 4FDAH, and on a match grants access without consulting the directory password word at all. Running these eight characters through that hash routine yields 113DH exactly, and the same routine reproduces the two known constants, 4296H for eight blanks and 42E0H for PASSWORD, so the identification is certain. This is the mechanism behind the manual's statement that the BACKUP utility will ignore any password protection on a file, whether doing a backup by class or a mirror image backup. The plaintext is the name of the MISOSYS author, Roy Soltoff, with each letter O written as a zero, and spelling it with real letters gives 0C6F7H instead.
Data, Not Instructions
The ten bytes at 63E0H are data and the two bytes at 63EAH and 63EBH that follow them are the two abandon-this-file exits, which the selection tests reach from a dozen places. A disassembler reading straight through this range resynchronises three bytes late, so the addresses either side of it have to be taken from the code that jumps into them rather than from the listing.
63EAH - The Directory Scan and the Selection Tests
The scan walks the Hash Index Table rather than reading every directory sector, because a zero byte there means a free record and can be rejected without touching the disk. For each occupied slot the directory record is read and put through the tests in the order the manual describes them: allocated and primary, then the SYS and INV classes, then the partspec with its wildcard and its optional negation, then the date range. Only a file that survives all of them is worth building a file specification for.
Load Register A from Register L, the current Hash Index Table offset.
Store it at 5D13H, which after the block move is an operand inside the date parser. The parser has already run and will not run again, so its instruction stream is being reused as a scratch cell.
Fetch a character of the mask from the address in Register Pair DE into Register A.
Fetch a character of the name from the address in Register Pair HL into Register A.
Fetch a character of the extension into Register A.
64F4H - The NEW and OLD Tests and the Query Prompt
NEW and OLD are decided by trying to open the file on the destination and looking at whether the error is file-not-found, which means the file control block has to be built and then rebuilt afterwards because the open destroys the specification it was built from. The query line is then assembled in place: the specification, a plus sign when the modification flag is set, and the date as DD-Mmm-YY, with the year taken from the extended field when the pack is in the x.3 format and from the three low bits of the old date byte when it is not.
INCrement Register B by 1, counting one more ten.
Test bit 0 of Register A. The second byte of this instruction is the one the routine at 6D6BH computes from the drive number, so this tests the bit belonging to whichever drive is current rather than always bit 0.
660AH - Copying a System File
A file occupying one of the fifteen reserved directory slots is not copied through the file system. Its directory record is written straight into the matching slot on the destination, and SYS0/SYS additionally carries the three system sectors on cylinder 0 with it. That is what the manual means when it says certain information about the default drive types and the state of the SYSGEN configuration parameter are moved from the source to the destination.
Load Register A from the operand at 6613H, which is the attribute byte of this directory record, stored there at 642CH.
66E2H - Copying an Ordinary File
The file is opened at the source and created at the destination, both with a logical record length of 256 so that the copy is byte for byte. Before any data moves the whole of the destination file is allocated by positioning to its last record, so that a full disk is discovered at once rather than half way through. The copy then fills the entire work space from the source and writes the entire work space to the destination, which is what keeps the number of disk swaps down to one per buffer on a single-drive machine.
Load Register Pair DE from the operand at 66F4H, the total number of sectors the destination disk can hold, computed at 633FH.
Store it at 5E83H, an operand inside the code that has already run, which is being reused as a scratch cell to remember where the destination file's directory record is.
Load Register Pair HL from the free-space pointer at 5285H, the bottom of the buffer.
Store Register Pair HL at 5220H, the buffer address field of the source file control block, so that the next record is read to this page.
Compare Register A against the operand at 6751H, the top page of the work space planted at 6306H.
Store Register Pair HL at 5240H, the buffer address field of the destination file control block.
Compare Register A against the operand at 6766H, the top page of the work space planted at 6309H.
676BH - Closing the File and Copying its Directory Record
Closing the destination file gives it an end-of-file byte and an ending record number, but not the original's date or attributes, so those are copied across by hand from the source directory record. This is also where an old-format date is converted to the x.3 form and where the source file's modification flag is cleared, which is the housekeeping the manual describes and the only part of the copy that writes to the source disk.
Load Register B from the operand at 6783H, the destination file's directory record number, saved there at 6715H.
Load Register A from the operand at 67DFH, which is set to 0FFH at 680DH the first time the source turns out to be write protected. It ships as zero.
Store it at 5EDFH, which is the operand of the load at 67DEH. From now on the modification-flag write is not even attempted, and the message below is not displayed again.
6819H - Building the Two File Specifications
One routine produces both the source and the destination specification. It appends the override password and the source drive digit to the name that has already been built, copies the whole thing to the destination file control block area, and changes the one character after the colon. The two specifications are therefore identical but for the drive digit.
Fetch a character of it into Register A.
684AH - Unpacking a Directory Date
The DATE parameter was parsed into the same two-byte form a directory record carries, so the selection test is a plain sixteen-bit comparison. This routine rebuilds that form from a record, and it is where the two date formats are reconciled: the month and day come from the same place either way, and only the year is taken from a different field depending on whether the pack has been converted to x.3 dating.
Test bit 0 of Register A. The second byte of this instruction is computed from the drive number by the routine at 6D6BH, so this tests the bit belonging to the current drive.
6874H - Write and Prove a System Sector
A short helper that writes the sector in the buffer with a deleted data address mark and then verifies it, with a distinct error number for each half so that a failure says which of the two it was.
Load Register A from the operand at 688FH, the top page of the work space planted at 630CH.
Load Register Pair HL from the free-space pointer at 5285H, the bottom of the buffer.
Store Register Pair HL at 5240H, the buffer address field of the destination file control block.
68A8H - Allocating the Whole File in Advance
Positioning past the end of a file is what makes the DOS allocate space for it, so the engine positions to the last record the file will need and writes one sector there before it copies anything. A disk-full failure at that point costs nothing, and the partly allocated file is killed so that the disk is left tidy before the operator is asked for a fresh one.
6907H - The File By File Engine Message Block and Tables
The last 321 bytes of the fifth load record are the engine's own text and its three tables. Like the mirror-image engine's message block these travel down with the code, so the engine reaches them 0900H lower than they are shown here. Two of the tables are duplicates of tables the front end also carries, because the front end's copies are destroyed by the block move that brings this engine into place.
Unloaded Storage
The image resumes at 6C00H. The 440 bytes from 6A48H to 6BFFH are not written by any load record. The block move at 6C5BH copies 0900H bytes rather than the 0748H the engine actually occupies, so most of this gap is carried down with the engine and lands harmlessly above it.
6C00H - The Engine Selector
This is the hinge the whole file turns on. Both backup engines are assembled to run at 5A00H and both are stored somewhere else, so something has to choose one, move it down over the front end and jump to it. That is all this block does, plus the one check that only makes sense at this moment: if the operator asked for a file-by-file backup with no system disk in drive 0, the four overlays that path depends on have to already be resident, and there is a way to find that out without loading anything. Note that the front end enters this block at two different addresses. Entering at 6C00H lets the class flag decide; entering at 6C13H, which is what a backup reconstruct does, forces the file-by-file engine regardless.
Load Register A from the operand at 6C01H, the backup-by-class flag that the front end assembled at 5B2DH by ORing together the ten parameter cells that are neither X nor MPW. Zero means nothing specific was asked for and a mirror image is possible.
Two Entry Points, One Block
6C00H tests the backup-by-class flag that the front end assembled and takes the mirror-image path when it is clear. 6C13H is the entry a backup reconstruct uses, reached by the jump at 5C6FH after the front end has found the two disks incompatible. Everything from 6C13H onwards is therefore common to a backup by class and a backup reconstruct, which is precisely the manual's statement that the two function identically and differ only in who asked for them.
6C68H - The Two Supervisor Request Stubs
BACKUP issues only two supervisor requests in the whole file and they sit here, nose to tail, as two three-byte stubs with no RET between or after them. That is deliberate and it is correct: the dispatcher at 4BCDH discards the return address the restart pushed, so its own RET goes back to whoever called the stub. The same idiom appears in FORMAT/CMD at 7277H and in the SYS6/SYS COPY overlay at 57C5H, and it is why a three-byte sequence can be a complete subroutine.
There is no RET after this instruction and none is needed. The dispatcher at 4BCDH exchanges the top of the stack into Register Pair HL and immediately overwrites Register Pair HL, discarding the address the restart pushed, so its own RET returns to whoever called this stub. Three bytes are therefore a complete subroutine, and it returns the sixteen-bit hash in Register Pair HL.
SYS2's entry dispatcher recognises sub-functions 10H, 20H, 40H, 50H and 60H and returns without doing anything for 00H, so this request performs no service at all. The only effect is that the overlay is read into 4E00H and becomes resident, which is the whole purpose: the open and initialise services are then in memory before the operator starts swapping disks.
6C71H - Collect Eight Characters
One routine fills the eight-byte field that a password hash is taken over, from the command line when the MPW parameter supplied one and from the keyboard when it did not. Either way the field is blank filled to exactly eight characters and folded to upper case, because the hash is always computed over eight bytes and eight blanks are what an unpassworded disk hashes to.
6CBFH - Read a Drive's Geometry
Before either engine can do anything it has to know what kind of disk is in each drive, and the disk itself is the only authority. This routine proves the unit is fitted, gets the head to a known cylinder, waits out a full index-hole revolution to prove the disk is actually turning, reads the Granule Allocation Table, and unpacks the configuration word into the Drive Control Table entry that Register Pair IY points at. Two of the bits it plants are worth naming: bit 7 of the second specification byte becomes the marker for a disk whose cylinder 0 is single density while the rest is double, and bit 5 becomes the double-sided flag. It also writes the resident per-drive extended-dating flag, and it does that with an instruction it computes at run time.
Store the computed byte at 6D25H, which is the second byte of the CB-prefixed instruction at 6D24H. One instruction body therefore serves all eight drives in both directions.
Execute the instruction just built, which sets bit [drive] of 475DH when this pack is in the x.3 extended date format and clears it when it is not. This is the writer's side of the flag that DATECONV/CMD exists to make meaningful, and the test at 6D61H is the reader's side.
6D4FH - Poll for the Index Hole
A short routine with an unusual exit. It reads the controller status and reports the index-hole bit, but if the deadline in Register D has arrived it discards its own return address, which abandons the caller's waiting loop as well as itself. That is what turns an empty drive into a device-not-available error instead of a hang.
6D61H - Test the Extended Dating Flag
The reader's half of the computed-instruction pair. The drive number is turned into the second byte of a bit-test instruction, planted, and executed against the resident flag byte, so that one instruction body answers the question for any of eight drives.
Store the computed byte at 6D72H, the second byte of the CB-prefixed instruction at 6D71H.
Execute the instruction just built, testing bit [drive] of the flag byte. The Z FLAG comes back set when the bit is clear, meaning this pack is in the old date format, and clear when the pack is in the x.3 extended format.
6D74H - The Parameter Table and the Front End Message Block
The last 686 bytes of the image are data. The parameter table comes first, in the standard LDOS @PARAM form: a six-character blank-padded keyword followed by the two-byte address of the cell the value is to be written into, repeated until a zero byte ends the list. A long form and its abbreviation are separate entries pointing at the same cell, and the cells themselves are scattered - some are in the small data run at 5200H and some are the operand fields of instructions out in the front end. Everything after the table is text belonging to the front end, which is why it survives in place: unlike the two engines the front end is never moved.
The source disk's master password. The cell is the operand of the load at 5C93H, so a password given here is found by the check at 5C89H and no prompt is issued.
Include system files. Folded into the attribute selection byte at 5AD9H and, at 6354H, taken as the instruction to reserve the fifteen system directory slots on the destination.
The abbreviation for SYS, pointing at the same cell.
Include files that are invisible in the directory. The cell is the operand of the load at 5AE3H.
The abbreviation for INV, pointing at the same cell.
Copy only files modified since the last backup, tested against bit 6 of directory offset 01H at 6440H.
The abbreviation for MOD, pointing at the same cell.
Ask before each file is copied. The high half of this cell is also cleared at 65EFH when the operator answers the prompt with C, which turns querying off for the rest of the run.
The abbreviation for QUERY, pointing at the same cell.
Allow the backup to run with no system disk in drive 0. Tested at 52C9H and again at 6C22H, where the four overlays the file-by-file path needs are checked for residency.
Select files by modification date. The cell is the operand of the load at 5AA1H and holds the address of the quoted string, which the parser at 5CCCH takes apart.
The abbreviation for DATE, pointing at the same cell.
Copy only files that are not already on the destination, decided at 651DH by whether the open reports file not found.
The abbreviation for NEW, pointing at the same cell.
Copy only files that already exist on the destination, the opposite test on the same open.
The abbreviation for OLD, pointing at the same cell.