/* libink — per-connection lifecycle and dispatch entry point * * Copyright (c) 2026 Joachim Wiberg * SPDX-License-Identifier: MIT */ #include #include #include #include #include #include "internal.h" int link_connection_get_fd(const link_connection_t *conn) { return conn ? conn->fd : -1; } uid_t link_connection_get_uid(const link_connection_t *conn) { return conn ? conn->peer_uid : (uid_t)-1; } /* Issue a method call and remember the serial so the reply can be * handed back to `cb` when the read loop picks it up. Nothing here * waits: this is the counterpart of link_client_call() for a * connection already owned by the event loop. */ int link_connection_call(link_connection_t *conn, const char *destination, const char *path, const char *interface, const char *member, link_reply_cb_t cb, void *userdata, const char *signature, ...) { uint8_t body[LINK_CALL_BODY_MAX]; uint8_t hdr[LINK_CALL_HDR_MAX]; ssize_t blen = 0; ssize_t hlen; struct link_bus *bus; uint32_t serial; int i; if (!conn || conn->fd < 0 || !path || !member) { errno = EINVAL; return -1; } bus = __bus_get(conn); if (!bus) return -1; for (i = 0; i < LINK_PENDING_CAP; i++) { if (!bus->pending[i].used) break; } if (i == LINK_PENDING_CAP) { errno = EBUSY; return -1; } if (signature && *signature) { va_list ap; va_start(ap, signature); blen = __marshal_va(body, sizeof(body), signature, ap); va_end(ap); if (blen < 0) { errno = EMSGSIZE; return -1; } } serial = ++conn->next_serial; hlen = __msg_build_method_call(hdr, sizeof(hdr), serial, path, interface, member, destination, signature, (uint32_t)blen); if (hlen < 0) { errno = EMSGSIZE; return -1; } if (__io_write_all(conn->fd, hdr, (size_t)hlen) < 0) return -1; if (blen > 0 && __io_write_all(conn->fd, body, (size_t)blen) < 0) return -1; __dbg("calling %s.%s on %s, serial %u", interface ? interface : "-", member, destination ? destination : "peer", serial); bus->pending[i].used = 1; bus->pending[i].serial = serial; bus->pending[i].stamp = __now_ms(); bus->pending[i].cb = cb; bus->pending[i].userdata = userdata; return 0; } struct link_bus *__bus_get(link_connection_t *conn) { if (!conn->bus) conn->bus = calloc(1, sizeof(*conn->bus)); return conn->bus; } void __bus_free(link_connection_t *conn) { free(conn->bus); conn->bus = NULL; } /* Neither half can time itself out, libink has no event loop, so the * embedder sweeps. Calls go first: one timing out usually resolves * the park it was made for, and being told AccessDenied says more to * that caller than a bare timeout. */ int link_connection_expire(link_connection_t *conn, unsigned int age_ms) { struct link_bus *bus; uint64_t now; int i, live = 0; if (!conn || !conn->bus) return 0; bus = conn->bus; now = __now_ms(); for (i = 0; i < LINK_PENDING_CAP; i++) { link_reply_cb_t cb; void *userdata; if (!bus->pending[i].used) continue; if (now - bus->pending[i].stamp < age_ms) { live++; continue; } cb = bus->pending[i].cb; userdata = bus->pending[i].userdata; bus->pending[i].used = 0; __dbg("timed out call serial %u, no reply", bus->pending[i].serial); if (cb) cb(conn, NULL, userdata); } return live + __dispatch_expire_parked(conn, age_ms); } void link_connection_close(link_connection_t *conn) { size_t i; if (!conn) return; __dbg("closing peer on fd %d", conn->fd); /* Anything waiting on this connection has to be told, or a parked * call sits forever and its caller never hears back. */ __dispatch_forget_conn(conn); for (i = 0; i < conn->matches_count; i++) __match_free(conn->matches[i]); if (conn->fd >= 0) close(conn->fd); free(conn); } /* Process buffered binary D-Bus messages, dispatching each complete * message and shifting consumed bytes out of rxbuf. Returns -1 if * we should drop the connection (peer closed, protocol error, * downstream send failure). */ static int process_binary(link_connection_t *conn) { while (conn->rxlen > 0) { struct link_msg msg; ssize_t consumed; consumed = __msg_parse(conn->rxbuf, conn->rxlen, &msg); if (consumed == 0) break; /* incomplete; wait for more bytes */ if (consumed < 0) return -1; if (__dispatch_message(conn, &msg, (size_t)consumed) < 0) return -1; memmove(conn->rxbuf, conn->rxbuf + consumed, conn->rxlen - (size_t)consumed); conn->rxlen -= (size_t)consumed; } return 0; } int link_connection_process(link_connection_t *conn) { if (!conn) { errno = EINVAL; return -1; } if (conn->auth == LINK_AUTH_FAILED) return -1; if (conn->auth != LINK_AUTH_DONE) { if (__auth_process(conn) < 0) return -1; /* Still in SASL phase — wait for more bytes. */ if (conn->auth != LINK_AUTH_DONE) return 0; /* Fall through: BEGIN may have arrived in the same read * as the first binary message. auth_process moved those * bytes into rxbuf; they must be dispatched now, because * no further wake-up is guaranteed (the kernel has * already delivered everything that was readable). */ if (process_binary(conn) < 0) return -1; } /* Read additional bytes and dispatch any complete messages. * process_binary is called inside the loop after every * successful read; no second call after EAGAIN because the * buffer hasn't changed. */ for (;;) { ssize_t n; size_t room = sizeof(conn->rxbuf) - conn->rxlen; if (room == 0) { errno = E2BIG; return -1; } n = read(conn->fd, conn->rxbuf + conn->rxlen, room); if (n == 0) return -1; /* peer closed */ if (n < 0) { if (errno == EINTR) continue; if (errno == EAGAIN || errno == EWOULDBLOCK) return 0; return -1; } conn->rxlen += (size_t)n; if (process_binary(conn) < 0) return -1; } }