Porting Unix software to AmigaOS 3.x is not a recompile. It is a negotiation.
Software written after 1985 assumes primitives the Amiga simply does not have -
no fork(), no stat() as POSIX defines it,
no signal delivery, no mmap(), no symbolic links, no protection bits
that map cleanly. Every port is a series of small decisions about what to
forward, what to fake, and what to refuse.
amiport is the library of those decisions. The page below is the map.
69
ports shipping
69
Tier 1 shims
2,057
tests passing
7
on Aminet
The three impossibilities
Most of the difficulty in porting collapses onto three primitives that AmigaOS 3.x does not provide.
Each demands a different response: forward, fake, or refuse.
fork()
Spawns an identical copy of the running process - the single most load-bearing primitive in Unix. Shells, pipelines, and build systems all lean on it.
What Unix expects
Full process duplication with copy-on-write memory, inherited fds, independent scheduling.
What AmigaOS offers
CreateNewProc() - a task in the same address space with no memory isolation and no exec() pair.
What amiport does
xRefuse. Ports that genuinely need fork() (bash, make, ssh server) are out-of-scope. Ports that only shell out (patch, diff) use System() one-shot invocation.
stat()
POSIX file metadata: mode, owner, size, three timestamps, inode, device. Every file tool on Earth uses this.
What Unix expects
Atomic stat(path, &sb) returning permission bits, two timestamps in Unix epoch, and a stable inode across renames.
What AmigaOS offers
Lock() + Examine(): two separate calls, protection bits with inverted semantics, one DateStamp in ticks-from-1978, no inode.
What amiport does
Forward.amiport_stat() composes Lock+Examine, converts DateStamp to Unix epoch, and maps AmigaDOS flags back to POSIX. Inode is synthesized from the lock hash.
Named signals, installable handlers, interrupt of any blocking call, process-group semantics.
What AmigaOS offers
32 task signal bits, polled via SetSignal(), synchronously checked. SIGBREAKF_CTRL_C only.
What amiport does
Fake.amiport_signal(SIGINT, h) stores a handler. Long-running loops call check_break() between iterations; it polls SetSignal(0,0) and invokes the handler if Ctrl-C fired.
The tier model
Not every missing primitive gets the same treatment. Every shim is classified by how closely the Amiga side matches POSIX semantics. A port's complexity is just the sum of the tiers it touches.
1
Direct mapping
One AmigaOS call matches POSIX semantics. Thin wrapper. No surprises.
Cannot be faked at the shim layer. Either the port avoids them, or the port is rejected.
fork, exec*, pthread_*, pipe, select() on non-sockets, mmap(MAP_SHARED)
∞
out of scope
The shim library
A curated selection of Tier 1 POSIX functions libamiport.a provides, and what each maps to on the AmigaOS side.
For the full list of 69 shims, see the shims browser or the
source tree.
amiport Tier 1 POSIX functions and their AmigaOS mappings
Process & time 9 functions · process.c, signal_emu.c, time_funcs.c
sleep
amiport_sleep
Delay() - 1/50s granularity
usleep
amiport_usleep
Delay() - rounds up
getpid
amiport_getpid
FindTask(NULL) cast to int
time
amiport_time
DateStamp() + epoch offset
gettimeofday
amiport_gettimeofday
Approximate microseconds
signal(SIGINT)
amiport_signal
SetSignal() / Ctrl-C only
raise(SIGINT)
amiport_raise
Signal()
getopt
amiport_getopt
Pure C implementation
check_break
check_break
Polls SetSignal(0,0) - call between loop iterations
Source: lib/posix-shim
· Header: amiport/amiport.h
· Link flag: -L../../lib/posix-shim -lamiport
Case study: porting patch
OpenBSD patch is a good case because it touches almost every shim category - file I/O, directory ops, temp files, memory-mapped reads, and signal handling - without needing the Tier 3 refusals.
emu_mmap / emu_munmap - backed by malloc + read. Patch never writes through the mapping, so shared semantics are unneeded.
Tier 3 refusals
None. Patch never fork()s or exec()s.
Upstream patches
4 patches:
remove flock() (no-op on AmigaOS - single-task access is already exclusive);
replace mmap reads with emu_mmap;
force amiport/*.h includes ahead of libc;
wire check_break into the hunk-apply loop so Ctrl-C aborts cleanly mid-patch.
Binary size
128 KB stripped, m68k-amigaos
Tests
42 / 42 passing under FS-UAE (Workbench 3.1, 4 MB Fast RAM)
Aminet
Not yet submitted. Review pending.
What amiport will not do
A short, deliberate list of the refusals. A shim that lies about its semantics is worse than a missing one; every item below was considered, prototyped, and rejected.
No fork emulation. Single-address-space tasks cannot honour process semantics. Use System() for one-shots.
No threading. Amiga tasks are cooperative and share memory. pthread_* would be a lie.
No anonymous pipes.PIPE: exists but is fragile; ports that want pipelines should be rewritten to streaming stdio.
No symlinks. FFS does not support them. lstat is aliased to stat rather than faked.
No chmod. Amiga protection bits invert POSIX meaning. A stub that silently succeeds protects callers; a stub that pretends to work corrupts files.
No select() on non-sockets.bsdsocket.library only polls sockets. Console input uses WaitForChar().
No MAP_SHARED. Tier 2 emu_mmap is read-only, malloc-backed.
No locale. Ports assume LANG=C. Wide-char paths are rejected at open().
Adding a shim
The bar for adding a Tier 1 shim is: does an AmigaOS call exist whose semantics match the POSIX function for the common case?
If yes - write the wrapper, add a test file under tests/shim/, run make test-shim, submit.
$ git clone https://github.com/bdgscotland/amiport && cd amiport $ make build-shim $ make test-shim
Each new Tier 1 shim needs: the wrapper in lib/posix-shim/src/,
a public declaration in the matching amiport/*.h,
at least one test that exercises the error path,
and an entry in this table. Pull requests welcome.