URI:
                                 CBM DOS
       
       
       The Commodore 8-bit computers (PET, VIC-20, 64, 128, etc.)
       did not have a Disk Operating System (DOS). Instead the DOS
       resided on a disk drive itself, and the computer simply
       sent commands to the disk drive to read, write, scratch
       (remove), list, etc. files. Most commonly this was done
       over the IEC serial bus (multiple devices can be connected
       to it in parallel, but only one device can talk at a time).
       (The serial bus on the C64 is (in)famously slow, at around
       400 bytes per second. Many techniques exist to increase the
       speed.)
       
       This is a work in progress and exists chiefly for my own
       reference. Most of the information is obtained from other
       resources available online, and some is from my personal
       experiments. Caveat emptor.
       
       ------------------------------------------------------------
                             Command channel
       ------------------------------------------------------------
       
       The command channel is on channel 15. (A channel is the
       secondary address of IEC, the primary address being the
       device number itself.) This channel is used for sending
       commands and receiving error codes.
       
       After each command or file open operation, the command
       channel can be read. It is in a CSV format: error number,
       error, track, sector. Each field except the second (error)
       is a two-digit decimal value. After a successful operation
       all values are `00`, and the error is `OK`.
       
       ------------------------------------------------------------
                              Other channels
       ------------------------------------------------------------
       
       Besides the command channel, channel 0 is used for loading
       programs and channel 1 is used for saving programs. If
       a file is not a program (PRG), its type can be given
       when loading, and it will load as a program, e.g., `load
       "file,s", 8` will load a sequential (SEQ) file as a program.
       If the file actually is a program, it will be loaded and run
       the same as if it were a PRG file.
       
       The other channels (2 through 14) are available for opening
       any file type.
       
       ------------------------------------------------------------
                             CBM DOS Commands
       ------------------------------------------------------------
       
       All commands are sent to the device over the command
       channel.
       
       The 1541 (and also 1571 and 1581 and possibly others)
       support only a single partition and no subdirectories. Some
       commands (particularly the partition and directory commands)
       are derived from the programming manual for the CMD HD
       drives. The 1541 seems to ignore the path in all commands
       that it supports.
       
        * Items in brackets `[]` are optional.
       
        * `n` is the medium number, typically a partition number
          (sometimes a drive number in dual-drive devices)
       
        * `path` is an absolute (with a `/` preceding all path
          segments) or relative (without a `/` before the first
          segment) path to the file. A relative path is relative to
          the current directory. The root path is a blank string.
          If included in a command, it must be enclosed in `/`
          characters.
       
        * `name` is a directory or file name
       
        * `newname` and `oldname` are new and old file names (in
          rename and copy commands)
       
       Directory and file names have restrictions:
       
        * Cannot be longer than 16 bytes
       
        * Cannot contain some special characters (`,=?*"`) (perhaps
          others)
       
       Also, at least on the CMD HD drives, the command input
       buffer is 254 bytes long which limits the total length of a
       path name (plus file name) that can be used with it.
       
       Since path segments are separated by `/`, it follows that
       directory names cannot contain `/`. But a file name might.
       The 1541 certainly allows `/` characters in file names.
       Perhaps this is why CMD HD uses the path syntax that it
       does? (A directory name might be able to contain a `/` on
       the CMD HD, but if you do that you’re gonna have a bad time.
       You’ll have to change to that directory, rather than use
       absolute paths, to access files or further subdirectories
       under it.)
       
       “Variables” such as `n` and `path` are encloded in braces
       `{}` to distinguish them from literal text in commands.
       
       Commands are shown here in ASCII; in PETSCII in the default
       shift state, lowercase letters appear in uppercase, and
       uppercase letters appear as various graphics glyphs (e.g.,
       box-drawing lines).
       
       
       # Format a disk
       
       `n[{n}]:{diskname},{id}`
       
       Note: disk name is limited to 16 characters, and ID must be
       exactly 2 characters long.
       
       
       # Initialize drive
       
       `i[{n}]`
       
       
       # Make directory
       
       `md[{n}][/{path}/]:{name}`
       
       
       # Change current directory
       
       `cd[{n}][[/{path}/][:]{name}]`
       
       or
       
       `cd[{n}]_` to go up a directory
       
       Note: `_` is a left arrow glyph in PETSCII.
       
       Also note: each partition has its own current directory, so
       changing to a different partition (see below) will place you
       in its current directory.
       
       
       # Remove directory
       
       `rd[{n}]:{name}`
       
       Note: current directory must be in the parent of the
       directory to remove (use the “change current directory”
       command above), and the directory must be empty.
       
       
       # Scratch (remove) file
       
       `s[{n}][/{path}/]:{name}`
       
       
       # Rename file
       
       `r[{n}][/{path}/]:{newfile}=[[{n}][/{path}/]:]{oldname}`
       
       Note: both file locations must be on the same partition.
       
       
       # Copy file
       
       `c[{n}][/{path}/]:{newfile}=[[{n}][/{path}/]:]{oldname}`
       
       Note: file locations may be on different partitions.
       
       Also note: this is actually a “concatenate” command,
       supporting up to 5 files to the right of the `=` separated
       by `,`.
       
       
       # Lock/unlock file
       
       `l[{n}][/{path}/]:{name}`
       
       Note: toggle lock on file
       
       
       # Change current partition
       
       `cp{n}` or `cP{nn}` (where `nn` is `n` encoded as an 8-bit
       value)`
       
       Note: partition number `n` ranges from 1 to 254 (possibly
       255, but CMD HD user docs say 254). Partition 0 is a special
       system partition which contains a directory of partitions
       (partition numbers are shown where file sizes normally are
       found in a directory).
       
       ------------------------------------------------------------
                        Reading and writing files
       ------------------------------------------------------------
       
       Unlike modern operating systems which allow a program to
       open a file for read with update and to seek within a file,
       CBM DOS treats files more or less as streams of data to read
       from start to finish or to write completely from start to
       finish (though the append mode allows writing more data at
       the end of a file).
       
       Then again, CBM DOS does support one file type (relative)
       that allows for arbitrary reads and writes and positioning
       the read/write pointer within the file. Unfortunately it’s
       record-based with fixed-sized records (the record size is
       specified when creating a relative file), but it may be
       useful for a database type of application.
       
       Here is a (hopefully complete) list of ways to open a file
       with CBM DOS:
       
        * Read (file MUST exist): `...,{type}[,r]`
       
        * Write (file MUST NOT exist): `...,{type},w`
       
        * Overwrite (file MAY exist): `@...,{type},w` (if file does
          not exist, this is the same as the “write” method)
       
        * Append (file MAY exist): `...,{type},a`
       
        * Read/write relative (file MAY exist): `...,l,{recsize}`
          (where `recsize` is encoded as an 8-bit value, not ASCII
          or PETSCII; it must be between 1 and 254 inclusive)
       
       (Only the regular “read” and “write” methods have
       restrictions on the existence of a file—the file must exist
       for “read” and must not exist for “write”.)
       
       File types (values of `type`):
       
        * Program (PRG): `p`
       
        * Sequential (SEQ): `s`
       
        * User (USR): `u`
       
        * Relative (REL): `l`
       
        * Any: `m`
       
       A few notes:
       
        * File names are omitted above for clarity. Replace `...`
          with `[[{n}][/{path}/]:]{name}` for the actual file name
          part.
       
        * File type `type` and its leading comma (and also
          `recsize` and its leading comma for relative files) can
          be omitted if the file already exists; it doesn’t hurt to
          always include these, though.
       
        * File type `type` and mode can be swapped for all types
          except relative; note that if the type is omitted and
          only `w` (write) mode is given and the file does not
          exist, a new DEL (deleted) file will be created. In
          fact, a new deleted file will be created each time it is
          opened, even if one by the same name already exists. Of
          course, you can’t open the file after it’s created, but
          you can remove (scratch) it. This “feature” can be used
          to create placeholder files in a directory listing (they
          can be used as visual separators or whatnot), but note
          that each deleted file occupies at least one block (and
          you can actually write data to the file which can consume
          more than one block).
       
        * The special type `m` can be used to open an existing file
          of any type (it cannot be used to create a file).
       
        * A “splat” file can be opened only if type `m` is used.
          (A splat file is one which the disk drive believes is
          already open and may be in an inconsistent state until
          it’s closed, so the drive doesn’t normally allow one to
          be opened. An asterisk `*` (aka a “splat”) appears before
          the file’s type in a directory listing.)
       
        * To position the read/write pointer for an open relative
          file, send `p{channel}{record_lo}{record_hi}{offset}`
          to the command channel; all variables are encoded as
          8-bit values (`channel` is the channel number of the open
          relative file, plus 96). Record number and offset are
          1-indexed.
       
       
       # Relative files
       
       Relative files are a somewhat obscure feature. As mentioned
       above they may be useful for storing a database with
       fixed-size records.
       
       Reading a “normal” (program, sequential, or user) file byte
       by byte results in a status (as in the BASIC `ST` special
       variable, or the return value from the `READST` ML routine)
       value of 64 on the last byte which means “end of file”.
       Doing the same with a relative file results in a status of
       64 on the last byte of each record. Continuing to read will
       read starting at the first byte of the next record.
       
       I found that if one creates at least one record, the DOS
       will create records for the remainder of the 254-byte block
       it’s in (as many as will fit completely within the block);
       reading those other records in the block results in a
       single byte value of 0xff and a status of 64. Attempting to
       read records past the last block results in a single byte
       value of 0x0d (carriage return) and a status of 64 (and the
       command channel reports error 50, "record not present").
       
               ----------------------------------------------------
               The Commodore 64 sets the “end of file” I/O status
               flag when the last byte is read rather than when
               attempting to read past the end of a file. If you’re
               coming from a C background (which had existed for
               several years before Commodore sold their first
               computer, the Commodore PET, and disk drive) or any
               modern language with a compatible file I/O system,
               this may look foreign to you. The limitation is
               that a file is always at least one byte in size. If
               nothing was written to the file it will contain a
               single carriage return.
       
               On the other hand, attempting to read from RS-232
               serial will report no bytes available if the receive
               buffer is empty, so at least there’s that.
               ----------------------------------------------------
       
       ------------------------------------------------------------
                           Listing a directory
       ------------------------------------------------------------
       
       To get a directory listing, open a file named
       `$[{n}][/{path}/][:{pattern}]` and read its contents until
       the end of the file. The pattern `pattern` may contain
       a question mark `?` to match any single character in
       its position and an asterisk `*` to match zero or more
       characters in its position (old disk drives support only one
       asterisk while modern drives support more than one).
       
       The directory listing is formatted as a BASIC program which
       can be listed with the `LIST` command. The first two bytes
       are the load address (always 0x0401). Following that is zero
       or more lines. Each line contains a two-byte address of the
       next line; a zero address indicates this is the end (the
       last byte of a zero next-line address should coincide with
       reading the end of the file). After a non-zero address is
       a two-byte line number, followed by the line’s contents[1]
       and a zero byte (null terminator). All two-byte values
       (addresses and line numbers) are little endian (low byte
       first).
       
       Lines are formatted as follows:
       
        1. The first line is the directory header. This line uses a
           reverse-video character before the header to display it
           in reverse video.
       
        2. Each file is listed; the line number is the file size
           (in blocks), the name is quoted, and then the type is
           last.
       
        3. The last line contains the number of blocks free (again
           using the line number) followed by the text `blocks
           free.`.
       
       Technically speaking, lines can be arranged in any order in
       memory, since next-line addresses are used to point to the
       next line as a linked list. However, a well-behaved drive
       should always format the lines sequentially in memory so
       that a program reading a directory listing can ignore the
       exact value of the next-line address (except when it’s zero
       for the end of the listing).
       
       The file name in each entry is enclosed in quotes. A file
       name may contain shifted-space (0xa0) characters; in that
       case the closing quote is placed at the first shifted
       space. Anything following a shifted space is ignored when
       matching an existing file. (I suspect shifted spaces are
       normally used as padding for names shorter than 16 bytes.)
       A file name may also contain quote characters. A file entry
       therefore must be parsed with care:
       
        1. Read up to and including the first quote character.
       
        2. Read the following 17 bytes.
       
        3. Find the last quote in the string.
       
        4. That is one character past the end of the file name.
       
       If a file name contains a quote after a shifted-space,
       I don’t see how such an entry can be parsed, as it’s
       indistinguishable from a name with a quote in place of the
       shifted space and without a following quote. In that case,
       you’re SOL. (Maybe this “feature” can be used to make a file
       unloadable by most mortals.)
       
       ------------------------------------------------------------
                                 Sources
       ------------------------------------------------------------
       
        * Commodore 1541[2]
       
        * CMD HD Manual Remaster[3] or CMD Hard Driver Users
          Manual[4]
       
        * LOAD"$",8[5]
       
       ------------------------------------------------------------
                         References and Footnotes
       ------------------------------------------------------------
       
       [1] BASIC programs are tokenized, but for all intents and
           purposes directory listings are just text since they
           don’t use any BASIC keywords
       [2] https://www.c64-wiki.com/wiki/Commodore_1541
       [3] https://archive.org/details/cmd-hd-manual-remaster
       [4] http://primrosebank.net/computers/pet/documents/CMD-HDD-Manual_OCR.pdf
       [5] https://www.pagetable.com/?p=273