From 304f469a27a0962c93d966eabd94fc4c5fe1ad94 Mon Sep 17 00:00:00 2001 From: retoor Date: Sun, 28 Sep 2025 01:46:51 +0200 Subject: [PATCH] Update. --- main.c | 148 +++++++++++++++++++++++++++++++++++++++++++++++++++++++-- rproc | Bin 27008 -> 31472 bytes 2 files changed, 144 insertions(+), 4 deletions(-) diff --git a/main.c b/main.c index 9002e51..bf05284 100644 --- a/main.c +++ b/main.c @@ -25,6 +25,7 @@ #define APP_LOG_FILE "rproc.log" #define PID_LOCK_FILE "/tmp/rproc.pid" #define MAX_LOG_LINE_LEN 4096 +#define TAIL_LINE_COUNT 300 typedef struct Process { pid_t pid; @@ -128,6 +129,11 @@ static void terminate_process(pid_t pid, const char* script_name) { if (kill(pid, SIGTERM) == -1) { if (errno == ESRCH) { app_log("Process %d for '%s' did not exist. Removing from tracking.", pid, script_name); + char pid_filename[FILENAME_MAX]; + snprintf(pid_filename, sizeof(pid_filename), "%s.pid", script_name); + if (access(pid_filename, F_OK) == 0) { + unlink(pid_filename); + } remove_process_by_pid(pid); } return; @@ -138,6 +144,11 @@ static void terminate_process(pid_t pid, const char* script_name) { const pid_t result = waitpid(pid, &status, WNOHANG); if (result == pid) { app_log("Process %d for '%s' terminated gracefully.", pid, script_name); + char pid_filename[FILENAME_MAX]; + snprintf(pid_filename, sizeof(pid_filename), "%s.pid", script_name); + if (access(pid_filename, F_OK) == 0) { + unlink(pid_filename); + } remove_process_by_pid(pid); return; } @@ -153,6 +164,11 @@ static void terminate_process(pid_t pid, const char* script_name) { app_log("Process %d for '%s' disappeared before SIGKILL.", pid, script_name); } waitpid(pid, NULL, 0); + char pid_filename[FILENAME_MAX]; + snprintf(pid_filename, sizeof(pid_filename), "%s.pid", script_name); + if (access(pid_filename, F_OK) == 0) { + unlink(pid_filename); + } remove_process_by_pid(pid); } @@ -214,6 +230,16 @@ static void start_script(const char* script_name) { fprintf(stderr, "Failed to execute script '%s': %s\n", script_name, strerror(errno)); exit(127); } + + char pid_filename[FILENAME_MAX]; + snprintf(pid_filename, sizeof(pid_filename), "%s.pid", script_name); + FILE* pid_fp = fopen(pid_filename, "w"); + if (pid_fp) { + fprintf(pid_fp, "%d", pid); + fclose(pid_fp); + } else { + app_log("Warning: could not create pid file for %s", script_name); + } add_process(pid, script_name); app_log("Started '%s' with PID %d", script_name, pid); @@ -235,16 +261,28 @@ static void handle_sigchld(int sig) { while ((pid = waitpid(-1, &status, WNOHANG)) > 0) { Process* p = find_process_by_pid(pid); if (p) { + char pid_filename[FILENAME_MAX]; + snprintf(pid_filename, sizeof(pid_filename), "%s.pid", p->script_name); + if (WIFEXITED(status) && WEXITSTATUS(status) != 0) { app_log("Script '%s' (PID: %d) crashed with status %d.", p->script_name, pid, WEXITSTATUS(status)); + if (access(pid_filename, F_OK) == 0) { + unlink(pid_filename); + } p->crashed_at = time(NULL); p->pid = -1; } else if (WIFSIGNALED(status)) { app_log("Script '%s' (PID: %d) terminated by signal %d.", p->script_name, pid, WTERMSIG(status)); + if (access(pid_filename, F_OK) == 0) { + unlink(pid_filename); + } p->crashed_at = time(NULL); p->pid = -1; } else { app_log("Script '%s' (PID: %d) exited cleanly.", p->script_name, pid); + if (access(pid_filename, F_OK) == 0) { + unlink(pid_filename); + } remove_process_by_pid(pid); } } @@ -278,7 +316,43 @@ static void setup_signal_handlers(void) { } } +static void adopt_existing_processes(void) { + DIR* d = opendir("."); + if (!d) return; + + struct dirent* dir; + while ((dir = readdir(d)) != NULL) { + size_t len = strlen(dir->d_name); + if (len > 4 && strcmp(dir->d_name + len - 4, ".pid") == 0) { + FILE* fp = fopen(dir->d_name, "r"); + if (!fp) continue; + + pid_t pid = 0; + if (fscanf(fp, "%d", &pid) == 1 && pid > 0) { + fclose(fp); + if (kill(pid, 0) == 0 || errno == EPERM) { + char script_name[FILENAME_MAX]; + strncpy(script_name, dir->d_name, len - 4); + script_name[len - 4] = '\0'; + app_log("Adopting existing process '%s' with PID %d", script_name, pid); + add_process(pid, script_name); + } else { + app_log("Found stale pid file '%s'. Removing.", dir->d_name); + if (access(dir->d_name, F_OK) == 0) { + unlink(dir->d_name); + } + } + } else { + fclose(fp); + } + } + } + closedir(d); +} + static void run_daemon(void) { + adopt_existing_processes(); + DIR* d = opendir("."); if (d) { struct dirent* dir; @@ -363,9 +437,57 @@ static void run_daemon(void) { typedef struct LogFile { char* name; off_t offset; + int color_index; struct LogFile* next; } LogFile; +static off_t get_tail_offset(const char* filename, int line_count) { + FILE* fp = fopen(filename, "rb"); + if (!fp) return 0; + + char buf[4096]; + off_t file_size; + long pos; + int newlines = 0; + + if (fseek(fp, 0, SEEK_END) != 0) { + fclose(fp); + return 0; + } + file_size = ftell(fp); + if (file_size <= 0) { + fclose(fp); + return 0; + } + pos = file_size; + + while (pos > 0) { + long seek_to = pos - sizeof(buf); + if (seek_to < 0) seek_to = 0; + + if (fseek(fp, seek_to, SEEK_SET) != 0) break; + + size_t bytes_read = fread(buf, 1, pos - seek_to, fp); + if (bytes_read == 0) break; + + for (long i = bytes_read - 1; i >= 0; --i) { + if (buf[i] == '\n' && (seek_to + i) != (file_size - 1)) { + newlines++; + if (newlines >= line_count) { + pos = seek_to + i + 1; + fclose(fp); + return pos; + } + } + } + pos = seek_to; + } + + fclose(fp); + return 0; +} + + static void run_monitor(void) { char cwd[PATH_MAX] = {0}; char line_buffer[PATH_MAX]; @@ -386,13 +508,26 @@ static void run_monitor(void) { } printf("Another instance is running in %s. Attaching as a live monitor...\n", cwd); - printf("--- Tailing all *.log files. Press Ctrl+C to exit. ---\n"); + printf("--- Tailing last %d lines from all *.log files. Press Ctrl+C to exit. ---\n", TAIL_LINE_COUNT); LogFile* log_list = NULL; int fd = inotify_init(); if (fd < 0) { perror("inotify_init"); return; } inotify_add_watch(fd, ".", IN_CREATE | IN_MODIFY); + + static const char* colors[] = { + "\033[0;32m", /* Green */ + "\033[0;33m", /* Yellow */ + "\033[0;34m", /* Blue */ + "\033[0;36m", /* Cyan */ + "\033[0;35m", /* Magenta */ + "\033[0;31m", /* Red */ + }; + static const int num_colors = sizeof(colors) / sizeof(colors[0]); + static const char* color_reset = "\033[0m"; + static int next_color_index = 0; + while(1) { DIR* d = opendir("."); @@ -411,7 +546,9 @@ static void run_monitor(void) { if (!found) { LogFile* new_log = malloc(sizeof(LogFile)); new_log->name = strdup(dir->d_name); - new_log->offset = 0; + new_log->offset = get_tail_offset(dir->d_name, TAIL_LINE_COUNT); + new_log->color_index = next_color_index; + next_color_index = (next_color_index + 1) % num_colors; new_log->next = log_list; log_list = new_log; } @@ -426,7 +563,8 @@ static void run_monitor(void) { fseek(fp, l->offset, SEEK_SET); char line[MAX_LOG_LINE_LEN]; while (fgets(line, sizeof(line), fp)) { - printf("[%s] %s", l->name, line); + line[strcspn(line, "\r\n")] = 0; + printf("%s[%s] %s%s\n", colors[l->color_index], l->name, line, color_reset); } l->offset = ftell(fp); fclose(fp); @@ -488,7 +626,9 @@ int main(void) { cleanup_lock: flock(pid_fd, LOCK_UN); close(pid_fd); - unlink(PID_LOCK_FILE); + if (access(PID_LOCK_FILE, F_OK) == 0) { + unlink(PID_LOCK_FILE); + } } return 0; diff --git a/rproc b/rproc index 969a07e2e495e5015740b895c31ab10b5d22213a..2212ac7a63476f93c0865803eecd489fdf15f7c1 100755 GIT binary patch literal 31472 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 oZn^$awyZnJ$)w+AUj36}{K|5r9L4`f)Bj