[← 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)` | 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 | --- ## 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 | Behavior | |---|---|---| | `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