Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement a KFMon generator #35

Merged
merged 16 commits into from
May 25, 2020
Merged
Show file tree
Hide file tree
Changes from 15 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion res/doc
Original file line number Diff line number Diff line change
Expand Up @@ -84,9 +84,12 @@
# <location> the menu to add the items to, same as for menu_item.
# <generator> the generator to use to generate the options, one of:
# _test - generates numbered items, for testing only
# kfmon - adds items from kfmon (coming soon)
# kfmon - adds items from kfmon
# <arg> the argument passed to the generator (if needed):
# _test - the number of items to generate (0-10)
# kfmon - one or none of:
# gui - only enumerate non-hidden active KFMon watches (this is the default)
# all - enumerate *every* active KFMon watches
#
# For example, you might have a configuration file in KOBOeReader/.adds/nm/mystuff like:
#
Expand All @@ -96,6 +99,7 @@
# menu_item :main :Kernel Version :cmd_output :500:uname -a
# menu_item :main :Sketch Pad :nickel_extras :sketch_pad
# menu_item :main :Plato :kfmon :plato.png
# generator :main :kfmon
# menu_item :reader :Invert Screen :nickel_setting :invert
# menu_item :main :IP Address :cmd_output :500:/sbin/ifconfig | /usr/bin/awk '/inet addr/{print substr($2,6)}'
# menu_item :main :Telnet :cmd_spawn :quiet:/bin/mount -t devpts | /bin/grep -q /dev/pts || { /bin/mkdir -p /dev/pts && /bin/mount -t devpts devpts /dev/pts; }
Expand Down
1 change: 0 additions & 1 deletion src/action.h
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,6 @@ void nm_action_result_free(nm_action_result_t *res);
X(dbg_toast) \
X(kfmon) \
X(kfmon_id) \
X(kfmon_list) \
X(nickel_setting) \
X(nickel_extras) \
X(nickel_misc) \
Expand Down
7 changes: 0 additions & 7 deletions src/action_c.c
Original file line number Diff line number Diff line change
Expand Up @@ -46,10 +46,3 @@ NM_ACTION_(kfmon) {

return nm_kfmon_return_handler(status, err_out);
}

// PoC
NM_ACTION_(kfmon_list) {
// Request a list from KFMon, parses it, and send the results to syslog
int status = nm_kfmon_list_request(arg);
return nm_kfmon_return_handler(status, err_out);
}
3 changes: 2 additions & 1 deletion src/generator.h
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,8 @@ nm_menu_item_t **nm_generator_do(nm_generator_t *gen, size_t *sz_out);
#endif

#define NM_GENERATORS \
X(_test)
X(_test) \
X(kfmon)

#define X(name) NM_GENERATOR_(name);
NM_GENERATORS
Expand Down
54 changes: 54 additions & 0 deletions src/generator_c.c
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

#include "generator.h"
#include "menu.h"
#include "kfmon.h"
#include "util.h"

NM_GENERATOR_(_test) {
Expand Down Expand Up @@ -35,3 +36,56 @@ NM_GENERATOR_(_test) {

#undef NM_ERR_RET
}

NM_GENERATOR_(kfmon) {
#define NM_ERR_RET NULL

// Default with no arg or an empty arg is to request a gui-listing
const char *kfmon_cmd = NULL;
if (!arg || !*arg || !strcmp(arg, "gui")) {
kfmon_cmd = "gui-list";
} else if (!strcmp(arg, "all")) {
kfmon_cmd = "list";
} else {
NM_RETURN_ERR("invalid argument '%s': if specified, must be either gui or all", arg);
}

// We'll want to retrieve our watch list in there.
kfmon_watch_list_t list = { 0 };
int status = nm_kfmon_list_request(kfmon_cmd, &list);

// If there was an error, handle it now.
if (status != KFMON_IPC_OK) {
return nm_kfmon_error_handler(status, err_out);
}

// Handle an empty listing safely
if (list.count == 0) {
*sz_out = 0;
NM_RETURN_OK(NULL);
}

// And now we can start populating an array of nm_menu_item_t :)
*sz_out = list.count;
nm_menu_item_t **items = calloc(list.count, sizeof(nm_menu_item_t*));

// Walk the list to populate the items array
size_t i = 0;
for (kfmon_watch_node_t* node = list.head; node != NULL; node = node->next) {
items[i] = calloc(1, sizeof(nm_menu_item_t));
items[i]->action = calloc(1, sizeof(nm_menu_action_t));
items[i]->lbl = strdup(node->watch.label);
items[i]->action->act = NM_ACTION(kfmon);
items[i]->action->arg = strdup(node->watch.filename);
items[i]->action->on_failure = true;
items[i]->action->on_success = true;
i++;
}

// Destroy the list now that we've dumped it into an array of nm_menu_item_t
kfmon_teardown_list(&list);

NM_RETURN_OK(items);

#undef NM_ERR_RET
}
90 changes: 40 additions & 50 deletions src/kfmon.c
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,9 @@
#include "util.h"

// Free all resources allocated by a list and its nodes
static void teardown_list(kfmon_watch_list_t* list) {
inline void kfmon_teardown_list(kfmon_watch_list_t* list) {
kfmon_watch_node_t* node = list->head;
while (node) {
NM_LOG("Freeing node at %p", node);
kfmon_watch_node_t* p = node->next;
free(node->watch.filename);
free(node->watch.label);
Expand All @@ -31,7 +30,7 @@ static void teardown_list(kfmon_watch_list_t* list) {
}

// Allocate a single new node to the list
static int grow_list(kfmon_watch_list_t* list) {
inline int kfmon_grow_list(kfmon_watch_list_t* list) {
kfmon_watch_node_t* prev = list->tail;
kfmon_watch_node_t* node = calloc(1, sizeof(*node));
if (!node) {
Expand Down Expand Up @@ -71,21 +70,21 @@ static int handle_reply(int data_fd, void *data __attribute__((unused))) {
}

// Check the reply for failures
if (strncmp(buf, "ERR_INVALID_ID", 14) == 0) {
if (!strncmp(buf, "ERR_INVALID_ID", 14)) {
return KFMON_IPC_ERR_INVALID_ID;
} else if (strncmp(buf, "WARN_ALREADY_RUNNING", 20) == 0) {
} else if (!strncmp(buf, "WARN_ALREADY_RUNNING", 20)) {
return KFMON_IPC_WARN_ALREADY_RUNNING;
} else if (strncmp(buf, "WARN_SPAWN_BLOCKED", 18) == 0) {
} else if (!strncmp(buf, "WARN_SPAWN_BLOCKED", 18)) {
return KFMON_IPC_WARN_SPAWN_BLOCKED;
} else if (strncmp(buf, "WARN_SPAWN_INHIBITED", 20) == 0) {
} else if (!strncmp(buf, "WARN_SPAWN_INHIBITED", 20)) {
return KFMON_IPC_WARN_SPAWN_INHIBITED;
} else if (strncmp(buf, "ERR_REALLY_MALFORMED_CMD", 24) == 0) {
} else if (!strncmp(buf, "ERR_REALLY_MALFORMED_CMD", 24)) {
return KFMON_IPC_ERR_REALLY_MALFORMED_CMD;
} else if (strncmp(buf, "ERR_MALFORMED_CMD", 17) == 0) {
} else if (!strncmp(buf, "ERR_MALFORMED_CMD", 17)) {
return KFMON_IPC_ERR_MALFORMED_CMD;
} else if (strncmp(buf, "ERR_INVALID_CMD", 15) == 0) {
} else if (!strncmp(buf, "ERR_INVALID_CMD", 15)) {
return KFMON_IPC_ERR_INVALID_CMD;
} else if (strncmp(buf, "OK", 2) == 0) {
} else if (!strncmp(buf, "OK", 2)) {
return EXIT_SUCCESS;
} else {
return KFMON_IPC_UNKNOWN_REPLY;
Expand Down Expand Up @@ -122,12 +121,12 @@ static int handle_list_reply(int data_fd, void *data) {
}

// The only valid reply for list is... a list ;).
if (strncmp(buf, "ERR_INVALID_CMD", 15) == 0) {
if (!strncmp(buf, "ERR_INVALID_CMD", 15)) {
status = KFMON_IPC_ERR_INVALID_CMD;
goto cleanup;
} else if ((strncmp(buf, "WARN_", 5) == 0) ||
(strncmp(buf, "ERR_", 4) == 0) ||
(strncmp(buf, "OK", 2) == 0)) {
} else if ((!strncmp(buf, "WARN_", 5)) ||
(!strncmp(buf, "ERR_", 4)) ||
(!strncmp(buf, "OK", 2))) {
status = KFMON_IPC_UNKNOWN_REPLY;
goto cleanup;
}
Expand All @@ -143,8 +142,8 @@ static int handle_list_reply(int data_fd, void *data) {
eot = true;
}

// NOTE to self: syslog strips LF, you dummy.
NM_LOG("<<< Got a %zd bytes reply:\n%.*s", len, len, buf);
// Keep some minimal debug logging around, just in case...
NM_LOG("Got a %zd bytes reply from KFMon (%s an EoT marker)", len, eot ? "with" : "*without*");
// Now that we're sure we didn't get a wonky reply from an unrelated command, parse the list
// NOTE: Format is:
// id:filename:label or id:filename for watches without a label
Expand All @@ -155,46 +154,37 @@ static int handle_list_reply(int data_fd, void *data) {
char *line = NULL;
// Break the reply line by line
while ((line = strsep(&p, "\n")) != NULL) {
// Then parse each line
NM_LOG("Parsing line: %s\n", line);
// Then parse each line...
// If it's the final line, its only content is a single NUL
if (*line == '\0') {
// NOTE: This might also simply be the end of a single-line read,
// in which case the NUL is thanks to calloc...
NM_LOG("Caught an empty line! EoT? %d", eot);
break;
}
NM_LOG("Parsing reply line: `%s`", line);
// NOTE: Simple syslog logging for now
char *watch_idx = strsep(&line, ":");
if (!watch_idx) {
status = KFMON_IPC_LIST_PARSE_FAILURE;
goto cleanup;
}
NM_LOG("watch index: %s", watch_idx);
char *filename = strsep(&line, ":");
if (!filename) {
status = KFMON_IPC_LIST_PARSE_FAILURE;
goto cleanup;
}
NM_LOG("filename: '%s'", filename);
// Final separator is optional, if it's not there, there's no label, use the filename instead.
char *label = strsep(&line, ":");
if (label) {
NM_LOG("label: '%s'", label);
} else {
NM_LOG("label -> filename");
}

// Store that at the tail of the list
kfmon_watch_list_t* list = (kfmon_watch_list_t*) data;
// Make room for a new node
if (grow_list(list) != EXIT_SUCCESS) {
if (kfmon_grow_list(list) != EXIT_SUCCESS) {
status = KFMON_IPC_CALLOC_FAILURE;
break;
}
// Use it
kfmon_watch_node_t* node = list->tail;
NM_LOG("New node at %p", node);

node->watch.idx = (uint8_t) strtoul(watch_idx, NULL, 10);
node->watch.filename = strdup(filename);
Expand Down Expand Up @@ -367,8 +357,8 @@ int nm_kfmon_simple_request(const char *restrict ipc_cmd, const char *restrict i
return status;
}

// PoC handling of a list request
int nm_kfmon_list_request(const char *restrict foo __attribute__((unused))) {
// Handle a list request for the KFMon generator
int nm_kfmon_list_request(const char *restrict ipc_cmd, kfmon_watch_list_t* list) {
// Assume everything's peachy until shit happens...
int status = EXIT_SUCCESS;

Expand All @@ -382,49 +372,35 @@ int nm_kfmon_list_request(const char *restrict foo __attribute__((unused))) {
}

// Attempt to send the specified command in full over the wire
status = send_ipc_command(data_fd, "list", NULL); // FIXME: switch to gui-list
status = send_ipc_command(data_fd, ipc_cmd, NULL);
// If it failed, return early, after closing the socket
if (status != EXIT_SUCCESS) {
close(data_fd);
return status;
}

// We'll want to retrieve our watch list in there.
kfmon_watch_list_t list = { 0 };

// We'll be polling the socket for a reply, this'll make things neater, and allows us to abort on timeout,
// in the unlikely event there's already an IPC session being handled by KFMon,
// in which case the reply would be delayed by an undeterminate amount of time (i.e., until KFMon gets to it).
// Here, we'll want to timeout after 2s
ipc_handler_t handler = &handle_list_reply;
status = wait_for_replies(data_fd, 500, 4, handler, (void *) &list);
status = wait_for_replies(data_fd, 500, 4, handler, (void *) list);
// NOTE: We happen to be done with the connection right now.
// But if we still needed it, KFMON_IPC_POLL_FAILURE would warrant an early abort w/ a forced close().

// Walk the list
NM_LOG("List has %zu nodes", list.count);
NM_LOG("Head is at %p", list.head);
NM_LOG("Tail is at %p", list.tail);
for (kfmon_watch_node_t* node = list.head; node != NULL; node = node->next) {
NM_LOG("Dumping node at %p", node);
NM_LOG("idx: %hhu // filename: %s // label: %s", node->watch.idx, node->watch.filename, node->watch.label);
}

// Destroy it
teardown_list(&list);

// Bye now!
close(data_fd);
return status;
}

// Giant ladder of fail
nm_action_result_t* nm_kfmon_return_handler(kfmon_ipc_errno_e status, char **err_out) {
void* nm_kfmon_error_handler(kfmon_ipc_errno_e status, char **err_out) {
#define NM_ERR_RET NULL

switch (status) {
// NOTE: Should never be passed a success status code!
case KFMON_IPC_OK:
NM_RETURN_OK(nm_action_result_silent());
NM_RETURN_OK(NULL);
// Fail w/ the right log message
case KFMON_IPC_ETIMEDOUT:
NM_RETURN_ERR("Timed out waiting for KFMon");
Expand Down Expand Up @@ -478,3 +454,17 @@ nm_action_result_t* nm_kfmon_return_handler(kfmon_ipc_errno_e status, char **err

#undef NM_ERR_RET
}

nm_action_result_t* nm_kfmon_return_handler(kfmon_ipc_errno_e status, char **err_out) {
#define NM_ERR_RET NULL

switch (status) {
case KFMON_IPC_OK:
NM_RETURN_OK(nm_action_result_silent());
// Fail w/ the right log message
default:
return nm_kfmon_error_handler(status, err_out);
}

#undef NM_ERR_RET
}
12 changes: 10 additions & 2 deletions src/kfmon.h
Original file line number Diff line number Diff line change
Expand Up @@ -68,14 +68,22 @@ typedef struct {
// (e.g., a pointer to a kfmon_watch_list_t, or NULL if no storage is needed).
typedef int (*ipc_handler_t)(int, void *);

// Free all resources allocated by a list and its nodes
NiLuJe marked this conversation as resolved.
Show resolved Hide resolved
void kfmon_teardown_list(kfmon_watch_list_t* list);
// Allocate a single new node to the list
int kfmon_grow_list(kfmon_watch_list_t* list);

// Given one of the error codes listed above, return with a formatted error message
void* nm_kfmon_error_handler(kfmon_ipc_errno_e status, char **err_out);

// Given one of the error codes listed above, return properly from an action. Success is silent.
nm_action_result_t* nm_kfmon_return_handler(kfmon_ipc_errno_e status, char **err_out);

// Send a simple KFMon IPC request, one where the reply is only used for its diagnostic value.
int nm_kfmon_simple_request(const char *restrict ipc_cmd, const char *restrict ipc_arg);

// PoC list test action
int nm_kfmon_list_request(const char *restrict foo);
// Handle a list request for the KFMon generator
int nm_kfmon_list_request(const char *restrict ipc_cmd, kfmon_watch_list_t* list);

#ifdef __cplusplus
}
Expand Down