trackfile.device API and DAControl Reference

trackfile.device (AmigaOS 3.2) is a trackdisk.device clone backed by ADF disk image files. Besides the usual device vectors it has library-style functions that start and stop units and insert and eject image files: the TF* calls declared in <devices/trackfile.h> and SFD/trackfile_lib.sfd. The DAControl shell command is the reference client for these calls, and the AmigaOS 3.2 installer uses it to swap virtual floppies.

This article covers the API in detail: function prototypes and registers, tags, error codes, the unit model, the DAControl template, and a minimal C client. For an overview of the driver and its source layout, see trackfile.device and DAControl.

Units and the control unit

Constant Value Meaning
TRACKFILENAME 3.2+ "trackfile.device" Device name for OpenDevice()
TFUNIT_CONTROL 3.2+ -1 Control unit. Open it to use the TF* functions
TFSU_NextAvailableUnit 3.2+ -1 Pass to TFStartUnitTagList() to pick the next unused unit number
TFGUD_AllUnits 3.2+ -1 Pass to TFGetUnitData() to list all units
TFEFS_Unsupported 3.2+ -1 Returned by TFExamineFileSize() for an unsupported size
TF_MINIMUM_CACHE_SIZE 3.2+ 46000 Minimum shared cache size (8 tracks)

From the driver's --background-- autodoc (in trackfile_device.c):

  • trackdisk.device had at most 4 units, fixed at power-up. trackfile.device adds virtual drives as you need them, limited only by memory. Release note 2.35 says the cache code sets an upper limit of 8,388,608 units.
  • A unit accepts 880 KB (3.5" DD, 901,120 bytes) or 1760 KB (3.5" HD, 1,802,240 bytes) images in the Amiga 1.0 format. 40-track 5.25" images and IBM 720K/1440K formats are not supported.
  • To call the library functions, open the device with unit TFUNIT_CONTROL. Open a numbered unit (≥ 0) only for normal trackdisk-style I/O, and only after that unit has been started.

A unit has three states:

  1. Not started. No memory and no unit Process.
  2. Started (active), no medium. TFStartUnitTagList() has allocated the unit and created its Process. It behaves like an empty drive.
  3. Medium present. TFInsertMediaTagList() has attached an image file. While inserted, the file stays open, so it cannot be deleted or moved.

The device does not create an AmigaDOS device such as DA0:. DAControl mounts one itself (see Mounting a DOS device).

Function table

trackfile_lib.sfd 2.3 uses base _TrackFileBase (type struct Device *) and bias 6. The first six slots are the private device vectors (Open, Close, Expunge, Reserved, BeginIO, AbortIO), so the public functions start at -42.

LVO Function Registers Since
-42 LONG TFStartUnitTagList(LONG which_unit, CONST struct TagItem *tags) d0, a0 V1
-48 LONG TFStopUnitTagList(LONG which_unit, CONST struct TagItem *tags) d0, a0 V1
-54 LONG TFInsertMediaTagList(LONG which_unit, CONST struct TagItem *tags) d0, a0 V1
-60 LONG TFEjectMediaTagList(LONG which_unit, CONST struct TagItem *tags) d0, a0 V1
-66 struct TrackFileUnitData *TFGetUnitData(LONG which_unit) d0 V1
-72 VOID TFFreeUnitData(struct TrackFileUnitData *tfud) a0 V1
-78 LONG TFChangeUnitTagList(LONG which_unit, CONST struct TagItem *tags) d0, a0 V2
-84 LONG TFExamineFileSize(LONG file_size) d0 V2

Every ...TagList function also has a varargs form: TFStartUnitTags(), TFStopUnitTags(), TFInsertMediaTags(), TFEjectMediaTags(), TFChangeUnitTags(). The offsets match Include_I/lvo/trackfile_lib.i, and proto/trackfile.h declares extern struct Device *TrackFileBase.

Version check. DAControl requires trackfile.device 2.15 or later because it uses TFChangeUnitTagList() and TFExamineFileSize(). It checks lib_Version/lib_Revision on the opened device. The function was first called TFChangeMediaTagList() and was renamed TFChangeUnitTagList() in 2.37.

Process only. TFStartUnitTagList(), TFInsertMediaTagList() and TFEjectMediaTagList() return TFERROR_Denied when called from a plain Task, because they open and close files. Release note 2.26 says that API functions needing a Process "now return either an error code or NULL" instead of crashing.

Functions

TFStartUnitTagList

Allocates the unit and creates the unit Process. which_unit must be ≥ 0, or TFSU_NextAvailableUnit. It returns the unit number started (≥ 0) or a negative error code.

Tag Type Meaning
TF_DriveType 3.2+ LONG DRIVE3_5 (DD, default) or DRIVE3_5_150RPM (HD), from <devices/trackdisk.h>
TF_EnableChecksums 3.2+ BOOL Keep track and disk checksums so that two images with the same contents cannot be inserted. Default FALSE
TF_MaxCacheMemory 3.2+ ULONG Shared cache size in bytes. Only takes effect the first time, before the cache exists. Default 0

Errors: TFERROR_Denied, TFERROR_InvalidDriveType, TFERROR_UnitBusy (already started), TFERROR_OutOfMemory, TFERROR_ProcessFailed (check IoErr()).

TFStopUnitTagList

Shuts down an active unit and frees its Process. DAControl says this releases "between 12-17 KBytes of RAM". No tags are defined. It returns 0 or an error: TFERROR_UnitNotFound, ERROR_OBJECT_IN_USE (a medium is still inserted, so eject it first), or TFERROR_Aborted (the unit is already shutting down). A stopped unit can be started again.

TFInsertMediaTagList

Attaches an image file to a started, empty unit.

Tag Type Meaning
TF_ImageFileName 3.2+ STRPTR Path of the ADF, relative to the caller's current directory
TF_ImageFileHandle 3.2+ BPTR An already-open file. If this is non-NULL, TF_ImageFileName is ignored. On success the unit takes ownership of the handle
TF_WriteProtected 3.2+ BOOL Insert the image read-only (non-zero) or writable (FALSE)
TF_EnableUnitCache 3.2+ BOOL Let this unit use the shared read cache. Default FALSE
TF_PrefillUnitCache 3.2+ BOOL Read the whole image into the cache when it is inserted. Default FALSE

The TF_WriteProtected autodoc says "Defaults to FALSE", but in functions.c the local write_protected starts as TRUE. Pass the tag explicitly. DAControl also inserts read-only unless you give WRITEPROTECTED=NO. A writable insert can still come out read-only if the image file or the volume it is on cannot be written.

Errors: TFERROR_Denied, TFERROR_UnitNotFound, TFERROR_AlreadyInUse (a medium is already present), TFERROR_InvalidFile, TFERROR_NoFileGiven, TFERROR_InvalidFileSize, TFERROR_DuplicateVolume, TFERROR_DuplicateDisk, TFERROR_OutOfMemory.

Insertions and ejections send the normal disk-change notifications, so software that uses TD_ADDCHANGEINT sees them as disk swaps (release note 2.31).

TFEjectMediaTagList

Detaches the image and writes back any buffered track data first.

Tag Type Meaning
TF_Timeout 3.2+ LONG Seconds to wait for the file system to "settle". Values below 5 mean eject immediately, which may fail while the disk is busy. Default 0

While a timeout is running, Ctrl-C cancels the wait and the function returns ERROR_BREAK. Other errors: TFERROR_Denied, TFERROR_UnitNotFound, TDERR_SeekError (the image size or attributes changed, so it may be truncated), TDERR_WriteProt (buffered data could no longer be written back), and TDERR_DiskChanged.

TFGetUnitData / TFFreeUnitData

TFGetUnitData(n) returns an independent snapshot of one unit. With TFGUD_AllUnits it returns a linked list of all units. On failure it returns NULL; check IoErr(). Always release the result with TFFreeUnitData(), which accepts NULL.

struct TrackFileUnitData {
    struct TrackFileUnitData *tfud_Next;   /* next unit or NULL */
    ULONG   tfud_Size;                     /* size of this structure */
    LONG    tfud_UnitNumber;
    LONG    tfud_DriveType;                /* DRIVE3_5, DRIVE3_5_150RPM */
    STRPTR  tfud_DeviceName;               /* e.g. "DA0", may be NULL */
    STRPTR  tfud_FileName;                 /* image path, may be NULL */
    BOOL    tfud_IsActive;                 /* unit Process running */
    BOOL    tfud_IsWritable;
    BOOL    tfud_MediumIsPresent;
    BOOL    tfud_IsBusy;                   /* I/O in progress ("motor on") */
    BOOL    tfud_ChecksumsEnabled;
    struct TrackFileChecksum tfud_Checksum;  /* 64-bit: tfc_high, tfc_low */
    BOOL    tfud_VolumeValid;
    TEXT    tfud_VolumeName[32];
    struct DateStamp tfud_VolumeDate;
    ULONG   tfud_FileSysSignature;         /* may be 0 */
    ULONG   tfud_BootBlockChecksum;        /* may be 0 */
    BOOL    tfud_CacheEnabled;
    ULONG   tfud_CacheAccesses;
    ULONG   tfud_CacheMisses;
};

The volume, boot block and checksum fields are updated live as the image changes.

TFChangeUnitTagList (V2)

Changes settings on a running unit, or global settings when which_unit is TFUNIT_CONTROL.

Tag Type Applies to Meaning
TF_WriteProtected 3.2+ BOOL unit ≥ 0 Change the write protection without a manual eject. Clearing it fails if the volume or the file is read-only
TF_EnableUnitCache 3.2+ BOOL unit ≥ 0 Turn cache use on or off for this unit. Disabling it invalidates the unit's entries and resets its counters
TF_MaxCacheMemory 3.2+ ULONG TFUNIT_CONTROL only Resize the shared cache. 0 disables it
TF_TagItemFailed 3.2+ struct TagItem ** any Receives the address of the tag that failed

Tags are processed in order, and processing stops at the first error, so earlier tags may already have taken effect.

TFExamineFileSize (V2)

TFExamineFileSize(size) maps an image size to a drive type: DRIVE3_5 for 901,120 bytes, DRIVE3_5_150RPM for 1,802,240 bytes, otherwise TFEFS_Unsupported (-1). The autodoc also lists DRIVE5_25, but 5.25" images are not currently supported and the implementation's size table has no entry for them. DAControl calls this function before it inserts each wildcard match.

Error codes

TFStart/Stop/InsertMedia/EjectMedia can return these codes in addition to normal dos.library and trackdisk.device errors. The text column is the message DAControl prints (tools.c); a dash means DAControl has no specific text for that code.

Code Value DAControl message
TFERROR_UnitBusy 3.2+ -202041 unit is busy
TFERROR_OutOfMemory 3.2+ -202042 out of memory
TFERROR_UnitNotFound 3.2+ -202043 unit not found
TFERROR_AlreadyInUse 3.2+ -202044 unit is already in use
TFERROR_UnitNotActive 3.2+ -202045 unit is not active
TFERROR_InvalidFile 3.2+ -202046 disk file is not valid
TFERROR_InvalidFileSize 3.2+ -202047 disk file size is not supported
TFERROR_NoFileGiven 3.2+ -202048 no disk file was given
TFERROR_Aborted 3.2+ -202049 command was aborted
TFERROR_InvalidDriveType 3.2+ -202050 —
TFERROR_ProcessFailed 3.2+ -202051 unit process creation has failed
TFERROR_NoMediumPresent 3.2+ -202052 no medium is present
TFERROR_ReadOnlyVolume 3.2+ -202053 image file parent volume is not writable
TFERROR_ReadOnlyFile 3.2+ -202054 image file is not writable
TFERROR_DuplicateDisk 3.2+ -202055 contents of image file are duplicate of an active file
TFERROR_DuplicateVolume 3.2+ -202056 contents would likely crash the Amiga file system
TFERROR_Denied 3.2+ -202057 —
TFERROR_NotSupported 3.2+ -202058 —

Tag summary

All tags are based on TF_Dummy = TAG_USER + 0x20200400.

Tag Offset Type Used by
TF_ImageFileName 3.2+ +1 STRPTR Insert
TF_ImageFileHandle 3.2+ +2 BPTR Insert
TF_WriteProtected 3.2+ +3 BOOL Insert, Change
TF_DriveType 3.2+ +4 LONG Start
TF_Timeout 3.2+ +5 LONG Eject
TF_EnableChecksums 3.2+ +6 BOOL Start
TF_TagItemFailed 3.2+ +7 struct TagItem ** Change
TF_MaxCacheMemory 3.2+ +8 ULONG Start (control unit, once), Change (control unit)
TF_EnableUnitCache 3.2+ +9 ULONG/BOOL Insert, Change
TF_PrefillUnitCache 3.2+ +10 BOOL Insert

Duplicate-disk protection

Two mounted volumes with the same root name and the same creation date can crash the AmigaDOS file system. trackfile.device guards against this in two ways:

  • TFERROR_DuplicateVolume (2.21 and later): before inserting, the driver reads the root block and compares its name and date with the volume nodes already in use.
  • TFERROR_DuplicateDisk: when a unit has TF_EnableChecksums, a 64-bit checksum over all tracks, with the file size added in (2.18), is compared with the other units. Turning this on means the whole image is read when it is inserted, so mounting is slower. DAControl turns it on with USECHECKSUMS=YES.

The shared cache

The read cache is shared by all units and is enabled by default from 2.44. Each entry costs 32 + 5632 = 5664 bytes, and sizes are rounded to whole entries. Successful reads fill the cache, successful writes update existing entries only, and ejecting an image invalidates that unit's entries. According to the DAControl release notes, PREFILLCACHE only makes sense when the cache holds at least 901,120 bytes; otherwise the prefill evicts its own entries. Prefill is ignored when the image is larger than the cache.

Mounting a DOS device

The device only emulates the drive. To get a DAx: volume, DAControl (mount_floppy_file.c) builds a DosEnvec the way the floppy "strap" module does, and adds it with AddDosEntry() (not AddBootNode()). The main values:

Field Value
de_TableSize DE_BOOTBLOCKS
de_SizeBlock TD_SECTOR / 4
de_Surfaces 2
de_BlocksPerTrack 11 (DD) or 22 (HD)
de_Reserved / de_BootBlocks 2
de_LowCyl / de_HighCyl 0 / 79
de_NumBuffers 5
de_BootPri -128 (never bootable)
de_DosType ID_DOS_DISK

The file system comes from the FileSystem.resource entry with the matching DOS type (the highest version wins), or from the file named by FILESYSTEM. When it ejects or stops a unit, DAControl calls Inhibit() on the DOS device around TFEjectMediaTags().

Minimal C example

This example opens the control unit, starts a unit, inserts an image read-only, prints the unit list, then ejects the image and stops the unit. It does not mount a DOS device, so the data is reachable only through trackdisk-style I/O on the unit. Run it from the Shell, because the calls need a Process.

#include <exec/types.h>
#include <exec/io.h>
#include <dos/dos.h>
#include <devices/trackdisk.h>
#include <devices/trackfile.h>

#include <proto/exec.h>
#include <proto/dos.h>
#include <proto/trackfile.h>

#include <string.h>

struct Device *TrackFileBase;

int main(int argc, char **argv)
{
    struct IOStdReq ctrl;
    struct TrackFileUnitData *tfud, *t;
    struct FileInfoBlock *fib;
    BPTR lock;
    LONG drive_type = TFEFS_Unsupported;
    LONG unit, error;
    int rc = RETURN_FAIL;

    if (argc != 2)
        return RETURN_ERROR;

    memset(&ctrl, 0, sizeof(ctrl));
    if (OpenDevice(TRACKFILENAME, TFUNIT_CONTROL, (struct IORequest *)&ctrl, 0) != 0)
    {
        Printf("Cannot open %s\n", TRACKFILENAME);
        return RETURN_FAIL;
    }
    TrackFileBase = ctrl.io_Device;

    /* TFExamineFileSize() is V2; DAControl requires 2.15 */
    if (TrackFileBase->dd_Library.lib_Version < 2)
        goto out;

    /* Pick the drive type from the image size */
    if ((fib = AllocDosObject(DOS_FIB, NULL)) != NULL)
    {
        if ((lock = Lock(argv[1], SHARED_LOCK)) != 0)
        {
            if (Examine(lock, fib))
                drive_type = TFExamineFileSize(fib->fib_Size);
            UnLock(lock);
        }
        FreeDosObject(DOS_FIB, fib);
    }
    if (drive_type == TFEFS_Unsupported)
    {
        Printf("%s: not an 880K/1760K ADF\n", argv[1]);
        goto out;
    }

    unit = TFStartUnitTags(TFSU_NextAvailableUnit,
                           TF_DriveType, drive_type,
                           TAG_DONE);
    if (unit < 0)
    {
        Printf("Start failed (%ld)\n", unit);
        goto out;
    }

    error = TFInsertMediaTags(unit,
                              TF_ImageFileName,  argv[1],
                              TF_WriteProtected, TRUE,
                              TAG_DONE);
    if (error != 0)
    {
        Printf("Insert failed (%ld)\n", error);
        TFStopUnitTags(unit, TAG_DONE);
        goto out;
    }

    tfud = TFGetUnitData(TFGUD_AllUnits);
    for (t = tfud; t != NULL; t = t->tfud_Next)
    {
        Printf("unit %ld  medium=%s  file=%s  volume=%s\n",
               t->tfud_UnitNumber,
               t->tfud_MediumIsPresent ? "yes" : "no",
               t->tfud_FileName ? t->tfud_FileName : "-",
               t->tfud_VolumeValid ? t->tfud_VolumeName : "-");
    }
    TFFreeUnitData(tfud);

    /* Wait up to 5 seconds for the image to settle, then release it */
    error = TFEjectMediaTags(unit, TF_Timeout, 5, TAG_DONE);
    if (error == 0)
        error = TFStopUnitTags(unit, TAG_DONE);

    rc = (error == 0) ? RETURN_OK : RETURN_WARN;

out:
    CloseDevice((struct IORequest *)&ctrl);
    return rc;
}

DAControl command reference

C:DAControl (source revision 2.35; the release notes end at 2.34) loads, ejects, changes and creates ADF images. It needs trackfile.device 2.15 or later.

LOAD/S,EJECT/S,CHANGE/S,TIMEOUT/K/N,START/S,STOP/S,CREATE/S,
INSTALL=BOOTABLE/S,USECHECKSUMS/K,ENABLECACHE/K,PREFILLCACHE/K,
CACHESIZE/K/N,SAFEEJECT/K,FILESYSTEM/K,FILESYSTEMTYPE=FSTYPE/K,
OVERWRITE/S,DISKTYPE/K,LABEL/K,PROTECT=WRITEPROTECTED/K,
UNIT=DEVICE/K,INFO/S,SHOWCHECKSUMS/S,SHOWVOLUMES/S,
SHOWBOOTBLOCKS/S,SHOWCACHES/S,SETENV/S,SETVAR/S,QUIET/S,
VERBOSE/S,IGNORE/S,FILE/M

The cache keywords (ENABLECACHE, PREFILLCACHE, CACHESIZE, SHOWCACHES) are compiled in when ENABLE_CACHE is defined in cache.h, which it is by default since DAControl 2.30.

Option Effect
LOAD Start or reuse a unit and mount the image(s). Wildcards are allowed
EJECT Eject the medium of DEVICE. After this the file can be moved or deleted
CHANGE Eject (if a medium is present) and load a new file. Without FILE, it changes the write protection or cache setting of a running unit
TIMEOUT n Seconds to wait for a busy medium on eject. The minimum is 5. Ctrl-C or BREAK cancels
START / STOP Start a unit without a medium, or stop one after ejecting. Combine with EJECT
UNIT=DEVICE A unit number or a name such as DA0:. ANY picks or starts a free unit. LAST reads DA_LASTDEVICE
WRITEPROTECTED=PROTECT YES (the default) or NO for a writable image
USECHECKSUMS YES turns on disk and track checksums (duplicate-disk check)
SAFEEJECT YES asks the file system to flush and release the medium, which is faster than waiting for the "motor" to stop. Some software may not notice that the volume has gone
CREATE Create, format and mount a blank image (always writable). Refuses to overwrite an existing file unless you give OVERWRITE
BOOTABLE=INSTALL Make the created image bootable
DISKTYPE DD (880 KB) or HD (1.76 MB) for CREATE
LABEL Volume name for CREATE
FILESYSTEMTYPE=FSTYPE OFS or FFS, optionally +INTERNATIONAL, +DIRCACHE or +LONGNAMES
FILESYSTEM Load this file system instead of the floppy default. It is reloaded on every use, so watch memory
INFO List the units. Add SHOWCHECKSUMS, SHOWVOLUMES, SHOWBOOTBLOCKS or SHOWCACHES for more columns
SETVAR / SETENV Store the last device used in the local or global variable DA_LASTDEVICE
QUIET / VERBOSE Less or more output
IGNORE Keep going past missing or unsuitable files (from 2.23)
ENABLECACHE, PREFILLCACHE Per-unit cache use at insert time. PREFILLCACHE=YES implies ENABLECACHE=YES
CACHESIZE n Shared cache size in bytes. 0 is valid and turns the cache off

Examples

1> DAControl LOAD #?.adf
1> DAControl LOAD disk.adf DEVICE DA0:
1> DAControl LOAD adf/test1.adf WRITEPROTECTED=NO
1> DAControl CREATE LABEL=empty adf/empty.adf
1> DAControl CREATE FSTYPE=FFS+INTL adf/empty.adf
1> DAControl CHANGE DEVICE=DA0: PROTECT=YES
1> DAControl EJECT TIMEOUT=5 STOP UNIT 0
1> DAControl INFO
Device  Type     Active  Access     File
DA0     3.5" DD  Yes     read-only  Archive:Demo 1.adf
DA2     3.5" DD  Yes *   read-only  Archive:Mindwalker.adf
DA5     3.5" DD  No      -          -

Yes * means the unit is busy (I/O in progress). A unit showing No has been stopped, but its DAx: entry remains and looks like an empty drive. SHOWCHECKSUMS prints the 64-bit disk checksum as an 11-character string.

In installation scripts

DAControl was written so that one install script works from any medium. A script typically loads a disk image into a fixed device, works with the volume, and ejects it:

DAControl LOAD ADF/Disk1.adf DEVICE DA0: QUIET
; ... copy files from the mounted volume ...
DAControl EJECT DEVICE DA0: TIMEOUT 10 QUIET

When the unit doesn't matter, combine DEVICE=ANY with SETVAR and then refer to $DA_LASTDEVICE or DEVICE=LAST.

Workbench icons

A project icon whose default tool is C:DAControl mounts its ADF when double-clicked. process_icons.c reads these tool types: WRITEPROTECTED (or PROTECTED), USECHECKSUMS, FILESYSTEM, ENABLECACHE, PREFILLCACHE, CACHESIZE.

Gotchas from the release notes

  • Change interrupts: as with trackdisk.device, an IOStdReq used for TD_ADDCHANGEINT is not replied. It stays queued until TD_REMCHANGEINT is sent with the same request (2.46). TD_REMCHANGEINT and CMD_START always run on the caller's context.
  • IOF_QUICK is honoured only when the flag is actually set (2.26), and only a small subset of commands support it.
  • FFS V40 quirk: requests whose mn_Length is 0 skip the message-length validity checks, because FFS V40 would otherwise fail to mount (2.25).
  • Old file systems: if the volume holding the image lacks ExamineFH(), NameFromFH() and similar calls, the driver falls back, and the full path name or write-protect detection may not be available (2.32/2.33).
  • Stricter than trackdisk: trackfile rejects some invalid requests that trackdisk.device silently accepts, such as a non-chip TD_RAWREAD buffer. A rejection usually points to undefined behaviour on real hardware too.

See Also


Sources: NDK 3.2 R4 SFD/trackfile_lib.sfd, FD/trackfile_lib.fd, Include_H/devices/trackfile.h, Include_H/devices/trackdisk.h, Include_I/lvo/trackfile_lib.i, Include_H/proto/trackfile.h, DAControl+trackfile/README, DAControl+trackfile/trackfile/{functions.c,trackfile_device.c}, DAControl+trackfile/DAControl/{cmd_main.c,start_unit.c,insert_media_by_name.c,mount_floppy_file.c,process_icons.c,tools.c,cache.h}, ReleaseNotes/trackfile-RelNotes, ReleaseNotes/DAControl-RelNotes Updated: 2026-11-13