Description of the XFS concept of MagiC V3.00 to 6.xx
#####################################################

Andreas Kromke
Hannover, 17.6.98
English translation: Peter West, May 99

Contents:

I Concepts
II Makeup of a XFS
III Data structures
IV Installation
V The kernel functions


I Concepts
==========

GEMDOS was until now the most conservative part of the MagiC operating 
system. For network drivers, practically all DOS calls inclusive of 
Pexec() had to be imitated, without being able to intervene at a lower 
level. In MagiC 1.x (as in TOS) the DOS was not even reentrant, because 
a statically created stack was used.

In MultiTOS/MiNT this problem is circumvented in a manner that in a way 
superimposes a system onto GEMDOS that deals with all the higher 
management functions, can handle other (non-DOS) file systems and only 
uses GEMDOS as a "dumb" file system driver. The advantage of this system 
is its great flexibility and extendability, though a decisive drawback is 
the massive overhead when using "normal" DOS file systems, which are just 
the ones that are installed by far the most often.
In addition, the GEMDOS file system can gain nothing in functionality, 
ease of use or speed by the use of MiNT, as it just runs the old 
routines with an additional overhead. Thus file system accesses under 
MiNT are generally not reentrant, so that (as under MS-Windows) every 
floppy or hard disk access paralyses the whole computer.

A further property of MiNT is the effort to incorporate those functions 
that are common to all file systems into the kernel. Although this keeps
the file system drivers (MiNT-XFSs) compact, the Inode-oriented makeup
of the kernel functions can in some circumstances force an unfavourable 
structure on the file drivers; furthermore, generally many calls of the 
file system driver are needed for one DOS call. Finally, the MiNT kernel 
itself is very long but to a large extent lies fallow as long as no 
other file systems than DOS are in use.

Under MagiC a different approach was chosen that consisted of a complete 
rewrite of GEMDOS from the ground up, including the low-level functions 
for sector buffering, and in dividing everything up into three, four or 
five layers that may be accessed from outside (by means of loadable 
drivers). A side-effect resulted in extending the functionality of the DOS
file system, and creating an additional file system on logical drive U:.
The whole concept, including accesses to DOS file systems, is reentrant 
and takes place in the background. Thus it is possible to work with 
files on drive A:, for instance, without slowing down the computer to 
any marked extent during disk accesses. Despite this MagiC till now has 
become only some 10 kB longer. In comparison to MiNT, however, more 
functions were farmed out to the file system drivers, and although this 
makes them larger it gives them the possibility to perform their 
functions much more efficiently. If anything, the DOS file system has 
become somewhat faster rather than slower.

Though realised completely in assembler, we have here an object-oriented 
system with virtual functions and multistage inheritance (polymorphism). 
A file descriptor (FD), as used in the kernel and made available by the 
XFS, is an object with special data fields and functions. But the XFS 
also realises a derived object with additional data fields and functions.
Finally the DFS sub-driver of DOS_XFS must in turn accommodate further 
functions and data fields in the FD and extends the class further. 
Things look exactly the same with the DMD (drive medium descriptor). The 
kernel requires only a little data, the lower layers however appreciably 
more, though always different ones.

The layers in detail:

1. The DOS kernel. This is incorporated in MagiC itself and is called 
   directly by the application programs via trap #1. It contains modules 
   for memory management, process management and file management.
   The latter has the following "sub-levels":
2. The file system (XFS = extended file system). Its makeup differs 
   fundamentally from a MiNT XFS, but fulfills the same purpose.
   MagiC itself on the Atari contains only a single XFS, the so-called 
   "DOS_XFS", though others may be installed. On the Macintosh a further 
   file system has been integrated, the "Mac-XFS".
   The DOS-XFS in particular makes use of sub-drivers:
2a A DOS file system (DFS), called by the DOS_XFS. It includes only the 
   file functions, while the directory management is undertaken in 
   effect by the DOS_XFS.
   MagiC directly contains two DFSs. One for drive U:, and a second one 
   for FAT file systems that are accommodated on BIOS drives.
   Further DFSs can be installed. Less work has to be done by a 
   programmer to create a DFS than an XFS, as many functions will have 
   been performed already by the DOS_XFS. The essential prerequisite is a 
   DOS-conform directory structure (with 32-byte entries and filenames in 
   the 8+3 format).
3. The file-drivers (MX_DEV), which essentially look after the reading 
   and writing of a file. They are created and managed by the XFS, but
   for functions such as Fread() and Fwrite() are called directly from 
   the kernel, which creates an exteremely low overhead.
   The DOS_XFS in fact contains only one file-driver. This looks after 
   the updating of the directory during write accesses, for instance, 
   and calls a sub-driver (MX_DDEV) in turn.
3a The sub-driver MX_DDEV, called only by the DOS file-driver of the
   DOS_XFS file system.
   Users can incorporate their own custom MX_DDEVs via the directory 
   U:\DEV\.

Notes:
- In contrast to MiNT, U:\PROC, U:\DEV etc. do not form their own file 
  systems, but are simply subdirectories of the DFS for drive U:. 
  Depending on the subdirectory, the U-DFS creates different file types 
  and drivers.
  As the control of directories is based on the DOS_XFS, write accesses 
  for, say, a device in the directory are logged via the file time/date-
  stamp. This even goes so far that a write access to the root directory 
  of drive A: will automatically alter the file time/date-stamp of U:\A.
  Devices, pipes and shared memory blocks can be moved, deleted or renamed.
  One can create symbolic links in all directories of drive U:, e.g. with 
  "ln -s U:\CON U:\CONSOLE" one can create an alias for the device file 
  CON. One can also simply create empty files but no folders, because 
  drive U: does not have any memory allocated to it.
- In contrast to MiNT, U:\ is not its own special file system. One just 
  has to pay regard to the fact that only symbolic links can be created.
  The directories U:\A etc. are merely symbolic links, which can also be 
  removed or renamed.
- All directories of U: are currently limited to a maximum of 32 entries.
- The makeup of all internal structures and the fast, register-based 
  parameter passing and return requires an implementation of all file 
  drivers in assembler, at least for most of the functions.
  Calling conventions are those of GEMDOS, i.e. d0-d2 and a0-a2 may be 
  altered in a function.
- The makeup of the MX_DDEV file driver has altered since MagiC V2.10. 
  The example driver DEV_LPT1 may not be used in MagiC 3.00 and higher.
  The concepts, though, have remained identical - only a few constants 
  have changed.


I.1 Symbolic links
------------------

Symbolic links (also called "aliases" in user documentation to agree with 
the Macintosh nomenclature) are files that carry some form of special 
identifier and instead of data they contain a path, which in turn points 
to another file. Such a path may also point to a not yet (or no longer) 
existing file. Paths may be absolute or relative, where the latter use 
the directory that contains the link as the reference.

In memory such links are managed as structures:

   {
   WORD n;     /* Rounded to an even number, including EOS */
   char path[n];  /* Path with EOS at end */
   }

Some XFS functions must, when encountering a symlink, dereference it, 
i.e. they return to the kernel the error code ELINK in d0 and a pointer 
to a structure of the above type in a0. The kernel then looks after the
dereferencing and the limitation of the maximum nesting levels (if the 
symlink points to another such).
The XFS functions that may return ELINK are:

xfs_path2DD
xfs_sfirst
xfs_xnext
xfs_fopen
xfs_xattr
xfs_attrib


I.2 Disk changes
----------------

Disk changes can occur in the following circumstances:

- Initiated explicitly from the outside by ejection of a medium or by 
  unmounting, perhaps with Dlock().
- Unforeseen removal of the medium (floppy disk or interchangeable 
  cartridge etc.) by the user.

The explicit change is the simplest case. The XFS driver is informed by
calling xfs_drv_close() in mode 0 (see below), and also has the option 
to prevent the change, i.e. to block the medium.

An unforeseen change takes somewhat more effort to handle. The access via 
a path is always started with the call of xfs_drv_open() (see below).
Here the XFS has to test the relevant drive, if the medium is already 
mounted, for a disk change. Moreover a disk change may also occur even 
"more unexpectedly" with opened files, perhaps during Fread() or 
Dxreaddir(). If such a change is recognised then the XFS has to call 
the disk change routine of the kernel (see below). Then the following 
happens:
The disk change function of the kernel (diskchange) first releases the 
structures of the kernel (opened files, standard paths) and then in turn 
calls xfs_drv_close() with mode 1, so that the XFS can release its 
structures.

The return value of this kernel routine, i.e. E_CHNG or EDRIVE, is then 
passed to the caller. Either (for E_CHNG, i.e. "Drive is valid again with 
a new medium") the kernel repeats the DOS call, or alternatively the 
caller receives the error code EDRIVE (drive at present invalid, no or 
unknown medium inserted).

Warning: If a disk change is recognised only in a subordinate procedure 
of an XFS or file driver, one must ensure that the return value of
diskchange() is passed on unchanged to the superordinate procedure in 
each case, and that this terminates immediately without accessing the 
possibly no longer present structures of the XFS.


II Makeup of an XFS
===================

Because the implementation can only take place in assembler, herewith the
makeup of an XFS in assembler syntax:

     OFFSET

xfs_name:      DS.B      8    /* Name of the file system                  */

     The name is only a comment up till now; in the future it may offer 
     the possibility of establishing which drivers are installed and 
     what, say, the driver responsible for drive B: is called (i.e. what 
     kind of file system is contained on the floppy disk). The name of 
     the integrated XFS is "DOS_XFS " (extended to 8 places with spaces).

xfs_next:      DS.L      1    /* Next driver                              */

     This is simply a chaining pointer to the next driver. A new driver 
     is installed from the front, so it always has the highest priority.
     This makes it possible to load a new driver over the top of the 
     integrated DOS driver, for instance.

xfs_flags:     DS.L      1    /* Flags according to MiNT                  */

     Reserved.
     This should actually contain flags, but is not used by the MagiC 
     kernel. So please do not use it!

xfs_init:      DS.L      1    /* Initialisation                           */

     Reserved.
     For the MagiC-internal XFSs this contains their initialisation. It 
     is not used with loaded in XFSs.

xfs_sync:      DS.L      1    /* Synchronises the file system             */
                              /* a0 = DMD *d                              */
                              /* -> long errcode                          */

     The kernel informs the XFS that all buffers on drive <d> are to be 
     written back. In register a0 a pointer is passed to a DMD (drive 
     media descriptor). This is created by the kernel; its makeup is 
     described below.
     Return value is an error code. If the XFS does not perform any 
     buffer management (e.g. a RAMdisk) then a 0 must be returned.
     The internal DOS_XFS simply calls the DFS function with the same 
     name.

xfs_pterm:     DS.L      1    /* Notifies a program termination           */
                              /* a0 = PD *                                */
                              /* -> void                                  */

     This function is called at every program termination and gives the XFS
     the chance to release internal structures or remove locks. Files 
     that are visible to the kernel (i.e. those that have been allocated 
     a handle), will be closed by the kernel before this. The reference 
     counters for the standard directories will also be decremented by 
     the kernel beforehand.
     In a0 a pointer to a process descriptor is passed.

xfs_garbcoll:  DS.L      1    /* Garbage collection or NULL               */
                              /* a0 = DMD *d                              */
                              /* -> d0 = 1L or 0L                         */

     Because the kernel urgently requires internal GEMDOS memory, it 
     performs a garbage collection.
     An XFS that does not use the internal memory management of the 
     kernel can enter a null-pointer here as a function.
     Attention: As many blocks as possible should be released. The return 
     value is 1 if (at least) one block could be released.
     During a garbage collection the kernel runs through the complete 
     list of all installed logical drives. The XFS therefore will be 
     asked for a garbage collection as many times as the number of drives 
     it is managing at the time.

xfs_freeDD:    DS.L      1    /* Free DD                                  */
                              /* a0 = DD *                                */
                              /* -> void                                  */

     The kernel has decremented the reference counter of a DD to 0, so 
     the DD is no longer referenced by the kernel. This function is 
     called, for instance, if after an Fopen() the path containing the 
     opened file and which was passed to xfs_fopen is no longer needed by 
     the kernel.
     XFSs that do not perform garbage collection can free their DDs with 
     this function.
     One must ensure that the root is never freed. One either builds in a 
     special request here, or (more elegantly) sets the reference counter 
     of the root to 1 already at drv_open.

xfs_drv_open:  DS.L      1    /* Initialises or tests the DMD (Mediach)   */
                              /* a0 = DMD *                               */
                              /* -> d0 = long errcode                     */

     MagiC supports just 26 simultaneously active file systems, which 
     are assigned the letters 'A'..'Z'. This entry has two tasks:

     1. At the first access to a drive (say D:) the kernel creates a DMD 
        (drive media descriptor) and "offers" this to the XFSs. The entry 
        d_xfs is still a null-pointer, d_drive is initialised (between 0
        and 25, corresponding to 'A'..'Z').
        The XFS drivers now attempt to recognise "their" file system on 
        the drive. If this is successful, then d_xfs and d_root must be 
        initialised; the return value is then E_OK.
        Warning: The root may not be freed during the "lifetime" of a 
                 mounted file system. The reference counter of the root 
                 should be preset with a 1 to prevent it from being 
                 freed with free_DD.
                 In the versions of MagiC before 4.01 this was not 
                 necessary, because the reference counter (in a dirty 
                 way) was not incremented before the path2DD call or 
                 decremented afterwards.
        Else EDRIVE is returned and the kernel tries the next XFS.

     2. At a repeated access d_xfs is alredy initialised, and the XFS has 
        the opportunity to test for a media change. If everything is in 
        order then E_OK has to be returned. Else the disk change routine 
        of the kernel must be called and E_CHNG returned.
        The pointer to the disk change routine of the kernel is obtained 
        at the installation of the XFS.

     The internal DOS_XFS passes on the call to the corresponding 
     function of the DFS, i.e. all DFS drivers will be tried in turn.

xfs_drv_close: DS.L      1    /* Forces a disk change                     */
                              /* a0 = DMD *                               */
                              /* d0 = int mode                            */
                              /* -> d0 = long errcode                     */

     This function too fulfills two tasks, depending on <mode>:

     1. mode == 0:

        The kernel requests the XFS to close the drive if possible. This 
        function is required for Dlock() and for the ejection of media 
        via Dcntl(CDROM_EJECT). For safety reasons, the kernel before 
        calling this function calls xfs_sync() if a write-back cache is 
        in use.

        If the closing is not possible or not permitted for the moment, 
        then EACCDN has to be returned. Opened files will already have 
        been recognised by the kernel, which prevents the closing of the 
        drive. However only those files that have a handle and are known 
        to the kernel can be recognised here.

        If the closing is permitted, on the other hand, then the XFS must 
        release all structures or write back all caches if appropriate. 
        The DDs for the standard directories of all running processes are 
        an exception to this. They are released subsequently by the 
        kernel with xfs_freeDD().
        One should ensure that the root directory, which was created in 
        the DMD via d_root, is also released by the XFS; in d_root one 
        should then enter NULL (though it would be more sensible if this
        were done by the kernel with xfs_freeDD(), unfortunately the 
        MagiC kernel has an error here that is hard to correct at a later 
        stage).
        xfs_drv_close() after these actions returns E_OK.

     2. mode == 1:

        The kernel forces closing, the XFS must return E_OK. No caches 
        may be written back, as the drive is already invalid (after a 
        disk change has been reported already).
        DDs and FDs do not need to be released as long as they are still 
        referenced by the kernel, i.e. even after the disk change the 
        kernel still performs a xfs_freeDD or dev_close as appropriate.
        An exception is the DD for the root directory. Here the same 
        applies as for mode 0 (see above).

     The internal DOS_XFS passes on this call to the corresponding 
     function of the DFS; in addition any XFS structures will be released 
     if appropriate.


xfs_path2DD:   DS.L      1    /* Returns a DD to the path name            */
                              /* mode = 0: Name is a file                 */
                              /*      = 1: Name is itself a directory     */
                              /* d0 = int mode                            */
                              /* a0 = DD *reldir    Current directory     */
                              /* a1 = char *pathname                      */
                              /* -> d0 = DD *                             */
                              /*    d1 = char *restpath    Rest of path   */
                              /* or                                       */
                              /* -> d0 = ELINK                            */
                              /*    d1 = Rest of path without leading '\' */
                              /*    a0 = FD of the path that contains the */
                              /*         symbolic link. This is important */
                              /*         for relative path specifications */
                              /*         in the link.                     */
                              /*    a1 = NULL  The path represents        */
                              /*               the parent of the root     */
                              /*               directory                  */
                              /*    or                                    */
                              /*    a1 = Path of the symbolic link        */

     The kernel differentiates between two types of descriptors, namely
     file descriptors (FD) and directory descriptors (DD), which however 
     can be identical in their makeup (see below).
     This function returns a descriptor to a path. The reference counter 
     of the DD must be incremented by 1 each time it is returned as a 
     function value, because it is referenced by the kernel. The function 
     path2DD corresponds to an "opening" of the path - a kind of "file 
     handle" is returned to the kernel which the kernel has to close again.
     The parsing of the path must always be accomplished by the XFS.
     
     Input parameters:

          <mode>         Specifies whether the last path element is 
                         itself a directory (mode == 1), or if the path 
                         that contains this file is to be ascertained.

          <reldir>       The directory from which the search is to start.

          <pathname>     The pathname, without drive letter and without 
                         the leading '\'.


     Ouput parameters:

     1st case: An error has arisen

          <d0>           Contains the error code.

     2nd case: A directory descriptor (DD) could be established.

          <d0>           Pointer to the DD. The reference counter of  
                         the DD was incremented by 1 by the XFS.
 
          <d1>           Pointer to the remaining filename without the 
                         leading '\' or '/'. If the end of the path has 
                         been reached then this pointer points to the 
                         terminating null-byte.

     3rd case: During the path evaluation the XFS has found a symbolic link.

          <d0>           Contains the internal MagiC error code ELINK.

          <d1>           pointer to the remaining path without the 
                         leading '\' or '/'.

          <a0>           Contains the DD of the path that contains the 
                         symbolic link. The reference counter of the DD 
                         was incremented by 1 by the XFS.

          <a1>           This is a pointer to the link itself. A link 
                         starts with a word (16 bits) for the length of 
                         the path, followed by the path itself.

                         Warning: The length must INCLUDE the terminating 
                                  null-byte and also be even. The link 
                                  must lie at an even memory address.

                         The buffer for the link may be static or 
                         transitory, because the kernel immediately 
                         copies the data, without the possibility of a 
                         context change inbetween.

                         If a1 == NULL is passed, this signals the kernel 
                         that the parent of the root directory was 
                         selected. If the path lies, say, on U:\A, then 
                         the kernel can go back to U:\. The value of the 
                         return from register a0 is ignored by the kernel, 
                         so no reference counter may be incremented.

xfs_sfirst:    DS.L      1    /* Searches for the first matching file     */
                              /* a0 = DD * srchdir                        */
                              /* a1 = char *name   (without path)         */
                              /* d0 = DTA *                               */
                              /* d1 = int  attrib                         */
                              /* -> d0 = long errcode                     */
                              /*    a0 = SYMLINK *     (possible symlink) */

     This is required for Fsfirst. MiNT uses in place of this function a 
     combination of Dreaddir and Fxattr, for which however very many 
     calls of the XFS driver are required. The reserved region of the DTA 
     can be used freely by the XFS; if this is too small then one must 
     proceed with similar heuristic methods to those used in MiNT, i.e. 
     pointers have to be entered in the DTA that point to descriptors. 
     But these descriptors can only be released with heuristic methods, 
     because one never knows whether another Fsnext is coming or not.
     The kernel already establishes the complete path via path2DD, so 
     that just the filename itself is passed in a1. As for all DOS calls 
     that are servicing a path, the XFS must ensure that the DD is 
     protected for the duration of the servicing. This is particularly 
     critical if the file system is reentrant.
     The special case that the search is for a media name (volume label) 
     is already intercepted by the MagiC kernel, i.e. the XFS may not (!) 
     return a media name.
     If a file being searched for is a symbolic link, then ELINK must be 
     passed in d0 as an error code and in a0 a pointer to the link (see 
     above for path2DD). The kernel then simply calls Fxattr to fill the 
     DTA. If a disk change has taken place then E_CHNG is to be returned 
     and the kernel then automatically repeats the function. This applies 
     also for all other functions.
     In each case dta_drive has to be initialised correctly, say with:
          dta.dta_drive = srchdir->dd_dmd->d_drive;
     If E_OK or ELINK is retuned without dta_drive having been initialised, 
     then the result is undefined!
     For the implementation of xfs_sfirst and xfs_snext it is desirable 
     to use the functions match_8_3() and conv_8_3() of the DFS kernel.
     The address of the DFS kernel can be found with Dcntl(DFS_GETINFO).
     For further details, see below.

xfs_snext:     DS.L      1    /* Searches for the next matching file      */
                              /* a0 = DTA *                               */
                              /* a1 = DMD *                               */
                              /* -> d0 = long errcode                     */
                              /*    a0 = SYMLINK *     (possible symlink) */

     This is required for Fsnext. The kernel has already established the 
     associated file system from the DTA whose DMD is passed in a1. Here 
     too symbolic links can occur.

xfs_fopen:     DS.L      1    /* Opens or/and creates a file              */
                              /* a0 = DD *                                */
                              /* a1 = char *name   (without path)         */
                              /* d0 = int omode    (for opening)          */
                              /* d1 = int attrib   (for creating)         */
                              /* -> d0 = FD *                             */
                              /*         or error code                    */
                              /*    a0 = SYMLINK *     (possible symlink) */

     This is used for the functions Fopen and Fcreate. The Open mode in 
     the lower value byte is different to that in MiNT, because the MiNT
     modes are inconvenient for the implementation of the queries.
     When calling Fopen via trap #1 the MiNT modes are converted by the 
     kernel into the internal modes. Herewith the internal modes that 
     have to be serviced by XFS drivers (NOINHERIT is not supported, 
     because according to TOS conventions only the handles 0..5 can be 
     inherited). Otherwise the high byte corresponds to the MiNT 
     conventions:

          OM_RPERM       EQU  1    /* File is opened for reading          */
          OM_WPERM       EQU  2    /* File is opened for writing          */
          OM_EXEC        EQU  4    /* File is opened for execution        */
          OM_APPEND      EQU  8    /* Write accesses to file end (kernel!)*/
          OM_RDENY       EQU  16   /* Others may not read simultaneously  */
          OM_WDENY       EQU  32   /* Others may not write simultaneously */
          OM_NOCHECK     EQU  64   /* NO checking by the XFS              */

     The bit OM_APPEND is taken into consideration automatically by the 
     kernel at Fwrite() calls, the kernel performing an Fseek() before 
     each write access.
                        
     OM_NOCHECK is set by the kernel when a file is opened as a device, 
     i.e. as handle -1, -2, -3 or -4, or if one redirects to such a handle
     (Fforce).
     If this bit is set then the XFS should perform no checks for multiple 
     opening of the file (see also below for fd_mode), but leave this to 
     the device driver.

     Here the bits, which are used as in MiNT:

          O_CREAT        EQU  $200      /* Create file if it doesn't exist*/
          O_TRUNC        EQU  $400      /* Clear file, if it exists       */
          O_EXCL         EQU  $800      /* Don't open, if it exists       */

     The kernel executes Fcreate as Fopen(O_CREAT+O_RDWR+O_TRUNC). 
     O_COMPAT (i.e. only specify TOS mode 0, 1 or 2) is in MagiC always 
     equivalent to O_WDENY.

     The checking of the file access permissions is completely up to the 
     XFS, the kernel does nothing. This wouldn't be very sensible in any 
     case as each file system has its own mechanisms and permissions.
     If it should prove to be really necessary, I will extend the kernel 
     with the necessary functions that allow the XFS can query the uid, 
     gid etc. of the running process.
     The file mask (umask) for the creation of files and folders can be 
     established from offset 0x64 of the basepage of a process.
     (See ->MGX_XFS.INC, also in this folder).

     The kernel has returned to it a pointer to an opened FD, i.e. the 
     opening of the file driver must be performed by the XFS. The 
     reference counter of the returned FD is to be incremented by 1 by 
     the XFS, or to be initialised to 1 at first opening.
     For symbolic links and disk changes the same applies as for xfs_sfirst.

     Fopen is implemented under MiNT as follows:
     1. Conversion pathname->fcookie with <lookup>
     2. Establishing the file access permissions with <getxattr>
     3. Testing of the file access permissions by the kernel
     4. Establishing the file driver with <getdev>
     5. Opening the file with <getdev->open>
     This procedure is very protracted. DOS already returns a pointer to 
     the 32-byte directory entry while searching for the file, which 
     serves directly for checking the attributes and hence the file 
     access permissions, as well as for opening the file.
     MagiC therefore expects the direct implementation of the Fopen 
     command with all verifications of the file access permissions. The 
     return value is an FD structure into which the file driver has been 
     entered and this has been opened.

xfs_fdelete:   DS.L      1    /* Deletes a file                           */
                              /* a0 = DD *                                */
                              /* a1 = char *name   (without path)         */
                              /* -> d0 = long errcode                     */

     For Fdelete. When deleting a symlink only the link may be deleted,
     not the referenced file. This means that the return value ELINK is 
     _INADMISSIBLE_ here.
     Access permission checks are completely the responsibility of the XFS.
     During deletion one has to ensure that no file is referenced by the 
     kernel (i.e. fd_refcnt != 0).

xfs_link:      DS.L      1    /* For Frename and Flink                    */
                              /* a0 = DD *olddir                          */
                              /* a1 = DD *newdir                          */
                              /* d0 = char *oldname                       */
                              /* d1 = char *newname                       */
                              /* d2 = int flag_link                       */
                              /* -> d0 = long errcode                     */

     This is used both for Frename (d2 = 0) as well as for Flink (d2 = 1).
     In the case of Frename a new directory entry is to be created and the
     old entry (= reference to the file) is to be deleted or overwritten.
     In the case of Flink a further reference to the same file is created 
     and the old entry is not deleted.
     Both DDs are always contained on the same file system, so have the 
     same DMD.
     As for Fdelete, in the case of a symbolic link this has to be 
     renamed or a further link created, as appropriate. This means that
     the return value ELINK here (as for xfs_fdelete) is _INADMISSIBLE_.
     Flink is not supported by the DOS_XFS.

xfs_xattr:     DS.L      1    /* For Fxattr                               */
                              /* a0 = DD *                                */
                              /* a1 = char *name                          */
                              /* d0 = XATTR *                             */
                              /* d1 = int mode                            */
                              /* -> d0 = long errcode                     */
                              /*    a0 = SYMLINK *     (possible symlink) */

          mode == 0:   Follow symbolic links (i.e. return ELINK if 
                       appropriate)
                  1:   Do not follow links (i.e. create XATTR for the link)

     This is used for Fxattr.
     In the case of mode == 0 (i.e. follow symbolic links) ELINK can be 
     returned in d0 and a link in a0, otherwise ELINK is inadmissible.

xfs_attrib:    DS.L      1    /* For Fattrib                              */
                              /* a0 = DD *                                */
                              /* a1 = char *name                          */
                              /* d0 = int rwflag                          */
                              /* d1 = int attrib                          */
                              /* -> d0 = char attr                        */
                              /*         or long errcode                  */
                              /*    a0 = SYMLINK *     (possible symlink) */

     For Fattrib. In contrast to MiNT, MagiC does not implement this 
     function as Fxattr, because in some circumstances this could lead to 
     massive overheads.
     In this case symbolic links have to be followed, i.e. the return of 
     ELINK is permissible.

xfs_chown:     DS.L      1    /* For Fchown                               */
                              /* a0 = DD *                                */
                              /* a1 = char *name                          */
                              /* d0 = uid                                 */
                              /* d1 = gid                                 */
                              /* -> d0 = long errcode                     */
xfs_chmod:     DS.L      1    /* For Fchmod                               */
                              /* a0 = DD *                                */
                              /* a1 = char *name                          */
                              /* d0 = int mode                            */
                              /* -> d0 = long errcode                     */

     Alters the owner (user-ID and group-ID) or the access permissions of 
     a file. The parameters correspond to the associated MiNT functions
     Fchown or Fchmod respectively.
     Here symbolic links are not followed, i.e the owner and group of the 
     symlink itseslf are modified.
     These functions are not supported by the XFS_DOS. 

xfs_dcreate:   DS.L      1    /* Creates a directory                      */
                              /* a0 = DD *                                */
                              /* a1 = char *name   (without path)         */
                              /* -> d0 = long errcode                     */

     For Dcreate.
     The file mask (umask) for the creation of files and folders can 
     be ascertained via offset 0x64 from the basepage of a process 
     (See ->MGX_XFS.INC, also in this folder).

xfs_ddelete:   DS.L      1    /* Deletes a directory                      */
                              /* a0 = DD *                                */
                              /* -> d0 = long errcode                     */

   This function had to be altered from MagiC 4.01 onwards.
   For MagiC < 4.01 (Kernel version < 3) the following applies:

     During deletion one has to ensure that no directory is referenced by 
     the kernel apart by the call itself (i.e. must have dd_refcnt == 1).
     Furthermore one has to ensure that there are no files in the 
     directory. ELINK may not be returned, symbolic links may not be 
     handled.
     The access permission (if this exists) must be checked by the XFS.

   For MagiC < 4.01 (Kernel version >= 3) the following applies:

   Due to reentrance problems some changes have resulted, where the 
   kernel takes over the access controls from the XFS and furthermore 
   the kernel, not the XFS, releases the DD:
   The kernel first opens the parent of the directory to be deleted 
   with xfs_path2DD, then it tests with xfs_xattr whether it is dealing 
   with a symlink, and if appropriate deletes it with xfs_fdelete.
   If it is dealing with a directory, the kernel opens this again with
   xfs_path2DD (mode 1) and releases the parent with xfs_freeDD once 
   more. The checking of dd_refcnt is performed by the kernel, hence 
   the counter can and must be ignored by the XFS. The XFS only has to 
   ensure that the directory to be deleted or the DD can not be opened 
   or used by others (important for reentrant XFSs). The XFS then 
   deletes the directory, but does not (!) release the DD, so that, if 
   successful (return E_OK), the kernel can release its standard paths 
   beforehand. The kernel then calls xfs_freeDD.
   As mentioned above, the XFS must check that the directory is empty, 
   or that access permissions exist if appropriate.

     Once more in context:
   
     -  Kernel establishes the DD to be deleted, checks the reference 
        counter.
     -  xfs_ddelete locks the DD. If this does not work then EACCDN
        is returned.
     -  xfs_ddelete deletes the directory if it is empty. During this 
        the DD operates as a lock for parallel-running attempts to use 
        the just-deleted directory.
     -  xfs_ddelete leaves the now invalid DD still valid to serve as a 
        lock (in contrast to the old concept).
     -  If successful the kernel makes its standard paths invalid.
     -  The kernel releases the DD.


xfs_DD2name:   DS.L      1    /* Returns the absolute pathname            */
                              /* a0 = DD *                                */
                              /* a1 = char *name                          */
                              /* d0 = int bufsize                         */
                              /* -> d0 = long errcode                     */

     For Dgetpath and Dgetcwd. The path that belongs to the passed 
     directory is copied after <name>. The path must be returned without 
     the trailing '\', i.e. as an empty string for the root directory.
     <bufsize> includes, as everywhere, the terminating null-byte.
     If the buffer is too small (bufsize smaller than the path), then, 
     as in MiNT, the error code ERANGE has to be returned.

xfs_dopendir:   DS.L      1   /* Opens a directory                        */
                              /* a0 = DD *                                */
                              /* d0 = int tosflag                         */
                              /* -> d0 = DHD *dhd                         */
                              /*         or error code                    */

     For Dopendir. The XFS will normally allocate a DHD, enter the 
     relevant data of the DD there and set the index of the DHD to the 
     first entry.
     As <tosflag> only 0 and 1 are permitted at present.
     If <tosflag> == 0 then filenames will not be truncated, and the 
     first 4 bytes that are returned by D(x)readdir contain the file index.
     If <tosflag> == 1, then dreaddir has to shorten the filenames to 8+3 
     and capitals, and may not return a file identifier.

xfs_dreaddir:   DS.L      1   /* Reads the next directory entry           */
                              /* a0 = DHD *dhd                            */
                              /* d0 = int  size                           */
                              /* a1 = char *buf                           */
                              /* d1 = XATTR *xattr   or NULL              */
                              /* d2 = long *xr         (If xattr != NULL) */
                              /* -> d0 = long errcode                     */
xfs_drewinddir: DS.L      1   /* Sets the dirhandle to the 1st entry      */
                              /* a0 = DHD * dhd                           */
                              /* -> d0 = long errcode                     */

     All functions as in MiNT.
     xfs_dreaddir supplants both Dreaddir and Dxreaddir.

xfs_dclosedir:  DS.L      1   /* Closes the dirhandle                     */
                              /* a0 = DHD *dhd                            */
                              /* -> d0 = long errcode                     */

   The DHD is closed. The XFS will normally simply release the DHD.

xfs_dpathconf: DS.L      1    /* Return info about various limits         */
                              /* a0 = DD *                                */
                              /* d0 = int which                           */
                              /* -> d0 = long value                       */

     As in MiNT. XFS_DOS directly calls the appropriate DFS driver.
     <which> can take the following values:
          DP_MAXREQ     (-1)       Return maximum valid value for <which>
          DP_IOPEN       (0)       Internal limit for number of open files
          DP_MAXLINKS    (1)       Maximum number of links for a file
          DP_PATHMAX     (2)       Maximum length for full path name
          DP_NAMEMAX     (3)       Maximum length for filenames
          DP_ATOMIC      (4)       Block size
          DP_TRUNC       (5)       Filename truncation
               Returns:
           DP_NOTRUNC    (0)       Filenames never truncated, ERANGE if 
                                   appropriate
           DP_AUTOTRUNC  (1)       Filenames truncated to max. length
           DP_DOSTRUNC   (2)       Automatic truncation to 8+3 (like DOS) 
          DP_CASE        (6)       Case sensitivity (caps/lower case)
               Returns:
           DP_CASESENS   (0)       Case sensitive
           DP_CASECONV   (1)       Always converted to capitals
           DP_CASEINSENS (2)       Not case sensitive and not converted

         From 21.5.95 onwards the following are also supported:

          DP_MODEATTR    (7)       -> Docs for MiNT
          DP_XATTRFIELDS (8)       -> Docs for MiNT



xfs_dfree:     DS.L      1    /* Return number of free blocks etc.        */
                              /* a0 = DD *                                */
                              /* a1 = long[4]                             */
                              /* -> d0 = long errcode                     */

     For Dfree. XFS_DOS directly calls the appropriate DFS driver.

xfs_wlabel:    DS.L      1    /* Write diskname (= volume label)          */
                              /* a0 = DD *                                */
                              /* a1 = char *name                          */
                              /* -> d0 = long errcode                     */

     For (re)naming of media. It is called by the kernel when Fcreate 
     with attribute 8 is executed. If the diskname, as in the DOS file 
     system, is saved as a special file then all other path-based XFS 
     functions have to ignore the diskname.
     An empty name or one consisting of "\xe5" (compatibility to TOS) 
     must delete the diskname (if unnamed media are permitted).
     It is also called for Dwritelabel().

xfs_rlabel:    DS.L      1    /* Read diskname  (= volume label)          */
                              /* a0 = DD *                                */
                              /* a1 = char *name                          */
                              /* d0 = char *buf                           */
                              /* d1 = int len                             */
                              /* -> d0 = long errcode                     */

     For reading the media name. It is called by the kernel when Fsfirst 
     with (attribute == 8) is executed. <name> is generally "*.*" and 
     can be ignored. <len> if the length of the buffer <buf>; if this is 
     exceeded then ERANGE must be returned.
     It is also called for Dreadlabel(). In that case name == NULL.

xfs_symlink:   DS.L      1    /* Create symbolic link                     */
                              /* a0 = DD * dir                            */
                              /* a1 = char *name                          */
                              /* d0 = char *to                            */
                              /* -> d0 = long errcode                     */

     For Fsymlink. A file with the new name <name> has to be created in 
     the directory <dir>, which points to the file <to>.
     It is supported by the DOS_XFS.

xfs_readlink:  DS.L      1    /* Read symbolic link                       */
                              /* a0 = DD *                                */
                              /* a1 = char *name                          */
                              /* d0 = char *buf                           */
                              /* d1 = int  size                           */
                              /* -> d0 = long errcode                     */

     For Freadlink.
     It is supported by the DOS_XFS.

xfs_dcntl:     DS.L      1    /* For Dcntl                                */
                              /* a0 = DD *                                */
                              /* a1 = char *name                          */
                              /* d0 = int cmd                             */
                              /* d1 = long arg                            */
                              /* -> d0 = long errcode                     */

     For Dcntl. Every XFS should support FUTIME.


III Data structures
===================

1. The device driver (MX_DEV)
-----------------------------

The device driver (MX_DEV) is inserted by the XFS into the file descriptor 
at the opening of a file and called directly by the kernel. The device 
driver must make the following functions available:

     OFFSET

dev_close:     DS.L      1    /* a0 = FD *file                            */
                              /* -> d0 = long errcode                     */

     If fd_refcnt is not already 0 then fd_refcnt must be decremented 
     (this must be accomplished by the MX_DEV). On this occasion any 
     buffers that may be present should be written back and/or directory 
     entries updated. If fd_refcnt is 0, then the FD can be released.
     At a disk change fd_refcnt is already 0 when dev_close is called,
     i.e. one must simply release the FD.

     The file driver installed by the DOS_XFS writes back the directory 
     data and then calls the MX_DDEV sub-driver.

dev_read:      DS.L      1    /* a0 = FD *file                            */
                              /* d0 = long count                          */
                              /* a1 = char *buffer                        */
                              /* -> d0 = long amount                      */

     From the file <file>, <count> bytes are read into the buffer <buffer>.
     The return is the number of characters actually read.

     The file driver installed by the DOS_XFS passes on the call directly 
     to the MX_DDEV sub-driver.

dev_write:     DS.L      1    /* a0 = FD *file                            */
                              /* d0 = long count                          */
                              /* a1 = char *buffer                        */
                              /* -> d0 = long amount                      */

     To the file <file>, <count> bytes are written from the buffer <buffer>.
     The return is the number of characters actually written.

     The file driver installed by the DOS_XFS updates the time/date-stamp 
     of the file and then passes on the call to the MX_DDEV sub-driver.

dev_stat:      DS.L      1    /* a0 = FD *file                            */
                              /* a1 = MAGX_UNSEL *unselect   or NULL      */
                              /* d0 = int rwflag                          */
                              /* d1 = long apcode                         */
                              /* -> d0 = long status                      */

     <unselect> is either NULL or a pointer to the following structure:

          typedef struct {
               union{
                    void (*unsel) (MAGX_UNSEL *un);
                    long status;
                    }
               long param;
          } MAGX_UNSEL;

     This gives information about the read/write-status of the file. It 
     is called, for instance, at Fselect() (or at Cconos()/Cconis() etc.). 
     In contrast to MiNT, it is left to the driver here whether it is 
     interrupt-capable or not, i.e. whether it is able to recall a 
     waiting application in an interrupt or not. 
     rwflag specifies whether the read or write status is being queried.

     Procedure:

     Generally: If <unselect> is not NULL then the return value must not 
     only be returned as a function result (in d0.l), but also in
     unselect->status. This applies to all types of return values. In 
     unsel->param one can also optionally store a parameter.

     If <apcode> == NULL, then polling takes place, i.e. the application 
     is not put to sleep. So return: 0=not ready, 1=ready, <0=error.

     If <apcode> != NULL (then also <unselect> != NULL), the returns are:

          <0        Error
          0         Not ready, device can only be polled
          1         Ready 
          >0        Possibly a pointer to a function that de-installs the 
                    awakening interrupt again (corresponds perhaps to the
                    unselect in MiNT).

     If the device is not ready but is interrupt-capable, then one 
     proceeds as follows:

     1. Initialise unselect with the address of the clean-up function and 
        an optional parameter. The prototype is something like:
        void unselect( a0 = MAGX_UNSEL *un, a1 = void *ap_code );
     2. Install interrupt for awakening, pass it unselect (and with that 
        also the optional parameters) and appl.
     3. Return pointer for clean-up routine in d0.

     The interrupt routine does the following:

     1. The interrupt arrives.
     2. unselect->status will have 1 (OK) or < 0 (not OK) written to it 
        and the interrupt will be deactivated! The application will be 
        awakened with:

          kernel->appl_IOcomplete( a0 = APPL *ap );

     The clean-up routine does the following:

     1. The interrupt is deactivated.
     2. If this has not happened already, unselect->status will have 
        written to it a 1 (arrived) or 0 (not arrived) or < 0 (error). 
        One has to ensure that no IOcomplete and no write access to
        unsel can take place after this.
        A value that is not 1 but greater than zero is always interpreted 
        as the address of the clean-up routine, which has to be called if 
        the interrupt has not arrived.
        If the interrupt enters a value of 2L, for instance, then the 
        kernel would assume that the interrupt has not arrived and that 
        the address of the clean-up routine is still contained in the
        MAGX_UNSEL structure. A jump to address 2 is then soon fairly 
        deadly!

     The DOS functions Finstat() and Foutstat() attempt at first to 
     execute the call as Fcntl(FIONREAD) or Fcntl(FIONWRITE) respectively.
     If this sub-function of dev_ioctl does not exist (the file driver 
     must return EINVFN!), then dev_stat is called. In this case one can 
     make the statement "Character is waiting" (value == 1) or "No 
     character is waiting" (value == 0).

     The file driver installed by DOS_XFS passes on the call directly to 
     the MX_DDEV sub-driver.

dev_seek:      DS.L      1    /* a0 = FD *file                            */
                              /* d0 = long where                          */
                              /* d1 = int mode                            */
                              /* -> d0 = long position                    */

     For Fseek. <mode> is, as in TOS, 0, 1 or 2. The return is the 
     current position of the write/read pointer; device drivers must 
     always return 0L here.

     The file driver installed by DOS_XFS passes on the call directly to 
     the MX_DDEV sub-driver.

dev_datime:    DS.L      1    /* a0 = FD *file                            */
                              /* a1 = int d[2]                            */
                              /* d0 = int setflag                         */
                              /* -> d0 = long errcode                     */

     For Fdatime.

     The file driver installed by DOS_XFS passes on the call directly to 
     the MX_DDEV sub-driver if the function is supported in the MX_DDEV 
     driver (pointer != NULL), else the function will be executed 
     automatically with the aid of the data of the FD.

dev_ioctl:     DS.L      1    /* a0 = FD *file                            */
                              /* d0 = int cmd                             */
                              /* a1 = void *buf                           */
                              /* -> d0 = long errcode                     */

     For Fcntl.

     The file driver installed by DOS_XFS passes on the call directly to 
     the MX_DDEV sub-driver. Before this however the following functions 
     are handled: FSTAT and FUTIME. These should be executed also by 
     other XFSs.
     Every file driver should support FIONREAD and FIONWRITE.

dev_getc:      DS.L      1    /* a0 = FD *file                            */
                              /* d0 = int mode                            */
                              /* -> d0 = unsigned long c                  */

     This is used by the kernel for Fgetchar() and the character-oriented 
     functions (Cconin, Cconout, Cauxin etc.).
     In the case of a device the return value can be a longword (so for 
     CON, say, the key scancode in the high word), else a byte (always 
     extended as "unsigned"). At EOF, 0x0000FF1A has to be returned.

     <mode> means:
          CMODE_COOKED (1)    Bit 0 set: Special control characters ^C, 
                              ^S, ^Q etc. will be acted on ("cooked" mode)
          CMODE_RAW (0)       Bit 0 not set: "raw" mode (i.e. un-"cooked")
          CMODE_ECHO (2)      Bit 1 set: Input will be echoed

     The file driver installed by DOS_XFS passes on the call directly to 
     the MX_DDEV sub-driver if the function is supported in the MX_DDEV 
     driver (pointer != NULL), else the function will be executed
     automatically as dev_fread.

dev_getline:   DS.L      1    /* a0 = FD *file                            */
                              /* a1 = char *buf                           */
                              /* d1 = long size                           */
                              /* d0 = int mode                            */
                              /* -> d0 = long amount                      */

     For line-oriented input. <mode> as in dev_getc. The return will be 
     the number of characters input, excluding the termination or similar 
     character.

     The file driver installed by DOS_XFS passes on the call directly to 
     the MX_DDEV sub-driver if the function is supported in the MX_DDEV 
     driver (pointer != NULL), else the function will be executed
     automatically as dev_fread, in which case the line will be 
     terminated with a CR and LF; control characters (BS or Del) will not 
     be evaluated.

dev_putc:      DS.L      1    /* a0 = FD *file                            */
                              /* d0 = int mode                            */
                              /* d1 = long value                          */
                              /* -> d0 = unsigned long count              */

     This is used by the kernel for Fgetchar() and the character-oriented
     functions (Cconout, Cauxout etc.).
     In the case of a terminal the return value has to be 4L (i.e. 4 bytes
     written), else 1L if writing was successful.

     <mode> means:
          CMODE_COOKED (1)    Bit 0 set: Special control characters ^C, 
                              ^S, ^Q etc. will be acted on ("cooked" mode)
          CMODE_RAW (0)       Bit 0 not set: "raw" mode (i.e. un-"cooked")

     The file driver installed by DOS_XFS passes on the call directly to 
     the MX_DDEV sub-driver if the function is supported in the MX_DDEV 
     driver (pointer != NULL), else the function will be executed
     automatically as dev_fwrite.


2. The directory descriptor (DD)
--------------------------------

The directory descriptors must be created and managed by the XFS. All 
descriptors that are known to the kernel (i.e. those that are used as a 
standard path for a process) have reference counters that are non-zero.
The only exception if the DD for the root directory of a drive; here the 
reference counter is always 0, no matter whether the process has the root 
directory as the standard directory or not.
For the kernel the DD looks like the following; these entries have to be 
created by the XFS:

dd_dmd:        DS.L      1    /* 0x00: Pointer to DMD                     */

     Here one finds the relevant file system.

dd_refcnt:     DS.W      1    /* 0x04: Ref. counter for standard paths    */

     The reference counter. This entry is used only by the kernel, and 
     must be initialised by the XFS to 0 at the creation of a DD (for the 
     root best to 1, see above for xfs_drv_open) and incremented by 1 for 
     each passing to the kernel (-> xfs_path2DD).

     Thus xfs_path2DD when returning a new DD (which is therefore not 
     referenced by the kernel otherwise) has to set the reference counter 
     to 1.

     If the reference counter is non-zero then the kernel has a pointer 
     to this DD, and it may on no account be released by the XFS.
     The kernel decrements the reference counter by 1 each time it no 
     longer needs the DD. If the counter reaches zero during this, then
     xfs_freeDD is called. The XFS can then release the DD, or else only 
     at the call of xfs_garbcoll (the "garbage collection") or at 
     xfs_drv_close.
     Warning:  The root may not be freed during the "lifetime" of a 
               mounted file system. The reference counter of the root 
               should be preloaded with a 1 to prevent it from being 
               freed with free_DD.


3. The file descriptor (FD)
---------------------------

The file descriptors must be created and managed by the XFS. All 
descriptors that are known to the kernel have reference counters that are 
non-zero. For the kernel an FD looks exactly like a DD, which is why the 
the DOS_XFS uses the same data structure for it. 
For the kernel an FD looks like the following; these entries have to be 
created by the XFS:

fd_dmd:        DS.L      1    /* 0x00: Pointer to DMD                     */

     Here one finds the relevant file system.

fd_refcnt:     DS.W      1    /* 0x04: Ref. counter for closing or -1     */

     The reference counter. This entry is used only by the kernel, must 
     be decremented by 1 at a dev_close call and initialised (with 1) at 
     xfs_fopen.
     A reference counter of -1 signals that the FD may never be released.
     This is necessary, for instance for the device files U:\DEV\CON, 
     U:\DEV\AUX etc., that are always available.

fd_mode:       DS.W      1    /* 0x06: Open mode and flags                */

     This contains the mode, as described for xfs_fopen (see above). If 
     two FDs point to the same file then their modes must be compatible.
     Thanks to the simple MagiC-internal mode coding, a simple rotation 
     and ANDing of the bits suffices. For instance:

          move.w   fd_mode(a0),d1
          btst     #BOM_NOCHECK,d1     ; No check by the XFS ?
          bne.b    _opf_nxt            ; Yes, ddev_open checks
          ror.b    #4,d1
          and.b    d0,d1
          bne      opd_eaccdn          ; Conflict: Return(EACCDN)

fd_dev:        DS.L      1    /* 0x08: Pointer to MX_DEV                  */

     This is the pointer to the device driver.

The class derived from the DOS_XFS (a "DOS-FD") has an appreciably greater 
number of fields and is specified in detail in the DFS binding description.


4. The directory handle descriptor (DHD)
---------------------------------------

The directory handles are required for Dopendir/Dclosedir/Drewinddir. In 
contrast to the file descriptors (FD), the kernel does not hold a list of 
opened DHDs. When a process terminates, the XFS is called via the function
xfs_pterm and has to release all the DHDs created for the process.
The owner of a DHD can be found by the XFS via the kernel pointer <act_pd>.
For the kernel a DHD looks like the following; these entries have to be 
created by the XFS:

dhd_dmd:       DS.L      1    /* 0x00: Pointer to DMD                     */

     Here one finds the relevant file system.


5. The medium descriptor (DMD)
------------------------------

The DMD is created by the kernel (!) for each opened drive and also 
released again when appropriate. Here the XFS stores all the data it has 
to memorise for the drive.  The following fields are those that the kernel
requires (the parent object from which the XFS derives its XFS-DMD):

d_xfs:         DS.L      1    /* 0x00: The file system driver             */

     Here one finds the associated file system driver (XFS). It is 
     entered by xfs_drv_open.

d_drive:       DS.W      1    /* 0x04: Drive number 0..25                 */

     Here one finds which logical drive ('A' .. 'Z') the file system has 
     been allocated to. This need not always correspond to a BIOS drive.
     This value is always (!) entered by the kernel when a drive is 
     opened, before an XFS is entered.

d_root:        DS.L      1    /* 0x06: Pointer to DD of the root          */

     Here one finds a pointer to the DD of the root directory.

d_biosdev:     DS.W      1    /* 0x0a: BIOS drive or -1                   */

     If we are dealing with a partition that is handled with BIOS Rwabs, 
     then one finds the BIOS device number here.
     Else -1 must be entered (e.g. for drive U: or a Macintosh partition).

d_driver:      DS.L      1    /* 0x0c: Defines with devcode the medium    */
d_devcode:     DS.L      1    /* 0x10: e.g. SCSI target & drive           */

     Together these two define the medium. Thus when d_driver and 
     d_devcode are identical then both drives lie on the same medium.
     This means that if the medium is to be ejected then both file systems 
     have to be locked. With a hard disk partition, d_biosdev is the BIOS 
     drive, d_driver can be anything (for instance a pointer to the XHDI 
     structure), d_devcode is the XHDI code of a medium, i.e. SCSI target 
     and device number (each one WORD).

d_dfs:         DS.L      1    /* 0x14: DOS-specific file system driver    */

     This entry only has to exist for DOS file systems (thus already for 
     a derived object) and contains a pointer to the DFS sub-driver.

Other XFSs or DFSs enter further data into the DMD. One can think of the 
DMD as an object class. A DMD derived for the DOS_XFS only has the 
additional entry d_dfs. The FAT-DFS enters additional data, such as the 
cluster size and the number of sectors.


6. The DTA
----------

The DTA is used by the old DOS functions Fsfirst and Fsnext, whose 
clumsy conception by the MS-DOS creators still burden us like a curse. 
For the kernel the structure looks like the following:

dta_res1:      DS.B      20

     This is not used by the kernel. Here the XFS may play around.

dta_drive:     DS.B      1    /* 0x14:*/

     Here one finds the associated logical drive ( 0 ~ 'A' etc.). With 
     this, the kernel can decide at Fsnext which XFS has to execute the 
     request.

dta_attribute: DS.B      1    /* 0x15: Found attribute                    */
dta_time:      DS.W      1    /* 0x16: Found time                         */
dta_date:      DS.W      1    /* 0x18: Found date                         */
dta_len:       DS.L      1    /* 0x1a: Found length                       */
dta_name:      DS.B      14   /* 0x1e: Found filename                     */

     This is the documented "User" region. It has to be handled according 
     to the GEMDOS specifications.


7. The file information structure (XATTR)
-----------------------------------------

The makeup corresponds to the MiNT specification. For the sake of 
completeness it is repeated here in summary:

xattr_mode:    DS.W      1    /* %ttttsssrwxrwxrwx                        */
                              /* Bit 12,13,14,15: File type               */
                              /*         2: BIOS special file             */
                              /*         4: Directory file                */
                              /*         8: Regular file                  */
                              /*        10: fifo                          */
                              /*        12: Memory region or process      */
                              /*        14: Symbolic link                 */
                              /* Bit 9,10,11: Special bits (a la UNIX)    */
                              /*         1: sticky bit                    */
                              /*         2: setgid                        */
                              /*         4: setuid                        */
                              /* Bit 0..8: Access modes                   */
                              /*         rwx for user/group/world         */
xattr_index:   DS.L      1
xattr_dev:     DS.W      1
xattr_res1:    DS.W      1
xattr_nlink:   DS.W      1
xattr_uid:     DS.W      1
xattr_gid:     DS.W      1
xattr_size:    DS.L      1
xattr_blksize: DS.L      1
xattr_nblocks: DS.L      1
xattr_mtime:   DS.W      1
xattr_mdate:   DS.W      1
xattr_atime:   DS.W      1
xattr_adate:   DS.W      1
xattr_ctime:   DS.W      1
xattr_cdate:   DS.W      1
xattr_attr:    DS.W      1
xattr_res2:    DS.W      1
xattr_res3:    DS.L      2


IV Installation
===============

XFS are loaded normally from the folder \gemsys\magic\xtension and must 
have an *.xfs extension. They are loaded before the AUTO-folder and after 
the device drivers (\gemsys\magic\xtension\*.dev). In principle, however, 
the installation of an XFS is possible at any time.

An XFS is simply a program that installs the driver and then terminates 
and stays resident.

The installation is effected with:

   /* Possibly first: dfskernel =  Dcntl(DFS_GETINFO, "U:\\", NULL); */
     kernel = Dcntl(KER_INSTXFS, NULL, &myxfs);

The return value is a pointer to important kernel functions (or an error 
code). The kernel functions can also be verified independently of the 
Installation of an XFS with:

     kernel = Dcntl(KER_GETINFO, NULL, NULL);

No provision is made for deinstalling an XFS.


V The kernel functions
======================

MagiC makes available to the installed XFSs, DFSs or device drivers some 
kernel information as well as kernel functions. For the kernel functions 
the same register conventions apply as for the XFS functions, i.e. d0-d2 
and a0-a2 may be destroyed. An XFS can well use two of the functions of 
the DFS kernel, which is why this is described here:

A pointer to the structure that contains the functions for the DFS kernel 
can be obtained with:

     dfskernel = Dcntl(DFS_GETINFO, "U:\\", NULL);

The makeup of the DFS kernel structure in detail (as far as is relevant 
to an XFS):

typedef struct {

   WORD  version;

   /* The version number is currently 0*/

   LONG  _dir_srch;
   LONG  reopen_FD;
   LONG  close_DD;

   /* These three functions are only to be used for a DFS */

   WORD  (*match_8_3) ( a0 = char *pattern, a1 = char *fname );

   /* Checks whether a file pattern with search attribute in 
      pattern[11] matches a filename */

   void  (*conv_8_3)(a0 = char *pathname, a1 = char *name);

   /* The first path element contained in <pathname> (before '\' or 
      EOS) is converted to the internal format and copied to <name>.
      During this "*.PAT", for instance, becomes "????????PAT".
      If after name[11] one also writes the search attribute and 
      stores the 12 bytes in the DTA, then together with the function 
      match_8_3() one can realise elegantly a Fsfirst/next */

   LONG  init_DTA;

   /* Only to be used for a DFS */
} MX_DFSKERNEL;

-----

A pointer to the structure that contains the kernel functions can be 
obtained with:

     kernel = Dcntl(KER_INSTXFS, NULL, &myxfs);
or:
     kernel = Dcntl(KER_GETINFO, NULL, NULL);

In the first case an XFS will be installed, in the second case one 
gets only the kernel structure (perhaps for a DFS or a device driver).
The makeup of the kernel structure in detail:

typedef struct {

 int  version;

     /* This is the version number, which is currently 4. For each change 
        of the kernel structure the version number is incremented, so 
        that programs can adapt themselves appropriately. */

 void fast_clrmem             ( a0 = void *from, a1 = void *to );

     /* A fast memory clearing routine that sets memory from <from> to 
        <to> (exclusive) to 0 */

 char toupper                 ( d0 = char c );

     /* Converts the character <c> to capitals, paying regard to the 
        special national characters */ 

 void _sprintf                (char *dest, char *source, long p[]);

     /* A function that expects its arguments on the stack. The data 
        passed is the destination character-string <dest>, the template
        <source> and the values to be entered p[]. The longwords will be 
        interpreted depending on the formating instructions as "unsigned 
        int" (%W), "signed long" (%L) or character-string (%S). For %W 
        the word lying lower in memory will be used. The sequence %% 
        inserts a percentage character. */

 void *act_pd;

     /* The pointer to the current basepage; the address can be 
        established also from the system header or the DOS variables. */

 APPL *act_appl;

     /* The pointer to the current application (i.e. the running task).
        The makeup of the structure is undocumented, the pointer is used 
        as a descriptor in place of the ap_id due to its faster access. */

 APPL *keyb_app;

     /* The application that currently "owns" the keyboard. The pointer 
        can be used by device drivers, for instance, that have to handle 
        the application that owns the keyboard in a different way. */

 int  *pe_slice;
 int  *pe_timer;

     /* For the preemptive multitasking. If <*pe_slice> == -1 then the 
        preemptive multitasking is switched off and disk accesses will 
        not be interrupted. */

 void (*appl_yield)       ( void );

     /* Gives CPU time to the system. This is important for drivers 
        that can not wait for an interrupt and have to prevent its 
        "busy waiting" from blocking the system. */

 void (*appl_suspend)     ( void );

     /* Works like appl_yield(), but gives the application a lower 
        priority, perhaps as a background process. */

 void (*appl_begcritic) ( void );

     /* The current application is entering a critical phase and may not 
        be terminated. */

 void (*appl_endcritic) ( void );

     /* End of the critical phase. If a command to terminate has been 
        received in the meantime, the program will be terminated. */ 

 long (*evnt_IO)          ( d0 = long ticks_50hz, a0 = void *unsel );

     /* evnt_IO() makes it possible to wait for ONE external event. 
        As external event one can have either an interrupt or another 
        application (that perhaps writes to a pipe for awakening).

     The function is used for the creation of device drivers.
     Procedure (see DEV_LPT1 as an example):

     1. Block interrupts.
     2. Query whether the event (in the case of DEV_LPT1 the event is 
        called: "Centronics Busy off") has arrived.
     3. If it has, release interrupts and execute the action (in the case 
        of DEV_LPT1: Print character).
     4. If not, set up interrupt routine (in the case of DEV_LPT1: MFP-I0,
        i.e. activate "Centronics Busy Interrupt") and make available 
        to it the current application (act_appl) as well as a longword 
        address to which the interrupt routine can write return values.
        This address is to be filled with the address of a routine that 
        can deinstall the interrupt again, which can be followed by 
        further data to suit one's own demands, for instance. The 
        unselect routine later gets a pointer to all this data and can, 
        say, evaluate any optional parameters.
        The whole mechanism is necessary in order to guarantee a correct 
        deinitialisation of the interrupt in every case.
     5. Release interrupts.
     6. Call evnt_IO. In d0.w specify the number of 50Hz ticks for the 
        timeout (0 means "no timeout"). In a0 specify the address of the 
        longword that contains the address of the clear-up routine that 
        deinstalls the interrupt again (this may be followed optionally 
        by further parameters if the unselect routine understands them).
        In the case of binding in of device drivers for Fselect() only an
        optional longword is possible.

     When the interrupt arrives the interrupt routine writes the status   
     < 0L (error) or 1L (OK) in place of the deinitialisation routine 
     into the status longword (which previously contained its own start 
     address).
     Following this the routine deinstalls itself, or ensures that no 
     actions will be performed for following interrupts.
     Finally the interrupt routine awakens the application assigned to 
     the interrupt, namely with the call appl_IOcomplete() with the 
     application as parameter.

     7. evnt_IO() gives a return value 0L (timeout, the interrupt did not 
        arrive) or < 0 (the interrupt has written an error code into the
        status longword) or 1L (the interrupt has written a 1L into the
        status longword). A deinstallation of the interrupt is not 
        necessary any more, because the kernel undertook this if the 
        interrupt routine did not do this itself at the arrival of the 
        interrupt. */

 void (*evnt_mIO)         ( d0 = long ticks_50hz, a0 = void *unsel,
                            d1 = int cnt );
 void (*evnt_emIO)        ( a0 = APPL *ap );

     /* evnt_mIO() makes it possible to wait for SEVERAL events; this 
        function is used by Fselect, for instance (several files!).

     Procedure:

      1. For n events create a longword table of length 2*n.
      2. For each event enter the clean-up routine ("unselect routine"),
         (followed by an optional longword parameter) and set up the 
         interrupt. The order is important, one must prevent the 
         unselect address overwriting the return value of the already 
         arrived interrupt; if necessary block interrupts.
      3. Call evnt_mIO. The parameters are as for evnt_IO, but in d1.w 
         the number of events is passed, a0 is the start of the table.
      4. evnt_mIO() gives no return value. The table must be searched 
         and the interrupts deinstalled (for instance by calling the
         unselect routines), and during this one can check which 
         interrupts have arrived already.
      5. Call evnt_emIO with the current application. This call ensures 
         that after the deinstallation of all interrupts their now 
         useless messages to the application will be deleted.

     For the events evnt_(m)IO, waiting applications appear in the 
     program manager as waiting for "io" (Input/Output) or "io ti"
     (Input/Output with timeout). */

 void (*appl_IOcomplete)  ( a0 = APPL *ap );

     /* Awakens an application that is currently waiting for evnt_(m)IO */

 long (*evnt_sem)         ( d0 = int mode, a0 = void *sem,
                            d1 = long timeout );

     /* For mode the following sub-functions are possible:

      0  SEM_FREE  Release semaphore (without task switching!)
                   a0 = Pointer to semaphore
         -> 0      OK
         -> -1     Semaphore unused or used by another APP

      1  SEM_SET   Set semaphore, or wait if appropriate
                   a0 = Pointer to semaphore
                   d1 = Timeout in 50Hz ticks
         -> 0      OK
         -> 1      Timeout
         -> -1     Semaphore was already set by me
         -> -2     Semaphore was removed in meantime

      2  SEM_TEST  Establish owner of semaphore (possibly NULL)
                   a0 = Pointer to semaphore
         -> >0     Owner
         ->  0     Not used

      3  SEM_CSET  Set semaphore, if not set already
                   a0 = Pointer to semaphore
                   d1 = Timeout in 50Hz ticks
         ->  0     OK
         ->  1     Semaphore set by other APPL
         ->  -1    Semaphore was already set by me

      4  SEM_GET   Get semaphore, if name is known
                   d1 = Name of semaphore
         ->  >0    Pointer to semaphore
         ->  -1    Semaphore not found

      5  SEM_CREATE Create semaphore, i.e. organise new one
                   a0 = Pointer to semaphore (32 bytes at even address)
                   d1 = Name
         void

      6  SEM_DEL   Delete semaphore
                   a0 = Pointer to semaphore
         ->   0    OK
         ->  -1    Semaphore invalid

     SEM_SET and SEM_CSET are the only sub-functions that can trigger 
     a task switch.

     In contrast to wind_update() one can not nest the setting and 
     releasing of semaphores with these calls. If one attempts to reserve 
     an already reserved semaphore again, an error code is returned.

     At the release of the semaphore no task switching will be 
     performed, i.e. though the semaphore may perhaps belong to another 
     application that has a "ready" status, this has not yet received 
     any CPU time. If the situation is not critical, then an appl_yield() 
     should be executed in each case afterwards.

     The screen semaphore has the name _SCR and may be handled with 
     evnt_sem() only with the sub-function SEM_TEST.

     SEM_CREATE does not set the creator automatically as owner. This is 
     not necessary, because no task switching has taken place. Thus one 
     can perform a SEM_SET subsequently without hesitation.

     SEM_DEL demands that the deleter is also the owner.
     At deletion all waiting applications will be released and will 
     receive, if they are waiting with evnt_sem(SEM_SET, ..), a -2 as 
     return value.
     Those who delete system semaphores (those whose name starts with '_'),
     have only themselves to blame! */

 void (*Pfree)            ( a0 = void *pd );

     /* Releases memory for a process that was terminated with Ptermres.
     This is necessary to remove a device drivr correctly. */

 int  int_msize;

     /* The length of a memory block of the internal (kernel) memory 
        management */

 void *int_malloc         ( void );

     /* Allocates an internal memory block. If there is no more memory 
        available then a global "garbage collection" will be executed at 
        first (see above in the description of the XFS), and if this 
        fails the system will be stopped.
        Internal memory blocks may be reserved only in small amounts, 
        else one quickly receives an "Out of internal memory" message 
        and the system freezes. */

 void  int_mfree         ( a0 = void *memblk );

     /* Releases a block once more */

 void  resv_intmem       ( a0 = void *mem, d0 = long bytes );

     /* Extends the kernel memory. Provision is made for the possibility 
        of reclaiming the memory. As the kernel only reserves sufficient 
        internal memory for the FAT drives at booting, it may be necessary
        for an XFS at start-up to reserve more kernel memory with this 
        function. */

 long diskchange         ( d0 = int drv );

     /* This must be called when an XFS or DFS driver has recognised a 
        disk change. The XFS driver releases its files and structures, 
        and following this so does the kernel.
        The kernel function diskchange calls xfs_drv_close() with mode 0.
        Returns: EDRIVE  Drive invalid
                 E_CHNG  Drive with new disk valid
    */

 long DMD_rdevinit      ( a0 = DMD *dmd );

   /* From kernel version 1 onwards.
      Initialises the fields <d_driver> and <d_devcode> of the DMD and 
      uses for this the field <d_biosdev>. It is required for disk 
      change mechanisms.
   */

 long proc_info      ( d0 = WORD code, a0 = PD *pd );

   /* From kernel version 2 onwards.
      Obtains data for the current process:
      d0 = 0:  Highest available sub-function number
           1: Domain
           2: Process-ID
   */

 long mxalloc        ( d0 = LONG amount, d1 = WORD mode, a0 = PD *pd );

   /* From kernel version 4 onwards.
      Enables fast memory allocation, e.g. for a RAMdisk XFS, without 
      having to go via a trap.
      <pd> specifies the process that is registered as the owner of the 
      new block, so nornmally the basepage of the XFS driver shoud be 
      passed.
   */

 long mfree       ( a0 = void *block );

   /* From kernel version 4 onwards.
      Releases the memory again.
   */

 long mshrink        ( d0 = LONG newlen, a0 = void *block );

   /* From kernel version 4 onwards.
      Alters the size of a memory block.
   */
} MX_KERNEL;
