summaryrefslogtreecommitdiffstats
path: root/src/lib-dict/dict-iter-lua.c
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--src/lib-dict/dict-iter-lua.c193
1 files changed, 193 insertions, 0 deletions
diff --git a/src/lib-dict/dict-iter-lua.c b/src/lib-dict/dict-iter-lua.c
new file mode 100644
index 0000000..780de03
--- /dev/null
+++ b/src/lib-dict/dict-iter-lua.c
@@ -0,0 +1,193 @@
+/* Copyright (c) 2021 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "array.h"
+#include "dict.h"
+#include "dlua-script-private.h"
+#include "dict-lua-private.h"
+#include "dlua-wrapper.h"
+
+struct lua_dict_iter {
+ pool_t pool;
+ struct dict_iterate_context *iter;
+ ARRAY(int) refs;
+ int error_ref;
+
+ lua_State *L;
+ bool yielded:1;
+};
+
+static void lua_dict_iter_unref(struct lua_dict_iter *iter)
+{
+ const char *error;
+
+ /* deinit iteration if it hasn't been done yet */
+ if (dict_iterate_deinit(&iter->iter, &error) < 0) {
+ e_error(dlua_script_from_state(iter->L)->event,
+ "Dict iteration failed: %s", error);
+ }
+
+ pool_unref(&iter->pool);
+}
+
+DLUA_WRAP_C_DATA(dict_iter, struct lua_dict_iter, lua_dict_iter_unref, NULL);
+
+static int lua_dict_iterate_step(lua_State *L);
+
+/* resume after a yield */
+static int lua_dict_iterate_step_continue(lua_State *L,
+ int status ATTR_UNUSED,
+ lua_KContext ctx ATTR_UNUSED)
+{
+ return lua_dict_iterate_step(L);
+}
+
+static void lua_dict_iterate_more(struct lua_dict_iter *iter);
+
+/*
+ * Iteration step function
+ *
+ * Takes two args (a userdata state, and previous value) and returns the
+ * next value.
+ */
+static int lua_dict_iterate_step(lua_State *L)
+{
+ struct lua_dict_iter *iter;
+ const int *refs;
+ unsigned nrefs;
+
+ DLUA_REQUIRE_ARGS(L, 2);
+
+ iter = xlua_dict_iter_getptr(L, 1, NULL);
+ iter->yielded = FALSE;
+
+ lua_dict_iterate_more(iter);
+
+ if (iter->iter != NULL) {
+ /* iteration didn't end yet - yield */
+ return lua_dict_iterate_step_continue(L,
+ lua_yieldk(L, 0, 0, lua_dict_iterate_step_continue), 0);
+ }
+
+ /* dict iteration ended - return first key-value pair */
+ refs = array_get(&iter->refs, &nrefs);
+ i_assert(nrefs % 2 == 0);
+
+ if (nrefs == 0) {
+ if (iter->error_ref != 0) {
+ /* dict iteration generated an error - raise it now */
+ lua_rawgeti(L, LUA_REGISTRYINDEX, iter->error_ref);
+ luaL_unref(L, LUA_REGISTRYINDEX, iter->error_ref);
+ return lua_error(L);
+ }
+
+ return 0; /* return nil */
+ }
+
+ /* get the key & value from the registry */
+ lua_rawgeti(L, LUA_REGISTRYINDEX, refs[0]);
+ lua_rawgeti(L, LUA_REGISTRYINDEX, refs[1]);
+ luaL_unref(L, LUA_REGISTRYINDEX, refs[0]);
+ luaL_unref(L, LUA_REGISTRYINDEX, refs[1]);
+
+ array_delete(&iter->refs, 0, 2);
+
+ return 2;
+}
+
+static void lua_dict_iterate_more(struct lua_dict_iter *iter)
+{
+ const char *key, *const *values;
+ lua_State *L = iter->L;
+ const char *error;
+
+ if (iter->iter == NULL)
+ return; /* done iterating the dict */
+
+ while (dict_iterate_values(iter->iter, &key, &values)) {
+ int ref;
+
+ /* stash key */
+ lua_pushstring(L, key);
+ ref = luaL_ref(L, LUA_REGISTRYINDEX);
+ array_push_back(&iter->refs, &ref);
+
+ /* stash values */
+ lua_newtable(L);
+ for (unsigned int i = 0; values[i] != NULL; i++) {
+ lua_pushstring(L, values[i]);
+ lua_seti(L, -2, i + 1);
+ }
+ ref = luaL_ref(L, LUA_REGISTRYINDEX);
+ array_push_back(&iter->refs, &ref);
+ }
+
+ if (dict_iterate_has_more(iter->iter))
+ return;
+
+ if (dict_iterate_deinit(&iter->iter, &error) < 0) {
+ lua_pushstring(L, error);
+ iter->error_ref = luaL_ref(L, LUA_REGISTRYINDEX);
+ }
+}
+
+/* dict iter callback */
+static void lua_dict_iterate_callback(struct lua_dict_iter *iter)
+{
+ if (iter->yielded)
+ return;
+ iter->yielded = TRUE;
+ dlua_pcall_yieldable_resume(iter->L, 1);
+}
+
+/*
+ * Iterate a dict at key [-(3|4),+2,e]
+ *
+ * Args:
+ * 1) userdata: sturct dict *dict
+ * 2) string: key
+ * 3) integer: flags
+ * 4*) string: username
+ *
+ * Returns:
+ * Returns a iteration step function and dict iter userdata.
+ * Username will be NULL if not provided in args.
+ */
+int lua_dict_iterate(lua_State *L)
+{
+ enum dict_iterate_flags flags;
+ struct lua_dict_iter *iter;
+ struct dict *dict;
+ const char *path, *username = NULL;
+ pool_t pool;
+
+ DLUA_REQUIRE_ARGS_IN(L, 3, 4);
+
+ dict = dlua_check_dict(L, 1);
+ path = luaL_checkstring(L, 2);
+ flags = luaL_checkinteger(L, 3);
+ if (lua_gettop(L) >= 4)
+ username = luaL_checkstring(L, 4);
+ lua_dict_check_key_prefix(L, path, username);
+
+ struct dict_op_settings set = {
+ .username = username,
+ };
+
+ /* set up iteration */
+ pool = pool_alloconly_create("lua dict iter", 128);
+ iter = p_new(pool, struct lua_dict_iter, 1);
+ iter->pool = pool;
+ iter->iter = dict_iterate_init(dict, &set, path, flags |
+ DICT_ITERATE_FLAG_ASYNC);
+ p_array_init(&iter->refs, iter->pool, 32);
+ iter->L = L;
+
+ dict_iterate_set_async_callback(iter->iter,
+ lua_dict_iterate_callback, iter);
+
+ /* push return values: func, state */
+ lua_pushcfunction(L, lua_dict_iterate_step);
+ xlua_pushdict_iter(L, iter, FALSE);
+ return 2;
+}