--- /dev/null
+From: Felix Fietkau <nbd@nbd.name>
+Date: Tue, 4 Mar 2025 12:01:09 +0100
+Subject: [PATCH] ubus: add channel defer() method
+
+Works in the same way as regular defer calls, except that it operates on
+channels.
+
+Signed-off-by: Felix Fietkau <nbd@nbd.name>
+---
+
+--- a/lib/ubus.c
++++ b/lib/ubus.c
+@@ -878,44 +878,30 @@ uc_ubus_chan_request(uc_vm_t *vm, size_t
+ ok_return(res.res);
+ }
+
+-static uc_value_t *
+-uc_ubus_defer(uc_vm_t *vm, size_t nargs)
++static int
++uc_ubus_defer_common(uc_vm_t *vm, uc_ubus_connection_t *c, uc_ubus_call_res_t *res,
++ uint32_t id, uc_value_t *funname, uc_value_t *funargs,
++ uc_value_t *fd, uc_value_t *fdcb, uc_value_t *replycb)
+ {
+- uc_value_t *objname, *funname, *funargs, *replycb, *fd, *fdcb, *conn, *res = NULL;
+ uc_ubus_deferred_t *defer;
+- uc_ubus_connection_t *c;
+ enum ubus_msg_status rv;
+ uc_callframe_t *frame;
+- uint32_t id;
++ uc_value_t *conn;
+ int fd_val = -1;
+
+- conn_get(vm, &c);
+-
+- args_get_named(vm, nargs,
+- "object", UC_STRING, REQUIRED, &objname,
+- "method", UC_STRING, REQUIRED, &funname,
+- "data", UC_OBJECT, OPTIONAL, &funargs,
+- "cb", UC_CLOSURE, OPTIONAL, &replycb,
+- "fd", 0, NAMED, &fd,
+- "fd_cb", UC_CLOSURE, NAMED, &fdcb);
+-
+ blob_buf_init(&c->buf, 0);
+
+ if (funargs)
+ ucv_object_to_blob(funargs, &c->buf);
+-
+ if (fd) {
+ fd_val = get_fd(vm, fd);
+- if (fd_val < 0)
+- err_return(UBUS_STATUS_INVALID_ARGUMENT, "Invalid file descriptor argument");
++ if (fd_val < 0) {
++ rv = UBUS_STATUS_INVALID_ARGUMENT;
++ set_error(rv, "Invalid file descriptor argument");
++ return rv;
++ }
+ }
+
+- rv = ubus_lookup_id(&c->ctx, ucv_string_get(objname), &id);
+-
+- if (rv != UBUS_STATUS_OK)
+- err_return(rv, "Failed to resolve object name '%s'",
+- ucv_string_get(objname));
+-
+ defer = xalloc(sizeof(*defer));
+
+ rv = ubus_invoke_async_fd(&c->ctx, id, ucv_string_get(funname),
+@@ -935,11 +921,11 @@ uc_ubus_defer(uc_vm_t *vm, size_t nargs)
+ defer->timeout.cb = uc_ubus_call_timeout_cb;
+ uloop_timeout_set(&defer->timeout, c->timeout * 1000);
+
+- res = uc_resource_new(defer_type, defer);
++ res->res = uc_resource_new(defer_type, defer);
+ frame = uc_vector_last(&vm->callframes);
+ conn = frame ? frame->ctx : NULL;
+
+- defer->registry_index = request_reg_add(vm, ucv_get(res), ucv_get(replycb), ucv_get(fdcb), ucv_get(conn), ucv_get(fd));
++ defer->registry_index = request_reg_add(vm, ucv_get(res->res), ucv_get(replycb), ucv_get(fdcb), ucv_get(conn), ucv_get(fd));
+
+ if (!uc_ubus_have_uloop()) {
+ have_own_uloop = true;
+@@ -958,11 +944,64 @@ uc_ubus_defer(uc_vm_t *vm, size_t nargs)
+ free(defer);
+ }
+
++ return rv;
++}
++
++static uc_value_t *
++uc_ubus_defer(uc_vm_t *vm, size_t nargs)
++{
++ uc_value_t *objname, *funname, *funargs, *replycb, *fd, *fdcb = NULL;
++ uc_ubus_call_res_t res = { 0 };
++ uc_ubus_connection_t *c;
++ uint32_t id;
++ int rv;
++
++ conn_get(vm, &c);
++
++ rv = ubus_lookup_id(&c->ctx, ucv_string_get(objname), &id);
++ if (rv != UBUS_STATUS_OK)
++ err_return(rv, "Failed to resolve object name '%s'",
++ ucv_string_get(objname));
++
++ args_get_named(vm, nargs,
++ "object", UC_STRING, REQUIRED, &objname,
++ "method", UC_STRING, REQUIRED, &funname,
++ "data", UC_OBJECT, OPTIONAL, &funargs,
++ "cb", UC_CLOSURE, OPTIONAL, &replycb,
++ "fd", 0, NAMED, &fd,
++ "fd_cb", UC_CLOSURE, NAMED, &fdcb);
++
++ rv = uc_ubus_defer_common(vm, c, &res, id, funname, funargs, fd, fdcb, replycb);
+ if (rv != UBUS_STATUS_OK)
+ err_return(rv, "Failed to invoke function '%s' on object '%s'",
+ ucv_string_get(funname), ucv_string_get(objname));
+
+- ok_return(res);
++ ok_return(res.res);
++}
++
++static uc_value_t *
++uc_ubus_chan_defer(uc_vm_t *vm, size_t nargs)
++{
++ uc_value_t *funname, *funargs, *replycb, *fd, *fdcb = NULL;
++ uc_ubus_call_res_t res = { 0 };
++ uc_ubus_connection_t *c;
++ int rv;
++
++ conn_get(vm, &c);
++
++ args_get_named(vm, nargs,
++ "method", UC_STRING, REQUIRED, &funname,
++ "data", UC_OBJECT, OPTIONAL, &funargs,
++ "cb", UC_CLOSURE, OPTIONAL, &replycb,
++ "fd", 0, NAMED, &fd,
++ "fd_cb", UC_CLOSURE, NAMED, &fdcb);
++
++ rv = uc_ubus_defer_common(vm, c, &res, 0, funname, funargs, fd, fdcb, replycb);
++ if (rv != UBUS_STATUS_OK)
++ err_return(rv, "Failed to invoke function '%s' on channel",
++ ucv_string_get(funname));
++
++ ok_return(res.res);
+ }
+
+
+@@ -2384,6 +2423,7 @@ static const uc_function_list_t conn_fns
+
+ static const uc_function_list_t chan_fns[] = {
+ { "request", uc_ubus_chan_request },
++ { "defer", uc_ubus_chan_defer },
+ { "error", uc_ubus_error },
+ { "disconnect", uc_ubus_disconnect },
+ };
--- /dev/null
+From: Felix Fietkau <nbd@nbd.name>
+Date: Tue, 4 Mar 2025 13:17:49 +0100
+Subject: [PATCH] ubus: add no_return argument to call and channel request
+
+Allows issuing a call without waiting for any result. This is useful
+for implementing non-blocking notification calls without the overhead
+of extra request tracking.
+
+Signed-off-by: Felix Fietkau <nbd@nbd.name>
+---
+
+--- a/lib/ubus.c
++++ b/lib/ubus.c
+@@ -764,7 +764,8 @@ get_fd(uc_vm_t *vm, uc_value_t *val)
+ static int
+ uc_ubus_call_common(uc_vm_t *vm, uc_ubus_connection_t *c, uc_ubus_call_res_t *res,
+ uint32_t id, uc_value_t *funname, uc_value_t *funargs,
+- uc_value_t *fd, uc_value_t *fdcb, uc_value_t *mret)
++ uc_value_t *fd, uc_value_t *fdcb, uc_value_t *noret,
++ uc_value_t *mret)
+ {
+ uc_ubus_deferred_t defer = {};
+ enum ubus_msg_status rv;
+@@ -796,8 +797,12 @@ uc_ubus_call_common(uc_vm_t *vm, uc_ubus
+ defer.registry_index = request_reg_add(vm, NULL, NULL, ucv_get(fdcb), NULL, NULL);
+ }
+
+- if (rv == UBUS_STATUS_OK)
+- rv = ubus_complete_request(&c->ctx, &defer.request, c->timeout * 1000);
++ if (rv == UBUS_STATUS_OK) {
++ if (ucv_is_truish(noret))
++ ubus_abort_request(&c->ctx, &defer.request);
++ else
++ rv = ubus_complete_request(&c->ctx, &defer.request, c->timeout * 1000);
++ }
+
+ if (defer.request.fd_cb)
+ request_reg_clear(vm, defer.registry_index);
+@@ -808,7 +813,7 @@ uc_ubus_call_common(uc_vm_t *vm, uc_ubus
+ static uc_value_t *
+ uc_ubus_call(uc_vm_t *vm, size_t nargs)
+ {
+- uc_value_t *obj, *funname, *funargs, *fd, *fdcb, *mret = NULL;
++ uc_value_t *obj, *funname, *funargs, *fd, *fdcb, *noret, *mret = NULL;
+ uc_ubus_call_res_t res = { 0 };
+ uc_ubus_connection_t *c;
+ enum ubus_msg_status rv;
+@@ -819,6 +824,7 @@ uc_ubus_call(uc_vm_t *vm, size_t nargs)
+ "method", UC_STRING, REQUIRED, &funname,
+ "data", UC_OBJECT, OPTIONAL, &funargs,
+ "multiple_return", UC_BOOLEAN, OPTIONAL, &mret,
++ "no_return", UC_BOOLEAN, NAMED, &noret,
+ "fd", 0, NAMED, &fd,
+ "fd_cb", UC_CLOSURE, NAMED, &fdcb);
+
+@@ -839,7 +845,7 @@ uc_ubus_call(uc_vm_t *vm, size_t nargs)
+ "Argument object is not string or integer");
+ }
+
+- rv = uc_ubus_call_common(vm, c, &res, id, funname, funargs, fd, fdcb, mret);
++ rv = uc_ubus_call_common(vm, c, &res, id, funname, funargs, fd, fdcb, noret, mret);
+
+ if (rv != UBUS_STATUS_OK) {
+ if (ucv_type(obj) == UC_STRING)
+@@ -856,7 +862,7 @@ uc_ubus_call(uc_vm_t *vm, size_t nargs)
+ static uc_value_t *
+ uc_ubus_chan_request(uc_vm_t *vm, size_t nargs)
+ {
+- uc_value_t *funname, *funargs, *fd, *fdcb, *mret = NULL;
++ uc_value_t *funname, *funargs, *fd, *fdcb, *noret, *mret = NULL;
+ uc_ubus_call_res_t res = { 0 };
+ uc_ubus_connection_t *c;
+ enum ubus_msg_status rv;
+@@ -865,12 +871,13 @@ uc_ubus_chan_request(uc_vm_t *vm, size_t
+ "method", UC_STRING, REQUIRED, &funname,
+ "data", UC_OBJECT, OPTIONAL, &funargs,
+ "multiple_return", UC_BOOLEAN, OPTIONAL, &mret,
++ "no_return", UC_BOOLEAN, NAMED, &noret,
+ "fd", 0, NAMED, &fd,
+ "fd_cb", UC_CLOSURE, NAMED, &fdcb);
+
+ conn_get(vm, &c);
+
+- rv = uc_ubus_call_common(vm, c, &res, 0, funname, funargs, fd, fdcb, mret);
++ rv = uc_ubus_call_common(vm, c, &res, 0, funname, funargs, fd, fdcb, noret, mret);
+ if (rv != UBUS_STATUS_OK)
+ err_return(rv, "Failed to send request '%s' on channel",
+ ucv_string_get(funname));
--- /dev/null
+From: Felix Fietkau <nbd@nbd.name>
+Date: Tue, 4 Mar 2025 14:32:25 +0100
+Subject: [PATCH] ubus: add separate data callback for ubus defer calls
+
+This allows receiving one call per data message, which makes it possible to
+properly deal with multiple response messages.
+
+Signed-off-by: Felix Fietkau <nbd@nbd.name>
+---
+
+--- a/lib/ubus.c
++++ b/lib/ubus.c
+@@ -306,14 +306,14 @@ _uc_reg_clear(uc_vm_t *vm, const char *k
+ _uc_reg_clear(vm, "ubus.connections", idx, 4)
+
+
+-#define request_reg_add(vm, request, cb, fdcb, conn, fd) \
+- _uc_reg_add(vm, "ubus.requests", 5, request, cb, fdcb, conn, fd)
++#define request_reg_add(vm, request, cb, datacb, fdcb, conn, fd) \
++ _uc_reg_add(vm, "ubus.requests", 6, request, cb, datacb, fdcb, conn, fd)
+
+-#define request_reg_get(vm, idx, request, cb, fdcb) \
+- _uc_reg_get(vm, "ubus.requests", idx, 3, request, cb, fdcb)
++#define request_reg_get(vm, idx, request, cb, datacb, fdcb) \
++ _uc_reg_get(vm, "ubus.requests", idx, 4, request, cb, datacb, fdcb)
+
+ #define request_reg_clear(vm, idx) \
+- _uc_reg_clear(vm, "ubus.requests", idx, 5)
++ _uc_reg_clear(vm, "ubus.requests", idx, 6)
+
+
+ #define object_reg_add(vm, obj, msg, cb) \
+@@ -636,7 +636,7 @@ uc_ubus_call_user_cb(uc_ubus_deferred_t
+ {
+ uc_value_t *this, *func;
+
+- request_reg_get(defer->vm, defer->registry_index, &this, &func, NULL);
++ request_reg_get(defer->vm, defer->registry_index, &this, &func, NULL, NULL);
+
+ if (ucv_is_callable(func)) {
+ uc_vm_stack_push(defer->vm, ucv_get(this));
+@@ -666,6 +666,26 @@ uc_ubus_call_data_cb(struct ubus_request
+ }
+
+ static void
++uc_ubus_call_data_user_cb(struct ubus_request *req, int type, struct blob_attr *msg)
++{
++ uc_ubus_deferred_t *defer = container_of(req, uc_ubus_deferred_t, request);
++ uc_value_t *this, *func, *reply;
++
++ request_reg_get(defer->vm, defer->registry_index, &this, NULL, &func, NULL);
++
++ if (ucv_is_callable(func)) {
++ reply = blob_array_to_ucv(defer->vm, blob_data(msg), blob_len(msg), true);
++
++ uc_vm_stack_push(defer->vm, ucv_get(this));
++ uc_vm_stack_push(defer->vm, ucv_get(func));
++ uc_vm_stack_push(defer->vm, ucv_get(reply));
++
++ if (uc_vm_call(defer->vm, true, 1) == EXCEPTION_NONE)
++ ucv_put(uc_vm_stack_pop(defer->vm));
++ }
++}
++
++static void
+ uc_ubus_call_fd_cb(struct ubus_request *req, int fd)
+ {
+ uc_ubus_deferred_t *defer = container_of(req, uc_ubus_deferred_t, request);
+@@ -674,7 +694,7 @@ uc_ubus_call_fd_cb(struct ubus_request *
+ if (defer->complete)
+ return;
+
+- request_reg_get(defer->vm, defer->registry_index, &this, NULL, &func);
++ request_reg_get(defer->vm, defer->registry_index, &this, NULL, NULL, &func);
+ if (ucv_is_callable(func)) {
+ uc_vm_stack_push(defer->vm, ucv_get(this));
+ uc_vm_stack_push(defer->vm, ucv_get(func));
+@@ -794,7 +814,7 @@ uc_ubus_call_common(uc_vm_t *vm, uc_ubus
+ defer.request.priv = res;
+ if (ucv_is_callable(fdcb)) {
+ defer.request.fd_cb = uc_ubus_call_fd_cb;
+- defer.registry_index = request_reg_add(vm, NULL, NULL, ucv_get(fdcb), NULL, NULL);
++ defer.registry_index = request_reg_add(vm, NULL, NULL, NULL, ucv_get(fdcb), NULL, NULL);
+ }
+
+ if (rv == UBUS_STATUS_OK) {
+@@ -888,7 +908,8 @@ uc_ubus_chan_request(uc_vm_t *vm, size_t
+ static int
+ uc_ubus_defer_common(uc_vm_t *vm, uc_ubus_connection_t *c, uc_ubus_call_res_t *res,
+ uint32_t id, uc_value_t *funname, uc_value_t *funargs,
+- uc_value_t *fd, uc_value_t *fdcb, uc_value_t *replycb)
++ uc_value_t *fd, uc_value_t *fdcb, uc_value_t *replycb,
++ uc_value_t *datacb)
+ {
+ uc_ubus_deferred_t *defer;
+ enum ubus_msg_status rv;
+@@ -918,7 +939,10 @@ uc_ubus_defer_common(uc_vm_t *vm, uc_ubu
+ defer->vm = vm;
+ defer->ctx = &c->ctx;
+
+- defer->request.data_cb = uc_ubus_call_data_cb;
++ if (ucv_is_callable(datacb))
++ defer->request.data_cb = uc_ubus_call_data_user_cb;
++ else
++ defer->request.data_cb = uc_ubus_call_data_cb;
+ if (ucv_is_callable(fdcb))
+ defer->request.fd_cb = uc_ubus_call_fd_cb;
+ defer->request.complete_cb = uc_ubus_call_done_cb;
+@@ -932,7 +956,8 @@ uc_ubus_defer_common(uc_vm_t *vm, uc_ubu
+ frame = uc_vector_last(&vm->callframes);
+ conn = frame ? frame->ctx : NULL;
+
+- defer->registry_index = request_reg_add(vm, ucv_get(res->res), ucv_get(replycb), ucv_get(fdcb), ucv_get(conn), ucv_get(fd));
++ defer->registry_index = request_reg_add(vm, ucv_get(res->res), ucv_get(replycb), ucv_get(datacb),
++ ucv_get(fdcb), ucv_get(conn), ucv_get(fd));
+
+ if (!uc_ubus_have_uloop()) {
+ have_own_uloop = true;
+@@ -957,7 +982,7 @@ uc_ubus_defer_common(uc_vm_t *vm, uc_ubu
+ static uc_value_t *
+ uc_ubus_defer(uc_vm_t *vm, size_t nargs)
+ {
+- uc_value_t *objname, *funname, *funargs, *replycb, *fd, *fdcb = NULL;
++ uc_value_t *objname, *funname, *funargs, *replycb, *datacb, *fd, *fdcb = NULL;
+ uc_ubus_call_res_t res = { 0 };
+ uc_ubus_connection_t *c;
+ uint32_t id;
+@@ -975,10 +1000,11 @@ uc_ubus_defer(uc_vm_t *vm, size_t nargs)
+ "method", UC_STRING, REQUIRED, &funname,
+ "data", UC_OBJECT, OPTIONAL, &funargs,
+ "cb", UC_CLOSURE, OPTIONAL, &replycb,
++ "data_cb", UC_CLOSURE, OPTIONAL, &datacb,
+ "fd", 0, NAMED, &fd,
+ "fd_cb", UC_CLOSURE, NAMED, &fdcb);
+
+- rv = uc_ubus_defer_common(vm, c, &res, id, funname, funargs, fd, fdcb, replycb);
++ rv = uc_ubus_defer_common(vm, c, &res, id, funname, funargs, fd, fdcb, replycb, datacb);
+ if (rv != UBUS_STATUS_OK)
+ err_return(rv, "Failed to invoke function '%s' on object '%s'",
+ ucv_string_get(funname), ucv_string_get(objname));
+@@ -989,7 +1015,7 @@ uc_ubus_defer(uc_vm_t *vm, size_t nargs)
+ static uc_value_t *
+ uc_ubus_chan_defer(uc_vm_t *vm, size_t nargs)
+ {
+- uc_value_t *funname, *funargs, *replycb, *fd, *fdcb = NULL;
++ uc_value_t *funname, *funargs, *replycb, *datacb, *fd, *fdcb = NULL;
+ uc_ubus_call_res_t res = { 0 };
+ uc_ubus_connection_t *c;
+ int rv;
+@@ -1000,10 +1026,11 @@ uc_ubus_chan_defer(uc_vm_t *vm, size_t n
+ "method", UC_STRING, REQUIRED, &funname,
+ "data", UC_OBJECT, OPTIONAL, &funargs,
+ "cb", UC_CLOSURE, OPTIONAL, &replycb,
++ "data_cb", UC_CLOSURE, OPTIONAL, &datacb,
+ "fd", 0, NAMED, &fd,
+ "fd_cb", UC_CLOSURE, NAMED, &fdcb);
+
+- rv = uc_ubus_defer_common(vm, c, &res, 0, funname, funargs, fd, fdcb, replycb);
++ rv = uc_ubus_defer_common(vm, c, &res, 0, funname, funargs, fd, fdcb, replycb, datacb);
+ if (rv != UBUS_STATUS_OK)
+ err_return(rv, "Failed to invoke function '%s' on channel",
+ ucv_string_get(funname));
+@@ -1520,7 +1547,7 @@ uc_ubus_handle_reply_common(struct ubus_
+
+ /* Add wrapped request context into registry to prevent GC'ing
+ * until reply or timeout occurred */
+- callctx->registry_index = request_reg_add(vm, ucv_get(reqobj), NULL, NULL, NULL, NULL);
++ callctx->registry_index = request_reg_add(vm, ucv_get(reqobj), NULL, NULL, NULL, NULL, NULL);
+ }
+
+ /* Otherwise, when the function returned an object, treat it as
--- /dev/null
+From: Felix Fietkau <nbd@nbd.name>
+Date: Tue, 4 Mar 2025 14:47:46 +0100
+Subject: [PATCH] ubus: support sending multiple reply messages on a
+ request
+
+When passing true in the return code argument, assume that more reply calls
+are coming and only send a reply data message without the status code.
+
+Signed-off-by: Felix Fietkau <nbd@nbd.name>
+---
+
+--- a/lib/ubus.c
++++ b/lib/ubus.c
+@@ -1058,16 +1058,21 @@ uc_ubus_request_finish_common(uc_ubus_re
+ }
+
+ static void
+-uc_ubus_request_finish(uc_ubus_request_t *callctx, int code, uc_value_t *reply)
++uc_ubus_request_send_reply(uc_ubus_request_t *callctx, uc_value_t *reply)
+ {
+- if (callctx->replied)
++ if (!reply)
+ return;
+
+- if (reply) {
+- blob_buf_init(&buf, 0);
+- ucv_object_to_blob(reply, &buf);
+- ubus_send_reply(callctx->ctx, &callctx->req, buf.head);
+- }
++ blob_buf_init(&buf, 0);
++ ucv_object_to_blob(reply, &buf);
++ ubus_send_reply(callctx->ctx, &callctx->req, buf.head);
++}
++
++static void
++uc_ubus_request_finish(uc_ubus_request_t *callctx, int code)
++{
++ if (callctx->replied)
++ return;
+
+ uc_ubus_request_finish_common(callctx, code);
+ request_reg_clear(callctx->vm, callctx->registry_index);
+@@ -1078,7 +1083,7 @@ uc_ubus_request_timeout(struct uloop_tim
+ {
+ uc_ubus_request_t *callctx = container_of(timeout, uc_ubus_request_t, timeout);
+
+- uc_ubus_request_finish(callctx, UBUS_STATUS_TIMEOUT, NULL);
++ uc_ubus_request_finish(callctx, UBUS_STATUS_TIMEOUT);
+ }
+
+ static uc_value_t *
+@@ -1087,25 +1092,36 @@ uc_ubus_request_reply(uc_vm_t *vm, size_
+ uc_ubus_request_t **callctx = uc_fn_this("ubus.request");
+ int64_t code = UBUS_STATUS_OK;
+ uc_value_t *reply, *rcode;
++ bool more = false;
+
+ if (!callctx || !*callctx)
+ err_return(UBUS_STATUS_INVALID_ARGUMENT, "Invalid call context");
+
+ args_get(vm, nargs,
+- "reply", UC_OBJECT, true, &reply,
+- "rcode", UC_INTEGER, true, &rcode);
++ "reply", UC_OBJECT, OPTIONAL, &reply,
++ "rcode", 0, OPTIONAL, &rcode);
+
+ if ((*callctx)->replied)
+ err_return(UBUS_STATUS_INVALID_ARGUMENT, "Reply has already been sent");
+
+- if (rcode) {
++ switch (ucv_type(rcode)) {
++ case UC_INTEGER:
+ code = ucv_int64_get(rcode);
+
+ if (errno == ERANGE || code < 0 || code > __UBUS_STATUS_LAST)
+ code = UBUS_STATUS_UNKNOWN_ERROR;
++ break;
++ case UC_BOOLEAN:
++ case UC_NULL:
++ more = ucv_is_truish(rcode);
++ break;
++ default:
++ err_return(UBUS_STATUS_INVALID_ARGUMENT, "Argument is not integer or bool");
+ }
+
+- uc_ubus_request_finish(*callctx, code, reply);
++ uc_ubus_request_send_reply(*callctx, reply);
++ if (!more)
++ uc_ubus_request_finish(*callctx, code);
+
+ ok_return(ucv_boolean_new(true));
+ }
+@@ -1171,7 +1187,7 @@ uc_ubus_request_error(uc_vm_t *vm, size_
+ if (errno == ERANGE || code < 0 || code > __UBUS_STATUS_LAST)
+ code = UBUS_STATUS_UNKNOWN_ERROR;
+
+- uc_ubus_request_finish(*callctx, code, NULL);
++ uc_ubus_request_finish(*callctx, code);
+
+ ok_return(ucv_boolean_new(true));
+ }
+@@ -2538,7 +2554,7 @@ static void free_object(void *ud) {
+ static void free_request(void *ud) {
+ uc_ubus_request_t *callctx = ud;
+
+- uc_ubus_request_finish(callctx, UBUS_STATUS_TIMEOUT, NULL);
++ uc_ubus_request_finish(callctx, UBUS_STATUS_TIMEOUT);
+ uloop_timeout_cancel(&callctx->timeout);
+ free(callctx);
+ }
--- /dev/null
+From: Felix Fietkau <nbd@nbd.name>
+Date: Tue, 4 Mar 2025 20:55:43 +0100
+Subject: [PATCH] ubus: fix potential use-after-free on channel disconnect
+
+Ensure that free function only gets called called after shutdown,
+because it potentially invalidates the uc_ubus_connection_t
+pointer.
+
+Signed-off-by: Felix Fietkau <nbd@nbd.name>
+---
+
+--- a/lib/ubus.c
++++ b/lib/ubus.c
+@@ -2358,12 +2358,12 @@ uc_ubus_channel_disconnect_cb(struct ubu
+ }
+
+ blob_buf_free(&c->buf);
+- if (c->registry_index >= 0)
+- connection_reg_clear(c->vm, c->registry_index);
+ if (c->ctx.sock.fd >= 0) {
+ ubus_shutdown(&c->ctx);
+ c->ctx.sock.fd = -1;
+ }
++ if (c->registry_index >= 0)
++ connection_reg_clear(c->vm, c->registry_index);
+ }
+
+ static uc_value_t *
--- /dev/null
+From: Felix Fietkau <nbd@nbd.name>
+Date: Fri, 7 Mar 2025 14:33:10 +0100
+Subject: [PATCH] ubus: remove broken implied await when calling defer()
+ outside of uloop.run()
+
+Calling ubus.defer() outside of uloop.run() was apparently broken since commit
+1cb04f9b76e2 ("ubus: add object publishing, notify and subscribe support") from
+March 2022.
+
+It was supposed to block until the request completes, however it blocked forever
+due to a counter imbalance introduced by the above commit.
+
+These days this 'feature' is of questionable value, since req.await() exists,
+and there is a legitimate use for issuing deferred requests from outside of
+uloop.run() and then calling uloop.run() or req.await() afterwards.
+
+Since nobody noticed the breakage in all this time, let's just get rid of this
+to get rid of some API footguns.
+
+Signed-off-by: Felix Fietkau <nbd@nbd.name>
+---
+
+--- a/CMakeLists.txt
++++ b/CMakeLists.txt
+@@ -180,7 +180,6 @@ if(UBUS_SUPPORT)
+ try_compile(HAVE_NEW_UBUS_STATUS_CODES
+ ${CMAKE_BINARY_DIR}
+ "${CMAKE_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/CMakeTmp/test.c")
+- check_symbol_exists(uloop_fd_set_cb "libubox/uloop.h" FD_SET_CB_EXISTS)
+ check_function_exists(uloop_timeout_remaining64 REMAINING64_FUNCTION_EXISTS)
+ check_function_exists(ubus_channel_connect HAVE_CHANNEL_SUPPORT)
+ if(REMAINING64_FUNCTION_EXISTS)
+--- a/lib/ubus.c
++++ b/lib/ubus.c
+@@ -132,9 +132,6 @@ static uc_resource_type_t *defer_type;
+ static uc_resource_type_t *conn_type;
+ static uc_resource_type_t *chan_type;
+
+-static uint64_t n_cb_active;
+-static bool have_own_uloop;
+-
+ static struct blob_buf buf;
+
+ typedef struct {
+@@ -649,11 +646,6 @@ uc_ubus_call_user_cb(uc_ubus_deferred_t
+ }
+
+ request_reg_clear(defer->vm, defer->registry_index);
+-
+- n_cb_active--;
+-
+- if (have_own_uloop && n_cb_active == 0)
+- uloop_end();
+ }
+
+ static void
+@@ -735,24 +727,6 @@ uc_ubus_call_timeout_cb(struct uloop_tim
+ uc_ubus_call_user_cb(defer, UBUS_STATUS_TIMEOUT, NULL);
+ }
+
+-static bool
+-uc_ubus_have_uloop(void)
+-{
+- bool prev = uloop_cancelled;
+- bool active;
+-
+-#ifdef HAVE_ULOOP_FD_SET_CB
+- if (uloop_fd_set_cb)
+- return true;
+-#endif
+-
+- uloop_cancelled = true;
+- active = uloop_cancelling();
+- uloop_cancelled = prev;
+-
+- return active;
+-}
+-
+ static int
+ get_fd(uc_vm_t *vm, uc_value_t *val)
+ {
+@@ -958,11 +932,6 @@ uc_ubus_defer_common(uc_vm_t *vm, uc_ubu
+
+ defer->registry_index = request_reg_add(vm, ucv_get(res->res), ucv_get(replycb), ucv_get(datacb),
+ ucv_get(fdcb), ucv_get(conn), ucv_get(fd));
+-
+- if (!uc_ubus_have_uloop()) {
+- have_own_uloop = true;
+- uloop_run();
+- }
+ }
+ else {
+ uc_vm_stack_push(vm, ucv_get(replycb));
+@@ -2303,11 +2272,6 @@ uc_ubus_defer_abort(uc_vm_t *vm, size_t
+
+ request_reg_clear((*d)->vm, (*d)->registry_index);
+
+- n_cb_active--;
+-
+- if (have_own_uloop && n_cb_active == 0)
+- uloop_end();
+-
+ (*d)->complete = true;
+
+ ok_return(ucv_boolean_new(true));