Add ctl-client code

Signed-off-by: Jim Ramsay <i.am@jimramsay.com>
pull/178/head
Jim Ramsay 2022-10-29 19:13:41 -04:00 committed by Andri Yngvason
parent 5043f8e149
commit be42c8b7bf
5 changed files with 319 additions and 11 deletions

View File

@ -0,0 +1,31 @@
/*
* Copyright (c) 2022 Jim Ramsay
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
* REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
* AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
* INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
* LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
* OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
* PERFORMANCE OF THIS SOFTWARE.
*/
#pragma once
#include <stdbool.h>
struct ctl_client;
void ctl_client_debug_log(bool enable);
struct ctl_client* ctl_client_new(const char* socket_path, void* userdata);
void ctl_client_destroy(struct ctl_client*);
void* ctl_client_userdata(struct ctl_client*);
int ctl_client_run_command(struct ctl_client* self,
int argc, char* argv[]);

View File

@ -16,8 +16,12 @@
#pragma once #pragma once
#include <sys/types.h>
#define UDIV_UP(a, b) (((a) + (b) - 1) / (b)) #define UDIV_UP(a, b) (((a) + (b) - 1) / (b))
extern const char* wayvnc_version; extern const char* wayvnc_version;
const char* default_ctl_socket_path(); const char* default_ctl_socket_path();
void advance_read_buffer(char (*buffer)[], size_t* current_len, size_t advance_by);

272
src/ctl-client.c 100644
View File

@ -0,0 +1,272 @@
/*
* Copyright (c) 2022 Jim Ramsay
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
* REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
* AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
* INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
* LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
* OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
* PERFORMANCE OF THIS SOFTWARE.
*/
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <poll.h>
#include <jansson.h>
#include "json-ipc.h"
#include "ctl-client.h"
#include "ctl-server.h"
#include "strlcpy.h"
#include "util.h"
#define WARN(fmt, ...) \
fprintf(stderr, "[WARNING] " fmt "\n", ##__VA_ARGS__)
static bool do_debug = false;
#define DEBUG(fmt, ...) \
if (do_debug) \
fprintf(stderr, "[%s:%d] " fmt "\n", __FILE__, __LINE__, ##__VA_ARGS__);
#define FAILED_TO(action) \
WARN("Failed to " action ": %m");
struct ctl_client {
void* userdata;
char read_buffer[512];
size_t read_len;
int fd;
};
void ctl_client_debug_log(bool enable)
{
do_debug = enable;
}
struct ctl_client* ctl_client_new(const char* socket_path, void* userdata)
{
if (!socket_path)
socket_path = default_ctl_socket_path();
struct ctl_client* new = calloc(1, sizeof(*new));
new->userdata = userdata;
struct sockaddr_un addr = {
.sun_family = AF_UNIX,
};
if (strlen(socket_path) >= sizeof(addr.sun_path)) {
errno = ENAMETOOLONG;
FAILED_TO("create unix socket");
goto socket_failure;
}
strcpy(addr.sun_path, socket_path);
new->fd = socket(AF_UNIX, SOCK_STREAM, 0);
if (new->fd < 0) {
FAILED_TO("create unix socket");
goto socket_failure;
}
if (connect(new->fd, (struct sockaddr*)&addr, sizeof(addr)) != 0) {
FAILED_TO("connect to unix socket");
goto connect_failure;
}
return new;
connect_failure:
close(new->fd);
socket_failure:
free(new);
return NULL;
}
void ctl_client_destroy(struct ctl_client* self)
{
close(self->fd);
free(self);
}
void* ctl_client_userdata(struct ctl_client* self)
{
return self->userdata;
}
static struct jsonipc_request* ctl_client_parse_args(struct ctl_client* self,
int argc, char* argv[])
{
struct jsonipc_request* request = NULL;
const char* method = argv[0];
json_t* params = json_object();
for (int i = 1; i < argc; ++i) {
char* key = argv[i];
char* value = NULL;
if (key[0] == '-' && key[1] == '-')
key += 2;
char* delim = strchr(key, '=');
if (delim) {
*delim = '\0';
value = delim + 1;
} else if (++i < argc) {
value = argv[i];
} else {
WARN("Argument must be of the format --key=value or --key value");
goto failure;
}
json_object_set_new(params, key, json_string(value));
}
request = jsonipc_request_new(method, params);
failure:
json_decref(params);
return request;
}
static ssize_t ctl_client_send_request(struct ctl_client* self,
struct jsonipc_request* request)
{
json_error_t err;
json_t* packed = jsonipc_request_pack(request, &err);
if (!packed) {
WARN("Could not encode json: %s", err.text);
return -1;
}
char buffer[512];
int len = json_dumpb(packed, buffer, sizeof(buffer), JSON_COMPACT);
json_decref(packed);
DEBUG(">> %.*s", len, buffer);
return send(self->fd, buffer, len, MSG_NOSIGNAL);
}
static json_t* json_from_buffer(struct ctl_client* self)
{
if (self->read_len == 0) {
DEBUG("Read buffer is empty");
errno = ENODATA;
return NULL;
}
json_error_t err;
json_t* root = json_loadb(self->read_buffer, self->read_len, 0, &err);
if (root) {
advance_read_buffer(&self->read_buffer, &self->read_len, err.position);
} else if (json_error_code(&err) == json_error_premature_end_of_input) {
DEBUG("Awaiting more data");
errno = ENODATA;
} else {
WARN("Json parsing failed: %s", err.text);
errno = EINVAL;
}
return root;
}
static json_t* read_one_object(struct ctl_client* self, int timeout_ms)
{
json_t* root = json_from_buffer(self);
if (root)
return root;
if (errno != ENODATA)
return NULL;
struct pollfd pfd = {
.fd = self->fd,
.events = POLLIN,
.revents = 0
};
while (root == NULL) {
int n = poll(&pfd, 1, timeout_ms);
if (n == -1) {
if (errno == EINTR)
continue;
WARN("Error waiting for a response: %m");
break;
} else if (n == 0) {
WARN("Timeout waiting for a response");
break;
}
char* readptr = self->read_buffer + self->read_len;
size_t remainder = sizeof(self->read_buffer) - self->read_len;
n = recv(self->fd, readptr, remainder, 0);
if (n == -1) {
WARN("Read failed: %m");
break;
} else if (n == 0) {
WARN("Disconnected");
errno = ECONNRESET;
break;
}
DEBUG("Read %d bytes", n);
DEBUG("<< %.*s", n, readptr);
self->read_len += n;
root = json_from_buffer(self);
if (!root && errno != ENODATA)
break;
}
return root;
}
static struct jsonipc_response* ctl_client_wait_for_response(struct ctl_client* self)
{
DEBUG("Waiting for a response");
json_t* root = read_one_object(self, 1000);
if (!root)
return NULL;
struct jsonipc_error jipc_err = JSONIPC_ERR_INIT;
struct jsonipc_response* response = jsonipc_response_parse_new(root,
&jipc_err);
if (!response) {
char* msg = json_dumps(jipc_err.data, JSON_EMBED);
WARN("Could not parse json: %s", msg);
free(msg);
}
json_decref(root);
jsonipc_error_cleanup(&jipc_err);
return response;
}
static int ctl_client_print_response(struct ctl_client* self,
struct jsonipc_response* response)
{
DEBUG("Response code: %d", response->code);
if (response->data) {
char* data = json_dumps(response->data, JSON_INDENT(4));
printf("%s\n", data);
free(data);
}
return response->code;
}
int ctl_client_run_command(struct ctl_client* self,
int argc, char* argv[])
{
int result = -1;
struct jsonipc_request* request = ctl_client_parse_args(self, argc, argv);
if (!request)
goto parse_failure;
if (ctl_client_send_request(self, request) < 0)
goto send_failure;
struct jsonipc_response* response = ctl_client_wait_for_response(self);
if (!response)
goto receive_failure;
result = ctl_client_print_response(self, response);
jsonipc_response_destroy(response);
receive_failure:
send_failure:
jsonipc_request_destroy(request);
parse_failure:
return result;
}

View File

@ -219,16 +219,7 @@ static ssize_t client_read(struct ctl_client* self, struct cmd_response** err)
return n; return n;
} }
static void client_advance_buffer(struct ctl_client* self, size_t len) static json_t* client_next_object(struct ctl_client* self, struct cmd_response** ierr)
{
size_t remainder = self->read_len - len;
if (remainder > 0)
memmove(self->read_buffer, self->read_buffer + len, remainder);
self->read_len = remainder;
}
static json_t* client_next_object(struct ctl_client* self,
struct cmd_response** ierr)
{ {
if (self->read_len == 0) if (self->read_len == 0)
return NULL; return NULL;
@ -238,7 +229,7 @@ static json_t* client_next_object(struct ctl_client* self,
JSON_DISABLE_EOF_CHECK, &err); JSON_DISABLE_EOF_CHECK, &err);
if (root) { if (root) {
nvnc_log(NVNC_LOG_DEBUG, "<< %.*s", err.position, self->read_buffer); nvnc_log(NVNC_LOG_DEBUG, "<< %.*s", err.position, self->read_buffer);
client_advance_buffer(self, err.position); advance_read_buffer(&self->read_buffer, &self->read_len, err.position);
} else if (json_error_code(&err) == json_error_premature_end_of_input) { } else if (json_error_code(&err) == json_error_premature_end_of_input) {
nvnc_trace("Awaiting more data"); nvnc_trace("Awaiting more data");
} else { } else {

View File

@ -43,3 +43,13 @@ const char* default_ctl_socket_path()
"/tmp/wayvncctl-%d", getuid()); "/tmp/wayvncctl-%d", getuid());
return buffer; return buffer;
} }
void advance_read_buffer(char (*buffer)[], size_t* current_len, size_t advance_by)
{
ssize_t remainder = *current_len - advance_by;
if (remainder < 0)
remainder = 0;
else if (remainder > 0)
memmove(*buffer, *buffer + advance_by, remainder);
*current_len = remainder;
}