AmigaDOS Notification Requests

Notification requests let a program monitor file or directory changes made by other processes. When the filesystem detects a change to the monitored object, it either sends a NotifyMessage to a MsgPort, or sets a signal bit on a Task. Notification requests were added in V36; the RAM-Handler supported them from the start, the Fast File System from V37. Not all filesystems implement them — network filesystems (whose protocol lacks a change-notification primitive) typically do not.

Granularity rules:

  • For a file: the request fires only after the modified file is closed, to avoid one notification per write.
  • For a directory: add/remove files or links, and renames within the directory, fire the request.
  • Changes to metadata (protection bits, comments) are filesystem-dependent — recent AmigaDOS versions treat them as modifications; older or third-party filesystems may not.
  • Assign creation, volume removal, reinsertion, or inhibition of the volume holding the object do not fire the request.
  • Issuing a request on a non-existent object in hope of being notified when it is created is not supported.

The canonical use case is IPrefs: it monitors preference files and reloads settings when the user saves a new version.

The NotifyRequest structure

Defined in dos/notify.h. Allocate with MEMF_CLEAR — some fields require zero-initialization, and once the request is issued the structure belongs to the filesystem and must not be touched.

struct NotifyRequest {
    UBYTE *nr_Name;                  /* path of object to monitor, relative to cwd */
    UBYTE *nr_FullName;              /* filled in by dos.library; do not touch */
    ULONG  nr_UserData;              /* free for caller; returned via NotifyMessage */
    ULONG  nr_Flags;                 /* NRF_SEND_MESSAGE / NRF_SEND_SIGNAL / NRF_NOTIFY_INITIAL / NRF_WAIT_REPLY */
    union {
        struct {
            struct MsgPort *nr_Port;  /* used with NRF_SEND_MESSAGE */
        } nr_Msg;
        struct {
            struct Task *nr_Task;     /* used with NRF_SEND_SIGNAL */
            UBYTE        nr_SignalNum;/* signal-bit *number*, not mask */
            UBYTE        nr_pad[3];
        } nr_Signal;
    } nr_stuff;
    ULONG nr_Reserved[4];            /* must be zero */
    ULONG nr_MsgCount;               /* handler-private; do not touch */
    struct MsgPort *nr_Handler;      /* handler-private; do not touch */
};

Field-by-field:

  • nr_Name — path of the object to monitor, relative to the current directory.
  • nr_FullName — written by the library to the full path; for handler use only.
  • nr_UserData — opaque to the library; lets you tag a request so the recipient can identify which object fired (essential when many requests share one nr_Port).
  • nr_Flags — see table below. Bits 16 and up are reserved for the filesystem; do not set them.
  • nr_Port / nr_Task + nr_SignalNum — exactly one is used, selected by the NRF_SEND_* flag. nr_SignalNum is a bit number, not a mask.
  • nr_Reserved[4] — must be zero.

Flags

Flag Purpose
NRF_SEND_MESSAGE Send a NotifyMessage to nr_Port on change
NRF_SEND_SIGNAL Set signal bit nr_SignalNum on nr_Task on change
NRF_WAIT_REPLY Do not fire again until the previous message has been replied
NRF_NOTIFY_INITIAL Fire once immediately on enqueueing the request

NRF_SEND_MESSAGE and NRF_SEND_SIGNAL are mutually exclusive — exactly one of them must be set. Bits 16 and up are reserved for filesystem-internal use.

NRF_WAIT_REPLY is incompatible with NRF_SEND_SIGNAL (signals have no reply). With NRF_SEND_MESSAGE it prevents notifications from piling up at the recipient's port: the filesystem will not issue a second message until the first has been replied. If multiple changes occurred during the wait, a single follow-up message is generated.

NRF_NOTIFY_INITIAL rolls the initial state read into the same flow as the monitored updates — useful for "show me what's there, then watch for changes" code paths.

The NotifyMessage structure

When NRF_SEND_MESSAGE is set, the filesystem delivers this message to nr_Port:

struct NotifyMessage {
    struct Message      nm_ExecMessage;   /* standard exec Message */
    ULONG               nm_Class;          /* always NOTIFY_CLASS */
    UWORD               nm_Code;           /* always NOTIFY_CODE */
    struct NotifyRequest *nm_NReq;         /* back-pointer to the request that fired */
    ULONG               nm_DoNotTouch;     /* handler-private */
    ULONG               nm_DoNotTouch2;    /* handler-private */
};

nm_NReq lets the recipient disambiguate which request fired when several share one port. The nm_DoNotTouch fields belong to the filesystem.

Intuition-port reuse. The NotifyMessage layout deliberately mirrors IntuiMessage, so an IDCMP MsgPort of an intuition window can also receive NotifyMessages — provided that window stays alive at least as long as the request is active. Do not mix IDCMP and non-IDCMP traffic on the same port without separate handling.

StartNotify — issuing a request

BOOL StartNotify(struct NotifyRequest *nr);   /* V36 */

Returns non-zero on success (with IoErr() undefined), or zero with IoErr() set on failure.

Once issued, the NotifyRequest belongs to the filesystem until EndNotify() succeeds. The handler may read or modify the structure at any time.

EndNotify — canceling a request

void EndNotify(struct NotifyRequest *nr);    /* V36 */

EndNotify() must be called only on requests that succeeded via StartNotify(). Calling it on a failed start is unsafe.

The filesystem will dequeue any NotifyMessages it sent but that the client has not yet replied. The caller is still responsible for replying any message it already dequeued from its own port before terminating the request.

After EndNotify() returns, the NotifyRequest is again available to the caller — it may be freed, or reused for a fresh request.

Worked example

struct NotifyRequest *nr;
struct MsgPort       *port;       /* reply port for the notification */
ULONG                 sigbit;     /* signal bit for the notification, used here only as gate */

/* 1. Create a port. NotifyMessages are exec Messages — any MsgPort works. */
port = CreateMsgPort();
if (!port) { /* fail */ }

/* 2. Allocate the request with MEMF_CLEAR so reserved fields are zero. */
nr = AllocMem(sizeof(*nr), MEMF_CLEAR);
if (!nr) { DeleteMsgPort(port); /* fail */ }

/* 3. Fill in the request. */
nr->nr_Name     = "SYS:Prefs/Env-archive/SYS/font.prefs";
nr->nr_UserData = (ULONG)my_cookie;       /* identify the source */
nr->nr_Flags    = NRF_SEND_MESSAGE | NRF_WAIT_REPLY;
nr->nr_stuff.nr_Msg.nr_Port = port;

/* 4. Issue. */
if (!StartNotify(nr)) {
    LONG err = IoErr();
    FreeMem(nr, sizeof(*nr));
    DeleteMsgPort(port);
    /* handle err */
}

/* 5. Wait for messages. */
for (;;) {
    struct NotifyMessage *nm;
    WaitPort(port);                          /* or Wait(SIGBREAKF_CTRL_C | (1L << port->mp_SigBit)) */
    while ((nm = (struct NotifyMessage *)GetMsg(port)) != NULL) {
        /* do work — preload, react, etc. */
        ReplyMsg((struct Message *)nm);      /* required for NRF_WAIT_REPLY semantics */
    }
    if (user_wants_to_quit) break;
}

/* 6. Tear down. */
EndNotify(nr);
FreeMem(nr, sizeof(*nr));
DeleteMsgPort(port);

See Also


Sources: Thomas Richter, "ROM Kernel Reference Manual: AmigaDOS" (First Edition, 2024), Chapter 7.5 — Notification Requests. Raw: raw/rkm/rkm-dos-book.md Updated: 2026-10-02