diff options
| author | Anders Betts <anders.betts@gmail.com> | 2026-09-17 19:55:36 +0200 |
|---|---|---|
| committer | Anders Betts <anders.betts@gmail.com> | 2026-09-17 19:55:36 +0200 |
| commit | 380195f7cd5e57acf2c1cf2bc41069e6b0b979ed (patch) | |
| tree | 32a88fb22a7fbe8f1fd5c105156d1f928c93950d /clients/client.c | |
| download | bokf-380195f7cd5e57acf2c1cf2bc41069e6b0b979ed.tar.gz bokf-380195f7cd5e57acf2c1cf2bc41069e6b0b979ed.zip | |
Initial commit: daemon, clients, docs, Docker deploy pipelinev0.1.0
Diffstat (limited to 'clients/client.c')
| -rw-r--r-- | clients/client.c | 241 |
1 files changed, 241 insertions, 0 deletions
diff --git a/clients/client.c b/clients/client.c new file mode 100644 index 0000000..ea5b906 --- /dev/null +++ b/clients/client.c @@ -0,0 +1,241 @@ +#include "client.h" + +#include <errno.h> +#include <netdb.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/socket.h> +#include <sys/un.h> +#include <unistd.h> + +#include "util.h" +#include "yyjson.h" + +static ssize_t write_all(int fd, const char *buf, size_t len) +{ + size_t off = 0; + while (off < len) { + ssize_t w = write(fd, buf + off, len - off); + if (w < 0) { + if (errno == EINTR) + continue; + return -1; + } + off += (size_t)w; + } + return (ssize_t)off; +} + +int client_send_line(int fd, const char *line) +{ + if (write_all(fd, line, strlen(line)) < 0) + return -1; + return write_all(fd, "\n", 1) < 0 ? -1 : 0; +} + +char *client_read_line(int fd) +{ + struct buf b; + buf_init(&b); + char chunk[4096]; + for (;;) { + ssize_t r = read(fd, chunk, sizeof chunk); + if (r < 0) { + if (errno == EINTR) + continue; + buf_free(&b); + return NULL; + } + if (r == 0) + break; + unsigned char *nl = memchr(chunk, '\n', (size_t)r); + if (nl) { + buf_append(&b, chunk, (size_t)(nl - (unsigned char *)chunk)); + break; + } + buf_append(&b, chunk, (size_t)r); + } + char *out = xmalloc(b.len + 1); + memcpy(out, b.p ? (char *)b.p : "", b.len); + out[b.len] = '\0'; + buf_free(&b); + return out; +} + +static int tcp_connect_addr(const char *addrport) +{ + char host[256] = "127.0.0.1"; + char port[16] = "8787"; + const char *colon = strrchr(addrport, ':'); + if (colon) { + size_t hl = (size_t)(colon - addrport); + if (hl < sizeof host) { + memcpy(host, addrport, hl); + host[hl] = '\0'; + } + snprintf(port, sizeof port, "%s", colon + 1); + } else { + snprintf(port, sizeof port, "%s", addrport); + } + struct addrinfo hints, *res = NULL; + memset(&hints, 0, sizeof hints); + hints.ai_family = AF_UNSPEC; + hints.ai_socktype = SOCK_STREAM; + if (getaddrinfo(host, port, &hints, &res) != 0) + return -1; + int fd = -1; + for (struct addrinfo *ai = res; ai; ai = ai->ai_next) { + fd = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol); + if (fd < 0) + continue; + if (connect(fd, ai->ai_addr, ai->ai_addrlen) == 0) + break; + close(fd); + fd = -1; + } + freeaddrinfo(res); + return fd; +} + +int client_connect(const char *target) +{ + if (strncmp(target, "tcp:", 4) == 0) + return tcp_connect_addr(target + 4); + struct sockaddr_un sa; + memset(&sa, 0, sizeof sa); + sa.sun_family = AF_UNIX; + if (strlen(target) >= sizeof sa.sun_path) { + errno = ENAMETOOLONG; + return -1; + } + snprintf(sa.sun_path, sizeof sa.sun_path, "%s", target); + int fd = socket(AF_UNIX, SOCK_STREAM, 0); + if (fd < 0) + return -1; + if (connect(fd, (struct sockaddr *)&sa, sizeof sa) != 0) { + close(fd); + return -1; + } + return fd; +} + +char *client_make_request(const char *cmd, const char *session, int64_t org, + const char *args_json, const char *id) +{ + yyjson_doc *adoc = NULL; + if (args_json) { + adoc = yyjson_read(args_json, strlen(args_json), 0); + if (!adoc || !yyjson_is_obj(yyjson_doc_get_root(adoc))) { + yyjson_doc_free(adoc); + return NULL; + } + } + yyjson_mut_doc *d = yyjson_mut_doc_new(NULL); + yyjson_mut_val *o = yyjson_mut_obj(d); + yyjson_mut_doc_set_root(d, o); + yyjson_mut_obj_add_int(d, o, "v", 1); + yyjson_mut_obj_add_strcpy(d, o, "id", id ? id : "cli"); + yyjson_mut_obj_add_strcpy(d, o, "cmd", cmd); + if (session) + yyjson_mut_obj_add_strcpy(d, o, "session", session); + if (org > 0) + yyjson_mut_obj_add_int(d, o, "org", org); + if (adoc) { + yyjson_mut_val *args = yyjson_val_mut_copy(d, yyjson_doc_get_root(adoc)); + yyjson_mut_obj_add_val(d, o, "args", args); + yyjson_doc_free(adoc); + } + char *s = yyjson_mut_write(d, 0, NULL); + yyjson_mut_doc_free(d); + return s; +} + +char *client_make_login_args(const char *user, const char *password) +{ + yyjson_mut_doc *d = yyjson_mut_doc_new(NULL); + yyjson_mut_val *o = yyjson_mut_obj(d); + yyjson_mut_doc_set_root(d, o); + yyjson_mut_obj_add_strcpy(d, o, "method", "password"); + yyjson_mut_obj_add_strcpy(d, o, "username", user); + yyjson_mut_obj_add_strcpy(d, o, "password", password); + char *s = yyjson_mut_write(d, 0, NULL); + yyjson_mut_doc_free(d); + return s; +} + +char *client_rpc(int fd, const char *cmd, const char *session, int64_t org, + const char *args_json) +{ + char *req = client_make_request(cmd, session, org, args_json, "rpc"); + if (!req) + return NULL; + int rc = client_send_line(fd, req); + free(req); + if (rc != 0) + return NULL; + return client_read_line(fd); +} + +int client_login(int fd, const char *user, const char *password, + char **session_out, char **err_out) +{ + *session_out = NULL; + *err_out = NULL; + char *args = client_make_login_args(user, password); + if (!args) { + *err_out = xstrdup("could not build login request"); + return -1; + } + char *resp = client_rpc(fd, "session.open", NULL, 0, args); + free(args); + if (!resp) { + *err_out = xstrdup(strerror(errno)); + return -1; + } + if (!client_ok(resp)) { + *err_out = resp; + return -1; + } + yyjson_doc *d = yyjson_read(resp, strlen(resp), 0); + yyjson_val *root = d ? yyjson_doc_get_root(d) : NULL; + yyjson_val *res = root ? yyjson_obj_get(root, "result") : NULL; + yyjson_val *s = res ? yyjson_obj_get(res, "session") : NULL; + if (!s || !yyjson_is_str(s)) { + *err_out = xstrdup("login response had no session"); + yyjson_doc_free(d); + free(resp); + return -1; + } + *session_out = xstrdup(yyjson_get_str(s)); + yyjson_doc_free(d); + return 0; +} + +int client_ok(const char *response) +{ + if (!response) + return 0; + yyjson_doc *d = yyjson_read(response, strlen(response), 0); + yyjson_val *ok = d ? yyjson_obj_get(yyjson_doc_get_root(d), "ok") : NULL; + int result = ok && yyjson_is_bool(ok) && yyjson_get_bool(ok); + yyjson_doc_free(d); + return result; +} + +int client_session_from(const char *response, char *buf, unsigned long cap) +{ + if (!response) + return -1; + yyjson_doc *d = yyjson_read(response, strlen(response), 0); + yyjson_val *root = d ? yyjson_doc_get_root(d) : NULL; + yyjson_val *res = root ? yyjson_obj_get(root, "result") : NULL; + yyjson_val *s = res ? yyjson_obj_get(res, "session") : NULL; + if (!s || !yyjson_is_str(s)) { + yyjson_doc_free(d); + return -1; + } + snprintf(buf, cap, "%s", yyjson_get_str(s)); + yyjson_doc_free(d); + return 0; +} |
