libink: let a handler park its call and reply later

The park machinery was welded to broker uid resolution; a handler
that cannot answer yet, like a device-settle call waiting for the
event queue to drain, had no way to defer.  link_call_park() holds
the request, link_call_resume() re-runs the handler with
link_call_resumed() reading true, and the expire sweep remains the
backstop for a resume that never comes.

Resume also no longer drops a local caller's kernel group set in the
privileged re-check: group source now keys on broker-ness.

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
This commit is contained in:
Joachim Wiberg committed 2026-08-16 22:03:42 +02:00
1 parent 2b861ea0af
commit 1bb7d508a4
4 files changed
+88 -11

No files matched your search

+68 -9
View File
@@ -393,6 +393,7 @@ static struct link_parked *park(link_connection_t *conn, const uint8_t *frame,
bus->parked[i].tok = ++bus->next_tok;
bus->parked[i].conn = conn;
bus->parked[i].stamp = __now_ms();
bus->parked[i].uid = (uid_t)-1;
bus->parked[i].len = len;
memcpy(bus->parked[i].buf, frame, len);
*tok = bus->parked[i].tok;
@@ -477,13 +478,20 @@ void __dispatch_forget_conn(link_connection_t *conn)
static int dispatch_call(link_connection_t *conn, const struct link_msg *m,
const uint8_t *frame, size_t framelen,
const uid_t *known_uid);
const uid_t *known_uid, int resumed);
void link_uid_resolved(link_connection_t *conn, link_authz_t tok, uid_t uid)
/*
* Shared resume: copy the parked frame out, free the slot, re-enter
* dispatch. `uid` NULL means "use what the slot recorded" (a
* handler-parked call); non-NULL is a resolver's answer.
*/
static void resume_parked(link_connection_t *conn, link_authz_t tok,
const uid_t *uid, int resumed)
{
uint8_t buf[LINK_PARKED_MSG_MAX];
struct link_parked *p = NULL;
struct link_msg msg;
uid_t slot_uid;
size_t len;
int i;
@@ -501,15 +509,58 @@ void link_uid_resolved(link_connection_t *conn, link_authz_t tok, uid_t uid)
/* Copy the message out and free the slot before dispatching:
* the handler may park a call of its own. */
len = p->len;
len = p->len;
slot_uid = p->uid;
memcpy(buf, p->buf, len);
unpark(p);
if (__msg_parse(buf, len, &msg) <= 0)
return;
__dbg("resumed %s, caller uid %d", msg.member ? msg.member : "call", (int)uid);
(void)dispatch_call(conn, &msg, NULL, 0, &uid);
if (!uid)
uid = &slot_uid;
__dbg("resumed %s, caller uid %d", msg.member ? msg.member : "call", (int)*uid);
(void)dispatch_call(conn, &msg, NULL, 0, uid, resumed);
}
void link_uid_resolved(link_connection_t *conn, link_authz_t tok, uid_t uid)
{
resume_parked(conn, tok, &uid, 0);
}
/*
* Defer the reply: hold the request and re-run the handler later via
* link_call_resume(), where link_call_resumed() reads true. A resumed
* call cannot park again -- the raw frame is gone -- so the handler
* must answer on the second run. link_connection_expire() is the
* backstop for a resume that never comes.
*/
int link_call_park(link_call_t *call, link_authz_t *tok)
{
struct link_parked *p;
if (!call || !tok || call->resumed)
return -1;
p = park(call->conn, call->frame, call->framelen, tok);
if (!p)
return -1;
p->uid = call->uid;
call->parked = 1;
return 0;
}
void link_call_resume(link_connection_t *conn, link_authz_t tok)
{
resume_parked(conn, tok, NULL, 1);
}
int link_call_resumed(const link_call_t *call)
{
return call ? call->resumed : 0;
}
int __dispatch_message(link_connection_t *conn, const struct link_msg *m, size_t framelen)
@@ -524,7 +575,7 @@ int __dispatch_message(link_connection_t *conn, const struct link_msg *m, size_t
return 0;
}
return dispatch_call(conn, m, conn->rxbuf, framelen, NULL);
return dispatch_call(conn, m, conn->rxbuf, framelen, NULL, 0);
}
/* Who may invoke a privileged method. Without an authorizer, only
@@ -582,7 +633,7 @@ static int resolve_caller(link_connection_t *conn, const struct link_msg *m,
static int dispatch_call(link_connection_t *conn, const struct link_msg *m,
const uint8_t *frame, size_t framelen,
const uid_t *known_uid)
const uid_t *known_uid, int resumed)
{
struct link_object *o;
struct link_vtable_entry *e = NULL;
@@ -676,8 +727,8 @@ static int dispatch_call(link_connection_t *conn, const struct link_msg *m,
* resolved over a broker carries only its uid, so the
* authorizer sees no groups and falls back to root-only. */
if (!caller_may(conn->server, call_uid,
known_uid ? NULL : conn->peer_groups,
known_uid ? 0 : conn->peer_ngroups)) {
conn->broker ? NULL : conn->peer_groups,
conn->broker ? 0 : conn->peer_ngroups)) {
__dbg("denied %s, caller uid %d is not privileged",
m->member, (int)call_uid);
return __send_error(conn, m,
@@ -690,10 +741,18 @@ static int dispatch_call(link_connection_t *conn, const struct link_msg *m,
call.conn = conn;
call.uid = call_uid;
call.incoming = *m;
call.frame = frame;
call.framelen = framelen;
call.resumed = resumed;
__r_init(&call.read_cursor, m->body, m->body_avail);
rc = meth->handler(&call, e->userdata);
/* The handler parked the call: no reply yet, it is resumed via
* link_call_resume() or expired by link_connection_expire(). */
if (call.parked)
return 0;
/* Every path returns the status of whatever it put on the wire,
* so a failed send propagates out and the read loop drops the
* peer rather than leaving a half-written frame on a live link.