Contributors: 5
Author Tokens Token Proportion Commits Commit Proportion
Tomas Glozar 832 78.79% 3 21.43%
Wander Lairson Costa 198 18.75% 7 50.00%
Daniel Bristot de Oliveira 16 1.52% 2 14.29%
Crystal Wood 8 0.76% 1 7.14%
Ivan Pravdin 2 0.19% 1 7.14%
Total 1056 14


// 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;

	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;
}