mirror of
https://github.com/alfishe/amiga-bootcamp.git
synced 2026-06-13 00:26:28 +00:00
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
This commit is contained in:
parent
aeaea88d75
commit
da9e7d3b63
7 changed files with 1599 additions and 301 deletions
|
|
@ -1,10 +1,10 @@
|
|||
[← Home](../README.md) · [AmigaDOS](README.md)
|
||||
|
||||
# File I/O — Open, Close, Read, Write, Seek
|
||||
# File I/O — Open, Close, Read, Write, Seek, Async I/O
|
||||
|
||||
## Overview
|
||||
|
||||
AmigaDOS file I/O is synchronous from the caller's perspective. All functions use `BPTR` file handles and communicate errors via `IoErr()`.
|
||||
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)).
|
||||
|
||||
---
|
||||
|
||||
|
|
@ -17,6 +17,12 @@ AmigaDOS file I/O is synchronous from the caller's perspective. All functions us
|
|||
| −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)` | D1–D4 | D0=blocks read (buffered) |
|
||||
| −336 | `FWrite(fh, buf, bsize, n)` | D1–D4 | D0=blocks written (buffered) |
|
||||
| −348 | `FGets(fh, buf, len)` | D1–D3 | D0=buf or NULL |
|
||||
| −354 | `FPuts(fh, str)` | D1–D2 | D0=0 or EOF |
|
||||
| −360 | `Flush(fh)` | D1=handle | D0=BOOL |
|
||||
| −366 | `SetVBuf(fh, buf, type, size)` | D1–D4 | D0=0 or −1 |
|
||||
|
||||
---
|
||||
|
||||
|
|
@ -29,6 +35,12 @@ AmigaDOS file I/O is synchronous from the caller's perspective. All functions us
|
|||
#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
|
||||
|
|
@ -39,49 +51,192 @@ AmigaDOS file I/O is synchronous from the caller's perspective. All functions us
|
|||
#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 (BPTR)
|
||||
## File Handle Structure
|
||||
|
||||
The returned handle is a BPTR to a `struct FileHandle`:
|
||||
|
||||
```c
|
||||
/* dos/dosextens.h */
|
||||
struct FileHandle {
|
||||
struct Message *fh_Link;
|
||||
struct MsgPort *fh_Interactive; /* non-NULL if console */
|
||||
struct MsgPort *fh_Type; /* handler process port */
|
||||
BPTR fh_Buf; /* I/O buffer (BPTR) */
|
||||
LONG fh_Pos; /* current position in buffer */
|
||||
LONG fh_End; /* end of valid data in buffer */
|
||||
LONG fh_Funcs; /* unused */
|
||||
LONG fh_Func2; /* unused */
|
||||
LONG fh_Func3; /* unused */
|
||||
LONG fh_Args; /* packet args */
|
||||
BPTR fh_Arg2;
|
||||
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) */
|
||||
```
|
||||
|
||||
To access as a C pointer: `struct FileHandle *fh = BADDR(handle);`
|
||||
```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 |
|
||||
|
||||
---
|
||||
|
||||
## Usage Example
|
||||
## Standard I/O Handles
|
||||
|
||||
```c
|
||||
BPTR fh = Open("RAM:test.txt", MODE_NEWFILE);
|
||||
if (fh) {
|
||||
Write(fh, "Hello Amiga\n", 12);
|
||||
Close(fh);
|
||||
}
|
||||
/* 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) */
|
||||
|
||||
fh = Open("RAM:test.txt", MODE_OLDFILE);
|
||||
if (fh) {
|
||||
UBYTE buf[64];
|
||||
LONG n = Read(fh, buf, sizeof(buf));
|
||||
if (n > 0) Write(Output(), buf, n); /* echo to stdout */
|
||||
/* 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);
|
||||
} else {
|
||||
PrintFault(IoErr(), "Open failed");
|
||||
*sizeOut = size;
|
||||
return buf;
|
||||
}
|
||||
```
|
||||
|
||||
|
|
@ -90,18 +245,41 @@ if (fh) {
|
|||
## Error Checking
|
||||
|
||||
```c
|
||||
LONG err = IoErr(); /* LVO −66 — returns last DOS error code */
|
||||
/* Common codes: */
|
||||
#define ERROR_OBJECT_NOT_FOUND 205
|
||||
#define ERROR_OBJECT_EXISTS 203
|
||||
#define ERROR_DISK_FULL 221
|
||||
#define ERROR_SEEK_ERROR 219
|
||||
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`
|
||||
- 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
|
||||
- ADCD 2.1: `Open`, `Close`, `Read`, `Write`, `Seek`
|
||||
- [packet_system.md](packet_system.md) — how I/O translates to handler packets
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue