#include <atalk/volume.h>Go to the source code of this file.
Functions | |
| int | utest_faultinject_selftest (const struct vol *vol) |
| Framework capability probe: does fault injection reach into libatalk? | |
| int | utest_openfork_no_fd_leak (const struct vol *vol) |
| afp_openfork() error cleanup closes an open fork's fd (no leak). | |
| int | utest_shared_adouble_refcount_balance (const struct vol *vol) |
| Shared-adouble open/close refcount + fileno ledger stays balanced. | |
| int | utest_adclose_underflow_aborts (const struct vol *vol) |
| ad_close() hard-fails on an adf_refcount underflow. | |
| int | utest_ro_retry_strips_destructive_flags (const struct vol *vol) |
| Read-only downgrade retry re-opens without destructive flags. | |
| int | utest_ad2openflags_accmode (const struct vol *vol) |
| ad2openflags(): RDONLY+SETSHRMD on the data fork is promoted to O_RDWR (share-mode locks need a writable fd); plain RDONLY stays O_RDONLY. | |
| int | utest_testlock_range_clamp (const struct vol *vol) |
| ad_testlock_range() clamps a data-zone probe; never sweeps the band. | |
| int | utest_testlock_whole (const struct vol *vol) |
| ad_testlock_whole() issues one unclamped F_GETLK over the whole fd. | |
| int | utest_testlock_range_no_self_report (const struct vol *vol) |
| ad_testlock_range() does not report this process's own band entry. | |
| int | utest_testlock_range_wrlck_sees_rdlck (const struct vol *vol) |
| ad_testlock_range()'s F_WRLCK probe sees a peer's F_RDLCK band entry. | |
| int | utest_of_get_locks_contract (const struct vol *vol) |
| of_get_locks(): positional band bitmap, independent df/rf, tri-valued. | |
| int | utest_of_get_locks_fastpath (const struct vol *vol) |
| of_get_locks(): >=2-dimension whole-fd fast path, per-bit on a hit. | |
| int | utest_of_get_locks_failclosed (const struct vol *vol) |
| of_get_locks(): indeterminate -> OF_LOCKS_ERROR; missing -> NOENT. | |
| int | utest_deletefile_quirk_hazard (const struct vol *vol) |
| Negative control: demonstrate the POSIX close-drops-locks hazard is real. | |
| int | utest_deletefile_quirk (struct vol *vol) |
| deletefile()'s conflict GET through a held fd preserves the held lock. | |
| int | utest_deletefile_nodelete (const struct vol *vol) |
| deletefile() honours the NODELETE attribute (per backend). | |
| int | utest_shared_rlock (const struct vol *vol) |
| Two same-range read locks from two fork owners share one refcount; the kernel lock survives the first release and drops only on the last. | |
| int | utest_overlap_strand (const struct vol *vol) |
| Overlapping read locks from two fork owners coalesce in the kernel; the union unlock must release the whole coalesced range, leaving no stranded remainder. | |
| int | utest_freelock_unlck_fail_logs (const struct vol *vol) |
| A failed refcount-0 F_UNLCK in adf_freelock() is logged and the entry still dropped (log-drop-continue), not silently swallowed. | |
| int | utest_fork_setmode_fdeny (const struct vol *vol) |
| fork_setmode_deny() maps each access mode to the right F_SHARE deny bits. The pre-fix expression mis-parsed via operator precedence, collapsing every input to the deny-write bit or 0. | |
| int | utest_of_alloc_fifo (struct vol *vol) |
| of_alloc()/of_dealloc() free-slot FIFO contract. | |
|
extern |
ad2openflags(): RDONLY+SETSHRMD on the data fork is promoted to O_RDWR (share-mode locks need a writable fd); plain RDONLY stays O_RDONLY.
Category: targeted. This promotion is load-bearing and easy to break silently (O_RDONLY is 0, so a regression only surfaces as subtle lock behaviour or strict-backend EINVAL). The access mode has no fd/refcount ledger; observe it via the shim, which records each armed open()'s flags in fi.open_last_flags. Arm with open_fail_after = -1 to record without failing. Skips where the shim cannot intercept libatalk's open().
|
extern |
ad_close() hard-fails on an adf_refcount underflow.
Category: targeted (nuanced). Accounting invariants are too broad to reach this: the underflow guard's else-branch fires only in a desync no correct open/close sequence produces — fileno still valid (>= 0) yet adf_refcount already <= 0 (a balanced close clears the fileno exactly as the refcount hits 0). The guard is defense-in-depth against a future stray double-close on a shared adouble, so the test deliberately manufactures that precise state: open the data fork, force adf_refcount to 0, then ad_close() — the outer fileno guard passes, the refcount else fires, AFP_ASSERT (live in debugoptimized) abort()s.
AFP_ASSERT abort()s the process, so run it in a fork()ed child and assert the child died via SIGABRT; the parent (the harness) survives. AFP_ASSERT self-gates on NDEBUG: in a release/NDEBUG build it compiles to a no-op and the guard cannot abort, so the test skips there rather than failing — its result is correctly build-type-aware.
|
extern |
deletefile() honours the NODELETE attribute (per backend).
Category: targeted (FU-D, the NODELETE read path). Create a file, set ATTRBIT_NODELETE, and assert deletefile(checkAttrib=1) returns AFPERR_OLOCK and leaves the file; clear it and assert the delete succeeds. Driven with the volume's own backend (called once per backend from test.c).
|
extern |
deletefile()'s conflict GET through a held fd preserves the held lock.
Category: targeted (the same-process lock-on-close repro). Register a tracked ofork holding a byte-range lock on an inode, then drive of_get_locks() the way deletefile() does (reusing the held fd, opening nothing). Assert the conflict read succeeds and a post-call F_GETLK in this process still shows the held lock (a transient open+close would have dropped it). No peer / no shim needed.
|
extern |
Negative control: demonstrate the POSIX close-drops-locks hazard is real.
Category: targeted (the hazard utest_deletefile_quirk's cure defends against). POSIX advisory locks are owned by the process, so closing ANY fd to an inode drops every lock the process holds on it. This test reproduces that hazard directly: take a lock through one fd, then open and close a SECOND, transient fd to the same inode, and observe (via a peer) that the lock is now gone — exactly what the legacy delete-time transient open+close did to a held fork's lock.
Without this control, utest_deletefile_quirk's "the lock survived" could pass vacuously on a platform that never drops the lock in the first place. Here, if the transient close does NOT drop the lock, this platform does not exhibit the quirk, so there is nothing to defend against: return TEST_SKIP (the cure is trivially safe here and its positive result is not meaningful). A reproduced hazard returns 0 — the danger is real, and the cure test then proves 9B avoids it. Cross-process peer (our own F_GETLK never reports our own locks).
|
extern |
Framework capability probe: does fault injection reach into libatalk?
Category: framework integrity (meta-test). Not a netatalk behavioural test — it validates the harness. The LD_PRELOAD shim interposes libc symbols at dynamic-link resolution; this arms an EMFILE on the next open() and calls ad_open() (whose open() lives in the shared libatalk.so), then checks the open failed with the injected errno.
Interception is not reliable on every platform — macOS's two-level namespace ignores a plain symbol preload, so the injected fault never fires there. In that case this returns TEST_SKIP rather than failing: the result is then honest per-platform (injection-based tests run where the mechanism works and skip where it does not).
The errno check lives only in the probe, which owns a direct open() it controls; after ad_open() returns, errno is unreliable (its EC_* error path runs close()/logging that can overwrite it), so the libatalk leg asserts only that the open FAILED while armed, not the specific errno.
|
extern |
fork_setmode_deny() maps each access mode to the right F_SHARE deny bits. The pre-fix expression mis-parsed via operator precedence, collapsing every input to the deny-write bit or 0.
|
extern |
A failed refcount-0 F_UNLCK in adf_freelock() is logged and the entry still dropped (log-drop-continue), not silently swallowed.
|
extern |
of_alloc()/of_dealloc() free-slot FIFO contract.
Public API only (of_alloc/of_dealloc/of_find): refnum == slot and never 0; of_find() round-trips a live refnum and rejects a closed one; a freed slot waits behind all other free slots before reuse (FIFO, not LIFO).
|
extern |
of_get_locks(): positional band bitmap, independent df/rf, tri-valued.
Category: targeted. With a peer holding a DENY_WR band lock, request a subset of band bits plus the data range and assert: the held bit comes back in its own position, an unrequested neighbour bit stays 0, df/rf flags are independent, and the status is OF_LOCKS_OK (separate from the data). Cross-process peer.
|
extern |
of_get_locks(): indeterminate -> OF_LOCKS_ERROR; missing -> NOENT.
Category: targeted (fail-closed contract). A missing target returns OF_LOCKS_NOENT (definite: no locks), distinct from an unreadable one. A forced F_GETLK hard error (shim fcntl -> EIO) must return OF_LOCKS_ERROR, never a silent "no conflict". Skips without shim fcntl interception for the error leg.
|
extern |
of_get_locks(): >=2-dimension whole-fd fast path, per-bit on a hit.
Category: targeted. With >= 2 data-fd dimensions requested on a clear file, exactly one whole-fd F_GETLK is issued and the per-bit band loop is skipped. With a peer band lock present, the whole-fd probe hits and the per-bit loop then runs and attributes the exact bit. Counts F_GETLK via the shim.
|
extern |
afp_openfork() error cleanup closes an open fork's fd (no leak).
Category: accounting invariant. The invariant: after the open-error cleanup ad_close(DF|RF|HF|SETSHRMD), no fork fd survives. When afp_openfork() fails partway it must close whatever the first ad_open() opened, or that fd leaks into the long-lived child. Verified by accounting (not an fd-count scan, which is racy and Linux-only): capture the raw data-fork fd, run the cleanup, and assert two independent ledgers agree the fd is gone — the ad layer (ad_data_fileno == -1) and the kernel (fcntl(fd) -> EBADF). A negative control (fd open before cleanup) keeps the assertion from passing vacuously.
|
extern |
Overlapping read locks from two fork owners coalesce in the kernel; the union unlock must release the whole coalesced range, leaving no stranded remainder.
|
extern |
Read-only downgrade retry re-opens without destructive flags.
Category: targeted (nuanced). ad_open_df() retries a failed open() read-only when the caller passed ADFLAGS_SETSHRMD | ADFLAGS_RDONLY and attempt 1 hit EACCES/EPERM/EROFS; the retry must strip O_TRUNC/O_CREAT/O_EXCL, not just flip the access mode, or a SETSHRMD|RDONLY|TRUNC caller would truncate on the retry a file it asked to open read-only. No fd/refcount ledger sees this – the bug is in which flags the second open() carries – so drive the retry and inspect the flags the shim recorded. ad2openflags() makes attempt 1 O_RDWR|O_TRUNC; arming EACCES on it forces the retry, whose flags land in open_last_flags (open_calls == 2). Skips where the shim cannot intercept libatalk's open().
|
extern |
Shared-adouble open/close refcount + fileno ledger stays balanced.
Category: accounting invariant (broad). When several forks reference one inode they share a single struct adouble; ad_open_df()'s already-open path bumps the fd-level adf_refcount instead of opening a second fd, and ad_close() decrements it, closing the fd only at the last reference. This pins down that whole family with one test by walking the ledger across a multi-reference open/close sequence and asserting the two coupled invariants at every step:
|
extern |
Two same-range read locks from two fork owners share one refcount; the kernel lock survives the first release and drops only on the last.
|
extern |
ad_testlock_range() clamps a data-zone probe; never sweeps the band.
Category: targeted. A whole-data-zone request (len == 0) must be bounded to the data zone (issued l_len != 0, l_start + l_len <= BYTELOCK_MAX), not turned into a POSIX l_len == 0 ("to infinity") that would reach the share-mode band. An explicit band probe (off >= AD_FILELOCK_BASE) must pass through unclamped. Observed via the shim's lock-fcntl recorder. Skips where the shim cannot intercept libatalk's fcntl().
|
extern |
ad_testlock_range() does not report this process's own band entry.
Category: targeted (the crux of using a sibling primitive). Plant an OPEN_RD band entry owned by this process via ad_lock(), then ad_testlock_range on the same offset must return 0 (kernel F_GETLK never reports our own locks), while the array-scanning ad_testlock() returns 1 on the same handle. No shim needed.
|
extern |
ad_testlock_range()'s F_WRLCK probe sees a peer's F_RDLCK band entry.
Category: targeted (the read-lock visibility the old detector missed). The legacy delete probe opened the fork read-only and so issued an F_RDLCK test, which does not conflict with another holder's read lock — a peer read-lock (and the F_RDLCK share-mode band entries) were invisible. ad_testlock_range() always probes with an explicit F_WRLCK, which conflicts with a peer's read OR write lock, so it must report a peer's F_RDLCK as a conflict. A forked peer holds an F_RDLCK at a band offset; assert the range probe returns 1. Control: the same offset with no peer returns 0. Cross-process (our own F_GETLK never reports our own locks), no shim needed.
|
extern |
ad_testlock_whole() issues one unclamped F_GETLK over the whole fd.
Category: targeted. The fast-path probe must be deliberately unclamped (l_start == 0, l_len == 0) so it spans the data zone and the share-mode band in a single call, and report a peer's band lock as a conflict. Uses a forked peer holding a band-offset lock; skips without shim fcntl interception.