/* Copyright (C) CZ.NIC, z.s.p.o. * SPDX-License-Identifier: GPL-3.0-or-later */ #include "daemon/bindings/impl.h" /** @internal return cache, or throw lua error if not open */ static struct kr_cache * cache_assert_open(lua_State *L) { struct kr_cache *cache = &the_worker->engine->resolver.cache; if (kr_fails_assert(cache) || !kr_cache_is_open(cache)) lua_error_p(L, "no cache is open yet, use cache.open() or cache.size, etc."); return cache; } /** Return available cached backends. */ static int cache_backends(lua_State *L) { struct engine *engine = the_worker->engine; lua_newtable(L); for (unsigned i = 0; i < engine->backends.len; ++i) { const struct kr_cdb_api *api = engine->backends.at[i]; lua_pushboolean(L, api == engine->resolver.cache.api); lua_setfield(L, -2, api->name); } return 1; } /** Return number of cached records. */ static int cache_count(lua_State *L) { struct kr_cache *cache = cache_assert_open(L); int count = cache->api->count(cache->db, &cache->stats); if (count >= 0) { /* First key is a version counter, omit it if nonempty. */ lua_pushinteger(L, count ? count - 1 : 0); return 1; } return 0; } /** Return time of last checkpoint, or re-set it if passed `true`. */ static int cache_checkpoint(lua_State *L) { struct kr_cache *cache = cache_assert_open(L); if (lua_gettop(L) == 0) { /* Return the current value. */ lua_newtable(L); lua_pushnumber(L, cache->checkpoint_monotime); lua_setfield(L, -2, "monotime"); lua_newtable(L); lua_pushnumber(L, cache->checkpoint_walltime.tv_sec); lua_setfield(L, -2, "sec"); lua_pushnumber(L, cache->checkpoint_walltime.tv_usec); lua_setfield(L, -2, "usec"); lua_setfield(L, -2, "walltime"); return 1; } if (lua_gettop(L) != 1 || !lua_isboolean(L, 1) || !lua_toboolean(L, 1)) lua_error_p(L, "cache.checkpoint() takes no parameters or a true value"); kr_cache_make_checkpoint(cache); return 1; } /** Return cache statistics. */ static int cache_stats(lua_State *L) { struct kr_cache *cache = cache_assert_open(L); lua_newtable(L); #define add_stat(name) \ lua_pushinteger(L, (cache->stats.name)); \ lua_setfield(L, -2, #name) add_stat(open); add_stat(close); add_stat(count); cache->stats.count_entries = cache->api->count(cache->db, &cache->stats); add_stat(count_entries); add_stat(clear); add_stat(commit); add_stat(read); add_stat(read_miss); add_stat(write); add_stat(remove); add_stat(remove_miss); add_stat(match); add_stat(match_miss); add_stat(read_leq); add_stat(read_leq_miss); /* usage_percent statistics special case - double */ cache->stats.usage_percent = cache->api->usage_percent(cache->db); lua_pushnumber(L, cache->stats.usage_percent); lua_setfield(L, -2, "usage_percent"); #undef add_stat return 1; } static const struct kr_cdb_api *cache_select(struct engine *engine, const char **conf) { /* Return default backend */ if (*conf == NULL || !strstr(*conf, "://")) { return engine->backends.at[0]; } /* Find storage backend from config prefix */ for (unsigned i = 0; i < engine->backends.len; ++i) { const struct kr_cdb_api *api = engine->backends.at[i]; if (strncmp(*conf, api->name, strlen(api->name)) == 0) { *conf += strlen(api->name) + strlen("://"); return api; } } return NULL; } static int cache_max_ttl(lua_State *L) { struct kr_cache *cache = cache_assert_open(L); int n = lua_gettop(L); if (n > 0) { if (!lua_isnumber(L, 1) || n > 1) lua_error_p(L, "expected 'max_ttl(number ttl)'"); uint32_t min = cache->ttl_min; int64_t ttl = lua_tointeger(L, 1); if (ttl < 1 || ttl < min || ttl > TTL_MAX_MAX) { lua_error_p(L, "max_ttl must be larger than minimum TTL, and in range <1, " STR(TTL_MAX_MAX) ">'"); } cache->ttl_max = ttl; } lua_pushinteger(L, cache->ttl_max); return 1; } static int cache_min_ttl(lua_State *L) { struct kr_cache *cache = cache_assert_open(L); int n = lua_gettop(L); if (n > 0) { if (!lua_isnumber(L, 1)) lua_error_p(L, "expected 'min_ttl(number ttl)'"); uint32_t max = cache->ttl_max; int64_t ttl = lua_tointeger(L, 1); if (ttl < 0 || ttl > max || ttl > TTL_MAX_MAX) { lua_error_p(L, "min_ttl must be smaller than maximum TTL, and in range <0, " STR(TTL_MAX_MAX) ">'"); } cache->ttl_min = ttl; } lua_pushinteger(L, cache->ttl_min); return 1; } /** Open cache */ static int cache_open(lua_State *L) { /* Check parameters */ int n = lua_gettop(L); if (n < 1 || !lua_isnumber(L, 1)) lua_error_p(L, "expected 'open(number max_size, string config = \"\")'"); /* Select cache storage backend */ struct engine *engine = the_worker->engine; lua_Integer csize_lua = lua_tointeger(L, 1); if (!(csize_lua >= 8192 && csize_lua < SIZE_MAX)) { /* min. is basically arbitrary */ lua_error_p(L, "invalid cache size specified, it must be in range <8192, " STR(SIZE_MAX) ">"); } size_t cache_size = csize_lua; const char *conf = n > 1 ? lua_tostring(L, 2) : NULL; const char *uri = conf; const struct kr_cdb_api *api = cache_select(engine, &conf); if (!api) lua_error_p(L, "unsupported cache backend"); /* Close if already open */ kr_cache_close(&engine->resolver.cache); /* Reopen cache */ struct kr_cdb_opts opts = { (conf && strlen(conf)) ? conf : ".", cache_size }; int ret = kr_cache_open(&engine->resolver.cache, api, &opts, engine->pool); if (ret != 0) { char cwd[PATH_MAX]; get_workdir(cwd, sizeof(cwd)); return luaL_error(L, "can't open cache path '%s'; working directory '%s'; %s", opts.path, cwd, kr_strerror(ret)); } /* Let's check_health() every five seconds to avoid keeping old cache alive * even in case of not having any work to do. */ ret = kr_cache_check_health(&engine->resolver.cache, 5000); if (ret != 0) { kr_log_error(CACHE, "periodic health check failed (ignored): %s\n", kr_strerror(ret)); } /* Store current configuration */ lua_getglobal(L, "cache"); lua_pushstring(L, "current_size"); lua_pushnumber(L, cache_size); lua_rawset(L, -3); lua_pushstring(L, "current_storage"); lua_pushstring(L, uri); lua_rawset(L, -3); lua_pop(L, 1); lua_pushboolean(L, 1); return 1; } static int cache_close(lua_State *L) { struct kr_cache *cache = &the_worker->engine->resolver.cache; if (!kr_cache_is_open(cache)) { return 0; } kr_cache_close(cache); lua_getglobal(L, "cache"); lua_pushstring(L, "current_size"); lua_pushnumber(L, 0); lua_rawset(L, -3); lua_pop(L, 1); lua_pushboolean(L, 1); return 1; } #if 0 /** @internal Prefix walk. */ static int cache_prefixed(struct kr_cache *cache, const char *prefix, bool exact_name, knot_db_val_t keyval[][2], int maxcount) { /* Convert to domain name */ uint8_t buf[KNOT_DNAME_MAXLEN]; if (!knot_dname_from_str(buf, prefix, sizeof(buf))) { return kr_error(EINVAL); } /* Start prefix search */ return kr_cache_match(cache, buf, exact_name, keyval, maxcount); } #endif /** Clear everything. */ static int cache_clear_everything(lua_State *L) { struct kr_cache *cache = cache_assert_open(L); /* Clear records and packets. */ int ret = kr_cache_clear(cache); lua_error_maybe(L, ret); /* Clear reputation tables */ struct kr_context *ctx = &the_worker->engine->resolver; lru_reset(ctx->cache_cookie); lua_pushboolean(L, true); return 1; } #if 0 /** @internal Dump cache key into table on Lua stack. */ static void cache_dump(lua_State *L, knot_db_val_t keyval[]) { knot_dname_t dname[KNOT_DNAME_MAXLEN]; char name[KNOT_DNAME_TXT_MAXLEN]; uint16_t type; int ret = kr_unpack_cache_key(keyval[0], dname, &type); if (ret < 0) { return; } ret = !knot_dname_to_str(name, dname, sizeof(name)); if (kr_fails_assert(!ret)) return; /* If name typemap doesn't exist yet, create it */ lua_getfield(L, -1, name); if (lua_isnil(L, -1)) { lua_pop(L, 1); lua_newtable(L); } /* Append to typemap */ char type_buf[KR_RRTYPE_STR_MAXLEN] = { '\0' }; knot_rrtype_to_string(type, type_buf, sizeof(type_buf)); lua_pushboolean(L, true); lua_setfield(L, -2, type_buf); /* Set name typemap */ lua_setfield(L, -2, name); } /** Query cached records. TODO: fix caveats in ./README.rst documentation? */ static int cache_get(lua_State *L) { //struct kr_cache *cache = cache_assert_open(L); // to be fixed soon /* Check parameters */ int n = lua_gettop(L); if (n < 1 || !lua_isstring(L, 1)) lua_error_p(L, "expected 'cache.get(string key)'"); /* Retrieve set of keys */ const char *prefix = lua_tostring(L, 1); knot_db_val_t keyval[100][2]; int ret = cache_prefixed(cache, prefix, false/*FIXME*/, keyval, 100); lua_error_maybe(L, ret); /* Format output */ lua_newtable(L); for (int i = 0; i < ret; ++i) { cache_dump(L, keyval[i]); } return 1; } #endif static int cache_get(lua_State *L) { lua_error_maybe(L, ENOSYS); return kr_error(ENOSYS); /* doesn't happen */ } /** Set time interval for cleaning rtt cache. * Servers with score >= KR_NS_TIMEOUT will be cleaned after * this interval ended up, so that they will be able to participate * in NS elections again. */ static int cache_ns_tout(lua_State *L) { struct kr_context *ctx = &the_worker->engine->resolver; /* Check parameters */ int n = lua_gettop(L); if (n < 1) { lua_pushinteger(L, ctx->cache_rtt_tout_retry_interval); return 1; } if (!lua_isnumber(L, 1)) lua_error_p(L, "expected 'cache.ns_tout(interval in ms)'"); lua_Integer interval_lua = lua_tointeger(L, 1); if (!(interval_lua > 0 && interval_lua < UINT_MAX)) { lua_error_p(L, "invalid interval specified, it must be in range > 0, < " STR(UINT_MAX)); } ctx->cache_rtt_tout_retry_interval = interval_lua; lua_pushinteger(L, ctx->cache_rtt_tout_retry_interval); return 1; } int kr_bindings_cache(lua_State *L) { static const luaL_Reg lib[] = { { "backends", cache_backends }, { "count", cache_count }, { "stats", cache_stats }, { "checkpoint", cache_checkpoint }, { "open", cache_open }, { "close", cache_close }, { "clear_everything", cache_clear_everything }, { "get", cache_get }, { "max_ttl", cache_max_ttl }, { "min_ttl", cache_min_ttl }, { "ns_tout", cache_ns_tout }, { NULL, NULL } }; luaL_register(L, "cache", lib); return 1; }