Links

A link makes a file system object appear at a second location. Modifying the object through either path changes the same data, and for a linked directory, entries created through one path also appear through the other. AmigaDOS supports three kinds:

  • Hard links (V36) are resolved entirely by the file system and must stay on the same volume.
  • Soft links (V36, reliable only from V45) store a path and are resolved by dos.library together with the file system; they can cross volumes and file systems.
  • External links (V47, RAM-Handler only) are a "copy on read" link from RAM: to another volume, used to implement ENV:.

Links turn the directory tree into a graph, so programs that walk directories recursively must take care not to follow them blindly. This page describes how each kind behaves, how to create and resolve links with MakeLink() and ReadLink(), which functions understand soft links, and the many version-specific defects.

Overview

Hard link Soft link External link
Since V36 V36 (unreliable before V45) V47
Target location Same volume Anywhere, as a path Another volume (RAM-Handler only)
MakeLink() type LINK_HARD + lock LINK_SOFT + C string LINK_HARD + lock on another volume
Resolved by File system File system + dos.library (ReadLink()) RAM-Handler, by copying
fib_DirEntryType ST_LINKFILE/ST_LINKDIR on FFS and RAM; indistinguishable elsewhere ST_SOFTLINK Indistinguishable from a regular entry
Target deleted A link takes over the object (FFS, RAM) Link dangles; resolution fails with ERROR_OBJECT_NOT_FOUND Uncopied: access fails with ERROR_OBJECT_NOT_FOUND; copied: unaffected

Common rules:

  • Locks never refer to links. Lock() and Open() always resolve links, so Examine() never reports a link type. Only ExNext() and ExAll() see link entries (see Directory Scanning).
  • DeleteFile() never resolves the last component: it deletes the link, never the target.
  • ST_SOFTLINK (3) is positive, so test for it before treating fib_DirEntryType > 0 as "directory".

A hard link is a second directory entry for the same object. The file system resolves it transparently; dos.library is not involved. Two implementation strategies exist:

  • Reference counting (for example the Linux ext family): every directory entry points to the object, which is deleted when its count drops to zero. Links are indistinguishable from the original entry.
  • Link chains (FFS): each link points to the object, and the object keeps a list of its links. If the original is deleted, one of the links takes over and becomes the object itself. The FFS and the RAM-Handler use distinct entry types (ST_LINKFILE, ST_LINKDIR).

So you cannot rely on hard links being recognisable. On the FFS and the RAM-Handler, a hard link's protection bits and date mirror the target, while its comment is separate (see Locks, FileInfoBlock and Examine). The RAM-Handler ignores attempts to set protection bits on a hard link and always copies them from the target.

A soft link stores the path of its target, relative to the location of the link, as a NUL-terminated C string (not a BSTR, so it is not limited to 255 characters). The target need not exist when the link is created. Because the target may live on another file system, the file system containing the link cannot know whether the target is a file or a directory, so a single type, ST_SOFTLINK, covers both.

How resolution works

  1. A dos.library function sends its packet (for example ACTION_LOCATE_OBJECT for Lock()) to the handler responsible for the path.
  2. If the handler meets a soft link anywhere in the path, it fails the packet with ERROR_IS_SOFT_LINK.
  3. dos.library calls ReadLink(), which sends ACTION_READ_LINK to the same handler. The handler merges the stored link target into the original path and returns the new path:
  4. an absolute target (containing :) replaces everything up to and including the link component; if it has no device name, the handler prefixes the current volume name;
  5. a relative target replaces the link component;
  6. the components after the link are appended in both cases.
  7. dos.library retries the operation with the new path, which may lead to another handler (the target can be on another volume) and possibly another soft link.
  8. After 15 resolutions dos.library gives up with ERROR_TOO_MANY_LEVELS, because nothing prevents circular links.

Each ACTION_READ_LINK resolves exactly one link; chains are handled by repeating the loop.

Only these dos.library functions resolve soft links in their path argument:

Function Purpose
CreateDir() 1.3+ Create a directory
DeleteFile() 1.3+ Delete an object (never resolves the last component)
MakeLink() 2.0+ Create a link
Lock() 1.3+ Obtain a lock
Open() 1.3+ Open a file
SetComment() 1.3+ Set the comment
SetFileDate() 2.0+ Set the modification date
SetOwner() 3.0+ Set owner and group IDs
SetProtection() 1.3+ Set protection bits
DeleteVar(), GetVar(), SetVar() Environment variables
Execute(), System() Run Shell commands or scripts
LoadSeg(), NewLoadSeg() Load executables
MatchFirst() 2.0+ Pattern matching; only partially (see below)

Intermediate components are always resolved. Whether a soft link as the last component is resolved depends on the function and the file system:

  • Open() and Lock() always resolve it.
  • DeleteFile() never does.
  • SetProtection(), SetFileDate(): the FFS answers ERROR_IS_SOFT_LINK, so dos.library redirects the change to the target; the RAM-Handler instead changes the link itself.
  • SetComment(): the FFS does not follow links, so link and target have separate comments.

MatchFirst() works only partially: without a wildcard it simply locks the path, so a dangling soft link fails with ERROR_OBJECT_NOT_FOUND (see DOS Pattern Matching).

Rename() does not support soft links at all, up to and including V47: a soft link anywhere in the source or destination path makes it fail with ERROR_IS_SOFT_LINK. A workaround is shown below.

Deleting, renaming or moving the target leaves the soft link in place. AmigaDOS does not search for such links; any attempt to resolve one fails with ERROR_OBJECT_NOT_FOUND. Hard links do not have this problem, because a link takes over the object.

The V47 RAM-Handler supports a third kind of link: MakeLink() with LINK_HARD and a lock on another volume creates an external link. The link object is created immediately and initially looks like an empty file or directory. When the link, or something inside it, is accessed, the RAM-Handler copies the target into RAM:, creating intermediate directories as needed. From then on the copy is detached: it can be modified or deleted without affecting the original, and changes to the original no longer show. If the target is deleted before anything was copied, access fails with ERROR_OBJECT_NOT_FOUND.

External links cannot be recognised by fib_DirEntryType; they appear as regular files or directories. Copied objects get their own protection bits.

The system uses this for ENV:: RAM:ENV is an external link to ENVARC:, so preference files are pulled into RAM only when a program actually reads them. The process is transparent to dos.library. See OS 3.2 File System and Handler Changes for the commands involved.

BOOL MakeLink(STRPTR name, LONG dest, LONG soft);   /* V36 */
soft (dos/dos.h) Value dest
LINK_HARD 2.0+ 0 BPTR lock on the target. Same volume as name, or another volume for a RAM-Handler external link
LINK_SOFT 2.0+ 1 const UBYTE * path of the target, cast to LONG; interpreted relative to the link's location

name is the path of the link to create. The function returns non-zero on success; IoErr() is undefined on success and holds the error on failure. The caller keeps ownership of the lock passed for a hard link.

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

/* Hard link Work:Docs/readme -> Work:Projects/readme.txt */
BOOL MakeHardLink(void)
{
    BOOL ok = FALSE;
    BPTR target = Lock("Work:Projects/readme.txt", SHARED_LOCK);

    if (target) {
        ok = MakeLink("Work:Docs/readme", target, LINK_HARD);
        UnLock(target);                 /* MakeLink() does not absorb it */
    }
    return ok;
}

/* Soft link Work:Docs/latest -> Work:Projects/v2, stored relative to Work:Docs */
BOOL MakeSoftLink(void)
{
    return MakeLink("Work:Docs/latest", (LONG)"/Projects/v2", LINK_SOFT);
}

In AmigaDOS syntax the parent directory is a leading /, not ..; the soft-link target above therefore reads /Projects/v2.

Note: the NDK 3.2 dos/dos.h header comments LINK_SOFT with "softlinks are not fully supported yet", and the MakeLink() autodoc states that soft links did not work in the V36 ROM file system and were fixed in V37.

FFS does not check the target length. The FFS stores the soft-link target in the link block, which has room for (block size in longwords − 56) × 4 − 1 characters (287 characters on 512-byte blocks). No FFS version checks this limit; a longer target damages the file system structure. Keep soft-link targets short.

LONG ReadLink(struct MsgPort *port, BPTR lock, STRPTR path,
              STRPTR buffer, ULONG size);                     /* V36 */

You only need ReadLink() when a function that does not resolve soft links (see the table above), or a packet you sent yourself, fails with ERROR_IS_SOFT_LINK. path is the path that failed, relative to lock; port and lock are normally dvp_Port and dvp_Lock of the DevProc obtained from GetDeviceProc(path, …). The new path is written to buffer (size bytes) and is relative to lock or absolute.

The return value is a mess:

Result Meaning according to the manual
> 0 Success: length of the new path, without the NUL
−2 Buffer too small; IoErr() = ERROR_LINE_TOO_LONG
−1 Other error; IoErr() holds the code
0 Error from file systems that get this wrong: FFS 46 and below return 0 for every error, including buffer overflow; FFS 47 returns −1 for buffer overflow

Treat any value ≤ 0 as failure, then look at IoErr(); if it is ERROR_LINE_TOO_LONG, enlarge the buffer and call again. Stop after 15 levels with ERROR_TOO_MANY_LEVELS.

When the path contains no soft link, the handler reports an error too, but not consistently: the FFS returns ERROR_OBJECT_NOT_FOUND (the useful answer, telling a resolver to try the next directory of a multi-assign), while the RAM-Handler returns ERROR_OBJECT_WRONG_TYPE.

Note: the NDK 3.2 autodoc declares ReadLink() as returning BOOL (a success flag) and states that soft links did not work in the V36 ROM file system, fixed in V37.

Inside dos.library, the resolver used by Lock(), Open() and the other functions has a fixed internal buffer. If a link target does not fit, it fails with ERROR_INVALID_COMPONENT_NAME, not ERROR_LINE_TOO_LONG.

This is the manual's partial workaround for Rename()'s lack of soft-link support. It resolves soft links in the source path, iterates over multi-assign directories (which dos.library itself gets wrong), and doubles the buffer on ERROR_LINE_TOO_LONG. It does not handle a soft link in the destination path.

#include <exec/memory.h>
#include <dos/dos.h>
#include <dos/dosextens.h>
#include <proto/exec.h>
#include <proto/dos.h>

#define BUFSIZE   256
#define MAX_LINKS 15

LONG RenameWithLinks(UBYTE *srcpath, UBYTE *dstpath)
{
    UBYTE *buf    = NULL;
    UBYTE *newbuf = NULL;
    ULONG bufsize = BUFSIZE;
    struct DevProc *dp = NULL;
    LONG levels = MAX_LINKS;
    LONG error  = 0;
    LONG success;

    while (!(success = Rename(srcpath, dstpath)) &&
           IoErr() == ERROR_IS_SOFT_LINK) {
        if (--levels < 0) {                     /* too many levels */
            SetIoErr(ERROR_TOO_MANY_LEVELS);
            break;
        }
        if (!(newbuf = AllocVec(bufsize, MEMF_PUBLIC)))
            break;

        do {                                    /* walk multi-assigns */
            if ((dp = GetDeviceProc(srcpath, dp))) {
                if ((success = ReadLink(dp->dvp_Port, dp->dvp_Lock,
                                        srcpath, newbuf, bufsize)) > 0) {
                    srcpath = newbuf;           /* rotate buffers */
                    newbuf  = buf;
                    buf     = srcpath;
                    error   = 0;
                    break;
                } else {
                    error = IoErr();
                    if (error == ERROR_LINE_TOO_LONG) {
                        bufsize <<= 1;          /* retry with a larger buffer */
                        success = 1;
                        break;
                    }
                    /* Not a link here, or not in this directory of a
                     * multi-assign: try the next one. (RAM: reports
                     * ERROR_OBJECT_WRONG_TYPE instead.) */
                    if (error != ERROR_OBJECT_WRONG_TYPE &&
                        error != ERROR_OBJECT_NOT_FOUND)
                        break;
                    error = ERROR_IS_SOFT_LINK;
                }
            } else {
                error = IoErr();
                break;
            }
        } while (dp && (dp->dvp_Flags & DVPF_ASSIGN));

        FreeDeviceProc(dp);                     /* NULL is fine */
        dp = NULL;
        if (newbuf)
            FreeVec(newbuf);
        newbuf = NULL;
        SetIoErr(error);
        if (success <= 0)
            break;
    }

    if (buf)
        FreeVec(buf);
    return success;
}

Known defects

Where Defect
FFS before V45 Soft links do not work correctly; do not rely on them before V45
RAM-Handler, all versions A soft link with two components whose first (directory) component is itself a soft link does not resolve correctly
dos.library, all versions including V47 Soft links reached through a multi-assign fail to resolve
Rename(), up to V47 Fails with ERROR_IS_SOFT_LINK if either path contains a soft link
ReadLink() on FFS ≤ 46 Returns 0 instead of −1/−2 on errors
ReadLink() on FFS 47 Returns −1 instead of −2 on buffer overflow
FFS, all versions No length check on soft-link targets; overlong targets corrupt the volume
FFS The date of a link cannot be changed: SetFileDate() on a hard link changes the target, on a soft link the FFS answers ERROR_IS_SOFT_LINK and the target is changed
MatchFirst() 2.0+ Without wildcards, a dangling soft link gives ERROR_OBJECT_NOT_FOUND

Packet level

Function Packet Arguments
MakeLink() 2.0+ ACTION_MAKE_LINK (1021) Arg1 directory lock, Arg2 BSTR link path, Arg3 target lock (LINK_HARD) or APTR to a C string (LINK_SOFT), Arg4 link type. A LINK_HARD lock on another file system requests an external link
ReadLink() 2.0+ ACTION_READ_LINK (1024) Arg1 lock, Arg2 APTR C-string path, Arg3 APTR buffer, Arg4 buffer size. Res1 = length of the new path, or −1/−2

Handlers signal a soft link in any path with ERROR_IS_SOFT_LINK. DeleteFile() (ACTION_DELETE_OBJECT) removes the link itself, and Rename() (ACTION_RENAME_OBJECT) into a soft link must fail with ERROR_IS_SOFT_LINK. Details are in DOS Packet Reference.

See Also


Sources: Thomas Richter, Amiga ROM Kernel Reference Manual: AmigaDOS (2024, v1.27), §7.1 (pp. 70–72), §7.2.2–7.2.5 (pp. 79–81), §7.4 (pp. 84–90), §10.2.9 (pp. 155–157), §13.6 link block (pp. 244–245), §14.4 (pp. 276–281), §14.5 (pp. 283–286); NDK 3.2 R4 dos/dos.h, dos/dosextens.h, dos.doc (MakeLink, ReadLink) Updated: 2026-11-13