System and CP/M services¶
This page covers low-level file descriptors, directory enumeration, error reporting, assertions, and the CP/M-specific extensions.
unistd.h / fcntl.h — low-level file I/O¶
These map onto CP/M file operations and operate on small integer file
descriptors. Include unistd.h and fcntl.h.
| Function | Summary |
|---|---|
int open(const char *path, int flags, ...) |
Open/create a file, returns a descriptor. |
int read(int fd, void *buf, unsigned n) |
Read up to n bytes. |
int write(int fd, const void *buf, unsigned n) |
Write up to n bytes. |
int close(int fd) |
Close a descriptor. |
long lseek(int fd, long off, int whence) |
Reposition the descriptor. |
int unlink(const char *path) |
Delete a file. |
int fsync(int fd) |
Flush file data to disk. |
int fdatasync(int fd) |
Flush file data to disk. |
fcntl.h also declares open in K&R form (int open();) for compatibility.
off_t from unistd.h is long.
| Constant | Value | Meaning |
|---|---|---|
O_RDONLY |
0 | open for reading |
O_WRONLY |
1 | open for writing |
O_RDWR |
2 | open for reading and writing |
O_CREAT |
0100 | create the file if needed |
O_TRUNC |
01000 | truncate an existing file |
int fd = open("OUT.BIN", O_WRONLY | O_CREAT | O_TRUNC);
if (fd >= 0) {
write(fd, data, len);
close(fd);
}
Single file I/O core
open/read/write/close/lseek/unlink/fsync/fdatasync share one
FCB/DMA core. The first file call links that core; additional file calls are
nearly free. See the appendix.
dirent.h — directory enumeration¶
Include dirent.h. CP/M has no subdirectories, so this enumerates files on the
selected drive.
| Function | Summary |
|---|---|
DIR *opendir(const char *path) |
Begin a scan (".", "*.*", or "A:"). |
struct dirent *readdir(DIR *dirp) |
Next entry, or NULL at the end. |
int closedir(DIR *dirp) |
End the scan. |
struct dirent has a single member, char d_name[13], holding the 8.3 name.
The public API uses POSIX-like names through macros:
| Public name | Runtime entry name |
|---|---|
opendir |
dopn |
readdir |
drd |
closedir |
dcls |
Call the public names. The short runtime names exist to avoid external-symbol collisions on the M80/L80 toolchain.
DIR *d = opendir("*.*");
struct dirent *e;
while ((e = readdir(d)) != NULL)
puts(e->d_name);
closedir(d);
Standard diagnostics and errors¶
The standard-library reference now has dedicated pages for
error reporting and
assertions. The CP/M file runtime uses errno for
file-related failures, and assert writes its diagnostic through stderr before
terminating with exit(1).
CP/M extensions¶
The runtime exposes the raw CP/M BDOS entry point for things the standard
library doesn't cover (console status, direct disk calls, and so on). It is
declared in stdlib.h:
int bdos(int fn, int dearg);
fn is the BDOS function number and dearg is the value passed in DE; the
byte result comes back in the low byte of the returned int. Calls whose useful
result is an FCB/DMA region (directory and file operations) return their data
through the memory dearg points at, not in the return value.
Non-blocking console input¶
The standard input calls (getchar, getc, fgets, scanf) are blocking
C-style input. For games, menus, terminal UIs, and other polling loops, use the
runtime's kbhit() and getch() (declared in stdio.h):
int kbhit(void)returns nonzero when a key is waiting and0otherwise. It never blocks and does not consume the character.int getch(void)reads one key without echo. It blocks until a key is ready, so it is normally guarded bykbhit().
#include <stdio.h>
int main(void)
{
int ch;
if (kbhit()) { /* non-blocking test */
ch = getch(); /* safe: a key is already waiting */
if (ch)
handle_key(ch);
}
return 0;
}
Under the hood kbhit() is CP/M BDOS function 11 (console status) and getch()
is BDOS function 6 (direct console input, E = 0xff). Raw calls can be made
through bdos(), but the named functions are clearer and also flush pending
buffered output before blocking:
#include <stdlib.h>
int raw_kbhit(void) { return bdos(11, 0) != 0; }
int raw_getch_nonblock(void) { return bdos(6, 0xff); } /* 0 = no key ready */
BDOS function 6 uses 0 as the "no character" sentinel, so it is best for
keyboard-style console input rather than protocols where NUL is meaningful.
Do not mix raw bdos() console I/O with buffered console functions in the same
code path; direct BDOS calls bypass the console output buffer used by
printf/puts.
Direct port I/O¶
For talking to hardware or an emulator's virtual devices, the runtime also
provides 8-bit port I/O, declared alongside bdos in stdlib.h:
int inp(unsigned port); /* IN A,(port) -> 0..255 */
void outp(unsigned port, unsigned val); /* OUT (port),A */
inp runs the Z80 IN A,(port) instruction and returns the byte read,
zero-extended to int (so the result is always 0..255). outp runs
OUT (port),A, sending the low byte of val to the port. Only the low 8 bits
of port are significant. Neither is part of C89.