Dev Docs Troubleshooting
- Troubleshooting and Diagnostics
Troubleshooting and Diagnostics
Developer documentation for diagnosing and resolving issues in Netatalk’s AFP server. This page covers the logging system, common error codes, cache diagnostics, CNID database repair, and platform-specific pitfalls—all verified against the current source tree.
Logging Configuration
Netatalk uses a per-module logging system defined in include/atalk/logger.h. The LOG() macro is the primary logging interface, accepting a severity level, a module type, and a format string.
Log Levels
The enum loglevels defines these severity levels, from most to least severe:
| Level | Enum Constant | Description |
|---|---|---|
none |
log_none |
Logging disabled |
severe |
log_severe |
Fatal errors requiring immediate attention |
error |
log_error |
Operational errors |
warning |
log_warning |
Non-fatal warnings |
note |
log_note |
Important operational notices |
info |
log_info |
Informational messages (session stats, cache reports) |
debug |
log_debug |
Debug-level tracing |
debug6–debug9 |
log_debug6–log_debug9 |
Increasingly verbose debug output |
maxdebug |
log_maxdebug |
Maximum verbosity |
The default log level when not configured is default:note, set in the afp_config_parse() path in libatalk/util/netatalk_conf.c.
Log Types (Modules)
The enum logtypes provides independent log level control per subsystem:
| Config Name | Enum Constant | Subsystem |
|---|---|---|
default |
logtype_default |
Default (used when no specific type applies) |
logger |
logtype_logger |
Logger subsystem itself |
cnid |
logtype_cnid |
CNID database backends |
afpd |
logtype_afpd |
AFP daemon operations |
dsi |
logtype_dsi |
DSI transport layer |
atalkd |
logtype_atalkd |
AppleTalk daemon |
papd |
logtype_papd |
Printer Access Protocol daemon |
uams |
logtype_uams |
User Authentication Modules |
fce |
logtype_fce |
File Change Events |
ad |
logtype_ad |
AppleDouble metadata layer |
sl |
logtype_sl |
Spotlight search |
Configuration
Log levels are set in afp.conf via the log level option in the [Global] section. The setuplog() function in libatalk/util/logger.c parses the comma-separated module:level pairs and configures each module independently.
[Global]
; Default: log_note for all modules
log level = default:note
; Enable debug logging for afpd and cnid modules
log level = default:note, afpd:debug, cnid:debug
; Maximum verbosity for all modules
log level = default:debug9
; Log to a specific file (default is syslog)
log file = /var/log/netatalk-debug.log
; Enable microsecond timestamps for performance analysis
log microseconds = yes
The LOG Macro
The LOG() macro in include/atalk/logger.h performs an inline level check before calling make_log_entry():
#define LOG(log_level, type, ...) \
do { \
if (log_level <= type_configs[type].level) \
make_log_entry((log_level), (type), __FILE__, \
type_configs[type].timestamp_us, __LINE__, \
__VA_ARGS__); \
} while(0)
When compiled with NO_DEBUG, the macro also gates on LOG_MAX (defined as log_info), which compiles out all debug-level calls for release builds.
Logging Architecture
%%{ init: { 'themeVariables': { 'fontSize': '14px' }, 'flowchart': { 'nodeSpacing': 15, 'rankSpacing': 40 } } }%%
flowchart TD
A["afp.conf<br/><code>log level = afpd:debug, cnid:info</code>"]:::yellow
A --> B["afp_config_parse()<br/><i>netatalk_conf.c</i>"]:::blue
B --> C["setuplog()<br/><i>logger.c</i>"]:::blue
C --> D["setuplog_internal()<br/>per module:level pair"]:::purple
D --> E["type_configs\[logtype\].level<br/>= configured level"]:::green
F["LOG(log_debug, logtype_afpd, ...)"]:::salmon
F --> G{"level ≤<br/>type_configs\[afpd\].level?"}:::grey
G -->|Yes| H["make_log_entry()<br/>→ syslog / file"]:::green
G -->|No| I["Suppressed<br/>(no-op)"]:::grey
classDef blue fill:#74b9ff,stroke:#333,rx:10,ry:10
classDef purple fill:#a29bfe,stroke:#333,rx:10,ry:10
classDef green fill:#55efc4,stroke:#333,rx:10,ry:10
classDef yellow fill:#ffeaa7,stroke:#333,rx:10,ry:10
classDef grey fill:#dfe6e9,stroke:#333,rx:10,ry:10
classDef salmon fill:#fab1a0,stroke:#333,rx:10,ry:10
Common AFP Error Codes
AFP error codes are defined in include/atalk/afp.h. These are returned to clients in DSI reply packets and logged by the server.
Connection and Session Errors
| Constant | Value | Description | Common Cause |
|---|---|---|---|
AFPERR_MAXSESS |
−1068 | Maximum sessions reached | Too many concurrent clients; increase max connections |
AFPERR_SESSCLOS |
−5022 | Session closed | DSI tickle timeout or network interruption |
AFPERR_SHUTDOWN |
−5027 | Server going down | SIGUSR1 sent; graceful shutdown in progress |
AFPERR_NOTAUTH |
−5023 | Not authenticated | Session expired or UAM failure |
AFPERR_NOOP |
−5024 | Command not supported | Client using unsupported AFP command for this version |
Authentication Errors
| Constant | Value | Description | Common Cause |
|---|---|---|---|
AFPERR_ACCESS |
−5000 | Permission denied | Unix permissions or ACL mismatch |
AFPERR_BADUAM |
−5002 | UAM doesn’t exist | Missing authentication module in uam list |
AFPERR_BADVERS |
−5003 | Bad AFP version | Client/server version negotiation mismatch |
File Operation Errors
| Constant | Value | Description | Common Cause |
|---|---|---|---|
AFPERR_BUSY |
−5010 | File busy | Deny-mode conflict; another client holds the fork |
AFPERR_DENYCONF |
−5006 | Lock conflict | File synchronization lock held by another session |
AFPERR_DFULL |
−5008 | Disk full | Volume out of space |
AFPERR_EOF |
−5009 | End of file | Read past end of data or resource fork |
AFPERR_EXIST |
−5017 | Object exists | File or directory already present at target |
AFPERR_LOCK |
−5013 | Lock error | Byte-range lock held by another session |
AFPERR_MISC |
−5014 | Miscellaneous error | Internal server error; check logs for details |
AFPERR_NOOBJ |
−5018 | Object not found | Path doesn’t exist on disk or CNID mismatch |
AFPERR_OLOCK |
−5032 | Object locked | Read-only volume, virtual icon protection, or immutable file |
AFPERR_PARAM |
−5019 | Parameter error | Invalid bitmap, bad CNID, or malformed request |
AFPERR_VLOCK |
−5031 | Volume locked | Volume mounted read-only |
Password Errors
| Constant | Value | Description |
|---|---|---|
AFPERR_PWDSAME |
−5040 | Same password / can’t change |
AFPERR_PWDSHORT |
−5041 | Password too short |
AFPERR_PWDEXPR |
−5042 | Password expired |
AFPERR_PWDPOLCY |
−5046 | Password fails policy check |
Connection Issues
DSI Transport Layer
The DSI (Data Stream Interface) protocol is defined in include/atalk/dsi.h. Connection problems manifest through DSI state flags and error codes.
DSI Session States
The DSI struct tracks session state through these bit flags:
| Flag | Bit | Description |
|---|---|---|
DSI_DATA |
1 << 0 | DSI command received |
DSI_RUNNING |
1 << 1 | AFP command in progress |
DSI_SLEEPING |
1 << 2 | Client sleeping (FPZzz) |
DSI_EXTSLEEP |
1 << 3 | Extended sleep mode |
DSI_DISCONNECTED |
1 << 4 | Socket error, disconnected state |
DSI_DIE |
1 << 5 | SIGUSR1 received, shutting down in 5 minutes |
DSI_RECONSOCKET |
1 << 7 | New socket from primary reconnect |
DSI_RECONINPROG |
1 << 8 | Reconnection in progress |
DSI_AFP_LOGGED_OUT |
1 << 9 | Client called FPLogout |
Tickle Failures
The tickle field in the DSI struct counts keepalive failures. When tickles are not acknowledged, the server considers the client unreachable and enters DSI_DISCONNECTED state.
Diagnosis:
# Check for tickle-related messages
journalctl -u netatalk | grep -i "tickle\|disconnect\|DSI"
Common causes: - Network interruption between client and server - Client machine sleeping without sending FPZzz - Firewall or NAT gateway dropping idle TCP connections - VPN tunnel timeout
DSI Error Codes
DSI-level errors are defined in include/atalk/dsi.h:
| Constant | Value | Description |
|---|---|---|
DSIERR_OK |
0x0000 | Success |
DSIERR_SERVBUSY |
0xfbd1 | Server too busy |
DSIERR_SESSCLOS |
0xfbd0 | Session closed |
DSIERR_TOOMANY |
0xfbce | Too many sessions |
DSIERR_NOACK |
0xfbcd | No acknowledgment received |
Signal-Based Session Control
Signal handling in the AFP child process is configured by afp_over_dsi_sighandlers() in etc/afpd/afp_dsi.c:
| Signal | Handler | Effect |
|---|---|---|
SIGHUP |
afp_dsi_reload |
Reload configuration |
SIGUSR1 |
afp_dsi_timedown |
Graceful shutdown in 5 minutes; sets DSI_DIE flag |
SIGUSR2 |
afp_dsi_getmesg |
Deliver server message to client |
SIGURG |
afp_dsi_transfer_session |
Session transfer (reconnect support) |
SIGTERM |
afp_dsi_die |
Immediate clean exit |
SIGQUIT |
afp_dsi_die |
Immediate clean exit (same as SIGTERM) |
SIGINT |
afp_dsi_debug |
Toggle debug mode: dumps caches and enables maxdebug logging |
Triggering a diagnostic dump:
Sending SIGINT to an afpd child process triggers afp_dsi_debug, which on the next main loop iteration calls dircache_dump() and uuidcache_dump(), then toggles debug logging to a temporary file:
# Send SIGINT to an afpd child process
kill -INT <afpd_child_pid>
# Dircache dump written to: /tmp/dircache-dump-<pid>
# Debug log written to: /tmp/afpd.PID.XXXXXX
Session Shutdown Sequence
%%{ init: { 'themeVariables': { 'fontSize': '14px' }, 'flowchart': { 'nodeSpacing': 15, 'rankSpacing': 40 } } }%%
flowchart TD
A["Child exit triggered<br/>(SIGTERM / client disconnect / tickle timeout)"]:::red
A --> B["idle_worker_stop_signal_safe()"]:::orange
B --> C["idle_worker_log_stats()<br/>Log idle worker statistics"]:::yellow
C --> D["log_dircache_stat()<br/>Log dircache + AD cache + rfork cache stats"]:::blue
D --> E["dircache_rfork_shutdown()<br/>Free rfork LRU resources"]:::purple
E --> F["dsi_close()<br/>Close DSI connection"]:::green
classDef blue fill:#74b9ff,stroke:#333,rx:10,ry:10
classDef purple fill:#a29bfe,stroke:#333,rx:10,ry:10
classDef green fill:#55efc4,stroke:#333,rx:10,ry:10
classDef yellow fill:#ffeaa7,stroke:#333,rx:10,ry:10
classDef red fill:#ee5a5a,stroke:#333,rx:10,ry:10
classDef orange fill:#ff9f43,stroke:#333,rx:10,ry:10
This sequence is implemented in the child exit path of afp_over_dsi() in etc/afpd/afp_dsi.c.
CNID Database Issues
Overview
The CNID (Catalog Node ID) database maps filesystem paths to persistent 32-bit identifiers. Each AFP volume maintains its own CNID database. The CNID subsystem is implemented across libatalk/cnid/.
Corruption Symptoms
- Files appear with wrong icons or types
AFPERR_NOOBJ(−5018) errors for files that exist on disk- File operations become extremely slow (database lock contention)
- Aliases and bookmarks stop working (CNID changed)
The dbd Repair Utility
The dbd utility in bin/dbd/cmd_dbd.c scans AFP volumes and repairs their CNID databases. It must be run as root while netatalk is stopped.
Usage:
dbd [-cfFstuvV] <path to netatalk volume>
Options:
| Option | Description |
|---|---|
-s |
Scan only — treat volume as read-only, no modifications |
-f |
Force — delete and recreate the CNID database via cnid_wipe() |
-c |
Convert from adouble:v2 to adouble:ea format |
-F |
Specify alternate afp.conf location |
-t |
Show statistics while running |
-u |
Specify username (for volumes using the $u variable) |
-v |
Verbose output |
-V |
Show version |
Example repair workflow:
# Stop netatalk first
systemctl stop netatalk
# Scan without modifications (dry run)
dbd -sv /path/to/volume
# Rebuild CNID database from scratch
dbd -f /path/to/volume
# Restart netatalk
systemctl start netatalk
Supported CNID backends (validated in cmd_dbd.c main()):
- dbd — Berkeley DB daemon
- mysql — MySQL/MariaDB backend
- sqlite — SQLite backend
Warning: Using
-fchanges file IDs, which breaks existing aliases, bookmarks, and Spotlight results pointing to the volume.
CNID Database Repair Flow
%%{ init: { 'themeVariables': { 'fontSize': '14px' }, 'flowchart': { 'nodeSpacing': 15, 'rankSpacing': 40 } } }%%
flowchart TD
A["dbd invoked<br/><code>dbd -f /vol</code>"]:::yellow
A --> B["afp_config_parse()<br/>Load afp.conf"]:::blue
B --> C["cnid_init()<br/>Initialize CNID subsystem"]:::blue
C --> D["load_volumes()<br/>Locate target volume"]:::purple
D --> E["cnid_open()<br/>Open CNID database"]:::purple
E --> F{"-f flag set?"}:::grey
F -->|Yes| G["cnid_wipe()<br/>Delete existing database"]:::red
F -->|No| H["Proceed with existing DB"]:::green
G --> I["cmd_dbd_scanvol()<br/><i>cmd_dbd_scanvol.c</i>"]:::orange
H --> I
I --> J["Walk filesystem tree<br/>Verify/create CNID entries"]:::orange
J --> K["cnid_close()<br/>Finalize database"]:::green
classDef blue fill:#74b9ff,stroke:#333,rx:10,ry:10
classDef purple fill:#a29bfe,stroke:#333,rx:10,ry:10
classDef green fill:#55efc4,stroke:#333,rx:10,ry:10
classDef yellow fill:#ffeaa7,stroke:#333,rx:10,ry:10
classDef grey fill:#dfe6e9,stroke:#333,rx:10,ry:10
classDef red fill:#ee5a5a,stroke:#333,rx:10,ry:10
classDef orange fill:#ff9f43,stroke:#333,rx:10,ry:10
Cache Diagnostics
Netatalk maintains three tiers of caching, all with statistics logged at session shutdown via log_dircache_stat() in etc/afpd/dircache.c.
Three-Tier Cache Architecture
%%{ init: { 'themeVariables': { 'fontSize': '14px' }, 'flowchart': { 'nodeSpacing': 15, 'rankSpacing': 40 } } }%%
flowchart TB
subgraph "Tier 0 — Directory Cache"
T0["LRU/ARC cache of struct dir entries<br/>Maps CNID ↔ path, avoids DB lookups<br/><i>dircache.c</i>"]:::blue
end
subgraph "Tier 1 — AD Metadata Cache"
T1["FinderInfo, FileDatesI, AFPFileI, rlen<br/>Stored in struct dir fields<br/><i>ad_cache.c</i>"]:::purple
end
subgraph "Tier 2 — Resource Fork Data Cache"
T2["Complete rfork data in memory<br/>Budget-managed LRU<br/><i>ad_cache.c + dircache.c</i>"]:::green
end
T0 --> T1 --> T2
classDef blue fill:#74b9ff,stroke:#333,rx:10,ry:10
classDef purple fill:#a29bfe,stroke:#333,rx:10,ry:10
classDef green fill:#55efc4,stroke:#333,rx:10,ry:10
Tier 0: Directory Cache (dircache)
The directory cache in etc/afpd/dircache.c is an LRU or ARC cache mapping CNIDs and names to struct dir entries. It avoids recursive walks up the path hierarchy with repeated CNID database queries.
Statistics tracked in struct dircache_stat:
| Counter | Description |
|---|---|
lookups |
Total cache queries |
hits |
Entries found in T1/T2 (cached, instant return) |
ghost_hits |
Entries found in B1/B2 ghost lists (ARC mode only) |
misses |
Not found in any list |
added |
New entries inserted |
removed |
Entries explicitly removed |
expunged |
Stale entries invalidated during lookup validation |
evicted |
Entries removed by LRU eviction |
invalid_on_use |
Entries found but failed use-time validation |
Size constants (in etc/afpd/dircache.h):
| Constant | Value | Description |
|---|---|---|
MIN_DIRCACHE_SIZE |
1,024 | Minimum cache entries |
DEFAULT_DIRCACHE_SIZE |
65,536 | Default cache entries |
MAX_DIRCACHE_SIZE |
1,048,576 | Maximum cache entries |
DIRCACHE_FREE_QUANTUM |
256 | Entries freed per eviction cycle |
Diagnostic dump: Send SIGINT to an afpd child process to trigger afp_dsi_debug, which calls dircache_dump() on the next main loop iteration. This writes a full cache inventory to /tmp/dircache-dump-<pid>. Each line shows the entry’s DID, volume ID, flags, AD cache state, rfork cache state, and full path.
Tier 1: AppleDouble Metadata Cache (AD cache)
The AD cache stores parsed AppleDouble metadata directly in struct dir entries, avoiding repeated ad_metadata() disk reads. Implementation is in etc/afpd/ad_cache.c.
Cached fields per entry:
- dcache_finderinfo — 32 bytes of FinderInfo
- dcache_filedatesi — 16 bytes of file dates (with pre-computed mdate)
- dcache_afpfilei — 4 bytes of AFP file attributes
- dcache_rlen — resource fork length (−1 = not loaded, −2 = confirmed no AD)
Statistics:
| Counter | Variable | Description |
|---|---|---|
| Hits | ad_cache_hits |
Metadata served from cache |
| Misses | ad_cache_misses |
Required disk read via ad_metadata() |
| No-AD | ad_cache_no_ad |
Cached negative result (no AppleDouble file exists) |
Key function — ad_metadata_cached():
%%{ init: { 'themeVariables': { 'fontSize': '14px' }, 'flowchart': { 'nodeSpacing': 15, 'rankSpacing': 40 } } }%%
flowchart TD
A["ad_metadata_cached()<br/><i>ad_cache.c</i>"]:::blue
A --> B{"strict mode?"}:::grey
B -->|"strict=true<br/>(rename, delete)"| C["ostat() file"]:::purple
C --> D{"ctime/inode match<br/>cached values?"}:::grey
D -->|Yes| E{"dcache_rlen ≥ 0?"}:::grey
D -->|No| F["dir_modify(DCMOD_STAT)<br/>Invalidate + re-stat"]:::red
F --> G["Fall through to disk read"]:::orange
E -->|Yes| H["ad_rebuild_from_cache()<br/>→ ad_cache_hits++"]:::green
E -->|"rlen = −2"| I["No AD exists<br/>→ ad_cache_no_ad++"]:::yellow
B -->|"strict=false<br/>(enumerate, GetFilDirParms)"| J{"dcache_rlen ≥ 0?"}:::grey
J -->|Yes| H
J -->|No| G
G --> K["ad_metadata() disk read<br/>→ ad_cache_misses++"]:::salmon
K --> L["ad_store_to_cache()"]:::green
classDef blue fill:#74b9ff,stroke:#333,rx:10,ry:10
classDef purple fill:#a29bfe,stroke:#333,rx:10,ry:10
classDef green fill:#55efc4,stroke:#333,rx:10,ry:10
classDef yellow fill:#ffeaa7,stroke:#333,rx:10,ry:10
classDef grey fill:#dfe6e9,stroke:#333,rx:10,ry:10
classDef salmon fill:#fab1a0,stroke:#333,rx:10,ry:10
classDef red fill:#ee5a5a,stroke:#333,rx:10,ry:10
classDef orange fill:#ff9f43,stroke:#333,rx:10,ry:10
Tier 2: Resource Fork Data Cache (rfork cache)
The rfork cache stores complete resource fork data in memory, avoiding repeated disk reads during FPRead operations. Buffer management is in etc/afpd/ad_cache.c, while counters and budget globals are in etc/afpd/dircache.c.
Statistics (declared in etc/afpd/dircache.h):
| Counter | Description |
|---|---|
rfork_stat_lookups |
Total rfork cache queries |
rfork_stat_hits |
Data served from cache |
rfork_stat_misses |
Cache miss — required disk read |
rfork_stat_added |
New entries cached via rfork_cache_store_from_fd() |
rfork_stat_evicted |
Entries evicted by rfork_cache_evict_to_budget() |
rfork_stat_invalidated |
Entries invalidated (size mismatch, external change) |
rfork_stat_used_max |
High-water mark of memory usage |
Budget management:
| Variable | Description |
|---|---|
rfork_cache_budget |
Total memory budget in bytes |
rfork_cache_used |
Current memory usage |
rfork_max_entry_size |
Maximum size for a single cached fork |
rfork_lru_count |
Number of entries in the rfork LRU queue |
Self-healing: When rfork_cache_store_from_fd() detects a short read (fork size changed since Tier 1 was populated), it invalidates all cached AD metadata for that entry and returns −1. The next ad_metadata_cached() call re-reads from disk automatically.
Reading Cache Statistics from Logs
Cache statistics are logged at log_info level when each AFP child session terminates. Set at least afpd:info to capture them:
[Global]
log level = default:note, afpd:info
Example log output (ARC mode):
afpd[1234]: dircache(ARC): entries=52000 T1=28000 T2=24000 c=10 lookups=150000
hits=142500(95.0%) ghost=2000(1.3%) beneficial=96.3% misses=5500(3.7%) ...
afpd[1234]: AD cache: hits=120000 misses=5000 no_ad=25000
afpd[1234]: rfork cache: entries=300 peak=4MB(80.0%) budget=5MB
lookups=50000 hits=48000(96.0%) misses=2000(4.0%) ...
Healthy indicators:
- Dircache hit ratio > 90%
- AD cache hits ≫ misses (high no_ad is normal — many files lack AD metadata)
- Rfork hit ratio > 90% when the working set fits within budget
- Low expunged / invalid_on_use counts (high values suggest external filesystem modifications)
Permission Issues
Unix Permission Mapping
AFP maps Mac file permissions to Unix permissions via etc/afpd/unix.c. Permission errors typically return AFPERR_ACCESS (−5000).
Common causes:
- Volume path not readable/writable by the connecting user
- Group membership mismatch (user not in the volume’s group)
- umask too restrictive, preventing file creation
- ACL denying access despite Unix permissions allowing it
Diagnosis:
# Check effective permissions for a user
sudo -u <username> ls -la /path/to/volume/
sudo -u <username> touch /path/to/volume/testfile
# Check ACLs (if enabled)
getfacl /path/to/volume/
# Check user's group membership
id <username>
groups <username>
Configuration options in afp.conf:
[MyVolume]
path = /path/to/volume
; Permission control
file perm = 0644
directory perm = 0755
umask = 022
; Force ownership
force user = shareuser
force group = sharegroup
; Inherit permissions from parent directory
unix priv = yes
ACL Issues
ACL support is implemented in etc/afpd/acls.c with mapping definitions in etc/afpd/acl_mappings.h. When ACLs are misconfigured, clients see permission errors even when Unix permissions appear correct. Enable afpd:debug logging to trace ACL evaluation.
Authentication Issues
Authentication is handled by User Authentication Modules (UAMs) in etc/uams/, coordinated by etc/afpd/auth.c.
Login Failures
Symptoms: - “Authentication failed” errors - Valid credentials rejected - Guest access not working
Available UAM modules:
| Module | File | Description |
|---|---|---|
uams_dhx2_pam.c |
etc/uams/uams_dhx2_pam.c | DHX2 encrypted auth via PAM |
uams_dhx_pam.c |
etc/uams/uams_dhx_pam.c | DHX encrypted auth via PAM |
uams_pam.c |
etc/uams/uams_pam.c | Cleartext PAM auth |
uams_dhx2_passwd.c |
etc/uams/uams_dhx2_passwd.c | DHX2 via passwd file |
uams_dhx_passwd.c |
etc/uams/uams_dhx_passwd.c | DHX via passwd file |
uams_guest.c |
etc/uams/uams_guest.c | Guest (no auth) |
uams_gss.c |
etc/uams/uams_gss.c | Kerberos/GSSAPI |
uams_randnum.c |
etc/uams/uams_randnum.c | Random number challenge |
uams_passwd.c |
etc/uams/uams_passwd.c | Cleartext passwd |
Diagnosis:
# Check available UAMs at runtime
afpd -V 2>&1 | grep -i uam
# Verify user account
id <username>
getent passwd <username>
# Debug authentication (enable uams:debug logging)
# afp.conf: log level = default:note, uams:debug
journalctl -u netatalk | grep -i "login\|auth\|uam"
Common solutions:
[Global]
; Configure UAM modules
uam list = uams_dhx2.so uams_dhx.so
uam path = /usr/local/lib/netatalk
; Guest access
uam list = uams_guest.so
guest account = nobody
PAM configuration (create /etc/pam.d/netatalk):
#%PAM-1.0
auth required pam_unix.so
account required pam_unix.so
Volume Issues
Volume configuration and mounting is handled by etc/afpd/volume.c with option parsing in etc/afpd/afp_options.c.
Volumes Not Appearing
Symptoms: - Configured volumes don’t show up in Finder - Server connects but no volumes available
Diagnosis:
# Verify volume paths exist and are accessible
ls -la /path/to/volume/
stat /path/to/volume/
# Check configuration syntax
grep -n "path" /etc/netatalk/afp.conf
Common causes:
- Volume path doesn’t exist or isn’t accessible by the connecting user
- Configuration syntax error in afp.conf
- Volume restricted by valid users or invalid users options
- Volume type mismatch (e.g., ea = ad on a filesystem without xattr support)
Time Machine Backup Failures
Time Machine volume support is configured in etc/afpd/volume.c.
Symptoms: - “Time Machine couldn’t complete the backup” - Sparsebundle corruption errors
Configuration:
[TimeMachine]
path = /srv/timemachine
time machine = yes
vol size limit = 1000000
Diagnosis:
# Check Time Machine volume settings
grep -A 10 "time machine" /etc/netatalk/afp.conf
# Monitor disk space
df -h /srv/timemachine/
Spotlight Issues
Spotlight search support is implemented in etc/afpd/spotlight.c with protocol marshalling in etc/afpd/spotlight_marshalling.c. The search backend uses Tracker SPARQL via D-Bus. Spotlight is conditionally compiled with WITH_SPOTLIGHT.
Search Not Working
Symptoms: - Spotlight searches return no results - Search functionality unavailable
Diagnosis:
# Check if netatalk was built with Spotlight support
afpd -V 2>&1 | grep -i spotlight
# Check Tracker status (the indexing backend)
tracker3 status
# Verify Spotlight configuration
grep -i spotlight /etc/netatalk/afp.conf
# Debug Spotlight (enable sl:debug logging)
# afp.conf: log level = default:note, sl:debug
Configuration:
[MyVolume]
path = /path/to/volume
spotlight = yes
Repair indexing:
# Re-index a volume
tracker3 index --file /path/to/volume
Service Discovery Issues
Service discovery via Avahi/Bonjour (Zeroconf) allows Mac clients to find the server in Finder’s Network browser. Network configuration is handled in etc/afpd/afp_config.c. Zeroconf registration uses conditional compilation with USE_ZEROCONF (see the zeroconfname field in the DSI struct in include/atalk/dsi.h).
Server Not Appearing in Finder
Symptoms:
- Server doesn’t appear in Finder sidebar
- Manual connection via afp:// required
Diagnosis:
# Check Avahi service
systemctl status avahi-daemon
# Browse for AFP services
avahi-browse -at | grep afp
Configuration:
[Global]
zeroconf = yes
mimic model = Xserve
AppleDouble Issues
Overview
AppleDouble files store Mac-specific metadata (FinderInfo, file dates, resource forks) alongside Unix files. The library is in libatalk/adouble/, with caching in etc/afpd/ad_cache.c.
Corrupt AppleDouble or Extended Attribute Data
Symptoms:
- Files show wrong file type or creator codes
- Custom icons don’t display
- Resource forks appear empty or corrupt
- AFPERR_PARAM (−5019) when opening files
Diagnosis:
# Check AD metadata for a file (adouble:ea mode uses xattrs)
xattr -l /path/to/file
# List AppleDouble sidecar files (adouble:v2 mode)
ls -la /path/to/volume/.AppleDouble/
# Check for orphaned ._ files
find /path/to/volume -name "._*" -type f
Repair:
# Use dbd to scan and repair metadata consistency
dbd -sv /path/to/volume
# Convert adouble:v2 to adouble:ea
dbd -c /path/to/volume
EA vs v2 Inconsistencies
AppleDouble has two storage modes:
- adouble:ea (modern) — Metadata stored in filesystem extended attributes
- adouble:v2 (legacy) — Metadata stored in .AppleDouble/ sidecar directories
Mismatches between the configured mode and on-disk storage cause metadata to appear missing. The dbd -c option handles v2→ea conversion (checked at the DBD_FLAGS_V2TOEA path in bin/dbd/cmd_dbd.c).
Performance Diagnostics
Cache Hit Rate Analysis
Enable afpd:info logging and examine session-end statistics. Key metrics:
| Tier | Healthy Indicator | Warning Sign |
|---|---|---|
| Dircache | Hit ratio > 90% | High expunged count = external fs changes |
| AD cache | Hits ≫ misses | Low hits + high misses = working set too large |
| Rfork cache | Hit ratio > 90% | High evictions + low hits = budget too small |
IPC Hint Rates
Cache hints are distributed from the parent process to child processes via IPC. The process_cache_hints() function (declared in etc/afpd/dircache.h) processes incoming hints. Hint processing statistics are logged alongside the dircache stats at session end.
Flamegraph Analysis
For detailed CPU profiling, generate flamegraphs of afpd child processes:
# Linux: Record CPU samples
perf record -g -p <afpd_pid> -- sleep 30
perf script | stackcollapse-perf.pl | flamegraph.pl > afpd-flame.svg
# macOS: Using dtrace
sudo dtrace -x ustackframes=100 -n \
'profile-97 /pid == <afpd_pid>/ { @[ustack()] = count(); }' \
-o afpd.stacks
stackcollapse.pl afpd.stacks | flamegraph.pl > afpd-flame.svg
Key hotspots to investigate:
| Function | Source | Concern |
|---|---|---|
dircache_search_by_name() |
etc/afpd/dircache.c | Cache lookup overhead |
ad_metadata() / ad_open() |
libatalk/adouble/ | AD disk I/O (should be reduced by AD cache) |
ad_read() |
libatalk/adouble/ | Rfork disk I/O (should be reduced by rfork cache) |
getmetadata() / getfilparams() |
etc/afpd/file.c | File parameter assembly |
macOS-Specific Issues
PAM / XPC Abort (Fork After pthread_create)
On macOS, calling fork() after pthread_create() triggers an abort() from the XPC runtime. This manifests as:
afpd[1234]: SIGABRT received
Cause: macOS’s libsystem_xpc marks the process as unsafe for forking once threads are created. If PAM modules or other libraries spawn threads during authentication, subsequent fork() calls in the session handler will crash.
Workaround: The macOS PAM modules in etc/uams/ are designed to complete authentication before the session forks. If custom PAM modules are loaded that create threads, they must be isolated from the AFP session child.
Preview.app Stale Fork Handles
macOS Preview.app and Quick Look can hold open resource fork handles after generating thumbnails. If the file is subsequently modified or deleted by another client:
Symptoms:
- AFPERR_BUSY (−5010) when trying to delete or rename a file
- Stale fork references in the ofork table
Diagnosis:
# List open forks for an afpd process
lsof -p <afpd_pid> | grep <filename>
Resolution: The stale fork is cleaned up when the Preview.app client session closes or when the fork reference count reaches zero via of_closefork() in etc/afpd/ofork.c.
Source File Reference
Troubleshooting-Relevant Source Files
| File | Purpose |
|---|---|
| include/atalk/logger.h | Log levels, log types, LOG() macro |
| include/atalk/afp.h | AFP error codes (AFPERR_*), AFP command numbers |
| include/atalk/dsi.h | DSI protocol definitions, session state flags |
| libatalk/util/logger.c | setuplog(), make_log_entry() — logging implementation |
| libatalk/util/netatalk_conf.c | Config parsing including log level, log file |
| etc/afpd/main.c | Parent process signal handling, child dispatching |
| etc/afpd/afp_dsi.c | afp_over_dsi(), session signal handlers, shutdown path |
| etc/afpd/dircache.c | Directory cache, log_dircache_stat(), dircache_dump() |
| etc/afpd/ad_cache.c | AD metadata cache, rfork cache implementation |
| etc/afpd/dircache.h | Cache size constants, rfork stats declarations |
| bin/dbd/cmd_dbd.c | dbd CNID database repair utility |
| bin/dbd/cmd_dbd_scanvol.c | Volume scanning and CNID verification |
| etc/afpd/unix.c | Unix permission mapping |
| etc/afpd/acls.c | ACL support |
| etc/afpd/auth.c | Authentication coordination, UAM dispatch |
| etc/uams/ | User Authentication Module implementations |
| etc/afpd/volume.c | Volume mounting, configuration, Time Machine |
| etc/afpd/afp_options.c | Volume/connection option parsing |
| etc/afpd/afp_config.c | Network interface and Zeroconf configuration |
| etc/afpd/spotlight.c | Spotlight search via Tracker SPARQL |
| etc/afpd/spotlight_marshalling.c | Spotlight protocol marshalling |
| etc/afpd/fork.c | Fork operations, rfork cache integration in afp_read() |
| etc/afpd/ofork.c | Open fork management, of_closefork() |
| config/afp.conf.in | Default configuration template |
Footnotes
This is a mirror of the Netatalk GitHub Wiki
Last updated 2026-04-06