Notes on the implementation of CPMFS
------------------------------------

This file contains technical notes on the choices made and the methods used
in implementing CPMFS.  I have had to decide on certain behaviours for the
filing system in the face of ambiguous or non-existent documentation both
for RISC OS and CP/M, though in each case I have tried to come to sensible
decisions by experimenting.  The major cause of problems has been fitting
CP/M's way of looking at files into the structure provided by FileSwitch.

First there are a few comments on particular topics, then I run through
all the image filing system entry points with brief notes on the
implementation of each one.

I would be very pleased to have feedback from anyone who can understand this
document and has ideas for the improvement of the implementation.  Please
e-mail matthew.phillips@chch.ox.ac.uk


1. What areas of a CP/M disc are free?
--------------------------------------
In working out which disc blocks are allocated, only those directory entries
with user areas from 0 to 15 are examined.  All blocks in such directory
entries are regarded as allocated, even if the block would lie beyond the
end of the file as far as EX and RC are concerned.

User areas 16 to 31 were used in some versions of CP/M for ordinary files,
and in CP/M+ are used for password control.  User areas 32 and 33 are used
in CP/M+ for the disc label and datestamps.

A directory entry is only considered free for use if the user area is set to
E5h.


2. Sparse files
---------------
CP/M files can be "sparse".  A sparse file has gaps in its allocation, so
that, for example, the first 6k of the file may have disc space allocated,
the next 2k does not, and then the file is finished with a further 4k of data
with disc space allocated.  Some CP/M programs can create and use sparse
files, others cannot.  CP/M files are further allocated in "extents".  An
extent essentially consists of a directory entry, and can allocate up to
eight or sixteen blocks, depending on the number of blocks on the disc.
Holes can also exist through an extent in the middle of the file being
missing.

When reading data from a hole in a sparse file, CPMFS will return data
consisting of zero bytes.  Attempting to write to a hole generates an error.

Files lacking their first extent are not returned by CPMFS when listing the
files in a directory, but they may well exist as far as other entry points
are concerned!

The file lengths reported for sparse files are described later.


3. Datestamps
-------------
CP/M+ discs support three types of datestamp, the creation date, last update
date and last access date.  The disc label record contains flags indicating
which of these datestamps are in operation for that disc.  Enablement of the
creation and last access dates are mutually exclusive: either can be enabled,
or neither, but not both.  The update date can be enabled or disabled
independently.  When responding to requests from RISC OS to read datestamps,
CPMFS returns the update date if it is enabled.  If not, the creation or
access date is returned.  If no datestamps are enabled, a date of zero is
returned, which in RISC OS corresponds to midnight on 1 January 1900.  Note
that CP/M cannot store datestamps with dates before midnight on 1 January
1978, and stores dates only to the nearest minute.

CPMFS does not update the last access date except when writing files.

CP/M 2.2 did not have facilities for datestamps or a disc name, though
datestamps and disc names are preserved by normal CP/M 2.2 operations.  In
general there should be no problems reading a CP/M+ disc with datestamps
on CP/M 2.2.  There may be problems writing, but only because CP/M 2.2 may
regard some blocks as being allocated which are not really allocated at all.
This will mean that the disc may fill up sooner than it really should.

In order to avoid problems, when formatting certain types of disc, namely
ROMDOS D10, D2 and D20, the date is not stored in the second half of the
disc label as it normally would be, nor are datestamps enabled on the disc.
The disc label itself will not cause problems for AMSDOS or for CP/M 2.2,
but it is useful for RISC OS purposes to distinguish one floppy disc from
another.


4. File lengths
---------------
CPMFS stores the exact file length when creating files on CP/M discs, and
returns the exact file length (if available) when reading discs.  The
system used is the one described by John Elliott.  Because most CP/M
programs do not use this facility, you may need to be prepared for up to
127 bytes of garbage at the end of a CP/M file.

The exact file length is stored by means of a byte called the "last record
byte count".  Neither CP/M 2.2 nor CP/M Plus alter this byte automatically;
it is up to the CP/M program to do so if it wishes to use this facility.
This means that if you save a file to a CP/M disc using CPMFS, and if the
length of that file is subsequently changed by a program running under CP/M
2.2 or CP/M Plus, the last record byte count may not be updated.  This could
have the effect that when reading the file using CPMFS, not quite all of the
file is accessible.

This problem is inherent in the way exact file lengths were implemented
rather late in the day in CP/M.  If this aspect of the implementation
causes you any problems, please let me know.  It may be better to revert to
CP/M 2.2 practice, which specifies file lengths rounded up to multiples of
128 bytes.


5. Filetypes
------------
Unlike DOS discs, there is no spare space on a CP/M disc to store filetype
information.  It therefore has to be derived from the filename.  A table
of extensions is maintained by CPMFS, using the CPMMap command.  Extensions
are first looked up in thie table, and if not found, the MimeMap system is
used, if available.  Otherwise a default value of &FE4 (DOS) is used.

CPMFS cannot respond to calls which set the filetype, but no errors are
generated.  The CPMMap command can be used to override MimeMap entries which
would otherwise be unhelpful for the kinds of files you are likely to meet
on CP/M discs.  For example, "CPMMap DOC Text" would mean that such files on
CP/M discs would be treated as text files rather than MS Word files!


6. Attributes
-------------
CP/M maintains three main attribute flags:

  SYS/DIR  - System files are hidden files accessible to all users.  This
             attribute is ignored by CPMFS.

  Archived - Indicates whether a file has been backed up.  Ignored by CPMFS.

  RO/RW    - Files can be set to read only, or to read/write.  If read-only,
             the attributes are returned to RISC OS as "user read access,
             locked", otherwise they are given as "user read and write,
             unlocked".  When setting attributes in response to requests
             from RISC OS, CPMFS will only set the file to RW if the RISC OS
             attributes include user write access and do not include locked.

  F1 to F4 - The four CP/M user-defined attributes are ignored by CPMFS.

Those CP/M attributes which are ignored by CPMFS are nevertheless preserved
when a file is extended in length.


7. Notes on particular RISC OS routines
---------------------------------------

ImageEntry_Open
  Contrary to the PRMs, it appears that on entry R0 does hold a reason code,
  as for FSEntry_Open, and that the reason code may be 1.  I have assumed
  that FileSwitch has checked that the object is not a directory, but
  perhaps that was unwise too!

ImageEntry_GetBytes
ImageEntry_PutBytes
  When ready from a hole in a sparse file, zeros are returned.  Attempting to
  write to a hole generates an error.

ImageEntry_Args 3
  As I understand it, this entry point will only set the extent within the
  limits of the file's allocated size.  An error is generated if an attempt
  is made to extend the file using this entry point.  An error is also
  generated if the CP/M extent that would contain the last byte of the file
  does not exist.  This should not happen except with files which are
  sparse in their extents.  Such files will not be created by CPMFS.

ImageEntry_Args 4 (OS_Args 4)
  The PRM (2-52) says that "the size allocated to a file will be at least as
  big as the current file extent".  However, as explained above, CP/M
  supports sparse files, which have a notional file extent greater than the
  amount of disc space allocated to them.  In view of the statement in the
  PRM, I was not sure therefore whether to be completely honest, and report
  an extent larger than the file allocation, so to play safe, CPMFS just
  returns as the allocation size the length of the file rounded up to a
  multiple of the block size.  If the file is not sparse, this figure is the
  same as the allocated disc space.

ImageEntry_Args 6
  Not implemented, as CPMFS does not do its own buffering.

ImageEntry_Args 7 (OS_Args 6)
  The PRM (2-54) says that "this space thus allocated is not yet part of the
  file, so the extent is unaltered, and no data is written".  With the CP/M
  file system it is impossible to allocate space to a file without most of it
  forming part of the extent of the file, therefore CPMFS does alter the
  extent of the file upwards, if necessary, in response to OS_Args 6.

ImageEntry_Args 8
  Identical in implementation to ImageEntry_PutBytes.

ImageEntry_Args 9
  Datestamps and filetypes are returned as described above.

ImageEntry_Close
  Does not generate errors if there was an error writing a new datestamp.
  
ImageEntry_File 0
ImageEntry_File 1
ImageEntry_File 5
ImageEntry_File 6
ImageEntry_File 7
  Attributes, datestamps and filetypes are handled as described above.
  
ImageEntry_File 8
  It is not possible to create subdirectories on CP/M discs, so this call
  generates an error.

ImageEntry_File 10
  Although this call is very easy to implement, FileSwitch does not appear
  to pass it on to the image filing system, so I have left it out.

ImageEntry_Func 8
  I have assumed that FileSwitch has already checked that the object exists,
  and that the proposed new name is not already in use.  It does not say you
  can assume this in the PRM, but it seems to work.

ImageEntry_Func 14
ImageEntry_Func 15
  At present, no error is generated if the object being catalogued is not
  found or is a file.  If it is a file, then the catalogue entries returned
  will be those of its parent directory.

ImageEntry_Func 21
  The image is not flagged internally as "stamp image on next update".  For
  most updates the stamp of the image will change anyway.  It is only
  ImageEntry_PutBytes which escapes.

ImageEntry_Func 22
  As it says in the PRM.

ImageEntry_Func 25
  No defect lists are maintained for CP/M discs, therefore the returned list
  is always null.

ImageEntry_Func 26
  This call generates an error.

ImageEntry_Func 27
  This call returns zero.

ImageEntry_Func 28
  This call does not seem to get as far as CPMFS, but if it did, it would
  give an error.

ImageEntry_Func 29
  This call generates an error for DSK images, and works as specified for
  PCW, D2, D10 and D20 disc images.

ImageEntry_Func 30
  As it says in the PRM.

ImageEntry_Func 31
  As described above, a CP/M disc label will not have datestamps stored in
  it if the disc is likely to be used with CP/M 2.2.

ImageEntry_Func 32
  The image stamp is derived as a CRC of the directory sectors.  Stamp on
  next update is ignored.  Stamp now is implemented by altering the second
  byte of the first empty directory entry found.  If there is no empty entry,
  the stamp is not altered, but no error is generated.

ImageEntry_Func 33
  Not implemented for DSK files.  The implementation for plain disc images
  is not completely correct, in that if a block is contained in more than
  one extent of the same file, R2 is returned equal to 3 rather than 2.
  Blocks allocated in an extent which are beyond the end of the file as far
  as EX and RC are concerned are still reported as belonging to that file.
  Since these are very technical considerations unlikely to arise in
  practice, and this is a very technical entry point, this will probably
  not cause endless difficulties!!


8. How are discs recognised?
----------------------------
For a physical disc in the floppy drive, Service_IdentifyDisc comes into
play.  For formats apart from PCW discs, CPMFS insists on the sectors per
track, number of heads, density, lowest numbered sector, and sector size
being correct for a disc format to be identified.  For a PCW disc, two
heads, double or high density, and a lowest numbered sector of 1 are
insisted upon.  The first sector is then read in, and the disc specification
held in the first sixteen bytes is then checked against the disc record
supplied to the service call in respect of the density, sectors per track
and sector size.

One severe problem is that the top two bits of the lowest numbered sector
in the disc record have been hijacked (see PRM 2-202).  This means that it
is impossible to recognise discs with a lowest sector number greater than
&3F.  In particular, the two main disc formats native to the Amstrad CPC are
denied us.  Sadly, I doubt this is a high priority for future versions of
RISC OS....

For a DSK or EDSK image, the process is similar, but more formats can be
recognised because of the additional information which is available.


9. What do the CPMFS SWIs do?
-----------------------------
CPMFS provides two SWIs, CPMFS_DiscFormat, &546C0, and CPMFS_LayoutStructure,
&546C1.  They provide the same sort of facilities as the similarly named
DOSFS and FileCore SWIs.  If you really need to know which parameter values
correspond to which disc formats, then you should be in a position to find
out for yourself.  I cannot guarantee they will stay the same, though I do
not anticipate their changing.
