amiga-bootcamp/07_dos/file_io.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

285 lines
8.6 KiB
Markdown
Raw 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)
# File I/O — Open, Close, Read, Write, Seek, Async I/O
## Overview
AmigaDOS file I/O is synchronous from the caller's perspective — each call blocks until the filesystem handler completes the operation. All functions use `BPTR` file handles and communicate errors via `IoErr()`. Internally, every I/O call is translated into a DosPacket sent to the filesystem handler process (see [packet_system.md](packet_system.md)).
---
## Core Functions
| LVO | Function | Registers | Returns |
|---|---|---|---|
| 30 | `Open(name, mode)` | D1=name, D2=mode | D0=BPTR handle (0=fail) |
| 36 | `Close(fh)` | D1=handle | D0=BOOL |
| 42 | `Read(fh, buf, len)` | D1=handle, D2=buf, D3=len | D0=actual bytes (1=error) |
| 48 | `Write(fh, buf, len)` | D1=handle, D2=buf, D3=len | D0=actual bytes (1=error) |
| 66 | `Seek(fh, pos, mode)` | D1=handle, D2=pos, D3=mode | D0=old position (1=error) |
| 330 | `FRead(fh, buf, bsize, n)` | D1D4 | D0=blocks read (buffered) |
| 336 | `FWrite(fh, buf, bsize, n)` | D1D4 | D0=blocks written (buffered) |
| 348 | `FGets(fh, buf, len)` | D1D3 | D0=buf or NULL |
| 354 | `FPuts(fh, str)` | D1D2 | D0=0 or EOF |
| 360 | `Flush(fh)` | D1=handle | D0=BOOL |
| 366 | `SetVBuf(fh, buf, type, size)` | D1D4 | D0=0 or 1 |
---
## Access Modes
```c
/* dos/dos.h — NDK39 */
#define MODE_READWRITE 1004 /* open existing, read+write */
#define MODE_OLDFILE 1005 /* open existing, read only */
#define MODE_NEWFILE 1006 /* create new (truncate if exists) */
```
| Mode | If file exists | If file doesn't exist |
|---|---|---|
| `MODE_OLDFILE` | Opens for reading | Fails — `IoErr() = ERROR_OBJECT_NOT_FOUND` |
| `MODE_NEWFILE` | Truncates to 0, opens for writing | Creates new file |
| `MODE_READWRITE` | Opens at current size, read+write | Creates new file |
---
## Seek Modes
```c
#define OFFSET_BEGINNING -1 /* from start of file */
#define OFFSET_CURRENT 0 /* from current position */
#define OFFSET_END 1 /* from end of file */
```
> **Note**: `Seek()` returns the **old position** (before seeking), not the new one. To get file size: `Seek(fh, 0, OFFSET_END)` then `size = Seek(fh, 0, OFFSET_BEGINNING)`.
---
## File Handle Structure
The returned handle is a BPTR to a `struct FileHandle`:
```c
/* dos/dosextens.h */
struct FileHandle {
struct Message *fh_Link; /* +$00: exec message for reply */
struct MsgPort *fh_Interactive; /* +$04: non-NULL if console device */
struct MsgPort *fh_Type; /* +$08: handler process MsgPort */
BPTR fh_Buf; /* +$0C: I/O buffer (BPTR) */
LONG fh_Pos; /* +$10: current position in buffer */
LONG fh_End; /* +$14: end of valid data in buffer */
LONG fh_Funcs; /* +$18: unused (was BCPL function) */
LONG fh_Func2; /* +$1C: unused */
LONG fh_Func3; /* +$20: unused */
LONG fh_Args; /* +$24: FH_Arg1 — passed to handler */
BPTR fh_Arg2; /* +$28: handler-specific */
};
/* sizeof(struct FileHandle) = 44 bytes ($2C) */
```
```c
/* To access as a C pointer: */
struct FileHandle *fhp = BADDR(handle); /* BADDR = (ptr << 2) */
/* Check if interactive (console): */
if (IsInteractive(fh))
Printf("Connected to a console\n");
```
### I/O Buffering
AmigaDOS uses **per-handle buffering** (OS 2.0+). Default buffer is 512 bytes. Control with `SetVBuf()`:
```c
/* Make a file fully buffered with 8 KB buffer: */
SetVBuf(fh, NULL, BUF_FULL, 8192);
/* Line-buffered (flush on newline — useful for consoles): */
SetVBuf(fh, NULL, BUF_LINE, 1024);
/* Unbuffered (every write goes to handler immediately): */
SetVBuf(fh, NULL, BUF_NONE, 0);
/* Manually flush: */
Flush(fh);
```
| Buffer Type | Constant | Behaviour |
|---|---|---|
| `BUF_FULL` | 1 | Flush when buffer fills |
| `BUF_LINE` | 0 | Flush on newline or buffer full |
| `BUF_NONE` | 1 | No buffering — direct I/O |
---
## Standard I/O Handles
```c
/* Get the current process's stdin/stdout/stderr: */
BPTR in = Input(); /* stdin — from CLI or WB */
BPTR out = Output(); /* stdout */
BPTR err = ErrorOutput(); /* stderr (OS 3.0+ only) */
/* Write to stdout: */
FPuts(Output(), "Hello from AmigaDOS\n");
/* Printf — formatted output to stdout: */
Printf("Count: %ld, Name: %s\n", count, name);
/* Note: Printf uses %ld (not %d) — AmigaDOS always uses LONG */
```
> [!IMPORTANT]
> AmigaDOS `Printf` uses `%ld` for integers, not `%d`. The `%d` format is undefined. This is a common bug source when porting from Unix/ANSI C.
---
## File Information — ExamineFH, ExAll
```c
/* Get file metadata from a handle: */
struct FileInfoBlock *fib = AllocDosObject(DOS_FIB, NULL);
if (ExamineFH(fh, fib))
{
Printf("Name: %s\n", fib->fib_FileName);
Printf("Size: %ld\n", fib->fib_Size);
Printf("Type: %ld\n", fib->fib_DirEntryType);
/* > 0 = directory, < 0 = file */
}
FreeDosObject(DOS_FIB, fib);
```
### FileInfoBlock Structure
```c
struct FileInfoBlock {
LONG fib_DiskKey; /* block number on disk */
LONG fib_DirEntryType; /* >0=dir, <0=file */
char fib_FileName[108]; /* null-terminated name */
LONG fib_Protection; /* RWED protection bits */
LONG fib_EntryType; /* same as DirEntryType */
LONG fib_Size; /* file size in bytes */
LONG fib_NumBlocks; /* blocks consumed */
struct DateStamp fib_Date; /* modification date */
char fib_Comment[80]; /* file comment string */
UWORD fib_OwnerUID; /* owner (multiuser) */
UWORD fib_OwnerGID; /* group */
};
```
---
## Practical Patterns
### Copy a File
```c
BOOL CopyFile(CONST_STRPTR src, CONST_STRPTR dst)
{
BPTR in = Open(src, MODE_OLDFILE);
if (!in) return FALSE;
BPTR out = Open(dst, MODE_NEWFILE);
if (!out) { Close(in); return FALSE; }
UBYTE buf[4096];
LONG n;
while ((n = Read(in, buf, sizeof(buf))) > 0)
{
if (Write(out, buf, n) != n)
{
Close(in); Close(out);
return FALSE; /* write error */
}
}
Close(out);
Close(in);
return (n == 0); /* 0=EOF=success, -1=error */
}
```
### Determine File Size
```c
LONG GetFileSize(BPTR fh)
{
LONG oldpos = Seek(fh, 0, OFFSET_END); /* seek to end */
LONG size = Seek(fh, oldpos, OFFSET_BEGINNING); /* seek back */
return size;
}
/* Or with ExamineFH (no seeking needed): */
struct FileInfoBlock fib;
ExamineFH(fh, &fib);
LONG size = fib.fib_Size;
```
### Read Entire File into Memory
```c
APTR LoadFileToRAM(CONST_STRPTR path, ULONG *sizeOut)
{
BPTR fh = Open(path, MODE_OLDFILE);
if (!fh) return NULL;
/* Get size */
Seek(fh, 0, OFFSET_END);
LONG size = Seek(fh, 0, OFFSET_BEGINNING);
APTR buf = AllocVec(size, MEMF_ANY);
if (buf)
{
if (Read(fh, buf, size) != size)
{
FreeVec(buf);
buf = NULL;
}
}
Close(fh);
*sizeOut = size;
return buf;
}
```
---
## Error Checking
```c
LONG err = IoErr(); /* returns last DOS error code */
/* Human-readable error message: */
PrintFault(err, "Operation failed");
/* Output: "Operation failed: object not found" */
/* Or get error string into buffer: */
Fault(err, "Error", buf, sizeof(buf));
```
### Common Error Codes
| Code | Constant | Meaning |
|---|---|---|
| 103 | `ERROR_NO_FREE_STORE` | Out of memory |
| 202 | `ERROR_OBJECT_IN_USE` | File is locked by another process |
| 203 | `ERROR_OBJECT_EXISTS` | File already exists |
| 204 | `ERROR_DIR_NOT_FOUND` | Path component not found |
| 205 | `ERROR_OBJECT_NOT_FOUND` | File not found |
| 209 | `ERROR_ACTION_NOT_KNOWN` | Handler doesn't support this action |
| 210 | `ERROR_INVALID_COMPONENT_NAME` | Bad filename character |
| 212 | `ERROR_OBJECT_WRONG_TYPE` | Expected file, got directory (or vice versa) |
| 214 | `ERROR_DISK_FULL` | No space on volume |
| 216 | `ERROR_DELETE_PROTECTED` | File has delete-protection bit |
| 218 | `ERROR_WRITE_PROTECTED` | Disk is write-protected |
| 219 | `ERROR_SEEK_ERROR` | Invalid seek position |
| 221 | `ERROR_DISK_FULL` | Volume full |
| 225 | `ERROR_NOT_A_DOS_DISK` | Unrecognised filesystem |
| 232 | `ERROR_NO_MORE_ENTRIES` | ExNext — end of directory |
---
## References
- NDK39: `dos/dos.h`, `dos/dosextens.h`, `dos/stdio.h`
- ADCD 2.1: `Open`, `Close`, `Read`, `Write`, `Seek`, `SetVBuf`, `FRead`
- [error_handling.md](error_handling.md) — full error code list
- [packet_system.md](packet_system.md) — how I/O translates to handler packets