netatalk: dircache.c File Reference

netatalk 4.5.1
Free and Open Source Apple Filing Protocol (AFP) Server
Loading...
Searching...
No Matches
dircache.c File Reference

Directory Cache. More...

`#include <assert.h>`
`#include <errno.h>`
`#include <stdio.h>`
`#include <stdlib.h>`
`#include <string.h>`
`#include <time.h>`
`#include <bstrlib.h>`
`#include <atalk/cnid.h>`
`#include <atalk/directory.h>`
`#include <atalk/globals.h>`
`#include <atalk/logger.h>`
`#include <atalk/netatalk_conf.h>`
`#include <atalk/queue.h>`
`#include <atalk/server_ipc.h>`
`#include <atalk/util.h>`
`#include <atalk/volume.h>`
`#include "ad_cache.h"`
`#include "dircache.h"`
`#include "directory.h"`
`#include "hash.h"`
`#include "idle_worker.h"`

## Data Structures
struct | deferred_cleanup
struct | dircache_stat

## Macros
#define | MAX_DEFERRED_CLEANUPS 5120
#define | get16bits(d)
#define | MAX_HINTS_PER_CYCLE 32
#define | HINT_READ_BUF_SIZE

## Enumerations
enum | arc_list_t { ARC_NONE = 0 , ARC_T1 , ARC_T2 , ARC_B1 , ARC_B2 }

## Functions
static hnode_t * | hash_chain_head (hash_t *hash, hashcount_t chain)
static hash_val_t | hash_vid_did (const void *key)
static int | hash_comp_vid_did (const void *key1, const void *key2)
static int | arc_init (size_t cache_size)
| Initialize ARC cache structures.
static void | arc_destroy (void)
| Destroy ARC cache structures.
static void | arc_verify_invariants (void)
| Verify ARC invariants (debug mode).
static void | arc_ensure_ghost_capacity (arc_list_t target_list)
| Ensure ghost directory has room for one more entry.
static struct dir * | arc_find_victim (q_t *queue, size_t queue_size)
| Find an evictable victim in a cache queue, skipping curdir.
static void | arc_evict_to_ghost (struct dir *victim, q_t *src, q_t *dst, int dst_list, bool fallback)
| Evict a victim from a cache queue to the corresponding ghost list.
static int | arc_replace (int in_b2)
| ARC REPLACE subroutine (from paper Figure 1).
static void | arc_case_i (struct dir *dir)
| ARC Case I: Cache hit in T1 or T2.
static void | arc_case_ii_adapt_and_replace (struct dir *ghost)
| ARC Case II: Ghost hit in B1.
static void | arc_case_iii_adapt_and_replace (struct dir *ghost)
| ARC Case III: Ghost hit in B2.
static void | arc_case_iv (struct dir *dir)
| ARC Case IV: Complete miss (not in any list).
static hash_val_t | hash_didname (const void *p)
static int | hash_comp_didname (const void *k1, const void *k2)
static int | should_validate_cache_entry (void)
| Determine if cache entry should be validated against filesystem.
static void | dircache_evict (void)
| Remove a fixed number of (oldest) entries from the cache and indexes.
struct dir * | dircache_search_by_did (const struct vol *vol, cnid_t cnid)
| Search the dircache via a CNID for a directory.
struct dir * | dircache_search_by_name (const struct vol *vol, const struct dir *dir, char *name, size_t len)
| Search the cache via did/name hashtable.
int | dircache_add (const struct vol *vol, struct dir *dir)
| create struct dir from struct path
void | dircache_remove (const struct vol *vol, struct dir *dir, int flags)
| Remove an entry from the dircache.
int | dircache_remove_children (const struct vol *vol, struct dir *dir)
| Remove all child entries of a directory from the dircache.
int | dircache_reindex_didname (const struct vol *vol, struct dir *dir)
| Re-insert entry into DID/name index after key change.
void | dircache_promote (struct dir *dir)
| Promote a cache entry to signal recency.
int | dircache_init (int reqsize)
| Initialize the dircache and indexes.
void | log_dircache_stat (void)
| Log dircache statistics.
void | dircache_rfork_shutdown (void)
| Shutdown the rfork cache — free remaining RFork LRU nodes and sentinel.
void | dircache_dump (void)
| Dump dircache to /tmp/dircache.PID.
int | dircache_set_validation_params (unsigned int freq)
| Set directory cache validation frequency.
void | dircache_reset_validation_counter (void)
| Reset validation counter for consistent testing.
void | process_cache_hints (AFPObj *obj)
| Process cross-process dircache invalidation hints.
void | dircache_report_invalid_entry (struct dir *dir)
| Report that a cache entry was invalid when actually used.
void | dircache_remove_children_defer (const struct vol *vol, struct dir *dir)
| Enqueue a deferred dircache_remove_children operation.
void | dircache_flush_deferred_for_vol (uint16_t vid)
| Process deferred cleanup entries for a closing volume synchronously.
int | dircache_has_deferred_work (void)
int | dircache_process_deferred_chain (void)
| Process one hash chain from the current deferred cleanup job.

## Variables
static hash_t * | dircache
static unsigned int | dircache_maxsize
static struct deferred_cleanup | deferred_queue [MAX_DEFERRED_CLEANUPS]
static int | deferred_head = 0
static int | deferred_tail = 0
static int | deferred_count = 0
static unsigned long | queue_count_max = 0
static unsigned int | dircache_validation_freq = DEFAULT_DIRCACHE_VALIDATION_FREQ
static volatile uint64_t | validation_counter = 0
static struct dircache_stat | dircache_stat
unsigned long long | rfork_stat_lookups = 0
unsigned long long | rfork_stat_hits = 0
unsigned long long | rfork_stat_misses = 0
unsigned long long | rfork_stat_added = 0
unsigned long long | rfork_stat_evicted = 0
unsigned long long | rfork_stat_invalidated = 0
size_t | rfork_stat_used_max = 0
size_t | rfork_cache_used = 0
size_t | rfork_cache_budget = 0
size_t | rfork_max_entry_size = 0
unsigned int | rfork_lru_count = 0
q_t * | rfork_lru = NULL
struct { |
unsigned long long hints_received |
unsigned long long hints_acted_on |
unsigned long long hints_no_match |
} | cache_hint_stat
struct { |
int enabled |
q_t * t1 |
q_t * t2 |
q_t * b1 |
q_t * b2 |
size_t t1_size |
size_t t2_size |
size_t b1_size |
size_t b2_size |
size_t p |
size_t c |
struct { |
uint64_t hits_t1 |
uint64_t hits_t2 |
uint64_t ghost_hits_b1 |
uint64_t ghost_hits_b2 |
uint64_t adaptations |
uint64_t evictions_t1 |
uint64_t evictions_t2 |
uint64_t promotions_t1_to_t2 |
size_t p_min |
size_t p_max |
uint64_t p_increases |
uint64_t p_decreases |
} stats
} | arc_cache
static hash_t * | index_didname
static q_t * | index_queue
static unsigned long | queue_count

## Detailed Description

Directory Cache.

Cache files and directories in a LRU cache.

The directory cache caches directories and files(!). The main reason for having the cache is avoiding recursive walks up the path, querying the CNID database each time, when we have to calculate the location of e.g. directory with CNID 30, which is located in a dir with CNID 25, next CNID 20 and then CNID 2 (the volume root as per AFP spec). If all these dirs where in the cache, each database look up can be avoided. Additionally there's the element "fullpath" in struct dir, which is used to avoid the recursion in any case. Wheneveer a struct dir is initialized, the fullpath to the directory is stored there.

In order to speed up the CNID query for files too, which e.g. happens when a directory is enumerated, files are stored too in the dircache. In order to differentiate between files and dirs, we set the flag DIRF_ISFILE in struct dir.d_flags for files.

The most frequent codepatch that leads to caching is directory enumeration (cf enumerate.c):

- if a element is a directory:
  1. the cache is searched by dircache_search_by_name()
  2. if it wasn't found a new struct dir is created and cached both from within dir_add()
- for files the caching happens a little bit down the call chain:
  1. first getfilparams() is called, which calls
  2. getmetadata() where the cache is searched with dircache_search_by_name()
  3. if the element is not found
  4. get_id() queries the CNID from the database
  5. then a struct dir is initialized via dir_new() (note the fullpath arg is NULL)
  6. finally added to the cache with dircache_add() (2) of course does contain the steps 6,7 and 8.

The dircache is a LRU cache, whenever it fills up we call dircache_evict internally which removes DIRCACHE_FREE_QUANTUM elements from the cache.

There is only one cache for all volumes, so of course we use the volume id in hashing calculations.

In order to avoid cache poisoning, we store the cached entries st_ctime from stat in struct dir.ctime_dircache. Later when we search the cache we compare the stored value with the result of a fresh stat. If the times differ, we remove the cached entry and return "no entry found in cache". A elements ctime changes when

1. the element is renamed (we lose the cached entry here, but it will expire when the cache fills)
2. its a directory and an object has been created therein
3. the element is deleted and recreated under the same name Using ctime leads to cache eviction in case 2) where it wouldn't be necessary, because the dir itself (name, CNID, ...) hasn't changed, but there's no other way.

# Indexes

The maximum dircache size is: max(DEFAULT_DIRCACHE_SIZE, min(size, MAX_DIRCACHE_SIZE)). It is a hashtable which we use to store "struct dir"s in. If the cache get full, oldest entries are evicted in chunks of DIRCACHE_FREE.

We have/need two indexes:

- a DID/name index on the main dircache, another hashtable
- a queue index on the dircache, for evicting the oldest entries

# Debugging

Sending SIGINT to a afpd child causes it to dump the dircache to a file "/tmp/dircache.PID".

## Macro Definition Documentation

## ◆ get16bits

#define get16bits | ( |  | d | ) |

Value:
((((uint32_t)(((const uint8_t *)(d))[1])) << 8) \
+(uint32_t)(((const uint8_t *)(d))[0]) )

## ◆ HINT_READ_BUF_SIZE

#define HINT_READ_BUF_SIZE

Value:
(MAX_HINTS_PER_CYCLE * \
(IPC_HEADERLEN + sizeof(struct ipc_cache_hint_payload)))
MAX_HINTS_PER_CYCLE
#define MAX_HINTS_PER_CYCLE
Definition dircache.c:2505
IPC_HEADERLEN
#define IPC_HEADERLEN
Definition server_ipc.h:12
ipc_cache_hint_payload
Definition server_ipc.h:31

## ◆ MAX_DEFERRED_CLEANUPS

#define MAX_DEFERRED_CLEANUPS 5120

## ◆ MAX_HINTS_PER_CYCLE

#define MAX_HINTS_PER_CYCLE 32

## Enumeration Type Documentation

## ◆ arc_list_t

enum arc_list_t

ARC list location for cache entries

Enumerator
ARC_NONE | Not in any ARC list (matches calloc() zero init)
ARC_T1 | Recent entries
ARC_T2 | Frequent entries
ARC_B1 | Ghost entries evicted from T1
ARC_B2 | Ghost entries evicted from T2

## Function Documentation

## ◆ arc_case_i()

void arc_case_i | ( | struct dir * | dir | ) |
| static

ARC Case I: Cache hit in T1 or T2.

From paper: "Move x to MRU of T2"

Promotes entry from T1 to T2 (frequency list) on second+ access, or moves within T2 if already frequent.

Parameters

  [in] | dir | Directory entry in T1 or T2

## ◆ arc_case_ii_adapt_and_replace()

void arc_case_ii_adapt_and_replace | ( | struct dir * | ghost | ) |
| static

ARC Case II: Ghost hit in B1.

From paper: "Adapt p = min(c, p + max(|B2|/|B1|, 1)) REPLACE(p) Move x to MRU of T2, load into cache"

B1 hit indicates we evicted this entry too soon from T1. Increase p to favor recency (grow T1 target size).

Ghost entries in this implementation are full struct dir with DIRF_ARC_GHOST flag. This function adapts p, calls REPLACE, and promotes ghost from B1 to T2 inline using queue_move_to_tail_of() for zero-allocation transition.

Parameters

  [in] | ghost | Ghost entry (struct dir with DIRF_ARC_GHOST) in B1

## ◆ arc_case_iii_adapt_and_replace()

void arc_case_iii_adapt_and_replace | ( | struct dir * | ghost | ) |
| static

ARC Case III: Ghost hit in B2.

From paper: "Adapt p = max(0, p - max(|B1|/|B2|, 1)) REPLACE(p) Move x to MRU of T2, load into cache"

B2 hit indicates we evicted this entry too soon from T2. Decrease p to favor frequency (grow T2 target size).

Ghost entries in this implementation are full struct dir with DIRF_ARC_GHOST flag. This function adapts p, calls REPLACE, and promotes ghost from B2 to T2 inline using queue_move_to_tail_of() for zero-allocation transition.

Parameters

  [in] | ghost | Ghost entry (struct dir with DIRF_ARC_GHOST) in B2

## ◆ arc_case_iv()

void arc_case_iv | ( | struct dir * | dir | ) |
| static

ARC Case IV: Complete miss (not in any list).

From paper: "case (i): |L1| = c if |T1| < c then delete LRU of B1, REPLACE(p) else delete LRU of T1, remove from cache case (ii): |L1| < c and |L1| + |L2| ≥ c if |L1| + |L2| = 2c then delete LRU of B2 REPLACE(p) Put x at MRU of T1, load into cache"

Adds new entry to T1 (recency list).

Parameters

  [in] | dir | New directory entry to insert

## ◆ arc_destroy()

void arc_destroy | ( | void |  | ) |
| static

Destroy ARC cache structures.

## ◆ arc_ensure_ghost_capacity()

void arc_ensure_ghost_capacity | ( | arc_list_t | target_list | ) |
| static

Ensure ghost directory has room for one more entry.

Maintains the ARC invariant: B1 + B2 ≤ c

Called before operations that will add a ghost entry to B1 or B2. If ghost directory is at or over capacity, deletes the LRU ghost from the specified target list to make room.

This handles the edge case where entries are removed from the cache via dircache_remove() (bypassing ghost lists), causing the ghost directory to stay at capacity while the cache shrinks.

Parameters

  [in] | target_list | Which ghost list will receive new entry (ARC_B1 or ARC_B2)

## ◆ arc_evict_to_ghost()

void arc_evict_to_ghost | ( | struct dir * | victim,
|  | q_t * | src,
|  | q_t * | dst,
|  | int | dst_list,
|  | bool | fallback )
| static

Evict a victim from a cache queue to the corresponding ghost list.

Moves victim's queue node from src to dst, sets ghost flag, and updates all ARC size counters and stats.

Parameters

  [in] | victim | Directory entry to evict (must have valid qidx_node)
  [in] | src | Source cache queue (T1 or T2)
  [in] | dst | Destination ghost queue (B1 or B2)
  [in] | dst_list | ARC_B1 or ARC_B2 (determines which counters to update)
  [in] | fallback | true if this is a cross-queue fallback eviction (for logging)

## ◆ arc_find_victim()

struct dir * arc_find_victim | ( | q_t * | queue,
|  | size_t | queue_size )
| static

Find an evictable victim in a cache queue, skipping curdir.

Only searches T1 and T2 (the real cache queues). B1/B2 are ghost lists with full data.

Tries the LRU entry first; if it's curdir, tries the second-LRU.

Parameters

  [in] | queue | The ARC queue to search (T1 or T2)
  [in] | queue_size | Number of entries in the queue

Returns
  evictable dir entry, or NULL if queue is empty/only contains curdir

## ◆ arc_init()

int arc_init | ( | size_t | cache_size | ) |
| static

Initialize ARC cache structures.

Parameters

  [in] | cache_size | Total cache size (c)

Returns
  0 on success, -1 on error

## ◆ arc_replace()

int arc_replace | ( | int | in_b2 | ) |
| static

ARC REPLACE subroutine (from paper Figure 1).

Evicts one entry from T1 or T2 and moves it to the corresponding ghost list (B1 or B2). Decision based on:

- Current size of T1 relative to target p
- Whether the incoming request is in B2

If the preferred queue only contains curdir (which must never be evicted), falls back to the other queue. Returns -1 only when both queues are exhausted, allowing the caller to proceed with a temporarily over-capacity cache that self-heals on the next eviction cycle.

From paper: "if |T1| ≥ 1 and ((x ∈ B2 and |T1| = p) or |T1| > p): Move LRU of T1 to MRU of B1, remove from cache else: Move LRU of T2 to MRU of B2, remove from cache"

Parameters

  [in] | in_b2 | 1 if incoming request is in B2, 0 otherwise

Returns
  0 on successful eviction, -1 if no entry could be evicted

## ◆ arc_verify_invariants()

void arc_verify_invariants | ( | void |  | ) |
| static

Verify ARC invariants (debug mode).

Checks that ARC list sizes satisfy all constraints from the paper. Called after each operation in debug builds.

## ◆ dircache_add()

int dircache_add | ( | const struct vol * | vol,
|  | struct dir * | dir )

create struct dir from struct path

Add a struct dir to the cache and its indexes. Supports both LRU mode (legacy) and ARC mode.

Parameters

  [in] | vol | pointer to volume
  [in] | dir | pointer to parent directory

Returns
  0 on success, -1 on error which should result in an abort

## ◆ dircache_dump()

void dircache_dump | ( | void |  | ) |

Dump dircache to /tmp/dircache.PID.

## ◆ dircache_evict()

void dircache_evict | ( | void |  | ) |
| static

Remove a fixed number of (oldest) entries from the cache and indexes.

The default is to remove the 256 oldest entries from the cache.

1. Get the oldest entry
2. If it's in use i.e. open forks reference it or it's curdir requeue it, don't remove it
3. Remove the dir from the main cache and the didname index
4. Free the struct dir structure and all its members

## ◆ dircache_flush_deferred_for_vol()

void dircache_flush_deferred_for_vol | ( | uint16_t | vid | ) |

Process deferred cleanup entries for a closing volume synchronously.

Precondition
  : Worker is dormant (called during AFP command processing). Called from afp_closevol() before volume structures are freed.

## ◆ dircache_has_deferred_work()

int dircache_has_deferred_work | ( | void |  | ) |

## ◆ dircache_init()

int dircache_init | ( | int | reqsize | ) |

Initialize the dircache and indexes.

This is called in child afpd initialization. The maximum cache size will be max(DEFAULT_DIRCACHE_SIZE, min(size, MAX_DIRCACHE_SIZE)). It initializes a hashtable which we use to store a directory cache in. It also initializes two indexes:

- a DID/name index on the main dircache
- a queue index on the dircache (LRU mode) or four queues (ARC mode)

Parameters

  [in] | reqsize | requested maximum size from afp.conf

Returns
  0 on success, -1 on error

## ◆ dircache_process_deferred_chain()

int dircache_process_deferred_chain | ( | void |  | ) |

Process one hash chain from the current deferred cleanup job.

Called by idle worker. Walks a single hash chain, collects up to DEFERRED_CHAIN_BATCH matching children, and removes+frees them via dir_remove_and_free(). Entries matching curdir are skipped.

Returns
  1 if more work remains, 0 if all deferred work is complete

## ◆ dircache_promote()

void dircache_promote | ( | struct dir * | dir | ) |

Promote a cache entry to signal recency.

Dispatches based on cache mode:

ARC mode (arc_list membership): T1/T2: arc_case_i() — move to MRU of T2 B1: arc_case_ii_adapt_and_replace() — promote ghost to T2 B2: arc_case_iii_adapt_and_replace() — promote ghost to T2

LRU mode: Move entry to MRU position of the LRU queue, so recently accessed entries are evicted last. Prevents actively-used entries from being evicted.

Parameters

  [in,out] | dir | Cache entry to promote (required)

## ◆ dircache_reindex_didname()

int dircache_reindex_didname | ( | const struct vol * | vol,
|  | struct dir * | dir )

Re-insert entry into DID/name index after key change.

Called by dir_modify() after updating d_pdid / d_u_name. The caller MUST have already removed the entry from the DIDNAME_INDEX via dircache_remove(vol, dir, DIDNAME_INDEX) before calling this.

Parameters

  [in] | vol | Volume (required)
  [in] | dir | Entry with updated d_pdid / d_u_name (required)

Returns
  0 on success, -1 on hash insert failure

## ◆ dircache_remove()

void dircache_remove | ( | const struct vol * | vol,
|  | struct dir * | dir,
|  | int | flags )

Remove an entry from the dircache.

Callers outside of dircache.c should call this with flags = QUEUE_INDEX | DIDNAME_INDEX | DIRCACHE.

## ◆ dircache_remove_children()

int dircache_remove_children | ( | const struct vol * | vol,
|  | struct dir * | dir )

Remove all child entries of a directory from the dircache.

When a directory is renamed or moved, the full paths stored in the dircache become invalid for all child entries of the renamed dir. This function prunes orphaned child dircache entries of given dir. CNID entries use parent DIDs and name, and requre recursion to get the full path, therefore parent changes do not invalidate the CNIDs.

Parameters

  [in] | vol | volume
  [in] | dir | parent directory whose children should be removed

Returns
  0 on success, -1 if curdir was removed and recovery failed

## ◆ dircache_remove_children_defer()

void dircache_remove_children_defer | ( | const struct vol * | vol,
|  | struct dir * | dir )

Enqueue a deferred dircache_remove_children operation.

O(1) enqueue — the idle worker performs the actual hash scan during poll() idle periods. Falls back to synchronous removal if the worker is not active or the queue is full.

## ◆ dircache_report_invalid_entry()

void dircache_report_invalid_entry | ( | struct dir * | dir | ) |

Report that a cache entry was invalid when actually used.

This function should be called when a cached directory entry that was returned without validation (for performance) turns out to be invalid when actually accessed (e.g., file doesn't exist, has been modified, etc). This helps track the effectiveness of the validation frequency setting.

Parameters

  [in] | dir | The directory entry that was found to be invalid

## ◆ dircache_reset_validation_counter()

void dircache_reset_validation_counter | ( | void |  | ) |

Reset validation counter for consistent testing.

Resets the global validation counter to ensure predictable validation patterns between test runs or configuration changes.

## ◆ dircache_rfork_shutdown()

void dircache_rfork_shutdown | ( | void |  | ) |

Shutdown the rfork cache — free remaining RFork LRU nodes and sentinel.

Called from the child shutdown path after log_dircache_stat(). Frees the qnodes (rfork data buffers are reclaimed by exit()) and the LRU sentinel.

## ◆ dircache_search_by_did()

struct dir * dircache_search_by_did | ( | const struct vol * | vol,
|  | cnid_t | cnid )

Search the dircache via a CNID for a directory.

Found cache entries are expunged if both the parent directory st_ctime and the objects st_ctime are modified. This func builds on the fact, that all our code only ever needs to and does search the dircache by CNID expecting directories to be returned, but not files. Thus (1) if we find a file for a given CNID we (1a) remove it from the cache (1b) return NULL indicating nothing found (2) we can then use d_fullpath to stat the directory

Parameters

  [in] | vol | pointer to struct vol
  [in] | cnid | CNID of the directory to search

Returns
  Pointer to struct dir if found, else NULL

## ◆ dircache_search_by_name()

struct dir * dircache_search_by_name | ( | const struct vol * | vol,
|  | const struct dir * | dir,
|  | char * | name,
|  | size_t | len )

Search the cache via did/name hashtable.

Found cache entries are expunged if both the parent directory st_ctime and the objects st_ctime are modified.

Parameters

  [in] | vol | volume
  [in] | dir | directory
  [in] | name | name (server side encoding)
  [in] | len | strlen of name

Returns
  pointer to struct dir if found in cache, else NULL

## ◆ dircache_set_validation_params()

int dircache_set_validation_params | ( | unsigned int | freq | ) |

Set directory cache validation frequency.

Allows runtime configuration of cache validation behavior for performance tuning. Lower validation frequency improves performance but may delay detection of external filesystem changes.

Parameters

  [in] | freq | validation frequency (1 = validate every access, 100 = every 100th access)

Returns
  0 on success, -1 on invalid parameters

## ◆ hash_chain_head()

hnode_t * hash_chain_head | ( | hash_t * | hash,
|  | hashcount_t | chain )
| inlinestatic

## ◆ hash_comp_didname()

int hash_comp_didname | ( | const void * | k1,
|  | const void * | k2 )
| inlinestatic

## ◆ hash_comp_vid_did()

int hash_comp_vid_did | ( | const void * | key1,
|  | const void * | key2 )
| inlinestatic

## ◆ hash_didname()

hash_val_t hash_didname | ( | const void * | p | ) |
| inlinestatic

## ◆ hash_vid_did()

hash_val_t hash_vid_did | ( | const void * | key | ) |
| inlinestatic

## ◆ log_dircache_stat()

void log_dircache_stat | ( | void |  | ) |

Log dircache statistics.

Includes hit ratio percentage for monitoring cache effectiveness, validation-specific metrics to monitor performance impact of the optimization changes, and username for tracking per-user stats. Shows both expunged (caught by validation) and invalid_on_use (missed by validation).

## ◆ process_cache_hints()

void process_cache_hints | ( | AFPObj * | obj | ) |

Process cross-process dircache invalidation hints.

Called from the DSI command loop after each AFP command completes. Uses direct hash_lookup() on the CNID hash table to support BOTH files (DIRF_ISFILE) and directories — dircache_search_by_did() is unsuitable because it actively removes file entries.

Dispatches on hint type:

- CACHE_HINT_REFRESH: ostat() + dir_modify() — stat refresh, AD invalidation
- CACHE_HINT_DELETE: direct dir_remove() — no ostat needed
- CACHE_HINT_DELETE_CHILDREN: dircache_remove_children() + parent cleanup

## ◆ should_validate_cache_entry()

int should_validate_cache_entry | ( | void |  | ) |
| static

Determine if cache entry should be validated against filesystem.

Uses probabilistic validation to reduce filesystem calls while still detecting external changes. Internal netatalk operations use explicit cache invalidation via dir_remove() calls, so frequent validation is only needed to detect external filesystem changes.

Returns
  1 if validation should be performed, 0 otherwise

## Variable Documentation

## ◆ adaptations

uint64_t adaptations

Number of p adjustments

## ◆ [struct]

struct { ... } arc_cache

Initial value:
= {
.enabled = 0,
.t1 = NULL, .t2 = NULL, .b1 = NULL, .b2 = NULL,
.t1_size = 0, .t2_size = 0, .b1_size = 0, .b2_size = 0,
.p = 0, .c = 0,
.stats = {0}
}
NULL
#define NULL
Definition utf8util.c:47

ARC cache state

## ◆ b1

q_t* b1

Ghost entries from T1 (full struct dir with DIRF_ARC_GHOST)

## ◆ b1_size

size_t b1_size

## ◆ b2

q_t* b2

Ghost entries from T2 (full struct dir with DIRF_ARC_GHOST)

## ◆ b2_size

size_t b2_size

## ◆ c

size_t c

Total cache size

## ◆ [struct]

struct { ... } cache_hint_stat

Cache hint processing statistics — logged by log_dircache_stat()

## ◆ deferred_count

int deferred_count = 0
| static

## ◆ deferred_head

int deferred_head = 0
| static

## ◆ deferred_queue

struct deferred_cleanup deferred_queue[MAX_DEFERRED_CLEANUPS]
| static

## ◆ deferred_tail

int deferred_tail = 0
| static

## ◆ dircache

hash_t* dircache
| static

## ◆ dircache_maxsize

unsigned int dircache_maxsize
| static

## ◆ dircache_stat

struct dircache_stat dircache_stat
| static

## ◆ dircache_validation_freq

unsigned int dircache_validation_freq = DEFAULT_DIRCACHE_VALIDATION_FREQ
| static

## ◆ enabled

int enabled

0 = LRU mode, 1 = ARC mode

## ◆ evictions_t1

uint64_t evictions_t1

Evictions from T1

## ◆ evictions_t2

uint64_t evictions_t2

Evictions from T2

## ◆ ghost_hits_b1

uint64_t ghost_hits_b1

Ghost hits in B1

## ◆ ghost_hits_b2

uint64_t ghost_hits_b2

Ghost hits in B2

## ◆ hints_acted_on

unsigned long long hints_acted_on

Hints that matched a local cache entry

## ◆ hints_no_match

unsigned long long hints_no_match

Hints for entries not in local cache (zero cost)

## ◆ hints_received

unsigned long long hints_received

Total hints read from pipe

## ◆ hits_t1

uint64_t hits_t1

Hits in T1

## ◆ hits_t2

uint64_t hits_t2

Hits in T2

## ◆ index_didname

hash_t* index_didname
| static

## ◆ index_queue

q_t* index_queue
| static

## ◆ p

size_t p

Target size for T1

## ◆ p_decreases

uint64_t p_decreases

Times p decreased

## ◆ p_increases

uint64_t p_increases

Times p increased

## ◆ p_max

size_t p_max

Min/max p values seen

## ◆ p_min

size_t p_min

## ◆ promotions_t1_to_t2

uint64_t promotions_t1_to_t2

Promotions from T1 to T2

## ◆ queue_count

unsigned long queue_count
| static

## ◆ queue_count_max

unsigned long queue_count_max = 0
| static

Peak cached entries reached this session (shared by LRU and ARC)

## ◆ rfork_cache_budget

size_t rfork_cache_budget = 0

## ◆ rfork_cache_used

size_t rfork_cache_used = 0

## ◆ rfork_lru

q_t* rfork_lru = NULL

## ◆ rfork_lru_count

unsigned int rfork_lru_count = 0

## ◆ rfork_max_entry_size

size_t rfork_max_entry_size = 0

## ◆ rfork_stat_added

unsigned long long rfork_stat_added = 0

## ◆ rfork_stat_evicted

unsigned long long rfork_stat_evicted = 0

## ◆ rfork_stat_hits

unsigned long long rfork_stat_hits = 0

## ◆ rfork_stat_invalidated

unsigned long long rfork_stat_invalidated = 0

## ◆ rfork_stat_lookups

unsigned long long rfork_stat_lookups = 0

## ◆ rfork_stat_misses

unsigned long long rfork_stat_misses = 0

## ◆ rfork_stat_used_max

size_t rfork_stat_used_max = 0

## ◆ [struct]

struct { ... } stats

## ◆ t1

q_t* t1

Recent entries (cached)

## ◆ t1_size

size_t t1_size

## ◆ t2

q_t* t2

Frequent entries (cached)

## ◆ t2_size

size_t t2_size

## ◆ validation_counter

volatile uint64_t validation_counter = 0
| static