This commit is contained in:
retoor 2026-04-03 10:42:43 +02:00
parent bd31abcbdb
commit 1057624ab8
28 changed files with 508 additions and 247 deletions

View File

@ -1,7 +1,7 @@
# retoor <retoor@molodetz.nl>
CC = gcc
CFLAGS = -Ofast -Werror -Wall -I./include
CFLAGS = -O3 -Werror -Wall -Wextra -Wshadow -I./include
LDFLAGS = -lreadline -lncurses -lcurl -ljson-c -lsqlite3 -lm -lpthread -lssl -lcrypto
SRCDIR = src
@ -21,6 +21,7 @@ SRC_CORE = $(SRCDIR)/r_error.c \
$(SRCDIR)/context_manager.c \
$(SRCDIR)/markdown.c \
$(SRCDIR)/r_diff.c \
$(SRCDIR)/json_repair.c \
$(SRCDIR)/main.c
SRC_TOOLS = $(TOOLSDIR)/tools_init.c \

View File

@ -3,6 +3,7 @@
#ifndef R_MESSAGES_H
#define R_MESSAGES_H
#include "db.h"
#include "r_error.h"
#include <json-c/json.h>
#include <stdbool.h>
@ -37,4 +38,6 @@ int messages_count(messages_handle msgs);
char *messages_generate_session_id(void);
db_handle messages_get_db(messages_handle msgs);
#endif

View File

@ -24,7 +24,6 @@ bool r_config_is_verbose(r_config_handle cfg);
void r_config_set_verbose(r_config_handle cfg, bool verbose);
double r_config_get_temperature(r_config_handle cfg);
int r_config_get_max_tokens(r_config_handle cfg);
const char *r_config_get_session_id(r_config_handle cfg);
bool r_config_set_session_id(r_config_handle cfg, const char *session_id);

View File

@ -27,6 +27,7 @@ struct agent_t {
int refusal_retry_count;
bool tools_were_used;
int goal_verification_count;
int consecutive_tool_error_iterations;
agent_state_t state;
time_t start_time;
char *last_error;
@ -85,29 +86,29 @@ static const char *refusal_phrases[] = {
};
extern tool_registry_t *tools_get_registry(void);
static void agent_update_heartbeat(agent_handle agent) {
if (!agent || !agent->agent_id) return;
db_handle db = db_open(NULL);
if (!agent || !agent->agent_id || !agent->messages) return;
db_handle db = messages_get_db(agent->messages);
if (!db) return;
char *sql = sqlite3_mprintf("UPDATE agents SET last_heartbeat = CURRENT_TIMESTAMP WHERE agent_id = %Q", agent->agent_id);
struct json_object *res = NULL;
db_execute(db, sql, &res);
sqlite3_free(sql);
if (res) json_object_put(res);
db_close(db);
}
static bool agent_check_budget(agent_handle agent) {
if (!agent || agent->budget_limit <= 0) return true;
return agent->used_tokens < agent->budget_limit;
}
static void agent_add_tokens(agent_handle agent, long tokens) {
if (!agent || !agent->agent_id) return;
if (!agent || !agent->agent_id || !agent->messages) return;
agent->used_tokens += tokens;
db_handle db = db_open(NULL);
db_handle db = messages_get_db(agent->messages);
if (!db) return;
char *sql = sqlite3_mprintf("UPDATE agents SET used_tokens = used_tokens + %ld WHERE agent_id = %Q", tokens, agent->agent_id);
struct json_object *res = NULL;
db_execute(db, sql, &res);
sqlite3_free(sql);
if (res) json_object_put(res);
db_close(db);
}
static void agent_set_error(agent_handle agent, const char *error) {
if (!agent) return;
@ -132,8 +133,6 @@ static char *agent_build_request(agent_handle agent, const char *role, const cha
json_object_get(messages_to_json(agent->messages)));
json_object_object_add(root, "temperature",
json_object_new_double(r_config_get_temperature(cfg)));
json_object_object_add(root, "max_tokens",
json_object_new_int(r_config_get_max_tokens(cfg)));
char *result = strdup(json_object_to_json_string_ext(root, JSON_C_TO_STRING_PRETTY));
if (agent->verbose && !agent->is_subagent) {
fprintf(stderr, "\n[LLM Request]\n%s\n", result);
@ -190,6 +189,28 @@ static struct json_object *agent_process_response(agent_handle agent, const char
json_object_put(parsed);
return NULL;
}
struct json_object *finish_reason_obj;
if (json_object_object_get_ex(first_choice, "finish_reason", &finish_reason_obj)) {
const char *finish_reason = json_object_get_string(finish_reason_obj);
if (finish_reason && strcmp(finish_reason, "length") == 0) {
struct json_object *msg_obj;
if (json_object_object_get_ex(first_choice, "message", &msg_obj)) {
struct json_object *tc_obj;
if (json_object_object_get_ex(msg_obj, "tool_calls", &tc_obj) &&
json_object_array_length(tc_obj) > 0) {
if (agent->verbose) {
fprintf(stderr, "[Agent] Output truncated (finish_reason=length) with pending tool_calls, stripping truncated calls\n");
}
json_object_object_del(msg_obj, "tool_calls");
json_object_object_add(msg_obj, "content",
json_object_new_string(
"My previous response was truncated due to output length limits. "
"I will split large file writes into smaller sections or use "
"linux_terminal_execute with heredoc for large content."));
}
}
}
}
return first_choice;
}
static bool agent_has_tool_calls(struct json_object *choice) {
@ -273,14 +294,6 @@ agent_handle agent_create(const char *goal, messages_handle messages) {
agent->agent_id = strdup("Executive-Apex");
agent->role = strdup("Executive");
agent->budget_limit = 1000000;
db_handle db = db_open(NULL);
char *sql = sqlite3_mprintf("INSERT OR IGNORE INTO agents (agent_id, role, budget_limit_tokens) VALUES (%Q, %Q, %ld)",
agent->agent_id, agent->role, agent->budget_limit);
struct json_object *res = NULL;
db_execute(db, sql, &res);
sqlite3_free(sql);
if (res) json_object_put(res);
db_close(db);
if (messages) {
agent->messages = messages;
agent->owns_messages = false;
@ -311,6 +324,15 @@ agent_handle agent_create(const char *goal, messages_handle messages) {
messages_add(agent->messages, "system", system_msg);
}
}
db_handle db = messages_get_db(agent->messages);
if (db) {
char *sql = sqlite3_mprintf("INSERT OR IGNORE INTO agents (agent_id, role, budget_limit_tokens) VALUES (%Q, %Q, %ld)",
agent->agent_id, agent->role, agent->budget_limit);
struct json_object *res = NULL;
db_execute(db, sql, &res);
sqlite3_free(sql);
if (res) json_object_put(res);
}
agent->http = http_client_create(r_config_get_api_key(cfg));
if (!agent->http) {
if (agent->owns_messages) {
@ -325,14 +347,26 @@ agent_handle agent_create(const char *goal, messages_handle messages) {
}
void agent_destroy(agent_handle agent) {
if (!agent) return;
if (agent->http) http_client_destroy(agent->http);
if (agent->messages && agent->owns_messages) messages_destroy(agent->messages);
if (agent->http) {
http_client_destroy(agent->http);
agent->http = NULL;
}
if (agent->messages && agent->owns_messages) {
messages_destroy(agent->messages);
agent->messages = NULL;
}
free(agent->agent_id);
agent->agent_id = NULL;
free(agent->role);
agent->role = NULL;
free(agent->manager_id);
agent->manager_id = NULL;
free(agent->department);
agent->department = NULL;
free(agent->goal);
agent->goal = NULL;
free(agent->last_error);
agent->last_error = NULL;
free(agent);
}
void agent_set_max_iterations(agent_handle agent, int max) {
@ -388,6 +422,7 @@ char *agent_run(agent_handle agent, const char *user_message) {
agent->refusal_retry_count = 0;
agent->tools_were_used = false;
agent->goal_verification_count = 0;
agent->consecutive_tool_error_iterations = 0;
if (!user_message || !*user_message) {
agent->state = AGENT_STATE_ERROR;
agent_set_error(agent, "Empty user message");
@ -473,11 +508,15 @@ char *agent_run(agent_handle agent, const char *user_message) {
if (new_acc) {
accumulated_response = new_acc;
if (accumulated_len > 0) {
strcat(accumulated_response, "\n");
accumulated_response[accumulated_len] = '\n';
accumulated_len += 1;
}
strcpy(accumulated_response + accumulated_len, content);
memcpy(accumulated_response + accumulated_len, content, content_len + 1);
accumulated_len += content_len;
} else {
free(accumulated_response);
accumulated_response = NULL;
accumulated_len = 0;
}
}
bool has_tools = agent_has_tool_calls(choice);
@ -494,12 +533,39 @@ char *agent_run(agent_handle agent, const char *user_message) {
}
struct json_object *results = tool_registry_execute(agent->tools, tool_calls, agent->verbose);
int count = json_object_array_length(results);
bool all_errors = count > 0;
for (int i = 0; i < count; i++) {
struct json_object *result = json_object_array_get_idx(results, i);
messages_add_tool_call(agent->messages, json_object_get(result));
struct json_object *content_obj;
if (all_errors && json_object_object_get_ex(result, "content", &content_obj)) {
const char *content_str = json_object_get_string(content_obj);
if (!content_str || strncmp(content_str, "Error", 5) != 0) {
all_errors = false;
}
}
}
if (all_errors) {
agent->consecutive_tool_error_iterations++;
} else {
agent->consecutive_tool_error_iterations = 0;
}
agent->state = AGENT_STATE_RUNNING;
json_data = agent_build_request(agent, NULL, NULL);
if (agent->consecutive_tool_error_iterations >= 2) {
agent->consecutive_tool_error_iterations = 0;
json_data = agent_build_request(agent, "user",
"Your last tool calls failed repeatedly. The most likely cause is "
"output truncation: your tool call arguments were cut off mid-JSON "
"because the content was too large. To fix this:\n"
"1. For large file writes, use linux_terminal_execute with cat and "
"heredoc (cat << 'HEREDOC_EOF' > file.html) instead of write_file.\n"
"2. If you must use write_file, keep the content argument under 4000 "
"characters. Split large files into multiple write operations using "
"file_edit or append mode.\n"
"3. Re-read any error messages carefully and verify argument types.");
} else {
json_data = agent_build_request(agent, NULL, NULL);
}
if (!json_data) {
agent->state = AGENT_STATE_ERROR;
agent_set_error(agent, "Failed to create follow-up JSON");

View File

@ -33,7 +33,8 @@ r_process_result_t *r_bash_execute_ext(const char *command, int timeout_seconds,
char tmp_script[] = "/tmp/r_bash_XXXXXX.sh";
int script_fd = mkstemps(tmp_script, 3);
if (script_fd == -1) {
res->output = strdup("Error: failed to create temp script");
char *msg = strdup("Error: failed to create temp script");
res->output = msg ? msg : NULL;
return res;
}
dprintf(script_fd, "%s\n", command);
@ -41,7 +42,8 @@ r_process_result_t *r_bash_execute_ext(const char *command, int timeout_seconds,
int pipe_fds[2];
if (pipe(pipe_fds) == -1) {
unlink(tmp_script);
res->output = strdup("Error: pipe failed");
char *msg = strdup("Error: pipe failed");
res->output = msg ? msg : NULL;
return res;
}
pid_t pid = fork();
@ -49,7 +51,8 @@ r_process_result_t *r_bash_execute_ext(const char *command, int timeout_seconds,
close(pipe_fds[0]);
close(pipe_fds[1]);
unlink(tmp_script);
res->output = strdup("Error: fork failed");
char *msg = strdup("Error: fork failed");
res->output = msg ? msg : NULL;
return res;
}
if (pid == 0) {
@ -143,9 +146,10 @@ r_process_result_t *r_bash_execute_ext(const char *command, int timeout_seconds,
return res;
}
char *r_bash_execute(const char *command, bool interactive, int timeout_seconds) {
// Legacy support wrapper
(void)interactive;
r_process_result_t *res = r_bash_execute_ext(command, timeout_seconds, false);
char *out = strdup(res->output);
if (!res) return NULL;
char *out = res->output ? strdup(res->output) : strdup("");
r_process_result_free(res);
return out;
}

View File

@ -16,10 +16,13 @@ static char *ensure_shebang(const char *text) {
}
// Heuristic: if it has multiple lines, add shebang
if (strchr(text, '\n')) {
char *result = malloc(strlen(text) + 32);
const char *shebang = "#!/usr/bin/env bash\n";
size_t shebang_len = strlen(shebang);
size_t text_len = strlen(text);
char *result = malloc(shebang_len + text_len + 1);
if (!result) return strdup(text);
strcpy(result, "#!/usr/bin/env bash\n");
strcat(result, text);
memcpy(result, shebang, shebang_len);
memcpy(result + shebang_len, text, text_len + 1);
return result;
}
return strdup(text);
@ -121,8 +124,14 @@ static char *fix_line_issues(const char *src) {
else if (line_buf[i] == '\'') single++;
else if (line_buf[i] == '"') double_q++;
}
if (single % 2 == 1 && double_q == 0) strcat(line_buf, "'");
else if (double_q % 2 == 1 && single == 0) strcat(line_buf, "\"");
size_t buf_len = strlen(line_buf);
if (single % 2 == 1 && double_q == 0 && buf_len + 1 < sizeof(line_buf)) {
line_buf[buf_len] = '\'';
line_buf[buf_len + 1] = '\0';
} else if (double_q % 2 == 1 && single == 0 && buf_len + 1 < sizeof(line_buf)) {
line_buf[buf_len] = '"';
line_buf[buf_len + 1] = '\0';
}
// 3. Fix trailing operators
size_t cur_len = strlen(line_buf);
const char *ops[] = {"||", "&&", ">>", "|&", "|", ">", "<", NULL};
@ -130,24 +139,26 @@ static char *fix_line_issues(const char *src) {
size_t op_len = strlen(ops[i]);
if (cur_len >= op_len) {
if (strcmp(line_buf + cur_len - op_len, ops[i]) == 0) {
// Comment it
char temp[4096];
strcpy(temp, line_buf);
temp[cur_len - op_len] = '\0';
strcat(temp, "# ");
strcat(temp, ops[i]);
strcpy(line_buf, temp);
size_t prefix_len = cur_len - op_len;
memcpy(temp, line_buf, prefix_len);
int written = snprintf(temp + prefix_len, sizeof(temp) - prefix_len, "# %s", ops[i]);
if (written > 0 && prefix_len + (size_t)written < sizeof(line_buf)) {
memcpy(line_buf, temp, prefix_len + (size_t)written + 1);
}
break;
}
}
}
// 4. Dangerous rm -rf check
if (strstr(line_buf, "sudo rm -rf /") || strstr(line_buf, "rm -rf / ")) {
strcpy(dst, "# WARNING: potentially destructive command detected\n");
dst += strlen(dst);
const char *warning = "# WARNING: potentially destructive command detected\n";
size_t warn_len = strlen(warning);
memcpy(dst, warning, warn_len);
dst += warn_len;
}
strcpy(dst, line_buf);
dst += strlen(dst);
size_t final_len = strlen(line_buf);
memcpy(dst, line_buf, final_len);
dst += final_len;
if (next_line) *dst++ = '\n';
if (next_line) line = next_line + 1;
else break;
@ -183,13 +194,14 @@ static char *collapse_nested_bash_c(const char *src) {
size_t prefix_len = (size_t)(p - s1);
memcpy(new_s, s1, prefix_len);
char *d = new_s + prefix_len;
strcpy(d, "bash -c ");
memcpy(d, "bash -c ", 8);
d += 8;
*d++ = inner;
memcpy(d, p + 19, cmd_len);
d += cmd_len;
*d++ = inner;
strcpy(d, inner_end + 2);
size_t tail_len = strlen(inner_end + 2);
memcpy(d, inner_end + 2, tail_len + 1);
free(s1);
s1 = new_s;

View File

@ -61,12 +61,14 @@ static r_status_t perform_truncate(messages_handle msgs, int index, double ratio
if (target_len >= len) return R_SUCCESS;
size_t keep_each = target_len / 2;
char *new_content = malloc(keep_each * 2 + strlen(TRUNCATE_MARKER) + 1);
size_t marker_len = strlen(TRUNCATE_MARKER);
char *new_content = malloc(keep_each * 2 + marker_len + 1);
if (!new_content) return R_ERROR_OUT_OF_MEMORY;
strncpy(new_content, content, keep_each);
new_content[keep_each] = '\0';
strcat(new_content, TRUNCATE_MARKER);
strcat(new_content, content + len - keep_each);
memcpy(new_content, content, keep_each);
memcpy(new_content + keep_each, TRUNCATE_MARKER, marker_len);
size_t offset = keep_each + marker_len;
memcpy(new_content + offset, content + len - keep_each, keep_each);
new_content[offset + keep_each] = '\0';
struct json_object *new_msg = json_tokener_parse(json_object_to_json_string(msg));
if (is_tool_result) {
json_object_object_add(new_msg, "tool_result", json_object_new_string(new_content));

View File

@ -6,12 +6,7 @@
// In a real implementation, this function would call the LLM API to get the summary.
static char* call_llm_to_summarize(const char* messages_concatenated) {
// For demonstration, just return a dummy summary.
const char* dummy_summary = "This is a summary of the oldest 20 messages.";
char* result = malloc(strlen(dummy_summary) + 1);
if (result) {
strcpy(result, dummy_summary);
}
return result;
return strdup("This is a summary of the oldest 20 messages.");
}
char* summarize_oldest_messages(const char** messages, size_t message_count) {
// Concatenate the oldest 20 messages
@ -27,13 +22,16 @@ char* summarize_oldest_messages(const char** messages, size_t message_count) {
if (!concatenated) {
return NULL;
}
concatenated[0] = '\0';
size_t write_offset = 0;
for (size_t i = start_index; i < message_count; ++i) {
strcat(concatenated, messages[i]);
size_t msg_len = strlen(messages[i]);
memcpy(concatenated + write_offset, messages[i], msg_len);
write_offset += msg_len;
if (i < message_count - 1) {
strcat(concatenated, " "); // separator
concatenated[write_offset++] = ' ';
}
}
concatenated[write_offset] = '\0';
// Call the LLM API to get the summary
char* summary = call_llm_to_summarize(concatenated);
free(concatenated);

View File

@ -18,10 +18,10 @@ static char *expand_home_directory(const char *path) {
if (!home_dir) return strdup(path);
size_t home_len = strlen(home_dir);
size_t path_len = strlen(path);
char *expanded = malloc(home_len + path_len);
char *expanded = malloc(home_len + path_len + 1);
if (!expanded) return NULL;
strcpy(expanded, home_dir);
strcat(expanded, path + 1);
memcpy(expanded, home_dir, home_len);
memcpy(expanded + home_len, path + 1, path_len);
return expanded;
}
db_handle db_open(const char *path) {

View File

@ -2,6 +2,7 @@
#include "http_client.h"
#include <curl/curl.h>
#include <pthread.h>
#include <stdatomic.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
@ -21,7 +22,7 @@ struct response_buffer_t {
size_t size;
};
static struct timespec spinner_start_time = {0, 0};
static volatile int spinner_running = 0;
static atomic_int spinner_running = 0;
static double get_elapsed_seconds(void) {
struct timespec now;
clock_gettime(CLOCK_MONOTONIC, &now);
@ -32,7 +33,7 @@ static void *spinner_thread(void *arg) {
(void)arg;
const char *frames[] = {"", "", "", "", "", "", "", "", "", ""};
int frame = 0;
while (spinner_running) {
while (atomic_load(&spinner_running)) {
double elapsed = get_elapsed_seconds();
fprintf(stderr, "\r%s Querying AI... (%.1fs) ", frames[frame % 10], elapsed);
fflush(stderr);
@ -99,8 +100,11 @@ r_status_t http_post(http_client_handle client, const char *url,
bool actually_show_spinner = client->show_spinner && isatty(STDERR_FILENO);
if (actually_show_spinner) {
clock_gettime(CLOCK_MONOTONIC, &spinner_start_time);
spinner_running = 1;
pthread_create(&spinner_tid, NULL, spinner_thread, NULL);
atomic_store(&spinner_running, 1);
if (pthread_create(&spinner_tid, NULL, spinner_thread, NULL) != 0) {
spinner_tid = 0;
actually_show_spinner = false;
}
}
while (retry_count < HTTP_MAX_RETRIES) {
free(resp.data);
@ -142,8 +146,8 @@ r_status_t http_post(http_client_handle client, const char *url,
goto cleanup;
}
retry_count++;
if (actually_show_spinner) {
spinner_running = 0;
if (actually_show_spinner && spinner_tid) {
atomic_store(&spinner_running, 0);
pthread_join(spinner_tid, NULL);
spinner_tid = 0;
fprintf(stderr, "\r \r");
@ -155,15 +159,17 @@ r_status_t http_post(http_client_handle client, const char *url,
usleep(HTTP_RETRY_DELAY_MS * 1000);
if (actually_show_spinner) {
clock_gettime(CLOCK_MONOTONIC, &spinner_start_time);
spinner_running = 1;
pthread_create(&spinner_tid, NULL, spinner_thread, NULL);
atomic_store(&spinner_running, 1);
if (pthread_create(&spinner_tid, NULL, spinner_thread, NULL) != 0) {
spinner_tid = 0;
}
}
}
}
status = R_ERROR_HTTP_TIMEOUT;
cleanup:
if (actually_show_spinner && spinner_tid) {
spinner_running = 0;
atomic_store(&spinner_running, 0);
pthread_join(spinner_tid, NULL);
fprintf(stderr, "\r \r");
fflush(stderr);

View File

@ -35,11 +35,11 @@ static const char *resolve_api_key(void) {
if (key && *key) return key;
key = getenv("OPENAI_API_KEY");
if (key && *key) return key;
return "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";
return NULL;
}
static bool is_valid_session_id(const char *session_id) {
if (!session_id || !*session_id) return false;
if (strlen(session_id) > 255) return false;
if (strlen(session_id) > 200) return false;
for (const char *p = session_id; *p; p++) {
if (!isalnum((unsigned char)*p) && *p != '-' && *p != '_' && *p != '.') {
return false;
@ -66,7 +66,8 @@ config_handle config_create(void) {
}
const char *model = getenv("R_MODEL");
cfg->model = strdup(model && *model ? model : "gpt-4o-mini");
cfg->api_key = strdup(resolve_api_key());
const char *resolved_key = resolve_api_key();
cfg->api_key = resolved_key ? strdup(resolved_key) : NULL;
cfg->db_path = strdup("~/.r.db");
cfg->temperature = 0.1;
cfg->use_tools = resolve_env_bool("R_USE_TOOLS", true);

View File

@ -256,6 +256,13 @@ static char *balance_brackets(const char *src) {
}
*dst++ = *p++;
}
if (in_string) {
if (escaped) {
*(dst - 1) = '"';
} else {
*dst++ = '"';
}
}
while (top > 0) {
char opener = stack[--top];
*dst++ = (opener == '{') ? '}' : ']';

View File

@ -1,5 +1,8 @@
// retoor <retoor@molodetz.nl>
#ifndef LINE_H
#define LINE_H
#include "utils.h"
#include <glob.h>
#include <readline/history.h>
@ -81,6 +84,7 @@ char *line_file_generator(const char *text, int state) {
}
char **line_command_completion(const char *text, int start, int end) {
(void)end;
rl_attempted_completion_over = 1;
// Check if the input is a file path
@ -254,3 +258,5 @@ void line_add_history(char *data) {
add_history(data);
write_history(get_history_file());
}
#endif

View File

@ -20,7 +20,7 @@
#include <time.h>
#include <unistd.h>
static volatile sig_atomic_t sigint_count = 0;
static time_t first_sigint_time = 0;
static struct timespec first_sigint_ts = {0, 0};
static bool syntax_highlight_enabled = true;
static bool api_mode = false;
static db_handle global_db = NULL;
@ -35,40 +35,51 @@ static void repl(void);
static void init(void);
static void cleanup(void);
static void handle_sigint(int sig);
extern char **environ;
static const char *safe_env_prefixes[] = {
"LANG=", "LC_", "TERM=", "SHELL=", "USER=", "HOME=",
"PATH=", "HOSTNAME=", "EDITOR=", "VISUAL=", "TZ=",
"R_", "OPENROUTER_", "XDG_",
NULL
};
static char *get_env_string(void) {
FILE *fp = popen("env", "r");
if (!fp)
return NULL;
size_t buffer_size = 1024;
size_t total_size = 0;
size_t buffer_size = 4096;
size_t offset = 0;
char *output = malloc(buffer_size);
if (!output) {
pclose(fp);
return NULL;
}
size_t bytes_read;
while ((bytes_read = fread(output + total_size, 1, buffer_size - total_size,
fp)) > 0) {
total_size += bytes_read;
if (total_size >= buffer_size) {
if (!output) return NULL;
output[0] = '\0';
for (char **env = environ; *env; env++) {
bool allowed = false;
for (int i = 0; safe_env_prefixes[i]; i++) {
if (strncmp(*env, safe_env_prefixes[i], strlen(safe_env_prefixes[i])) == 0) {
allowed = true;
break;
}
}
if (!allowed) continue;
size_t entry_len = strlen(*env);
if (offset + entry_len + 2 >= buffer_size) {
buffer_size *= 2;
char *temp = realloc(output, buffer_size);
if (!temp) {
char *expanded = realloc(output, buffer_size);
if (!expanded) {
free(output);
pclose(fp);
return NULL;
}
output = temp;
output = expanded;
}
memcpy(output + offset, *env, entry_len);
offset += entry_len;
output[offset++] = '\n';
}
output[total_size] = '\0';
pclose(fp);
output[offset] = '\0';
return output;
}
static char *get_prompt_from_stdin(char *prompt) {
int index = 0;
int c;
int max_size = 10 * 1024 * 1024;
while ((c = getchar()) != EOF) {
if (index >= max_size - 1) break;
prompt[index++] = (char)c;
}
prompt[index] = '\0';
@ -112,8 +123,15 @@ static char *get_prompt_from_args(int argc, char **argv) {
i++;
continue;
} else {
strcat(system_msg, argv[i]);
strcat(system_msg, (i < argc - 1) ? " " : ".");
size_t remaining = (1024 * 1024) - strlen(system_msg) - 2;
size_t arg_len = strlen(argv[i]);
if (arg_len < remaining) {
size_t offset = strlen(system_msg);
memcpy(system_msg + offset, argv[i], arg_len);
const char *sep = (i < argc - 1) ? " " : ".";
system_msg[offset + arg_len] = sep[0];
system_msg[offset + arg_len + 1] = '\0';
}
}
}
if (get_from_stdin) {
@ -141,7 +159,6 @@ static bool try_prompt(int argc, char *argv[]) {
free(prompt);
return false;
}
// response is already printed inside agent_run
free(response);
free(prompt);
return true;
@ -164,6 +181,8 @@ static void repl(void) {
line_init();
char *line = NULL;
while (true) {
free(line);
line = NULL;
line = line_read(line_build_prompt());
if (!line || !*line)
continue;
@ -209,7 +228,7 @@ static void repl(void) {
verbose ? "Verbose mode enabled" : "Verbose mode disabled");
continue;
}
if (line && *line != '\n') {
if (*line != '\n') {
line_add_history(line);
}
if (!strncmp(line, "!tools", 6)) {
@ -240,18 +259,17 @@ static void repl(void) {
continue;
}
if (!strncmp(line, "exit", 4)) {
free(line);
line = NULL;
exit(0);
}
while (line && *line != '\n') {
if (*line != '\n') {
char *response = agent_chat(line, global_messages);
if (response) {
// response is already printed inside agent_run via
// parse_markdown_to_ansi
free(response);
} else {
fprintf(stderr, "Agent returned no response\n");
}
line = NULL;
}
}
}
@ -263,17 +281,22 @@ static void init(void) {
global_db = db_open(NULL);
global_messages = messages_create(r_config_get_session_id(cfg));
char *schema = db_get_schema(global_db);
char payload[1024 * 1024] = {0};
char *payload = calloc(1, 1024 * 1024);
if (!payload) {
free(schema);
return;
}
time_t now = time(NULL);
struct tm *tm_info = localtime(&now);
char datetime[64];
strftime(datetime, sizeof(datetime), "%Y-%m-%d %H:%M:%S %Z", tm_info);
char cwd[4096];
if (!getcwd(cwd, sizeof(cwd))) {
strcpy(cwd, "unknown");
snprintf(cwd, sizeof(cwd), "unknown");
}
size_t payload_size = 1024 * 1024;
snprintf(
payload, sizeof(payload),
payload, payload_size,
"# AUTONOMOUS AGENT INSTRUCTIONS\n"
"Current date/time: %s\n"
"Working directory: %s\n\n"
@ -419,6 +442,7 @@ static void init(void) {
if (global_messages) {
messages_add(global_messages, "system", payload);
}
free(payload);
const char *env_system_msg = r_config_get_system_message(cfg);
if (env_system_msg && *env_system_msg && global_messages) {
messages_add(global_messages, "system", env_system_msg);
@ -441,20 +465,24 @@ static void cleanup(void) {
spawn_tracker_destroy();
r_config_destroy();
}
static volatile sig_atomic_t exit_requested = 0;
static void handle_sigint(int sig) {
(void)sig;
time_t current_time = time(NULL);
printf("\n");
const char nl = '\n';
(void)!write(STDERR_FILENO, &nl, 1);
struct timespec now_ts;
clock_gettime(CLOCK_MONOTONIC, &now_ts);
if (sigint_count == 0) {
first_sigint_time = current_time;
sigint_count++;
first_sigint_ts = now_ts;
sigint_count = 1;
} else {
if (difftime(current_time, first_sigint_time) <= 1) {
cleanup();
exit(0);
long elapsed = now_ts.tv_sec - first_sigint_ts.tv_sec;
if (elapsed <= 1) {
exit_requested = 1;
_exit(0);
} else {
sigint_count = 1;
first_sigint_time = current_time;
first_sigint_ts = now_ts;
}
}
}
@ -486,9 +514,12 @@ int main(int argc, char *argv[]) {
parse_session_arg(argc, argv);
init();
char *env_string = get_env_string();
if (env_string && *env_string && global_messages) {
messages_add(global_messages, "system", env_string);
if (env_string) {
if (*env_string && global_messages) {
messages_add(global_messages, "system", env_string);
}
free(env_string);
env_string = NULL;
}
messages_load(global_messages);
if (try_prompt(argc, argv)) {

View File

@ -2,12 +2,14 @@
#include "messages.h"
#include "db.h"
#include <ctype.h>
#include <limits.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#define MAX_CONTENT_LENGTH 1048570
#define MAX_TOOL_RESULT_LENGTH 104000
#define MAX_SESSION_ID_LENGTH 200
#define SESSION_EXPIRY_SECONDS 86400
struct messages_t {
struct json_object *array;
@ -17,7 +19,7 @@ struct messages_t {
};
static bool is_valid_session_id(const char *session_id) {
if (!session_id || !*session_id) return false;
if (strlen(session_id) > 200) return false;
if (strlen(session_id) > MAX_SESSION_ID_LENGTH) return false;
for (const char *p = session_id; *p; p++) {
if (!isalnum((unsigned char)*p) && *p != '-' && *p != '_' && *p != '.') {
return false;
@ -104,8 +106,9 @@ r_status_t messages_add(messages_handle msgs, const char *role, const char *cont
if (content) {
size_t len = strlen(content);
if (len > MAX_CONTENT_LENGTH) len = MAX_CONTENT_LENGTH;
int safe_len = (len <= (size_t)INT_MAX) ? (int)len : INT_MAX;
json_object_object_add(message, "content",
json_object_new_string_len(content, (int)len));
json_object_new_string_len(content, safe_len));
}
json_object_array_add(msgs->array, message);
if (strcmp(role, "system") != 0) {
@ -132,8 +135,9 @@ r_status_t messages_add_tool_result(messages_handle msgs, const char *tool_call_
json_object_object_add(message, "tool_call_id", json_object_new_string(tool_call_id));
size_t len = strlen(result);
if (len > MAX_TOOL_RESULT_LENGTH) len = MAX_TOOL_RESULT_LENGTH;
int safe_len = (len <= (size_t)INT_MAX) ? (int)len : INT_MAX;
json_object_object_add(message, "tool_result",
json_object_new_string_len(result, (int)len));
json_object_new_string_len(result, safe_len));
json_object_array_add(msgs->array, message);
messages_save(msgs);
return R_SUCCESS;
@ -240,3 +244,6 @@ int messages_count(messages_handle msgs) {
if (!msgs || !msgs->array) return 0;
return json_object_array_length(msgs->array);
}
db_handle messages_get_db(messages_handle msgs) {
return msgs ? msgs->db : NULL;
}

View File

@ -43,7 +43,9 @@ static char *dedent_code(const char *src) {
dst += line_len;
curr = next_line + 1;
} else {
strcpy(dst, curr);
size_t rest_len = strlen(curr);
memcpy(dst, curr, rest_len + 1);
dst += rest_len;
break;
}
}

View File

@ -14,7 +14,6 @@ struct r_config_t {
char *system_message;
char *current_prompt;
double temperature;
int max_tokens;
int max_spawn_depth;
int max_total_spawns;
bool use_tools;
@ -39,7 +38,7 @@ static const char *resolve_api_key(void) {
if (key && *key) return key;
key = getenv("OPENAI_API_KEY");
if (key && *key) return key;
return "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";
return NULL;
}
static bool is_valid_session_id(const char *session_id) {
if (!session_id || !*session_id) return false;
@ -70,17 +69,23 @@ r_config_handle r_config_get_instance(void) {
}
const char *model = getenv("R_MODEL");
instance->model = strdup(model && *model ? model : "gpt-4o-mini");
instance->api_key = strdup(resolve_api_key());
const char *resolved_key = resolve_api_key();
if (!resolved_key) {
fprintf(stderr, "Warning: No API key found. Set R_KEY, OPENROUTER_API_KEY, or OPENAI_API_KEY.\n");
}
instance->api_key = resolved_key ? strdup(resolved_key) : NULL;
instance->db_path = strdup("~/.r.db");
instance->temperature = 0.1;
const char *max_tokens_env = getenv("R_MAX_TOKENS");
instance->max_tokens = max_tokens_env ? atoi(max_tokens_env) : 4096;
const char *spawn_depth_env = getenv("R_MAX_SPAWN_DEPTH");
instance->max_spawn_depth = spawn_depth_env ? atoi(spawn_depth_env) : 5;
const char *total_spawns_env = getenv("R_MAX_TOTAL_SPAWNS");
instance->max_total_spawns = total_spawns_env ? atoi(total_spawns_env) : 20;
instance->use_tools = resolve_env_bool("R_USE_TOOLS", true);
instance->use_strict = resolve_env_bool("R_USE_STRICT", true);
if (instance->use_strict && instance->api_url &&
!strstr(instance->api_url, "api.openai.com")) {
instance->use_strict = false;
}
instance->verbose = false;
const char *session = getenv("R_SESSION");
if (session && is_valid_session_id(session)) {
@ -139,9 +144,6 @@ void r_config_set_verbose(r_config_handle cfg, bool verbose) {
double r_config_get_temperature(r_config_handle cfg) {
return cfg ? cfg->temperature : 0.1;
}
int r_config_get_max_tokens(r_config_handle cfg) {
return cfg ? cfg->max_tokens : 4096;
}
const char *r_config_get_session_id(r_config_handle cfg) {
return cfg ? cfg->session_id : NULL;
}

View File

@ -1,5 +1,6 @@
// retoor <retoor@molodetz.nl>
#include "tool.h"
#include "json_repair.h"
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
@ -8,6 +9,8 @@ typedef struct {
tool_t *tool;
struct json_object *args;
char *output;
const char *name;
const char *args_json;
} tool_thread_args_t;
static void *tool_thread_func(void *ptr) {
tool_thread_args_t *args = (tool_thread_args_t *)ptr;
@ -95,15 +98,55 @@ struct json_object *tool_registry_execute(tool_registry_t *registry,
continue;
}
const char *name = json_object_get_string(json_object_object_get(function_obj, "name"));
const char *args_json = json_object_get_string(json_object_object_get(function_obj, "arguments"));
// DEDUPLICATION LOGIC: Check if this exact call (name + args) appeared earlier in this batch
struct json_object *args_field = json_object_object_get(function_obj, "arguments");
struct json_object *args = NULL;
const char *args_json = NULL;
if (args_field) {
enum json_type atype = json_object_get_type(args_field);
if (atype == json_type_object) {
args = json_object_get(args_field);
args_json = json_object_to_json_string(args);
} else if (atype == json_type_string) {
const char *raw = json_object_get_string(args_field);
args_json = raw;
if (raw) {
args = json_tokener_parse(raw);
if (!args) {
char *repaired = json_repair_string(raw);
if (repaired) {
args = json_tokener_parse(repaired);
free(repaired);
}
}
}
}
}
if (!args) {
if (args_json) {
size_t args_len = strlen(args_json);
fprintf(stderr, "\033[1;31m[Registry] Failed to parse args for %s (len=%zu): %.200s...\033[0m\n",
name ? name : "unknown", args_len, args_json);
}
json_object_object_add(result_objs[i], "content",
json_object_new_string(
"Error: failed to parse tool arguments (invalid JSON). "
"This is likely caused by output truncation — the arguments were cut off. "
"For large file content, use linux_terminal_execute with: "
"cat << 'HEREDOC_EOF' > filename\n...content...\nHEREDOC_EOF"));
continue;
}
for (int j = 0; j < i; j++) {
struct json_object *prev_call = json_object_array_get_idx(tool_calls, j);
struct json_object *prev_func;
json_object_object_get_ex(prev_call, "function", &prev_func);
const char *prev_name = json_object_get_string(json_object_object_get(prev_func, "name"));
const char *prev_args = json_object_get_string(json_object_object_get(prev_func, "arguments"));
if (strcmp(name, prev_name) == 0 && strcmp(args_json, prev_args) == 0) {
const char *prev_args = t_args[j].args_json;
if (name && prev_name && args_json && prev_args &&
strcmp(name, prev_name) == 0 && strcmp(args_json, prev_args) == 0) {
is_duplicate[i] = true;
if (verbose) {
fprintf(stderr, " \033[1;33m[Registry] Redundant call to %s prevented.\033[0m\n", name);
@ -111,63 +154,87 @@ struct json_object *tool_registry_execute(tool_registry_t *registry,
break;
}
}
if (is_duplicate[i]) continue;
if (is_duplicate[i]) {
json_object_put(args);
continue;
}
tool_t *tool = tool_registry_find(registry, name);
if (!tool) {
json_object_object_add(result_objs[i], "content", json_object_new_string("Error: tool not found"));
json_object_put(args);
continue;
}
struct json_object *args = json_tokener_parse(args_json);
if (tool->vtable->print_action) {
tool->vtable->print_action(tool->name, args);
}
if (verbose && args) {
if (verbose) {
fprintf(stderr, " \033[2m[parallel] launching %s\033[0m\n", tool->name);
}
t_args[i].tool = tool;
t_args[i].args = args;
t_args[i].output = NULL;
pthread_create(&threads[i], NULL, tool_thread_func, &t_args[i]);
t_args[i].name = name;
t_args[i].args_json = args_json;
if (pthread_create(&threads[i], NULL, tool_thread_func, &t_args[i]) != 0) {
threads[i] = 0;
t_args[i].output = strdup("Error: failed to create thread for tool execution");
}
}
for (int i = 0; i < len; i++) {
if (is_duplicate[i]) {
// Find the original result to copy it
struct json_object *curr_func;
json_object_object_get_ex(json_object_array_get_idx(tool_calls, i), "function", &curr_func);
const char *name = json_object_get_string(json_object_object_get(curr_func, "name"));
const char *args_json = json_object_get_string(json_object_object_get(curr_func, "arguments"));
for (int j = 0; j < i; j++) {
struct json_object *prev_func;
json_object_object_get_ex(json_object_array_get_idx(tool_calls, j), "function", &prev_func);
if (strcmp(name, json_object_get_string(json_object_object_get(prev_func, "name"))) == 0 &&
strcmp(args_json, json_object_get_string(json_object_object_get(prev_func, "arguments"))) == 0) {
for (int i = 0; i < len; i++) {
if (!is_duplicate[i] && threads[i]) {
pthread_join(threads[i], NULL);
}
}
for (int i = 0; i < len; i++) {
if (is_duplicate[i]) {
struct json_object *curr_func;
json_object_object_get_ex(json_object_array_get_idx(tool_calls, i), "function", &curr_func);
const char *name = json_object_get_string(json_object_object_get(curr_func, "name"));
const char *dup_args = t_args[i].args_json;
for (int j = 0; j < i; j++) {
const char *prev_name = t_args[j].name;
const char *prev_args = t_args[j].args_json;
if (name && prev_name && dup_args && prev_args &&
strcmp(name, prev_name) == 0 && strcmp(dup_args, prev_args) == 0) {
struct json_object *orig_content;
if (json_object_object_get_ex(result_objs[j], "content", &orig_content)) {
json_object_object_add(result_objs[i], "content", json_object_get(orig_content));
} else {
// Original hasn't finished yet or failed
json_object_object_add(result_objs[i], "content", json_object_new_string("Result mirrored from previous parallel call."));
}
break;
}
}
} else {
if (threads[i]) {
pthread_join(threads[i], NULL);
}
char *output = t_args[i].output ? t_args[i].output : "";
json_object_object_add(result_objs[i], "content", json_object_new_string(output));
if (output && strncmp(output, "Error:", 6) == 0) {
fprintf(stderr, "\033[1;31m[Tool Error] %s\033[0m\n", output);
if (output[0] != '\0' && strncmp(output, "Error:", 6) == 0) {
const char *tool_name = t_args[i].name ? t_args[i].name : "unknown";
const char *received_args = t_args[i].args_json ? t_args[i].args_json : "{}";
size_t enriched_len = strlen(output) + strlen(tool_name) + strlen(received_args) + 64;
char *enriched = malloc(enriched_len);
if (enriched) {
snprintf(enriched, enriched_len,
"Error in tool '%s': %s. Arguments received: %s",
tool_name, output + 7, received_args);
json_object_object_add(result_objs[i], "content", json_object_new_string(enriched));
fprintf(stderr, "\033[1;31m[Tool Error] %s\033[0m\n", enriched);
free(enriched);
} else {
json_object_object_add(result_objs[i], "content", json_object_new_string(output));
fprintf(stderr, "\033[1;31m[Tool Error] %s\033[0m\n", output);
}
} else {
json_object_object_add(result_objs[i], "content", json_object_new_string(output));
}
free(t_args[i].output);
if (t_args[i].args) json_object_put(t_args[i].args);
t_args[i].output = NULL;
if (t_args[i].args) {
json_object_put(t_args[i].args);
t_args[i].args = NULL;
}
}
json_object_array_add(results, result_objs[i]);
}

View File

@ -343,7 +343,7 @@ static char *merge_search_results(char **results, int count) {
struct json_object *result_array;
if (json_object_object_get_ex(result_json, "results", &result_array)) {
for (int j = 0; j < json_object_array_length(result_array); j++) {
for (size_t j = 0; j < json_object_array_length(result_array); j++) {
struct json_object *item = json_object_array_get_idx(result_array, j);
json_object_array_add(results_array, json_object_get(item));
}

View File

@ -70,8 +70,8 @@ static void ChangetoDnsNameFormat(unsigned char* dns, unsigned char* host) {
size_t len = strlen((char*)host);
if (len > 250) len = 250;
memcpy(temp_host, host, len);
temp_host[len] = '\0';
strcat(temp_host, ".");
temp_host[len] = '.';
temp_host[len + 1] = '\0';
for(i = 0; i < (int)strlen(temp_host); i++) {
if(temp_host[i] == '.') {
@ -363,7 +363,7 @@ static char *dns_lookup_execute(tool_t *self, struct json_object *args) {
fflush(stderr);
char sub[512];
snprintf(sub, sizeof(sub), "%s.%s", common_subdomains[i], final_hostname);
int prev_len = json_object_array_length(results);
size_t prev_len = json_object_array_length(results);
perform_single_query(sub, T_A, dns_server, results);
if (json_object_array_length(results) > prev_len) {
fprintf(stderr, "[\033[32mFOUND\033[0m] \n");
@ -388,7 +388,7 @@ static char *dns_lookup_execute(tool_t *self, struct json_object *args) {
struct json_object *ip_entry = json_object_array_get_idx(ip_results, 0);
struct json_object *ip_val;
if (json_object_object_get_ex(ip_entry, "value", &ip_val)) {
int p_len = json_object_array_length(results);
size_t p_len = json_object_array_length(results);
perform_axfr(final_hostname, json_object_get_string(ip_val), results);
if (json_object_array_length(results) > p_len) {
fprintf(stderr, "[\033[32mSUCCESS\033[0m]\n");
@ -412,7 +412,7 @@ static char *dns_lookup_execute(tool_t *self, struct json_object *args) {
for (size_t i = 0; i < sizeof(types)/sizeof(types[0]); i++) {
fprintf(stderr, " -> Querying %s... ", type_names[i]);
fflush(stderr);
int p_len = json_object_array_length(results);
size_t p_len = json_object_array_length(results);
perform_single_query(final_hostname, types[i], dns_server, results);
if (json_object_array_length(results) > p_len) {
fprintf(stderr, "[\033[32mOK\033[0m]\n");

View File

@ -17,7 +17,7 @@ static void compute_hash(const char *input, char *output) {
unsigned char hash[SHA256_DIGEST_LENGTH];
SHA256((unsigned char*)input, strlen(input), hash);
for(int i = 0; i < SHA256_DIGEST_LENGTH; i++) {
sprintf(output + (i * 2), "%02x", hash[i]);
snprintf(output + (i * 2), 3, "%02x", hash[i]);
}
output[64] = '\0';
}

View File

@ -102,11 +102,11 @@ static char *expand_home_directory(const char *path) {
size_t home_len = strlen(home_dir);
size_t path_len = strlen(path);
char *expanded = malloc(home_len + path_len);
char *expanded = malloc(home_len + path_len + 1);
if (!expanded) return NULL;
strcpy(expanded, home_dir);
strcat(expanded, path + 1);
memcpy(expanded, home_dir, home_len);
memcpy(expanded + home_len, path + 1, path_len);
return expanded;
}

View File

@ -7,6 +7,7 @@
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/wait.h>
static struct json_object *file_line_replace_get_description(void);
static char *file_line_replace_execute(tool_t *self, struct json_object *args);
@ -82,7 +83,9 @@ static char *file_line_replace_execute(tool_t *self, struct json_object *args) {
char *saveptr;
char *line = strtok_r(copy, "\n", &saveptr);
while (line) {
lines = realloc(lines, sizeof(char *) * (count + 1));
char **expanded = realloc(lines, sizeof(char *) * (size_t)(count + 1));
if (!expanded) break;
lines = expanded;
lines[count++] = strdup(line);
line = strtok_r(NULL, "\n", &saveptr);
}
@ -113,11 +116,15 @@ static char *file_line_replace_execute(tool_t *self, struct json_object *args) {
size_t add_len = strlen(to_add);
if (current_len + add_len + 2 >= new_size) {
new_size *= 2;
new_content = realloc(new_content, new_size);
char *expanded_content = realloc(new_content, new_size);
if (!expanded_content) break;
new_content = expanded_content;
}
strcat(new_content, to_add);
strcat(new_content, "\n");
current_len += add_len + 1;
memcpy(new_content + current_len, to_add, add_len);
current_len += add_len;
new_content[current_len] = '\n';
current_len += 1;
new_content[current_len] = '\0';
}
}
@ -170,9 +177,16 @@ static char *file_apply_patch_execute(tool_t *self, struct json_object *args) {
return strdup("Error: could not write full patch to temp file");
}
char cmd[1024];
snprintf(cmd, sizeof(cmd), "patch %s %s", path, patch_file);
int res = system(cmd);
pid_t pid = fork();
int res = -1;
if (pid == 0) {
execl("/usr/bin/patch", "patch", path, patch_file, (char *)NULL);
_exit(127);
} else if (pid > 0) {
int status = 0;
waitpid(pid, &status, 0);
res = WIFEXITED(status) ? WEXITSTATUS(status) : -1;
}
unlink(patch_file);
if (res != 0) {

View File

@ -19,7 +19,7 @@ static void compute_url_hash(const char *url, char *output) {
unsigned char hash[SHA256_DIGEST_LENGTH];
SHA256((unsigned char*)url, strlen(url), hash);
for(int i = 0; i < SHA256_DIGEST_LENGTH; i++) {
sprintf(output + (i * 2), "%02x", hash[i]);
snprintf(output + (i * 2), 3, "%02x", hash[i]);
}
output[64] = '\0';
}

View File

@ -21,6 +21,24 @@ static char *snapshot_resolve_session_id(void) {
return messages_generate_session_id();
}
static int mkdirs_recursive(const char *path) {
if (!path || !*path) return -1;
char temp[4096];
size_t len = strlen(path);
if (len >= sizeof(temp)) return -1;
memcpy(temp, path, len + 1);
if (temp[len - 1] == '/') temp[len - 1] = '\0';
for (char *p = temp + 1; *p; p++) {
if (*p == '/') {
*p = '\0';
if (mkdir(temp, 0755) != 0 && errno != EEXIST) return -1;
*p = '/';
}
}
if (mkdir(temp, 0755) != 0 && errno != EEXIST) return -1;
return 0;
}
static int snapshot_mkdirs(const char *filepath) {
char *path_copy = strdup(filepath);
if (!path_copy) return -1;
@ -29,9 +47,7 @@ static int snapshot_mkdirs(const char *filepath) {
free(path_copy);
return 0;
}
char mkdir_cmd[4096];
snprintf(mkdir_cmd, sizeof(mkdir_cmd), "mkdir -p '%s'", dir);
int rc = system(mkdir_cmd);
int rc = mkdirs_recursive(dir);
free(path_copy);
return rc;
}

View File

@ -3,6 +3,7 @@
#include "tool.h"
#include "r_config.h"
#include "bash_executor.h"
#include <ctype.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
@ -58,22 +59,40 @@ static char *process_monitor_execute(tool_t *self, struct json_object *args) {
(void)self;
struct json_object *action_obj;
if (!json_object_object_get_ex(args, "action", &action_obj)) {
return strdup("Error: missing 'action' argument (list or kill)");
char *msg = strdup("Error: missing 'action' argument (list or kill)");
return msg;
}
const char *action = json_object_get_string(action_obj);
if (strcmp(action, "list") == 0) {
struct json_object *filter_obj;
if (json_object_object_get_ex(args, "filter", &filter_obj)) {
const char *raw_filter = json_object_get_string(filter_obj);
if (!raw_filter || !*raw_filter) {
return r_bash_execute("ps aux --sort=-%cpu | head -n 20", false, 300);
}
char sanitized[256];
size_t j = 0;
for (size_t i = 0; raw_filter[i] && j < sizeof(sanitized) - 1; i++) {
unsigned char ch = (unsigned char)raw_filter[i];
if (isalnum(ch) || ch == ' ' || ch == '.' || ch == '-' || ch == '_') {
sanitized[j++] = (char)ch;
}
}
sanitized[j] = '\0';
if (!sanitized[0]) {
return r_bash_execute("ps aux --sort=-%cpu | head -n 20", false, 300);
}
char cmd[512];
snprintf(cmd, sizeof(cmd), "ps aux | grep -i '%s' | grep -v grep", json_object_get_string(filter_obj));
snprintf(cmd, sizeof(cmd), "ps aux | grep -i '%s' | grep -v grep", sanitized);
return r_bash_execute(cmd, false, 300);
}
return r_bash_execute("ps aux --sort=-%cpu | head -n 20", false, 300);
} else if (strcmp(action, "kill") == 0) {
struct json_object *pid_obj;
if (!json_object_object_get_ex(args, "pid", &pid_obj)) {
return strdup("Error: missing 'pid' for kill action");
char *msg = strdup("Error: missing 'pid' for kill action");
return msg;
}
char cmd[256];
snprintf(cmd, sizeof(cmd), "kill -9 %d 2>&1", json_object_get_int(pid_obj));

View File

@ -17,11 +17,11 @@ char *path_expand_home(const char *path) {
size_t home_len = strlen(home_dir);
size_t path_len = strlen(path);
char *expanded = malloc(home_len + path_len);
char *expanded = malloc(home_len + path_len + 1);
if (!expanded) return NULL;
strcpy(expanded, home_dir);
strcat(expanded, path + 1);
memcpy(expanded, home_dir, home_len);
memcpy(expanded + home_len, path + 1, path_len);
return expanded;
}
@ -55,9 +55,9 @@ char *path_join(const char *base, const char *relative) {
char *joined = malloc(total_len + 1);
if (!joined) return NULL;
strcpy(joined, base);
if (needs_sep) strcat(joined, "/");
strcat(joined, relative);
memcpy(joined, base, base_len);
if (needs_sep) joined[base_len++] = '/';
memcpy(joined + base_len, relative, rel_len + 1);
return joined;
}
@ -76,7 +76,8 @@ char *path_dirname(const char *path) {
*last_sep = '\0';
}
} else {
strcpy(copy, ".");
copy[0] = '.';
copy[1] = '\0';
}
return copy;

View File

@ -1,22 +1,9 @@
// Written by retoor@molodetz.nl
// This header file contains utility functions for manipulating file system
// paths, focusing primarily on expanding paths starting with '~' to the home
// directory.
// This code uses standard libraries: stdio.h, stdlib.h, and string.h, and
// conditionally includes the posix libraries pwd.h and unistd.h when expanding
// the home directory manually.
// MIT License
//
// Permission is granted to use, copy, modify, merge, distribute, sublicense,
// and/or sell copies of the Software. The license includes conditions about
// providing a copy of the license and the limitation of liability and warranty.
// retoor <retoor@molodetz.nl>
#ifndef UTILS_H
#define UTILS_H
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
@ -50,20 +37,25 @@ void get_current_directory() {
char *expand_home_directory(const char *path) {
if (path[0] != '~') {
return strdup(path); // Return the original path if it doesn't start with ~
return strdup(path);
}
char *home_dir;
const char *home_dir = NULL;
#ifdef _WIN32
home_dir = getenv("USERPROFILE"); // Get home directory on Windows
home_dir = getenv("USERPROFILE");
#else
struct passwd *pw = getpwuid(getuid());
home_dir = pw->pw_dir; // Get home directory on Linux
if (pw) {
home_dir = pw->pw_dir;
}
#endif
if (home_dir == NULL) {
return NULL; // Error getting home directory
if (!home_dir) {
home_dir = getenv("HOME");
}
if (!home_dir) {
return NULL;
}
size_t home_len = strlen(home_dir);
@ -81,66 +73,71 @@ char *expand_home_directory(const char *path) {
}
unsigned long hash(const char *str) {
unsigned long hash = 5381; // Starting value
int c;
unsigned long result = 5381;
unsigned char c;
while ((c = *str++)) {
hash = ((hash << 5) + hash) + c; // hash * 33 + c
while ((c = (unsigned char)*str++)) {
result = ((result << 5) + result) + c;
}
return hash;
return result;
}
char *joinpath(const char *base_url, const char *path) {
static char result[4096];
size_t base_len = strlen(base_url);
size_t pos = 0;
if (base_len >= sizeof(result) - 2) {
base_len = sizeof(result) - 2;
}
memcpy(result, base_url, base_len);
pos = base_len;
if (pos > 0 && result[pos - 1] != '/') {
result[pos++] = '/';
}
size_t path_len = strlen(path);
if (path[0] == '/') {
path++;
path_len--;
}
size_t path_len = strlen(path);
if (pos + path_len >= sizeof(result)) {
path_len = sizeof(result) - pos - 1;
bool need_slash = (base_len > 0 && base_url[base_len - 1] != '/');
size_t total = base_len + (need_slash ? 1 : 0) + path_len + 1;
char *result = malloc(total);
if (!result) return NULL;
size_t pos = 0;
memcpy(result, base_url, base_len);
pos = base_len;
if (need_slash) {
result[pos++] = '/';
}
memcpy(result + pos, path, path_len);
result[pos + path_len] = '\0';
pos += path_len;
result[pos] = '\0';
return result;
}
char *read_file(const char *path) {
char *expanded_path = expand_home_directory(path);
if (expanded_path == NULL) {
if (!expanded_path) {
return NULL;
}
FILE *file = fopen(expanded_path, "r");
free(expanded_path);
if (file == NULL) {
expanded_path = NULL;
if (!file) {
return NULL;
}
fseek(file, 0, SEEK_END);
long size = ftell(file);
fseek(file, 0, SEEK_SET);
char *buffer = (char *)malloc(size + 1);
size_t read = fread(buffer, 1, size, file);
if (read == 0) {
free(buffer);
if (size < 0) {
fclose(file);
return NULL;
}
fseek(file, 0, SEEK_SET);
char *buffer = malloc((size_t)size + 1);
if (!buffer) {
fclose(file);
return NULL;
}
size_t bytes_read = fread(buffer, 1, (size_t)size, file);
fclose(file);
buffer[read] = '\0';
if (bytes_read == 0 && size > 0) {
free(buffer);
buffer = NULL;
return NULL;
}
buffer[bytes_read] = '\0';
return buffer;
}
#endif