/*
 * event.c - Functions to access event attributes
 * Copyright (c) 2016,2018-20,2023 Red Hat Inc.
 * All Rights Reserved.
 *
 * This software may be freely redistributed and/or modified under the
 * terms of the GNU General Public License as published by the Free
 * Software Foundation; either version 2, or (at your option) any
 * later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; see the file COPYING. If not, write to the
 * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor
 * Boston, MA 02110-1335, USA.
 *
 * Authors:
 *   Steve Grubb <sgrubb@redhat.com>
 *   Radovan Sroka <rsroka@redhat.com>
 */

#include "config.h"
#include <string.h>
#include <limits.h>
#include <sys/fanotify.h>
#include <stdatomic.h>
#include <stdlib.h>
#include <stdio.h>
#include <stdbool.h>
#include <stdint.h>
#include <time.h>
#include <errno.h>
#include <libudev.h>

#include "attr-lookup-metrics.h"
#include "daemon-config.h"
#include "decision-context.h"
#include "event.h"
#include "database.h"
#include "decision-timing.h"
#include "file.h"
#include "lru.h"
#include "message.h"
#include "policy.h"
#include "rules.h"
#include "process.h"

#define ALL_EVENTS (FAN_ALL_EVENTS|FAN_OPEN_PERM|FAN_ACCESS_PERM| \
	FAN_OPEN_EXEC_PERM)

/*
 * A normal exec/open pattern should complete quickly. Keep this below the
 * daemon deadman window so one stuck BUILDING slot cannot park permission
 * events indefinitely. The stale timeout is 10 seconds, expressed in
 * milliseconds so the intended window is easy to read.
 */
#define SUBJECT_BUILDING_STALE_MS 10000ULL
#define NSEC_PER_MSEC 1000000ULL
#define SUBJECT_BUILDING_STALE_NS \
	(SUBJECT_BUILDING_STALE_MS * NSEC_PER_MSEC)
#define SUBJECT_BUILDING_LOG_INTERVAL 60

static struct decision_context default_decision_context = {
	.building_tracer_rate_limit =
		MESSAGE_RATE_LIMIT_INIT(SUBJECT_BUILDING_LOG_INTERVAL),
	.building_stale_rate_limit =
		MESSAGE_RATE_LIMIT_INIT(SUBJECT_BUILDING_LOG_INTERVAL),
};
static __thread struct decision_context *current_decision_context;
static struct decision_context *report_contexts;

static inline struct decision_context *active_decision_context(void)
{
	if (current_decision_context == NULL)
		return &default_decision_context;
	return current_decision_context;
}

#define subj_cache (active_decision_context()->subject_cache)
#define obj_cache (active_decision_context()->object_cache)
#define obj_cache_warned (active_decision_context()->object_cache_warned)
#define early_subj_cache_evictions \
	(active_decision_context()->early_subject_cache_evictions)
#define building_tracer_evictions \
	(active_decision_context()->building_tracer_evict_count)
#define building_stale_evictions \
	(active_decision_context()->building_stale_evict_count)
#define building_tracer_log \
	(active_decision_context()->building_tracer_rate_limit)
#define building_stale_log \
	(active_decision_context()->building_stale_rate_limit)

static atomic_ulong object_cache_flush_generation;

enum building_evict_reason {
	BUILDING_EVICT_TRACER,
	BUILDING_EVICT_STALE,
};

struct building_evict_context {
	pid_t pid;
	unsigned int slot;
	state_t state;
	const char *path1;
	uint64_t age_ns;
	unsigned int event_count;
};

/*
 * decision_context_current - return active mutable decision state.
 *
 * A default empty context is available before the daemon initializes the real
 * caches so policy-only tests can still use the logging buffer.
 */
struct decision_context *decision_context_current(void)
{
	return active_decision_context();
}

/*
 * decision_context_set_current - publish the active decision context.
 * @ctx: context to use, or NULL to restore the default empty context.
 * Returns nothing.
 */
void decision_context_set_current(struct decision_context *ctx)
{
	current_decision_context = ctx;
}

/*
 * decision_context_close_file_helpers - release context-owned file state.
 * @ctx: context whose libmagic, udev, and device cache state should be freed.
 * Returns nothing.
 */
void decision_context_close_file_helpers(struct decision_context *ctx)
{
	if (ctx == NULL)
		return;

	if (ctx->file_udev) {
		udev_unref(ctx->file_udev);
		ctx->file_udev = NULL;
	}
	if (ctx->magic_fast) {
		magic_close(ctx->magic_fast);
		ctx->magic_fast = NULL;
	}
	if (ctx->magic_full) {
		magic_close(ctx->magic_full);
		ctx->magic_full = NULL;
	}
	free(ctx->device_cache.devname);
	ctx->device_cache.devname = NULL;
	ctx->device_cache.device = 0;
}

/*
 * decision_context_set_worker_id - label a context for per-worker reports.
 * @ctx: worker-owned decision context to label.
 * @worker_id: decision worker index that owns @ctx.
 * Returns nothing.
 */
void decision_context_set_worker_id(struct decision_context *ctx,
		unsigned int worker_id)
{
	if (ctx)
		ctx->worker_id = worker_id;
}

/*
 * decision_context_register - expose a worker context to report aggregation.
 * @ctx: context whose private counters should be included in reports.
 * Returns nothing.
 */
static void decision_context_register(struct decision_context *ctx)
{
	if (ctx == NULL || ctx == &default_decision_context ||
	    ctx->report_registered)
		return;

	ctx->report_next = report_contexts;
	ctx->report_registered = true;
	report_contexts = ctx;
}

/*
 * decision_context_unregister - remove a context from report aggregation.
 * @ctx: context being destroyed.
 * Returns nothing.
 */
static void decision_context_unregister(struct decision_context *ctx)
{
	struct decision_context **cur = &report_contexts;

	if (ctx == NULL || !ctx->report_registered)
		return;

	while (*cur) {
		if (*cur == ctx) {
			*cur = ctx->report_next;
			ctx->report_next = NULL;
			ctx->report_registered = false;
			return;
		}
		cur = &(*cur)->report_next;
	}

	ctx->report_next = NULL;
	ctx->report_registered = false;
}

/*
 * decision_context_for_each - visit contexts that own decision state.
 * @iter: callback invoked for each registered context.
 * @data: caller-private callback data.
 *
 * Policy-only tests can run without init_event_system(). In that case the
 * default thread context is visited so metrics recorded through
 * decision_context_current() remain visible to reports.
 */
void decision_context_for_each(decision_context_iter_fn iter, void *data)
{
	struct decision_context *ctx;

	if (iter == NULL)
		return;

	if (report_contexts == NULL) {
		iter(active_decision_context(), data);
		return;
	}

	for (ctx = report_contexts; ctx; ctx = ctx->report_next)
		iter(ctx, data);
}

/*
 * decision_context_init_rate_limits - initialize per-context log throttles.
 * @ctx: context to initialize.
 * Returns nothing.
 */
static void decision_context_init_rate_limits(struct decision_context *ctx)
{
	atomic_init(&ctx->building_tracer_rate_limit.last_log, 0);
	ctx->building_tracer_rate_limit.interval =
		SUBJECT_BUILDING_LOG_INTERVAL;
	atomic_init(&ctx->building_stale_rate_limit.last_log, 0);
	ctx->building_stale_rate_limit.interval = SUBJECT_BUILDING_LOG_INTERVAL;
}

/*
 * decision_context_init_policy_counters - initialize policy metric counters.
 * @counters: policy counter block to initialize.
 * Returns nothing.
 */
static void decision_context_init_policy_counters(
		struct decision_policy_counters *counters)
{
	atomic_init(&counters->allowed, 0);
	atomic_init(&counters->denied, 0);
	atomic_init(&counters->allowed_by_rule, 0);
	atomic_init(&counters->allowed_by_fallthrough, 0);
	atomic_init(&counters->fallthrough_open, 0);
	atomic_init(&counters->fallthrough_execute, 0);
	atomic_init(&counters->fallthrough_trusted, 0);
	atomic_init(&counters->fallthrough_untrusted, 0);
	atomic_init(&counters->fallthrough_trust_unknown, 0);
	atomic_init(&counters->fallthrough_executable, 0);
	atomic_init(&counters->fallthrough_programmatic, 0);
	atomic_init(&counters->fallthrough_sharedlib, 0);
	atomic_init(&counters->fallthrough_unknown_ftype, 0);
	atomic_init(&counters->fallthrough_other_ftype, 0);
}

/*
 * object_cache_flush_generation_snapshot - read the global object-cache
 * invalidation generation.
 *
 * Returns the latest generation requested by trust database publishers.
 */
unsigned long object_cache_flush_generation_snapshot(void)
{
	return atomic_load_explicit(&object_cache_flush_generation,
				    memory_order_acquire);
}

/*
 * request_object_cache_flush - publish an object-cache invalidation.
 *
 * Each decision worker owns a private object cache, so trust database updates
 * must be broadcast as a generation. Workers compare this global value with
 * the generation last applied to their local context and flush independently
 * before processing the next event.
 *
 * Returns the newly published generation.
 */
unsigned long request_object_cache_flush(void)
{
	return atomic_fetch_add_explicit(&object_cache_flush_generation, 1,
					 memory_order_acq_rel) + 1;
}

/*
 * event_now_ns - read monotonic time for BUILDING age checks.
 *
 * Returns monotonic nanoseconds, or zero if the clock cannot be read.
 */
static uint64_t event_now_ns(void)
{
	struct timespec ts;

	if (clock_gettime(CLOCK_MONOTONIC, &ts))
		return 0;

	return (uint64_t)ts.tv_sec * 1000000000ULL + (uint64_t)ts.tv_nsec;
}

/*
 * subject_building_age_ns - calculate the age of a BUILDING subject.
 * @info: cached process state to inspect.
 *
 * Returns the elapsed BUILDING age in nanoseconds, or zero when no start
 * time is available.
 */
static uint64_t subject_building_age_ns(const struct proc_info *info)
{
	uint64_t now;

	if (info == NULL || info->building_started_ns == 0)
		return 0;

	now = event_now_ns();
	if (now <= info->building_started_ns)
		return 0;

	return now - info->building_started_ns;
}

/*
 * subject_mark_building_event - record one event seen while BUILDING.
 * @info: cached process state to update.
 *
 * Returns nothing.
 */
static void subject_mark_building_event(struct proc_info *info)
{
	if (info == NULL || info->state >= STATE_FULL)
		return;

	if (info->building_started_ns == 0)
		info->building_started_ns = event_now_ns();
	if (info->building_event_count != UINT_MAX)
		info->building_event_count++;
}

/*
 * building_evict_reason_name - return display text for an eviction reason.
 * @reason: reason being reported.
 *
 * Returns a stable string for logs and reports.
 */
static const char *building_evict_reason_name(
		enum building_evict_reason reason)
{
	if (reason == BUILDING_EVICT_TRACER)
		return "tracer";

	return "stale";
}

/*
 * record_building_evict - count and log a traced or stale BUILDING eviction.
 * @reason: reason the cache occupant is being evicted.
 * @ctx: immutable details for the diagnostic message.
 *
 * Returns nothing.
 */
static void record_building_evict(enum building_evict_reason reason,
		const struct building_evict_context *ctx)
{
	struct message_rate_limit *log_limit;

	if (reason == BUILDING_EVICT_TRACER) {
		atomic_fetch_add_explicit(&building_tracer_evictions, 1,
					  memory_order_relaxed);
		log_limit = &building_tracer_log;
	} else {
		atomic_fetch_add_explicit(&building_stale_evictions, 1,
					  memory_order_relaxed);
		log_limit = &building_stale_log;
	}

	if (ctx == NULL || !message_rate_limit_allow(log_limit, time(NULL)))
		return;

	msg(LOG_WARNING,
	    "BUILDING subject cache eviction: reason=%s pid=%d slot=%u "
	    "state=%d path1=%s age_ns=%llu events=%u",
	    building_evict_reason_name(reason), ctx->pid, ctx->slot,
	    ctx->state, ctx->path1 ? ctx->path1 : "?",
	    (unsigned long long)ctx->age_ns, ctx->event_count);
}

/*
 * subject_evict_warn - warn when a subject is evicted before fully built
 * @s: subject array to be evicted
 *
 * Rapid PID reuse can force a partially collected subject out of the
 * cache if the cache is too small. When this happens the dynamic linker
 * (ld.so) rule may deny access because when the evicted process re-appears
 * in the future, the loader (ld.so) appears as a standalone execution and
 * matches the ld_so pattern. Warn the administrator so they can consider
 * raising subj_cache_size to reduce the chances of this happening.
 */
static void subject_evict_warn(s_array *s)
{
	int warn = 0;
	if (s && s->info && s->info->state < STATE_FULL) {
		/*
		 * Normal interpreter re-exec replaces the process image
		 * before all paths are gathered. If the re-exec ends in
		 * a script (with or without #!) we know it is benign.
		 * Suppress the suggestion to grow the cache.
		 */
		if (!((s->info->state == STATE_REOPEN) &&
		      (s->info->elf_info & (HAS_SHEBANG|TEXT_SCRIPT))) ) {
			warn = 1;
			atomic_fetch_add_explicit(&early_subj_cache_evictions,
						  1, memory_order_relaxed);
		}
	}

	if (atomic_load_explicit(&early_subj_cache_evictions,
				 memory_order_relaxed) > 5)
		return;

	if (warn) {
		msg(LOG_WARNING,
		    "pid %d in state %d (%s) is being evicted from the "
		    "subject cache before pattern detection completes: "
		    "increase subj_cache_size",
		    s->info->pid, s->info->state, s->info->path1);
	}
}

/*
 * obj_evict_warn - check object cache eviction ratios
 *
 * Opportunistically check eviction ratios during evictions and warn the
 * administrator when thresholds indicate the object cache is too small.
 * Checks occur no more than once every 16 evictions and only one runtime
 * warning is emitted.
 *
 * It uses 2 ratios to decide if we need to issue a warning:
 *
 * E_over_M = evictions / misses
 *   - Measures how often a miss requires throwing out an existing object.
 *   - High values mean the cache is not just missing, but actively churning,
 *     which points to either capacity pressure or poor distribution.
 *
 * E_over_Q = evictions / total lookups
 *   - Measures the overall fraction of requests that cause an eviction.
 *   - This gives a user-facing view of churn: how much of the workload is
 *     paying the eviction penalty out of all operations.
 *
 * Together, these ratios let us distinguish "expected misses" from
 * "pathological evictions" and trigger a resize warning only when the cache
 * is turning over too aggressively for its occupancy level.
 */
__attribute__((cold))
static void obj_evict_warn(void *unused)
{
	unsigned long evicts, miss, hit, lookups, e_over_m, e_over_q;
	unsigned long raw_evictions;
	unsigned int count;
	unsigned int occ, thr_m = 0, thr_q = 0;

	if (obj_cache_warned)
		return;

	raw_evictions = atomic_load_explicit(&obj_cache->evictions,
					     memory_order_relaxed);
	if (raw_evictions & 0xF)
		return;

	evicts = raw_evictions + 1;
	miss = atomic_load_explicit(&obj_cache->misses,
				    memory_order_relaxed) + 1;
	hit = atomic_load_explicit(&obj_cache->hits, memory_order_relaxed);
	lookups = hit + miss;
	count = atomic_load_explicit(&obj_cache->count, memory_order_relaxed);
	occ = (count * 100) / obj_cache->total;
	e_over_m = (evicts * 100) / miss;
	e_over_q = (evicts * 100) / (lookups ? lookups : 1);

	if (occ >= 85) {
		// Near-full tables churn; above these levels growth is
		// usually cheaper than misses.
		thr_m = 80;
		thr_q = 35;
	} else if (occ >= 75) {
		// Some churn is expected; beyond this you’re throwing away
		// too much reuse.
		thr_m = 55;
		thr_q = 20;
	} else if (occ >= 60) {
		// At this level evictions should be infrequent; higher means
		// collisions/skew or underprovisioning.
		thr_m = 35;
		thr_q = 12;
	} else
		return;

	if (e_over_m > thr_m || e_over_q > thr_q) {
		msg(LOG_WARNING,
		    "object cache eviction ratios high (occupancy: %u%%, "
		    "evict/miss=%lu%%, evict/lookups=%lu%%): "
		    "increase obj_cache_size",
		    occ, e_over_m, e_over_q);
		obj_cache_warned = true;
	}
}

/*
 * decision_context_destroy - release one worker's mutable decision state.
 * @ctx: context returned by decision_context_create().
 *
 * The caller must stop the owning worker before destroying its context. The
 * cleanup disables cache eviction warnings because every live entry is being
 * intentionally discarded.
 */
void decision_context_destroy(struct decision_context *ctx)
{
	if (ctx == NULL || ctx == &default_decision_context)
		return;

	/* We're intentionally clearing the caches; disable warnings */
	if (ctx->subject_cache)
		ctx->subject_cache->evict_cb = NULL;
	if (atomic_load_explicit(&ctx->early_subject_cache_evictions,
				 memory_order_relaxed))
		msg(LOG_WARNING,
		   "Processes are being evicted from the subject cache before "
		   "pattern detection completes: increase subj_cache_size "
		   "(total early evictions: %u)",
		   atomic_load_explicit(&ctx->early_subject_cache_evictions,
					memory_order_relaxed));
	if (ctx->object_cache)
		ctx->object_cache->evict_cb = NULL;
	if (ctx->object_cache_warned)
		msg(LOG_WARNING,
		  "object cache eviction ratios high: increase obj_cache_size");

	decision_context_unregister(ctx);
	decision_context_close_file_helpers(ctx);
	destroy_lru(ctx->subject_cache);
	destroy_lru(ctx->object_cache);
	decision_defer_destroy(&ctx->defer_queue);
	free(ctx->working_buffer);
	if (current_decision_context == ctx)
		decision_context_set_current(NULL);
	free(ctx);
}

/*
 * decision_context_create - allocate private mutable state for one worker.
 * @config: daemon cache and defer sizing configuration.
 *
 * The returned context owns its subject cache, object cache, defer array,
 * policy counters, logging buffer storage, and file helper state. File helper
 * handles are opened later by file_init() after the daemon has dropped to its
 * runtime credentials.
 *
 * Returns a new registered context, or NULL on allocation failure.
 */
struct decision_context *decision_context_create(const struct conf *config)
{
	struct decision_context *ctx;

	if (config == NULL)
		return NULL;

	ctx = calloc(1, sizeof(*ctx));
	if (ctx == NULL)
		return NULL;
	decision_context_init_rate_limits(ctx);
	atomic_init(&ctx->early_subject_cache_evictions, 0);
	atomic_init(&ctx->building_tracer_evict_count, 0);
	atomic_init(&ctx->building_stale_evict_count, 0);
	decision_context_init_policy_counters(&ctx->policy_counters);

	/*
	 * Attach subject_evict_warn so we can see when fast PID turnover
	 * drops a subject before classification completes.  Without all the
	 * paths collected ld.so can report spurious access denials.  A larger
	 * subj_cache_size lengthens the window and avoids this condition.
	 */
	ctx->subject_cache = init_lru(config->subj_cache_size,
				(void (*)(void *))subject_clear, "Subject",
				(void (*)(void *))subject_evict_warn);
	if (!ctx->subject_cache) {
		decision_context_destroy(ctx);
		return NULL;
	}

	ctx->object_cache = init_lru(config->obj_cache_size,
				(void (*)(void *))object_clear, "Object",
				obj_evict_warn);
	if (!ctx->object_cache) {
		decision_context_destroy(ctx);
		return NULL;
	}
	ctx->object_cache_flush_generation =
		object_cache_flush_generation_snapshot();

	if (decision_defer_init(&ctx->defer_queue, config->subj_cache_size)) {
		decision_context_destroy(ctx);
		return NULL;
	}
	decision_defer_metrics_snapshot_reset(&ctx->defer_queue,
					      &ctx->last_defer_metrics, 0);
	decision_context_register(ctx);

	return ctx;
}

// Return 0 on success and 1 on error
int init_event_system(const conf_t *config)
{
	struct decision_context *ctx = decision_context_create(config);

	if (ctx == NULL)
		return 1;
	decision_context_set_current(ctx);
	return 0;
}

static int flush_cache(void)
{
	Queue *old_cache = obj_cache;
	Queue *new_cache;
	unsigned int size;

	if (old_cache == NULL)
		return 1;
	if (atomic_load_explicit(&old_cache->count, memory_order_relaxed) == 0)
		return 0;

	size = old_cache->total;

	msg(LOG_DEBUG, "Flushing object cache");
	new_cache = init_lru(size,
			     (void (*)(void *))object_clear, "Object",
			     obj_evict_warn);
	if (!new_cache)
		return 1;

	old_cache->evict_cb = NULL;
	destroy_lru(old_cache);
	obj_cache = new_cache;

	msg(LOG_DEBUG, "Flushed");

	return 0;
}

/*
 * flush_cache_if_needed - apply pending object-cache invalidations locally.
 *
 * Trust database updates advance a global generation. Since every decision
 * context owns its own object cache, each worker must compare and update its
 * local generation instead of consuming a single global flag.
 *
 * Returns 0 on success and 1 if the cache could not be rebuilt.
 */
static int flush_cache_if_needed(void)
{
	struct decision_context *ctx = active_decision_context();
	unsigned long generation;
	struct decision_timing_span timing;
	int rc;

	generation = object_cache_flush_generation_snapshot();
	if (ctx->object_cache_flush_generation == generation)
		return 0;

	decision_timing_stage_begin(DECISION_TIMING_STAGE_CACHE_FLUSH,
				    &timing);
	rc = flush_cache();
	decision_timing_stage_end(&timing);
	if (rc == 0)
		ctx->object_cache_flush_generation = generation;

	return rc;
}

void destroy_event_system(void)
{
	struct decision_context *ctx;

	while (report_contexts) {
		ctx = report_contexts;
		decision_context_destroy(ctx);
	}
	decision_context_set_current(NULL);
}

static inline void reset_subject_attributes(s_array *s)
{
	subject_reset(s, EXE);
	subject_reset(s, COMM);
	subject_reset(s, EXE_TYPE);
	subject_reset(s, SUBJ_TRUST);
}

/*
 * event_subject_slot - compute the subject cache slot for a pid.
 * @pid: process id from fanotify metadata.
 *
 * Returns the hash slot used by new_event() for the subject cache. Deferral
 * must use this helper so it observes the same collision domain as the cache.
 */
unsigned int event_subject_slot(pid_t pid)
{
	return compute_subject_key(subj_cache, pid);
}

/*
 * subject_slot_info - return cached process state for one subject slot.
 * @slot: subject cache slot to inspect.
 *
 * Returns the cached process metadata, or NULL when the slot is empty.
 */
static struct proc_info *subject_slot_info(unsigned int slot)
{
	QNode *q_node = lru_peek_slot(subj_cache, slot);
	s_array *s;

	if (q_node == NULL || q_node->item == NULL)
		return NULL;

	s = (s_array *)q_node->item;
	return s->info;
}

/*
 * subject_slot_state - return the cached subject state for one slot.
 * @slot: subject cache slot to inspect.
 * @pid: optional destination for the cached pid.
 *
 * Returns STATE_FULL when the slot is empty or has no process metadata. That
 * lets callers treat incomplete cache entries only as blocking when the cache
 * has a real subject whose startup pattern state is still before STATE_FULL.
 */
static state_t subject_slot_state(unsigned int slot, pid_t *pid)
{
	struct proc_info *info = subject_slot_info(slot);

	if (info == NULL)
		return STATE_FULL;

	if (pid)
		*pid = info->pid;
	return info->state;
}

/*
 * subject_building_is_traced - test if a BUILDING subject has a tracer.
 * @info: cached process state for the subject cache occupant.
 *
 * Returns 1 when /proc reports a nonzero TracerPid, 0 otherwise.
 */
static int subject_building_is_traced(const struct proc_info *info)
{
	struct proc_status_info status = {
		.ppid = -1,
		.tracer_state = PROC_TRACER_UNKNOWN,
		.uid = NULL,
		.groups = NULL,
		.comm = NULL
	};

	if (info == NULL)
		return 0;

	if (read_proc_status(info->pid, PROC_STAT_TRACER, &status) != 0)
		return 0;

	return status.tracer_state == PROC_TRACER_TRACED;
}

/*
 * subject_building_is_stale - test if BUILDING metadata is stuck or obsolete.
 * @info: cached process state for the subject cache occupant.
 *
 * Returns 1 when the process disappeared, the PID was reused, or the subject
 * has stayed BUILDING longer than the bounded stale window.
 */
static int subject_building_is_stale(const struct proc_info *info)
{
	struct proc_info *current;
	int stale;

	if (info == NULL)
		return 0;

	current = stat_proc_entry(info->pid);
	if (current == NULL)
		return 1;

	stale = compare_proc_infos(current, info);
	clear_proc_info(current);
	free(current);
	if (stale)
		return 1;

	return subject_building_age_ns(info) >= SUBJECT_BUILDING_STALE_NS;
}

/*
 * evict_building_subject - remove a traced or stale BUILDING occupant.
 * @slot: subject cache slot to evict.
 * @info: cached process state expected to occupy @slot.
 * @reason: eviction reason to count and log.
 *
 * Returns nothing.
 */
static void evict_building_subject(unsigned int slot, struct proc_info *info,
		enum building_evict_reason reason)
{
	struct building_evict_context ctx;
	QNode *q_node;
	s_array *s;

	if (info == NULL)
		return;

	q_node = check_lru_cache(subj_cache, slot);
	if (q_node == NULL || q_node->item == NULL)
		return;

	s = (s_array *)q_node->item;
	if (s->info != info)
		return;

	ctx.pid = info->pid;
	ctx.slot = slot;
	ctx.state = info->state;
	ctx.path1 = info->path1;
	ctx.age_ns = subject_building_age_ns(info);
	ctx.event_count = info->building_event_count;
	record_building_evict(reason, &ctx);
	lru_record_collision(subj_cache);
	lru_evict(subj_cache, slot);
}

/*
 * event_subject_slot_is_blocked - test whether an incoming event must wait.
 * @slot: subject cache slot for the incoming event.
 * @pid: incoming event pid.
 *
 * A different pid in a pre-STATE_FULL slot is still building pattern state and
 * would be prematurely evicted by new_event(). Same-pid events are allowed to
 * proceed because they may be the event that advances the subject state.
 * Deferring same-pid events would park the work needed to move the subject out
 * of its building state and could deadlock the slot.
 *
 * Returns 1 when the incoming event should be deferred, 0 otherwise.
 */
int event_subject_slot_is_blocked(unsigned int slot, pid_t pid)
{
	struct proc_info *info = subject_slot_info(slot);

	if (info == NULL)
		return 0;

	if (info->pid == pid || info->state >= STATE_FULL)
		return 0;

	if (subject_building_is_traced(info)) {
		evict_building_subject(slot, info, BUILDING_EVICT_TRACER);
		return 0;
	}
	if (subject_building_is_stale(info)) {
		evict_building_subject(slot, info, BUILDING_EVICT_STALE);
		return 0;
	}

	return 1;
}

/*
 * event_subject_slot_is_unblocked - test whether a slot can release defers.
 * @slot: subject cache slot to inspect.
 *
 * Returns 1 when the slot is empty or its current subject has reached
 * STATE_FULL or a later terminal pattern state.
 */
int event_subject_slot_is_unblocked(unsigned int slot)
{
	return subject_slot_state(slot, NULL) >= STATE_FULL;
}

// Return 0 on success and 1 on error
int new_event(const struct fanotify_event_metadata *m, event_t *e)
{
	subject_attr_t subj;
	QNode *q_node;
	unsigned int key, subject_key, rc, evict = 1, skip_path = 0;
	s_array *s;
	o_array *o;
	struct proc_info *pinfo;
	struct file_info *finfo;
	struct decision_timing_span timing;

	if (flush_cache_if_needed())
		return 1;

	// Transfer things from fanotify structs to ours
	e->pid = m->pid;
	e->fd = m->fd;
	e->type = m->mask & ALL_EVENTS;
	e->num = 0;

	key = compute_subject_key(subj_cache, m->pid);
	subject_key = key;
	q_node = check_lru_cache(subj_cache, key);
	s = (s_array *)q_node->item;

	// get proc fingerprint
	decision_timing_stage_begin(
		DECISION_TIMING_STAGE_PROC_FINGERPRINT, &timing);
	pinfo = stat_proc_entry(m->pid);
	decision_timing_stage_end(&timing);
	if (pinfo == NULL)
		return 1;

	// Check the subject to see if its what its supposed to be
	if (s) {
		rc = compare_proc_infos(pinfo, s->info);

		// EXEC_PERM causes 2 events for every execute. First is an
		// execute request. This is followed by an open request of
		// the same file. So, if we are collecting and perm is open,
		// that means this is the second step, open. We also need
		// be sure we are the same process. We skip collecting path
		// because it was collected on perm = execute.
		if ((s->info->state == STATE_COLLECTING) &&
			(e->type & FAN_OPEN_PERM) && !rc) {
			// special branch after ld_so exec
			// next opens will go fall trough
			if (s->info->path1 &&
				(strcmp(s->info->path1, SYSTEM_LD_SO) == 0))
				s->info->state = STATE_DEFAULT_REOPEN;
			else {
				skip_path = 1;
				s->info->state = STATE_REOPEN;
			}
		}

		// If not same proc or we detect execution, evict
		if (rc)
			lru_record_collision(subj_cache);
		evict = rc || e->type & FAN_OPEN_EXEC_PERM;

		// We need to reset everything now that execve has finished
		if (s->info->state == STATE_STATIC_PARTIAL && !rc) {
			// If the static app itself launches an app right
			// away, go back to collecting.
			if (e->type & FAN_OPEN_EXEC_PERM)
				s->info->state = STATE_COLLECTING;
			else {
				s->info->state = STATE_STATIC;
				skip_path = 1;
			}
			evict = 0;
			reset_subject_attributes(s);
		}
		// Static has to sequence through a state machine to get to
		// the point where we can do a full subject reset. Still
		// in execve at this point.
		if ((s->info->state == STATE_STATIC_REOPEN) &&
					(e->type & FAN_OPEN_PERM) && !rc) {
			s->info->state = STATE_STATIC_PARTIAL;
			evict = 0;
			skip_path = 1;
		}


		// If we've seen the reopen and its an execute and process
		// has an interpreter and we're the same process, don't evict
		// and don't collect the path since reopen interp will. The
		// !skip_path is to prevent the STATE_REOPEN change above from
		// falling into this.
		if ((s->info->state == STATE_REOPEN) && !skip_path &&
				(e->type & FAN_OPEN_EXEC_PERM) &&
				(s->info->elf_info & HAS_INTERP) && !rc) {
			s->info->state = STATE_DEFAULT_REOPEN;
			evict = 0;
			skip_path = 1;
		}

		// this is how STATE_REOPEN and
		// STATE_DEFAULT_REOPEN differs
		// in STATE_REOPEN path is always skipped
		if ((s->info->state == STATE_REOPEN) && !skip_path &&
				(e->type & FAN_OPEN_PERM) && !rc) {
			skip_path = 1;
		}

		if (evict) {
			lru_evict(subj_cache, key);
			q_node = check_lru_cache(subj_cache, key);
			s = (s_array *)q_node->item;
		} else if (s->cnt == 0)
			msg(LOG_DEBUG, "cached subject has cnt of 0");
	}

	if (evict) {
		// If empty, setup the subject with what we currently have
		e->s = malloc(sizeof(s_array));
		if (e->s == NULL || subject_create(e->s)) {
			free(e->s);
			e->s = NULL;
			clear_proc_info(pinfo);
			free(pinfo);
			lru_evict(subj_cache, key);
			return 1;
		}
		subj.type = PID;
		subj.pid = e->pid;
		if (subject_add(e->s, &subj)) {
			subject_clear(e->s);
			free(e->s);
			e->s = NULL;
			clear_proc_info(pinfo);
			free(pinfo);
			lru_evict(subj_cache, key);
			return 1;
		}

		// give custody of the list to the cache
		q_node->item = e->s;
		((s_array *)q_node->item)->info = pinfo;

		// If this is the first time we've seen this process
		// and its doing a file open, its likely to be a running
		// process. That means we should not do pattern detection.
		if (!s && (e->type & FAN_OPEN_PERM))
			pinfo->state = STATE_NORMAL;
	} else	{ // Use the one from the cache
		e->s = s;
		clear_proc_info(pinfo);
		free(pinfo);
	}

	// Init the object
	// get file fingerprint
	rc = 1;
	decision_timing_stage_begin(DECISION_TIMING_STAGE_FD_STAT,
				    &timing);
	finfo = stat_file_entry(m->fd);
	decision_timing_stage_end(&timing);
	if (finfo == NULL) {
		/* On stat_file_entry failure, evict the subject to avoid
		 * leaving an incomplete subject cached, which could
		 * confuse later lookups and pattern matching. */
		if (evict) {
			lru_evict(subj_cache, key);
			e->s = NULL;
		}
		return 1;
	}

	// Just using inodes don't give a good key. It needs
	// conditioning to use more slots in the cache.
	unsigned long magic = finfo->inode + finfo->time.tv_nsec + finfo->size;
	key = compute_object_key(obj_cache, magic);
	q_node = check_lru_cache(obj_cache, key);
	o = (o_array *)q_node->item;

	if (o) {
		rc = compare_file_infos(finfo, o->info);
		if (rc) {
			lru_record_collision(obj_cache);
			lru_evict(obj_cache, key);
			q_node = check_lru_cache(obj_cache, key);
			o = (o_array *)q_node->item;
		}
	}

	if (rc) {
		// If empty, setup the object with what we currently have
		e->o = malloc(sizeof(o_array));
		if (e->o == NULL || object_create(e->o)) {
			free(e->o);
			e->o = NULL;
			free(finfo);
			lru_evict(obj_cache, key);
			if (evict) {
				lru_evict(subj_cache, subject_key);
				e->s = NULL;
			}
			return 1;
		}

		// give custody of the list to the cache
		q_node->item = e->o;
		((o_array *)q_node->item)->info = finfo;
	} else { // Use the one from the cache
		e->o = o;
		free(finfo);
	}

	// Setup pattern info
	pinfo = e->s->info;
	if (pinfo && !skip_path && pinfo->state < STATE_FULL) {
		object_attr_t *on = get_obj_attr(e, PATH);
		if (on) {
			const char *file = on->o;
			if (pinfo->path1 == NULL) {
				// In this step, we gather info on what is
				// being asked permission to execute.
				pinfo->path1 = strdup(file);
				pinfo->elf_info = gather_elf(e->fd,
							e->o->info->size);
			//	pinfo->state = STATE_COLLECTING;Just for clarity
			} else if (pinfo->path2 == NULL) {
				pinfo->path2 = strdup(file);
				pinfo->state = STATE_PARTIAL;
			} else {
				// This third look is needed because the first
				// two are still the old process as far as
				// procfs is concerned. Reset things that could
				// change based on the new process name.
				pinfo->state = STATE_FULL;
				reset_subject_attributes(s);
			}
		}
	}
	subject_mark_building_event(pinfo);
	return 0;
}

/*
 * fetch_proc_status - populate subject cache entries using /proc status
 * @e: event whose subject cache should be filled
 * @t: subject attribute type requested by the caller
 *
 * The function gathers all configured fields from /proc/<pid>/status for the
 * process associated with @e.  Each successfully read attribute is added to
 * the subject cache so subsequent lookups do not need to touch procfs
 * again.
 *
 * Return: pointer to the requested attribute on success, NULL otherwise.
 */
static subject_attr_t *fetch_proc_status(event_t *e, subject_type_t t)
{
	unsigned int mask = policy_get_proc_status_mask();
	struct proc_status_info info = {
		.ppid = -1,
		.uid = NULL,
		.groups = NULL,
		.comm = NULL
	};
	struct decision_timing_span timing;

	decision_timing_stage_begin(
		DECISION_TIMING_STAGE_PROC_STATUS_EXE_LOOKUP, &timing);
	if (read_proc_status(e->pid, mask, &info) != 0) {
		decision_timing_stage_end(&timing);
		return NULL;
	}
	decision_timing_stage_end(&timing);

	// Cache everything - sets and comm are malloc'ed. Transfer ownership.
	// Not checking return of subject_add. Caller needs to check for NULL.
	if (mask & PROC_STAT_PPID) {
		subject_attr_t sub;
		sub.type = PPID;
		sub.pid = info.ppid;
		subject_add(e->s, &sub);
	}
	if (mask & PROC_STAT_UID) {
		subject_attr_t sub;
		sub.type = UID;
		sub.set = info.uid;
		subject_add(e->s, &sub);
	}
	if (mask & PROC_STAT_GID) {
	    subject_attr_t sub;
	    sub.type = GID;
	    sub.set = info.groups;
	    subject_add(e->s, &sub);
	}
	if (mask & PROC_STAT_COMM) {
		subject_attr_t sub;
		sub.type = COMM;
		sub.str = info.comm;
		subject_add(e->s, &sub);
	}

	//return the subject entry
	return subject_access(e->s, t);
}

/*
 * get_subj_attr - return a subject attribute, creating it on demand
 * @e: event describing the subject whose attribute is needed
 * @t: subject attribute identifier
 *
 * The function first looks for @t in the subject cache.  When missing, it
 * performs the necessary lookup and stores the result for reuse.  Some
 * attributes are retrieved directly, while UID/GID and credential data
 * are collected in bulk via fetch_proc_status().
 *
 * Return: pointer to the requested attribute, or NULL if acquisition fails.
 */
__attribute__((hot))
subject_attr_t *get_subj_attr(event_t *e, subject_type_t t)
{
	subject_attr_t subj;
	subject_attr_t *sn;
	s_array *s = e->s;
	struct decision_timing_span timing;

	attr_lookup_metrics_count_subject_request(t);
	sn = subject_access(s, t);
	if (sn)
		return sn;

	// The desired attribute is not on the list, look it up and cache it
	subj.type = t;
	subj.str = NULL;
	switch (t) {
		case AUID:
			attr_lookup_metrics_count_subject_lookup(t);
			decision_timing_stage_begin(
				DECISION_TIMING_STAGE_PROC_STATUS_EXE_LOOKUP,
				&timing);
			subj.uval = get_program_auid_from_pid(e->pid);
			decision_timing_stage_end(&timing);
			break;
		case PPID:
		case UID:
		case GID:
		case COMM:
			attr_lookup_metrics_count_subject_lookup(t);
			/*
			 * UID/GID credentials may differ between the real,
			 * effective, saved, and filesystem slots.  Cache all
			 * but saved so the rule engine can evaluate all
			 * possible identities during matching.
			 */
			return fetch_proc_status(e, t);
			break;
		case SESSIONID:
			attr_lookup_metrics_count_subject_lookup(t);
			decision_timing_stage_begin(
				DECISION_TIMING_STAGE_PROC_STATUS_EXE_LOOKUP,
				&timing);
			subj.uval = (unsigned int)
					get_program_sessionid_from_pid(e->pid);
			decision_timing_stage_end(&timing);
			break;
		case PID:
			attr_lookup_metrics_count_subject_lookup(t);
			subj.pid = e->pid;
			break;
		// If these 2 ever get separated, update subject_add
		// and subject_access in subject.c
		case EXE:
		case EXE_DIR: {
			char buf[PATH_MAX+1], *ptr;

			attr_lookup_metrics_count_subject_lookup(t);
			errno = 0;
			decision_timing_stage_begin(
				DECISION_TIMING_STAGE_PROC_STATUS_EXE_LOOKUP,
				&timing);
			ptr = get_program_from_pid(e->pid, sizeof(buf), buf);
			decision_timing_stage_end(&timing);
			if (errno == ENOENT) {
				/* kworkers have no exe entry
				 * readlink("/proc/4/exe", 0x55624a28d410, 64) = -1 ENOENT (No such file or directory)
				 * use comm
				 */
				sn = subject_access(s, COMM);
				if (!sn)
					sn = fetch_proc_status(e, COMM);
				if (sn)
					subj.str = strdup(sn->str);
				else
					subj.str = strdup("?");
			} else if (ptr)
				subj.str = strdup(buf);
			else
				subj.str = strdup("?");
		}
		break;
		case EXE_TYPE: {
			char buf[128], *ptr;

			attr_lookup_metrics_count_subject_lookup(t);
			decision_timing_stage_begin(
				DECISION_TIMING_STAGE_PROC_STATUS_EXE_LOOKUP,
				&timing);
			ptr = get_type_from_pid(e->pid, sizeof(buf), buf);
			decision_timing_stage_end(&timing);
			if (ptr)
				subj.str = strdup(buf);
			else
				subj.str = strdup("?");
			}
			break;
		case SUBJ_TRUST: {
			subject_attr_t *exe;

			attr_lookup_metrics_count_subject_lookup(t);
			exe = get_subj_attr(e, EXE);
			subj.uval = 0;
			if (exe) {
				if (exe->str) {
					int res = check_trust_database(exe->str,
								       NULL, 0);

					// ignore -1
					if (res == 1)
						subj.uval = 1;
					else
						subj.uval = 0;
				}
			}
			}
			break;
		default:
			return NULL;
	}

	if (subject_add(e->s, &subj) == 0) {
		sn = subject_access(e->s, t);
		return sn;
	}

	// free .str only when it was really used
	// otherwise invalid free is possible
	if (t >= COMM)
		free(subj.str);
	return NULL;
}


/*
 * This function will search the list for a nv pair of the right type.
 * If not found, it will create the type and return it.
 */
__attribute__((hot))
object_attr_t *get_obj_attr(event_t *e, object_type_t t)
{
	char buf[PATH_MAX+1], *ptr;
	object_attr_t obj;
	object_attr_t *on;
	o_array *o = e->o;
	struct decision_timing_span timing;

	attr_lookup_metrics_count_object_request(t);
	on = object_access(o, t);
	if (on)
		return on;

	// One not on the list, look it up and make one
	obj.type = t;
	obj.o = NULL;
	obj.val = 0;
	switch (t) {
		case PATH:
		case ODIR:
			attr_lookup_metrics_count_object_lookup(t);
			// Try to avoid looking up the path if we have it
			on = object_find_file(o);
			if (on)
				obj.o = strdup(on->o);
			else {
				decision_timing_stage_begin(
					DECISION_TIMING_STAGE_FD_PATH_RESOLUTION,
					&timing);
				ptr = get_file_from_fd(e->fd, e->pid,
							sizeof(buf), buf);
				decision_timing_stage_end(&timing);
				if (ptr)
					obj.o = strdup(buf);
				else
					obj.o = strdup("?");
			}
			break;
		case DEVICE:
			attr_lookup_metrics_count_object_lookup(t);
			ptr = get_device_from_stat(o->info->device,
					sizeof(buf), buf);
			if (ptr)
				obj.o = strdup(buf);
			else
				obj.o = strdup("?");
			break;
		case FTYPE: {
			object_attr_t *path;

			attr_lookup_metrics_count_object_lookup(t);
			path =  get_obj_attr(e, PATH);
			// Attribute MIME lookup to the active logical driver
			// so rule and response/debug costs stay separate in
			// timing reports.
			decision_timing_mime_stage_begin(
				DECISION_TIMING_MIME_TOTAL,
				&timing);
			ptr = get_file_type_from_fd(e->fd, o->info,
							path ? path->o : "?",
							sizeof(buf), buf);
			decision_timing_stage_end(&timing);
			if (ptr)
				obj.o = strdup(buf);
			else
				obj.o = strdup("?");
			}
			break;
		case FILE_HASH: {
			file_hash_alg_t alg = FILE_HASH_ALG_SHA256;

			attr_lookup_metrics_count_object_lookup(t);
			if (o->info) {
				if (o->info->digest_alg != FILE_HASH_ALG_NONE)
					alg = o->info->digest_alg;

				if (o->info->digest[0]) {
					obj.o = strdup(o->info->digest);
					break;
				}

				obj.o = get_hash_from_fd2(e->fd,
							  o->info->size, alg);
				if (obj.o) {
					file_info_cache_digest(o->info, alg);
					strncpy(o->info->digest, obj.o,
						FILE_DIGEST_STRING_MAX-1);
					o->info->digest[FILE_DIGEST_STRING_MAX-1] = 0;
				} else
					file_info_reset_digest(o->info);
			}
		}
		break;
		case OBJ_TRUST: {
			object_attr_t *path;

			attr_lookup_metrics_count_object_lookup(t);
			path =  get_obj_attr(e, PATH);
			if (path && path->o) {
				int res = check_trust_database(path->o,
							       o->info, e->fd);

				// ignore -1
				if (res == 1)
					obj.val = 1;
				else
					obj.val = 0;
			}
			}
			break;
		default:
			obj.o = NULL;
			return NULL;
	}

	if (object_add(e->o, &obj) == 0) {
		on = object_access(e->o, t);
		return on;
	}

	free(obj.o);
	return NULL;
}

static void print_queue_stats(FILE *f, const struct lru_metrics *metrics)
{
	fprintf(f, "%s cache size: %u\n", metrics->name, metrics->total);
	fprintf(f, "%s slots in use: %u (%u%%)\n", metrics->name,
		metrics->count,
		metrics->total ? (100*metrics->count)/metrics->total : 0);
	fprintf(f, "%s hits: %lu\n", metrics->name, metrics->hits);
	fprintf(f, "%s misses: %lu\n", metrics->name, metrics->misses);
	fprintf(f, "%s collisions: %lu\n", metrics->name,
		metrics->collisions);
	fprintf(f, "%s evictions: %lu (%lu%%)\n", metrics->name,
		metrics->evictions,
		metrics->hits ? (100*metrics->evictions)/metrics->hits : 0);
}

/*
 * print_cache_config - write cache capacity from a metrics snapshot.
 * @f: report stream.
 * @metrics: cache metrics snapshot.
 * Returns nothing.
 */
static void print_cache_config(FILE *f, const struct lru_metrics *metrics)
{
	fprintf(f, "Per worker %s cache size: %u\n",
		metrics->name, metrics->total);
}

/*
 * print_cache_utilization - write current cache occupancy.
 * @f: report stream.
 * @metrics: cache metrics snapshot.
 * Returns nothing.
 */
static void print_cache_utilization(FILE *f,
		const struct lru_metrics *metrics)
{
	fprintf(f, "%s slots in use: %u (%u%%)\n", metrics->name,
		metrics->count,
		metrics->total ? (100*metrics->count)/metrics->total : 0);
}

/*
 * print_cache_metrics - write resettable cache effectiveness counters.
 * @f: report stream.
 * @metrics: cache metrics snapshot.
 * Returns nothing.
 */
static void print_cache_metrics(FILE *f, const struct lru_metrics *metrics)
{
	fprintf(f, "%s hits: %lu\n", metrics->name, metrics->hits);
	fprintf(f, "%s misses: %lu\n", metrics->name, metrics->misses);
	fprintf(f, "%s collisions: %lu\n", metrics->name,
		metrics->collisions);
	fprintf(f, "%s evictions: %lu (%lu%%)\n", metrics->name,
		metrics->evictions,
		metrics->hits ? (100*metrics->evictions)/metrics->hits : 0);
}

struct cache_worker_snapshot {
	unsigned int worker_id;
	struct lru_metrics subject;
	struct lru_metrics object;
	unsigned int early_evictions;
	unsigned int tracer_evictions;
	unsigned int stale_evictions;
};

struct cache_report_snapshot {
	struct lru_metrics subject;
	struct lru_metrics object;
	unsigned int early_evictions;
	unsigned int tracer_evictions;
	unsigned int stale_evictions;
	unsigned int worker_count;
	struct cache_worker_snapshot workers[DAEMON_CONFIG_DECISION_THREADS_MAX];
	int reset;
};

/*
 * lru_metrics_merge - fold one worker cache snapshot into an aggregate.
 * @aggregate: total cache metrics reported to operators.
 * @metrics: metrics copied from one worker-owned cache.
 * Returns nothing.
 */
static void lru_metrics_merge(struct lru_metrics *aggregate,
		const struct lru_metrics *metrics)
{
	aggregate->count += metrics->count;
	aggregate->total += metrics->total;
	aggregate->hits += metrics->hits;
	aggregate->misses += metrics->misses;
	aggregate->collisions += metrics->collisions;
	aggregate->evictions += metrics->evictions;
}

/*
 * cache_report_snapshot_context - snapshot one worker context for reports.
 * @ctx: worker context being sampled.
 * @data: struct cache_report_snapshot aggregate.
 * Returns nothing.
 */
static void cache_report_snapshot_context(struct decision_context *ctx,
		void *data)
{
	struct cache_report_snapshot *snapshot = data;
	struct cache_worker_snapshot *worker = NULL;
	unsigned int index;

	if (ctx == NULL || snapshot == NULL)
		return;

	index = snapshot->worker_count;
	if (index < DAEMON_CONFIG_DECISION_THREADS_MAX) {
		worker = &snapshot->workers[index];
		worker->worker_id = ctx->worker_id;
		lru_metrics_snapshot(ctx->subject_cache, &worker->subject,
				      snapshot->reset);
		lru_metrics_snapshot(ctx->object_cache, &worker->object,
				      snapshot->reset);
		lru_metrics_merge(&snapshot->subject, &worker->subject);
		lru_metrics_merge(&snapshot->object, &worker->object);
	} else {
		struct lru_metrics metrics;

		lru_metrics_snapshot(ctx->subject_cache, &metrics,
				      snapshot->reset);
		lru_metrics_merge(&snapshot->subject, &metrics);
		lru_metrics_snapshot(ctx->object_cache, &metrics,
				      snapshot->reset);
		lru_metrics_merge(&snapshot->object, &metrics);
	}

	if (snapshot->reset) {
		unsigned int early = atomic_exchange_explicit(
			&ctx->early_subject_cache_evictions, 0,
			memory_order_relaxed);
		unsigned int tracer = atomic_exchange_explicit(
			&ctx->building_tracer_evict_count, 0,
			memory_order_relaxed);
		unsigned int stale = atomic_exchange_explicit(
			&ctx->building_stale_evict_count, 0,
			memory_order_relaxed);
		snapshot->early_evictions += early;
		snapshot->tracer_evictions += tracer;
		snapshot->stale_evictions += stale;
		if (worker) {
			worker->early_evictions = early;
			worker->tracer_evictions = tracer;
			worker->stale_evictions = stale;
		}
	} else {
		unsigned int early = atomic_load_explicit(
			&ctx->early_subject_cache_evictions,
			memory_order_relaxed);
		unsigned int tracer = atomic_load_explicit(
			&ctx->building_tracer_evict_count,
			memory_order_relaxed);
		unsigned int stale = atomic_load_explicit(
			&ctx->building_stale_evict_count,
			memory_order_relaxed);
		snapshot->early_evictions += early;
		snapshot->tracer_evictions += tracer;
		snapshot->stale_evictions += stale;
		if (worker) {
			worker->early_evictions = early;
			worker->tracer_evictions = tracer;
			worker->stale_evictions = stale;
		}
	}
	if (snapshot->worker_count < DAEMON_CONFIG_DECISION_THREADS_MAX)
		snapshot->worker_count++;
}

/*
 * cache_report_snapshot_reset - aggregate cache counters across workers.
 * @snapshot: destination for aggregate cache and BUILDING eviction metrics.
 * @reset: non-zero resets interval counters after copying them.
 * Returns nothing.
 */
static void cache_report_snapshot_reset(struct cache_report_snapshot *snapshot,
		int reset)
{
	memset(snapshot, 0, sizeof(*snapshot));
	snapshot->subject.name = "Subject";
	snapshot->object.name = "Object";
	snapshot->reset = reset;
	decision_context_for_each(cache_report_snapshot_context, snapshot);
}

/*
 * cache_worker_snapshot_sort - order worker snapshots by worker id.
 * @workers: worker snapshot array.
 * @count: number of populated entries in @workers.
 * Returns nothing.
 */
static void cache_worker_snapshot_sort(struct cache_worker_snapshot *workers,
		unsigned int count)
{
	unsigned int i, j;

	for (i = 0; i < count; i++) {
		for (j = i + 1; j < count; j++) {
			struct cache_worker_snapshot tmp;

			if (workers[i].worker_id <= workers[j].worker_id)
				continue;
			tmp = workers[i];
			workers[i] = workers[j];
			workers[j] = tmp;
		}
	}
}

/*
 * print_cache_metrics_worker - write one worker's cache counters.
 * @f: report stream.
 * @worker_id: decision worker that owns these cache counters.
 * @metrics: cache metrics snapshot.
 * Returns nothing.
 */
static void print_cache_metrics_worker(FILE *f, unsigned int worker_id,
		const struct lru_metrics *metrics)
{
	fprintf(f, "  Decision worker %u %s hits: %lu\n",
		worker_id, metrics->name, metrics->hits);
	fprintf(f, "  Decision worker %u %s misses: %lu\n",
		worker_id, metrics->name, metrics->misses);
	fprintf(f, "  Decision worker %u %s collisions: %lu\n",
		worker_id, metrics->name, metrics->collisions);
	fprintf(f, "  Decision worker %u %s evictions: %lu (%lu%%)\n",
		worker_id, metrics->name, metrics->evictions,
		metrics->hits ? (100*metrics->evictions)/metrics->hits : 0);
}

void run_usage_report(const conf_t *config, FILE *f)
{
	time_t t;
	QNode *q_node;
	struct lru_metrics metrics;

	if (f == NULL)
		return;

	if (config->detailed_report) {
		t = time(NULL);
		fprintf(f,
			"File access attempts from oldest to newest as of %s\n",
			ctime(&t));
		fprintf(f, "\tFILE\t\t\t\t\t\t    ATTEMPTS\n");
		fprintf(f,
"---------------------------------------------------------------------------\n"
			);
		if (atomic_load_explicit(&obj_cache->count,
					 memory_order_relaxed) == 0) {
			fprintf(f, "(none)\n");
			return;
		}

		q_node = obj_cache->end;

		while (q_node) {
			unsigned int len;
			const char *file;
			o_array *o = (o_array *)q_node->item;
			object_attr_t *on = object_find_file(o);
			if (on == NULL)
				goto next_obj;
			file = on->o;
			if (file == NULL)
				goto next_obj;

			len = strlen(file);
			if (len > 62)
				fprintf(f, "%s\t%lu\n", file, q_node->uses);
			else
				fprintf(f, "%-62s\t%lu\n", file, q_node->uses);
		next_obj:
			q_node = q_node->prev;
		}

		fprintf(f, "\n---\n\n");
	}
	lru_metrics_snapshot(obj_cache, &metrics, 0);
	print_queue_stats(f, &metrics);
	fprintf(f, "\n\n");

	if (config->detailed_report) {
		fprintf(f,
		   "Active processes oldest to most recently active as of %s\n",
		   ctime(&t));
		fprintf(f, "\tEXE\tCOMM\t\t\t\t\t    ATTEMPTS\n");
		fprintf(f,
"---------------------------------------------------------------------------\n"
			);
		if (atomic_load_explicit(&subj_cache->count,
					 memory_order_relaxed) == 0) {
			fprintf(f, "(none)\n");
			return;
		}

		q_node = subj_cache->end;

		while (q_node) {
			unsigned int len;
			char *exe, *comm, *text;
			subject_attr_t *se, *sc;
			s_array *s = (s_array *)q_node->item;
			se = subject_find_exe(s);
			if (se == NULL)
				goto next_subj;
			exe = se->str;
			if (exe == NULL)
				goto next_subj;

			sc = subject_find_comm(s);
			if (sc == NULL)
				comm = "?";
			else
				comm = sc->str ? sc->str : "?";

			if (asprintf(&text, "%s (%s)", exe, comm) < 0) {
				fprintf(f, "?\n");
				goto next_subj;
			}

			len = strlen(text);
			if (len > 62)
				fprintf(f, "%s\t%lu\n", text, q_node->uses);
			else
				fprintf(f,"%-62s\t%lu\n", text, q_node->uses);
			free(text);
		next_subj:
			q_node = q_node->prev;
		}
		fprintf(f, "\n---\n\n");
	}
	lru_metrics_snapshot(subj_cache, &metrics, 0);
	print_queue_stats(f, &metrics);
	fprintf(f, "\n");
}

void do_cache_reports(FILE *f)
{
	do_cache_reports_reset(f, 0);
}

/*
 * do_cache_config_report - write cache capacities sized at startup.
 * @f: report stream.
 * Returns nothing.
 */
void do_cache_config_report(FILE *f)
{
	struct cache_report_snapshot snapshot;
	struct lru_metrics subject;
	struct lru_metrics object;

	if (f == NULL)
		return;

	cache_report_snapshot_reset(&snapshot, 0);
	subject = snapshot.subject;
	object = snapshot.object;
	if (snapshot.worker_count) {
		subject.total /= snapshot.worker_count;
		object.total /= snapshot.worker_count;
	}
	print_cache_config(f, &subject);
	print_cache_config(f, &object);
}

/*
 * do_cache_utilization_report - write current cache slot utilization.
 * @f: report stream.
 * Returns nothing.
 */
void do_cache_utilization_report(FILE *f)
{
	struct cache_report_snapshot snapshot;

	if (f == NULL)
		return;

	cache_report_snapshot_reset(&snapshot, 0);
	print_cache_utilization(f, &snapshot.subject);
	print_cache_utilization(f, &snapshot.object);
}

/*
 * do_cache_health_report - write cache counters that warrant investigation.
 * @f: report stream.
 * Returns nothing.
 */
void do_cache_health_report(FILE *f)
{
	struct cache_report_snapshot snapshot;

	if (f == NULL)
		return;

	cache_report_snapshot_reset(&snapshot, 0);
	fprintf(f, "Early subject cache evictions: %u\n",
		snapshot.early_evictions);
	fprintf(f, "Subject BUILDING tracer evictions: %u\n",
		snapshot.tracer_evictions);
	fprintf(f, "Subject BUILDING stale evictions: %u\n",
		snapshot.stale_evictions);
}

/*
 * do_cache_metrics_report_reset - write cache effectiveness counters.
 * @f: report stream.
 * @reset: non-zero resets counters after snapshotting them.
 * Returns nothing.
 */
void do_cache_metrics_report_reset(FILE *f, int reset)
{
	struct cache_report_snapshot snapshot;

	if (f == NULL)
		return;

	cache_report_snapshot_reset(&snapshot, reset);

	fprintf(f, "\nSubject cache effectiveness:\n");
	print_cache_metrics(f, &snapshot.subject);
	fprintf(f, "Early subject cache evictions: %u\n",
		snapshot.early_evictions);
	fprintf(f, "Subject BUILDING tracer evictions: %u\n",
		snapshot.tracer_evictions);
	fprintf(f, "Subject BUILDING stale evictions: %u\n",
		snapshot.stale_evictions);

	fprintf(f, "\nObject cache effectiveness:\n");
	print_cache_metrics(f, &snapshot.object);

	if (snapshot.worker_count) {
		unsigned int i;

		cache_worker_snapshot_sort(snapshot.workers,
					   snapshot.worker_count);
		fprintf(f, "\nPer-worker cache effectiveness:\n");
		for (i = 0; i < snapshot.worker_count; i++) {
			struct cache_worker_snapshot *worker =
				&snapshot.workers[i];

			print_cache_metrics_worker(f, worker->worker_id,
						   &worker->subject);
			fprintf(f,
				"  Decision worker %u Early subject cache evictions: %u\n",
				worker->worker_id, worker->early_evictions);
			fprintf(f,
				"  Decision worker %u Subject BUILDING tracer evictions: %u\n",
				worker->worker_id, worker->tracer_evictions);
			fprintf(f,
				"  Decision worker %u Subject BUILDING stale evictions: %u\n",
				worker->worker_id, worker->stale_evictions);
			print_cache_metrics_worker(f, worker->worker_id,
						   &worker->object);
		}
	}
}

/*
 * do_cache_reports_reset - write cache metrics and optionally reset counters.
 * @f: output stream.
 * @reset: non-zero resets interval counters after copying them.
 *
 * Cache sizes and occupancy are state values and are never reset.
 */
void do_cache_reports_reset(FILE *f, int reset)
{
	do_cache_config_report(f);
	do_cache_utilization_report(f);
	do_cache_metrics_report_reset(f, reset);
}
