/*
 * examples/libsyd_test.c: Build & link against libsyd + libcheck + yajl.
 * Run under syd(2) with `lib` profile by the Rust test harness.
 * Copyright (c) 2025, 2026 Ali Polatel <alip@chesswob.org>
 * SPDX-License-Identifier: LGPL-3.0
 */

#define _POSIX_C_SOURCE 200809L
#include <errno.h>
#include <fcntl.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

#include <check.h>
#include <yajl/yajl_tree.h>

#include "syd.h"

#define TEST_OUT_PATH "/tmp/libsyd"

/* JSON helpers: minimal utilities to inspect /dev/syd */

static char *slurp(const char *path, size_t *len_out)
{
	int fd = open(path, O_RDONLY);
	ck_assert_msg(fd >= 0, "open %s", path);
	size_t cap = 4096, len = 0;
	char *buf = (char *)malloc(cap + 1);
	ck_assert_msg(buf != NULL, "malloc");
	for (;;) {
		if (len == cap) {
			cap *= 2;
			char *tmp = (char *)realloc(buf, cap + 1);
			ck_assert_msg(tmp != NULL, "realloc");
			buf = tmp;
		}
		ssize_t n = read(fd, buf + len, cap - len);
		ck_assert_msg(n >= 0, "read %s", path);
		if (n == 0) break;
		len += (size_t)n;
	}
	close(fd);
	buf[len] = '\0';
	if (len_out) *len_out = len;
	return buf;
}

static yajl_val info(void)
{
	size_t n = 0;
	char *s = slurp("/dev/syd", &n);
	ck_assert_ptr_nonnull(s);
	ck_assert_msg(n > 0, "/dev/syd empty");

	char errbuf[256];
	errbuf[0] = '\0';
	yajl_val root = yajl_tree_parse(s, errbuf, sizeof(errbuf));
	free(s);
	ck_assert_msg(root != NULL, "json parse %s", errbuf[0] ? errbuf : "(unknown)");
	return root;
}

static long long json_ll(yajl_val v)
{
	ck_assert_msg(v && YAJL_IS_NUMBER(v), "expected number");
	const char *raw = YAJL_GET_NUMBER(v);
	ck_assert_msg(raw && *raw, "number raw");
	errno = 0;
	char *end = NULL;
	long long out = strtoll(raw, &end, 10);
	ck_assert_msg(errno == 0 && end && *end == '\0', "parse int: %s", raw);
	return out;
}

static int find_glob_rule(yajl_val root, const char *act, const char *cap,
                          const char *pat)
{
	const char *p_glob[] = { "glob_rules", NULL };
	yajl_val arr = yajl_tree_get(root, p_glob, yajl_t_array);
	ck_assert_msg(arr && YAJL_IS_ARRAY(arr), "glob_rules not array");

	size_t n = arr->u.array.len;
	for (size_t i = 0; i < n; i++) {
		yajl_val r = arr->u.array.values[i];
		if (!YAJL_IS_OBJECT(r)) continue;

		const char *p_act[] = { "act", NULL };
		const char *p_cap[] = { "cap", NULL };
		const char *p_pat[] = { "pat", NULL };

		yajl_val v_act = yajl_tree_get(r, p_act, yajl_t_string);
		yajl_val v_cap = yajl_tree_get(r, p_cap, yajl_t_string);
		yajl_val v_pat = yajl_tree_get(r, p_pat, yajl_t_string);

		if (!(v_act && v_cap && v_pat)) continue;
		const char *s_act = YAJL_GET_STRING(v_act);
		const char *s_cap = YAJL_GET_STRING(v_cap);
		const char *s_pat = YAJL_GET_STRING(v_pat);

		if (s_act && s_cap && s_pat &&
		    strcmp(s_act, act) == 0 &&
		    strcmp(s_cap, cap) == 0 &&
		    strcmp(s_pat, pat) == 0) {
			return (int)i;
		}
	}
	return -1;
}

static int find_cidr_rule(yajl_val root, const char *act, const char *cap,
                          const char *addr, int lo, int hi)
{
	const char *p_cidr[] = { "cidr_rules", NULL };
	yajl_val arr = yajl_tree_get(root, p_cidr, yajl_t_array);
	ck_assert_msg(arr && YAJL_IS_ARRAY(arr), "cidr_rules not array");

	size_t n = arr->u.array.len;
	for (size_t i = 0; i < n; i++) {
		yajl_val r = arr->u.array.values[i];
		if (!YAJL_IS_OBJECT(r)) continue;

		const char *p_act[] = { "act", NULL };
		const char *p_cap[] = { "cap", NULL };
		const char *p_pat[] = { "pat", NULL };

		yajl_val v_act = yajl_tree_get(r, p_act, yajl_t_string);
		yajl_val v_cap = yajl_tree_get(r, p_cap, yajl_t_string);
		yajl_val v_pat = yajl_tree_get(r, p_pat, yajl_t_object);
		if (!(v_act && v_cap && v_pat)) continue;

		const char *s_act = YAJL_GET_STRING(v_act);
		const char *s_cap = YAJL_GET_STRING(v_cap);
		if (!(s_act && s_cap && strcmp(s_act, act) == 0
		      && strcmp(s_cap, cap) == 0)) continue;

		const char *p_addr[] = { "pat", "addr", NULL };
		yajl_val v_addr = yajl_tree_get(r, p_addr, yajl_t_string);
		if (!v_addr) continue;
		const char *s_addr = YAJL_GET_STRING(v_addr);
		if (!(s_addr && strcmp(s_addr, addr) == 0)) continue;

		if (lo == -1 && hi == -1) return (int)i;

		const char *p_port_num[] = { "pat", "port", NULL };
		yajl_val v_port = yajl_tree_get(r, p_port_num, yajl_t_any);

		if (!v_port) continue;
		if (YAJL_IS_NUMBER(v_port) && lo == hi) {
			long long one = json_ll(v_port);
			if ((int)one == lo) return (int)i;
		} else if (YAJL_IS_ARRAY(v_port) && v_port->u.array.len == 2) {
			yajl_val p0 = v_port->u.array.values[0];
			yajl_val p1 = v_port->u.array.values[1];
			if (YAJL_IS_NUMBER(p0) && YAJL_IS_NUMBER(p1)) {
				int r_lo = (int)json_ll(p0);
				int r_hi = (int)json_ll(p1);
				if (r_lo == lo && r_hi == hi) return (int)i;
			}
		}
	}
	return -1;
}

static int find_force_rule(yajl_val root, const char *act, const char *sha,
                           const char *pat)
{
	const char *p_force[] = { "force_rules", NULL };
	yajl_val arr = yajl_tree_get(root, p_force, yajl_t_array);
	ck_assert_msg(arr && YAJL_IS_ARRAY(arr), "force_rules not array");

	size_t n = arr->u.array.len;
	for (size_t i = 0; i < n; i++) {
		yajl_val r = arr->u.array.values[i];
		if (!YAJL_IS_OBJECT(r)) continue;

		const char *p_act[] = { "act", NULL };
		const char *p_sum[] = { "sum", NULL };
		const char *p_pat[] = { "pat", NULL };

		yajl_val v_act = yajl_tree_get(r, p_act, yajl_t_string);
		yajl_val v_sum = yajl_tree_get(r, p_sum, yajl_t_string);
		yajl_val v_pat = yajl_tree_get(r, p_pat, yajl_t_string);
		if (!(v_act && v_sum && v_pat)) continue;

		const char *s_act = YAJL_GET_STRING(v_act);
		const char *s_sum = YAJL_GET_STRING(v_sum);
		const char *s_pat = YAJL_GET_STRING(v_pat);
		if (s_act && s_sum && s_pat &&
		    strcmp(s_act, act) == 0 &&
		    strcmp(s_sum, sha) == 0 &&
		    strcmp(s_pat, pat) == 0) {
			return (int)i;
		}
	}
	return -1;
}

/* Tests */

START_TEST(test_api_version)
{
	ck_assert_int_eq(syd_check(), 0);
	ck_assert_int_eq(syd_api(), 3);
}
END_TEST

START_TEST(test_enable_flags)
{
	ck_assert(!syd_enabled_fs());
	ck_assert_int_eq(syd_enable_fs(), 0);
	ck_assert(syd_enabled_fs());
	ck_assert_int_eq(syd_disable_fs(), 0);
	ck_assert(!syd_enabled_fs());

	ck_assert(!syd_enabled_walk());
	ck_assert_int_eq(syd_enable_walk(), 0);
	ck_assert(syd_enabled_walk());
	ck_assert_int_eq(syd_disable_walk(), 0);
	ck_assert(!syd_enabled_walk());

	ck_assert(!syd_enabled_stat());
	ck_assert_int_eq(syd_enable_stat(), 0);
	ck_assert(syd_enabled_stat());
	ck_assert_int_eq(syd_disable_stat(), 0);
	ck_assert(!syd_enabled_stat());

	ck_assert(!syd_enabled_read());
	ck_assert_int_eq(syd_enable_read(), 0);
	ck_assert(syd_enabled_read());
	ck_assert_int_eq(syd_disable_read(), 0);
	ck_assert(!syd_enabled_read());

	ck_assert(!syd_enabled_write());
	ck_assert_int_eq(syd_enable_write(), 0);
	ck_assert(syd_enabled_write());
	ck_assert_int_eq(syd_disable_write(), 0);
	ck_assert(!syd_enabled_write());

	ck_assert(!syd_enabled_exec());
	ck_assert_int_eq(syd_enable_exec(), 0);
	ck_assert(syd_enabled_exec());
	ck_assert_int_eq(syd_disable_exec(), 0);
	ck_assert(!syd_enabled_exec());

	ck_assert(!syd_enabled_ioctl());
	ck_assert_int_eq(syd_enable_ioctl(), 0);
	ck_assert(syd_enabled_ioctl());
	ck_assert_int_eq(syd_disable_ioctl(), 0);
	ck_assert(!syd_enabled_ioctl());

	ck_assert(!syd_enabled_create());
	ck_assert_int_eq(syd_enable_create(), 0);
	ck_assert(syd_enabled_create());
	ck_assert_int_eq(syd_disable_create(), 0);
	ck_assert(!syd_enabled_create());

	ck_assert(!syd_enabled_delete());
	ck_assert_int_eq(syd_enable_delete(), 0);
	ck_assert(syd_enabled_delete());
	ck_assert_int_eq(syd_disable_delete(), 0);
	ck_assert(!syd_enabled_delete());

	ck_assert(!syd_enabled_rename());
	ck_assert_int_eq(syd_enable_rename(), 0);
	ck_assert(syd_enabled_rename());
	ck_assert_int_eq(syd_disable_rename(), 0);
	ck_assert(!syd_enabled_rename());

	ck_assert(!syd_enabled_symlink());
	ck_assert_int_eq(syd_enable_symlink(), 0);
	ck_assert(syd_enabled_symlink());
	ck_assert_int_eq(syd_disable_symlink(), 0);
	ck_assert(!syd_enabled_symlink());

	ck_assert(!syd_enabled_truncate());
	ck_assert_int_eq(syd_enable_truncate(), 0);
	ck_assert(syd_enabled_truncate());
	ck_assert_int_eq(syd_disable_truncate(), 0);
	ck_assert(!syd_enabled_truncate());

	ck_assert(!syd_enabled_chdir());
	ck_assert_int_eq(syd_enable_chdir(), 0);
	ck_assert(syd_enabled_chdir());
	ck_assert_int_eq(syd_disable_chdir(), 0);
	ck_assert(!syd_enabled_chdir());

	ck_assert(!syd_enabled_readdir());
	ck_assert_int_eq(syd_enable_readdir(), 0);
	ck_assert(syd_enabled_readdir());
	ck_assert_int_eq(syd_disable_readdir(), 0);
	ck_assert(!syd_enabled_readdir());

	ck_assert(!syd_enabled_mkdir());
	ck_assert_int_eq(syd_enable_mkdir(), 0);
	ck_assert(syd_enabled_mkdir());
	ck_assert_int_eq(syd_disable_mkdir(), 0);
	ck_assert(!syd_enabled_mkdir());

	ck_assert(!syd_enabled_rmdir());
	ck_assert_int_eq(syd_enable_rmdir(), 0);
	ck_assert(syd_enabled_rmdir());
	ck_assert_int_eq(syd_disable_rmdir(), 0);
	ck_assert(!syd_enabled_rmdir());

	ck_assert(!syd_enabled_chown());
	ck_assert_int_eq(syd_enable_chown(), 0);
	ck_assert(syd_enabled_chown());
	ck_assert_int_eq(syd_disable_chown(), 0);
	ck_assert(!syd_enabled_chown());

	ck_assert(!syd_enabled_chgrp());
	ck_assert_int_eq(syd_enable_chgrp(), 0);
	ck_assert(syd_enabled_chgrp());
	ck_assert_int_eq(syd_disable_chgrp(), 0);
	ck_assert(!syd_enabled_chgrp());

	ck_assert(!syd_enabled_chmod());
	ck_assert_int_eq(syd_enable_chmod(), 0);
	ck_assert(syd_enabled_chmod());
	ck_assert_int_eq(syd_disable_chmod(), 0);
	ck_assert(!syd_enabled_chmod());

	ck_assert(!syd_enabled_chattr());
	ck_assert_int_eq(syd_enable_chattr(), 0);
	ck_assert(syd_enabled_chattr());
	ck_assert_int_eq(syd_disable_chattr(), 0);
	ck_assert(!syd_enabled_chattr());

	ck_assert(!syd_enabled_chroot());
	ck_assert_int_eq(syd_enable_chroot(), 0);
	ck_assert(syd_enabled_chroot());
	ck_assert_int_eq(syd_disable_chroot(), 0);
	ck_assert(!syd_enabled_chroot());

	ck_assert(!syd_enabled_utime());
	ck_assert_int_eq(syd_enable_utime(), 0);
	ck_assert(syd_enabled_utime());
	ck_assert_int_eq(syd_disable_utime(), 0);
	ck_assert(!syd_enabled_utime());

	ck_assert(!syd_enabled_mkbdev());
	ck_assert_int_eq(syd_enable_mkbdev(), 0);
	ck_assert(syd_enabled_mkbdev());
	ck_assert_int_eq(syd_disable_mkbdev(), 0);
	ck_assert(!syd_enabled_mkbdev());

	ck_assert(!syd_enabled_mkcdev());
	ck_assert_int_eq(syd_enable_mkcdev(), 0);
	ck_assert(syd_enabled_mkcdev());
	ck_assert_int_eq(syd_disable_mkcdev(), 0);
	ck_assert(!syd_enabled_mkcdev());

	ck_assert(!syd_enabled_mkfifo());
	ck_assert_int_eq(syd_enable_mkfifo(), 0);
	ck_assert(syd_enabled_mkfifo());
	ck_assert_int_eq(syd_disable_mkfifo(), 0);
	ck_assert(!syd_enabled_mkfifo());

	ck_assert(!syd_enabled_mktemp());
	ck_assert_int_eq(syd_enable_mktemp(), 0);
	ck_assert(syd_enabled_mktemp());
	ck_assert_int_eq(syd_disable_mktemp(), 0);
	ck_assert(!syd_enabled_mktemp());

	ck_assert(!syd_enabled_net());
	ck_assert_int_eq(syd_enable_net(), 0);
	ck_assert(syd_enabled_net());
	ck_assert_int_eq(syd_disable_net(), 0);
	ck_assert(!syd_enabled_net());

	ck_assert(syd_enabled_mem());
	ck_assert_int_eq(syd_disable_mem(), 0);
	ck_assert(!syd_enabled_mem());

	ck_assert(!syd_enabled_pid());
	ck_assert_int_eq(syd_enable_pid(), 0);
	ck_assert(syd_enabled_pid());
	ck_assert_int_eq(syd_disable_pid(), 0);
	ck_assert(!syd_enabled_pid());

	ck_assert(syd_enabled_force());
	ck_assert_int_eq(syd_disable_force(), 0);
	ck_assert(!syd_enabled_force());

	ck_assert(!syd_enabled_tpe());
	ck_assert_int_eq(syd_enable_tpe(), 0);
	ck_assert(syd_enabled_tpe());
	ck_assert_int_eq(syd_disable_tpe(), 0);
	ck_assert(!syd_enabled_tpe());
}
END_TEST

START_TEST(test_resources)
{
	yajl_val root, v;

	ck_assert_int_eq(syd_mem_max("1G"), 0);
	root = info();
	v = yajl_tree_get(root, (const char*[]) {
		"mem_max", NULL
	}, yajl_t_number);
	ck_assert_msg(v, "mem_max exists");
	ck_assert_int_eq((int)(json_ll(v) / (1024 * 1024 * 1024)), 1);
	yajl_tree_free(root);

	ck_assert_int_eq(syd_mem_max("10G"), 0);
	root = info();
	v = yajl_tree_get(root, (const char*[]) {
		"mem_max", NULL
	}, yajl_t_number);
	ck_assert_msg(v, "mem_max exists");
	ck_assert_int_eq((int)(json_ll(v) / (1024 * 1024 * 1024)), 10);
	yajl_tree_free(root);

	ck_assert_int_eq(syd_mem_vm_max("1G"), 0);
	root = info();
	v = yajl_tree_get(root, (const char*[]) {
		"mem_vm_max", NULL
	}, yajl_t_number);
	ck_assert_msg(v, "mem_vm_max exists");
	ck_assert_int_eq((int)(json_ll(v) / (1024 * 1024 * 1024)), 1);
	yajl_tree_free(root);

	ck_assert_int_eq(syd_mem_vm_max("10G"), 0);
	root = info();
	v = yajl_tree_get(root, (const char*[]) {
		"mem_vm_max", NULL
	}, yajl_t_number);
	ck_assert_msg(v, "mem_vm_max exists");
	ck_assert_int_eq((int)(json_ll(v) / (1024 * 1024 * 1024)), 10);
	yajl_tree_free(root);

	ck_assert_int_eq(syd_pid_max(4096), 0);
	root = info();
	v = yajl_tree_get(root, (const char*[]) {
		"pid_max", NULL
	}, yajl_t_number);
	ck_assert_msg(v, "pid_max exists");
	ck_assert_int_eq((int)json_ll(v), 4096);
	yajl_tree_free(root);

	ck_assert_int_eq(syd_pid_max(8192), 0);
	root = info();
	v = yajl_tree_get(root, (const char*[]) {
		"pid_max", NULL
	}, yajl_t_number);
	ck_assert_msg(v, "pid_max exists");
	ck_assert_int_eq((int)json_ll(v), 8192);
	yajl_tree_free(root);
}
END_TEST

START_TEST(test_glob_rules)
{
	const char *p = TEST_OUT_PATH;
	yajl_val root;
	int idx;

	ck_assert_int_eq(syd_walk_add(ACTION_ALLOW, p), 0);
	root = info(); idx = find_glob_rule(root, "allow", "walk", p);
	ck_assert_int_eq(idx, 0); yajl_tree_free(root);
	ck_assert_int_eq(syd_walk_del(ACTION_ALLOW, p), 0);
	root = info(); idx = find_glob_rule(root, "allow", "walk", p);
	ck_assert_int_eq(idx, -1); yajl_tree_free(root);
	ck_assert_int_eq(syd_walk_add(ACTION_ALLOW, p), 0);
	ck_assert_int_eq(syd_walk_add(ACTION_ALLOW, p), 0);
	ck_assert_int_eq(syd_walk_add(ACTION_ALLOW, p), 0);
	ck_assert_int_eq(syd_walk_rem(ACTION_ALLOW, p), 0);
	root = info(); idx = find_glob_rule(root, "allow", "walk", p);
	ck_assert_int_eq(idx, -1); yajl_tree_free(root);

	ck_assert_int_eq(syd_stat_add(ACTION_DENY, p), 0);
	root = info(); idx = find_glob_rule(root, "deny", "stat", p);
	ck_assert_int_eq(idx, 0); yajl_tree_free(root);
	ck_assert_int_eq(syd_stat_del(ACTION_DENY, p), 0);
	root = info(); idx = find_glob_rule(root, "deny", "stat", p);
	ck_assert_int_eq(idx, -1); yajl_tree_free(root);
	ck_assert_int_eq(syd_stat_add(ACTION_DENY, p), 0);
	ck_assert_int_eq(syd_stat_add(ACTION_DENY, p), 0);
	ck_assert_int_eq(syd_stat_add(ACTION_DENY, p), 0);
	ck_assert_int_eq(syd_stat_rem(ACTION_DENY, p), 0);
	root = info(); idx = find_glob_rule(root, "deny", "stat", p);
	ck_assert_int_eq(idx, -1); yajl_tree_free(root);

	ck_assert_int_eq(syd_read_add(ACTION_FILTER, p), 0);
	root = info(); idx = find_glob_rule(root, "filter", "read", p);
	ck_assert_int_eq(idx, 0); yajl_tree_free(root);
	ck_assert_int_eq(syd_read_del(ACTION_FILTER, p), 0);
	root = info(); idx = find_glob_rule(root, "filter", "read", p);
	ck_assert_int_eq(idx, -1); yajl_tree_free(root);
	ck_assert_int_eq(syd_read_add(ACTION_FILTER, p), 0);
	ck_assert_int_eq(syd_read_add(ACTION_FILTER, p), 0);
	ck_assert_int_eq(syd_read_add(ACTION_FILTER, p), 0);
	ck_assert_int_eq(syd_read_rem(ACTION_FILTER, p), 0);
	root = info(); idx = find_glob_rule(root, "filter", "read", p);
	ck_assert_int_eq(idx, -1); yajl_tree_free(root);

	ck_assert_int_eq(syd_write_add(ACTION_ALLOW, p), 0);
	root = info(); idx = find_glob_rule(root, "allow", "write", p);
	ck_assert_int_eq(idx, 0); yajl_tree_free(root);
	ck_assert_int_eq(syd_write_del(ACTION_ALLOW, p), 0);
	root = info(); idx = find_glob_rule(root, "allow", "write", p);
	ck_assert_int_eq(idx, -1); yajl_tree_free(root);
	ck_assert_int_eq(syd_write_add(ACTION_ALLOW, p), 0);
	ck_assert_int_eq(syd_write_add(ACTION_ALLOW, p), 0);
	ck_assert_int_eq(syd_write_add(ACTION_ALLOW, p), 0);
	ck_assert_int_eq(syd_write_rem(ACTION_ALLOW, p), 0);
	root = info(); idx = find_glob_rule(root, "allow", "write", p);
	ck_assert_int_eq(idx, -1); yajl_tree_free(root);

	ck_assert_int_eq(syd_exec_add(ACTION_DENY, p), 0);
	root = info(); idx = find_glob_rule(root, "deny", "exec", p);
	ck_assert_int_eq(idx, 0); yajl_tree_free(root);
	ck_assert_int_eq(syd_exec_del(ACTION_DENY, p), 0);
	root = info(); idx = find_glob_rule(root, "deny", "exec", p);
	ck_assert_int_eq(idx, -1); yajl_tree_free(root);
	ck_assert_int_eq(syd_exec_add(ACTION_DENY, p), 0);
	ck_assert_int_eq(syd_exec_add(ACTION_DENY, p), 0);
	ck_assert_int_eq(syd_exec_add(ACTION_DENY, p), 0);
	ck_assert_int_eq(syd_exec_rem(ACTION_DENY, p), 0);
	root = info(); idx = find_glob_rule(root, "deny", "exec", p);
	ck_assert_int_eq(idx, -1); yajl_tree_free(root);

	ck_assert_int_eq(syd_create_add(ACTION_ALLOW, p), 0);
	root = info(); idx = find_glob_rule(root, "allow", "create", p);
	ck_assert_int_eq(idx, 0); yajl_tree_free(root);
	ck_assert_int_eq(syd_create_del(ACTION_ALLOW, p), 0);
	root = info(); idx = find_glob_rule(root, "allow", "create", p);
	ck_assert_int_eq(idx, -1); yajl_tree_free(root);
	ck_assert_int_eq(syd_create_add(ACTION_ALLOW, p), 0);
	ck_assert_int_eq(syd_create_add(ACTION_ALLOW, p), 0);
	ck_assert_int_eq(syd_create_add(ACTION_ALLOW, p), 0);
	ck_assert_int_eq(syd_create_rem(ACTION_ALLOW, p), 0);
	root = info(); idx = find_glob_rule(root, "allow", "create", p);
	ck_assert_int_eq(idx, -1); yajl_tree_free(root);

	ck_assert_int_eq(syd_delete_add(ACTION_DENY, p), 0);
	root = info(); idx = find_glob_rule(root, "deny", "delete", p);
	ck_assert_int_eq(idx, 0); yajl_tree_free(root);
	ck_assert_int_eq(syd_delete_del(ACTION_DENY, p), 0);
	root = info(); idx = find_glob_rule(root, "deny", "delete", p);
	ck_assert_int_eq(idx, -1); yajl_tree_free(root);
	ck_assert_int_eq(syd_delete_add(ACTION_DENY, p), 0);
	ck_assert_int_eq(syd_delete_add(ACTION_DENY, p), 0);
	ck_assert_int_eq(syd_delete_add(ACTION_DENY, p), 0);
	ck_assert_int_eq(syd_delete_rem(ACTION_DENY, p), 0);
	root = info(); idx = find_glob_rule(root, "deny", "delete", p);
	ck_assert_int_eq(idx, -1); yajl_tree_free(root);

	ck_assert_int_eq(syd_rename_add(ACTION_FILTER, p), 0);
	root = info(); idx = find_glob_rule(root, "filter", "rename", p);
	ck_assert_int_eq(idx, 0); yajl_tree_free(root);
	ck_assert_int_eq(syd_rename_del(ACTION_FILTER, p), 0);
	root = info(); idx = find_glob_rule(root, "filter", "rename", p);
	ck_assert_int_eq(idx, -1); yajl_tree_free(root);
	ck_assert_int_eq(syd_rename_add(ACTION_FILTER, p), 0);
	ck_assert_int_eq(syd_rename_add(ACTION_FILTER, p), 0);
	ck_assert_int_eq(syd_rename_add(ACTION_FILTER, p), 0);
	ck_assert_int_eq(syd_rename_rem(ACTION_FILTER, p), 0);
	root = info(); idx = find_glob_rule(root, "filter", "rename", p);
	ck_assert_int_eq(idx, -1); yajl_tree_free(root);

	ck_assert_int_eq(syd_symlink_add(ACTION_ALLOW, p), 0);
	root = info(); idx = find_glob_rule(root, "allow", "symlink", p);
	ck_assert_int_eq(idx, 0); yajl_tree_free(root);
	ck_assert_int_eq(syd_symlink_del(ACTION_ALLOW, p), 0);
	root = info(); idx = find_glob_rule(root, "allow", "symlink", p);
	ck_assert_int_eq(idx, -1); yajl_tree_free(root);
	ck_assert_int_eq(syd_symlink_add(ACTION_ALLOW, p), 0);
	ck_assert_int_eq(syd_symlink_add(ACTION_ALLOW, p), 0);
	ck_assert_int_eq(syd_symlink_add(ACTION_ALLOW, p), 0);
	ck_assert_int_eq(syd_symlink_rem(ACTION_ALLOW, p), 0);
	root = info(); idx = find_glob_rule(root, "allow", "symlink", p);
	ck_assert_int_eq(idx, -1); yajl_tree_free(root);

	ck_assert_int_eq(syd_truncate_add(ACTION_DENY, p), 0);
	root = info(); idx = find_glob_rule(root, "deny", "truncate", p);
	ck_assert_int_eq(idx, 0); yajl_tree_free(root);
	ck_assert_int_eq(syd_truncate_del(ACTION_DENY, p), 0);
	root = info(); idx = find_glob_rule(root, "deny", "truncate", p);
	ck_assert_int_eq(idx, -1); yajl_tree_free(root);
	ck_assert_int_eq(syd_truncate_add(ACTION_DENY, p), 0);
	ck_assert_int_eq(syd_truncate_add(ACTION_DENY, p), 0);
	ck_assert_int_eq(syd_truncate_add(ACTION_DENY, p), 0);
	ck_assert_int_eq(syd_truncate_rem(ACTION_DENY, p), 0);
	root = info(); idx = find_glob_rule(root, "deny", "truncate", p);
	ck_assert_int_eq(idx, -1); yajl_tree_free(root);

	ck_assert_int_eq(syd_chdir_add(ACTION_FILTER, p), 0);
	root = info(); idx = find_glob_rule(root, "filter", "chdir", p);
	ck_assert_int_eq(idx, 0); yajl_tree_free(root);
	ck_assert_int_eq(syd_chdir_del(ACTION_FILTER, p), 0);
	root = info(); idx = find_glob_rule(root, "filter", "chdir", p);
	ck_assert_int_eq(idx, -1); yajl_tree_free(root);
	ck_assert_int_eq(syd_chdir_add(ACTION_FILTER, p), 0);
	ck_assert_int_eq(syd_chdir_add(ACTION_FILTER, p), 0);
	ck_assert_int_eq(syd_chdir_add(ACTION_FILTER, p), 0);
	ck_assert_int_eq(syd_chdir_rem(ACTION_FILTER, p), 0);
	root = info(); idx = find_glob_rule(root, "filter", "chdir", p);
	ck_assert_int_eq(idx, -1); yajl_tree_free(root);

	ck_assert_int_eq(syd_readdir_add(ACTION_ALLOW, p), 0);
	root = info(); idx = find_glob_rule(root, "allow", "readdir", p);
	ck_assert_int_eq(idx, 0); yajl_tree_free(root);
	ck_assert_int_eq(syd_readdir_del(ACTION_ALLOW, p), 0);
	root = info(); idx = find_glob_rule(root, "allow", "readdir", p);
	ck_assert_int_eq(idx, -1); yajl_tree_free(root);
	ck_assert_int_eq(syd_readdir_add(ACTION_ALLOW, p), 0);
	ck_assert_int_eq(syd_readdir_add(ACTION_ALLOW, p), 0);
	ck_assert_int_eq(syd_readdir_add(ACTION_ALLOW, p), 0);
	ck_assert_int_eq(syd_readdir_rem(ACTION_ALLOW, p), 0);
	root = info(); idx = find_glob_rule(root, "allow", "readdir", p);
	ck_assert_int_eq(idx, -1); yajl_tree_free(root);

	ck_assert_int_eq(syd_mkdir_add(ACTION_DENY, p), 0);
	root = info(); idx = find_glob_rule(root, "deny", "mkdir", p);
	ck_assert_int_eq(idx, 0); yajl_tree_free(root);
	ck_assert_int_eq(syd_mkdir_del(ACTION_DENY, p), 0);
	root = info(); idx = find_glob_rule(root, "deny", "mkdir", p);
	ck_assert_int_eq(idx, -1); yajl_tree_free(root);
	ck_assert_int_eq(syd_mkdir_add(ACTION_DENY, p), 0);
	ck_assert_int_eq(syd_mkdir_add(ACTION_DENY, p), 0);
	ck_assert_int_eq(syd_mkdir_add(ACTION_DENY, p), 0);
	ck_assert_int_eq(syd_mkdir_rem(ACTION_DENY, p), 0);
	root = info(); idx = find_glob_rule(root, "deny", "mkdir", p);
	ck_assert_int_eq(idx, -1); yajl_tree_free(root);

	ck_assert_int_eq(syd_rmdir_add(ACTION_FILTER, p), 0);
	root = info(); idx = find_glob_rule(root, "filter", "rmdir", p);
	ck_assert_int_eq(idx, 0); yajl_tree_free(root);
	ck_assert_int_eq(syd_rmdir_del(ACTION_FILTER, p), 0);
	root = info(); idx = find_glob_rule(root, "filter", "rmdir", p);
	ck_assert_int_eq(idx, -1); yajl_tree_free(root);
	ck_assert_int_eq(syd_rmdir_add(ACTION_FILTER, p), 0);
	ck_assert_int_eq(syd_rmdir_add(ACTION_FILTER, p), 0);
	ck_assert_int_eq(syd_rmdir_add(ACTION_FILTER, p), 0);
	ck_assert_int_eq(syd_rmdir_rem(ACTION_FILTER, p), 0);
	root = info(); idx = find_glob_rule(root, "filter", "rmdir", p);
	ck_assert_int_eq(idx, -1); yajl_tree_free(root);

	ck_assert_int_eq(syd_chown_add(ACTION_ALLOW, p), 0);
	root = info(); idx = find_glob_rule(root, "allow", "chown", p);
	ck_assert_int_eq(idx, 0); yajl_tree_free(root);
	ck_assert_int_eq(syd_chown_del(ACTION_ALLOW, p), 0);
	root = info(); idx = find_glob_rule(root, "allow", "chown", p);
	ck_assert_int_eq(idx, -1); yajl_tree_free(root);
	ck_assert_int_eq(syd_chown_add(ACTION_ALLOW, p), 0);
	ck_assert_int_eq(syd_chown_add(ACTION_ALLOW, p), 0);
	ck_assert_int_eq(syd_chown_add(ACTION_ALLOW, p), 0);
	ck_assert_int_eq(syd_chown_rem(ACTION_ALLOW, p), 0);
	root = info(); idx = find_glob_rule(root, "allow", "chown", p);
	ck_assert_int_eq(idx, -1); yajl_tree_free(root);

	ck_assert_int_eq(syd_chgrp_add(ACTION_DENY, p), 0);
	root = info(); idx = find_glob_rule(root, "deny", "chgrp", p);
	ck_assert_int_eq(idx, 0); yajl_tree_free(root);
	ck_assert_int_eq(syd_chgrp_del(ACTION_DENY, p), 0);
	root = info(); idx = find_glob_rule(root, "deny", "chgrp", p);
	ck_assert_int_eq(idx, -1); yajl_tree_free(root);
	ck_assert_int_eq(syd_chgrp_add(ACTION_DENY, p), 0);
	ck_assert_int_eq(syd_chgrp_add(ACTION_DENY, p), 0);
	ck_assert_int_eq(syd_chgrp_add(ACTION_DENY, p), 0);
	ck_assert_int_eq(syd_chgrp_rem(ACTION_DENY, p), 0);
	root = info(); idx = find_glob_rule(root, "deny", "chgrp", p);
	ck_assert_int_eq(idx, -1); yajl_tree_free(root);

	ck_assert_int_eq(syd_chmod_add(ACTION_FILTER, p), 0);
	root = info(); idx = find_glob_rule(root, "filter", "chmod", p);
	ck_assert_int_eq(idx, 0); yajl_tree_free(root);
	ck_assert_int_eq(syd_chmod_del(ACTION_FILTER, p), 0);
	root = info(); idx = find_glob_rule(root, "filter", "chmod", p);
	ck_assert_int_eq(idx, -1); yajl_tree_free(root);
	ck_assert_int_eq(syd_chmod_add(ACTION_FILTER, p), 0);
	ck_assert_int_eq(syd_chmod_add(ACTION_FILTER, p), 0);
	ck_assert_int_eq(syd_chmod_add(ACTION_FILTER, p), 0);
	ck_assert_int_eq(syd_chmod_rem(ACTION_FILTER, p), 0);
	root = info(); idx = find_glob_rule(root, "filter", "chmod", p);
	ck_assert_int_eq(idx, -1); yajl_tree_free(root);

	ck_assert_int_eq(syd_chattr_add(ACTION_ALLOW, p), 0);
	root = info(); idx = find_glob_rule(root, "allow", "chattr", p);
	ck_assert_int_eq(idx, 0); yajl_tree_free(root);
	ck_assert_int_eq(syd_chattr_del(ACTION_ALLOW, p), 0);
	root = info(); idx = find_glob_rule(root, "allow", "chattr", p);
	ck_assert_int_eq(idx, -1); yajl_tree_free(root);
	ck_assert_int_eq(syd_chattr_add(ACTION_ALLOW, p), 0);
	ck_assert_int_eq(syd_chattr_add(ACTION_ALLOW, p), 0);
	ck_assert_int_eq(syd_chattr_add(ACTION_ALLOW, p), 0);
	ck_assert_int_eq(syd_chattr_rem(ACTION_ALLOW, p), 0);
	root = info(); idx = find_glob_rule(root, "allow", "chattr", p);
	ck_assert_int_eq(idx, -1); yajl_tree_free(root);

	ck_assert_int_eq(syd_chroot_add(ACTION_ALLOW, p), 0);
	root = info(); idx = find_glob_rule(root, "allow", "chroot", p);
	ck_assert_int_eq(idx, 0); yajl_tree_free(root);
	ck_assert_int_eq(syd_chroot_del(ACTION_ALLOW, p), 0);
	root = info(); idx = find_glob_rule(root, "allow", "chroot", p);
	ck_assert_int_eq(idx, -1); yajl_tree_free(root);
	ck_assert_int_eq(syd_chroot_add(ACTION_ALLOW, p), 0);
	ck_assert_int_eq(syd_chroot_add(ACTION_ALLOW, p), 0);
	ck_assert_int_eq(syd_chroot_add(ACTION_ALLOW, p), 0);
	ck_assert_int_eq(syd_chroot_rem(ACTION_ALLOW, p), 0);
	root = info(); idx = find_glob_rule(root, "allow", "chroot", p);
	ck_assert_int_eq(idx, -1); yajl_tree_free(root);

	ck_assert_int_eq(syd_utime_add(ACTION_DENY, p), 0);
	root = info(); idx = find_glob_rule(root, "deny", "utime", p);
	ck_assert_int_eq(idx, 0); yajl_tree_free(root);
	ck_assert_int_eq(syd_utime_del(ACTION_DENY, p), 0);
	root = info(); idx = find_glob_rule(root, "deny", "utime", p);
	ck_assert_int_eq(idx, -1); yajl_tree_free(root);
	ck_assert_int_eq(syd_utime_add(ACTION_DENY, p), 0);
	ck_assert_int_eq(syd_utime_add(ACTION_DENY, p), 0);
	ck_assert_int_eq(syd_utime_add(ACTION_DENY, p), 0);
	ck_assert_int_eq(syd_utime_rem(ACTION_DENY, p), 0);
	root = info(); idx = find_glob_rule(root, "deny", "utime", p);
	ck_assert_int_eq(idx, -1); yajl_tree_free(root);

	ck_assert_int_eq(syd_mkbdev_add(ACTION_FILTER, p), 0);
	root = info(); idx = find_glob_rule(root, "filter", "mkbdev", p);
	ck_assert_int_eq(idx, 0); yajl_tree_free(root);
	ck_assert_int_eq(syd_mkbdev_del(ACTION_FILTER, p), 0);
	root = info(); idx = find_glob_rule(root, "filter", "mkbdev", p);
	ck_assert_int_eq(idx, -1); yajl_tree_free(root);
	ck_assert_int_eq(syd_mkbdev_add(ACTION_FILTER, p), 0);
	ck_assert_int_eq(syd_mkbdev_add(ACTION_FILTER, p), 0);
	ck_assert_int_eq(syd_mkbdev_add(ACTION_FILTER, p), 0);
	ck_assert_int_eq(syd_mkbdev_rem(ACTION_FILTER, p), 0);
	root = info(); idx = find_glob_rule(root, "filter", "mkbdev", p);
	ck_assert_int_eq(idx, -1); yajl_tree_free(root);

	ck_assert_int_eq(syd_mkcdev_add(ACTION_ALLOW, p), 0);
	root = info(); idx = find_glob_rule(root, "allow", "mkcdev", p);
	ck_assert_int_eq(idx, 0); yajl_tree_free(root);
	ck_assert_int_eq(syd_mkcdev_del(ACTION_ALLOW, p), 0);
	root = info(); idx = find_glob_rule(root, "allow", "mkcdev", p);
	ck_assert_int_eq(idx, -1); yajl_tree_free(root);
	ck_assert_int_eq(syd_mkcdev_add(ACTION_ALLOW, p), 0);
	ck_assert_int_eq(syd_mkcdev_add(ACTION_ALLOW, p), 0);
	ck_assert_int_eq(syd_mkcdev_add(ACTION_ALLOW, p), 0);
	ck_assert_int_eq(syd_mkcdev_rem(ACTION_ALLOW, p), 0);
	root = info(); idx = find_glob_rule(root, "allow", "mkcdev", p);
	ck_assert_int_eq(idx, -1); yajl_tree_free(root);

	ck_assert_int_eq(syd_mkfifo_add(ACTION_DENY, p), 0);
	root = info(); idx = find_glob_rule(root, "deny", "mkfifo", p);
	ck_assert_int_eq(idx, 0); yajl_tree_free(root);
	ck_assert_int_eq(syd_mkfifo_del(ACTION_DENY, p), 0);
	root = info(); idx = find_glob_rule(root, "deny", "mkfifo", p);
	ck_assert_int_eq(idx, -1); yajl_tree_free(root);
	ck_assert_int_eq(syd_mkfifo_add(ACTION_DENY, p), 0);
	ck_assert_int_eq(syd_mkfifo_add(ACTION_DENY, p), 0);
	ck_assert_int_eq(syd_mkfifo_add(ACTION_DENY, p), 0);
	ck_assert_int_eq(syd_mkfifo_rem(ACTION_DENY, p), 0);
	root = info(); idx = find_glob_rule(root, "deny", "mkfifo", p);
	ck_assert_int_eq(idx, -1); yajl_tree_free(root);

	ck_assert_int_eq(syd_mktemp_add(ACTION_FILTER, p), 0);
	root = info(); idx = find_glob_rule(root, "filter", "mktemp", p);
	ck_assert_int_eq(idx, 0); yajl_tree_free(root);
	ck_assert_int_eq(syd_mktemp_del(ACTION_FILTER, p), 0);
	root = info(); idx = find_glob_rule(root, "filter", "mktemp", p);
	ck_assert_int_eq(idx, -1); yajl_tree_free(root);
	ck_assert_int_eq(syd_mktemp_add(ACTION_FILTER, p), 0);
	ck_assert_int_eq(syd_mktemp_add(ACTION_FILTER, p), 0);
	ck_assert_int_eq(syd_mktemp_add(ACTION_FILTER, p), 0);
	ck_assert_int_eq(syd_mktemp_rem(ACTION_FILTER, p), 0);
	root = info(); idx = find_glob_rule(root, "filter", "mktemp", p);
	ck_assert_int_eq(idx, -1); yajl_tree_free(root);

	ck_assert_int_eq(syd_net_bind_add(ACTION_ALLOW, p), 0);
	root = info(); idx = find_glob_rule(root, "allow", "net/bind", p);
	ck_assert_int_eq(idx, 0); yajl_tree_free(root);
	ck_assert_int_eq(syd_net_bind_del(ACTION_ALLOW, p), 0);
	root = info(); idx = find_glob_rule(root, "allow", "net/bind", p);
	ck_assert_int_eq(idx, -1); yajl_tree_free(root);
	ck_assert_int_eq(syd_net_bind_add(ACTION_ALLOW, p), 0);
	ck_assert_int_eq(syd_net_bind_add(ACTION_ALLOW, p), 0);
	ck_assert_int_eq(syd_net_bind_add(ACTION_ALLOW, p), 0);
	ck_assert_int_eq(syd_net_bind_rem(ACTION_ALLOW, p), 0);
	root = info(); idx = find_glob_rule(root, "allow", "net/bind", p);
	ck_assert_int_eq(idx, -1); yajl_tree_free(root);

	ck_assert_int_eq(syd_net_connect_add(ACTION_ALLOW, p), 0);
	root = info(); idx = find_glob_rule(root, "allow", "net/connect", p);
	ck_assert_int_eq(idx, 0); yajl_tree_free(root);
	ck_assert_int_eq(syd_net_connect_del(ACTION_ALLOW, p), 0);
	root = info(); idx = find_glob_rule(root, "allow", "net/connect", p);
	ck_assert_int_eq(idx, -1); yajl_tree_free(root);
	ck_assert_int_eq(syd_net_connect_add(ACTION_ALLOW, p), 0);
	ck_assert_int_eq(syd_net_connect_add(ACTION_ALLOW, p), 0);
	ck_assert_int_eq(syd_net_connect_add(ACTION_ALLOW, p), 0);
	ck_assert_int_eq(syd_net_connect_rem(ACTION_ALLOW, p), 0);
	root = info(); idx = find_glob_rule(root, "allow", "net/connect", p);
	ck_assert_int_eq(idx, -1); yajl_tree_free(root);

	ck_assert_int_eq(syd_net_sendfd_add(ACTION_ALLOW, p), 0);
	root = info(); idx = find_glob_rule(root, "allow", "net/sendfd", p);
	ck_assert_int_eq(idx, 0); yajl_tree_free(root);
	ck_assert_int_eq(syd_net_sendfd_del(ACTION_ALLOW, p), 0);
	root = info(); idx = find_glob_rule(root, "allow", "net/sendfd", p);
	ck_assert_int_eq(idx, -1); yajl_tree_free(root);
	ck_assert_int_eq(syd_net_sendfd_add(ACTION_ALLOW, p), 0);
	ck_assert_int_eq(syd_net_sendfd_add(ACTION_ALLOW, p), 0);
	ck_assert_int_eq(syd_net_sendfd_add(ACTION_ALLOW, p), 0);
	ck_assert_int_eq(syd_net_sendfd_rem(ACTION_ALLOW, p), 0);
	root = info(); idx = find_glob_rule(root, "allow", "net/sendfd", p);
	ck_assert_int_eq(idx, -1); yajl_tree_free(root);
}
END_TEST

START_TEST(test_cidr_rules)
{
	const char *addr = "127.3.1.4/8";
	char spec[128];
	yajl_val root;
	int idx;

	snprintf(spec, sizeof(spec), "%s!%d", addr, 31415);
	ck_assert_int_eq(syd_net_bind_add(ACTION_ALLOW, spec), 0);
	root = info();
	idx = find_cidr_rule(root, "allow", "net/bind", addr, 31415, 31415);
	ck_assert_int_ge(idx, 0); yajl_tree_free(root);
	ck_assert_int_eq(syd_net_bind_del(ACTION_ALLOW, spec), 0);
	root = info();
	idx = find_cidr_rule(root, "allow", "net/bind", addr, 31415, 31415);
	ck_assert_int_lt(idx, 0); yajl_tree_free(root);
	ck_assert_int_eq(syd_net_bind_add(ACTION_ALLOW, spec), 0);
	ck_assert_int_eq(syd_net_bind_add(ACTION_ALLOW, spec), 0);
	ck_assert_int_eq(syd_net_bind_add(ACTION_ALLOW, spec), 0);
	ck_assert_int_eq(syd_net_bind_rem(ACTION_ALLOW, spec), 0);
	root = info();
	idx = find_cidr_rule(root, "allow", "net/bind", addr, 31415, 31415);
	ck_assert_int_lt(idx, 0); yajl_tree_free(root);

	snprintf(spec, sizeof(spec), "%s!%d-%d", addr, 10000, 10010);
	ck_assert_int_eq(syd_net_bind_add(ACTION_DENY, spec), 0);
	root = info();
	idx = find_cidr_rule(root, "deny", "net/bind", addr, 10000, 10010);
	ck_assert_int_ge(idx, 0); yajl_tree_free(root);
	ck_assert_int_eq(syd_net_bind_del(ACTION_DENY, spec), 0);
	root = info();
	idx = find_cidr_rule(root, "deny", "net/bind", addr, 10000, 10010);
	ck_assert_int_lt(idx, 0); yajl_tree_free(root);
	ck_assert_int_eq(syd_net_bind_add(ACTION_DENY, spec), 0);
	ck_assert_int_eq(syd_net_bind_add(ACTION_DENY, spec), 0);
	ck_assert_int_eq(syd_net_bind_add(ACTION_DENY, spec), 0);
	ck_assert_int_eq(syd_net_bind_rem(ACTION_DENY, spec), 0);
	root = info();
	idx = find_cidr_rule(root, "deny", "net/bind", addr, 10000, 10010);
	ck_assert_int_lt(idx, 0); yajl_tree_free(root);

	snprintf(spec, sizeof(spec), "%s!%d", addr, 31415);
	ck_assert_int_eq(syd_net_connect_add(ACTION_FILTER, spec), 0);
	root = info();
	idx = find_cidr_rule(root, "filter", "net/connect", addr, 31415, 31415);
	ck_assert_int_ge(idx, 0); yajl_tree_free(root);
	ck_assert_int_eq(syd_net_connect_del(ACTION_FILTER, spec), 0);
	root = info();
	idx = find_cidr_rule(root, "filter", "net/connect", addr, 31415, 31415);
	ck_assert_int_lt(idx, 0); yajl_tree_free(root);
	ck_assert_int_eq(syd_net_connect_add(ACTION_FILTER, spec), 0);
	ck_assert_int_eq(syd_net_connect_add(ACTION_FILTER, spec), 0);
	ck_assert_int_eq(syd_net_connect_add(ACTION_FILTER, spec), 0);
	ck_assert_int_eq(syd_net_connect_rem(ACTION_FILTER, spec), 0);
	root = info();
	idx = find_cidr_rule(root, "filter", "net/connect", addr, 31415, 31415);
	ck_assert_int_lt(idx, 0); yajl_tree_free(root);

	snprintf(spec, sizeof(spec), "%s!%d-%d", addr, 10000, 10010);
	ck_assert_int_eq(syd_net_connect_add(ACTION_ALLOW, spec), 0);
	root = info();
	idx = find_cidr_rule(root, "allow", "net/connect", addr, 10000, 10010);
	ck_assert_int_ge(idx, 0); yajl_tree_free(root);
	ck_assert_int_eq(syd_net_connect_del(ACTION_ALLOW, spec), 0);
	root = info();
	idx = find_cidr_rule(root, "allow", "net/connect", addr, 10000, 10010);
	ck_assert_int_lt(idx, 0); yajl_tree_free(root);
	ck_assert_int_eq(syd_net_connect_add(ACTION_ALLOW, spec), 0);
	ck_assert_int_eq(syd_net_connect_add(ACTION_ALLOW, spec), 0);
	ck_assert_int_eq(syd_net_connect_add(ACTION_ALLOW, spec), 0);
	ck_assert_int_eq(syd_net_connect_rem(ACTION_ALLOW, spec), 0);
	root = info();
	idx = find_cidr_rule(root, "allow", "net/connect", addr, 10000, 10010);
	ck_assert_int_lt(idx, 0); yajl_tree_free(root);
}
END_TEST

START_TEST(test_ioctl_deny)
{
	ck_assert_int_eq(syd_ioctl_deny(0xdeadca11u), 0);
}
END_TEST

START_TEST(test_force_rules)
{
	const char *p = TEST_OUT_PATH;
	yajl_val root;
	int r;

	/* Invalid actions. */
	ck_assert_int_eq(syd_force_add(p, "sha256", "0", -1), -EINVAL);
	ck_assert_int_eq(syd_force_add(p, "sha256", "0", -10), -EINVAL);
	ck_assert_int_eq(syd_force_add(p, "sha256", "0", 10), -EINVAL);
	ck_assert_int_eq(syd_force_add(p, "sha256", "0", 100), -EINVAL);
	ck_assert_int_eq(syd_force_add(p, "sha256", "0", ACTION_ALLOW), -EINVAL);

	/*
	 * Test many algorithms with correct digest sizes.
	 * Skip gracefully on -EOPNOTSUPP (kernel lacks algorithm).
	 * At least one must succeed.
	 */
	struct {
		const char *alg;
		int hex_chars;
	} algs[] = {
		{"crc32",      8},
		{"crc32c",     8},
		{"md4",       32},
		{"md5",       32},
		{"sha1",      40},
		{"sha224",    56},
		{"sha256",    64},
		{"sha384",    96},
		{"sha512",   128},
		{"sha3-224",  56},
		{"sha3-256",  64},
		{"sha3-384",  96},
		{"sha3-512", 128},
		{"rmd160",    40},
		{"sm3",       64},
		{"blake2b-256", 64},
		{"blake2b-512", 128},
		{"streebog256", 64},
		{"streebog512", 128},
		{"wp256",     64},
		{"wp384",     96},
		{"wp512",    128},
		{"xxhash64",  16},
	};
	int n_algs = sizeof(algs) / sizeof(algs[0]);
	int n_ok = 0;

	for (int i = 0; i < n_algs; i++) {
		/* Build a non-zero hex hash of the right length. */
		char hash[256];
		memset(hash, 'a', sizeof(hash));
		hash[algs[i].hex_chars] = '\0';

		/* Try add */
		r = syd_force_add(p, algs[i].alg, hash, ACTION_KILL);
		if (r == -EOPNOTSUPP) {
			continue; /* kernel doesn't support this algorithm */
		}
		ck_assert_msg(r == 0,
		              "force_add(%s) failed: %d", algs[i].alg, r);

		/* Verify rule present */
		root = info();
		ck_assert_int_ge(find_force_rule(root, "kill", hash, p), 0);
		yajl_tree_free(root);

		/* Delete and verify absent */
		ck_assert_int_eq(syd_force_del(p), 0);
		root = info();
		ck_assert_int_lt(find_force_rule(root, "kill", hash, p), 0);
		yajl_tree_free(root);

		n_ok++;
	}
	ck_assert_msg(n_ok > 0, "No hash algorithms were supported!");

	/* Test clear: add two with any working algorithm, then clear. */
	for (int i = 0; i < n_algs; i++) {
		char h1[256], h2[256];
		memset(h1, 'b', sizeof(h1));
		h1[algs[i].hex_chars] = '\0';
		memset(h2, 'c', sizeof(h2));
		h2[algs[i].hex_chars] = '\0';

		const char *p1 = "/tmp/libsyd_1";
		const char *p2 = "/tmp/libsyd_2";
		if (syd_force_add(p1, algs[i].alg, h1, ACTION_WARN) == -EOPNOTSUPP)
			continue;
		ck_assert_int_eq(syd_force_add(p2, algs[i].alg, h2, ACTION_KILL), 0);
		ck_assert_int_eq(syd_force_clr(), 0);
		break;
	}
}
END_TEST

START_TEST(test_segvguard)
{
	yajl_val root, v;

	root = info();
	v = yajl_tree_get(root, (const char*[]) {
		"segvguard_expiry", NULL
	}, yajl_t_number);
	ck_assert_msg(v, "segvguard_expiry");
	long long expiry0 = json_ll(v);
	v = yajl_tree_get(root, (const char*[]) {
		"segvguard_suspension", NULL
	}, yajl_t_number);
	ck_assert_msg(v, "segvguard_suspension");
	long long susp0 = json_ll(v);
	v = yajl_tree_get(root, (const char*[]) {
		"segvguard_maxcrashes", NULL
	}, yajl_t_number);
	ck_assert_msg(v, "segvguard_maxcrashes");
	long long max0 = json_ll(v);
	yajl_tree_free(root);

	ck_assert_int_eq(syd_segvguard_expiry(42), 0);
	root = info();
	v = yajl_tree_get(root, (const char*[]) {
		"segvguard_expiry", NULL
	}, yajl_t_number);
	ck_assert_int_eq((int)json_ll(v), 42);
	yajl_tree_free(root);

	ck_assert_int_eq(syd_segvguard_suspension(43), 0);
	root = info();
	v = yajl_tree_get(root, (const char*[]) {
		"segvguard_suspension", NULL
	}, yajl_t_number);
	ck_assert_int_eq((int)json_ll(v), 43);
	yajl_tree_free(root);

	ck_assert_int_eq(syd_segvguard_maxcrashes(44), 0);
	root = info();
	v = yajl_tree_get(root, (const char*[]) {
		"segvguard_maxcrashes", NULL
	}, yajl_t_number);
	ck_assert_int_eq((int)json_ll(v), 44);
	yajl_tree_free(root);

	(void)syd_segvguard_expiry((int)expiry0);
	(void)syd_segvguard_suspension((int)susp0);
	(void)syd_segvguard_maxcrashes((int)max0);
}
END_TEST

START_TEST(test_load_config_from_fd)
{
	char tmpl[] = "/tmp/libsyd_conf_XXXXXX";
	int fd = mkstemp(tmpl);
	ck_assert_int_ge(fd, 0);

	const char *cfg1 = "pid/max:77\n";
	ck_assert_int_eq((int)write(fd, cfg1, (int)strlen(cfg1)), (int)strlen(cfg1));
	ck_assert_int_eq((int)lseek(fd, 0, SEEK_SET), 0);
	ck_assert_int_eq(syd_load(fd), 0);
	yajl_val root = info();
	yajl_val v = yajl_tree_get(root, (const char*[]) {
		"pid_max", NULL
	}, yajl_t_number);
	ck_assert_msg(v, "pid_max exists");
	ck_assert_int_eq((int)json_ll(v), 77);
	yajl_tree_free(root);

	ck_assert_int_eq((int)lseek(fd, 4096, SEEK_SET), 4096);
	const char *cfg2 = "pid/max:81\n";
	ck_assert_int_eq((int)write(fd, cfg2, (int)strlen(cfg2)), (int)strlen(cfg2));
	ck_assert_int_eq((int)lseek(fd, 4096, SEEK_SET), 4096);
	ck_assert_int_eq(syd_load(fd), 0);
	root = info();
	v = yajl_tree_get(root, (const char*[]) {
		"pid_max", NULL
	}, yajl_t_number);
	ck_assert_msg(v, "pid_max exists");
	ck_assert_int_eq((int)json_ll(v), 81);
	yajl_tree_free(root);

	close(fd);
	unlink(tmpl);
}
END_TEST

START_TEST(test_lock)
{
	ck_assert_int_eq(syd_lock(LOCK_OFF), -EPERM);
	ck_assert_int_eq(syd_lock(LOCK_EXEC), 0); // no-op
	ck_assert_int_eq(syd_lock(LOCK_DROP), 0);
	ck_assert_int_eq(syd_lock(LOCK_ON), 0);

	ck_assert_int_eq(syd_lock(LOCK_OFF), -ENOENT);
	ck_assert_int_eq(syd_lock(LOCK_EXEC), -ENOENT);
	ck_assert_int_eq(syd_lock(LOCK_DROP), -ENOENT);
	ck_assert_int_eq(syd_lock(LOCK_READ), -ENOENT);
	ck_assert_int_eq(syd_lock(LOCK_ON), -ENOENT);
}
END_TEST

START_TEST(test_exec_write_file)
{
	const char *file = "/bin/sh";
	const char *argv[] = { "-c", "echo 42 > \"" TEST_OUT_PATH "\"", NULL };
	ck_assert_int_eq(syd_exec(file, argv), 0);

	sleep(3);
	FILE *fp = fopen(TEST_OUT_PATH, "r");
	ck_assert_ptr_nonnull(fp);
	char buf[64] = {0};
	ck_assert_ptr_nonnull(fgets(buf, sizeof(buf), fp));
	fclose(fp);
	size_t L = strlen(buf);
	if (L && buf[L - 1] == '\n') buf[L - 1] = '\0';
	ck_assert_str_eq(buf, "42");
}
END_TEST

/* Runner */

static Suite *make_suite(void)
{
	Suite *s = suite_create("libsyd");
	TCase *tc = tcase_create("core");

	tcase_add_test(tc, test_api_version);
	tcase_add_test(tc, test_enable_flags);
	tcase_add_test(tc, test_resources);
	tcase_add_test(tc, test_glob_rules);
	tcase_add_test(tc, test_cidr_rules);
	tcase_add_test(tc, test_ioctl_deny);
	tcase_add_test(tc, test_force_rules);
	tcase_add_test(tc, test_segvguard);
	tcase_add_test(tc, test_load_config_from_fd);
	tcase_add_test(tc, test_exec_write_file);
	tcase_add_test(tc, test_lock);

	suite_add_tcase(s, tc);
	return s;
}

int main(void)
{
	if (syd_check()) {
		printf("syd_check: not under syd; skipping\n");
		return 0;
	}

	Suite *s = make_suite();
	SRunner *sr = srunner_create(s);
	srunner_set_fork_status(sr, CK_NOFORK);
	srunner_set_tap(sr, "/proc/self/fd/1");
	srunner_run_all(sr, CK_NORMAL);
	return srunner_ntests_failed(sr);
}
