diff --git a/Makefile b/Makefile index 7a7b5f5..15c7130 100755 --- a/Makefile +++ b/Makefile @@ -1,7 +1,7 @@ # retoor 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 \ diff --git a/include/messages.h b/include/messages.h index 9b37447..a05868f 100755 --- a/include/messages.h +++ b/include/messages.h @@ -3,6 +3,7 @@ #ifndef R_MESSAGES_H #define R_MESSAGES_H +#include "db.h" #include "r_error.h" #include #include @@ -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 diff --git a/include/r_config.h b/include/r_config.h index 52af993..8c87e25 100755 --- a/include/r_config.h +++ b/include/r_config.h @@ -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); diff --git a/src/agent.c b/src/agent.c index d84c320..5e2266b 100755 --- a/src/agent.c +++ b/src/agent.c @@ -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"); diff --git a/src/bash_executor.c b/src/bash_executor.c index 1eb024c..8fd3695 100644 --- a/src/bash_executor.c +++ b/src/bash_executor.c @@ -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; } diff --git a/src/bash_repair.c b/src/bash_repair.c index 8ec5b06..f75a6cb 100644 --- a/src/bash_repair.c +++ b/src/bash_repair.c @@ -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; diff --git a/src/context_manager.c b/src/context_manager.c index f80def7..8a75ae6 100644 --- a/src/context_manager.c +++ b/src/context_manager.c @@ -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)); diff --git a/src/context_summarizer.c b/src/context_summarizer.c index bc8851f..51f27ea 100644 --- a/src/context_summarizer.c +++ b/src/context_summarizer.c @@ -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); diff --git a/src/db.c b/src/db.c index 3bc2155..e3fc4a5 100755 --- a/src/db.c +++ b/src/db.c @@ -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) { diff --git a/src/http_client.c b/src/http_client.c index 1554d3b..7030f9f 100755 --- a/src/http_client.c +++ b/src/http_client.c @@ -2,6 +2,7 @@ #include "http_client.h" #include #include +#include #include #include #include @@ -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); diff --git a/src/interfaces/config.c b/src/interfaces/config.c index 7dc289e..35a7c0c 100644 --- a/src/interfaces/config.c +++ b/src/interfaces/config.c @@ -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); diff --git a/src/json_repair.c b/src/json_repair.c index 6d3532e..5f53922 100644 --- a/src/json_repair.c +++ b/src/json_repair.c @@ -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 == '{') ? '}' : ']'; diff --git a/src/line.h b/src/line.h index c696bfa..f870583 100755 --- a/src/line.h +++ b/src/line.h @@ -1,5 +1,8 @@ // retoor +#ifndef LINE_H +#define LINE_H + #include "utils.h" #include #include @@ -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 diff --git a/src/main.c b/src/main.c index 6b8c8dd..2ec7e15 100755 --- a/src/main.c +++ b/src/main.c @@ -20,7 +20,7 @@ #include #include 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)) { diff --git a/src/messages.c b/src/messages.c index 2b93807..7d2a814 100755 --- a/src/messages.c +++ b/src/messages.c @@ -2,12 +2,14 @@ #include "messages.h" #include "db.h" #include +#include #include #include #include #include #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; +} diff --git a/src/python_repair.c b/src/python_repair.c index 1d4154a..20b3c5b 100644 --- a/src/python_repair.c +++ b/src/python_repair.c @@ -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; } } diff --git a/src/r_config.c b/src/r_config.c index aede422..294d1a9 100755 --- a/src/r_config.c +++ b/src/r_config.c @@ -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; } diff --git a/src/tool_registry.c b/src/tool_registry.c index bc248fd..a010c9b 100755 --- a/src/tool_registry.c +++ b/src/tool_registry.c @@ -1,5 +1,6 @@ // retoor #include "tool.h" +#include "json_repair.h" #include #include #include @@ -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]); } diff --git a/src/tools/tool_deepsearch.c b/src/tools/tool_deepsearch.c index 1c036a4..dacbdc1 100644 --- a/src/tools/tool_deepsearch.c +++ b/src/tools/tool_deepsearch.c @@ -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)); } diff --git a/src/tools/tool_dns.c b/src/tools/tool_dns.c index 1ea8f4f..a0fb3d5 100644 --- a/src/tools/tool_dns.c +++ b/src/tools/tool_dns.c @@ -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"); diff --git a/src/tools/tool_enterprise.c b/src/tools/tool_enterprise.c index 4b52b81..e6d8ffe 100644 --- a/src/tools/tool_enterprise.c +++ b/src/tools/tool_enterprise.c @@ -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'; } diff --git a/src/tools/tool_file.c b/src/tools/tool_file.c index e17a85c..3d752ab 100755 --- a/src/tools/tool_file.c +++ b/src/tools/tool_file.c @@ -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; } diff --git a/src/tools/tool_file_edit.c b/src/tools/tool_file_edit.c index ed6431c..8a7f97c 100644 --- a/src/tools/tool_file_edit.c +++ b/src/tools/tool_file_edit.c @@ -7,6 +7,7 @@ #include #include #include +#include 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) { diff --git a/src/tools/tool_research.c b/src/tools/tool_research.c index a4cde91..b3729f6 100644 --- a/src/tools/tool_research.c +++ b/src/tools/tool_research.c @@ -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'; } diff --git a/src/tools/tool_snapshot.c b/src/tools/tool_snapshot.c index fdb5ee0..688fd9f 100644 --- a/src/tools/tool_snapshot.c +++ b/src/tools/tool_snapshot.c @@ -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; } diff --git a/src/tools/tool_system.c b/src/tools/tool_system.c index 4599436..1efcf24 100644 --- a/src/tools/tool_system.c +++ b/src/tools/tool_system.c @@ -3,6 +3,7 @@ #include "tool.h" #include "r_config.h" #include "bash_executor.h" +#include #include #include #include @@ -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)); diff --git a/src/util/path.c b/src/util/path.c index e28a057..69f18d8 100644 --- a/src/util/path.c +++ b/src/util/path.c @@ -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; diff --git a/src/utils.h b/src/utils.h index d4fa2e4..dfa5074 100755 --- a/src/utils.h +++ b/src/utils.h @@ -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 #ifndef UTILS_H #define UTILS_H +#include #include #include #include @@ -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