| // SPDX-License-Identifier: GPL-2.0 | |
| #include <stdlib.h> | |
| #include <string.h> | |
| #include <signal.h> | |
| #include <unistd.h> | |
| #include "actions.h" | |
| #include "trace.h" | |
| #include "utils.h" | |
| /* | |
| * actions_init - initialize struct actions | |
| */ | |
| void | |
| actions_init(struct actions *self) | |
| { | |
| self->size = action_default_size; | |
| self->list = calloc_fatal(self->size, sizeof(struct action)); | |
| self->len = 0; | |
| self->continue_flag = false; | |
| /* This has to be set by the user */ | |
| self->trace_output_inst = NULL; | |
| } | |
| /* | |
| * actions_destroy - destroy struct actions | |
| */ | |
| void | |
| actions_destroy(struct actions *self) | |
| { | |
| /* Free any action-specific data */ | |
| struct action *action; | |
| for_each_action(self, action) { | |
| if (action->type == ACTION_SHELL) | |
| free(action->command); | |
| if (action->type == ACTION_TRACE_OUTPUT) | |
| free(action->trace_output); | |
| } | |
| /* Free action list */ | |
| free(self->list); | |
| } | |
| /* | |
| * actions_new - Get pointer to new action | |
| */ | |
| static struct action * | |
| actions_new(struct actions *self) | |
| { | |
| if (self->len >= self->size) { | |
| const size_t new_size = self->size * 2; | |
| self->list = reallocarray_fatal(self->list, new_size, sizeof(struct action)); | |
| self->size = new_size; | |
| } | |
| return &self->list[self->len++]; | |
| } | |
| /* | |
| * actions_add_trace_output - add an action to output trace | |
| */ | |
| void | |
| actions_add_trace_output(struct actions *self, const char *trace_output) | |
| { | |
| struct action *action = actions_new(self); | |
| self->present[ACTION_TRACE_OUTPUT] = true; | |
| action->type = ACTION_TRACE_OUTPUT; | |
| action->trace_output = strdup_fatal(trace_output); | |
| } | |
| /* | |
| * actions_add_trace_output - add an action to send signal to a process | |
| */ | |
| void | |
| actions_add_signal(struct actions *self, int signal, int pid) | |
| { | |
| struct action *action = actions_new(self); | |
| self->present[ACTION_SIGNAL] = true; | |
| action->type = ACTION_SIGNAL; | |
| action->signal = signal; | |
| action->pid = pid; | |
| } | |
| /* | |
| * actions_add_shell - add an action to execute a shell command | |
| */ | |
| void | |
| actions_add_shell(struct actions *self, const char *command) | |
| { | |
| struct action *action = actions_new(self); | |
| self->present[ACTION_SHELL] = true; | |
| action->type = ACTION_SHELL; | |
| action->command = strdup_fatal(command); | |
| } | |
| /* | |
| * actions_add_continue - add an action to resume measurement | |
| */ | |
| void | |
| actions_add_continue(struct actions *self) | |
| { | |
| struct action *action = actions_new(self); | |
| self->present[ACTION_CONTINUE] = true; | |
| action->type = ACTION_CONTINUE; | |
| } | |
| static inline const char *__extract_arg(const char *token, const char *opt, size_t opt_len) | |
| { | |
| const size_t tok_len = strlen(token); | |
| if (tok_len <= opt_len) | |
| return NULL; | |
| if (strncmp(token, opt, opt_len)) | |
| return NULL; | |
| return token + opt_len; | |
| } | |
| /* | |
| * extract_arg - extract argument value from option token | |
| * @token: option token (e.g., "file=trace.txt") | |
| * @opt: option name to match (e.g., "file") | |
| * | |
| * Returns pointer to argument value after "=" if token matches "opt=", | |
| * otherwise returns NULL. | |
| */ | |
| #define extract_arg(token, opt) __extract_arg(token, opt "=", STRING_LENGTH(opt "=")) | |
| /* | |
| * actions_parse - add an action based on text specification | |
| */ | |
| int | |
| actions_parse(struct actions *self, const char *trigger, const char *tracefn) | |
| { | |
| enum action_type type = ACTION_NONE; | |
| const char *token; | |
| char trigger_c[strlen(trigger) + 1]; | |
| const char *arg_value; | |
| /* For ACTION_SIGNAL */ | |
| int signal = 0, pid = 0; | |
| /* For ACTION_TRACE_OUTPUT */ | |
| const char *trace_output; | |
| strcpy(trigger_c, trigger); | |
| token = strtok(trigger_c, ","); | |
| if (!token) | |
| return -1; | |
| if (strcmp(token, "trace") == 0) | |
| type = ACTION_TRACE_OUTPUT; | |
| else if (strcmp(token, "signal") == 0) | |
| type = ACTION_SIGNAL; | |
| else if (strcmp(token, "shell") == 0) | |
| type = ACTION_SHELL; | |
| else if (strcmp(token, "continue") == 0) | |
| type = ACTION_CONTINUE; | |
| else | |
| /* Invalid trigger type */ | |
| return -1; | |
| token = strtok(NULL, ","); | |
| switch (type) { | |
| case ACTION_TRACE_OUTPUT: | |
| /* Takes no argument */ | |
| if (token == NULL) | |
| trace_output = tracefn; | |
| else { | |
| trace_output = extract_arg(token, "file"); | |
| if (!trace_output) | |
| /* Invalid argument */ | |
| return -1; | |
| token = strtok(NULL, ","); | |
| if (token != NULL) | |
| /* Only one argument allowed */ | |
| return -1; | |
| } | |
| actions_add_trace_output(self, trace_output); | |
| break; | |
| case ACTION_SIGNAL: | |
| /* Takes two arguments, num (signal) and pid */ | |
| while (token != NULL) { | |
| arg_value = extract_arg(token, "num"); | |
| if (arg_value) { | |
| if (strtoi(arg_value, &signal)) | |
| return -1; | |
| } else { | |
| arg_value = extract_arg(token, "pid"); | |
| if (arg_value) { | |
| if (strncmp_static(arg_value, "parent") == 0) | |
| pid = -1; | |
| else if (strtoi(arg_value, &pid)) | |
| return -1; | |
| } else { | |
| /* Invalid argument */ | |
| return -1; | |
| } | |
| } | |
| token = strtok(NULL, ","); | |
| } | |
| if (!signal || !pid) | |
| /* Missing argument */ | |
| return -1; | |
| actions_add_signal(self, signal, pid); | |
| break; | |
| case ACTION_SHELL: | |
| if (token == NULL) | |
| return -1; | |
| arg_value = extract_arg(token, "command"); | |
| if (!arg_value) | |
| return -1; | |
| actions_add_shell(self, arg_value); | |
| break; | |
| case ACTION_CONTINUE: | |
| /* Takes no argument */ | |
| if (token != NULL) | |
| return -1; | |
| actions_add_continue(self); | |
| break; | |
| default: | |
| return -1; | |
| } | |
| return 0; | |
| } | |
| /* | |
| * actions_perform - perform all actions | |
| */ | |
| int | |
| actions_perform(struct actions *self) | |
| { | |
| int pid, retval; | |
| const struct action *action; | |
| self->continue_flag = false; | |
| for_each_action(self, action) { | |
| switch (action->type) { | |
| case ACTION_TRACE_OUTPUT: | |
| retval = save_trace_to_file(self->trace_output_inst, action->trace_output); | |
| if (retval) { | |
| err_msg("Error saving trace\n"); | |
| return retval; | |
| } | |
| break; | |
| case ACTION_SIGNAL: | |
| if (action->pid == -1) | |
| pid = getppid(); | |
| else | |
| pid = action->pid; | |
| retval = kill(pid, action->signal); | |
| if (retval) { | |
| err_msg("Error sending signal\n"); | |
| return retval; | |
| } | |
| break; | |
| case ACTION_SHELL: | |
| retval = system(action->command); | |
| if (retval) | |
| return retval; | |
| break; | |
| case ACTION_CONTINUE: | |
| self->continue_flag = true; | |
| return 0; | |
| default: | |
| break; | |
| } | |
| } | |
| return 0; | |
| } |