netatalk.io

Dev Docs Troubleshooting

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
debug6debug9 log_debug6log_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

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 -f changes 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