The AmigaDOS Device List

The device list is the master catalog maintained by dos.library of every handler, file system, volume, and assign. Each entry is a DosList structure (defined in dos/dosextens.h), a BCPL-PTR-linked node carrying the name used in absolute paths (the part before the colon) and the MsgPort to which operations on that name are dispatched. Open("DF0:foo"), Read(), Lock(), and CreateNewProc() all consult this list via GetDeviceProc().

The list's head sits in the DosInfo structure (Ch. 17.3). It should not be walked manually — use FindDosEntry() and NextDosEntry() instead.

The DosList structure

A DosList is a single struct that covers all four kinds of entries by dol_Type. Three historical alias structures exist (DeviceList, DevInfo, DeviceNode); they overlay the same memory at different field names. The manual recommends using DosList exclusively and ignoring the others.

struct DosList {
    BPTR            dol_Next;            /* BPTR to next entry (ZERO = last) */
    LONG            dol_Type;            /* DLT_DEVICE / DLT_VOLUME / DLT_DIRECTORY / DLT_LATE / DLT_NONBINDING / DLT_PRIVATE */
    struct MsgPort *dol_Task;            /* port of handler/file system */
    BPTR            dol_Lock;            /* bound lock (assigns only) */
    union {
        struct {                         /* dol_Type == DLT_DEVICE */
            BSTR   dol_Handler;
            LONG   dol_StackSize;
            LONG   dol_Priority;
            ULONG  dol_Startup;
            BPTR   dol_SegList;
            BPTR   dol_GlobVec;
        } dol_handler;
        struct {                         /* dol_Type == DLT_VOLUME */
            struct DateStamp dol_VolumeDate;
            BPTR            dol_LockList;
            LONG            dol_DiskType;
            APTR            dol_NotifyList;  /* undocumented */
        } dol_volume;
        struct {                         /* dol_Type == DLT_DIRECTORY / DLT_LATE / DLT_NONBINDING */
            UBYTE             *dol_AssignName;
            struct AssignList *dol_List;
        } dol_assign;
    } dol_misc;
    BSTR             dol_Name;           /* BSTR; NUL-terminated as a courtesy */
};

Entry types

dol_Type Meaning
DLT_DEVICE File system or handler (e.g., DF0:, CON:, PRT:)
DLT_VOLUME A volume on a mounted medium
DLT_DIRECTORY Regular or multi-assign
DLT_LATE Late-binding assign
DLT_NONBINDING Non-binding assign
DLT_PRIVATE Internal, used to resolve late-binding assigns

dol_Task semantics vary by type:

  • File system / handler: MsgPort of the handler; NULL if not running (CON-Handler stays NULL because each window is its own process).
  • Volume: port of the underlying FS; NULL while the medium is ejected but locks/handles to the volume are still open (Workbench shows a dimmed icon).
  • Regular assign: port of the bound FS (first directory of a multi-assign).
  • Late-binding assign: initially NULL, populated on first resolution.
  • Non-binding assign: always NULL.

dol_Lock is the lock to the bound directory; it is ZERO for non-binding, late (until bound), and for all non-assign types.

dol_Name is a BSTR; for C convenience dos.library guarantees an extra NUL byte after the BCPL string, so ((UBYTE *)BADDR(dl->dol_Name)) + 1 is a regular C string.

GetDeviceProc — resolving a path to a port

struct DevProc *GetDeviceProc(STRPTR name, struct DevProc *prev);   /* V36 */
void            FreeDeviceProc(struct DevProc *dp);                  /* V36 */

GetDeviceProc() returns a DevProc describing the next handler/file system candidate for name. Pass prev = NULL on the first call; pass the previous return value to iterate. NULL means "no match" or "scan finished" — in either case, do not call FreeDeviceProc() on it (it already released).

struct DevProc {
    struct MsgPort *dvp_Port;      /* MsgPort of the candidate handler */
    BPTR            dvp_Lock;     /* dir to use as base for the rest of name */
    ULONG           dvp_Flags;    /* DVPF_ASSIGN (multi-assign candidate), DVPF_UNLOCK (internal) */
    struct DosList *dvp_DevNode;  /* handler-private, do not touch */
};

The substring of name after the colon (or the whole name, if no colon) is interpreted relative to dvp_Lock on the filesystem reached via dvp_Port. If the file does not exist there and the lock came from a multi-assign, iterate again with the same name to try the next directory.

Important restrictions of GetDeviceProc()

  • dvp_Lock is owned by dos.library — do not UnLock() it, do not pass it to Examine()/ExAll(). DupLock() first if you need to keep it.
  • A ZERO dvp_Lock means the root of the file system (not the boot volume's root) — path resolution then proceeds from there.
  • IoErr() is unreliable on success; check the boolean only.
  • GetDeviceProc() does not handle NIL:, *, or paths relative to CONSOLE:/PROGDIR: reliably; filter these out and use GetConsoleTask() for the console case.
  • Names longer than 30 characters are truncated — old BCPL limit.
  • Assigns can be removed underneath an in-flight GetDeviceProc(), so locks handed out from assigns may dangle.

Side effects of GetDeviceProc

If the matching handler is not loaded, it will be loaded (using dol_Handler path) in the context of the calling process. If dol_Task is NULL, an instance of the handler is started with name as a BSTR in the startup packet. The CON-Handler uses this to know which window to open — historically it opened the window before any packet round-trip, which produced stray windows on early V37 (fixed in V39). Once started, a CON-Handler process remains alive until its last open filehandle is closed; GetDeviceProc() therefore has the side effect of leaving a process around.

Recommendation. Use GetDeviceProc() only as a preparation step for a subsequent packet send (Ch. 12). Avoid it for handlers you do not intend to communicate with.

Iterating the device list directly

For tasks like enumerating mounts or looking up dol_Startup, walk the list yourself.

LockDosList / AttemptLockDosList / UnLockDosList

struct DosList *LockDosList(ULONG flags);        /* V36, blocks */
struct DosList *AttemptLockDosList(ULONG flags); /* V36, non-blocking */
void            UnLockDosList(ULONG flags);     /* V36 */

Flags (defined in dos/dosextens.h):

Flag Meaning
LDF_DEVICES Handlers and file systems
LDF_VOLUMES Volumes
LDF_ASSIGNS Assigns
LDF_ALL LDF_DEVICES \| LDF_VOLUMES \| LDF_ASSIGNS
LDF_ENTRY Internal only — used by handler startup; do not set
LDF_DELETE Internal only — LDF_WRITE access for removal; do not set
LDF_READ Shared access (must be set with one of the above)
LDF_WRITE Exclusive access (must be set with one of the above)

LDF_READ and LDF_WRITE are mutually exclusive. NULL return from LockDosList() means the flag combination is invalid; from AttemptLockDosList() it means either invalid flags or "list is currently locked" — the two cannot be distinguished.

Deadlock warning. Handlers and filesystems must not call LockDosList() from inside packet handlers. dos.library may already be holding the list lock from inside GetDeviceProc() while it loads a handler; a handler that then blocks on LockDosList() will deadlock the system. Use AttemptLockDosList(), and if it fails, continue serving packets and retry.

NextDosEntry — step through the list

struct DosList *NextDosEntry(struct DosList *dlist, ULONG flags);   /* V36 */

Pass the return value of LockDosList()/AttemptLockDosList() (or a previous NextDosEntry()/FindDosEntry()) as dlist. flags should be a subset of the lock flags. Returns the next matching DosList or NULL at end of list.

void ListAllVolumes(void)
{
    struct DosList *dl;

    dl = LockDosList(LDF_VOLUMES | LDF_READ);
    while ((dl = NextDosEntry(dl, LDF_VOLUMES)) != NULL) {
        UBYTE *name = ((UBYTE *)BADDR(dl->dol_Name)) + 1;
        /* DO NOT print here — printing implies a packet to the
         * console, which itself may need the device list locked. */
        /* Copy name to a local buffer if you need it later. */
    }
    UnLockDosList(LDF_VOLUMES | LDF_READ);
}

FindDosEntry — name lookup

struct DosList *FindDosEntry(struct DosList *dlist, STRPTR name, ULONG flags);   /* V36 */

Search starting at dlist for the next entry whose dol_Name matches name (case-insensitive) and whose dol_Type is in flags. Pass name = NULL to match any. Volumes may share a name (distinguished by dol_VolumeDate); handlers and assigns may not. Pseudo-devices (NIL:, CONSOLE:, *, PROGDIR:) are not on the list and cannot be found here.

Adding and removing entries

AddDosEntry

LONG AddDosEntry(struct DosList *dlist);   /* V36 */

AddDosEntry() acquires LDF_WRITE itself — do not lock the list first. Returns non-zero on success (the DosList is now library-owned and must not be touched); 0 with IoErr() = ERROR_OBJECT_EXISTS on a name clash. Two volumes of the same name are permitted only if their dol_VolumeDate differs.

Use AddDosEntry() for handler/filesystem/volume entries. For assigns use the AssignLock/AssignPath/AssignLate family.

Handlers adding volumes during packet handling must pre-check availability with AttemptLockDosList() first, otherwise a deadlock with dos.library is possible.

RemDosEntry

BOOL RemDosEntry(struct DosList *dlist);   /* V36 */

RemDosEntry() does not lock the list itself; the caller must hold the appropriate LDF_WRITE lock. Returns non-zero on success; the only failure mode is "entry not on list". Does not release the structure's memory or resources — that is the caller's job. For assigns, prefer AssignLock(name, ZERO).

MakeDosEntry / FreeDosEntry

struct DosList *MakeDosEntry(STRPTR name, LONG type);   /* V36 */
void            FreeDosEntry(struct DosList *dlist);    /* V36 */

MakeDosEntry() allocates a DosList and initializes only dol_Type and dol_Name (as a NUL-terminated BSTR); everything else is zero. The caller must fill in the union before AddDosEntry(). Names are recommended to be ≤ 30 chars even though MakeDosEntry() does not enforce it.

FreeDosEntry() releases the structure and its name, nothing more. It is not safe to use freely:

  • For DLT_DEVICE entries: MakeDosNode() (from expansion.library) uses a different allocator, so FreeDosEntry() is unsafe; memory leaks are the recommended outcome.
  • For DLT_DIRECTORY, DLT_LATE, DLT_NONBINDING: use AssignLock(name, ZERO) instead.
  • For DLT_VOLUME: only the managing file system knows how to free the resources it allocated.

Practical application-level use of FreeDosEntry() is essentially zero; the function exists for internal dos.library use.

GetConsoleTask / GetFileSysTask / DeviceProc

struct MsgPort *GetConsoleTask(void);   /* V36 — port of the caller's console */
struct MsgPort *GetFileSysTask(void);   /* V36 — port of the default FS */
struct MsgPort *DeviceProc(STRPTR);     /* legacy; prefer GetDeviceProc */

GetConsoleTask() returns the port of the handler responsible for * / CONSOLE: paths of the calling process. Returns NULL for Workbench-launched programs (no console).

GetFileSysTask() returns the port of the default file system — the one used as fallback when resolving paths relative to the ZERO lock. By default this is the boot volume's FS, possibly overridden with SetFileSysTask().

DeviceProc() is the legacy single-shot equivalent of GetDeviceProc(): it does not iterate multi-assigns, does not handle non-binding assigns, and suffers from the same race conditions. It is documented for source-compatibility only; do not use in new code.

See Also


Sources: Thomas Richter, "ROM Kernel Reference Manual: AmigaDOS" (First Edition, 2024), Chapter 8 — Administration of Volumes, Devices and Assigns. Raw: raw/rkm/rkm-dos-book.md Updated: 2026-10-02