amiga-bootcamp/07_dos/locks_examine.md
Ilia Sharin da9e7d3b63 Phase 1: enrich 07_dos and 10_devices (highest FPGA priority)
07_dos:
- file_io.md: 108→240+ lines — buffered I/O (FRead/FWrite/SetVBuf),
  access mode comparison, FileHandle struct with offsets, standard
  handles, Printf %ld warning, FileInfoBlock, practical patterns
  (copy file, get size, load to RAM), error code table
- filesystem.md: 91→270+ lines — full disk geometry (ADF/HDF),
  all 8 DOS\x filesystem IDs, root block byte-level layout, file
  header layout with reverse-order pointer quirk, OFS vs FFS data
  blocks with efficiency numbers, bitmap blocks, extension blocks,
  checksum algorithm, Python ADF reader
- locks_examine.md: 113→270+ lines — lock semantics diagram, FileLock
  struct with handler discovery, ExAll bulk scan, practical patterns
  (atomic write, path resolution, volume info), 4 antipatterns
  (leaked locks, exclusive too long, unchecked IoErr, DupLock),
  pattern matching

10_devices:
- audio.md: 73→240+ lines — hardware architecture diagram, channel
  registers with offsets, period/frequency table, priority allocation,
  double-buffering, audio interrupts, AM/PM modulation, direct HW
- timer.md: 80→230+ lines — CIA timer hardware, all 5 units with
  decision flowchart, non-blocking delays, signal-based waiting,
  time arithmetic, ReadEClock, periodic game loop pattern, pitfalls
- trackdisk.md: 82→210+ lines — MFM encoding, track format, disk
  geometry, read/write/motor, change notification, track caching,
  direct hardware access, FPGA timing implications
- keyboard.md: 58→220+ lines — CIA-A serial handshake protocol with
  sequence diagram, bit rotation quirk, complete key code map,
  key matrix bitmap, reset sequence, FPGA notes
2026-04-23 20:23:50 -04:00

382 lines
11 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

[← Home](../README.md) · [AmigaDOS](README.md)
# Locks, Examine, and Directory Scanning
## Overview
A **Lock** is AmigaDOS's equivalent of a Unix file descriptor for directory operations. It represents a reference to a filesystem object (file or directory) and is the primary mechanism for navigating the filesystem, examining metadata, and preventing concurrent modifications. Understanding lock semantics — especially the shared/exclusive model — is critical for writing robust AmigaOS applications.
---
## Lock Types
| Mode | Constant | Dec | Meaning |
|---|---|---|---|
| Shared | `SHARED_LOCK` / `ACCESS_READ` | 2 | Multiple readers allowed simultaneously |
| Exclusive | `EXCLUSIVE_LOCK` / `ACCESS_WRITE` | 1 | Only one holder; blocks all other locks |
```c
/* Obtain a shared lock: */
BPTR lock = Lock("SYS:Libs", SHARED_LOCK);
/* Obtain an exclusive lock: */
BPTR lock = Lock("RAM:temp.dat", EXCLUSIVE_LOCK);
/* Always unlock when done: */
UnLock(lock);
```
### Lock Semantics
```mermaid
graph TD
subgraph "Shared Lock Active"
S1["Process A: SHARED"] --> OK1["Process B: SHARED → OK"]
S1 --> FAIL1["Process C: EXCLUSIVE → FAILS"]
end
subgraph "Exclusive Lock Active"
E1["Process A: EXCLUSIVE"] --> FAIL2["Process B: SHARED → FAILS"]
E1 --> FAIL3["Process C: EXCLUSIVE → FAILS"]
end
```
| Scenario | Result | Error Code |
|---|---|---|
| Shared + Shared | Both succeed | — |
| Shared + Exclusive | Exclusive fails | `ERROR_OBJECT_IN_USE` (202) |
| Exclusive + Shared | Shared fails | `ERROR_OBJECT_IN_USE` (202) |
| Exclusive + Exclusive | Second fails | `ERROR_OBJECT_IN_USE` (202) |
> [!IMPORTANT]
> `Lock()` is **non-blocking** — it returns immediately with 0 and sets `IoErr()` if the lock cannot be obtained. There is no "wait for lock" mechanism in AmigaDOS. You must poll or retry.
---
## Lock Internals — struct FileLock
```c
/* dos/dosextens.h */
struct FileLock {
BPTR fl_Link; /* BPTR to next lock (handler's list) */
LONG fl_Key; /* block number on disk (handler-specific) */
LONG fl_Access; /* SHARED_LOCK or EXCLUSIVE_LOCK */
struct MsgPort *fl_Task; /* handler's MsgPort — THIS IS HOW YOU
FIND THE FILE HANDLER */
BPTR fl_Volume; /* BPTR to DosList volume node */
};
```
### Finding a File's Handler Process
This is a common need — e.g., to send custom packets:
```c
/* Get the handler MsgPort from any lock: */
struct FileLock *fl = (struct FileLock *)BADDR(lock);
struct MsgPort *handler = fl->fl_Task;
/* Now you can PutMsg(handler, &packet) */
/* From a filename (without an existing lock): */
struct DevProc *dp = GetDeviceProc("DF0:myfile", NULL);
if (dp) {
struct MsgPort *handler = dp->dvp_Port;
/* ... send packets ... */
FreeDeviceProc(dp);
}
/* From a file handle: */
struct FileHandle *fhp = (struct FileHandle *)BADDR(filehandle);
struct MsgPort *handler = fhp->fh_Type;
```
---
## Examining Files and Directories
### Examine a Single Object
```c
struct FileInfoBlock *fib = AllocDosObject(DOS_FIB, NULL);
BPTR lock = Lock("SYS:C/Dir", SHARED_LOCK);
if (lock && Examine(lock, fib))
{
Printf("Name: %s\n", fib->fib_FileName);
Printf("Size: %ld bytes\n", fib->fib_Size);
Printf("Type: %s\n",
fib->fib_DirEntryType > 0 ? "Directory" : "File");
/* Protection bits: */
Printf("Protect: %08lx\n", fib->fib_Protection);
/* Bits are ACTIVE-LOW: bit set = permission DENIED */
/* FIBF_READ=8, FIBF_WRITE=4, FIBF_EXECUTE=2, FIBF_DELETE=1 */
/* Bits 4-7: hold/script/pure/archive (active-HIGH) */
}
UnLock(lock);
FreeDosObject(DOS_FIB, fib);
```
> [!WARNING]
> **Protection bits are inverted** for RWED: bit set means the permission is **denied**, not granted. This is opposite to Unix. `fib_Protection = 0` means "all permissions granted".
### Scan a Directory (ExNext Loop)
```c
/* Classic directory enumeration pattern: */
BPTR dirLock = Lock("SYS:Libs", SHARED_LOCK);
struct FileInfoBlock *fib = AllocDosObject(DOS_FIB, NULL);
if (dirLock && Examine(dirLock, fib)) /* examine the dir itself first */
{
while (ExNext(dirLock, fib))
{
Printf(" %s (%ld bytes)\n",
fib->fib_FileName, fib->fib_Size);
}
/* ExNext returns FALSE when done — check error: */
if (IoErr() != ERROR_NO_MORE_ENTRIES)
PrintFault(IoErr(), "ExNext error");
}
UnLock(dirLock);
FreeDosObject(DOS_FIB, fib);
```
### ExAll — Bulk Directory Scan (OS 2.0+)
`ExAll` is significantly faster than `ExNext` for large directories — it batches results:
```c
#define BUFSIZE 4096
APTR buf = AllocVec(BUFSIZE, MEMF_ANY);
struct ExAllControl *eac = AllocDosObject(DOS_EXALLCONTROL, NULL);
eac->eac_LastKey = 0;
BPTR dirLock = Lock("SYS:Libs", SHARED_LOCK);
BOOL more;
do {
more = ExAll(dirLock, buf, BUFSIZE, ED_SIZE, eac);
if (!more && IoErr() != ERROR_NO_MORE_ENTRIES)
{
PrintFault(IoErr(), "ExAll error");
break;
}
struct ExAllData *ead = (struct ExAllData *)buf;
while (ead)
{
Printf(" %s (%ld bytes)\n", ead->ed_Name, ead->ed_Size);
ead = ead->ed_Next;
}
} while (more);
FreeDosObject(DOS_EXALLCONTROL, eac);
FreeVec(buf);
UnLock(dirLock);
```
---
## Practical Patterns
### Safe File Replacement (Atomic Write)
```c
/* Anti-pattern: overwriting directly — corruption on crash */
/* WRONG: */
fh = Open("config.prefs", MODE_NEWFILE); /* truncates original! */
Write(fh, data, size); /* crash here = lost config */
Close(fh);
/* Correct: write-to-temp then rename */
fh = Open("config.prefs.tmp", MODE_NEWFILE);
Write(fh, data, size);
Close(fh);
/* Atomic swap: */
DeleteFile("config.prefs.bak"); /* remove old backup */
Rename("config.prefs", "config.prefs.bak"); /* backup current */
Rename("config.prefs.tmp", "config.prefs"); /* install new */
```
### Check If File Exists (Without Opening)
```c
/* Use Lock — lighter than Open: */
BPTR lock = Lock("SYS:Libs/68040.library", SHARED_LOCK);
if (lock) {
UnLock(lock);
/* file exists */
} else {
/* file doesn't exist (or permission denied — check IoErr()) */
}
```
### Get Parent Directory
```c
BPTR fileLock = Lock("SYS:Libs/68040.library", SHARED_LOCK);
BPTR parentLock = ParentDir(fileLock);
if (parentLock) {
/* parentLock = lock on SYS:Libs */
NameFromLock(parentLock, buf, sizeof(buf));
Printf("Parent: %s\n", buf); /* "SYS:Libs" */
UnLock(parentLock);
}
UnLock(fileLock);
```
### Resolve Full Path from Lock
```c
/* Get the full filesystem path of any lock: */
BPTR lock = Lock("PROGDIR:data", SHARED_LOCK);
char fullpath[256];
if (NameFromLock(lock, fullpath, sizeof(fullpath)))
Printf("Full path: %s\n", fullpath);
/* e.g. "DH0:Games/MyGame/data" */
UnLock(lock);
```
### Determine Volume / Device Name
```c
/* Find which volume a lock belongs to: */
struct InfoData *id = AllocVec(sizeof(struct InfoData), MEMF_ANY);
BPTR lock = Lock("SYS:", SHARED_LOCK);
if (Info(lock, id))
{
struct DosList *vol = (struct DosList *)BADDR(id->id_VolumeNode);
UBYTE *bname = (UBYTE *)BADDR(vol->dol_Name);
Printf("Volume: %.*s\n", bname[0], &bname[1]); /* BSTR */
Printf("Disk type: $%08lx\n", id->id_DiskType); /* 'DOS\1'=FFS */
Printf("Used: %ld / %ld blocks\n",
id->id_NumBlocksUsed, id->id_NumBlocks);
}
UnLock(lock);
FreeVec(id);
```
---
## Common Antipatterns
### ❌ Leaking Locks
```c
/* WRONG: lock is never released → filesystem handler keeps reference
forever, preventing volume ejection */
void bad_function(void) {
BPTR lock = Lock("DF0:file", SHARED_LOCK);
if (!lock) return;
/* ... forgot UnLock(lock) ... */
}
/* CORRECT: always pair Lock/UnLock */
void good_function(void) {
BPTR lock = Lock("DF0:file", SHARED_LOCK);
if (!lock) return;
/* ... do work ... */
UnLock(lock); /* ALWAYS release */
}
```
> Leaked locks are the #1 cause of "please insert volume X" requesters that never go away. The filesystem handler keeps the volume "in use" because an outstanding lock exists.
### ❌ Holding Exclusive Lock Too Long
```c
/* WRONG: other processes blocked for entire read operation */
BPTR lock = Lock("shared_data.dat", EXCLUSIVE_LOCK);
fh = OpenFromLock(lock); /* lock consumed by OpenFromLock */
/* ... lengthy processing ... */
Close(fh);
/* BETTER: use shared lock, only exclusive for the write phase */
BPTR lock = Lock("shared_data.dat", SHARED_LOCK);
fh = OpenFromLock(lock);
Read(fh, buf, size); /* shared — others can read too */
Close(fh);
/* Now open exclusively just for writing: */
fh = Open("shared_data.dat", MODE_READWRITE);
Write(fh, newdata, newsize);
Close(fh);
```
### ❌ Not Checking IoErr() After Lock Failure
```c
/* WRONG: can't distinguish "file not found" from "disk error" */
BPTR lock = Lock("DF0:important", SHARED_LOCK);
if (!lock) Printf("Not found!\n"); /* might be disk error! */
/* CORRECT: */
BPTR lock = Lock("DF0:important", SHARED_LOCK);
if (!lock) {
LONG err = IoErr();
switch (err) {
case ERROR_OBJECT_NOT_FOUND: Printf("Not found\n"); break;
case ERROR_OBJECT_IN_USE: Printf("Locked by another process\n"); break;
case ERROR_DISK_NOT_VALIDATED: Printf("Disk not ready\n"); break;
default: PrintFault(err, "Lock failed"); break;
}
}
```
### ❌ Using DupLock on Exclusive Locks
```c
/* WRONG: DupLock on an exclusive lock creates a SHARED copy.
This breaks the exclusivity guarantee! */
BPTR excl = Lock("data.dat", EXCLUSIVE_LOCK);
BPTR dup = DupLock(excl); /* dup is SHARED — exclusivity lost */
/* DupLock always creates a shared lock, regardless of the source. */
```
---
## Pattern Matching (Directory Wildcards)
```c
/* Match files against AmigaDOS wildcard patterns: */
/* #? = any string (like Unix *) */
/* ? = any single char */
/* ~ = NOT */
/* | = OR */
/* ( ) = grouping */
char tokenBuf[256];
LONG tok = ParsePattern("#?.library", tokenBuf, sizeof(tokenBuf));
if (tok >= 0) {
/* tok > 0 means pattern contains wildcards */
/* tok = 0 means literal string (no wildcards) */
while (ExNext(dirLock, fib)) {
if (MatchPattern(tokenBuf, fib->fib_FileName))
Printf(" Match: %s\n", fib->fib_FileName);
}
}
```
| AmigaDOS Pattern | Unix Equivalent | Meaning |
|---|---|---|
| `#?` | `*` | Any string |
| `?` | `?` | Any single character |
| `#?.library` | `*.library` | All library files |
| `~(#?.info)` | (no equiv) | Everything except .info files |
| `(a|b)#?` | `{a,b}*` | Starting with a or b |
---
## References
- NDK39: `dos/dos.h`, `dos/dosextens.h`, `dos/exall.h`
- ADCD 2.1: `Lock`, `UnLock`, `Examine`, `ExNext`, `ExAll`, `NameFromLock`
- Ralph Babel: *The AmigaDOS Manual* — lock semantics chapter
- See also: [packet_system.md](packet_system.md) — how locks translate to handler packets
- See also: [file_io.md](file_io.md) — file operations using handles