TODO.md Validity Audit
Audit date: 2026-07-07
Auditor: GPT-5.5
This report checks each bullet in TODO.md against the current source tree.
Pretty much all of these items were noted by the original creator of this project, with a handful of exceptions.
Statuses mean:
- valid: still describes missing or incomplete behavior.
- partially valid: some implementation exists, but the TODO is not fully satisfied or its scope has changed.
- resolved: the requested behavior is implemented well enough that the bullet is stale.
- stale: the old wording no longer matches the current architecture.
- unclear: no decisive local evidence either way.
Summary
| Status |
Count |
| valid |
18 |
| partially valid |
7 |
| resolved |
6 |
| stale |
0 |
| unclear |
0 |
General Client Shortcomings
| TODO item |
Status |
Evidence and notes |
| integration with avahi/bonjour |
valid |
No Avahi, Bonjour, DNS-SD, or mDNS implementation is present. lib/identify.c only notes that server identification "could later use mDNS". |
| shutting down notices aren't honoured |
resolved |
DSI attention packets are parsed in lib/dsi.c; AFPATTN_CRASH and AFPATTN_SHUTDOWN set shutdown state, fetch server messages when needed, and call loop_disconnect(). |
Command Line Client
| TODO item |
Status |
Evidence and notes |
| paginate directory listings with >256 files |
resolved |
Low-level directory reads loop in 256-entry batches in lib/lowlevel.c. The stateless API also loops and exposes start, count, and eod in daemon/stateless.c; cmdline/cmdline_afp.c has remote_readdir_all() to collect pages. |
FUSE Client
| TODO item |
Status |
Evidence and notes |
| do correct address/signature matching; right now we don't actually use the signature |
partially valid |
The current stateless resume/reconnect path matches by server name/address and auth in daemon/commands.c, and find_server_by_signature() exists in lib/afp.c, but no current caller uses the server signature for session matching. For FUSE mounts, the architecture is now mostly one daemon per mount, so the original FUSE-specific wording is stale, but the broader signature-matching gap remains. |
AFP 3.x
| TODO item |
Status |
Evidence and notes |
| filenames have a maximum length of 255, but AFP 3.x allows for much more |
partially valid |
invalid_filename() allows 1024-byte UTF-8 path components for AFP 3.x, so this is not purely true anymore. However, AFP_MAX_PATH is still 768, many public structures use fixed AFP_MAX_PATH buffers, and ml_statfs() still reports f_namemax = 255. |
| ACL support |
valid |
AFP ACL command IDs and kSupportsACLs exist in headers, but there are no afp_getacl/afp_setacl implementations or stateless/FUSE API surface. docs/FEATURES.md still says ACLs have not been implemented. |
AFP 2.x
| TODO item |
Status |
Evidence and notes |
| use getsrvrinfo to get connection IP address to make room for AT |
valid |
The URL structure still has an AT protocol enum and zone, but afp_get_address() is TCP/IP via getaddrinfo(), and docs/FEATURES.md says AppleTalk is unsupported. |
| connection recovery: open files |
valid |
Open forks are tracked in lib/forklist.c, but reconnect only reopens volumes in afp_server_reconnect(); there is no replay of open forks with fresh fork IDs. |
| connection recovery: locked files |
valid |
Byte-range locks are taken and released around individual read/write calls in lib/lowlevel.c. There is no persisted lock journal or replay across reconnect. |
| desktop database support |
partially valid |
Desktop code exists for OpenDT/CloseDT, comments, and icons in lib/proto_desktop.c and lib/resource.c. Broader desktop database support remains incomplete, and docs/FEATURES.md says only comments are actually implemented. |
| UTF8 flag is now server-specific, but it should be volume-specific |
valid |
afp_connect_volume() derives encoding from each volume's kSupportsUTF8Names, but writes it into server->path_encoding, so one volume can still change the whole server object's path encoding. |
Authentication
| TODO item |
Status |
Evidence and notes |
| ClientKRB |
valid |
UAM_CLIENTKRB is defined and the UAM string exists, but no Kerberos login implementation is registered in lib/uams.c. proto_session.c explicitly says kGetKerberosSessionKey is unsupported. |
| reconnection |
partially valid |
Generic reconnect and stateless resume exist (afp_server_reconnect(), afp_sl_resume(), FUSE suspend/resume). The AFP Recon1 UAM is still not implemented or registered, and DHX/DHX2 still contain FIXME comments about stashing keys for Reconnect UAM support. |
| Open directory integration |
valid |
Server flags are displayed by afpgetstatus, but there is no Open Directory integration layer or authentication implementation. |
Performance
| TODO item |
Status |
Evidence and notes |
| in mknod(), you only need to do the setfiledirparms if the mode or perms are different |
resolved |
fuse_mknod() calls ml_creat(), and ml_creat() now deliberately skips FPSetFileDirParms after FPCreateFile; mode changes are left to later chmod paths. |
| asynchronous unlocking |
valid |
ll_read() and ll_write() synchronously call ll_handle_unlocking() before returning. There is no async unlock queue. |
| use rx and tx quantums properly |
partially valid |
Writes use server->tx_quantum; reads chunk by server->rx_quantum; the DSI open-session reply parses server tx quantum. However afp_server_connect() still hardcodes rx_quantum = 128 * 1024 after measuring status latency, so this is only partly addressed. |
| queue writes to be one tx quantum |
partially valid |
ll_write() chunks each AFP write to at most tx_quantum, but writes are synchronous; there is no queued/coalesced write pipeline. |
| optimize locking |
valid |
Current reads/writes take byte-range locks around every operation and release synchronously. No broader lock coalescing, caching, or optimization layer is visible. |
| don't go back through the select loop to read what comes after the DSI packet |
valid |
dsi_recv() reads the DSI header and returns immediately for non-empty payloads, with a comment saying the rest of the packet will be read next time. |
| make a preallocated pool of dsi requests |
valid |
dsi_send() still allocates each struct dsi_request with malloc(). |
| make a preallocated pool for dsi messages |
valid |
There is a per-server incoming_buffer, but no message pool; temporary protocol messages and attention contexts are still allocated ad hoc. |
| is_dir function should look in did cache |
valid |
is_dir() contains a disabled #if 0 cache lookup, and the active path always calls afp_getfiledirparms(). The current DID cache entry does not store isdir. |
| check to see how Mac OS does locking on writes |
valid |
The code has macOS compatibility notes for open access flags, but no local test or documented conclusion for write locking behavior. |
| large block writes for FUSE 3.x |
valid |
FUSE 3 is detected, but the operations table does not implement write_buf, and init does not request or tune large-write capabilities such as max_write/big writes. |
Protocol Bugs
| TODO item |
Status |
Evidence and notes |
| Netatalk Client doesn't handle the situation where the server is shutdown |
resolved |
Shutdown attention notices and close-session packets disconnect the server; EOF/protocol read errors in dsi_recv() cause loop_disconnect() and leave the server in SERVER_STATE_DISCONNECTED for later reconnect. A narrower "mid-transaction recovery" gap remains, but that is covered by reconnect reliability/open-file bullets. |
| reconnect isn't reliable |
partially valid |
Reconnect support has been substantially improved: pending requests are failed/flushed, connection generations reject stale replies, FUSE suspend/resume exists, and stateless resume exists. Remaining gaps include open fork and lock replay, signature matching, and docs that still say mid-transaction recovery is not supported. |
| If a DSI stream gets broken or there's a protocol error, the connection should be reset |
resolved |
dsi_recv() returns -1 for read errors, EOF, oversized payloads, unknown DSI commands, and other protocol errors; process_server_fds() then calls loop_disconnect(). |
| for logins, fpLoginExt should be used instead of fpLogin |
valid |
afp_loginext() exists and SRP uses it, but the normal UAM implementations still call afp_login()/afpLogin for No User Auth, Cleartext, Randnum, DHX, and DHX2. |
| for fpCreateFile, use soft creates |
resolved |
ml_creat() uses kFPSoftCreate; ll_open() also uses soft creates for O_CREAT without O_EXCL and hard creates only for exclusive creation. |
| honour volume's HasConfigInfo flag |
valid |
HasConfigInfo is defined for FPGetSrvrParms volume flags and those flags are parsed into volume->flags, but no behavior appears to branch on HasConfigInfo. |
TODO.md Validity Audit
Audit date: 2026-07-07
Auditor: GPT-5.5
This report checks each bullet in
TODO.mdagainst the current source tree.Pretty much all of these items were noted by the original creator of this project, with a handful of exceptions.
Statuses mean:
Summary
General Client Shortcomings
lib/identify.conly notes that server identification "could later use mDNS".lib/dsi.c;AFPATTN_CRASHandAFPATTN_SHUTDOWNset shutdown state, fetch server messages when needed, and callloop_disconnect().Command Line Client
lib/lowlevel.c. The stateless API also loops and exposesstart,count, andeodindaemon/stateless.c;cmdline/cmdline_afp.chasremote_readdir_all()to collect pages.FUSE Client
daemon/commands.c, andfind_server_by_signature()exists inlib/afp.c, but no current caller uses the server signature for session matching. For FUSE mounts, the architecture is now mostly one daemon per mount, so the original FUSE-specific wording is stale, but the broader signature-matching gap remains.AFP 3.x
invalid_filename()allows 1024-byte UTF-8 path components for AFP 3.x, so this is not purely true anymore. However,AFP_MAX_PATHis still 768, many public structures use fixedAFP_MAX_PATHbuffers, andml_statfs()still reportsf_namemax = 255.kSupportsACLsexist in headers, but there are noafp_getacl/afp_setaclimplementations or stateless/FUSE API surface.docs/FEATURES.mdstill says ACLs have not been implemented.AFP 2.x
ATprotocol enum andzone, butafp_get_address()is TCP/IP viagetaddrinfo(), anddocs/FEATURES.mdsays AppleTalk is unsupported.lib/forklist.c, but reconnect only reopens volumes inafp_server_reconnect(); there is no replay of open forks with fresh fork IDs.lib/lowlevel.c. There is no persisted lock journal or replay across reconnect.lib/proto_desktop.candlib/resource.c. Broader desktop database support remains incomplete, anddocs/FEATURES.mdsays only comments are actually implemented.afp_connect_volume()derives encoding from each volume'skSupportsUTF8Names, but writes it intoserver->path_encoding, so one volume can still change the whole server object's path encoding.Authentication
UAM_CLIENTKRBis defined and the UAM string exists, but no Kerberos login implementation is registered inlib/uams.c.proto_session.cexplicitly sayskGetKerberosSessionKeyis unsupported.afp_server_reconnect(),afp_sl_resume(), FUSE suspend/resume). The AFPRecon1UAM is still not implemented or registered, and DHX/DHX2 still contain FIXME comments about stashing keys for Reconnect UAM support.afpgetstatus, but there is no Open Directory integration layer or authentication implementation.Performance
fuse_mknod()callsml_creat(), andml_creat()now deliberately skipsFPSetFileDirParmsafterFPCreateFile; mode changes are left to later chmod paths.ll_read()andll_write()synchronously callll_handle_unlocking()before returning. There is no async unlock queue.server->tx_quantum; reads chunk byserver->rx_quantum; the DSI open-session reply parses server tx quantum. Howeverafp_server_connect()still hardcodesrx_quantum = 128 * 1024after measuring status latency, so this is only partly addressed.ll_write()chunks each AFP write to at mosttx_quantum, but writes are synchronous; there is no queued/coalesced write pipeline.dsi_recv()reads the DSI header and returns immediately for non-empty payloads, with a comment saying the rest of the packet will be read next time.dsi_send()still allocates eachstruct dsi_requestwithmalloc().incoming_buffer, but no message pool; temporary protocol messages and attention contexts are still allocated ad hoc.is_dir()contains a disabled#if 0cache lookup, and the active path always callsafp_getfiledirparms(). The current DID cache entry does not storeisdir.write_buf, and init does not request or tune large-write capabilities such asmax_write/big writes.Protocol Bugs
dsi_recv()causeloop_disconnect()and leave the server inSERVER_STATE_DISCONNECTEDfor later reconnect. A narrower "mid-transaction recovery" gap remains, but that is covered by reconnect reliability/open-file bullets.dsi_recv()returns-1for read errors, EOF, oversized payloads, unknown DSI commands, and other protocol errors;process_server_fds()then callsloop_disconnect().afp_loginext()exists and SRP uses it, but the normal UAM implementations still callafp_login()/afpLoginfor No User Auth, Cleartext, Randnum, DHX, and DHX2.ml_creat()useskFPSoftCreate;ll_open()also uses soft creates forO_CREATwithoutO_EXCLand hard creates only for exclusive creation.HasConfigInfois defined forFPGetSrvrParmsvolume flags and those flags are parsed intovolume->flags, but no behavior appears to branch onHasConfigInfo.