netatalk.io

Dev Docs Libatalk API

libatalk API Reference

Overview

The libatalk shared library provides the core APIs used by afpd, cnid_dbd, and CLI utilities. It is built from source modules in libatalk/ with public headers in include/atalk/. The build system at libatalk/meson.build links all sub-libraries into a single libatalk.so.19.

%%{ init: { 'themeVariables': { 'fontSize': '14px' }, 'flowchart': { 'nodeSpacing': 15, 'rankSpacing': 40 } } }%%
graph LR
    style A fill:#74b9ff,rx:10,ry:10
    style B fill:#a29bfe,rx:10,ry:10
    style C fill:#55efc4,rx:10,ry:10
    style D fill:#ffeaa7,rx:10,ry:10
    style E fill:#dfe6e9,rx:10,ry:10

    A["afpd / cnid_dbd / utilities"] --> B["libatalk.so.19"]
    B --> C["adouble · cnid · dsi"]
    B --> D["unicode · util · vfs"]
    B --> E["acl · compat"]

Key design points:


Module Reference

Subdirectory Purpose Key Header
libatalk/adouble/ AppleDouble file format handling (metadata, resource forks) adouble.h
libatalk/cnid/ CNID (Catalog Node ID) database interface with pluggable backends cnid.h
libatalk/dsi/ DSI (Data Stream Interface) protocol implementation dsi.h
libatalk/unicode/ Character encoding conversion (UCS-2, UTF-8, Mac charsets) unicode.h
libatalk/util/ General utilities: IPC, child management, logging, sockets, locking util.h
libatalk/vfs/ Virtual filesystem layer (permissions, EAs, ACLs) vfs.h
libatalk/acl/ ACL and UUID mapping (LDAP, local cache) acl.h
libatalk/compat/ Platform compatibility shims compat.h
libatalk/asp/ AppleTalk Session Protocol (optional, AppleTalk builds)
libatalk/atp/ AppleTalk Transaction Protocol (optional)
libatalk/nbp/ Name Binding Protocol (optional)
libatalk/netddp/ DDP networking (optional)
libatalk/dalloc/ Dynamic allocation for Spotlight (optional)

AppleDouble API

Header: include/atalk/adouble.h Implementation: libatalk/adouble/

The AppleDouble API handles Mac metadata storage on Unix filesystems. Two storage backends exist:

Backend Constant Storage
AppleDouble v2 AD_VERSION2 (0x00020000) ._ sidecar files in .AppleDouble/ directories
EA-based AD_VERSION_EA (0x00020002) Native extended attributes (org.netatalk.Metadata, org.netatalk.ResourceFork)

Core Structures

/* adouble.h — struct ad_entry */
struct ad_entry {
    off_t     ade_off;
    ssize_t   ade_len;
};

struct ad_fd {
    int          adf_fd;        /* -1: invalid, AD_SYMLINK: symlink */
    char         *adf_syml;
    int          adf_flags;
    adf_lock_t   *adf_lock;
    int          adf_refcount, adf_lockcount, adf_lockmax;
};

struct adouble {
    uint32_t           ad_magic;         /* AD_MAGIC (0x00051607) */
    uint32_t           ad_version;
    char               ad_filler[16];
    struct ad_entry    ad_eid[ADEID_MAX]; /* entry offset/length table */
    struct ad_fd       ad_data_fork;     /* data fork fd */
    struct ad_fd       ad_resource_fork; /* resource fork fd (EA or ._ file) */
    struct ad_fd      *ad_rfp;           /* pointer to resource fork fd */
    struct ad_fd      *ad_mdp;           /* pointer to metadata fd */
    int                ad_vers;          /* AD_VERSION2 or AD_VERSION_EA */
    int                ad_adflags;       /* ADFLAGS_* open flags */
    int                ad_refcount;      /* multiple forks may share one adouble */
    int                ad_data_refcount;
    int                ad_meta_refcount;
    int                ad_reso_refcount;
    off_t              ad_rlen;          /* resource fork length */
    size_t             valid_data_len;   /* bytes read into ad_data */
    char               ad_data[AD_DATASZ_MAX]; /* 1024-byte in-memory buffer */
};

Core Functions

Function File Signature Purpose
ad_init() ad_open.c void ad_init(struct adouble *ad, const struct vol *vol) Initialize adouble with volume defaults and entry offsets
ad_open() ad_open.c int ad_open(struct adouble *ad, const char *path, int adflags, ...) Open data/resource/metadata forks (variadic: mode_t for create)
ad_close() ad_open.c int ad_close(struct adouble *ad, int adflags) Close specified forks, decrement refcounts
ad_flush() ad_open.c int ad_flush(struct adouble *ad) Write modified metadata back to disk/EA
ad_read() ad_read.c ssize_t ad_read(struct adouble *ad, uint32_t eid, off_t off, char *buf, size_t buflen) Read from data or resource fork
ad_write() ad_write.c ssize_t ad_write(struct adouble *ad, uint32_t eid, off_t off, int end, const char *buf, size_t buflen) Write to data or resource fork
ad_lock() ad_lock.c int ad_lock(struct adouble *ad, uint32_t eid, int type, off_t off, off_t len, int fork) Set fcntl byte-range or share-mode lock
ad_unlock() ad_lock.c void ad_unlock(struct adouble *ad, int fork, int unlckbrl) Release locks for a fork
ad_tmplock() ad_lock.c int ad_tmplock(struct adouble *ad, uint32_t eid, int type, off_t off, off_t len, int fork) Temporary lock (not tracked in lock list)
ad_metadata() ad_open.c int ad_metadata(const char *path, int flags, struct adouble *ad) Read-only open + header parse (leaves fd open)
ad_entry() ad_open.c void *ad_entry(const struct adouble *ad, int eid) Get pointer to entry data within ad_data[] buffer
ad_refresh() ad_open.c int ad_refresh(const char *path, struct adouble *ad) Re-read header from disk

Entry IDs

/* adouble.h — AppleDouble entry IDs */
#define ADEID_DFORK         1   /* Data fork */
#define ADEID_RFORK         2   /* Resource fork */
#define ADEID_NAME          3   /* Real name */
#define ADEID_COMMENT       4   /* Comment */
#define ADEID_ICONBW        5   /* B&W icon */
#define ADEID_ICONCOL       6   /* Color icon */
#define ADEID_FILEI         7   /* v1 file info (replaced by FILEDATESI) */
#define ADEID_FILEDATESI    8   /* File dates: create/modify/backup/access */
#define ADEID_FINDERI       9   /* Finder info (32 bytes) */
#define ADEID_MACFILEI      10  /* Unused */
#define ADEID_PRODOSFILEI   11  /* ProDOS info */
#define ADEID_MSDOSFILEI    12  /* Unused */
#define ADEID_SHORTNAME     13  /* 8.3 short name */
#define ADEID_AFPFILEI      14  /* AFP file attributes (4 bytes) */
#define ADEID_DID           15  /* Directory ID */
#define ADEID_PRIVDEV       16  /* Netatalk private: device number */
#define ADEID_PRIVINO       17  /* Netatalk private: inode number */
#define ADEID_PRIVSYN       18  /* Netatalk private: DB sync stamp */
#define ADEID_PRIVID        19  /* Netatalk private: CNID */

Open Flags

/* adouble.h — ADFLAGS_* */
#define ADFLAGS_DF        (1<<0)   /* Data fork */
#define ADFLAGS_RF        (1<<1)   /* Resource fork */
#define ADFLAGS_HF        (1<<2)   /* Header (metadata) fork */
#define ADFLAGS_DIR       (1<<3)   /* Entry is a directory */
#define ADFLAGS_NOHF      (1<<4)   /* Not an error if no metadata fork */
#define ADFLAGS_NORF      (1<<5)   /* Not an error if no resource fork */
#define ADFLAGS_CHECK_OF  (1<<6)   /* Check for open forks from us and others */
#define ADFLAGS_SETSHRMD  (1<<7)   /* Set share mode with exclusive fcntl lock */
#define ADFLAGS_RDWR      (1<<8)   /* Open read/write */
#define ADFLAGS_RDONLY    (1<<9)   /* Open read-only */
#define ADFLAGS_CREATE    (1<<10)  /* Create if not existing */
#define ADFLAGS_EXCL      (1<<11)  /* Exclusive create */
#define ADFLAGS_TRUNC     (1<<12)  /* Truncate on open */

Refcounting

Multiple AFP forks can share one struct adouble. The macros ad_ref() and ad_unref() in adouble.h manage ad_refcount, while per-fork refcounts (ad_data_refcount, ad_meta_refcount, ad_reso_refcount) track individual fork opens.

/* adouble.h — ad_ref / ad_unref macros */
#define ad_ref(ad)   (ad)->ad_refcount++
#define ad_unref(ad) --((ad)->ad_refcount)

Lock Model

Locks are placed on the data fork file using fcntl() at high offsets starting at AD_FILELOCK_BASE:

/* adouble.h — share-mode lock offsets */
#define AD_FILELOCK_OPEN_WR        (AD_FILELOCK_BASE + 0)
#define AD_FILELOCK_OPEN_RD        (AD_FILELOCK_BASE + 1)
#define AD_FILELOCK_RSRC_OPEN_WR   (AD_FILELOCK_BASE + 2)
#define AD_FILELOCK_RSRC_OPEN_RD   (AD_FILELOCK_BASE + 3)
#define AD_FILELOCK_DENY_WR        (AD_FILELOCK_BASE + 4)
#define AD_FILELOCK_DENY_RD        (AD_FILELOCK_BASE + 5)
#define AD_FILELOCK_RSRC_DENY_WR   (AD_FILELOCK_BASE + 6)
#define AD_FILELOCK_RSRC_DENY_RD   (AD_FILELOCK_BASE + 7)
#define AD_FILELOCK_OPEN_NONE      (AD_FILELOCK_BASE + 8)
#define AD_FILELOCK_RSRC_OPEN_NONE (AD_FILELOCK_BASE + 9)

CNID API

Header: include/atalk/cnid.h Implementation: libatalk/cnid/cnid.c, libatalk/cnid/cnid_init.c

The CNID API provides a backend-agnostic interface for assigning and resolving persistent file/directory IDs. Function pointers in cnid_db dispatch to the registered backend.

Backend Registration

cnid_init() in cnid_init.c registers compiled backends via cnid_register() in cnid.c:

Backend Module Subdirectory
dbd cnid_dbd_module libatalk/cnid/dbd/
mysql cnid_mysql_module libatalk/cnid/mysql/
sqlite cnid_sqlite_module libatalk/cnid/sqlite/

Core Structure

/* cnid.h — cnid_db */
typedef struct _cnid_db {
    uint32_t      cnid_db_flags;
    struct vol   *cnid_db_vol;
    void         *cnid_db_private;       /* backend-specific data */

    /* Function pointer table — set by backend's cnid_open() */
    cnid_t (*cnid_add)(...);
    int    (*cnid_delete)(...);
    cnid_t (*cnid_get)(...);
    cnid_t (*cnid_lookup)(...);
    cnid_t (*cnid_nextid)(...);
    char  *(*cnid_resolve)(...);
    int    (*cnid_update)(...);
    void   (*cnid_close)(...);
    int    (*cnid_getstamp)(...);
    cnid_t (*cnid_rebuild_add)(...);
    int    (*cnid_find)(...);
    int    (*cnid_wipe)(...);
} cnid_db;

Core Functions

All dispatch functions in cnid.c wrap the function pointer call with signal blocking (when CNID_FLAG_BLOCK is set) and CNID validation:

Function Signature Purpose
cnid_open() struct _cnid_db *cnid_open(struct vol *vol, char *type, int flags) Open CNID database for a volume
cnid_close() void cnid_close(struct _cnid_db *db) Close and free CNID database
cnid_add() cnid_t cnid_add(cnid_db *cdb, const struct stat *st, cnid_t did, const char *name, size_t len, cnid_t hint) Add new entry, return assigned CNID
cnid_get() cnid_t cnid_get(cnid_db *cdb, cnid_t did, char *name, size_t len) Look up CNID by parent DID + name
cnid_lookup() cnid_t cnid_lookup(cnid_db *cdb, const struct stat *st, cnid_t did, char *name, size_t len) Look up by stat + DID + name
cnid_resolve() char *cnid_resolve(cnid_db *cdb, cnid_t *id, void *buffer, size_t len) Resolve CNID → parent DID + name
cnid_delete() int cnid_delete(cnid_db *cdb, cnid_t id) Remove entry
cnid_update() int cnid_update(cnid_db *cdb, cnid_t id, const struct stat *st, cnid_t did, char *name, size_t len) Update entry metadata
cnid_find() int cnid_find(cnid_db *cdb, const char *name, size_t namelen, void *buffer, size_t buflen) Search by name substring
cnid_wipe() int cnid_wipe(cnid_db *cdb) Delete all entries

Constants

/* cnid.h — CNID constants */
#define CNID_INVALID   0
#define CNID_START     17    /* First assignable CNID */

/* directory.h — reserved directory IDs */
#define DIRDID_ROOT_PARENT    htonl(1)  /* Parent of root directory */
#define DIRDID_ROOT           htonl(2)  /* Root directory */

/* cnid.h — backend flags */
#define CNID_FLAG_PERSISTENT   0x01  /* Backend supports DID persistence */
#define CNID_FLAG_MANGLING     0x02  /* Backend supports name mangling */
#define CNID_FLAG_SETUID       0x04  /* Set DB owner to parent folder owner */
#define CNID_FLAG_BLOCK        0x08  /* Block signals during updates */
#define CNID_FLAG_NODEV        0x10  /* Ignore device numbers (inode-only) */
#define CNID_FLAG_LAZY_INIT    0x20  /* Lazy initialization */
#define CNID_FLAG_INODE        0x80  /* Inode is authoritative in cnid_add */

DSI Protocol API

Header: include/atalk/dsi.h Implementation: libatalk/dsi/

The DSI (Data Stream Interface) API implements the AFP-over-TCP transport protocol. Every AFP command is carried inside a DSI frame with a 16-byte header.

DSI Structure

/* dsi.h — struct DSI */
typedef struct DSI {
    struct DSI *next;             /* multiple listening addresses */
    AFPObj   *AFPobj;
    int      statuslen;
    char     status[1400];
    char     *signature;
    struct dsi_block        header;
    struct sockaddr_storage server, client;
    struct itimerval        timer;
    int      tickle;              /* tickle count */
    int      in_write;            /* mid-write flag for signal safety */
    int      msg_request;         /* pending message to client */
    int      down_request;        /* pending SIGUSR1 shutdown */
    uint32_t attn_quantum, datasize, server_quantum;
    uint16_t serverID, clientID;
    uint8_t  *commands;           /* receive buffer */
    uint8_t  data[DSI_DATASIZ];   /* 64KB reply buffer */
    size_t   datalen, cmdlen;
    off_t    read_count, write_count;
    uint32_t flags;               /* DSI_SLEEPING, DSI_DISCONNECTED, etc. */
    int      socket;              /* AFP session socket */
    int      serversock;          /* listening socket */
    size_t   dsireadbuf;          /* readahead buffer size */
    char     *buffer, *start, *eof, *end; /* readahead pointers */
    pid_t    (*proto_open)(struct DSI *);
    void     (*proto_close)(struct DSI *);
} DSI;

DSI Header Format (16 bytes)

/* dsi.h — struct dsi_block */
struct dsi_block {
    uint8_t  dsi_flags;       /* DSIFL_REQUEST (0x00) or DSIFL_REPLY (0x01) */
    uint8_t  dsi_command;     /* DSIFUNC_* command type */
    uint16_t dsi_requestID;   /* request ID */
    union {
        uint32_t dsi_code;    /* error code (replies) */
        uint32_t dsi_doff;    /* data offset (requests) */
    } dsi_data;
    uint32_t dsi_len;         /* total data length */
    uint32_t dsi_reserved;
};

Core Functions

Function File Purpose
dsi_init() dsi_init.c Allocate and initialize DSI structure
dsi_free() dsi_init.c Free DSI structure
dsi_getsession() dsi_getsess.c Fork child, set up session
dsi_stream_read() dsi_stream.c Read bytes from socket
dsi_stream_write() dsi_stream.c Write bytes to socket
dsi_stream_send() dsi_stream.c Send DSI header + payload
dsi_stream_receive() dsi_stream.c Read DSI header + payload
dsi_cmdreply() dsi_cmdreply.c Send command reply
dsi_attention() dsi_attn.c Send attention packet
dsi_tickle() dsi_stream.c Send keepalive tickle
dsi_disconnect() dsi_stream.c Force disconnect

DSI Commands

/* dsi.h — DSIFUNC_* */
#define DSIFUNC_CLOSE   1   /* DSICloseSession */
#define DSIFUNC_CMD     2   /* DSICommand (AFP command) */
#define DSIFUNC_STAT    3   /* DSIGetStatus */
#define DSIFUNC_OPEN    4   /* DSIOpenSession */
#define DSIFUNC_TICKLE  5   /* DSITickle (keepalive) */
#define DSIFUNC_WRITE   6   /* DSIWrite */
#define DSIFUNC_ATTN    8   /* DSIAttention */

DSI Session State Flags

/* dsi.h — DSI state flags */
#define DSI_DATA             (1 << 0)  /* DSI command received */
#define DSI_RUNNING          (1 << 1)  /* AFP command received */
#define DSI_SLEEPING         (1 << 2)  /* Sleeping after FPZzz */
#define DSI_EXTSLEEP         (1 << 3)  /* Extended sleep */
#define DSI_DISCONNECTED     (1 << 4)  /* Disconnected after socket error */
#define DSI_DIE              (1 << 5)  /* SIGUSR1 shutdown in 5 minutes */
#define DSI_NOREPLY          (1 << 6)  /* In dsi_write, self-generating replies */
#define DSI_RECONSOCKET      (1 << 7)  /* New socket from primary reconnect */
#define DSI_RECONINPROG      (1 << 8)  /* Reconnect in progress */
#define DSI_AFP_LOGGED_OUT   (1 << 9)  /* Client called FPLogout */

IPC API

Header: include/atalk/server_ipc.h Implementation: libatalk/util/server_ipc.c

The IPC system uses Unix socketpairs between the afpd parent and child worker processes. Each message has a 14-byte header followed by a variable-length payload.

Wire Format

/* server_ipc.h — wire format constants */
#define IPC_HEADERLEN  14   /* cmd(2) + pid(4) + uid(4) + len(4) */
#define IPC_MAXMSGSIZE 90
/* server_ipc.c — ipc_header_t */
typedef struct ipc_header {
    uint16_t command;
    pid_t    child_pid;
    uid_t    uid;
    uint32_t len;
    char     *msg;
    int      afp_socket;
    uint16_t DSI_requestID;
} ipc_header_t;

IPC Commands

/* server_ipc.h — IPC commands */
#define IPC_DISCOLDSESSION   0  /* Disconnect old session (reconnect) */
#define IPC_GETSESSION       1  /* Register client ID */
#define IPC_STATE            2  /* Pass AFP session state */
#define IPC_VOLUMES          3  /* Pass list of open volumes */
#define IPC_LOGINDONE        4  /* Login completed notification */
#define IPC_CACHE_HINT       5  /* Cross-process dircache invalidation hint */

Core Functions

Function Direction Signature Purpose
ipc_child_write() Child → Parent int ipc_child_write(int fd, uint16_t command, size_t len, void *msg) Send IPC message from child
ipc_server_read() Parent reads int ipc_server_read(server_child_t *children, int fd) Read and dispatch IPC message
ipc_child_state() Child → Parent int ipc_child_state(AFPObj *obj, uint16_t state) Report session state change
ipc_send_cache_hint() Child → Parent int ipc_send_cache_hint(const AFPObj *obj, uint16_t vid, cnid_t cnid, uint8_t event) Send dircache invalidation hint
hint_flush_pending() Parent → Children void hint_flush_pending(server_child_t *children) Flush buffered hints to siblings
hint_buf_count() Parent only int hint_buf_count(void) Return number of buffered hints

Cache Hint Types

/* server_ipc.h — cache hint event types */
#define CACHE_HINT_REFRESH          0  /* Refresh stat + AD metadata */
#define CACHE_HINT_DELETE           1  /* Remove entry by CNID */
#define CACHE_HINT_DELETE_CHILDREN  2  /* Remove children + refresh parent */

Cache Hint Payload

/* server_ipc.h — ipc_cache_hint_payload (packed, 8 bytes) */
struct __attribute__((packed)) ipc_cache_hint_payload {
    uint8_t   event;       /* CACHE_HINT_REFRESH/DELETE/DELETE_CHILDREN */
    uint8_t   reserved;
    uint16_t  vid;         /* Volume ID — network byte order */
    cnid_t    cnid;        /* CNID — network byte order */
};

Hint Batching

The parent buffers hints from children in a static array of HINT_BUF_SIZE (128) entries. Flushing is poll-driven: when hint_buf_count() > 0, the main loop uses HINT_FLUSH_INTERVAL_MS (50ms) as poll timeout, then calls hint_flush_pending() which performs priority sorting (REFRESH first, DELETE, DELETE_CHILDREN last) and PIPE_BUF-safe chunked writes to each sibling’s afpch_hint_fd.

%%{ init: { 'themeVariables': { 'fontSize': '14px' }, 'flowchart': { 'nodeSpacing': 15, 'rankSpacing': 40 } } }%%
graph LR
    style A fill:#74b9ff,rx:10,ry:10
    style B fill:#ffeaa7,rx:10,ry:10
    style C fill:#a29bfe,rx:10,ry:10
    style D fill:#55efc4,rx:10,ry:10
    style E fill:#74b9ff,rx:10,ry:10

    A["Child afpd<br/>ipc_send_cache_hint()"] -->|IPC socket| B["Parent afpd<br/>hint_buf[128]"]
    B -->|50ms flush| C["hint_flush_pending()<br/>sort + serialize"]
    C -->|hint pipe| D["Sibling A<br/>process_cache_hints()"]
    C -->|hint pipe| E["Sibling B<br/>process_cache_hints()"]

Child Management API

Header: include/atalk/server_child.h Implementation: libatalk/util/server_child.c

Manages AFP session child processes using a hash table (PID-indexed, 32 buckets).

Core Structures

/* server_child.h — afp_child_t */
typedef struct afp_child {
    pid_t     afpch_pid;         /* worker process PID */
    uid_t     afpch_uid;         /* client user ID */
    int       afpch_valid;       /* 1 if we have a client ID */
    int       afpch_killed;      /* 1 if SIGTERM already sent */
    uint32_t  afpch_boottime;    /* client boot time */
    time_t    afpch_logintime;   /* time child was added */
    uint32_t  afpch_idlen;       /* client ID length */
    char     *afpch_clientid;    /* client ID (from Mac) */
    int       afpch_ipc_fd;      /* IPC socketpair fd */
    int       afpch_hint_fd;     /* cache hint pipe fd */
    char     *afpch_hostname;    /* server hostname child connected to */
    int16_t   afpch_state;       /* AFP session state */
    char     *afpch_volumes;     /* mounted volume names */
    struct afp_child **afpch_prevp;
    struct afp_child *afpch_next;
} afp_child_t;

/* server_child.h — server_child_t */
typedef struct {
    pthread_mutex_t servch_lock;
    int             servch_count;      /* current active session count */
    int             servch_nsessions;  /* max allowed sessions */
    afp_child_t    *servch_table[CHILD_HASHSIZE]; /* 32-bucket hash table */
} server_child_t;

Core Functions

Function Signature Purpose
server_child_alloc() server_child_t *server_child_alloc(int connections) Allocate child table with session limit
server_child_add() afp_child_t *server_child_add(server_child_t *, pid_t, int ipc_fd, int hint_fd) Register a new child process
server_child_remove() int server_child_remove(server_child_t *, pid_t) Remove and free child, returns IPC fd
server_child_resolve() afp_child_t *server_child_resolve(server_child_t *, id_t pid) Find child by PID
server_child_kill() void server_child_kill(server_child_t *, int sig) Send signal to all children
server_child_free() void server_child_free(server_child_t *) Free entire table
server_child_transfer_session() int server_child_transfer_session(...) Transfer socket to disconnected session (reconnect)
server_child_kill_one_by_id() void server_child_kill_one_by_id(...) Kill child matching client ID (reboot detection)
server_child_login_done() void server_child_login_done(...) Mark child as authenticated

Hash Function

/* server_child.c — PID hash */
#define HASH(i) ((((i) >> 8) ^ (i)) & (CHILD_HASHSIZE - 1))

Character Conversion API

Header: include/atalk/unicode.h Implementation: libatalk/unicode/charcnv.c, libatalk/unicode/

Handles charset conversion between Mac encodings, UTF-8, and Unix filesystem encodings.

Charset Types

/* unicode.h — charset_t */
typedef enum {
    CH_UCS2     = 0,  /* UCS-2 (internal pivot format) */
    CH_UTF8     = 1,  /* UTF-8 */
    CH_MAC      = 2,  /* Volume's Mac charset (e.g., MacRoman) */
    CH_UNIX     = 3,  /* System default charset */
    CH_UTF8_MAC = 4   /* UTF-8 with Mac decomposition */
} charset_t;

Charset Registration

/* unicode.h — struct charset_functions */
struct charset_functions {
    const char *name;
    const long kTextEncoding;
    size_t (*pull)(void *, char **inbuf, size_t *inbytesleft,
                   char **outbuf, size_t *outbytesleft);
    size_t (*push)(void *, char **inbuf, size_t *inbytesleft,
                   char **outbuf, size_t *outbytesleft);
    uint32_t flags;
    const char *iname;
    struct charset_functions *prev, *next;
};

Core Functions

Function File Purpose
init_iconv() charcnv.c Initialize conversion handles
convert_charset() charcnv.c Convert between any two charsets with flags
convert_string() charcnv.c Simple charset conversion
convert_string_allocate() charcnv.c Convert with auto-allocated output buffer
charset_precompose() charcnv.c Unicode precomposition
charset_decompose() charcnv.c Unicode decomposition
atalk_iconv_open() charcnv.c Open iconv conversion handle
atalk_iconv() charcnv.c Perform iconv conversion
atalk_register_charset() charcnv.c Register custom charset module

Conversion Flags

/* unicode.h — CONV_* flags */
#define CONV_IGNORE         (1<<0)  /* Return first convertible characters */
#define CONV_ESCAPEHEX      (1<<1)  /* Escape unconvertible as :[UCS2HEX] */
#define CONV_ESCAPEDOTS     (1<<2)  /* Escape leading dots as :2600 */
#define CONV_UNESCAPEHEX    (1<<3)  /* Reverse hex escaping */
#define CONV_TOUPPER        (1<<4)  /* Convert to UPPER case */
#define CONV_TOLOWER        (1<<5)  /* Convert to lower case */
#define CONV_PRECOMPOSE     (1<<6)  /* Unicode precomposition */
#define CONV_DECOMPOSE      (1<<7)  /* Unicode decomposition */
#define CONV_FORCE          (1<<8)  /* Force conversion */
#define CONV__EILSEQ        (1<<9)  /* Replace illegal sequences with '_' */

Logging API

Header: include/atalk/logger.h Implementation: libatalk/util/logger.c

LOG Macro

The primary logging interface is the LOG() macro in logger.h, which checks the configured log level before calling make_log_entry():

/* logger.h — LOG macro */
#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)

Log Levels

/* logger.h — enum loglevels */
enum loglevels {
    log_none,       /* disabled */
    log_severe,     /* system errors */
    log_error,      /* serious errors */
    log_warning,    /* warnings */
    log_note,       /* important info */
    log_info,       /* general info */
    log_debug,      /* debug info */
    log_debug6,     /* verbose debug */
    log_debug7,     /* very verbose */
    log_debug8,     /* extremely verbose */
    log_debug9,     /* maximum verbosity */
    log_maxdebug
};

Log Types

/* logger.h — enum logtypes */
enum logtypes {
    logtype_default,  /* default (used when no other specified) */
    logtype_logger,   /* general logger */
    logtype_cnid,     /* CNID backends */
    logtype_afpd,     /* AFP daemon */
    logtype_dsi,      /* DSI layer */
    logtype_atalkd,   /* AppleTalk daemon */
    logtype_papd,     /* PAP daemon */
    logtype_uams,     /* UAM modules */
    logtype_fce,      /* File Change Events */
    logtype_ad,       /* AppleDouble */
    logtype_sl,       /* Spotlight */
    logtype_end_of_list_marker
};

Configuration

/* logger.h — setup functions */
void setuplog(const char *loglevel, const char *logfile, const bool log_us_timestamp);
void set_processname(const char *processname);

VFS API

Header: include/atalk/vfs.h Implementation: libatalk/vfs/

The VFS layer provides an indirection table for filesystem operations, enabling different backends for permissions, file operations, and extended attributes.

VFS Operations Table

/* vfs.h — struct vfs_ops */
struct vfs_ops {
    vfs_validupath_fn      vfs_validupath;     /* Validate Unix path */
    vfs_chown_fn           vfs_chown;          /* Change ownership */
    vfs_renamedir_fn       vfs_renamedir;      /* Rename directory */
    vfs_deletecurdir_fn    vfs_deletecurdir;   /* Delete current directory */
    vfs_setfilmode_fn      vfs_setfilmode;     /* Set file permissions */
    vfs_setdirmode_fn      vfs_setdirmode;     /* Set directory permissions */
    vfs_setdirunixmode_fn  vfs_setdirunixmode; /* Set Unix dir permissions */
    vfs_setdirowner_fn     vfs_setdirowner;    /* Set directory owner */
    vfs_deletefile_fn      vfs_deletefile;     /* Delete file */
    vfs_renamefile_fn      vfs_renamefile;     /* Rename file */
    vfs_copyfile_fn        vfs_copyfile;       /* Copy file */
    /* ACL operations (conditional compilation) */
    vfs_remove_acl_fn      vfs_remove_acl;     /* Remove ACL */
    /* Extended Attribute operations */
    vfs_ea_getsize_fn      vfs_ea_getsize;     /* Get EA size */
    vfs_ea_getcontent_fn   vfs_ea_getcontent;  /* Get EA content */
    vfs_ea_list_fn         vfs_ea_list;        /* List EAs */
    vfs_ea_set_fn          vfs_ea_set;         /* Set EA */
    vfs_ea_remove_fn       vfs_ea_remove;      /* Remove EA */
};

Volumes chain up to VFS_MODULES_MAX (3) modules via vol->vfs_modules[]. The function initvol_vfs() initializes VFS for a volume based on its configuration.


Extended Attributes API

Header: include/atalk/ea.h Implementation: libatalk/vfs/

Two EA backends exist:

Backend Flag Storage
Native (sys) AFPVOL_EA_SYS Platform xattr syscalls
AppleDouble (ad) AFPVOL_EA_AD .AppleDouble/ directory files

EA Names

/* ea.h — Extended Attribute xattr names */
#define AD_EA_META "org.netatalk.Metadata"    /* AppleDouble metadata xattr */
#define AD_EA_RESO "org.netatalk.ResourceFork" /* Resource fork xattr (Linux) */
/* On macOS: */
/* #define AD_EA_RESO "com.apple.ResourceFork" */

Native EA Wrappers

Function Purpose
sys_getxattr() Get xattr value
sys_setxattr() Set xattr value
sys_listxattr() List xattr names
sys_removexattr() Remove xattr
sys_copyxattr() Copy all xattrs between files

VFS-Indirected EA Functions

Function Purpose
get_easize() Get EA size via VFS
get_eacontent() Get EA content via VFS
list_eas() List EAs via VFS
set_ea() Set EA via VFS
remove_ea() Remove EA via VFS

File Change Event (FCE) API

Header: include/atalk/fce_api.h

FCE sends UDP notifications for filesystem events to external listeners.

Event Types

/* fce_api.h — FCE event types */
#define FCE_FILE_MODIFY     1
#define FCE_FILE_DELETE     2
#define FCE_DIR_DELETE      3
#define FCE_FILE_CREATE     4
#define FCE_DIR_CREATE      5
#define FCE_FILE_MOVE       6
#define FCE_DIR_MOVE        7
#define FCE_LOGIN           8
#define FCE_LOGOUT          9

Core Functions

/* fce_api.h */
int fce_register(const AFPObj *obj, fce_ev_t event, const char *path,
                 const char *oldpath);
int fce_add_udp_socket(const char *target);   /* "IP" or "IP:Port" */
int fce_set_events(const char *events);        /* "fmod,fdel,ddel,fcre,dcre" */

Default UDP port: FCE_DEFAULT_PORT (12250).


Error Handling

Header: include/atalk/errchk.h

A macro-based error handling framework using goto cleanup pattern. Functions following this pattern have a consistent structure:

int func(void)
{
    EC_INIT;                    /* int ret = 0; */

    EC_ZERO( some_call() );    /* goto cleanup if != 0 */
    EC_NULL( some_ptr() );     /* goto cleanup if NULL */
    EC_NEG1( some_fd() );      /* goto cleanup if == -1 */

    EC_STATUS(0);               /* ret = 0 */

EC_CLEANUP:                     /* cleanup: */
    /* cleanup code here */
    EC_EXIT;                    /* return ret; */
}

Macro Reference

Macro Condition Logging
EC_INIT Declares int ret = 0
EC_ZERO(a) a != 0 No
EC_ZERO_LOG(a) a != 0 Logs errno
EC_ZERO_LOGSTR(a, fmt, ...) a != 0 Logs custom message
EC_ZERO_LOG_ERR(a, err) a != 0 Logs errno, sets ret = err
EC_NEG1(a) a == -1 No
EC_NEG1_LOG(a) a == -1 Logs errno
EC_NULL(a) a == NULL No
EC_NULL_LOG(a) a == NULL Logs errno
EC_FAIL Always ret = -1; goto cleanup
EC_FAIL_LOG(fmt, ...) Always Logs message, ret = -1
EC_STATUS(a) Sets ret = a
EC_EXIT_STATUS(a) Sets ret = a; goto cleanup
EC_CLEANUP Label alias: cleanup:
EC_EXIT return ret

Global Structures

Header: include/atalk/globals.h

AFPObj

The central per-session object, passed to all AFP command handlers:

/* globals.h — AFPObj */
typedef struct AFPObj {
    char *cmdlineconfigfile;
    int cmdlineflags;
    int proto;
    const void *signature;
    struct DSI *dsi;
    void *handle;
    struct afp_options options;
    char *Obj, *Type, *Zone;
    dictionary *iniconfig;
    char username[MAXUSERLEN];          /* 256 */
    char oldtmp[AFPOBJ_TMPSIZ + 1];    /* MAXPATHLEN + 1 */
    char newtmp[AFPOBJ_TMPSIZ + 1];
    void *uam_cookie;                   /* UAM authentication state */
    struct session_info sinfo;
    uid_t uid;                          /* client login user ID */
    uid_t euid;                         /* effective process user ID */
    int ipc_fd;                         /* IPC socketpair with parent */
    int hint_fd;                        /* cache hint pipe from parent */
    gid_t *groups;
    int ngroups;
    int afp_version;
    int cnx_cnt, cnx_max;
    void (*logout)(void);
    void (*exit)(int);
    int (*reply)(void *, int);
    int (*attention)(void *, AFPUserBytes);
    int fce_version;
    char *fce_ign_names;
    char *fce_ign_directories;
    char *fce_notify_script;
    struct sl_ctx *sl_ctx;
} AFPObj;

afp_options

/* globals.h — struct afp_options (key fields) */
struct afp_options {
    int connections;              /* Max AFP connections */
    int tickleval;                /* Tickle interval */
    int timeout;                  /* Session timeout */
    int flags;                    /* OPTION_* flags */
    int dircachesize;             /* Directory cache size */
    int dircache_validation_freq; /* Validate every Nth access */
    int dircache_rfork_budget;    /* Rfork cache budget (KB, 0=disabled) */
    int dircache_rfork_maxentry;  /* Max rfork per entry (KB) */
    int sleep;                    /* Max sleep time (tickles) */
    int disconnected;             /* Max disconnected time (tickles) */
    unsigned int tcp_sndbuf, tcp_rcvbuf;
    uint32_t server_quantum;     /* DSI server quantum */
    char *hostname;
    char *listen, *interfaces, *port;
    char *Cnid_srv, *Cnid_port;
    char *configfile;
    char *uampath, *fqdn;
    char *unixcodepage, *maccodepage;
    charset_t maccharset, unixcharset;
    /* ... more fields ... */
};

Directory and Path Structures

/* directory.h — struct dir (key fields) */
struct dir {
    bstring     d_fullpath;          /* complete unix path */
    bstring     d_m_name;            /* mac name */
    bstring     d_u_name;            /* unix name */
    void       *dcache_rfork_buf;    /* Tier 2: cached rfork data */
    qnode_t    *rfork_lru_node;      /* rfork LRU position */
    time_t      dcache_ctime;        /* cached inode ctime */
    ino_t       dcache_ino;          /* cached inode number */
    time_t      dcache_mtime;        /* cached st_mtime */
    off_t       dcache_size;         /* cached file size */
    off_t       dcache_rlen;         /* cached resource fork length */
    int         d_flags;             /* DIRF_* flags */
    cnid_t      d_pdid;              /* parent CNID */
    cnid_t      d_did;               /* CNID */
    uint32_t    d_offcnt;            /* offspring count */
    uint8_t     arc_list;            /* ARC list: 0=NONE,1=T1,2=T2,3=B1,4=B2 */
    uint8_t     dcache_afpfilei[4];  /* Tier 1: AFP attributes */
    uint8_t     dcache_finderinfo[32]; /* Tier 1: Finder info */
    uint8_t     dcache_filedatesi[16]; /* Tier 1: file dates (served values) */
};

/* directory.h — struct path */
struct path {
    int         m_type;     /* mac name type */
    char       *m_name;     /* mac name */
    char       *u_name;     /* unix name */
    cnid_t      id;         /* file ID */
    struct dir *d_dir;      /* directory entry (NULL for files) */
    int         st_valid;   /* stat validity flag */
    int         st_errno;
    struct stat st;
};

Directory Flags

/* directory.h — DIRF_* flags */
#define DIRF_FSMASK    (3<<0)
#define DIRF_NOFS      (0<<0)
#define DIRF_UFS       (1<<1)
#define DIRF_ISFILE    (1<<3)   /* Cached file, not a directory */
#define DIRF_OFFCNT    (1<<4)   /* Offspring count is valid */
#define DIRF_CNID      (1<<5)   /* Renumerate ID */
#define DIRF_ARC_GHOST (1<<6)   /* ARC ghost entry (in B1/B2) */

Cache APIs

These APIs live in etc/afpd/ (not in libatalk), but operate on libatalk’s struct dir and struct adouble.

AD Cache (Tier 1: Metadata)

Header: etc/afpd/ad_cache.h Implementation: etc/afpd/ad_cache.c

Caches AppleDouble metadata fields (FinderInfo, FileDatesI, AFPFileI, resource fork length) inside struct dir fields, avoiding repeated getxattr() / disk reads.

Function Signature Purpose
ad_store_to_cache() void ad_store_to_cache(struct adouble *adp, struct dir *cached) Copy AD fields to cache; pre-compute served mdate
ad_rebuild_from_cache() void ad_rebuild_from_cache(struct adouble *adp, const struct dir *cached) Populate struct adouble from cached fields
ad_metadata_cached() int ad_metadata_cached(const char *name, int flags, struct adouble *adp, const struct vol *vol, struct dir *dir, bool strict, struct stat *recent_st) Unified AD access: cache-first or strict validation

Cache state in dcache_rlen:

Value Meaning
>= 0 AD loaded; cached resource fork length
-1 Not yet loaded (triggers ad_metadata() on first access)
-2 Confirmed no AD exists (avoids repeated ENOENT)

Statistics: ad_cache_hits, ad_cache_misses, ad_cache_no_ad.

Dircache (ARC Algorithm)

Header: etc/afpd/dircache.h Implementation: etc/afpd/dircache.c

An ARC (Adaptive Replacement Cache) implementation for directory/file entries. Uses a hash table with DID/name index and queue index for LRU eviction.

Function Signature Purpose
dircache_init() int dircache_init(int reqsize) Initialize with requested size
dircache_add() int dircache_add(const struct vol *, struct dir *) Add entry to cache
dircache_remove() void dircache_remove(const struct vol *, struct dir *, int flag) Remove with flag control
dircache_search_by_did() struct dir *dircache_search_by_did(const struct vol *, cnid_t) Look up by CNID
dircache_search_by_name() struct dir *dircache_search_by_name(const struct vol *, const struct dir *, char *, size_t) Look up by parent + name
dircache_promote() void dircache_promote(struct dir *dir) Promote entry in ARC (on access)
dircache_remove_children() int dircache_remove_children(const struct vol *, struct dir *) Remove all children of a directory
process_cache_hints() void process_cache_hints(AFPObj *obj) Process incoming IPC cache hints
dircache_dump() void dircache_dump(void) Dump cache to /tmp/dircache.PID (SIGINT)
log_dircache_stat() void log_dircache_stat(void) Log cache statistics

Size constants:

/* dircache.h — size bounds */
#define MIN_DIRCACHE_SIZE     1024      /* 1K minimum */
#define DEFAULT_DIRCACHE_SIZE 65536     /* 64K default */
#define MAX_DIRCACHE_SIZE     1048576   /* 1M maximum */
#define DIRCACHE_FREE_QUANTUM 256       /* eviction batch size */

Resource Fork Cache (Tier 2: Data)

Header: etc/afpd/ad_cache.h (rfork functions) Implementation: etc/afpd/ad_cache.c (rfork functions), etc/afpd/dircache.c (budget globals, LRU list)

Caches small resource fork data in memory to avoid repeated disk/xattr reads. Budget-managed with a dedicated LRU list separate from the main dircache.

Function Signature Purpose
rfork_cache_store_from_fd() int rfork_cache_store_from_fd(struct dir *entry, struct adouble *adp, int eid) Read rfork data via ad_read() and cache in dcache_rfork_buf
rfork_cache_free() void rfork_cache_free(struct dir *entry) Free buffer, remove from LRU, update budget
rfork_cache_evict_to_budget() void rfork_cache_evict_to_budget(size_t needed) Evict LRU entries until budget accommodates needed bytes

Budget management globals (defined in dircache.c):

Variable Type Purpose
rfork_cache_used size_t Current total bytes in rfork cache
rfork_cache_budget size_t Configurable limit (bytes), 0 = disabled
rfork_max_entry_size size_t Per-entry limit (bytes)
rfork_lru_count unsigned int Entries currently in rfork LRU list
rfork_lru q_t * Dedicated LRU list for rfork cache entries

Configuration (in globals.h):

/* globals.h — rfork cache hard caps */
#define RFORK_BUDGET_MAX_KB   (10 * 1024 * 1024)   /* 10 GB in KB */
#define RFORK_ENTRY_MAX_KB    (10 * 1024)           /* 10 MB in KB */

Configured via afp_options.dircache_rfork_budget and afp_options.dircache_rfork_maxentry (both in KB).

Statistics:

Counter Purpose
rfork_stat_lookups Total rfork cache lookups
rfork_stat_hits Cache hits (data served from memory)
rfork_stat_misses Cache misses (fell through to disk)
rfork_stat_added Entries successfully cached
rfork_stat_evicted Entries evicted by LRU
rfork_stat_invalidated Entries invalidated by stat change
rfork_stat_used_max High-water mark of bytes used
%%{ init: { 'themeVariables': { 'fontSize': '14px' }, 'flowchart': { 'nodeSpacing': 15, 'rankSpacing': 40 } } }%%
graph TD
    style T1 fill:#74b9ff,rx:10,ry:10
    style T2 fill:#a29bfe,rx:10,ry:10
    style DC fill:#55efc4,rx:10,ry:10
    style DISK fill:#dfe6e9,rx:10,ry:10
    style DIR fill:#ffeaa7,rx:10,ry:10

    DIR["struct dir<br/>dcache_finderinfo[32]<br/>dcache_filedatesi[16]<br/>dcache_afpfilei[4]<br/>dcache_rlen"] --> T1
    DIR --> T2

    T1["Tier 1: AD Metadata Cache<br/>ad_metadata_cached()<br/>ad_store_to_cache()<br/>ad_rebuild_from_cache()"]
    T2["Tier 2: Rfork Data Cache<br/>rfork_cache_store_from_fd()<br/>dcache_rfork_buf<br/>Budget-managed LRU"]

    DC["Dircache (ARC)<br/>dircache_search_by_did()<br/>dircache_search_by_name()"] --> DIR

    T1 -->|miss| DISK["Disk / xattr<br/>ad_metadata()<br/>ad_read()"]
    T2 -->|miss| DISK

Footnotes

This is a mirror of the Netatalk GitHub Wiki

Last updated 2026-04-06