Classic Computer Magazine Archive COMPUTE! ISSUE 65 / OCTOBER 1985 / PAGE 79

Save With Replace:
     Debugged At Last
Part 1

P. A. Slaymaker

Since the early days of the Commodore PET in the late 1970s, a controversy has raged over one particular disk command-Save-with-Replace. This convenient command automatically replaces an existing disk file with a new file of the same name, combining SCRATCH and SAVE in one operation. But for years, many Commodore users have shunned Save-with-Replace like poison, swearing that it contains a mysterious bug which unpredictably scrambles disks. And just as many other users contend the bug doesn't exist at all. Now, finally, there's proof: The bug does exist in the 1541 drive, it can be demonstrated, and most importantly, it can be avoided. This two-part article is the first full explanation of why the bug happens and how you can circumvent it. The author is the president of Quantum Software, which produces the Peek a Byte disk utility for the Commodore 64.


It's time to settle something once and for all: There is a Save-with-Replace bug! It afflicts the Disk Operating System (DOS) built into every 1541 disk drive, potentially threatening every disk on which you use the Save-with-Replace command. In this two-part series, we'll review what the Save-with-Replace bug typically does; list a program which demonstrates the bug beyond doubt; explain why it happens; and finally, recommend a procedure for avoiding the bug.
    The Save-with-Replace command (typed as SAVE@) has been accused of scrambling, swapping, duplicating, or overwriting disk files and of messing up Block Allocation Maps (a BAM is a map on a disk which keeps track of which blocks are storing files and which are free). Many computer magazines and other authorities in the Commodore community have warned against using SAVE@. Yet other Commodore experts have never experienced problems with SAVE@ and swear the bug is an old hacker's tale. There are many anecdotes about when the bug strikes, which files are affected, and when the files or BAM will be garbled. The mystery has persisted for so long because usually the bug is not repeatable. But this article shows how to replicate the bug and explains why it is related primarily to the file length and the distribution of free blocks on the disk as determined by the BAM.
    Recently some new evidence surfaced about SAVE@. In an article published in the July 1985 issue of The Transactor, "SAVE with Replace Exposed!!," author Charles H. Whittern showed that the bug exists under some conditions. This article made some observations on files likely to be affected and listed a program which repeatedly loaded and saved files using SAVE@. Afterward, an examination of the disk showed some files to be scrambled. Unfortunately, no details of the file configurations were given, and the editors admitted the bug had them baffled-but at least the problem was recognized, a first step.
    Our investigation shows that the bug usually occurs when the drive number has not been specified on previous drive operations, such as loading a file or listing a directory. In other words, typing LOAD"filename",8 or LOAD"$",8 instead of LOAD"0:filename",8 or LOAD"0: $",8 sets up conditions for the bug. The drive number 0 should be specified in disk commands because, as we'll explain later, the SAVE@ bug is related to the phantom software drive 1 in the 1541. In addition, the bug tends to bite disks on which many files have been scratched and rewritten. This leaves gaps on the disk so that a file is scattered over many tracks. These gaps do not normally cause a problem if you specify the drive number in disk commands.
    Therefore, the key to avoiding the SAVE@ bug is to always specify drive 0 when performing any disk drive function, or to always reset the drive before any SAVE@ operation. Resetting the drive requires either turning the drive off and then on, or sending a reset command (OPEN15, 8,15,"UJ").

Demonstrating The Bug
At this point, some of you might be skeptical that the SAVE@ bug really exists. To prove that it does, the accompanying program formats a new disk with the single file "SAVE@ DEMO" and alters the BAM to simulate a partially used disk with a gap due to scratched files. Follow these instructions carefully:

1. The program is for the Commodore 64. For the VIC-20, change these lines:

10 POKE 36879,8         :rem 5
100 IF K<>39 THEN 90: REM WAIT
     FOR F1           :rem 154
150 IF K=11 THEN 170: REM CONT
    INUE IF <Y>       :rem 187

    For the Plus/4 and 16, change these lines:

10 COLOR 0,1:COLOR 4,l:rem 183
340 POKE 239,0: REM CLEAR KEYB
    OARD BUFFER        :rem 80
5000 POKE239,0:POKE198,64: REM
      CLEAR KEYBOARD BUFFER
                       :rem 31
5010 K=PEEK(198)      :rem 102

Type the program exactly as listed-including all uppercase REM statements (the lowercase rem statements are checksums for COMPUTE!'s "Automatic Proofreader"; do not type them in). It's important to type the program as listed because it must be at least nine blocks long on the test disk to insure proper results.

2. Save the program on another disk before running it.

3. Put a blank test disk in the drive and run the program. It will format the disk and save a file called SAVE@ DEMO on the disk. Type LOAD"$",8 to list the directory and notice that 254 blocks are free.

4. Reset the drive by turning it off, then on. Load the file by typing LOAD"SAVE@ DEMO",8.

5. Save the file three times using the SAVE@ command (SAVE"@0: SAVE@ DEMO",8). Do not list the directory or perform any other operation between SAVE@ commands.

6. List the directory by typing LOAD"$",8. What's this? There were 254 blocks free before, but now there are 258-a discrepancy of four blocks. (If you don't get this result, it probably means that you haven't followed the directions exactly. Start again at step 3.) If you examine the BAM with a disk utility, you'll see that the first four sectors of the file are marked as free! (Specifically, the file starts on track 17, block 0; blocks 0 through 3 are marked as unallocated.) If you executed a fourth SAVE@ command, it would overwrite the beginning of the file, and the disk would be corrupted even worse!

7. Now rerun the program to make a new test disk. Reset the drive and run the above test again, but specify the drive number for the load (LOAD"0:SAVE@ DEMO",8). The SAVE@ bug does not occur!

Always Specify Drive 0
This demonstration provides a powerful lesson: All DOS commands should include the drive number 0:

LOAD"0:filename",8 (Load file)
SAVE"0:filename",8 (Save file)
SAVE"@0:filename",8 (Save with Replace)
LOAD"$0",8 (Load directory)
LOAD"$0:filename",8 (Loan directory
  entry with filename)
OPEN15,8,15,"I0":CLOSE15 (Initialize
  drive 0)
OPEN15,8,15,"V0":CLOSE15 (Validate
   BAM)

    Similarly, all disk file commands should specify the drive number.
    Most Commodore users do not specify the drive number when loading the directory or files. The 1541 User's Manual examples for the LOAD command don't specify the drive, and neither do most magazine articles. If the drive number is not specified, the 1541 is supposed to default to drive 0. What actually happens very often causes an error message such as 74,DRIVE NOT READY,00,00. For a simple example, use the DOS 5.1 Wedge that comes with the 1541. List the directory for the file "TEST" on the 1541 Test/Demo disk by using the Wedge command:

>$TEST (list directory for files "TEST")

    Since this file doesn't exist on the Test/Demo disk, the red error light begins blinking. This command should include the drive number, but is accepted without it. Now repeat the command and read the error channel with this Wedge command:

> (read error channel)

    The error will be 74,DRIVE NOT READY,00,00. Repeat this test, but specify the drive number:

>$0:TEST (List directory with drive specified)

    No matter how many times this command is repeated, no error will occur.

The Missing Drive
Part 2 in next month's COMPUTE! will present a full technical explanation of the SAVE@ bug. For those who aren't so technically inclined, here's a brief summary.
    The early Commodore PETS were available with dual disk drives-two drives in one unit. The drives were addressed as 0: and 1: when using disk commands. But on later Commodore computers designed to use the 1540/1541, multiple drives are addressed by changing the device number, not the drive number. The device number for a single drive is 8. That's why you type a command like LOAD "filename",8. On two-drive systems, the second drive is usually addressed as device 9, as in LOAD "filename",9. Therefore, most people stopped (or never started) specifying the drive number, which is 0: for all 1541 disk drives. Drive 1: simply doesn't exist with the 1541.
    What happens when the drive number is not specified for a LOAD or SAVE? DOS first checks for a drive number. If none is specified, it assumes drive 0. Okay so far. Then DOS attempts to read the disk. If no disk is found, DOS automatically switches to the nonexistent drive 1. A DRIVE NOT READY error then results whether or not a drive number was specified. If a disk is found, DOS searches its internal directory for the specified file. If the default drive was used, DOS switches to drive 1 to continue searching. This also causes the DRIVE NOT READY error, since there is no drive 1. Furthermore, drive 1 remains the default drive as long as there are directory searches to be done. The internal drive pointers must be reset to recover from this error condition.
    SAVE@ always works properly in our tests if the drive number is specified on all operations and no direct access buffers are allocated. We are not aware of anyone who has documented a failure under these conditions (assuming a closed file was specified, sufficient room was present on the disk, and no read or write errors occurred). Thus, Commodore experts who claim there is no bug are partially correct. We have also found that if the drive number is not always specified during loads and directory listings, as is common practice, the SAVE@ bug can occur even though the drive number is specified in the SAVE@ command.
    Files stored on just one or two tracks-such as short files on a fresh disk-are not prone to the SAVE@ bug. Files stored over many tracks on disks on which many files have been saved and scratched are the most susceptible, as are files saved with some utilities intended to speed up the 1541 disk drive.

Next month: Part 2 examines the technical reasons for the Save-with-Replace bug in more detail. Our special thanks to Jim Gracely of Commodore and Associate Editor Jim Butterfield for very helpful discussions.


SAVE@ Bug Demonstration
For instructions on entering this listing, please refer to "COMPUTE!'s Guide to Typing In Programs" published bimonthly in COMPUTE!.

10 POKE 53281,0:POKE 53280,11
                      :rem 232
20 PRINT"{CLR}";CHR$(14)CHR$(8
                       :rem 66
30 PRINT"{YEL}{RIGHT}{RVS} SAV
   E@ BUG EXAMPLE "    :rem 90
40 PRINT"[CYN}{DOWN) THIS PROG
   RAM FORMATS":PRINT"A BLANK
   {SPACE}DISK, ALTERS"
                      :rem 167
50 PRINT"THE BAM, SAVES ITSELF
   ":PRINT"AND THEN ALTERS THE
   "                  :rem 149
60 PRINT"BAM AGAIN.":PRINT"
   {DOWN} SAVE@ WILL FAIL THE"
                      :rem 213
70 PRINT"THIRD TIME IT IS USED
   ":PRINT"ON THIS DISK."
                      :rem 133
80 PRINT"{DOWN}{RIGHT}{GRN}INS
   ERT DISK TO FORMAT - PRESS
   {SPACE){RVS} F1 {OFF)."
                      :rem 116
90 GOSUB 5000: REM GET KEYPRES
   S                   :rem 34
100 IF K<>4 THEN 90: REM WAIT
    {SPACE}FOR F1      :rem 98
110 PRINT"{DOWN}{RED}WARNING!
    {SPACE}THE DISK WYLL BE ER
    ASED."            :rem 116
120 PRINT"{DOWN}{RIGHT}{YEL}AR
    E YOU SURE?":PRINT" (PRESS
    {SPACE}{RVS}Y{OFF} TO CONT
    INUE.)"            :rem 31
130 FOR T=0 TO 100:NEXT: REM T
    IME DELAY         :rem 165
140 GOSUB 5000: REM GET KEYPRE
    SS                 :rem 78
150 IF K=25 THEN 170: REM CONT
    INUE IF <Y>       :rem 192
160 PRINT"{DOWN}{RIGHT}{YEL}PR
    OGRAM ABORTED.":GOTO 330
                        :rem 4
170 CLOSE2:CLOSE15: REM
    {2 SPACES}CLOSE CHANNELS
                       :rem 54
180 OPEN15,8,15: REM OPEN COMM
    AND CHANNEL       :rem 111
190 PRINT"{DOWN}{RIGHT}{CYN}NO
    W FORMATTING DISK - PLEASE
     WAIT."            :rem 28
200 PRINT#15,"N0:SAVE@ TEST"CH
    R$(44)"PS": REM FORMAT DIS
    K                  :rem 50
210 GOSUB 3000: REM CHECK ERRO
    R CHANNEL         :rem 213
220 PRINT"{UP}{RIGHT}{PUR}FORM
    ATTING COMPLETED.
    {2 SPACES}{3 SHIFT-SPACE}
    {8 SPACES)"       :rem 213
230 PRINT"{DOWN}{RIGHT}ALTERIN
    G BAM."           :rem 232
240 GOSUB 4010: REM OPEN DIREC
    T CHANNEL AND CHECK ERROR
    {SPACE}CHANNEL    :rem 147
250 GOSUB 1010: REM ALTER BAM
                       :rem 63
260 CLOSE2:CLOSE15: REM CLOSE
    {SPACE}CHANNELS    :rem 54
270 PRINT"{DOWN}{RIGHT}{RED}SA
    VINE SAVE@ DEMO." :rem 190
280 SAVE"0:SAVE DEMO",8
                      :rem 111
290 PRINT"{DOWN}{RIGHT}{YEL}AL
    TERING BAM."      :rem 140
300 GOSUB 4000: REM OPEN DIREC
     T CHANNEL AND CHECK ERROR
     {SPACE}CHANNEL   :rem 143
310 GOSUB 2010: REM ALTER BAM
                       :rem 61
320 PRINT"{DOWN}{RIGHT}{CYN}
    {TAB}DISK IS FINISHED! NOW
     REFER TO TEXT."  :rem 236
330 CLOSE2:CLOSE15: REM CLOSE
    {SPACE}CHANNELS    :rem 52
340 POKE 198,0: REM CLEAR KEYB
    OARD BUFFER        :rem 84
350 END               :rem 111
1000 REM * MODIFY BAM SECTOR F
     OR SAVE           :rem 77
1010 PRINT#15,"U1:2 0 18 0":GO
     SUB 3000: REM READ BAM SE
     CTOR             :rem. 90
1020 PRINT#15,"B-P:2 52":GOSUB
      3000: REM POSITION BUFFE
     R POINTER TRACK 13
                      :rem 159
1030 FOR I=1 TO 20:PRINT#2,CHR
     $(0);:NEXT: REM FILL BAM
     {SPACE}WITH ZEROS:rem 201
1040 PRINT#15,"B-P:2 76":GOSUB
      3000: REM POSITION BUFFE
     R POINTER TRACK 19
                      :rem 173
1050 FOR I=25 TO 92:PRINT#2,CH
     R$(0);:NEXT: REM FILL BAM
      WITH ZEROS      :rem. 10
1060 PRINT#15,"U2:2 0 18 0":G0
     SUB 3000: REM WRITE TO BA
     M SECTOR         :rem 114
1070 PRINT#15,"I0":GOSUB 3000:
      REM INITIALIZE BAM
                       :rem 36
1080 RETURN           :rem 169
2000 REM * MODIFY BAM SECTOR A
     FTER SAVE        :rem 217
2010 PRINT#15,"U1:2 0 18 0":GO
     SUB 3000: REM READ BAM SE
     CTOR              :rem 91
2020 PRINT#15,"B-P:2 60":GOSUB
      3000: REM POSITION BUFFE
     R POINTER TRACK 15
                      :rem 161
2030 REM FREE UP 12 SECTORS ON
      TRACKS 15 TO 17 :rem 204
2040 PRINT#2,CHR$(4)CHR$(15)CH
     R$(0)CHR$(0);     :rem 81
2050 PRINT#2,CHR$(4)CHR$(15)CH
     R$(0)CHR$(0);     :rem 82
2060 PRINT#2,CHR$(4)CHR$(15)CH
     R$(0)CHR$(0);     :rem 83
2070 PRINT#15,"U2:2 0 18 0":G0
     SUB 3000: REM WRITE TO BA
     M SECTOR         :rem 116
2080 PRINT#15,"I0":GOSUB 3000:
      REM INITIALIZE BAM
                       :rem 38
2090 RETURN           :rem 171
3000 INPUT#15,EN,E$,ET,ES
                      :rem 185
3010 IF EN=0 OR EN=73 THEN RET
     URN               :rem 61
3020 PRINT"{2 DOWN}{RIGHT}"EN;
     E$;ET;ES         :rem 179
3030 CLOSE2:CLOSE15:END
                      :rem 149
4000 OPEN15,8,15:GOSUB3000: RE
     M OPEN COMMAND CHANNEL AN
     D CHECK ERROR    :rem 210
4010 OPEN2,8,2,"#":GOSUB3000:
     {SPACE)REM OPEN DIRECT CH
     ANNEL AND CHECK ERROR CHA
     NNEL             :rem 179
4020 RETURN           :rem 166
5000 POKE198,0:POKE203,64: REM
      CLEAR KEYBOARD BUFFER
                       :rem 22
5010 K=PEEK(203)       :rem 89
5020 IF K=64 THEN 5010 :rem 61
5030 RETURN          :rem, 168