diff --git a/libink/dispatch.c b/libink/dispatch.c index ad4ebcfe..df320e76 100644 --- a/libink/dispatch.c +++ b/libink/dispatch.c @@ -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. diff --git a/libink/internal.h b/libink/internal.h index e94e4371..e101ec61 100644 --- a/libink/internal.h +++ b/libink/internal.h @@ -40,7 +40,7 @@ typedef enum { * than a number per call site. */ #define LINK_CALL_HDR_MAX 1024 #define LINK_CALL_BODY_MAX 1024 -#define LINK_PARKED_CAP 4 /* inbound calls awaiting a uid */ +#define LINK_PARKED_CAP 4 /* inbound calls held for later */ /* A call parked for authorization is a privileged one: an object path * and at most a service name. Finit's per-service paths alone run to * 512 bytes, so leave room for the header around one. Anything that @@ -74,6 +74,7 @@ struct link_parked { link_authz_t tok; link_connection_t *conn; uint64_t stamp; + uid_t uid; /* caller, for handler-parked calls */ size_t len; uint8_t buf[LINK_PARKED_MSG_MAX]; }; @@ -133,6 +134,10 @@ struct link_call { int reply_consumed; int error_sent; uid_t uid; /* caller, resolved for a broker peer */ + const uint8_t *frame; /* raw frame, for link_call_park() */ + size_t framelen; + int parked; /* handler deferred its reply */ + int resumed; /* re-run via link_call_resume() */ }; /* A parsed AddMatch rule. Fields are NULL when the rule omits the diff --git a/libink/link.h b/libink/link.h index ed571960..73e42270 100644 --- a/libink/link.h +++ b/libink/link.h @@ -155,6 +155,19 @@ void link_server_set_authorizer(link_server_t *server, link_authorizer_t cb, * asked about; a reply callback is handed it as its first argument. */ void link_uid_resolved(link_connection_t *conn, link_authz_t tok, uid_t uid); +/* ---------- handler-deferred replies ---------- */ + +/* A handler that cannot answer yet parks the call and returns 0 + * without replying; the framework sends nothing. Resume re-runs the + * handler with link_call_resumed() reading true, and this time it + * must reply -- a resumed call cannot park again. Parked calls that + * are never resumed are expired by link_connection_expire() with a + * TimedOut error. Room is limited (four per connection); a failed + * park means answer now. */ +int link_call_park (link_call_t *call, link_authz_t *tok); +void link_call_resume (link_connection_t *conn, link_authz_t tok); +int link_call_resumed(const link_call_t *call); + /* Called with the reply to an outbound link_connection_call(). `reply` * is NULL if the connection dropped before one arrived. */ typedef void (*link_reply_cb_t)(link_connection_t *conn, const link_reply_t *reply, diff --git a/libink/marshal.h b/libink/marshal.h index 2c020db1..f40ef902 100644 --- a/libink/marshal.h +++ b/libink/marshal.h @@ -21,8 +21,8 @@ ssize_t __w_finish(struct link_writer *w); void __w_byte (struct link_writer *w, uint8_t v); void __w_bool (struct link_writer *w, int v); -void __w_u64 (struct link_writer *w, uint64_t v); void __w_u32 (struct link_writer *w, uint32_t v); +void __w_u64 (struct link_writer *w, uint64_t v); void __w_string (struct link_writer *w, const char *s); /* "s" */ void __w_path (struct link_writer *w, const char *s); /* "o" */ void __w_sig (struct link_writer *w, const char *s); /* "g" */