Locks, FileInfoBlock and Examine¶
A lock is an access right on a file system object (a file or a directory). While a lock exists, the object cannot be deleted, and depending on the lock type it cannot be opened or locked by anyone else. Locks are also the way AmigaDOS names objects without paths: a lock on a directory plus a short relative name identifies an object at any nesting depth, which is how programs work around the 255-character path limit that most dos.library functions inherit from BSTRs.
This page covers the lock API of Chapter 6 of the RKM AmigaDOS manual (obtaining, duplicating, comparing and converting locks, opening files from locks, the FileLock structure) and the per-object inspection functions of §7.1 (Examine(), ExamineFH() and the FileInfoBlock). Directory iteration (ExNext(), ExAll()) is covered in Directory Scanning; Info() and volume-level operations in Volume Administration.
Shared and exclusive locks¶
Lock() takes one of four access modes from dos/dos.h; two of them are synonyms:
| Mode | Value | Meaning |
|---|---|---|
SHARED_LOCK 1.3+ |
−2 | Any number of shared locks may coexist on the object |
ACCESS_READ 1.3+ |
−2 | Synonym of SHARED_LOCK |
EXCLUSIVE_LOCK 1.3+ |
−1 | Only this lock may exist on the object |
ACCESS_WRITE 1.3+ |
−1 | Synonym of EXCLUSIVE_LOCK |
Rules:
- An exclusive lock excludes every other lock. Locking an exclusively locked object fails with
ERROR_OBJECT_IN_USE; so does an attempt to lock exclusively an object that already has a shared lock. - Prefer shared locks. Release every lock as early as possible, so that other processes can open, overwrite or delete the object.
- A shared lock on a file does not protect its contents: it prevents the file from being replaced (
MODE_NEWFILE), but the file can still be opened in any other mode and modified. An exclusively locked file cannot be opened by name at all, only throughOpenFromLock()(packet level: onlyACTION_FH_FROM_LOCK). An exclusively locked directory cannot be deleted, but objects inside it can still be created, deleted or changed (§14.2.1). - File systems should treat an invalid mode passed to
Lock()asSHARED_LOCK, because some programs pass garbage. Since the root directory is also reachable as the ZERO lock, an exclusive lock on a volume root is "probably not very exclusive". - Links are never locked. Hard and soft links are resolved as the lock is acquired, so a lock always refers to the link target.
- As long as at least one lock on a volume exists, its file system keeps the volume in the device list; Workbench therefore keeps showing the volume icon even after the medium is ejected.
The ZERO lock¶
The lock value ZERO (0) is special. In principle it stands for SYS:; in reality it is the root directory of the file system in pr_FileSystemTask of the calling process, which is usually, but not necessarily, the same volume as the SYS: assign. Many functions treat it inconsistently:
| Function | Behaviour with ZERO |
|---|---|
DupLock(ZERO) |
Returns ZERO without touching IoErr(), indistinguishable from failure unless you clear IoErr() first |
ParentDir(ZERO) |
Returns ZERO; IoErr() is 0 (no parent, not an error) |
UnLock(ZERO) |
No-op |
Examine(ZERO, fib) |
Examines the root of the default file system of the process |
SameLock() 2.0+ |
Does not recognise ZERO as equal to a lock on SYS:; do not pass ZERO |
NameFromLock(ZERO, …) |
Produces "SYS:", which is wrong if SYS: or pr_FileSystemTask was changed |
ExNext() / ExAll() |
ZERO is not usable for scanning; see Directory Scanning |
At the packet level, ZERO in a lock argument means "the root of the volume currently inserted in this handler", not the boot volume. In particular, the dvp_Lock returned by GetDeviceProc() may be ZERO and then refers to the root of the file system behind dvp_Port (see The AmigaDOS Device List).
Obtaining and releasing locks¶
BPTR Lock(STRPTR name, LONG accessMode);
BPTR DupLock(BPTR lock);
BPTR ParentDir(BPTR lock);
BPTR CreateDir(STRPTR name);
void UnLock(BPTR lock);
BPTR DupLockFromFH(BPTR fh); /* V36 */
BPTR ParentOfFH(BPTR fh); /* V36 */
BPTR OpenFromLock(BPTR lock); /* V36 */
| Function | Returns | Notes |
|---|---|---|
Lock() 1.3+ |
Lock of the requested type, or ZERO |
Path is absolute or relative to the current directory |
DupLock() 1.3+ |
New shared lock | Only shared locks can be duplicated; an exclusive lock gives ERROR_OBJECT_IN_USE. Never copy a FileLock by copying its memory, which can crash the system |
ParentDir() 1.3+ |
Shared lock on the parent directory (directory of a file) | ZERO for the root or the ZERO lock (then IoErr() = 0) or on error (IoErr() ≠ 0, e.g. out of memory or parent locked exclusively). Does not release its argument |
CreateDir() 1.3+ |
Exclusive lock on the new directory | Creates only the last component, never intermediate directories. Existing object: ERROR_OBJECT_EXISTS (some file systems wrongly return ERROR_OBJECT_IN_USE if the existing directory is locked). Flat file systems do not support directories |
UnLock() 1.3+ |
nothing | ZERO is fine. Does not set IoErr(). Never unlock the lock installed as CurrentDir() |
DupLockFromFH() 2.0+ |
Shared lock on the open file | Fails for files opened with MODE_NEWFILE (implicit exclusive lock). The handle stays valid |
ParentOfFH() 2.0+ |
Shared lock on the directory of the open file | Works even for MODE_NEWFILE files; fails if the parent is exclusively locked |
OpenFromLock() 2.0+ |
File handle | See below |
On success, IoErr() is set to an undefined value by all functions above except UnLock() (which never touches it) and DupLock(ZERO) (which leaves it alone). Only test IoErr() after a failure.
The claim in older documentation that
CreateDir()deletes a conflicting file or directory is wrong; it fails instead.
Duplicating a lock that may be ZERO¶
#include <dos/dos.h>
#include <proto/dos.h>
/* Returns the copy. *error is 0 on success, including the case where
* 'lock' was ZERO and the copy is therefore ZERO as well. */
BPTR DupLockZeroSafe(BPTR lock, LONG *error)
{
BPTR copy;
SetIoErr(0); /* DupLock(ZERO) leaves IoErr() untouched */
copy = DupLock(lock);
*error = copy ? 0 : IoErr();
return copy;
}
Telling the root from a ParentDir() failure¶
BPTR parent = ParentDir(lock);
if (parent == 0) {
LONG err = IoErr();
if (err == 0) {
/* 'lock' is the root directory (or ZERO): no parent */
} else {
/* real failure: err holds the error code */
}
}
Note that ParentDir() and locking the path "/" relative to a lock differ only at the root: the latter fails with ERROR_OBJECT_NOT_FOUND (§14.2.3).
Changing the mode of a lock or file handle¶
BOOL ChangeMode(ULONG type, BPTR object, ULONG newmode); /* V36 */
type is CHANGE_LOCK (0) for a lock or CHANGE_FH (1) for a file handle; newmode is SHARED_LOCK or EXCLUSIVE_LOCK. An exclusive lock can always be downgraded to shared; a shared lock (or handle) can be upgraded to exclusive only if it is the only lock on the object.
Caveats:
- The function is unreliable for file handles: the RAM-Handler does not interpret
newmodecorrectly forCHANGE_FH. - At the packet level (
ACTION_CHANGE_MODE, 1028),dp_Arg2is the BPTR to theFileHandleitself, not itsfh_Arg1as with every other file packet. File systems should also acceptACTION_FINDINPUT/ACTION_FINDUPDATEas "shared" andACTION_FINDOUTPUTas "exclusive", because callers pass those. - Passing the wrong
typefor the object may crash the system.
Note: the NDK 3.2 autodoc states that
ChangeMode()did not work in V36 (2.02 and earlier, possibly crashing), and thatCHANGE_FHon a handle opened onNIL:crashes up to dos.library 47.23 (fixed in 47.24).
Comparing locks¶
SameLock()¶
LONG SameLock(BPTR lock1, BPTR lock2); /* V36 */
| Result | Value | Meaning |
|---|---|---|
LOCK_SAME 2.0+ |
0 | Same object |
LOCK_SAME_VOLUME 2.0+ |
1 | Different objects on the same volume (LOCK_SAME_HANDLER is an obsolete alias) |
LOCK_DIFFERENT 2.0+ |
−1 | Objects on different volumes |
IoErr()is not set consistently.ZEROis not recognised as identical to a lock onSYS:; do not passZEROfor either argument.dos.libraryfirst checks whether both locks belong to the same file system; only then does it sendACTION_SAME_LOCK(40). If the file system does not implement that packet,dos.libraryfalls back to comparingfl_Key, which is only meaningful for file systems wherefl_Keyuniquely identifies the object.
On V34 and earlier, where SameLock() does not exist, the manual offers this limited replacement. It is correct only for the ROM file system, the FFS and the RAM-Handler of V34 and below, because it relies on fl_Key:
#include <dos/dos.h>
#include <dos/dosextens.h>
LONG SameLockV34(BPTR lock1, BPTR lock2)
{
struct FileLock *fl1 = BADDR(lock1);
struct FileLock *fl2 = BADDR(lock2);
if (fl1 == fl2) /* includes two ZERO locks */
return LOCK_SAME;
if (fl1 && fl2) { /* does not treat ZERO as SYS: */
if (fl1->fl_Volume != fl2->fl_Volume) return LOCK_DIFFERENT;
if (fl1->fl_Task != fl2->fl_Task) return LOCK_DIFFERENT;
if (fl1->fl_Key != fl2->fl_Key) return LOCK_SAME_VOLUME;
return LOCK_SAME;
}
return LOCK_DIFFERENT; /* not fully correct */
}
Note: the NDK 3.2 autodoc states that in V36
SameLock()returnedLOCK_SAME_VOLUMEfor different volumes on the same handler.
SameDevice()¶
BOOL SameDevice(BPTR lock1, BPTR lock2);
Returns non-zero if the file systems owning the two locks run on the same unit of the same exec device, even if they are different partitions. Only device name and unit are compared, so it cannot tell whether two locks are on the same physical volume. It returns 0 if either file system or its device cannot be identified, and never sets IoErr(). The result can become stale: a floppy moved from DF0: to DF1: changes its device after the call. The intended use is copy tools deciding whether source and destination can be accessed in parallel.
Note: the NDK 3.2 autodoc marks
SameDevice()as V37 and states that the V36 entry always returned 0.
Locks and files¶
A file handle is normally backed by an internal lock on the opened file. The lock type follows the open mode:
| Open mode | Implied lock |
|---|---|
MODE_OLDFILE 1.3+ |
SHARED_LOCK |
MODE_READWRITE 2.0+ |
SHARED_LOCK |
MODE_NEWFILE 1.3+ |
EXCLUSIVE_LOCK |
MODE_READWRITE mapping to a shared lock goes back to a V36 RAM-Handler defect that the FFS then copied. In V34 and earlier it implied an exclusive lock. On V36+ the way to get exclusive access to a file without truncating it is to lock it exclusively and then open it from the lock.
Opening a file from a lock¶
OpenFromLock() opens the locked object as a file. On success the lock is absorbed into the handle and released by Close(); do not UnLock() it. On failure it returns ZERO and the lock remains yours. It fails if the lock refers to a directory.
#include <dos/dos.h>
#include <proto/dos.h>
/* Open an existing file for exclusive access without truncating it.
* Requires V37: OpenFromLock() does not work in V36. */
BPTR OpenExclusive(STRPTR name)
{
BPTR lock, fh = 0;
if ((lock = Lock(name, EXCLUSIVE_LOCK)) != 0) {
if ((fh = OpenFromLock(lock)) == 0)
UnLock(lock); /* not absorbed on failure */
}
return fh;
}
OpenFromLock() does not work in V36, because of an incomplete implementation in the ROM file systems and defects in dos.library; it was fixed in V37. For V34, which lacks the function, the manual gives a workaround that only handles shared locks: make the lock the current directory and open the empty name, which every AmigaDOS file system interprets as "the object locked by the current directory", even if that object is a file.
BPTR OpenFromLockV34(BPTR lock)
{
BPTR dir = CurrentDir(lock); /* yes, a file as current directory */
BPTR fh = Open("", MODE_OLDFILE);
if (fh)
UnLock(lock);
CurrentDir(dir);
return fh;
}
The FileLock structure¶
Locks are BPTRs to FileLock structures (dos/dosextens.h):
struct FileLock {
BPTR fl_Link; /* file system private: chain on dol_LockList */
LONG fl_Key; /* file system private: object identifier */
LONG fl_Access; /* SHARED_LOCK or EXCLUSIVE_LOCK */
struct MsgPort *fl_Task; /* port of the owning file system */
BPTR fl_Volume; /* BPTR to the DLT_VOLUME DosList entry */
};
FileLocks are allocated and freed by the file system, never by dos.library, and may be larger than the public structure.
| Field | Meaning |
|---|---|
fl_Link |
Only used while the volume is ejected: the file system chains all outstanding locks of the volume through fl_Link, starting at dol_LockList of the volume's DosList. When the volume reappears, possibly in another drive, the file system instance serving that drive picks the locks up and rewrites their fl_Task. This lets a lock taken in DF0: stay valid when the disk is reinserted into DF1: |
fl_Key |
Opaque. The FFS stores the block number of the object's header block; other file systems may store a pointer. Do not interpret it |
fl_Access |
The mode passed to Lock() |
fl_Task |
Port that all requests on this lock go to. While the volume is ejected and unclaimed, it still points to the last file system that owned the lock |
fl_Volume |
BPTR to the volume's DosList entry |
Applications should treat fl_Link and fl_Key as private. fl_Task is the reliable way to find the handler behind a lock, for example for direct packet I/O. The migration mechanism covers locks only. File handles are not carried over by the FFS: reading from a file whose disk moved to another drive produces a requester asking for the original drive.
The FileInfoBlock¶
Examine(), ExamineFH() and ExNext() fill a FileInfoBlock (dos/dos.h):
struct FileInfoBlock {
LONG fib_DiskKey;
LONG fib_DirEntryType;
char fib_FileName[108];
LONG fib_Protection;
LONG fib_EntryType;
LONG fib_Size;
LONG fib_NumBlocks;
struct DateStamp fib_Date;
char fib_Comment[80];
UWORD fib_OwnerUID;
UWORD fib_OwnerGID;
char fib_Reserved[32];
};
It must be longword-aligned. Do not declare it as a plain local variable, because compilers do not guarantee 4-byte alignment on the stack. Allocate it with AllocDosObject(DOS_FIB, NULL) (and free it with FreeDosObject(DOS_FIB, fib)), with AllocVec()/AllocMem(), or use the alignment macro of §2.4:
#define D_S(type,name) char a_##name[sizeof(type)+3]; \
type *name = (type *)((ULONG)(a_##name+3) & ~3UL)
D_S(struct FileInfoBlock, fib); /* fib is an aligned pointer */
Field by field:
| Field | Meaning and caveats |
|---|---|
fib_DiskKey |
File system private. The FFS stores the disk key, the RAM-Handler a pointer, some file systems nothing. Not a unique identifier, and not necessarily equal to fl_Key (for example when the entry is a link) |
fib_DirEntryType |
Object type, see below |
fib_FileName |
NUL-terminated name. FFS limit: 30, 54 or 106 characters depending on the flavour |
fib_Protection |
Protection bits, see below |
fib_EntryType |
Undocumented; may equal fib_DirEntryType. Do not use |
fib_Size |
File size in bytes. Signed, so behaviour above 2 GB is unclear. Undefined for directories, hard links to directories and soft links |
fib_NumBlocks |
Blocks occupied. Unreliable: the old BCPL ROM file system counted extension blocks too, the FFS (since Kickstart 1.3) only estimates from the size plus one header block, and OFS flavours underestimate. Not comparable across file systems, or even across identical files. Leave it alone |
fib_Date |
Last modification (DateStamp). For directories, which operations update it is file-system dependent; the FFS does not update it for metadata changes (comment, protection) |
fib_Comment |
NUL-terminated comment, at most 79 characters. Not all file systems support comments |
fib_OwnerUID, fib_OwnerGID |
Opaque owner and group IDs used by network file systems such as Envoy. The FFS stores them but enforces nothing; AmigaDOS has no concept of users. Introduced in V43 |
fib_Reserved |
Do not access. Kickstart 1.3 BCPL commands such as Dir and List allocate a shortened FIB without it, so file systems must never write to it |
Handlers fill fib_FileName and fib_Comment as BSTRs; dos.library converts them to C strings before returning.
fib_DirEntryType¶
Value (dos/dosextens.h) |
Meaning |
|---|---|
ST_ROOT (1) |
Root directory |
ST_USERDIR (2) |
Directory |
ST_SOFTLINK (3) |
Soft link (target may be a file or a directory) |
ST_LINKDIR (4) |
Hard link to a directory |
ST_FILE (−3) |
File |
ST_LINKFILE (−4) |
Hard link to a file |
ST_PIPEFILE (−5) |
Pipe that supports ExamineFH() |
| any other > 0 | Directory |
| any other < 0 | File |
Gotchas:
ST_SOFTLINKis positive. A naïve "> 0 means directory" test classifies every soft link as a directory, so test forST_SOFTLINKfirst.Lock()resolves links, soExamine()never reports a link type. OnlyExNext()andExAll()can, because they list directory entries without resolving them.- Only the FFS and the RAM-Handler give hard links a distinct type. On file systems that implement hard links as duplicate directory entries (all Linux ext variants, for example), a hard link looks like a regular entry. RAM-Handler external links (V47) also look like regular files or directories. Do not depend on being able to recognise links.
Protection bits¶
Bit (FIBB_) |
No. | Meaning |
|---|---|---|
FIBB_DELETE 1.3+ |
0 | 0 = deletable. If set, DeleteFile() and replacing via MODE_NEWFILE fail with ERROR_DELETE_PROTECTED |
FIBB_EXECUTE 1.3+ |
1 | 0 = executable. Only interpreted by the Shell (V34 and up), which refuses to run the file if set; ignored for directories and by Workbench |
FIBB_WRITE 1.3+ |
2 | 0 = writable. If set, modifying or overwriting fails with ERROR_WRITE_PROTECTED; deleting is still allowed. Ignored by the V34 and older ROM file system (OFS); meaningless for directories |
FIBB_READ 1.3+ |
3 | 0 = readable. If set, opening for reading fails with ERROR_READ_PROTECTED. Ignored by the V34 and older OFS; meaningless for directories |
FIBB_ARCHIVE 1.3+ |
4 | Cleared by the file system on every write, modification or creation; set by backup software |
FIBB_PURE 1.3+ |
5 | 1 = reentrant executable that Resident may keep loaded (Shell V34+). Nothing verifies it |
FIBB_SCRIPT 1.3+ |
6 | 1 = script; the Shell hands it to an interpreter (Execute, ARexx; V45+ also the interpreter named in the first line) |
FIBB_HOLD 3.2+ |
7 | 1 = make resident on first use (requires PURE too). Introduced in V36, dropped in V40 for ROM space, reintroduced in V45 |
The four low bits are inverted: the protection is active when the bit is 1, but Shell commands and Workbench display the letter when the bit is 0. dos/dos.h also defines group and "other" bits (FIBB_GRP_* bits 8–11, FIBB_OTR_* bits 12–15) for network file systems; AmigaDOS itself assigns them no meaning.
FIBB_ARCHIVE is not recursive: modifying a file clears the bit on its directory, but changes deeper in the tree do not propagate upwards. Backup software must therefore descend into directories whose archive bit is still set.
Links and their metadata¶
Whether a link has its own metadata is up to the file system. The ROM file systems behave as follows:
| Metadata | Hard link (FFS and RAM) | Soft link, FFS | Soft link, RAM-Handler |
|---|---|---|---|
| Protection bits | Kept identical to the target | Cannot be set on the link; setting them changes the target | Separate bits |
| Modification date | Kept identical to the target | Separate date, but setting it updates the target | Separate date |
| Comment | Own comment | Own comment | Own comment |
Separate link metadata has limited value, since Lock() always resolves the link and only ExNext()/ExAll() can see the link itself. Objects copied in through a RAM-Handler external link get their own protection bits, because the handler creates a copy on access (see Links).
Examine() and ExamineFH()¶
BOOL Examine(BPTR lock, struct FileInfoBlock *fib);
BOOL ExamineFH(BPTR fh, struct FileInfoBlock *fib); /* V36 */
Examine() describes the locked object, never a link (see above). With ZERO it examines the root of the default file system of the process. ExamineFH() does the same for an open file, and also works with handlers that do not support locks. Both return non-zero on success; IoErr() is undefined on success and holds the error code on failure. Because files can change at any time, the result is a snapshot; the autodoc explicitly does not guarantee that fib_Size reflects changes made since the file was opened.
ExamineFH() crashes on a NIL: handle in every version. A NIL: handle has fh_Type == NULL, so check that first:
#include <dos/dos.h>
#include <dos/dosextens.h>
#include <proto/dos.h>
/* Print name and size of an open file; returns FALSE for NIL: or on error. */
BOOL ShowOpenFile(BPTR fh)
{
struct FileHandle *h = BADDR(fh);
struct FileInfoBlock *fib;
BOOL ok = FALSE;
if (h == NULL || h->fh_Type == NULL) /* NIL: has no handler */
return FALSE;
if ((fib = AllocDosObject(DOS_FIB, NULL)) != NULL) {
if ((ok = ExamineFH(fh, fib)) != FALSE) {
if (fib->fib_DirEntryType < 0)
Printf("%s: %ld bytes\n", fib->fib_FileName, fib->fib_Size);
else
Printf("%s: not a file\n", fib->fib_FileName);
}
FreeDosObject(DOS_FIB, fib);
}
return ok;
}
Note: the NDK 3.2 autodoc for
Examine()states that a local copy of aFileInfoBlockmay be made as long as the copy is never passed toExNext()(or back to the system).
Packets behind the functions¶
For handler writers and for direct packet I/O, each function maps to one packet (details in DOS Packet Reference):
| Function | Packet | Notes |
|---|---|---|
Lock() 1.3+ |
ACTION_LOCATE_OBJECT (8) |
Arg1 lock, Arg2 BSTR, Arg3 mode. An empty name locks the Arg1 object itself |
DupLock() 1.3+ |
ACTION_COPY_DIR (19) |
Despite the name, copies locks on files too. With Arg1 = ZERO the handler must return a real shared lock on the root, not ZERO |
ParentDir() 1.3+ |
ACTION_PARENT (29) |
Root or ZERO: Res1 = ZERO, Res2 = 0 (not an error) |
DupLockFromFH() 2.0+ |
ACTION_COPY_DIR_FH (1030) |
Arg1 = fh_Arg1; fails on exclusively opened files |
ParentOfFH() 2.0+ |
ACTION_PARENT_FH (1031) |
Arg1 = fh_Arg1; works for exclusive files |
CreateDir() 1.3+ |
ACTION_CREATE_DIR (22) |
Not recursive |
SameLock() 2.0+ |
ACTION_SAME_LOCK (40) |
Res1 DOSTRUE = same object; Res1 DOSFALSE with Res2 = 0 = different objects |
ChangeMode() 2.0+ |
ACTION_CHANGE_MODE (1028) |
Arg2 is the FileHandle BPTR itself for CHANGE_FH |
UnLock() 1.3+ |
ACTION_FREE_LOCK (15) |
Result ignored by UnLock(); handlers should clear fl_Task first, since dos.library ignores unlock requests for locks with a NULL fl_Task |
OpenFromLock() 2.0+ |
ACTION_FH_FROM_LOCK (1026) |
Lock absorbed on success |
Examine() 1.3+ |
ACTION_EXAMINE_OBJECT (23) |
Arg1 ZERO = root of the inserted volume; fib_FileName = volume name |
ExamineFH() 2.0+ |
ACTION_EXAMINE_FH (1034) |
Arg1 = fh_Arg1 |
See Also¶
- Directory Scanning —
ExNext(),ExAll(),ExAllEnd() - Links — hard, soft and external links
- Volume Administration —
Info(),InfoData,Inhibit(),Format() - The AmigaDOS Device List —
dol_LockList, volume entries,GetDeviceProc() - dos.library Reference
- AmigaDOS Record Locking — locks on byte ranges of open files
- BPTR, BSTR, and BCPL Legacy
- DOS Packet Reference
Sources: Thomas Richter, Amiga ROM Kernel Reference Manual: AmigaDOS (2024, v1.27), §2.4 (p. 7), ch. 6 (pp. 57–67), §7.1–7.1.2 (pp. 69–73), §7.2.4–7.2.5 (pp. 80–81), §14.2 (pp. 265–269), §14.3.1 and §14.3.5 (pp. 270–271, 275); NDK 3.2 R4 dos/dos.h, dos/dosextens.h, dos.doc (ChangeMode, SameLock, SameDevice, Examine, ExamineFH, OpenFromLock)
Updated: 2026-11-13