diff --git a/.SRCINFO b/.SRCINFO index 2d75ce2..67ae45f 100644 --- a/.SRCINFO +++ b/.SRCINFO @@ -1,6 +1,6 @@ pkgbase = coolerdash pkgdesc = Extends CoolerControl with a polished LCD dashboard - pkgver = 1.82 + pkgver = 1.83 pkgrel = 1 url = https://github.com/damachine/coolerdash install = coolerdash.install diff --git a/VERSION b/VERSION index a92432a..74c280f 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -1.82 +1.83 diff --git a/src/config.c b/src/config.c index 1f6d75e..2b80f7c 100644 --- a/src/config.c +++ b/src/config.c @@ -176,7 +176,8 @@ static int parse_config_data(void *user, const char *section, const char *name, * @brief String configuration entry for lookup table. * @details Structure for string configuration keys. */ -typedef struct { +typedef struct +{ const char *key; size_t offset; size_t size; @@ -186,14 +187,17 @@ typedef struct { * @brief Generic helper for string-based configuration sections. * @details Processes string configuration keys using lookup table approach. */ -static int handle_string_config(Config *config, const char *name, const char *value, - const StringConfigEntry *entries, size_t entry_count) +static int handle_string_config(Config *config, const char *name, const char *value, + const StringConfigEntry *entries, size_t entry_count) { - if (!value || value[0] == '\0') return 1; - - for (size_t i = 0; i < entry_count; i++) { - if (strcmp(name, entries[i].key) == 0) { - char *dest = (char*)config + entries[i].offset; + if (!value || value[0] == '\0') + return 1; + + for (size_t i = 0; i < entry_count; i++) + { + if (strcmp(name, entries[i].key) == 0) + { + char *dest = (char *)config + entries[i].offset; cc_safe_strcpy(dest, entries[i].size, value); return 1; } @@ -209,9 +213,8 @@ static int get_daemon_config(Config *config, const char *name, const char *value { static const StringConfigEntry entries[] = { {"address", offsetof(Config, daemon_address), sizeof(config->daemon_address)}, - {"password", offsetof(Config, daemon_password), sizeof(config->daemon_password)} - }; - + {"password", offsetof(Config, daemon_password), sizeof(config->daemon_password)}}; + return handle_string_config(config, name, value, entries, sizeof(entries) / sizeof(entries[0])); } @@ -225,8 +228,7 @@ static int get_paths_config(Config *config, const char *name, const char *value) {"images", offsetof(Config, paths_images), sizeof(config->paths_images)}, {"image_coolerdash", offsetof(Config, paths_image_coolerdash), sizeof(config->paths_image_coolerdash)}, {"image_shutdown", offsetof(Config, paths_image_shutdown), sizeof(config->paths_image_shutdown)}, - {"pid", offsetof(Config, paths_pid), sizeof(config->paths_pid)} - }; + {"pid", offsetof(Config, paths_pid), sizeof(config->paths_pid)}}; return handle_string_config(config, name, value, entries, sizeof(entries) / sizeof(entries[0])); } @@ -250,30 +252,36 @@ typedef void (*DisplayConfigHandler)(Config *config, const char *value); * @brief Display configuration handlers. * @details Individual handlers for each display configuration key. */ -static void handle_display_width(Config *config, const char *value) { +static void handle_display_width(Config *config, const char *value) +{ int width = safe_atoi(value, 0); config->display_width = (width > 0) ? (uint16_t)width : 0; } -static void handle_display_height(Config *config, const char *value) { +static void handle_display_height(Config *config, const char *value) +{ int height = safe_atoi(value, 0); config->display_height = (height > 0) ? (uint16_t)height : 0; } -static void handle_refresh_interval_sec(Config *config, const char *value) { +static void handle_refresh_interval_sec(Config *config, const char *value) +{ config->display_refresh_interval_sec = safe_atoi(value, 0); } -static void handle_refresh_interval_nsec(Config *config, const char *value) { +static void handle_refresh_interval_nsec(Config *config, const char *value) +{ config->display_refresh_interval_nsec = safe_atoi(value, 0); } -static void handle_brightness(Config *config, const char *value) { +static void handle_brightness(Config *config, const char *value) +{ int brightness = safe_atoi(value, 0); config->lcd_brightness = (brightness >= 0 && brightness <= 100) ? (uint8_t)brightness : 0; } -static void handle_orientation(Config *config, const char *value) { +static void handle_orientation(Config *config, const char *value) +{ int orientation = safe_atoi(value, 0); config->lcd_orientation = is_valid_orientation(orientation) ? orientation : 0; } @@ -281,7 +289,8 @@ static void handle_orientation(Config *config, const char *value) { /** * @brief Display configuration entry for lookup table. */ -typedef struct { +typedef struct +{ const char *key; DisplayConfigHandler handler; } DisplayConfigEntry; @@ -298,11 +307,12 @@ static int get_display_config(Config *config, const char *name, const char *valu {"refresh_interval_sec", handle_refresh_interval_sec}, {"refresh_interval_nsec", handle_refresh_interval_nsec}, {"brightness", handle_brightness}, - {"orientation", handle_orientation} - }; + {"orientation", handle_orientation}}; - for (size_t i = 0; i < sizeof(entries) / sizeof(entries[0]); i++) { - if (strcmp(name, entries[i].key) == 0) { + for (size_t i = 0; i < sizeof(entries) / sizeof(entries[0]); i++) + { + if (strcmp(name, entries[i].key) == 0) + { entries[i].handler(config, value); return 1; } @@ -313,10 +323,16 @@ static int get_display_config(Config *config, const char *name, const char *valu /** * @brief Mixed-type configuration entry for lookup table. */ -typedef struct { +typedef struct +{ const char *key; size_t offset; - enum { TYPE_UINT16, TYPE_FLOAT, TYPE_STRING } type; + enum + { + TYPE_UINT16, + TYPE_FLOAT, + TYPE_STRING + } type; size_t string_size; } MixedConfigEntry; @@ -325,30 +341,37 @@ typedef struct { * @details Processes mixed-type configuration keys using lookup table approach. */ static int handle_mixed_config(Config *config, const char *name, const char *value, - const MixedConfigEntry *entries, size_t entry_count) -{ - for (size_t i = 0; i < entry_count; i++) { - if (strcmp(name, entries[i].key) == 0) { - void *field_ptr = (void*)((char*)config + entries[i].offset); - - switch (entries[i].type) { - case TYPE_UINT16: { - uint16_t *dest = (uint16_t*)field_ptr; - *dest = (uint16_t)safe_atoi(value, 0); - break; - } - case TYPE_FLOAT: { - float *dest = (float*)field_ptr; - *dest = safe_atof(value, 12.0f); - break; - } - case TYPE_STRING: { - if (value && value[0] != '\0') { - char *dest = (char*)field_ptr; - cc_safe_strcpy(dest, entries[i].string_size, value); - } - break; + const MixedConfigEntry *entries, size_t entry_count) +{ + for (size_t i = 0; i < entry_count; i++) + { + if (strcmp(name, entries[i].key) == 0) + { + void *field_ptr = (void *)((char *)config + entries[i].offset); + + switch (entries[i].type) + { + case TYPE_UINT16: + { + uint16_t *dest = (uint16_t *)field_ptr; + *dest = (uint16_t)safe_atoi(value, 0); + break; + } + case TYPE_FLOAT: + { + float *dest = (float *)field_ptr; + *dest = safe_atof(value, 12.0f); + break; + } + case TYPE_STRING: + { + if (value && value[0] != '\0') + { + char *dest = (char *)field_ptr; + cc_safe_strcpy(dest, entries[i].string_size, value); } + break; + } } return 1; } @@ -369,8 +392,7 @@ static int get_layout_config(Config *config, const char *name, const char *value {"bar_width", offsetof(Config, layout_bar_width), TYPE_UINT16, 0}, {"bar_height", offsetof(Config, layout_bar_height), TYPE_UINT16, 0}, {"bar_gap", offsetof(Config, layout_bar_gap), TYPE_UINT16, 0}, - {"bar_border_width", offsetof(Config, layout_bar_border_width), TYPE_FLOAT, 0} - }; + {"bar_border_width", offsetof(Config, layout_bar_border_width), TYPE_FLOAT, 0}}; return handle_mixed_config(config, name, value, entries, sizeof(entries) / sizeof(entries[0])); } @@ -384,8 +406,7 @@ static int get_font_config(Config *config, const char *name, const char *value) static const MixedConfigEntry entries[] = { {"font_face", offsetof(Config, font_face), TYPE_STRING, CONFIG_MAX_FONT_NAME_LEN}, {"font_size_temp", offsetof(Config, font_size_temp), TYPE_FLOAT, 0}, - {"font_size_labels", offsetof(Config, font_size_labels), TYPE_FLOAT, 0} - }; + {"font_size_labels", offsetof(Config, font_size_labels), TYPE_FLOAT, 0}}; return handle_mixed_config(config, name, value, entries, sizeof(entries) / sizeof(entries[0])); } @@ -394,7 +415,8 @@ static int get_font_config(Config *config, const char *name, const char *value) * @brief Temperature configuration entry for lookup table. * @details Structure for temperature threshold configuration keys. */ -typedef struct { +typedef struct +{ const char *key; size_t offset; } TemperatureConfigEntry; @@ -408,13 +430,14 @@ static int get_temperature_config(Config *config, const char *name, const char * static const TemperatureConfigEntry entries[] = { {"temp_threshold_1", offsetof(Config, temp_threshold_1)}, {"temp_threshold_2", offsetof(Config, temp_threshold_2)}, - {"temp_threshold_3", offsetof(Config, temp_threshold_3)} - }; + {"temp_threshold_3", offsetof(Config, temp_threshold_3)}}; - for (size_t i = 0; i < sizeof(entries) / sizeof(entries[0]); i++) { - if (strcmp(name, entries[i].key) == 0) { - void *field_ptr = (void*)((char*)config + entries[i].offset); - float *dest = (float*)field_ptr; + for (size_t i = 0; i < sizeof(entries) / sizeof(entries[0]); i++) + { + if (strcmp(name, entries[i].key) == 0) + { + void *field_ptr = (void *)((char *)config + entries[i].offset); + float *dest = (float *)field_ptr; *dest = safe_atof(value, 50.0f + i * 15.0f); // 50, 65, 80 return 1; } @@ -426,7 +449,8 @@ static int get_temperature_config(Config *config, const char *name, const char * * @brief Color section mapping entry for lookup table. * @details Structure for color section mapping. */ -typedef struct { +typedef struct +{ const char *section_name; size_t color_offset; } ColorSectionEntry; @@ -445,12 +469,13 @@ static Color *get_color_pointer_from_section(Config *config, const char *section {"temp_threshold_1_bar", offsetof(Config, temp_threshold_1_bar)}, {"temp_threshold_2_bar", offsetof(Config, temp_threshold_2_bar)}, {"temp_threshold_3_bar", offsetof(Config, temp_threshold_3_bar)}, - {"temp_threshold_4_bar", offsetof(Config, temp_threshold_4_bar)} - }; + {"temp_threshold_4_bar", offsetof(Config, temp_threshold_4_bar)}}; - for (size_t i = 0; i < sizeof(entries) / sizeof(entries[0]); i++) { - if (strcmp(section, entries[i].section_name) == 0) { - return (Color*)((char*)config + entries[i].color_offset); + for (size_t i = 0; i < sizeof(entries) / sizeof(entries[0]); i++) + { + if (strcmp(section, entries[i].section_name) == 0) + { + return (Color *)((char *)config + entries[i].color_offset); } } return NULL; @@ -462,12 +487,23 @@ static Color *get_color_pointer_from_section(Config *config, const char *section */ static void set_color_component(Color *color, const char *name, const char *value) { - if (!color || !name || !value) return; + if (!color || !name || !value) + return; - switch (name[0]) { - case 'r': if (strcmp(name, "r") == 0) parse_color_component(value, &color->r); break; - case 'g': if (strcmp(name, "g") == 0) parse_color_component(value, &color->g); break; - case 'b': if (strcmp(name, "b") == 0) parse_color_component(value, &color->b); break; + switch (name[0]) + { + case 'r': + if (strcmp(name, "r") == 0) + parse_color_component(value, &color->r); + break; + case 'g': + if (strcmp(name, "g") == 0) + parse_color_component(value, &color->g); + break; + case 'b': + if (strcmp(name, "b") == 0) + parse_color_component(value, &color->b); + break; } } @@ -478,7 +514,8 @@ static void set_color_component(Color *color, const char *name, const char *valu static int get_color_config(Config *config, const char *section, const char *name, const char *value) { Color *color = get_color_pointer_from_section(config, section); - if (color) { + if (color) + { set_color_component(color, name, value); } return 1; @@ -550,8 +587,8 @@ static void set_display_defaults(Config *config) config->display_refresh_interval_nsec = 500000000; if (config->lcd_brightness == 0) config->lcd_brightness = 80; - if (config->lcd_orientation == 0) - config->lcd_orientation = 0; + if (!is_valid_orientation(config->lcd_orientation)) + config->lcd_orientation = 0; // Fallback to 0 if invalid } /** diff --git a/src/display.c b/src/display.c index 87e971c..a7b880a 100644 --- a/src/display.c +++ b/src/display.c @@ -35,6 +35,17 @@ #include "coolercontrol.h" #include "monitor.h" +// Define mathematical constants if not defined +#ifndef M_PI +#define M_PI 3.14159265358979323846 +#endif +#ifndef DISPLAY_M_PI +#define DISPLAY_M_PI M_PI +#endif +#ifndef DISPLAY_M_PI_2 +#define DISPLAY_M_PI_2 (M_PI / 2.0) +#endif + /** * @brief Convert color component to cairo format. * @details Converts 8-bit color component (0-255) to cairo's double format (0.0-1.0) for rendering operations. @@ -159,7 +170,7 @@ static void draw_temperature_displays(cairo_t *cr, const monitor_sensor_data_t * return; // temp_cpu display (CPU temperature) with validation - draw_temp(cr, config, data->temp_cpu, - DISPLAY_TEMP_DISPLAY_Y_OFFSET); + draw_temp(cr, config, data->temp_cpu, -DISPLAY_TEMP_DISPLAY_Y_OFFSET); // temp_gpu display (GPU temperature) with validation draw_temp(cr, config, data->temp_gpu, config->layout_box_height + DISPLAY_TEMP_DISPLAY_Y_OFFSET); diff --git a/src/main.c b/src/main.c index 641dbb5..94305f6 100644 --- a/src/main.c +++ b/src/main.c @@ -49,6 +49,11 @@ #define SHUTDOWN_RETRY_COUNT 2 #define VERSION_BUFFER_SIZE 32 +// Define O_NOFOLLOW if not defined (for portability) +#ifndef O_NOFOLLOW +#define O_NOFOLLOW 0 +#endif + /** * @brief Global variables for daemon management. * @details Used for controlling the main daemon loop and shutdown image logic. @@ -71,23 +76,27 @@ const Config *g_config_ptr = NULL; * @brief Read version string from VERSION file with enhanced security. * @details Safely reads version from VERSION file with buffer overflow protection and proper validation. Returns fallback version on error. */ -static const char* read_version_from_file(void) { +static const char *read_version_from_file(void) +{ static char version_buffer[VERSION_BUFFER_SIZE] = {0}; static int version_loaded = 0; - + // Return cached version if already loaded - if (version_loaded) { + if (version_loaded) + { return version_buffer[0] ? version_buffer : DEFAULT_VERSION; } - + // Try to read from VERSION file FILE *fp = fopen("VERSION", "r"); - if (!fp) { + if (!fp) + { // Try alternative path for installed version fp = fopen("/opt/coolerdash/VERSION", "r"); } - - if (!fp) { + + if (!fp) + { log_message(LOG_WARNING, "Could not open VERSION file, using default version"); cc_safe_strcpy(version_buffer, sizeof(version_buffer), DEFAULT_VERSION); version_loaded = 1; @@ -95,19 +104,24 @@ static const char* read_version_from_file(void) { } // Secure reading with fixed buffer size - if (!fgets(version_buffer, sizeof(version_buffer), fp)) { + if (!fgets(version_buffer, sizeof(version_buffer), fp)) + { log_message(LOG_WARNING, "Could not read VERSION file, using default version"); cc_safe_strcpy(version_buffer, sizeof(version_buffer), DEFAULT_VERSION); - } else { + } + else + { // Remove trailing whitespace and newlines version_buffer[strcspn(version_buffer, "\n\r \t")] = '\0'; - // Validate version string (manual bounded length calculation to avoid strnlen portability issues) - size_t ver_len = 0; - while (ver_len < 21 && version_buffer[ver_len] != '\0') { - ver_len++; - } - if (version_buffer[0] == '\0' || ver_len > 20) { + // Validate version string (manual bounded length calculation to avoid strnlen portability issues) + size_t ver_len = 0; + while (ver_len < 21 && version_buffer[ver_len] != '\0') + { + ver_len++; + } + if (version_buffer[0] == '\0' || ver_len > 20) + { log_message(LOG_WARNING, "Invalid version format, using default version"); cc_safe_strcpy(version_buffer, sizeof(version_buffer), DEFAULT_VERSION); } @@ -122,17 +136,21 @@ static const char* read_version_from_file(void) { * @brief Safely parse PID from string with validation. * @details Uses strtol for secure parsing with proper error checking. */ -static pid_t safe_parse_pid(const char *pid_str) { - if (!pid_str || !pid_str[0]) return -1; - +static pid_t safe_parse_pid(const char *pid_str) +{ + if (!pid_str || !pid_str[0]) + return -1; + char *endptr; errno = 0; long pid = strtol(pid_str, &endptr, 10); - + // Validation checks - if (errno != 0 || endptr == pid_str || *endptr != '\0') return -1; - if (pid <= 0 || pid > INT_MAX) return -1; - + if (errno != 0 || endptr == pid_str || *endptr != '\0') + return -1; + if (pid <= 0 || pid > INT_MAX) + return -1; + return (pid_t)pid; } @@ -140,23 +158,27 @@ static pid_t safe_parse_pid(const char *pid_str) { * @brief Detect if we were started by systemd service (not user session). * @details Distinguishes between systemd service and user session/terminal. */ -static int is_started_by_systemd(void) { +static int is_started_by_systemd(void) +{ // Check if running as systemd service by looking at process hierarchy // Real systemd services have parent PID 1 and specific unit name - if (getppid() != 1) { + if (getppid() != 1) + { return 0; // Not direct child of init/systemd } - + // Check if we have a proper systemd service unit name const char *invocation_id = getenv("INVOCATION_ID"); - if (invocation_id && invocation_id[0]) { + if (invocation_id && invocation_id[0]) + { // Check if we're running as a proper service (not user session) // Services typically have no controlling terminal - if (!isatty(STDIN_FILENO) && !isatty(STDOUT_FILENO) && !isatty(STDERR_FILENO)) { + if (!isatty(STDIN_FILENO) && !isatty(STDOUT_FILENO) && !isatty(STDERR_FILENO)) + { return 1; // Likely a real systemd service } } - + return 0; // User session or manual start } @@ -164,45 +186,53 @@ static int is_started_by_systemd(void) { * @brief Check if another instance of CoolerDash is running with secure PID validation. * @details Uses secure file reading and PID validation. */ -static int check_existing_instance_and_handle(const char *pid_file, int is_service_start) { +static int check_existing_instance_and_handle(const char *pid_file, int is_service_start) +{ (void)is_service_start; // Mark as intentionally unused - - if (!pid_file || !pid_file[0]) { + + if (!pid_file || !pid_file[0]) + { log_message(LOG_ERROR, "Invalid PID file path provided"); return -1; } - + FILE *fp = fopen(pid_file, "r"); - if (!fp) return 0; // No PID file exists, no running instance - + if (!fp) + return 0; // No PID file exists, no running instance + // Secure reading with fixed buffer size char pid_buffer[PID_READ_BUFFER_SIZE] = {0}; - if (!fgets(pid_buffer, sizeof(pid_buffer), fp)) { + if (!fgets(pid_buffer, sizeof(pid_buffer), fp)) + { fclose(fp); unlink(pid_file); // Remove corrupted PID file return 0; } fclose(fp); - + // Remove trailing newline and validate pid_buffer[strcspn(pid_buffer, "\n\r")] = '\0'; pid_t existing_pid = safe_parse_pid(pid_buffer); - - if (existing_pid <= 0) { + + if (existing_pid <= 0) + { log_message(LOG_WARNING, "Invalid PID in file, removing stale PID file"); unlink(pid_file); return 0; } - + // Check if process exists using kill(pid, 0) - if (kill(existing_pid, 0) == 0) { + if (kill(existing_pid, 0) == 0) + { log_message(LOG_ERROR, "Another instance is already running (PID %d)", existing_pid); return -1; - } else if (errno == EPERM) { + } + else if (errno == EPERM) + { log_message(LOG_ERROR, "Another instance may be running (PID %d) - insufficient permissions to verify", existing_pid); return -1; } - + // Process doesn't exist, remove stale PID file log_message(LOG_INFO, "Removing stale PID file (process %d no longer exists)", existing_pid); unlink(pid_file); @@ -213,74 +243,95 @@ static int check_existing_instance_and_handle(const char *pid_file, int is_servi * @brief Write current PID to file with enhanced security and error checking. * @details Creates PID file with proper permissions and atomic write operation. */ -static int write_pid_file(const char *pid_file) { - if (!pid_file || !pid_file[0]) { +static int write_pid_file(const char *pid_file) +{ + if (!pid_file || !pid_file[0]) + { log_message(LOG_ERROR, "Invalid PID file path provided"); return -1; } - + // Create directory if it doesn't exist char *dir_path = strdup(pid_file); - if (!dir_path) { + if (!dir_path) + { log_message(LOG_ERROR, "Memory allocation failed for directory path"); return -1; } - + char *last_slash = strrchr(dir_path, '/'); - if (last_slash) { + if (last_slash) + { *last_slash = '\0'; - if (mkdir(dir_path, 0755) == -1 && errno != EEXIST) { + if (mkdir(dir_path, 0755) == -1 && errno != EEXIST) + { log_message(LOG_WARNING, "Could not create PID directory '%s': %s", dir_path, strerror(errno)); } } free(dir_path); - + // Atomic write: write to temporary file first, then rename char temp_file[PATH_MAX]; int ret = snprintf(temp_file, sizeof(temp_file), "%s.tmp", pid_file); - if (ret >= (int)sizeof(temp_file) || ret < 0) { + if (ret >= (int)sizeof(temp_file) || ret < 0) + { log_message(LOG_ERROR, "PID file path too long"); return -1; } - + // Open with specific permissions to avoid race condition - int fd = open(temp_file, O_WRONLY | O_CREAT | O_EXCL, 0644); - if (fd == -1) { + int fd = open(temp_file, O_WRONLY | O_CREAT | O_EXCL | O_NOFOLLOW, 0644); + struct stat st; + if (fd == -1) + { log_message(LOG_ERROR, "Could not create temporary PID file '%s': %s", temp_file, strerror(errno)); return -1; } - + if (fstat(fd, &st) == 0) + { + if (!S_ISREG(st.st_mode)) + { + close(fd); + log_message(LOG_ERROR, "PID file is not a regular file: %s", temp_file); + return -1; + } + } + // Convert to FILE* for easier writing FILE *f = fdopen(fd, "w"); - if (!f) { + if (!f) + { log_message(LOG_ERROR, "Could not convert file descriptor to FILE*: %s", strerror(errno)); close(fd); unlink(temp_file); return -1; } - + // Write PID with validation pid_t current_pid = getpid(); - if (fprintf(f, "%d\n", current_pid) < 0) { + if (fprintf(f, "%d\n", current_pid) < 0) + { log_message(LOG_ERROR, "Could not write PID to temporary file '%s': %s", temp_file, strerror(errno)); fclose(f); // This also closes the fd unlink(temp_file); return -1; } - - if (fclose(f) != 0) { + + if (fclose(f) != 0) + { log_message(LOG_ERROR, "Could not close temporary PID file '%s': %s", temp_file, strerror(errno)); unlink(temp_file); return -1; } - + // Atomic rename - file already has correct permissions from open() - if (rename(temp_file, pid_file) != 0) { + if (rename(temp_file, pid_file) != 0) + { log_message(LOG_ERROR, "Could not rename temporary PID file to '%s': %s", pid_file, strerror(errno)); unlink(temp_file); return -1; } - + log_message(LOG_STATUS, "PID file: %s (PID: %d)", pid_file, current_pid); return 0; } @@ -289,12 +340,17 @@ static int write_pid_file(const char *pid_file) { * @brief Remove PID file with enhanced error handling. * @details Securely removes the PID file with proper error reporting. */ -static void remove_pid_file(const char *pid_file) { - if (!pid_file || !pid_file[0]) return; - - if (unlink(pid_file) == 0) { +static void remove_pid_file(const char *pid_file) +{ + if (!pid_file || !pid_file[0]) + return; + + if (unlink(pid_file) == 0) + { log_message(LOG_INFO, "PID file removed"); - } else if (errno != ENOENT) { + } + else if (errno != ENOENT) + { log_message(LOG_WARNING, "Could not remove PID file '%s': %s", pid_file, strerror(errno)); } } @@ -303,12 +359,17 @@ static void remove_pid_file(const char *pid_file) { * @brief Remove generated image file during cleanup. * @details Securely removes the generated PNG image file with proper error reporting. */ -static void remove_image_file(const char *image_file) { - if (!image_file || !image_file[0]) return; - - if (unlink(image_file) == 0) { +static void remove_image_file(const char *image_file) +{ + if (!image_file || !image_file[0]) + return; + + if (unlink(image_file) == 0) + { log_message(LOG_INFO, "Image file removed"); - } else if (errno != ENOENT) { + } + else if (errno != ENOENT) + { log_message(LOG_WARNING, "Could not remove image file '%s': %s", image_file, strerror(errno)); } } @@ -317,13 +378,15 @@ static void remove_image_file(const char *image_file) { * @brief Enhanced help display with improved formatting and security information. * @details Prints comprehensive usage information and security recommendations. */ -static void show_help(const char *program_name, const Config *config) { +static void show_help(const char *program_name, const Config *config) +{ (void)config; // Mark parameter as intentionally unused - - if (!program_name) program_name = "coolerdash"; - + + if (!program_name) + program_name = "coolerdash"; + const char *version = read_version_from_file(); - + printf("================================================================================\n"); printf("CoolerDash v%s - LCD Dashboard for CoolerControl\n", version); printf("================================================================================\n\n"); @@ -359,70 +422,86 @@ static void show_help(const char *program_name, const Config *config) { * @brief Display system information for diagnostics. * @details Shows display configuration, API validation results, and refresh interval settings for system diagnostics. */ -static void show_system_diagnostics(const Config *config, int api_width, int api_height) { - if (!config) return; - +static void show_system_diagnostics(const Config *config, int api_width, int api_height) +{ + if (!config) + return; + // Display configuration with API validation integrated - if (api_width > 0 && api_height > 0) { - if (api_width != config->display_width || api_height != config->display_height) { - log_message(LOG_STATUS, "Display configuration: (%dx%d pixels)", - config->display_width, config->display_height); - log_message(LOG_WARNING, "API reports different dimensions: (%dx%d pixels)", - api_width, api_height); - } else { - log_message(LOG_STATUS, "Display configuration: (%dx%d pixels) (Device confirmed)", - config->display_width, config->display_height); + if (api_width > 0 && api_height > 0) + { + if (api_width != config->display_width || api_height != config->display_height) + { + log_message(LOG_STATUS, "Display configuration: (%dx%d pixels)", + config->display_width, config->display_height); + log_message(LOG_WARNING, "API reports different dimensions: (%dx%d pixels)", + api_width, api_height); + } + else + { + log_message(LOG_STATUS, "Display configuration: (%dx%d pixels) (Device confirmed)", + config->display_width, config->display_height); } - } else { - log_message(LOG_STATUS, "Display configuration: (%dx%d pixels) (Device confirmed)", - config->display_width, config->display_height); - } - - log_message(LOG_STATUS, "Refresh interval: %d.%03d seconds", - config->display_refresh_interval_sec, - config->display_refresh_interval_nsec / 1000000); + } + else + { + log_message(LOG_STATUS, "Display configuration: (%dx%d pixels) (Device confirmed)", + config->display_width, config->display_height); + } + + log_message(LOG_STATUS, "Refresh interval: %d.%03d seconds", + config->display_refresh_interval_sec, + config->display_refresh_interval_nsec / 1000000); } /** * @brief Send shutdown image if needed or turn off LCD if image is missing. * @details Checks if shutdown image should be sent to LCD device and performs the transmission if conditions are met. If shutdown image is missing, sets LCD brightness to 0 to turn off the display. */ -static void send_shutdown_image_if_needed(void) { +static void send_shutdown_image_if_needed(void) +{ // Basic validation - if (!is_session_initialized() || !g_config_ptr) { + if (!is_session_initialized() || !g_config_ptr) + { return; } - + // Get device UID char device_uid[128]; - if (!get_liquidctl_data(g_config_ptr, device_uid, sizeof(device_uid), NULL, 0, NULL, NULL) || !device_uid[0]) { - return; + if (!get_liquidctl_data(g_config_ptr, device_uid, sizeof(device_uid), NULL, 0, NULL, NULL) || !device_uid[0]) + { + return; } - + // Get shutdown image path const char *shutdown_image_path = g_config_ptr->paths_image_shutdown; - if (!shutdown_image_path || !shutdown_image_path[0]) { + if (!shutdown_image_path || !shutdown_image_path[0]) + { return; } // Check if shutdown image file exists FILE *image_file = fopen(shutdown_image_path, "r"); - if (image_file) { + if (image_file) + { // Image exists, send it normally fclose(image_file); send_image_to_lcd(g_config_ptr, shutdown_image_path, device_uid); send_image_to_lcd(g_config_ptr, shutdown_image_path, device_uid); // Send twice for better reliability - } else { + } + else + { // Image doesn't exist, create temporary config with brightness 0 to turn off LCD log_message(LOG_WARNING, "Shutdown image '%s' not found, turning off LCD display", shutdown_image_path); - + // Create a temporary config copy with brightness set to 0 Config temp_config = *g_config_ptr; temp_config.lcd_brightness = 0; - + // Use the main coolerdash image as fallback (should exist) or create a minimal black image const char *fallback_image = g_config_ptr->paths_image_coolerdash; - if (fallback_image && fallback_image[0]) { + if (fallback_image && fallback_image[0]) + { send_image_to_lcd(&temp_config, fallback_image, device_uid); send_image_to_lcd(&temp_config, fallback_image, device_uid); // Send twice for better reliability } @@ -433,50 +512,55 @@ static void send_shutdown_image_if_needed(void) { * @brief Enhanced signal handler with atomic operations and secure shutdown. * @details Signal-safe implementation using only async-signal-safe functions. */ -static void handle_shutdown_signal(int signum) { +static void handle_shutdown_signal(int signum) +{ // Use only async-signal-safe functions in signal handlers static const char term_msg[] = "Received SIGTERM - initiating graceful shutdown\n"; static const char int_msg[] = "Received SIGINT - initiating graceful shutdown\n"; static const char unknown_msg[] = "Received signal - initiating shutdown\n"; - + const char *msg; size_t msg_len; - + // Determine appropriate message based on signal - switch (signum) { - case SIGTERM: - msg = term_msg; - msg_len = sizeof(term_msg) - 1; - break; - case SIGINT: - msg = int_msg; - msg_len = sizeof(int_msg) - 1; - break; - default: - msg = unknown_msg; - msg_len = sizeof(unknown_msg) - 1; - break; - } - + switch (signum) + { + case SIGTERM: + msg = term_msg; + msg_len = sizeof(term_msg) - 1; + break; + case SIGINT: + msg = int_msg; + msg_len = sizeof(int_msg) - 1; + break; + default: + msg = unknown_msg; + msg_len = sizeof(unknown_msg) - 1; + break; + } + // Write message using async-signal-safe function ssize_t written = write(STDERR_FILENO, msg, msg_len); (void)written; // Suppress unused variable warning - + // Send shutdown image immediately for clean LCD state send_shutdown_image_if_needed(); - + // Clean up temporary files (PID and image) - signal-safe operations - if (g_config_ptr) { + if (g_config_ptr) + { // Remove PID file - array address is never NULL, just check if path is set - if (g_config_ptr->paths_pid[0]) { + if (g_config_ptr->paths_pid[0]) + { unlink(g_config_ptr->paths_pid); } // Remove generated image file - array address is never NULL, just check if path is set - if (g_config_ptr->paths_image_coolerdash[0]) { + if (g_config_ptr->paths_image_coolerdash[0]) + { unlink(g_config_ptr->paths_image_coolerdash); } } - + // Signal graceful shutdown atomically running = 0; } @@ -485,31 +569,35 @@ static void handle_shutdown_signal(int signum) { * @brief Setup enhanced signal handlers with comprehensive signal management. * @details Installs signal handlers for graceful shutdown and blocks unwanted signals. */ -static void setup_enhanced_signal_handlers(void) { +static void setup_enhanced_signal_handlers(void) +{ struct sigaction sa; sigset_t block_mask; - + // Initialize signal action structure with enhanced settings memset(&sa, 0, sizeof(sa)); sa.sa_handler = handle_shutdown_signal; sigemptyset(&sa.sa_mask); sa.sa_flags = SA_RESTART; // Restart interrupted system calls - + // Install handlers for graceful shutdown signals - if (sigaction(SIGTERM, &sa, NULL) == -1) { + if (sigaction(SIGTERM, &sa, NULL) == -1) + { log_message(LOG_WARNING, "Failed to install SIGTERM handler: %s", strerror(errno)); } - - if (sigaction(SIGINT, &sa, NULL) == -1) { + + if (sigaction(SIGINT, &sa, NULL) == -1) + { log_message(LOG_WARNING, "Failed to install SIGINT handler: %s", strerror(errno)); } - + // Block unwanted signals to prevent interference sigemptyset(&block_mask); - sigaddset(&block_mask, SIGPIPE); // Prevent broken pipe crashes - sigaddset(&block_mask, SIGHUP); // Ignore hangup signal for daemon operation - - if (pthread_sigmask(SIG_BLOCK, &block_mask, NULL) != 0) { + sigaddset(&block_mask, SIGPIPE); // Prevent broken pipe crashes + sigaddset(&block_mask, SIGHUP); // Ignore hangup signal for daemon operation + + if (pthread_sigmask(SIG_BLOCK, &block_mask, NULL) != 0) + { log_message(LOG_WARNING, "Failed to block unwanted signals"); } } @@ -518,42 +606,47 @@ static void setup_enhanced_signal_handlers(void) { * @brief Enhanced main daemon loop with improved timing and error handling. * @details Runs the main loop with precise timing, optimized sleep, and graceful error recovery. */ -static int run_daemon(const Config *config) { - if (!config) { +static int run_daemon(const Config *config) +{ + if (!config) + { log_message(LOG_ERROR, "Invalid configuration provided to daemon"); return -1; } - + const struct timespec interval = { .tv_sec = config->display_refresh_interval_sec, - .tv_nsec = config->display_refresh_interval_nsec - }; - + .tv_nsec = config->display_refresh_interval_nsec}; + struct timespec next_time; - if (clock_gettime(CLOCK_MONOTONIC, &next_time) != 0) { + if (clock_gettime(CLOCK_MONOTONIC, &next_time) != 0) + { log_message(LOG_ERROR, "Failed to get current time: %s", strerror(errno)); return -1; } - - while (running) { + + while (running) + { // Calculate next execution time with overflow protection next_time.tv_sec += interval.tv_sec; next_time.tv_nsec += interval.tv_nsec; - if (next_time.tv_nsec >= 1000000000L) { + if (next_time.tv_nsec >= 1000000000L) + { next_time.tv_sec++; next_time.tv_nsec -= 1000000000L; } - + // Execute main rendering task draw_combined_image(config); - + // Sleep until absolute time with error handling int sleep_result = clock_nanosleep(CLOCK_MONOTONIC, TIMER_ABSTIME, &next_time, NULL); - if (sleep_result != 0 && sleep_result != EINTR) { + if (sleep_result != 0 && sleep_result != EINTR) + { log_message(LOG_WARNING, "Sleep interrupted: %s", strerror(sleep_result)); } } - + return 0; } @@ -561,32 +654,42 @@ static int run_daemon(const Config *config) { * @brief Enhanced main entry point for CoolerDash with comprehensive error handling. * @details Loads configuration, ensures single instance, initializes all modules, and starts the main daemon loop. */ -int main(int argc, char **argv) { +int main(int argc, char **argv) +{ // Parse arguments for logging and help const char *config_path = "/etc/coolerdash/config.ini"; // Default config path - - for (int i = 1; i < argc; i++) { - if (strcmp(argv[i], "-h") == 0 || strcmp(argv[i], "--help") == 0) { + + for (int i = 1; i < argc; i++) + { + if (strcmp(argv[i], "-h") == 0 || strcmp(argv[i], "--help") == 0) + { show_help(argv[0], NULL); return EXIT_SUCCESS; - } else if (strcmp(argv[i], "--log") == 0) { + } + else if (strcmp(argv[i], "--log") == 0) + { verbose_logging = 1; // Enable detailed INFO logging - } else if (argv[i][0] != '-') { + } + else if (argv[i][0] != '-') + { // This is the config path (no dash prefix) config_path = argv[i]; - } else { + } + else + { fprintf(stderr, "Error: Unknown option '%s'. Use --help for usage information.\n", argv[i]); return EXIT_FAILURE; } } - + log_message(LOG_STATUS, "CoolerDash v%s starting up...", read_version_from_file()); // Load configuration Config config = {0}; - + log_message(LOG_STATUS, "Loading configuration..."); - if (load_config(config_path, &config) != 0) { + if (load_config(config_path, &config) != 0) + { log_message(LOG_ERROR, "Failed to load configuration file: %s", config_path); fprintf(stderr, "Error: Could not load config file '%s'\n", config_path); fprintf(stderr, "Please check:\n"); @@ -599,8 +702,9 @@ int main(int argc, char **argv) { // Check for existing instances and create PID file with enhanced validation int is_service_start = is_started_by_systemd(); log_message(LOG_INFO, "Running mode: %s", is_service_start ? "systemd service" : "manual"); - - if (check_existing_instance_and_handle(config.paths_pid, is_service_start) < 0) { + + if (check_existing_instance_and_handle(config.paths_pid, is_service_start) < 0) + { log_message(LOG_ERROR, "Instance management failed"); fprintf(stderr, "Error: Another CoolerDash instance is already running\n"); fprintf(stderr, "To stop the running instance:\n"); @@ -609,12 +713,13 @@ int main(int argc, char **argv) { fprintf(stderr, " sudo systemctl status coolerdash # Check service status\n"); return EXIT_FAILURE; } - - if (write_pid_file(config.paths_pid) != 0) { + + if (write_pid_file(config.paths_pid) != 0) + { log_message(LOG_ERROR, "Failed to create PID file: %s", config.paths_pid); return EXIT_FAILURE; } - + // Set global config pointer for signal handlers and cleanup g_config_ptr = &config; @@ -623,14 +728,16 @@ int main(int argc, char **argv) { // Initialize CoolerControl session log_message(LOG_STATUS, "Initializing CoolerControl session..."); - if (!init_coolercontrol_session(&config)) { + if (!init_coolercontrol_session(&config)) + { log_message(LOG_ERROR, "CoolerControl session initialization failed"); fprintf(stderr, "Error: CoolerControl session could not be initialized\n" "Please check:\n" " - Is coolercontrold running? (systemctl status coolercontrold)\n" " - Is the daemon running on %s?\n" " - Is the password correct in configuration?\n" - " - Are network connections allowed?\n", config.daemon_address); + " - Are network connections allowed?\n", + config.daemon_address); fflush(stderr); remove_pid_file(config.paths_pid); return EXIT_FAILURE; @@ -638,14 +745,16 @@ int main(int argc, char **argv) { // Initialize device cache once at startup for optimal performance log_message(LOG_STATUS, "CoolerDash initializing device cache...\n"); - if (!init_device_cache(&config)) { + if (!init_device_cache(&config)) + { log_message(LOG_ERROR, "Failed to initialize device cache"); fprintf(stderr, "Error: CoolerControl session could not be initialized\n" "Please check:\n" " - Is coolercontrold running? (systemctl status coolercontrold)\n" " - Is the daemon running on %s?\n" " - Is the password correct in configuration?\n" - " - Are network connections allowed?\n", config.daemon_address); + " - Are network connections allowed?\n", + config.daemon_address); remove_pid_file(config.paths_pid); return EXIT_FAILURE; } @@ -658,47 +767,56 @@ int main(int argc, char **argv) { // Get complete device info from cache (no API call) if (get_liquidctl_data(&config, device_uid, sizeof(device_uid), - device_name, sizeof(device_name), &api_screen_width, &api_screen_height)) { - - const char *uid_display = (device_uid[0] != '\0') - ? device_uid - : "Unknown device UID"; - const char *name_display = (device_name[0] != '\0') - ? device_name - : "Unknown device"; - + device_name, sizeof(device_name), &api_screen_width, &api_screen_height)) + { + + const char *uid_display = (device_uid[0] != '\0') + ? device_uid + : "Unknown device UID"; + const char *name_display = (device_name[0] != '\0') + ? device_name + : "Unknown device"; + log_message(LOG_STATUS, "Device: %s [%s]", name_display, uid_display); - + // Get temperature data separately for validation and log sensor detection status - if (get_temperature_monitor_data(&config, &temp_data)) { - if (temp_data.temp_cpu > 0.0f || temp_data.temp_gpu > 0.0f) { + if (get_temperature_monitor_data(&config, &temp_data)) + { + if (temp_data.temp_cpu > 0.0f || temp_data.temp_gpu > 0.0f) + { log_message(LOG_STATUS, "Sensor values successfully detected"); - } else { + } + else + { log_message(LOG_WARNING, "Sensor detection issues - temperature values not available"); } - } else { + } + else + { log_message(LOG_WARNING, "Sensor detection issues - check CoolerControl connection"); } - + // Show diagnostic information in debug mode show_system_diagnostics(&config, api_screen_width, api_screen_height); - } else { + } + else + { log_message(LOG_ERROR, "Could not retrieve device information"); // Continue execution - some functionality may still work } log_message(LOG_STATUS, "Starting daemon"); - + // Run daemon with proper error handling int result = run_daemon(&config); - + // Ensure proper cleanup on exit log_message(LOG_INFO, "Daemon shutdown initiated"); send_shutdown_image_if_needed(); // Ensure shutdown image is sent on normal exit remove_pid_file(config.paths_pid); remove_image_file(config.paths_image_coolerdash); running = 0; - + log_message(LOG_INFO, "CoolerDash shutdown complete"); return result; } \ No newline at end of file