XXNU Source Handbook Download offline version from here
Part 10 · Chapter 10.11

Quota records, cache identity and filesystem boundaries

VFS · Mount lifecycle · Vnode caching

The generic quota implementation caches per-identity records and reads/writes quota files. A filesystem decides where allocation is charged and which quota control operations it implements. This checkout exposes the cache and VFS dispatch but does not supply a complete APFS quota-accounting implementation.

Control dispatch and the stored objects

quotactl resolves the target mount, handles operation-specific user data and the configured MAC check, and calls VFS_QUOTACTL. The filesystem's registered operation is the boundary for enable/disable, retrieval, additional permission checks and accounting policy. Syscall dispatch.

A quotafile holds the backing vnode, stored credentials, quota-file lock, opening/closing/reference state, table dimensions and grace intervals. A dquot holds identity/type, reference count, flags, quota record, on-disk index and links into the hash and reusable/dirty lists. dqblk carries current bytes/inodes, hard/soft limits and grace deadlines. The hash key includes quota-file vnode plus ID, so user 501 on two mounts does not share one record. Definitions, hash and lists.

Quota records, cache identity and filesystem boundaries diagram
Quota records, cache identity and filesystem boundaries diagram · Open SVG
Mermaid source
flowchart LR
    FS[Filesystem accounting / quota operation] --> D[dquot: ID, refs, limits, usage]
    D --> Q[quotafile: vnode, credentials, lock]
    Q --> V[Quota-file vnode]
    D --> H[Hash by quota vnode and ID]
    D -->|zero refs, clean| F[Reusable list]
    D -->|zero refs, dirty after reclaim| O[Dirty orphan list]
    O -->|sync| F

Quota-file open and close exclusion

qf_get(QTF_OPENING) prevents overlapping opens and waits for closing. qf_get(QTF_CLOSING) marks closing and drains both an in-progress open and qf_refcnt. New qf_ref requests fail while opening/closing, so a quota-off operation can stop new dqget users before waiting for existing ones. qf_put clears the transition flag and wakes waiters. These temporary file references are distinct from dq_cnt. Transition protocol.

dqfileopen reads a fixed header using the quota credential, verifies magic/version, power-of-two entry count and consistency with file size, then establishes table/grace parameters. dqfileclose reads the header and attempts to write the updated entry count. It returns void and ignores that final write result; it is not a durability-reporting API. Open/close.

Cache lookup survives sleeping and races

dqget verifies initialization and input, acquires a quota-file reference under the global list lock, then searches by vnode/ID. Its per-record lock helper may sleep while dropping the global lock. Therefore a pointer found before that wait must have both list generation and identity revalidated afterward. A cached zero-reference object is removed from the clean or dirty free list before reuse as an active reference. Hit path.

bsd/vfs/vfs_quota.c, lines 514–528:

		dq_lock_internal(dq);
		if (dq_list_lock_changed(listlockval)) {
			dq_unlock_internal(dq);
			goto relookup;
		}

		/*
		 * dq_lock_internal may drop the quota_list_lock to msleep, so
		 * we need to re-evaluate the identity of this dq
		 */
		if (dq->dq_id != id || dq->dq_qfile == NULL ||
		    dq->dq_qfile->qf_vp != dqvp) {
			dq_unlock_internal(dq);
			goto relookup;
		}

On a miss the allocator drops the global lock to allocate, then searches again: another thread may have created the same identity. Reusing a clean free record also detaches old identity and restarts before publication. The cache refuses exhausted capacity with EUSERS. A newly inserted record remains per-record locked across dqlookup, so another finder cannot use incompletely loaded contents. I/O failure removes its published identity and releases the candidate. Allocation/relookup/publication.

Example: A and B simultaneously request (quota vnode Q, ID 501). Both can initially miss. A allocates while unlocked; B inserts the record first. A must discard its unused allocation and return B's record, otherwise two independently modified copies would represent one on-disk quota.

The on-disk lookup and byte order

dqlookup serializes quota-file access with qf_lock, calculates a primary index and secondary probe stride, and reads fixed dqblk records through VNOP_READ. Empty ID zero means an unallocated entry: the code writes a zeroed record with the requested ID to reserve it, increments the entry count on successful write and saves its index. Existing records are converted from big-endian fields. Short reads/writes become EIO where checked. Disk lookup.

dqget sets DQ_FAKE when all limits are zero and initializes missing grace deadlines from the quota-file intervals. Those are record initialization decisions. The actual allocation path that increments usage or rejects an allocation must come from a filesystem caller; a search for dqget here finds the exported implementation rather than an in-tree APFS charging routine.

Three release routes and their different I/O rules

Route Last-reference behavior
dqrele Attempts synchronization if modified, then puts the object on the reusable list
dqreclaim Performs no I/O; moves a modified zero-reference record to the dirty-orphan list
dqsync_orphans Takes an orphan record, revalidates it after possible sleeps, writes it and moves it to reusable state

Release/reclaim/orphan synchronization. The dirty list preserves work when reclaim context cannot write. Synchronization cannot assume identity remained fixed while locking: dqsync_orphans checks quota-file association and dirty state again after the per-record lock acquisition.

bsd/vfs/vfs_quota.c, lines 890–897:

	if (dq->dq_flags & DQ_MOD) {
		TAILQ_INSERT_TAIL(&dqdirtylist, dq, dq_freelist);
	} else {
		TAILQ_INSERT_TAIL(&dqfreelist, dq, dq_freelist);
	}

	dq_unlock_internal(dq);
	dq_list_unlock();

dqsync_locked writes a big-endian snapshot to the stored index under record ownership and stored credentials. It reports a short write as EIO, but clears DQ_MOD even if the write failed. Callers such as dqrele deliberately ignore its result. Do not describe a dirty flag clearing as confirmed persistence or an automatic write retry guarantee. Writeback.

dqflush detaches every cache identity for a quota-file vnode and expects no live record references; a remaining count triggers a panic. Storage can remain on the reusable list after identity is gone. Flush.

This explains the generic record/cache route. Filesystem allocation hooks, quota-file creation policy, transactional charging and APFS-specific quota domains are not bodies supplied by this source tree and should remain labeled provider boundaries.

Search the handbook

Search every chapter, interface and code excerpt.

    Diagram

    Source preview

    Open full file