CDOS is the Cromemco Disk Operating System. It was patterned after CP/M; but it isn’t really CP/M. In most cases the differences don’t matter or, IMHO, work in CDOS’s favour. However, with R.COM and W.COM, there are some examples where it doesn’t.
We always knew that CDOS and CP/M weren’t the same and, in the late 1970s, we also knew a lot better than today what the differences were. We still remember the obvious ones; but more subtle ones exist too. Running R2.COM under CP/M in z80sim worked great. Running it under CDOS produced some strange results. This is what happened.
Existing file deleted
Everytime I’d read (R2) a file, it would tell me it had deleted the existing file first. That’s fine if you have an existing file in CP/M that is being overwritten by the new one. That isn’t so fine, and is in fact troubling, if it tells you it has been deleting things when you don’t have an existing file.
The SPL for R2 for this bit looks like:
IF BDOS(deleteFileCmd, @defaultFCB) AND 0FFH <> cpmError THEN printString(@originalFileDeleted) ENDIF;
If the CP/M delete doesn’t return a value of 255 (“cpmError”), then it says it deleted the file. This is a perfectly reasonable approach. Under CP/M, per the Ver2.2 manual, it “returns a decimal 255 if the referenced file or files cannot be found; otherwise, a value in the range 0 to 3 returned”.
However, the CDOS2 manual for the same function says of the return value, “A contains the number of deleted directory entries”. In this case, a value of 0 means none were deleted. When you run R2.COM (or R.COM) under CDOS, you will always get told that non-existent files have been deleted.
It’s not as neat as the original, but I had to change this to:
IF BDOS(openFileCmd, @defaultFCB) AND 0FFH <> cpmError THEN BDOS(closeFileCmd, @defaultFCB); BDOS(deleteFileCmd, @defaultFCB); printString(@originalFileDeleted) ENDIF;
This one works under both CP/M and CDOS.
Existing files not found
Swapping to the W2.COM (or W.COM) program showed some interesting problems when writing a series of files to the host (ie Windows) environment. It was saying that files that were right there, didn’t exist. Hey, it was even the one working out the list of files that it then said didn’t exist. For my example I was doing “W2 D*.*” to write files DUMP.COM, DEBUG.COM and DISKCOPY.COM into my Windows directory. They were in the CP/M directory. I could W2 each one by itself. It identified all three of them, but couldn’t open any of them.
The original WRITE.SPL program looks like this:
CPM2(searchForFirstCmd, @defaultFCB); WHILE cpmResult <> fileNotFound DO topName^:[fullFilenameLength] := buffer[32 * cpmResult + 1]:[fullFilenameLength];
Under CP/M 2.2, searchForFirst returns a directory entry number 0-3 and the current disk buffer address contains the directory sector that has the found directory entry. The program exits if cpmResult is 255 (“fileNotFound”). Otherwise, it uses the value 0-3 to find the directory entry in the buffer (each entry is 32 bytes long) and adds 1 to point to the name in the directory entry (to see which file was found, eg “DUMP.COM” after a search for “D*.*”).
Under CDOS 2, the same function returns “the block number if the file is found” or 255 if not. It sounds the same, and – whilst not stated – the disk buffer address does also contain the directory sector that has the found entry. All seems easy with this one; until you look at what comes back as the block number. It is 0-3 like CP/M; but it can also be 4, 5, 6 and so on too. Under CP/M the return value is the directory entry in the found sector. Under CDOS, it is for the whole directory. For anything after the first 4 files, the program will point past the end of the buffer and get a name from random characters in the computer’s memory. It is no wonder it couldn’t find that.
The solution is mostly this:
CPM2(searchForFirstCmd, @defaultFCB); WHILE cpmResult <> fileNotFound DO cpmResult := cpmResult AND 3; {gss: cdos1 gives dir entry num 0-63} topName^:[fullFilenameLength] := buffer[32 * cpmResult + 1]:[fullFilenameLength];
The change converts a CDOS directory entry number (0-63) to what CP/M expects (0-3). I should point out that Cromemco included an extra return value, in HL, that already gives the address of the entry in the buffer. However, the existing BDOS function in SPL doesn’t provide access to HL and ANDing the return value with 3 was easier than writing an enhanced BDOS function for the language.
Whilst the solution is “mostly” the above, there is an exception. CDOS Ver 00.20 (and probably others near or before that version, if they exist) got the return value wrong. Maybe it was right at the time but someone later decided that directory entries are numbered 0-63. They were numbered, in CDOS Ver 00.20, as 1-64.
I haven’t come up with a reliable way of telling if we’re in CDOS or CP/M nor, therefore, a way of getting the CDOS version. There is a function call to GetCpmVersion – that wasn’t implemented in CP/M 1 and that does a disk reset in CDOS. There is a function call to GetCdosVersion that wasn’t implemented in CDOS 0.X and, of course, isn’t implemented in CP/M. Checking specific bytes within the OS (does this say “00.20” or cause that to be said?) is messy. In the end, I built a different version of W2.COM for CDOS0020. I include that in my builds for that OS and version; and the normal one in everything else. The only changes in the CDOS0020 one are:
– cpmResult := cpmResult -1; {to get 1-64 to 0-63}
– the signon message includes “for CDOS0020”
Only one file found
Under CP/M, the SearchForNext function requires no parameters. It just finds the next match from last time.
Under CDOS, the FindNextDirectoryEntry requires the original File Control Block (FCB) to be passed in again.
My solution is to pass @defaultFCB as a parameter to the searchForNextCmd call. CP/M will ignore it. CDOS will use it.
Strange Filenames
The other gotcha that isn’t mentioned in the CDOS manuals I saw, is a FileOpen reads a directory sector into the current disk buffer address. This doesn’t happen under CP/M (it probably uses an internal buffer). This shouldn’t be a problem. However, Peter was saving the command line in the buffer address and restoring it with a FileOpen in the middle. The command line has the path in it and it’s needed whenever the program says ‘Write “name” to “path\name”.’ so it has to be saved somewhere. I just added another cmdbuffer and saved to and restored from that.
Downloads
I’ve added the original source code, my updated versions and the compiled (for CP/M or CDOS) programs; in cpm/z80sim_rw/. You’ll need a recent one of my z80sim builds (pref 0.13 or later) to use them.
This is part of the CP/M topic.