From f8b9c796f45359e60a4aba3a0c1af88158e9a8ec Mon Sep 17 00:00:00 2001 From: mstoeckl Date: Thu, 23 Aug 2018 17:25:48 -0400 Subject: [PATCH] Split parsing and execution of commands Due to variable substitution and string unescaping, parsing commands necessitates allocation. Because allocation can fail, commands (invoked, via bindings or criteria) may in turn fail when memory is restricted, which is most often the case in anomalous situations (i.e., a memory leak in sway, overcommit disabled, bad config/ipc, etc.). This commit defines a `struct stored_command` in which a parsed command can be stored, and then executed from. Command execution in execute_command now generates a series of struct stored_command, which are promptly invoked by execute_stored_command. --- include/sway/commands.h | 15 +++++- sway/commands.c | 102 ++++++++++++++++++++++------------------ 2 files changed, 71 insertions(+), 46 deletions(-) diff --git a/include/sway/commands.h b/include/sway/commands.h index 8e91c158f..81ae75def 100644 --- a/include/sway/commands.h +++ b/include/sway/commands.h @@ -51,7 +51,20 @@ struct cmd_handler *find_handler(char *line, struct cmd_handler *cmd_handlers, /** * Parse and executes a command. */ -struct cmd_results *execute_command(char *command, struct sway_seat *seat); +struct cmd_results *execute_command(const char *command, struct sway_seat *seat); + +struct stored_command { + sway_cmd *handle; + int argc; + char** argv; + struct criteria* criteria; +}; + +/** + * Executes a pre-parsed command. (Returns NULL if command was not unsuccessful: TODO, fix) + */ +struct cmd_results *execute_stored_command(const struct stored_command *command, + struct sway_seat *seat); /** * Parse and handles a command during config file loading. * diff --git a/sway/commands.c b/sway/commands.c index d9c54adc4..38cc9fac6 100644 --- a/sway/commands.c +++ b/sway/commands.c @@ -209,7 +209,7 @@ struct cmd_handler *find_handler(char *line, struct cmd_handler *cmd_handlers, return res; } -struct cmd_results *execute_command(char *_exec, struct sway_seat *seat) { +struct cmd_results *execute_command(const char *_exec, struct sway_seat *seat) { // Even though this function will process multiple commands we will only // return the last error, if any (for now). (Since we have access to an // error string we could e.g. concatenate all errors there.) @@ -218,7 +218,6 @@ struct cmd_results *execute_command(char *_exec, struct sway_seat *seat) { char *head = exec; char *cmdlist; char *cmd; - list_t *views = NULL; if (seat == NULL) { // passing a NULL seat means we just pick the default seat @@ -228,25 +227,20 @@ struct cmd_results *execute_command(char *_exec, struct sway_seat *seat) { } } - config->handler_context.seat = seat; - head = exec; do { // Extract criteria (valid for this command list only). - config->handler_context.using_criteria = false; + struct criteria *criteria = NULL; if (*head == '[') { char *error = NULL; - struct criteria *criteria = criteria_parse(head, &error); + criteria = criteria_parse(head, &error); if (!criteria) { results = cmd_results_new(CMD_INVALID, head, "%s", error); free(error); goto cleanup; } - views = criteria_get_views(criteria); head += strlen(criteria->raw); - criteria_destroy(criteria); - config->handler_context.using_criteria = true; // Skip leading whitespace head += strspn(head, whitespace); } @@ -280,6 +274,9 @@ struct cmd_results *execute_command(char *_exec, struct sway_seat *seat) { } results = cmd_results_new(CMD_INVALID, cmd, "Unknown/invalid command"); free_argv(argc, argv); + if (criteria) { + criteria_destroy(criteria); + } goto cleanup; } @@ -289,53 +286,68 @@ struct cmd_results *execute_command(char *_exec, struct sway_seat *seat) { unescape_string(argv[i]); } - if (!config->handler_context.using_criteria) { - // without criteria, the command acts upon the focused - // container - config->handler_context.current_container = - seat_get_focus_inactive(seat, &root_container); - if (!sway_assert(config->handler_context.current_container, - "could not get focus-inactive for root container")) { - return NULL; - } - struct cmd_results *res = handler->handle(argc-1, argv+1); - if (res->status != CMD_SUCCESS) { - free_argv(argc, argv); - if (results) { - free_cmd_results(results); - } - results = res; - goto cleanup; - } - free_cmd_results(res); - } else { - for (int i = 0; i < views->length; ++i) { - struct sway_view *view = views->items[i]; - config->handler_context.current_container = view->swayc; - struct cmd_results *res = handler->handle(argc-1, argv+1); - if (res->status != CMD_SUCCESS) { - free_argv(argc, argv); - if (results) { - free_cmd_results(results); - } - results = res; - goto cleanup; - } - free_cmd_results(res); - } - } + struct stored_command command; + command.handle = handler->handle; + command.argv = argv; + command.argc = argc; + command.criteria = criteria; + struct cmd_results *res = execute_stored_command(&command, seat); free_argv(argc, argv); + + if (res->status != CMD_SUCCESS) { + if (results) { + free_cmd_results(results); + } + results = res; + if (criteria) { + criteria_destroy(criteria); + } + goto cleanup; + } + free_cmd_results(res); + } while(cmdlist); } while(head); cleanup: free(exec); - list_free(views); if (!results) { results = cmd_results_new(CMD_SUCCESS, NULL, NULL); } return results; } +struct cmd_results *execute_stored_command(const struct stored_command* command, + struct sway_seat* seat) { + config->handler_context.seat = seat; + + if (!command->criteria) { + // without criteria, the command acts upon the focused container + config->handler_context.using_criteria = true; + config->handler_context.current_container = + seat_get_focus_inactive(seat, &root_container); + if (!sway_assert(config->handler_context.current_container, + "could not get focus-inactive for root container")) { + return NULL; + } + return command->handle(command->argc-1, command->argv+1); + } else { + config->handler_context.using_criteria = false; + list_t *views = criteria_get_views(command->criteria); + for (int i = 0; i < views->length; ++i) { + struct sway_view *view = views->items[i]; + config->handler_context.current_container = view->swayc; + struct cmd_results *res = command->handle(command->argc-1, command->argv+1); + if (res->status != CMD_SUCCESS) { + return res; + } + free_cmd_results(res); + } + + list_free(views); + } + return NULL; +} + // this is like execute_command above, except: // 1) it ignores empty commands (empty lines) // 2) it does variable substitution