/* * DWN - Desktop Window Manager * Utility functions implementation */ #include "util.h" #include "dwn.h" #include #include #include #include #include #include #include #include #include #include #include #include #include /* ========== Async Logging Configuration ========== */ #define LOG_RING_SIZE 256 /* Number of log entries in ring buffer */ #define LOG_MSG_MAX_LEN 512 /* Max length of a single log message */ #define LOG_MAX_FILE_SIZE (5 * 1024 * 1024) /* 5 MB max log file size */ #define LOG_FLUSH_INTERVAL_MS 100 /* Flush to disk every 100ms */ /* Log entry in ring buffer */ typedef struct { char message[LOG_MSG_MAX_LEN]; LogLevel level; _Atomic int ready; /* 0 = empty, 1 = ready to write */ } LogEntry; /* Static state for async logging */ static LogEntry log_ring[LOG_RING_SIZE]; static _Atomic size_t log_write_idx = 0; /* Next slot to write to */ static _Atomic size_t log_read_idx = 0; /* Next slot to read from */ static _Atomic int log_running = 0; /* Log thread running flag */ static pthread_t log_thread; static int log_fd = -1; /* File descriptor for log file */ static char *log_path = NULL; /* Path to log file */ static LogLevel min_level = LOG_INFO; static _Atomic size_t log_file_size = 0; /* Current log file size */ static const char *level_names[] = { "DEBUG", "INFO", "WARN", "ERROR" }; static const char *level_colors[] = { "\033[36m", /* Cyan for DEBUG */ "\033[32m", /* Green for INFO */ "\033[33m", /* Yellow for WARN */ "\033[31m" /* Red for ERROR */ }; /* Forward declarations for internal log functions */ static void log_rotate_if_needed(void); static void *log_writer_thread(void *arg); /* ========== Async Logging Implementation ========== */ /* Rotate log file if it exceeds max size */ static void log_rotate_if_needed(void) { if (log_fd < 0 || log_path == NULL) { return; } size_t current_size = atomic_load(&log_file_size); if (current_size < LOG_MAX_FILE_SIZE) { return; } /* Close current file */ close(log_fd); log_fd = -1; /* Create backup filename */ size_t path_len = strlen(log_path); char *backup_path = malloc(path_len + 8); if (backup_path != NULL) { snprintf(backup_path, path_len + 8, "%s.old", log_path); /* Remove old backup if exists, rename current to backup */ unlink(backup_path); rename(log_path, backup_path); free(backup_path); } /* Reopen fresh log file */ log_fd = open(log_path, O_WRONLY | O_CREAT | O_APPEND | O_CLOEXEC, 0644); atomic_store(&log_file_size, 0); } /* Background thread that writes log entries to file */ static void *log_writer_thread(void *arg) { (void)arg; while (atomic_load(&log_running)) { size_t read_idx = atomic_load(&log_read_idx); size_t write_idx = atomic_load(&log_write_idx); /* Process all available entries */ while (read_idx != write_idx) { size_t idx = read_idx % LOG_RING_SIZE; LogEntry *entry = &log_ring[idx]; /* Wait for entry to be ready (spin briefly) */ int attempts = 0; while (!atomic_load(&entry->ready) && attempts < 100) { attempts++; /* Brief yield */ struct timespec ts = {0, 1000}; /* 1 microsecond */ nanosleep(&ts, NULL); } if (atomic_load(&entry->ready)) { /* Write to file if open */ if (log_fd >= 0) { log_rotate_if_needed(); if (log_fd >= 0) { ssize_t written = write(log_fd, entry->message, strlen(entry->message)); if (written > 0) { atomic_fetch_add(&log_file_size, (size_t)written); } } } /* Mark entry as consumed */ atomic_store(&entry->ready, 0); } read_idx++; atomic_store(&log_read_idx, read_idx); } /* Sleep before next check */ struct timespec ts = {0, LOG_FLUSH_INTERVAL_MS * 1000000L}; nanosleep(&ts, NULL); } return NULL; } void log_init(const char *path) { /* Initialize ring buffer */ memset(log_ring, 0, sizeof(log_ring)); atomic_store(&log_write_idx, 0); atomic_store(&log_read_idx, 0); if (path != NULL) { char *expanded = expand_path(path); if (expanded != NULL) { /* Ensure directory exists */ char *dir = strdup(expanded); if (dir != NULL) { char *last_slash = strrchr(dir, '/'); if (last_slash != NULL) { *last_slash = '\0'; /* Create directory recursively using mkdir */ struct stat st; if (stat(dir, &st) != 0) { /* Directory doesn't exist, try to create it */ char cmd[512]; snprintf(cmd, sizeof(cmd), "mkdir -p '%s' 2>/dev/null", dir); int ret = system(cmd); (void)ret; /* Ignore result - file open will fail if dir creation fails */ } } free(dir); } /* Open log file with O_APPEND for atomic writes */ log_fd = open(expanded, O_WRONLY | O_CREAT | O_APPEND | O_CLOEXEC, 0644); if (log_fd < 0) { fprintf(stderr, "Warning: Could not open log file: %s\n", expanded); } else { /* Get current file size */ struct stat st; if (fstat(log_fd, &st) == 0) { atomic_store(&log_file_size, (size_t)st.st_size); } } log_path = expanded; /* Keep for rotation */ } } /* Start background writer thread */ atomic_store(&log_running, 1); if (pthread_create(&log_thread, NULL, log_writer_thread, NULL) != 0) { fprintf(stderr, "Warning: Could not create log writer thread\n"); atomic_store(&log_running, 0); } } void log_close(void) { /* Signal thread to stop */ if (!atomic_load(&log_running)) { goto cleanup; } atomic_store(&log_running, 0); /* Wait for thread to finish - give it time to flush */ pthread_join(log_thread, NULL); cleanup: /* Close file */ if (log_fd >= 0) { close(log_fd); log_fd = -1; } /* Free path */ if (log_path != NULL) { free(log_path); log_path = NULL; } } void log_set_level(LogLevel level) { min_level = level; } void log_flush(void) { /* Force flush all pending log entries synchronously */ if (!atomic_load(&log_running) || log_fd < 0) { return; } size_t read_idx = atomic_load(&log_read_idx); size_t write_idx = atomic_load(&log_write_idx); while (read_idx != write_idx) { size_t idx = read_idx % LOG_RING_SIZE; LogEntry *entry = &log_ring[idx]; if (atomic_load(&entry->ready)) { ssize_t written = write(log_fd, entry->message, strlen(entry->message)); if (written > 0) { atomic_fetch_add(&log_file_size, (size_t)written); } atomic_store(&entry->ready, 0); } read_idx++; atomic_store(&log_read_idx, read_idx); } /* Sync to disk */ fsync(log_fd); } void log_msg(LogLevel level, const char *fmt, ...) { if (level < min_level) { return; } /* Get timestamp */ time_t now = time(NULL); struct tm tm_info; localtime_r(&now, &tm_info); /* Thread-safe version */ char time_buf[32]; strftime(time_buf, sizeof(time_buf), "%Y-%m-%d %H:%M:%S", &tm_info); /* Format the log message */ char msg_buf[LOG_MSG_MAX_LEN - 64]; /* Leave room for prefix */ va_list args; va_start(args, fmt); vsnprintf(msg_buf, sizeof(msg_buf), fmt, args); va_end(args); /* Print to stderr (always, for immediate visibility) */ fprintf(stderr, "%s[%s] %s: %s%s\n", level_colors[level], time_buf, level_names[level], "\033[0m", msg_buf); /* Add to ring buffer for async file write (non-blocking) */ if (atomic_load(&log_running)) { size_t write_idx = atomic_fetch_add(&log_write_idx, 1); size_t idx = write_idx % LOG_RING_SIZE; LogEntry *entry = &log_ring[idx]; /* Check if slot is available (not overwriting unread entry) */ /* If buffer is full, we drop the message rather than block */ if (!atomic_load(&entry->ready)) { entry->level = level; snprintf(entry->message, sizeof(entry->message), "[%s] %s: %s\n", time_buf, level_names[level], msg_buf); atomic_store(&entry->ready, 1); } /* If entry->ready is true, buffer is full - message is dropped (non-blocking) */ } } /* ========== Memory allocation ========== */ void *dwn_malloc(size_t size) { void *ptr = malloc(size); if (ptr == NULL && size > 0) { LOG_ERROR("Memory allocation failed for %zu bytes", size); exit(EXIT_FAILURE); } return ptr; } void *dwn_calloc(size_t nmemb, size_t size) { void *ptr = calloc(nmemb, size); if (ptr == NULL && nmemb > 0 && size > 0) { LOG_ERROR("Memory allocation failed for %zu elements", nmemb); exit(EXIT_FAILURE); } return ptr; } void *dwn_realloc(void *ptr, size_t size) { void *new_ptr = realloc(ptr, size); if (new_ptr == NULL && size > 0) { LOG_ERROR("Memory reallocation failed for %zu bytes", size); exit(EXIT_FAILURE); } return new_ptr; } char *dwn_strdup(const char *s) { if (s == NULL) { return NULL; } char *dup = strdup(s); if (dup == NULL) { LOG_ERROR("String duplication failed"); exit(EXIT_FAILURE); } return dup; } void dwn_free(void *ptr) { free(ptr); } void secure_wipe(void *ptr, size_t size) { if (ptr == NULL || size == 0) { return; } #if defined(__GLIBC__) && (__GLIBC__ >= 2) && (__GLIBC_MINOR__ >= 25) /* Use explicit_bzero if available (glibc 2.25+) */ explicit_bzero(ptr, size); #else /* Fallback: Use volatile to prevent compiler optimization */ volatile unsigned char *p = (volatile unsigned char *)ptr; while (size--) { *p++ = 0; } #endif } /* ========== String utilities ========== */ char *str_trim(char *str) { if (str == NULL) { return NULL; } /* Trim leading whitespace */ while (isspace((unsigned char)*str)) { str++; } if (*str == '\0') { return str; } /* Trim trailing whitespace */ char *end = str + strlen(str) - 1; while (end > str && isspace((unsigned char)*end)) { end--; } end[1] = '\0'; return str; } bool str_starts_with(const char *str, const char *prefix) { if (str == NULL || prefix == NULL) { return false; } return strncmp(str, prefix, strlen(prefix)) == 0; } bool str_ends_with(const char *str, const char *suffix) { if (str == NULL || suffix == NULL) { return false; } size_t str_len = strlen(str); size_t suffix_len = strlen(suffix); if (suffix_len > str_len) { return false; } return strcmp(str + str_len - suffix_len, suffix) == 0; } int str_split(char *str, char delim, char **parts, int max_parts) { if (str == NULL || parts == NULL || max_parts <= 0) { return 0; } int count = 0; char *start = str; while (*str && count < max_parts) { if (*str == delim) { *str = '\0'; parts[count++] = start; start = str + 1; } str++; } if (count < max_parts && *start) { parts[count++] = start; } return count; } char *shell_escape(const char *str) { if (str == NULL) { return dwn_strdup(""); } /* Count single quotes to determine buffer size */ size_t quotes = 0; for (const char *p = str; *p; p++) { if (*p == '\'') quotes++; } /* Each single quote becomes '\'' (4 chars), plus 2 for surrounding quotes */ size_t len = strlen(str) + quotes * 3 + 3; char *escaped = dwn_malloc(len); char *dst = escaped; *dst++ = '\''; for (const char *src = str; *src; src++) { if (*src == '\'') { /* Close quote, add escaped quote, reopen quote: '\'' */ *dst++ = '\''; *dst++ = '\\'; *dst++ = '\''; *dst++ = '\''; } else { *dst++ = *src; } } *dst++ = '\''; *dst = '\0'; return escaped; } /* ========== File utilities ========== */ bool file_exists(const char *path) { if (path == NULL) { return false; } struct stat st; return stat(path, &st) == 0; } char *file_read_all(const char *path) { FILE *f = fopen(path, "r"); if (f == NULL) { return NULL; } fseek(f, 0, SEEK_END); long size = ftell(f); fseek(f, 0, SEEK_SET); char *content = dwn_malloc(size + 1); size_t read = fread(content, 1, size, f); content[read] = '\0'; fclose(f); return content; } bool file_write_all(const char *path, const char *content) { FILE *f = fopen(path, "w"); if (f == NULL) { return false; } size_t len = strlen(content); size_t written = fwrite(content, 1, len, f); fclose(f); return written == len; } char *expand_path(const char *path) { if (path == NULL) { return NULL; } if (path[0] == '~') { const char *home = getenv("HOME"); if (home == NULL) { struct passwd *pw = getpwuid(getuid()); if (pw != NULL) { home = pw->pw_dir; } } if (home != NULL) { size_t len = strlen(home) + strlen(path); char *expanded = dwn_malloc(len); snprintf(expanded, len, "%s%s", home, path + 1); return expanded; } } return dwn_strdup(path); } /* ========== Color utilities ========== */ unsigned long parse_color(const char *color_str) { if (color_str == NULL || dwn == NULL || dwn->display == NULL) { return 0; } XColor color, exact; if (XAllocNamedColor(dwn->display, dwn->colormap, color_str, &color, &exact)) { return color.pixel; } /* Try parsing as hex */ if (color_str[0] == '#') { unsigned int r, g, b; if (sscanf(color_str + 1, "%02x%02x%02x", &r, &g, &b) == 3) { color.red = r << 8; color.green = g << 8; color.blue = b << 8; color.flags = DoRed | DoGreen | DoBlue; if (XAllocColor(dwn->display, dwn->colormap, &color)) { return color.pixel; } } } LOG_WARN("Could not parse color: %s", color_str); return 0; } void color_to_rgb(unsigned long color, int *r, int *g, int *b) { XColor xc; xc.pixel = color; XQueryColor(dwn->display, dwn->colormap, &xc); *r = xc.red >> 8; *g = xc.green >> 8; *b = xc.blue >> 8; } /* ========== Time utilities ========== */ long get_time_ms(void) { struct timespec ts; clock_gettime(CLOCK_MONOTONIC, &ts); return (long)(ts.tv_sec * 1000 + ts.tv_nsec / 1000000); } void sleep_ms(int ms) { struct timespec ts; ts.tv_sec = ms / 1000; ts.tv_nsec = (ms % 1000) * 1000000L; nanosleep(&ts, NULL); } /* ========== Process utilities ========== */ int spawn(const char *cmd) { if (cmd == NULL) { return -1; } LOG_DEBUG("Spawning: %s", cmd); pid_t pid = fork(); if (pid == 0) { /* Child process */ setsid(); execl("/bin/sh", "sh", "-c", cmd, NULL); _exit(EXIT_FAILURE); } else if (pid < 0) { LOG_ERROR("Fork failed: %s", strerror(errno)); return -1; } /* Wait for child */ int status; waitpid(pid, &status, 0); return WEXITSTATUS(status); } int spawn_async(const char *cmd) { if (cmd == NULL) { return -1; } LOG_DEBUG("Spawning async: %s", cmd); pid_t pid = fork(); if (pid == 0) { /* Child process */ setsid(); /* Double fork to avoid zombies */ pid_t pid2 = fork(); if (pid2 == 0) { execl("/bin/sh", "sh", "-c", cmd, NULL); _exit(EXIT_FAILURE); } _exit(EXIT_SUCCESS); } else if (pid < 0) { LOG_ERROR("Fork failed: %s", strerror(errno)); return -1; } /* Wait for first child (which exits immediately) */ int status; waitpid(pid, &status, 0); return 0; } char *spawn_capture(const char *cmd) { if (cmd == NULL) { return NULL; } LOG_DEBUG("Spawning capture: %s", cmd); FILE *fp = popen(cmd, "r"); if (fp == NULL) { LOG_ERROR("popen failed: %s", strerror(errno)); return NULL; } /* Read output */ size_t buf_size = 1024; size_t len = 0; char *output = dwn_malloc(buf_size); output[0] = '\0'; char line[256]; while (fgets(line, sizeof(line), fp) != NULL) { size_t line_len = strlen(line); if (len + line_len + 1 > buf_size) { buf_size *= 2; output = dwn_realloc(output, buf_size); } /* Use memcpy with explicit bounds instead of strcpy */ memcpy(output + len, line, line_len + 1); len += line_len; } int status = pclose(fp); if (status != 0) { LOG_DEBUG("Command exited with status %d", status); } /* Trim trailing newline */ if (len > 0 && output[len - 1] == '\n') { output[len - 1] = '\0'; } return output; }