The problem

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
x Refuse. 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.
signal()

Asynchronous delivery of events - Ctrl-C, SIGPIPE, SIGALRM. Unix interactivity assumes them.

What Unix expects
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.

open, read, write, close, getcwd, opendir, stat, getenv...

69 functions
2

Behavioural emulation

No clean Amiga equivalent. Semantics faked in C, often via polling or re-implementation.

regex, mkstemp, check_break, expand_argv, recallocarray, mmap...

19 functions
3

Structural refusal

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
POSIX function amiport call AmigaOS mapping / note
File I/O 17 functions · file_io.c
openamiport_openOpen() with flag mapping
closeamiport_closeClose() - protects fd 0/1/2
readamiport_readRead()
writeamiport_writeWrite()
lseekamiport_lseekSeek() - handles old-position return
dupamiport_dupfd_table sharing + scan-on-close
dup2amiport_dup2fd_table sharing + scan-on-close
unlinkamiport_unlinkDeleteFile()
renameamiport_renameRename()
accessamiport_accessLock() test
isattyamiport_isattyIsInteractive()
preadamiport_preadSeek + Read + Seek. Non-atomic.
pwriteamiport_pwriteSeek + Write + Seek. Non-atomic.
tmpfileamiport_tmpfileOpen("T:...")
mkstempamiport_mkstempUnique name via task address + counter
chmodamiport_chmodNo-op stub - Amiga protection bits are inverted
realpathamiport_realpathLock() + NameFromLock()
Directory operations 7 functions · dir_ops.c
opendiramiport_opendirLock() + Examine()
readdiramiport_readdirExNext()
closediramiport_closedirUnLock()
mkdiramiport_mkdirCreateDir() - mode bits ignored
rmdiramiport_rmdirSame call as unlink on AmigaOS
getcwdamiport_getcwdGetCurrentDirName()
chdiramiport_chdirCurrentDir() + Lock()
File status 3 functions · stat.c
statamiport_statLock() + Examine() - DateStamp → Unix epoch
fstatamiport_fstatExamineFH() - requires AmigaOS 2.0+
lstatamiport_lstatAlias to stat - FFS has no symlinks
Environment 3 functions · process.c
getenvamiport_getenvGetVar() - returns malloc'd string
setenvamiport_setenvSetVar() + GVF_GLOBAL_ONLY - writes to ENV:
unsetenvamiport_unsetenvDeleteVar() + GVF_GLOBAL_ONLY
String safety 6 functions · string_bsd.c
strlcpyamiport_strlcpyBSD safe string copy
strlcatamiport_strlcatBSD safe string concatenation
reallocarrayamiport_reallocarrayOverflow-checked realloc
strtok_ramiport_strtok_rThread-safe strtok
strsepamiport_strsepBSD string tokenizer
strndupamiport_strndupBounded string duplication
stdio extensions 4 functions · stdio_ext.c
asprintfamiport_asprintfDynamic formatted string
vasprintfamiport_vasprintfva_list variant
getlineamiport_getlineGNU extension, fgets-based
getdelimamiport_getdelimRead until delimiter
Pattern matching 3 functions · fnmatch.c / scandir.c
fnmatchamiport_fnmatchShell-style glob matching
scandiramiport_scandirWith filter and sort callbacks
alphasortamiport_alphasortstrcmp wrapper for scandir
Process & time 9 functions · process.c, signal_emu.c, time_funcs.c
sleepamiport_sleepDelay() - 1/50s granularity
usleepamiport_usleepDelay() - rounds up
getpidamiport_getpidFindTask(NULL) cast to int
timeamiport_timeDateStamp() + epoch offset
gettimeofdayamiport_gettimeofdayApproximate microseconds
signal(SIGINT)amiport_signalSetSignal() / Ctrl-C only
raise(SIGINT)amiport_raiseSignal()
getoptamiport_getoptPure C implementation
check_breakcheck_breakPolls 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.

Upstream
OpenBSD patch v1.78 (ISC + BSD 3-Clause)
Tier 1 shims used
stat, fstat, open, read, write, close, lseek, unlink, rename, mkdir, chdir, chmod, opendir, readdir, closedir, signal, getline, mkstemp, strtonum, warnc, recallocarray, getopt_long
Tier 2 emulations
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.

Read the porting guide   Open issues