// nucleic-linux-agent — the in-guest control agent for Nucleic **Linux** VMs.
//
// The Mac-guest counterpart is `guest/NucleicVMAgent` (Swift/AppKit: AX, ScreenCaptureKit, CGEvent).
// A Linux guest needs none of that — computer-use (screen capture + mouse/keyboard) runs entirely
// host-side through the guest's virtio-gpu framebuffer + USB HID, so this agent only carries the two
// ops the host actually reaches into the guest for:
//
//   • ping — capability/readiness handshake (reports protocol version + a synthetic display).
//   • exec — the streaming shell-exec sub-protocol the host's MacVMExecChannel speaks.
//
// Transport: NDJSON over AF_VSOCK port 2035 — one JSON request object per line, one JSON reply per
// line. This mirrors `VMAgentCore.AgentWire` / `MacVMAgentWire` byte-for-byte (see
// docs/MACOS_VM_NATIVE_AGENT.md §3–§4 and docs/LINUX_VM.md). Keep the port/version/keys in lockstep.
//
// Deliberately dependency-free C so it links fully static and runs on any arm64 Linux guest, and so it
// can be built in the Nucleic Linux build container with just cc. One detached thread per accepted
// connection; in practice there is one client (the host), but a host reconnect must not need a restart.

#define _GNU_SOURCE
#include <errno.h>
#include <fcntl.h>
#include <poll.h>
#include <pthread.h>
#include <signal.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include <linux/vm_sockets.h>

// ---- Wire constants (mirror of AgentWire) ----------------------------------------------------------

#define AGENT_PORT 2035u
#define AGENT_VERSION 2

// ---- small utilities -------------------------------------------------------------------------------

static void log_line(const char *msg) {
    // Timestampless is fine; systemd's journal stamps each line.
    fprintf(stderr, "[nucleic-linux-agent] %s\n", msg);
    fflush(stderr);
}

// Write all of buf to fd, retrying short writes / EINTR. Returns 0 on success, -1 on error.
static int write_all(int fd, const char *buf, size_t len) {
    size_t off = 0;
    while (off < len) {
        ssize_t n = write(fd, buf + off, len - off);
        if (n > 0) {
            off += (size_t)n;
        } else if (n < 0 && (errno == EINTR || errno == EAGAIN)) {
            continue;
        } else {
            return -1;
        }
    }
    return 0;
}

// ---- base64 ----------------------------------------------------------------------------------------

static const char B64_ENC[] =
    "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";

// Encode len bytes into a freshly malloc'd, NUL-terminated base64 string. Caller frees.
static char *base64_encode(const unsigned char *in, size_t len) {
    size_t olen = 4 * ((len + 2) / 3);
    char *out = (char *)malloc(olen + 1);
    if (!out) return NULL;
    size_t i, o = 0;
    for (i = 0; i + 2 < len; i += 3) {
        uint32_t v = (in[i] << 16) | (in[i + 1] << 8) | in[i + 2];
        out[o++] = B64_ENC[(v >> 18) & 0x3F];
        out[o++] = B64_ENC[(v >> 12) & 0x3F];
        out[o++] = B64_ENC[(v >> 6) & 0x3F];
        out[o++] = B64_ENC[v & 0x3F];
    }
    size_t rem = len - i;
    if (rem == 1) {
        uint32_t v = in[i] << 16;
        out[o++] = B64_ENC[(v >> 18) & 0x3F];
        out[o++] = B64_ENC[(v >> 12) & 0x3F];
        out[o++] = '=';
        out[o++] = '=';
    } else if (rem == 2) {
        uint32_t v = (in[i] << 16) | (in[i + 1] << 8);
        out[o++] = B64_ENC[(v >> 18) & 0x3F];
        out[o++] = B64_ENC[(v >> 12) & 0x3F];
        out[o++] = B64_ENC[(v >> 6) & 0x3F];
        out[o++] = '=';
    }
    out[o] = '\0';
    return out;
}

static int b64_val(char c) {
    if (c >= 'A' && c <= 'Z') return c - 'A';
    if (c >= 'a' && c <= 'z') return c - 'a' + 26;
    if (c >= '0' && c <= '9') return c - '0' + 52;
    if (c == '+') return 62;
    if (c == '/') return 63;
    return -1;
}

// Decode a base64 string (len chars) into a malloc'd buffer; *outlen gets the byte count. Caller frees.
static unsigned char *base64_decode(const char *in, size_t len, size_t *outlen) {
    unsigned char *out = (unsigned char *)malloc(len / 4 * 3 + 3);
    if (!out) return NULL;
    size_t o = 0;
    int quad[4], qi = 0;
    for (size_t i = 0; i < len; i++) {
        char c = in[i];
        if (c == '=' ) { quad[qi++] = 0; }
        else {
            int v = b64_val(c);
            if (v < 0) continue;  // skip whitespace/newlines
            quad[qi++] = v;
        }
        if (qi == 4) {
            out[o++] = (unsigned char)((quad[0] << 2) | (quad[1] >> 4));
            out[o++] = (unsigned char)((quad[1] << 4) | (quad[2] >> 2));
            out[o++] = (unsigned char)((quad[2] << 6) | quad[3]);
            qi = 0;
        }
    }
    // Trim padding: count '=' at the end of the input.
    size_t pad = 0;
    for (size_t i = len; i > 0 && in[i - 1] == '='; i--) pad++;
    if (pad > o) pad = o;
    o -= pad;
    *outlen = o;
    return out;
}

// ---- minimal JSON (flat object) --------------------------------------------------------------------

// Decode a JSON string literal beginning at *p (which must point at the opening '"'). Writes a
// malloc'd, NUL-terminated decoded byte string to *out (also returns its length via *outlen). Advances
// *p past the closing quote. Returns 0 on success, -1 on malformed input.
static int json_decode_string(const char **p, char **out, size_t *outlen) {
    const char *s = *p;
    if (*s != '"') return -1;
    s++;
    size_t cap = 32, n = 0;
    char *buf = (char *)malloc(cap);
    if (!buf) return -1;
    while (*s && *s != '"') {
        char c = *s++;
        if (c == '\\') {
            char e = *s++;
            switch (e) {
                case '"': c = '"'; break;
                case '\\': c = '\\'; break;
                case '/': c = '/'; break;
                case 'n': c = '\n'; break;
                case 't': c = '\t'; break;
                case 'r': c = '\r'; break;
                case 'b': c = '\b'; break;
                case 'f': c = '\f'; break;
                case 'u': {
                    // \uXXXX — decode to UTF-8 (BMP only; surrogate pairs handled naively).
                    unsigned int cp = 0;
                    for (int k = 0; k < 4; k++) {
                        char h = *s++;
                        cp <<= 4;
                        if (h >= '0' && h <= '9') cp |= (unsigned)(h - '0');
                        else if (h >= 'a' && h <= 'f') cp |= (unsigned)(h - 'a' + 10);
                        else if (h >= 'A' && h <= 'F') cp |= (unsigned)(h - 'A' + 10);
                        else { free(buf); return -1; }
                    }
                    // Emit UTF-8 for the code point (up to 3 bytes for BMP).
                    if (cp < 0x80) {
                        if (n + 1 >= cap) { cap *= 2; buf = (char *)realloc(buf, cap); }
                        buf[n++] = (char)cp;
                    } else if (cp < 0x800) {
                        if (n + 2 >= cap) { cap *= 2; buf = (char *)realloc(buf, cap); }
                        buf[n++] = (char)(0xC0 | (cp >> 6));
                        buf[n++] = (char)(0x80 | (cp & 0x3F));
                    } else {
                        if (n + 3 >= cap) { cap *= 2; buf = (char *)realloc(buf, cap); }
                        buf[n++] = (char)(0xE0 | (cp >> 12));
                        buf[n++] = (char)(0x80 | ((cp >> 6) & 0x3F));
                        buf[n++] = (char)(0x80 | (cp & 0x3F));
                    }
                    continue;
                }
                default: c = e; break;
            }
        }
        if (n + 1 >= cap) { cap *= 2; buf = (char *)realloc(buf, cap); if (!buf) return -1; }
        buf[n++] = c;
    }
    if (*s != '"') { free(buf); return -1; }
    s++;
    buf[n] = '\0';
    *out = buf;
    if (outlen) *outlen = n;
    *p = s;
    return 0;
}

// Skip whitespace.
static const char *skip_ws(const char *s) {
    while (*s == ' ' || *s == '\t' || *s == '\n' || *s == '\r') s++;
    return s;
}

// Skip a JSON value at *p (string/number/bool/null/object/array — enough to step over a member value
// in a flat object; nested containers are skipped by brace/bracket counting). Returns pointer past it.
static const char *json_skip_value(const char *s) {
    s = skip_ws(s);
    if (*s == '"') {
        char *tmp = NULL;
        const char *p = s;
        if (json_decode_string(&p, &tmp, NULL) == 0) { free(tmp); return p; }
        return s + 1;
    }
    if (*s == '{' || *s == '[') {
        int depth = 0;
        int instr = 0;
        while (*s) {
            char c = *s;
            if (instr) {
                if (c == '\\' && s[1]) { s += 2; continue; }
                if (c == '"') instr = 0;
            } else {
                if (c == '"') instr = 1;
                else if (c == '{' || c == '[') depth++;
                else if (c == '}' || c == ']') { depth--; if (depth == 0) { s++; break; } }
            }
            s++;
        }
        return s;
    }
    // number / true / false / null
    while (*s && *s != ',' && *s != '}' && *s != ']') s++;
    return s;
}

// Find top-level string member `key` in flat JSON object `obj`; on success returns a malloc'd decoded
// value (caller frees) via *out and returns 0, else returns -1.
static int json_get_string(const char *obj, const char *key, char **out) {
    const char *s = skip_ws(obj);
    if (*s != '{') return -1;
    s++;
    while (1) {
        s = skip_ws(s);
        if (*s == '}' || *s == '\0') return -1;
        if (*s != '"') return -1;
        char *k = NULL;
        if (json_decode_string(&s, &k, NULL) != 0) return -1;
        s = skip_ws(s);
        if (*s != ':') { free(k); return -1; }
        s++;
        s = skip_ws(s);
        int match = (strcmp(k, key) == 0);
        free(k);
        if (match && *s == '"') {
            char *v = NULL;
            if (json_decode_string(&s, &v, NULL) != 0) return -1;
            *out = v;
            return 0;
        }
        s = json_skip_value(s);
        s = skip_ws(s);
        if (*s == ',') { s++; continue; }
        return -1;
    }
}

// Find top-level numeric member `key`; returns 0 and sets *out on success.
static int json_get_number(const char *obj, const char *key, long *out) {
    const char *s = skip_ws(obj);
    if (*s != '{') return -1;
    s++;
    while (1) {
        s = skip_ws(s);
        if (*s == '}' || *s == '\0') return -1;
        if (*s != '"') return -1;
        char *k = NULL;
        if (json_decode_string(&s, &k, NULL) != 0) return -1;
        s = skip_ws(s);
        if (*s != ':') { free(k); return -1; }
        s++;
        s = skip_ws(s);
        int match = (strcmp(k, key) == 0);
        free(k);
        if (match) { *out = strtol(s, NULL, 10); return 0; }
        s = json_skip_value(s);
        s = skip_ws(s);
        if (*s == ',') { s++; continue; }
        return -1;
    }
}

// Append a JSON-escaped copy of src to a malloc'd/grown buffer (*buf, *cap, *len).
static void json_append_escaped(char **buf, size_t *cap, size_t *len, const char *src) {
    for (const char *p = src; *p; p++) {
        char esc[8];
        int n;
        unsigned char c = (unsigned char)*p;
        if (c == '"') { esc[0] = '\\'; esc[1] = '"'; n = 2; }
        else if (c == '\\') { esc[0] = '\\'; esc[1] = '\\'; n = 2; }
        else if (c == '\n') { esc[0] = '\\'; esc[1] = 'n'; n = 2; }
        else if (c == '\r') { esc[0] = '\\'; esc[1] = 'r'; n = 2; }
        else if (c == '\t') { esc[0] = '\\'; esc[1] = 't'; n = 2; }
        else if (c < 0x20) { n = snprintf(esc, sizeof esc, "\\u%04x", c); }
        else { esc[0] = (char)c; n = 1; }
        if (*len + (size_t)n + 1 >= *cap) { *cap = (*cap + (size_t)n) * 2; *buf = (char *)realloc(*buf, *cap); }
        memcpy(*buf + *len, esc, (size_t)n);
        *len += (size_t)n;
    }
    (*buf)[*len] = '\0';
}

// ---- ping ------------------------------------------------------------------------------------------

// Send the ping reply: protocol version + a synthetic display (the host drives capture host-side, so
// this is informational) + all-true readiness (Linux computer-use needs no in-guest permission).
static int send_ping_reply(int fd) {
    static const char reply[] =
        "{\"ok\":true,\"version\":2,\"os\":\"linux\","
        "\"displays\":[{\"id\":0,\"width\":1920,\"height\":1200,\"main\":true}],"
        "\"readiness\":{\"accessibility\":true,\"screenRecording\":true,\"postEvent\":true}}\n";
    return write_all(fd, reply, sizeof(reply) - 1);
}

// ---- exec ------------------------------------------------------------------------------------------

// Send one framed output chunk: {"t":<tag>,"d":"<base64>"}\n. tag is "o" or "e".
static int send_data_frame(int fd, const char *tag, const unsigned char *data, size_t len) {
    char *b64 = base64_encode(data, len);
    if (!b64) return -1;
    size_t cap = strlen(b64) + 32;
    char *line = (char *)malloc(cap);
    int n = snprintf(line, cap, "{\"t\":\"%s\",\"d\":\"%s\"}\n", tag, b64);
    int rc = write_all(fd, line, (size_t)n);
    free(b64);
    free(line);
    return rc;
}

static int send_exit_frame(int fd, int code) {
    char line[64];
    int n = snprintf(line, sizeof line, "{\"t\":\"x\",\"code\":%d}\n", code);
    return write_all(fd, line, (size_t)n);
}

static int send_spawn_error(int fd, const char *msg) {
    size_t cap = strlen(msg) + 32, len = 0;
    char *buf = (char *)malloc(cap);
    buf[0] = '\0';
    const char *pre = "{\"t\":\"err\",\"m\":\"";
    memcpy(buf, pre, strlen(pre)); len = strlen(pre);
    json_append_escaped(&buf, &cap, &len, msg);
    const char *post = "\"}\n";
    if (len + strlen(post) + 1 >= cap) { cap += strlen(post) + 1; buf = (char *)realloc(buf, cap); }
    memcpy(buf + len, post, strlen(post)); len += strlen(post);
    int rc = write_all(fd, buf, len);
    free(buf);
    return rc;
}

// Handle one host→guest control frame line (stdin / eof / signal) for the running child.
static void handle_host_frame(const char *line, int child_stdin, int *stdin_closed, pid_t pid) {
    char *tag = NULL;
    if (json_get_string(line, "t", &tag) != 0) return;
    if (strcmp(tag, "i") == 0) {
        char *d = NULL;
        if (json_get_string(line, "d", &d) == 0) {
            size_t dl = 0;
            unsigned char *bytes = base64_decode(d, strlen(d), &dl);
            if (bytes && !*stdin_closed) (void)write_all(child_stdin, (char *)bytes, dl);
            free(bytes);
            free(d);
        }
    } else if (strcmp(tag, "eof") == 0) {
        if (!*stdin_closed) { close(child_stdin); *stdin_closed = 1; }
    } else if (strcmp(tag, "sig") == 0) {
        long n = 0;
        if (json_get_number(line, "n", &n) == 0) kill(pid, (int)n);
    }
    free(tag);
}

// Line-framer state for the connection (host→guest frames after the request line).
struct linebuf {
    char *data;
    size_t len, cap;
};
static void lb_append(struct linebuf *lb, const char *buf, size_t n) {
    if (lb->len + n + 1 >= lb->cap) { lb->cap = (lb->len + n) * 2 + 64; lb->data = (char *)realloc(lb->data, lb->cap); }
    memcpy(lb->data + lb->len, buf, n);
    lb->len += n;
    lb->data[lb->len] = '\0';
}
// Pop the next complete line (NUL-terminated, newline stripped) into a malloc'd string, or NULL.
static char *lb_next_line(struct linebuf *lb) {
    char *nl = (char *)memchr(lb->data, '\n', lb->len);
    if (!nl) return NULL;
    size_t linelen = (size_t)(nl - lb->data);
    char *out = (char *)malloc(linelen + 1);
    memcpy(out, lb->data, linelen);
    if (linelen > 0 && out[linelen - 1] == '\r') out[linelen - 1] = '\0';
    else out[linelen] = '\0';
    size_t rest = lb->len - linelen - 1;
    memmove(lb->data, nl + 1, rest);
    lb->len = rest;
    lb->data[lb->len] = '\0';
    return out;
}

// Run the streaming exec op: spawn `/bin/sh -c command`, pump child stdio ⇄ vsock as base64 frames
// until the child exits and both pipes drain. `leftover`/`leftover_len` are any host frames already
// buffered after the request line. This owns the connection for the command's lifetime.
static void run_exec(int fd, const char *command, const char *leftover, size_t leftover_len) {
    int in_pipe[2], out_pipe[2], err_pipe[2];
    if (pipe(in_pipe) || pipe(out_pipe) || pipe(err_pipe)) {
        send_spawn_error(fd, "could not create pipes for the command");
        return;
    }
    pid_t pid = fork();
    if (pid < 0) {
        send_spawn_error(fd, "fork failed");
        return;
    }
    if (pid == 0) {
        // Child: wire stdio to the pipes and exec a login-ish shell.
        dup2(in_pipe[0], 0);
        dup2(out_pipe[1], 1);
        dup2(err_pipe[1], 2);
        close(in_pipe[0]); close(in_pipe[1]);
        close(out_pipe[0]); close(out_pipe[1]);
        close(err_pipe[0]); close(err_pipe[1]);
        setsid();
        execl("/bin/sh", "sh", "-c", command, (char *)NULL);
        _exit(127);
    }
    // Parent.
    close(in_pipe[0]); close(out_pipe[1]); close(err_pipe[1]);
    int child_stdin = in_pipe[1], out_r = out_pipe[0], err_r = err_pipe[0];
    fcntl(out_r, F_SETFL, O_NONBLOCK);
    fcntl(err_r, F_SETFL, O_NONBLOCK);

    int stdin_closed = 0, out_open = 1, err_open = 1, exited = 0, status = 0;

    struct linebuf lb = {0};
    if (leftover && leftover_len) {
        lb_append(&lb, leftover, leftover_len);
        char *line;
        while ((line = lb_next_line(&lb))) {
            if (line[0]) handle_host_frame(line, child_stdin, &stdin_closed, pid);
            free(line);
        }
    }


    unsigned char buf[65536];
    while (!(exited && !out_open && !err_open)) {
        struct pollfd pfds[3];
        int nf = 0;
        int idx_conn = -1, idx_out = -1, idx_err = -1;
        pfds[nf].fd = fd; pfds[nf].events = POLLIN; idx_conn = nf; nf++;
        if (out_open) { pfds[nf].fd = out_r; pfds[nf].events = POLLIN; idx_out = nf; nf++; }
        if (err_open) { pfds[nf].fd = err_r; pfds[nf].events = POLLIN; idx_err = nf; nf++; }
        int ready = poll(pfds, (nfds_t)nf, 200);
        if (ready < 0 && errno != EINTR) break;

        if (idx_out >= 0 && (pfds[idx_out].revents & (POLLIN | POLLHUP))) {
            ssize_t n = read(out_r, buf, sizeof buf);
            if (n > 0) { if (send_data_frame(fd, "o", buf, (size_t)n) != 0) break; }
            else if (n == 0) { out_open = 0; }
            else if (errno != EAGAIN && errno != EINTR) { out_open = 0; }
        }
        if (idx_err >= 0 && (pfds[idx_err].revents & (POLLIN | POLLHUP))) {
            ssize_t n = read(err_r, buf, sizeof buf);
            if (n > 0) { if (send_data_frame(fd, "e", buf, (size_t)n) != 0) break; }
            else if (n == 0) { err_open = 0; }
            else if (errno != EAGAIN && errno != EINTR) { err_open = 0; }
        }
        if (idx_conn >= 0 && (pfds[idx_conn].revents & POLLIN)) {
            ssize_t n = read(fd, buf, sizeof buf);
            if (n > 0) {
                lb_append(&lb, (char *)buf, (size_t)n);
                char *line;
                while ((line = lb_next_line(&lb))) {
                    if (line[0]) handle_host_frame(line, child_stdin, &stdin_closed, pid);
                    free(line);
                }
            } else if (n == 0) {
                // Host hung up: kill the command and stop.
                kill(pid, SIGKILL);
                break;
            } else if (errno != EAGAIN && errno != EINTR) {
                kill(pid, SIGKILL);
                break;
            }
        }
        if (idx_conn >= 0 && (pfds[idx_conn].revents & (POLLHUP | POLLERR))) {
            kill(pid, SIGKILL);
            break;
        }
        if (!exited) {
            pid_t w = waitpid(pid, &status, WNOHANG);
            if (w == pid) exited = 1;
        }
    }

    if (!stdin_closed) close(child_stdin);
    close(out_r);
    close(err_r);
    if (!exited) waitpid(pid, &status, 0);
    int code = WIFEXITED(status) ? WEXITSTATUS(status) : (WIFSIGNALED(status) ? 128 + WTERMSIG(status) : -1);
    send_exit_frame(fd, code);
    free(lb.data);
}

// ---- connection handling ---------------------------------------------------------------------------

// Handle one accepted connection: frame NDJSON request lines, dispatch each op, write one reply line.
// `exec` takes over the connection for the life of the command (single-use), then we return.
static void handle_connection(int fd) {
    struct linebuf lb = {0};
    unsigned char buf[65536];
    for (;;) {
        char *line;
        while ((line = lb_next_line(&lb))) {
            if (!line[0]) { free(line); continue; }
            char *op = NULL;
            if (json_get_string(line, "op", &op) != 0) {
                const char *e = "{\"ok\":false,\"error\":\"malformed request (want a JSON object with an \\\"op\\\")\"}\n";
                write_all(fd, e, strlen(e));
                free(line);
                continue;
            }
            if (strcmp(op, "exec") == 0) {
                char *command = NULL;
                if (json_get_string(line, "command", &command) != 0) {
                    send_spawn_error(fd, "exec requires a \"command\" string");
                } else {
                    // Any host frames pipelined right after the request line are already in lb.
                    run_exec(fd, command, lb.data, lb.len);
                    free(command);
                }
                free(op);
                free(line);
                free(lb.data);
                return;  // exec connections are single-use
            } else if (strcmp(op, "ping") == 0) {
                send_ping_reply(fd);
            } else {
                char err[128];
                int n = snprintf(err, sizeof err,
                                 "{\"ok\":false,\"error\":\"unknown op '%s' (agent protocol v%d)\"}\n",
                                 op, AGENT_VERSION);
                write_all(fd, err, (size_t)n);
            }
            free(op);
            free(line);
        }
        // Runaway-line guard.
        if (lb.len > 4 * 1024 * 1024) break;
        ssize_t n = read(fd, buf, sizeof buf);
        if (n > 0) lb_append(&lb, (char *)buf, (size_t)n);
        else if (n == 0) break;  // EOF
        else if (errno != EINTR) break;
    }
    free(lb.data);
}

static void *connection_thread(void *arg) {
    int fd = (int)(intptr_t)arg;
    handle_connection(fd);
    close(fd);
    return NULL;
}

// ---- main ------------------------------------------------------------------------------------------

#ifndef AGENT_NO_MAIN
int main(void) {
    // A peer that vanishes mid-write must not kill us — writes surface EPIPE instead.
    signal(SIGPIPE, SIG_IGN);

    unsigned int port = AGENT_PORT;
    const char *env_port = getenv("NUCLEIC_AGENT_PORT");
    if (env_port) { unsigned long p = strtoul(env_port, NULL, 10); if (p) port = (unsigned int)p; }

    int fd = socket(AF_VSOCK, SOCK_STREAM, 0);
    if (fd < 0) { log_line("FATAL: socket(AF_VSOCK) failed — is this a VM guest?"); return 1; }

    struct sockaddr_vm addr;
    memset(&addr, 0, sizeof addr);
    addr.svm_family = AF_VSOCK;
    addr.svm_cid = VMADDR_CID_ANY;
    addr.svm_port = port;
    if (bind(fd, (struct sockaddr *)&addr, sizeof addr) != 0) {
        log_line("FATAL: vsock bind failed");
        return 1;
    }
    if (listen(fd, 8) != 0) {
        log_line("FATAL: vsock listen failed");
        return 1;
    }
    {
        char msg[80];
        snprintf(msg, sizeof msg, "listening on vsock port %u (protocol v%d)", port, AGENT_VERSION);
        log_line(msg);
    }

    for (;;) {
        int conn = accept(fd, NULL, NULL);
        if (conn < 0) {
            if (errno == EINTR) continue;
            log_line("accept failed; retrying");
            sleep(1);
            continue;
        }
        pthread_t t;
        if (pthread_create(&t, NULL, connection_thread, (void *)(intptr_t)conn) == 0) {
            pthread_detach(t);
        } else {
            close(conn);
        }
    }
}
#endif  // AGENT_NO_MAIN
