Signed-off-by: NotAShelf <raf@notashelf.dev> Change-Id: I7af033c8d3f437e5574b050223cbc16a6a6a6964
493 lines
11 KiB
C
493 lines
11 KiB
C
#include <ctype.h>
|
|
#include <errno.h>
|
|
#include <libgen.h>
|
|
#include <pwd.h>
|
|
#include <signal.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <sys/stat.h>
|
|
#include <sys/time.h>
|
|
#include <sys/types.h>
|
|
#include <time.h>
|
|
#include <unistd.h>
|
|
|
|
#include "../include/chroma.h"
|
|
|
|
// Global state pointer for signal handling
|
|
static chroma_state_t *g_state = NULL;
|
|
static char *g_config_file = NULL;
|
|
|
|
// Signal handler implementation
|
|
static void signal_handler_impl(int sig) {
|
|
switch (sig) {
|
|
case SIGTERM:
|
|
case SIGINT:
|
|
chroma_log("INFO", "Received signal %d (%s), shutting down gracefully", sig,
|
|
(sig == SIGTERM) ? "SIGTERM" : "SIGINT");
|
|
chroma_should_quit = 1;
|
|
if (g_state) {
|
|
g_state->running = false;
|
|
}
|
|
break;
|
|
case SIGHUP:
|
|
chroma_log("INFO", "Received SIGHUP, reloading configuration");
|
|
if (g_state && g_config_file) {
|
|
chroma_reload_config(g_state, g_config_file);
|
|
}
|
|
break;
|
|
case SIGPIPE:
|
|
// Ignore SIGPIPE - we'll handle broken pipes in read/write calls
|
|
break;
|
|
default:
|
|
chroma_log("WARN", "Received unexpected signal: %d", sig);
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Set up signal handlers
|
|
void chroma_handle_signals(void) {
|
|
struct sigaction sa;
|
|
|
|
memset(&sa, 0, sizeof(sa));
|
|
sa.sa_handler = signal_handler_impl;
|
|
sigemptyset(&sa.sa_mask);
|
|
sa.sa_flags = SA_RESTART;
|
|
|
|
// Install signal handlers
|
|
if (sigaction(SIGTERM, &sa, NULL) == -1) {
|
|
chroma_log("ERROR", "Failed to install SIGTERM handler: %s",
|
|
strerror(errno));
|
|
}
|
|
|
|
if (sigaction(SIGINT, &sa, NULL) == -1) {
|
|
chroma_log("ERROR", "Failed to install SIGINT handler: %s",
|
|
strerror(errno));
|
|
}
|
|
|
|
if (sigaction(SIGHUP, &sa, NULL) == -1) {
|
|
chroma_log("ERROR", "Failed to install SIGHUP handler: %s",
|
|
strerror(errno));
|
|
}
|
|
|
|
// Ignore SIGPIPE
|
|
sa.sa_handler = SIG_IGN;
|
|
if (sigaction(SIGPIPE, &sa, NULL) == -1) {
|
|
chroma_log("ERROR", "Failed to ignore SIGPIPE: %s", strerror(errno));
|
|
}
|
|
|
|
chroma_log("DEBUG", "Signal handlers installed");
|
|
}
|
|
|
|
// Set global state for signal handling
|
|
void chroma_set_signal_state(chroma_state_t *state, const char *config_file) {
|
|
g_state = state;
|
|
|
|
free(g_config_file);
|
|
g_config_file = config_file ? strdup(config_file) : NULL;
|
|
}
|
|
|
|
// Clean up signal handling resources
|
|
void chroma_cleanup_signals(void) {
|
|
g_state = NULL;
|
|
free(g_config_file);
|
|
g_config_file = NULL;
|
|
}
|
|
|
|
// Expand environment variables in a string
|
|
static char *expand_env_vars(const char *str) {
|
|
if (!str || strchr(str, '$') == NULL) {
|
|
return strdup(str);
|
|
}
|
|
|
|
char *result = strdup("");
|
|
if (!result) {
|
|
return NULL;
|
|
}
|
|
|
|
const char *p = str;
|
|
while (*p) {
|
|
if (*p == '$') {
|
|
p++;
|
|
if (*p == '{') {
|
|
// ${VAR} format
|
|
p++;
|
|
const char *end = strchr(p, '}');
|
|
if (!end) {
|
|
// No closing brace, treat as literal
|
|
char *tmp = realloc(result, strlen(result) + 2);
|
|
if (!tmp) {
|
|
free(result);
|
|
return NULL;
|
|
}
|
|
result = tmp;
|
|
strcat(result, "${");
|
|
break;
|
|
}
|
|
|
|
size_t var_len = (size_t)(end - p);
|
|
char *var_name = malloc(var_len + 1);
|
|
if (!var_name) {
|
|
free(result);
|
|
return NULL;
|
|
}
|
|
strncpy(var_name, p, var_len);
|
|
var_name[var_len] = '\0';
|
|
|
|
const char *var_value = getenv(var_name);
|
|
if (var_value) {
|
|
char *tmp = realloc(result, strlen(result) + strlen(var_value) + 1);
|
|
if (!tmp) {
|
|
free(var_name);
|
|
free(result);
|
|
return NULL;
|
|
}
|
|
result = tmp;
|
|
strcat(result, var_value);
|
|
}
|
|
|
|
free(var_name);
|
|
p = end + 1;
|
|
} else {
|
|
// $VAR format
|
|
const char *start = p;
|
|
while (*p && (isalnum((unsigned char)*p) || *p == '_')) {
|
|
p++;
|
|
}
|
|
|
|
if (p == start) {
|
|
// Not a valid variable name, treat $ as literal
|
|
char *tmp = realloc(result, strlen(result) + 2);
|
|
if (!tmp) {
|
|
free(result);
|
|
return NULL;
|
|
}
|
|
result = tmp;
|
|
strcat(result, "$");
|
|
} else {
|
|
size_t var_len = (size_t)(p - start);
|
|
char *var_name = malloc(var_len + 1);
|
|
if (!var_name) {
|
|
free(result);
|
|
return NULL;
|
|
}
|
|
strncpy(var_name, start, var_len);
|
|
var_name[var_len] = '\0';
|
|
|
|
const char *var_value = getenv(var_name);
|
|
if (var_value) {
|
|
char *tmp = realloc(result, strlen(result) + strlen(var_value) + 1);
|
|
if (!tmp) {
|
|
free(var_name);
|
|
free(result);
|
|
return NULL;
|
|
}
|
|
result = tmp;
|
|
strcat(result, var_value);
|
|
}
|
|
|
|
free(var_name);
|
|
}
|
|
}
|
|
} else {
|
|
// Regular character
|
|
size_t len = strlen(result);
|
|
char *tmp = realloc(result, len + 2);
|
|
if (!tmp) {
|
|
free(result);
|
|
return NULL;
|
|
}
|
|
result = tmp;
|
|
result[len] = *p;
|
|
result[len + 1] = '\0';
|
|
p++;
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
// Expand tilde and environment variables in path
|
|
char *chroma_expand_path(const char *path) {
|
|
if (!path) {
|
|
return NULL;
|
|
}
|
|
|
|
// First expand environment variables
|
|
char *env_expanded = expand_env_vars(path);
|
|
if (!env_expanded) {
|
|
return NULL;
|
|
}
|
|
|
|
// Then expand tilde if present
|
|
if (env_expanded[0] != '~') {
|
|
return env_expanded;
|
|
}
|
|
|
|
const char *home;
|
|
if (env_expanded[1] == '/' || env_expanded[1] == '\0') {
|
|
// ~/... or just ~
|
|
home = getenv("HOME");
|
|
if (!home) {
|
|
struct passwd *pw = getpwuid(getuid());
|
|
if (pw) {
|
|
home = pw->pw_dir;
|
|
}
|
|
}
|
|
if (!home) {
|
|
chroma_log("ERROR", "Could not determine home directory");
|
|
free(env_expanded);
|
|
return strdup(path); // Return original path as fallback
|
|
}
|
|
|
|
size_t home_len = strlen(home);
|
|
size_t path_len = strlen(env_expanded);
|
|
char *expanded = malloc(home_len + path_len); // -1 for ~ +1 for \0
|
|
if (!expanded) {
|
|
chroma_log("ERROR", "Failed to allocate memory for path expansion");
|
|
free(env_expanded);
|
|
return strdup(path);
|
|
}
|
|
|
|
strcpy(expanded, home);
|
|
if (env_expanded[1] == '/') {
|
|
strcat(expanded, env_expanded + 1);
|
|
}
|
|
|
|
free(env_expanded);
|
|
return expanded;
|
|
} else {
|
|
// ~user/...
|
|
const char *slash = strchr(env_expanded, '/');
|
|
size_t user_len =
|
|
slash ? (size_t)(slash - env_expanded - 1) : strlen(env_expanded) - 1;
|
|
|
|
char *username = malloc(user_len + 1);
|
|
if (!username) {
|
|
free(env_expanded);
|
|
return strdup(path);
|
|
}
|
|
|
|
strncpy(username, env_expanded + 1, user_len);
|
|
username[user_len] = '\0';
|
|
|
|
struct passwd *pw = getpwnam(username);
|
|
|
|
if (!pw) {
|
|
chroma_log("ERROR", "User not found: %s", username);
|
|
free(username);
|
|
free(env_expanded);
|
|
return strdup(path);
|
|
}
|
|
|
|
free(username);
|
|
|
|
size_t home_len = strlen(pw->pw_dir);
|
|
size_t remaining_len = slash ? strlen(slash) : 0;
|
|
char *expanded = malloc(home_len + remaining_len + 1);
|
|
if (!expanded) {
|
|
free(env_expanded);
|
|
return strdup(path);
|
|
}
|
|
|
|
strcpy(expanded, pw->pw_dir);
|
|
if (slash) {
|
|
strcat(expanded, slash);
|
|
}
|
|
|
|
free(env_expanded);
|
|
return expanded;
|
|
}
|
|
}
|
|
|
|
// Create directory recursively
|
|
int chroma_mkdir_recursive(const char *path, mode_t mode) {
|
|
if (!path) {
|
|
return -1;
|
|
}
|
|
|
|
char *path_copy = strdup(path);
|
|
if (!path_copy) {
|
|
return -1;
|
|
}
|
|
|
|
char *p = path_copy;
|
|
|
|
// Skip leading slashes
|
|
while (*p == '/') {
|
|
p++;
|
|
}
|
|
|
|
while (*p) {
|
|
// Find next slash
|
|
while (*p && *p != '/') {
|
|
p++;
|
|
}
|
|
|
|
if (*p) {
|
|
*p = '\0';
|
|
|
|
// Create directory
|
|
if (mkdir(path_copy, mode) == -1 && errno != EEXIST) {
|
|
chroma_log("ERROR", "Failed to create directory %s: %s", path_copy,
|
|
strerror(errno));
|
|
free(path_copy);
|
|
return -1;
|
|
}
|
|
|
|
*p = '/';
|
|
p++;
|
|
}
|
|
}
|
|
|
|
// Create final directory
|
|
if (mkdir(path_copy, mode) == -1 && errno != EEXIST) {
|
|
chroma_log("ERROR", "Failed to create directory %s: %s", path_copy,
|
|
strerror(errno));
|
|
free(path_copy);
|
|
return -1;
|
|
}
|
|
|
|
free(path_copy);
|
|
return 0;
|
|
}
|
|
|
|
// Get configuration directory
|
|
char *chroma_get_config_dir(void) {
|
|
const char *xdg_config = getenv("XDG_CONFIG_HOME");
|
|
|
|
if (xdg_config) {
|
|
char *config_dir = malloc(strlen(xdg_config) + strlen("/chroma") + 1);
|
|
if (config_dir) {
|
|
sprintf(config_dir, "%s/chroma", xdg_config);
|
|
return config_dir;
|
|
}
|
|
}
|
|
|
|
const char *home = getenv("HOME");
|
|
if (home) {
|
|
char *config_dir = malloc(strlen(home) + strlen("/.config/chroma") + 1);
|
|
if (config_dir) {
|
|
sprintf(config_dir, "%s/.config/chroma", home);
|
|
return config_dir;
|
|
}
|
|
}
|
|
|
|
return strdup("/etc/chroma"); // Fallback
|
|
}
|
|
|
|
// Check if path exists
|
|
bool chroma_path_exists(const char *path) {
|
|
if (!path) {
|
|
return false;
|
|
}
|
|
|
|
struct stat st;
|
|
return (stat(path, &st) == 0);
|
|
}
|
|
|
|
// Check if path is a regular file
|
|
bool chroma_is_regular_file(const char *path) {
|
|
if (!path) {
|
|
return false;
|
|
}
|
|
|
|
struct stat st;
|
|
if (stat(path, &st) != 0) {
|
|
return false;
|
|
}
|
|
|
|
return S_ISREG(st.st_mode);
|
|
}
|
|
|
|
// Check if path is a directory
|
|
bool chroma_is_directory(const char *path) {
|
|
if (!path) {
|
|
return false;
|
|
}
|
|
|
|
struct stat st;
|
|
if (stat(path, &st) != 0) {
|
|
return false;
|
|
}
|
|
|
|
return S_ISDIR(st.st_mode);
|
|
}
|
|
|
|
// Get file size
|
|
long chroma_get_file_size(const char *path) {
|
|
if (!path) {
|
|
return -1;
|
|
}
|
|
|
|
struct stat st;
|
|
if (stat(path, &st) != 0) {
|
|
return -1;
|
|
}
|
|
|
|
return st.st_size;
|
|
}
|
|
|
|
// Get file extension
|
|
const char *chroma_get_file_extension(const char *path) {
|
|
if (!path) {
|
|
return NULL;
|
|
}
|
|
|
|
const char *last_dot = strrchr(path, '.');
|
|
if (!last_dot || last_dot == path) {
|
|
return NULL;
|
|
}
|
|
|
|
return last_dot + 1;
|
|
}
|
|
|
|
// Get current time in milliseconds
|
|
long long chroma_get_time_ms(void) {
|
|
struct timeval tv;
|
|
if (gettimeofday(&tv, NULL) != 0) {
|
|
return 0;
|
|
}
|
|
|
|
return (long long)tv.tv_sec * 1000 + tv.tv_usec / 1000;
|
|
}
|
|
|
|
// Sleep for specified milliseconds
|
|
void chroma_sleep_ms(long ms) {
|
|
if (ms <= 0) {
|
|
return;
|
|
}
|
|
|
|
struct timespec ts;
|
|
ts.tv_sec = ms / 1000;
|
|
ts.tv_nsec = (ms % 1000) * 1000000;
|
|
|
|
nanosleep(&ts, NULL);
|
|
}
|
|
|
|
// Format memory size in human readable format
|
|
void chroma_format_memory_size(size_t bytes, char *buffer, size_t buffer_size) {
|
|
if (!buffer || buffer_size == 0) {
|
|
return;
|
|
}
|
|
|
|
const char *units[] = {"B", "KB", "MB", "GB", "TB"};
|
|
const int num_units = sizeof(units) / sizeof(units[0]);
|
|
|
|
double size = (double)bytes;
|
|
int unit_index = 0;
|
|
|
|
while (size >= 1024.0 && unit_index < num_units - 1) {
|
|
size /= 1024.0;
|
|
unit_index++;
|
|
}
|
|
|
|
if (unit_index == 0) {
|
|
snprintf(buffer, buffer_size, "%.0f %s", size, units[unit_index]);
|
|
} else {
|
|
snprintf(buffer, buffer_size, "%.2f %s", size, units[unit_index]);
|
|
}
|
|
}
|
|
|
|
// Cleanup utility functions
|
|
void chroma_utils_cleanup(void) { chroma_cleanup_signals(); }
|