Dev Docs CNID Database System
CNID Database System
Audited against Netatalk
d63155e13on 2026-10-10. This describes the current source checkout; older releases may differ.
Role and supported backends
Catalog Node IDs identify files and directories independently of their current names. The server reconciles parent/name and device/inode information with persistent records; IDs should survive supported AFP renames and moves, but persistence is not guaranteed after database resets or arbitrary external replacement of files.
| Backend | Availability | Access model | Source |
|---|---|---|---|
sqlite |
Always built; default | Embedded connection in the AFP worker | libatalk/cnid/sqlite/cnid_sqlite.c |
mysql |
Optional Meson with-cnid-backends selection |
Worker connects to external MySQL/MariaDB | libatalk/cnid/mysql/cnid_mysql.c |
There is no dbd/Berkeley DB backend in this checkout. cnid_metad, cnid_dbd, their RPC protocol, and BDB environment parameters are obsolete for this source tree. The command named dbd remains as an offline maintenance utility.
Dispatch layer
include/atalk/cnid.h defines cnid_db, cnid_module, and cnid_open_args. libatalk/cnid/cnid_init.c registers SQLite unconditionally and MySQL under CNID_BACKEND_MYSQL. Modules are statically linked implementations with function-pointer dispatch, rather than dynamically loaded plugins.
libatalk/cnid/cnid.c validates inputs and dispatches to a backend. cnid_scheme_registered() checks a configured scheme without opening it.
| API | Operation |
|---|---|
cnid_open() / cnid_close() |
Open/close a volume’s backend handle |
cnid_add() |
Find or allocate an ID, optionally using a valid metadata hint |
cnid_get() |
Lookup by parent DID and name |
cnid_lookup() |
Reconcile stat identity with parent DID and name |
cnid_resolve() |
Resolve an ID to its parent DID and name in the caller’s buffer |
cnid_update() / cnid_delete() |
Change or remove a record |
cnid_getstamp() |
Read the database stamp used to validate stored hints |
cnid_find() / cnid_find_scoped() |
Name search, optionally limited to a directory subtree |
cnid_wipe() |
Reset persistent records |
cnid_find() and cnid_find_scoped() take bool *more_available in addition to the result buffer. The backend cnid_find callback also takes scope_did. Callers must distinguish truncation from a complete result set. The wrapper requires at least CNID_FIND_MIN_RESULTS (100) result slots.
CNIDs and parent DIDs passed through the AFP-facing API are in network byte order; SQL row IDs are host-order numeric values. CNID_INVALID is 0, CNID_START is 17, and directory IDs 1 and 2 have reserved root roles. Current capability flags are CNID_FLAG_PERSISTENT and CNID_FLAG_NODEV; older flags and cnid_rebuild_add/cnid_nextid callbacks are absent.
Error contract
CNID_ERR_* values occupy the unsigned high range stored in errno. Compare them through CNID_ERRNO(), not a signed literal comparison.
| Condition | Constant | Caller implication |
|---|---|---|
| No matching record | CNID_ERR_NOTFOUND |
May mean a genuinely absent object |
| Temporary contention | CNID_ERR_BUSY |
Backend did not establish existence |
| Corrupt/unusable result | CNID_ERR_CORRUPT |
Backend failure, not an absent object |
| Database unavailable | CNID_ERR_DB |
Backend failure |
| Table reset | CNID_ERR_RESET |
Existing IDs are stale; session must be ended |
| Invalid parameter/path or closed database | CNID_ERR_PARAM, CNID_ERR_PATH, CNID_ERR_CLOSE |
Check arguments and lifecycle |
| Exhausted ID range | CNID_ERR_MAX |
ID allocation failure |
CNID_ERRNO_IS_BACKEND_FAILURE() groups busy, corrupt, and database failures. AFP callers use retryable AFPERR_MISC for these cases rather than permanent AFPERR_NOOBJ/AFPERR_NOID replies. See the handling in etc/afpd/directory.c, etc/afpd/file.c, and etc/afpd/catsearch.c.
SQLite storage and concurrency
cnid_sqlite_open() uses vol->v_dbpath, or the state directory’s CNID/<volume name> fallback, and creates <volume name>.sqlite inside it. The per-volume table is named by the volume UUID with dashes removed. A volumes table stores UUID, path, stamp, and depletion state. A missing UUID prevents opening the backend.
The CNID table has Id, Name, Did, DevNo, and InodeNo columns. Id is an autoincrement primary key, with uniqueness constraints on (Did, Name) and (DevNo, InodeNo). Sequence seeding reserves IDs below 17.
Current connection settings are WAL journaling, synchronous=NORMAL, and a 3,000 ms busy timeout. Each session has its own connection and prepared statements. WAL permits concurrent readers, while write-lock contention still requires handling; embedded does not mean one connection shared between processes.
Mutation paths use explicit transaction ownership and rollback so cleanup cannot accidentally roll back a caller’s transaction. Update operations reconcile conflicting name and inode keys before inserting the requested identity. Add retries are bounded (CNID_SQLITE_ADD_ATTEMPTS = 16); database failures must not become an endless lookup/insert loop.
SQLite rows, UUIDs, and metadata hints are validated. Hints near the top of the 32-bit range are excluded by a reserved range so user-writable AppleDouble metadata cannot trivially force depletion. On actual depletion, the backend resets records and reports CNID_ERR_RESET.
Database path handling checks file type, links, and ownership before changing modes. Normal multi-user service state and owner-only single-user state follow different permission rules; do not document one universal database mode. Details are in cnid_sqlite_fchmod_regular(), cnid_sqlite_fchmod_companions(), and cnid_sqlite_open().
MySQL
The optional MySQL backend uses per-volume UUID tables and a shared volume registry. Connection parameters come from [Global]:
[Global]
cnid mysql host = localhost
cnid mysql user = netatalk
cnid mysql pw = example-password
cnid mysql db = netatalk_cnid
[Shared]
path = /srv/share
cnid scheme = mysql
Inspect libatalk/cnid/mysql/cnid_mysql.c and include/atalk/cnid_mysql_private.h for connection timeouts, prepared statements, reconnection, and query behavior. A central CNID database alone does not provide distributed file locking or cross-host cache invalidation; backend availability is not a claim that a multi-server deployment is coherent.
Database resets and cache hints
Persistent records and in-memory directory caches are separate. Ordinary AFP mutations emit best-effort hints to sibling workers. A table reset invalidates the entire identity space, so cnid_volume_reset() sends CACHE_HINT_VOLUME_RESET and terminates the originating session.
cnid_volume_tag() identifies the volume using its UUID. The reset tag travels in the hint payload’s cnid field; receivers match open volumes by tag instead of their process-local volume ID. A matching receiver raises SIGTERM so its client reconnects and obtains current IDs. The relay has separate reset rate limits and bounded delivery handling. See libatalk/util/cnid.c, libatalk/util/server_ipc.c, and etc/afpd/dircache.c.
Maintenance utility
bin/dbd/cmd_dbd.c loads the configured volume and checks cnid_scheme_registered(). bin/dbd/cmd_dbd_scanvol.c scans filesystem entries and reconciles CNID/AppleDouble information.
| Option | Purpose |
|---|---|
-F path |
Select configuration file |
-s |
Treat the volume as read-only; suppress filesystem modifications |
-f |
Wipe and recreate CNID records |
-i |
Invalidate AppleDouble CNID hints |
-c |
Convert legacy AppleDouble v2 metadata to EA format |
-t, -v, -V |
Statistics, verbosity, version |
-u username |
Resolve volumes using the user variable |
The supplied path must be the configured volume root. Run maintenance with appropriate permissions and without active clients. -s is not a promise that opening SQLite and scanning have no database side effects. -f can change IDs and invalidate aliases or stored search references; it is a reset operation, not a routine first diagnostic step.
Verification sources
test/afpd/subtests_cnid.c covers backend errors, transactions, record validation, reset behavior, and search boundaries. test/testsuite/FPResolveID.c, test/testsuite/FPCatSearch.c, and test/testsuite/FPCatSearchExt.c exercise AFP-facing behavior. Build and execution instructions are in test/README.md.
Footnotes
Generated from the Netatalk GitHub Wiki
Last updated 2026-10-21