From 6a521a5f08349b9a31c10873b5c1cf68e8b90c28 Mon Sep 17 00:00:00 2001 From: damachine Date: Fri, 12 Sep 2025 13:00:19 +0200 Subject: [PATCH 1/6] bump version to 1.57 --- .SRCINFO | 2 +- VERSION | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.SRCINFO b/.SRCINFO index 78612f3..79268ec 100644 --- a/.SRCINFO +++ b/.SRCINFO @@ -1,6 +1,6 @@ pkgbase = coolerdash pkgdesc = Extends CoolerControl with a polished LCD dashboard - pkgver = 1.56 + pkgver = 1.57 pkgrel = 1 url = https://github.com/damachine/coolerdash install = coolerdash.install diff --git a/VERSION b/VERSION index e01e6c1..c88cb2a 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -1.56 +1.57 From f9f02ceafa6d3d98db3d03b2dcbc962f25caba9a Mon Sep 17 00:00:00 2001 From: damachine Date: Fri, 12 Sep 2025 16:29:47 +0200 Subject: [PATCH 2/6] instructions --- .github/instructions/copilot.instructions.md | 109 +++++++++++++++++++ 1 file changed, 109 insertions(+) create mode 100644 .github/instructions/copilot.instructions.md diff --git a/.github/instructions/copilot.instructions.md b/.github/instructions/copilot.instructions.md new file mode 100644 index 0000000..c9a12cd --- /dev/null +++ b/.github/instructions/copilot.instructions.md @@ -0,0 +1,109 @@ +--- +applyTo: "**" +--- + +**Chat-Sprachregeln** +- Beantworte Chat-Fragen in deutscher Sprache. + +**Immer ganz oben am Anfang einer Datei `.c` `.h`** +```c +/** + * @author damachine (christkue79@gmail.com) + * @Maintainer: damachine + * @website https://github.com/damachine + * @copyright (c) 2025 damachine + * @license MIT + * @version 1.0 + * This software is provided "as is", without warranty of any kind, express or implied. + * I do not guarantee that it will work as intended on your system. + */ + ``` + +**Kommentar- und Dokumentationsstil** +- Dokumentiere in der `README.md` und `AUR-README.md` in englischer Sprache. +- Schreibe Code-Kommentare in englischer Sprache. +- Verwende Doxygen-Stil für Funktionskommentare. +- Nutze Doxygen-Kommentare für Funktionen, Strukturen und wichtige Abschnitte. +- Öffnende geschweifte Klammern stehen bei Funktionen und Kontrollstrukturen in derselben Zeile (K&R-Stil). +- Nutze `//` für einzeilige Kommentare. +- Nutze `/* ... */` für mehrzeilige Kommentare. +- Nutze Inline-Kommentare sparsam, nur wenn nötig. +- Doppelte Header-Kommentare vermeiden. +- Kommentiere alle nicht sofort verständlichen Codeabschnitte. +- Vermeide redundante Kommentare, die den Code wiederholen. +- Dokumentiere komplexe Algorithmen und Datenstrukturen ausführlich. +- Nutze als 1. Kommentar `@brief` für eine kurze Zusammenfassung der Funktion. +- Nutze als 2. Kommentar `@details` für eine ausführliche Beschreibung der Funktion. +- Nutze als 3. Kommentar `@example` für Codebeispiele, die die Nutzung einer Funktion demonstrieren. +- Beispiel für nutze als 1. 2. 3. Kommentar: +```c +/** + * @brief + * ... + * @details + * ... + */ + ``` +- Entferne Kommentare, die den Code nicht mehr beschreiben oder veraltet sind. + +**Code-Richtlinien und Codestil** +- Halte dich an ISO/IEC 9899:1999 (C99). +- Binde nur notwendige Header ein; trenne System- und lokale Header +- Verwende Include Guards: `#ifndef HEADER_H` / `#define HEADER_H` / `#endif`. +- Nutze `const` für unveränderliche Variablen und Funktionsparameter. +- Nutze `static` für Funktionen und Variablen, die nur in der Datei sichtbar sein sollen. +- Nutze `inline` für kleine, häufig genutzte Funktionen. +- Nutze `malloc()` für dynamische Speicherallokation. +- Nutze `calloc()` für dynamische Speicherallokation mit Nullinitialisierung. +- Nutze `realloc()` für dynamische Speicheranpassung. +- Nutze `enum` für Status- und Fehlercodes, z.B. `enum Status { SUCCESS, ERROR }`. +- Nutze `typedef` für komplexe Datentypen, z.B. `typedef struct { int x; int y; } Point;`. +- Nutze `struct` für Datenstrukturen, z.B. `struct MyStruct { int a; float b; };`. +- Nutze `union` für gemeinsame Datenstrukturen, z.B. `union Data { int i; float f; };`. +- Nutze `typedef` für Zeiger auf Funktionen, z.B. `typedef void (*Callback)(int);`. +- Nutze `static_assert` für Compile-Zeit-Prüfungen, z.B. `static_assert(sizeof(int) == 4, "int must be 4 bytes");`. +- Nutze `restrict` für Zeiger, die nicht auf dieselben Daten zeigen, z.B. `void func(int * restrict a, int * restrict b);`. +- Nutze `volatile` für Variablen, die sich außerhalb des Programms ändern können, z.B. `volatile int *ptr;`. +- Nutze `inline` für kleine, häufig genutzte Funktionen, z.B. `inline int square(int x) { return x * x; }`. +- Vermeide `free()` auf NULL-Zeiger, aber setze Zeiger nach `free()` auf NULL. +- Vermeide `gets()`, nutze stattdessen `fgets()` oder `getline()`. +- Vermeide `strcpy()`, nutze stattdessen `strncpy()` oder `strlcpy()`. +- Vermeide `sprintf()`, nutze stattdessen `snprintf()` oder `asprintf()`. +- Vermeide `strcat()`, nutze stattdessen `strncat()` oder `strlcat()`. +- Vermeide ` strtok()`, nutze stattdessen `strsep()` oder `strtok_r()`. +- Vermeide `atoi()`, `atol()`, `atoll()`, nutze stattdessen `strtol()`, `strtoll()`, `strtof()`, `strtod()`, `strtold()`. +- Vermeide `printf()` für Fehler und Debugging, nutze stattdessen `fprintf(stderr, ...)`. +- Vermeide `exit()` in Bibliotheksfunktionen, nutze stattdessen `return` oder `longjmp()`. +- Vermeide `goto`, nutze statdessen Schleifen und Kontrollstrukturen. +- Vermeide globale Variablen, nutze stattdessen lokale Variablen oder Strukturen. +- Vermeide rekursive Funktionen, wenn möglich, nutze stattdessen Iteration. +- Vermeide unnötige Typumwandlungen, nutze stattdessen den richtigen Datentyp. +- Vermeide unnötige Zeigerarithmetik, nutze stattdessen Array-Indizes. +- Vermeide unnötige Funktionsaufrufe, nutze stattdessen Inline-Funktionen. +- Vermeide unnötige Schleifen, nutze stattdessen bedingte Anweisungen. +- Vermeide unnötige Speicherallokation, nutze stattdessen statische Arrays oder Strukturen. +- Vermeide unnötige Typdefinitionen, nutze stattdessen Standardtypen. +- Vermeide unnötige Makros, nutze stattdessen Inline-Funktionen oder Konstanten. +- Überprüfe Rückgabewerte von `malloc()`, `calloc()`, `realloc()`. +- Funktionsnamen sind Verben im snake_case, z.B. `calculate_sum()`, `parse_input()`. +- Variablen im snake_case, z.B. `user_count`. +- Konstanten und Makros in UPPER_CASE, z.B. `MAX_SIZE`, `PI`. +- Typdefinitionen in PascalCase, z.B. `MyType`. +- Enum-Namen in UPPER_CASE, z.B. `STATUS_OK`, ` +- Gib dynamisch reservierten Speicher frei und setze Zeiger danach auf NULL. +- Halte den Code strukturiert: Trenne Deklarationen, Definitionen und Implementierungen. +- Halte den Code sauber und lesbar: Einrückung mit 4 Leerzeichen, keine Tabulatoren. +- Vermeide unnötigen Code und redundante Kommentare. +- Halte den Code modular: Teile große Funktionen in kleinere auf. +- Halte den Code effizient: Vermeide unnötige Berechnungen und Schleifen. +- Halte den Code portabel: Vermeide plattformspezifische Funktionen und Bibliotheken. +- Halte den Code sicher: Vermeide Pufferüberläufe, nutze sichere Funktionen. +- Halte den Code wartbar: Schreibe klaren, verständlichen Code mit sinnvollen Kommentaren. +- Halte den Code testbar: Schreibe Unit-Tests für wichtige Funktionen. +- Halte den Code dokumentiert: Nutze Doxygen-Kommentare für Funktionen und Strukturen. +- Halte den Code performant: Optimiere nur, wenn es notwendig ist, und vermeide premature optimization. +- Halte den Code konsistent: Nutze einheitliche Stilrichtlinien und Namenskonventionen. +- Halte den Code lesbar: Nutze sprechende Namen und vermeide kryptische Abkürzungen. +- Halte den Code flexibel: Nutze Parameter und Rückgabewerte, um Funktionen anpassbar zu machen. +- Halte den Code erweiterbar: Schreibe Funktionen so, dass sie leicht erweitert werden können. +- Halte den Code robust: Behandle Fehlerfälle und unerwartete Eingaben angemessen. \ No newline at end of file From a6225203fc872e07e7ebb1742d00d9407a6fcf3b Mon Sep 17 00:00:00 2001 From: damachine Date: Fri, 12 Sep 2025 16:35:27 +0200 Subject: [PATCH 3/6] Add Codacy security scan workflow (#22) --- .github/workflows/codacy.yml | 61 ++++++++++++++++++++++++++++++++++++ 1 file changed, 61 insertions(+) create mode 100644 .github/workflows/codacy.yml diff --git a/.github/workflows/codacy.yml b/.github/workflows/codacy.yml new file mode 100644 index 0000000..65b6c85 --- /dev/null +++ b/.github/workflows/codacy.yml @@ -0,0 +1,61 @@ +# This workflow uses actions that are not certified by GitHub. +# They are provided by a third-party and are governed by +# separate terms of service, privacy policy, and support +# documentation. + +# This workflow checks out code, performs a Codacy security scan +# and integrates the results with the +# GitHub Advanced Security code scanning feature. For more information on +# the Codacy security scan action usage and parameters, see +# https://github.com/codacy/codacy-analysis-cli-action. +# For more information on Codacy Analysis CLI in general, see +# https://github.com/codacy/codacy-analysis-cli. + +name: Codacy Security Scan + +on: + push: + branches: [ "main", "beta", "v2.00rc" ] + pull_request: + # The branches below must be a subset of the branches above + branches: [ "main" ] + schedule: + - cron: '41 7 * * 0' + +permissions: + contents: read + +jobs: + codacy-security-scan: + permissions: + contents: read # for actions/checkout to fetch code + security-events: write # for github/codeql-action/upload-sarif to upload SARIF results + actions: read # only required for a private repository by github/codeql-action/upload-sarif to get the Action run status + name: Codacy Security Scan + runs-on: ubuntu-latest + steps: + # Checkout the repository to the GitHub Actions runner + - name: Checkout code + uses: actions/checkout@v4 + + # Execute Codacy Analysis CLI and generate a SARIF output with the security issues identified during the analysis + - name: Run Codacy Analysis CLI + uses: codacy/codacy-analysis-cli-action@d840f886c4bd4edc059706d09c6a1586111c540b + with: + # Check https://github.com/codacy/codacy-analysis-cli#project-token to get your project token from your Codacy repository + # You can also omit the token and run the tools that support default configurations + project-token: ${{ secrets.CODACY_PROJECT_TOKEN }} + verbose: true + output: results.sarif + format: sarif + # Adjust severity of non-security issues + gh-code-scanning-compat: true + # Force 0 exit code to allow SARIF file generation + # This will handover control about PR rejection to the GitHub side + max-allowed-issues: 2147483647 + + # Upload the SARIF file generated in the previous step + - name: Upload SARIF results file + uses: github/codeql-action/upload-sarif@v3 + with: + sarif_file: results.sarif From 5815b44a0b404279f3cd3ec20a147f158ac591e1 Mon Sep 17 00:00:00 2001 From: damachine Date: Fri, 12 Sep 2025 17:03:56 +0200 Subject: [PATCH 4/6] fix: resolve vulnerabilities in file operations - Replace unsafe fopen() calls with secure stat() checks in main.c - Add symlink attack protection for PID file creation - Implement regular file validation before opening VERSION files - Enhance PID file reading with file type verification - Prevent TOCTOU (Time-of-Check Time-of-Use) race conditions - Add comprehensive error handling for security edge cases Security improvements address Flawfinder warnings and follow OWASP secure coding practices for file system operations. --- src/main.c | 557 ++++++++++++++++++++++++++++++++++------------------- 1 file changed, 354 insertions(+), 203 deletions(-) diff --git a/src/main.c b/src/main.c index 8ae29ce..91af9ed 100644 --- a/src/main.c +++ b/src/main.c @@ -71,23 +71,38 @@ 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) { + + // Try to read from VERSION file with security checks + struct stat version_stat; + FILE *fp = NULL; + + // Check VERSION file in current directory first + if (stat("VERSION", &version_stat) == 0 && S_ISREG(version_stat.st_mode)) + { + fp = fopen("VERSION", "r"); + } + + if (!fp) + { // Try alternative path for installed version - fp = fopen("/opt/coolerdash/VERSION", "r"); + if (stat("/opt/coolerdash/VERSION", &version_stat) == 0 && S_ISREG(version_stat.st_mode)) + { + 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 +110,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 +142,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 +164,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 +192,66 @@ 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; } - + + // Check if PID file exists and is a regular file + struct stat pid_stat; + if (stat(pid_file, &pid_stat) != 0) + { + return 0; // No PID file exists, no running instance + } + + if (!S_ISREG(pid_stat.st_mode)) + { + log_message(LOG_WARNING, "PID file '%s' is not a regular file", pid_file); + return 0; + } + FILE *fp = fopen(pid_file, "r"); - if (!fp) return 0; // No PID file exists, no running instance - + if (!fp) + return 0; // Could not open PID file + // 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 +262,110 @@ 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; } - + + // First, check if the target is a symlink to prevent symlink attacks + struct stat link_stat; + if (lstat(temp_file, &link_stat) == 0) + { + if (S_ISLNK(link_stat.st_mode)) + { + log_message(LOG_ERROR, "Symlink attack detected for PID file '%s'", temp_file); + return -1; + } + // File already exists, remove it if it's a regular file + if (S_ISREG(link_stat.st_mode)) + { + if (unlink(temp_file) == -1) + { + log_message(LOG_ERROR, "Could not remove existing temporary PID file '%s': %s", temp_file, strerror(errno)); + return -1; + } + } + else + { + log_message(LOG_ERROR, "Temporary PID file path '%s' exists but is not a regular file", temp_file); + 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) { + if (fd == -1) + { log_message(LOG_ERROR, "Could not create temporary PID file '%s': %s", temp_file, strerror(errno)); 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 +374,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 +393,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 +412,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 +456,85 @@ 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) { - // Image exists, send it normally - fclose(image_file); + // Check if shutdown image file exists and is a regular file (not a symlink or device) + struct stat file_stat; + if (stat(shutdown_image_path, &file_stat) == 0 && S_ISREG(file_stat.st_mode)) + { + // Image exists and is a regular file, send it normally 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 +545,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 +602,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 +639,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 +687,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 +735,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 +746,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 +761,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 +778,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 +800,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 From d4c1e21aa48b11f8a364fcb7188910caa978737a Mon Sep 17 00:00:00 2001 From: damachine Date: Fri, 12 Sep 2025 17:11:54 +0200 Subject: [PATCH 5/6] Delete instructions (#23) --- .github/instructions/copilot.instructions.md | 109 ------------------- 1 file changed, 109 deletions(-) delete mode 100644 .github/instructions/copilot.instructions.md diff --git a/.github/instructions/copilot.instructions.md b/.github/instructions/copilot.instructions.md deleted file mode 100644 index c9a12cd..0000000 --- a/.github/instructions/copilot.instructions.md +++ /dev/null @@ -1,109 +0,0 @@ ---- -applyTo: "**" ---- - -**Chat-Sprachregeln** -- Beantworte Chat-Fragen in deutscher Sprache. - -**Immer ganz oben am Anfang einer Datei `.c` `.h`** -```c -/** - * @author damachine (christkue79@gmail.com) - * @Maintainer: damachine - * @website https://github.com/damachine - * @copyright (c) 2025 damachine - * @license MIT - * @version 1.0 - * This software is provided "as is", without warranty of any kind, express or implied. - * I do not guarantee that it will work as intended on your system. - */ - ``` - -**Kommentar- und Dokumentationsstil** -- Dokumentiere in der `README.md` und `AUR-README.md` in englischer Sprache. -- Schreibe Code-Kommentare in englischer Sprache. -- Verwende Doxygen-Stil für Funktionskommentare. -- Nutze Doxygen-Kommentare für Funktionen, Strukturen und wichtige Abschnitte. -- Öffnende geschweifte Klammern stehen bei Funktionen und Kontrollstrukturen in derselben Zeile (K&R-Stil). -- Nutze `//` für einzeilige Kommentare. -- Nutze `/* ... */` für mehrzeilige Kommentare. -- Nutze Inline-Kommentare sparsam, nur wenn nötig. -- Doppelte Header-Kommentare vermeiden. -- Kommentiere alle nicht sofort verständlichen Codeabschnitte. -- Vermeide redundante Kommentare, die den Code wiederholen. -- Dokumentiere komplexe Algorithmen und Datenstrukturen ausführlich. -- Nutze als 1. Kommentar `@brief` für eine kurze Zusammenfassung der Funktion. -- Nutze als 2. Kommentar `@details` für eine ausführliche Beschreibung der Funktion. -- Nutze als 3. Kommentar `@example` für Codebeispiele, die die Nutzung einer Funktion demonstrieren. -- Beispiel für nutze als 1. 2. 3. Kommentar: -```c -/** - * @brief - * ... - * @details - * ... - */ - ``` -- Entferne Kommentare, die den Code nicht mehr beschreiben oder veraltet sind. - -**Code-Richtlinien und Codestil** -- Halte dich an ISO/IEC 9899:1999 (C99). -- Binde nur notwendige Header ein; trenne System- und lokale Header -- Verwende Include Guards: `#ifndef HEADER_H` / `#define HEADER_H` / `#endif`. -- Nutze `const` für unveränderliche Variablen und Funktionsparameter. -- Nutze `static` für Funktionen und Variablen, die nur in der Datei sichtbar sein sollen. -- Nutze `inline` für kleine, häufig genutzte Funktionen. -- Nutze `malloc()` für dynamische Speicherallokation. -- Nutze `calloc()` für dynamische Speicherallokation mit Nullinitialisierung. -- Nutze `realloc()` für dynamische Speicheranpassung. -- Nutze `enum` für Status- und Fehlercodes, z.B. `enum Status { SUCCESS, ERROR }`. -- Nutze `typedef` für komplexe Datentypen, z.B. `typedef struct { int x; int y; } Point;`. -- Nutze `struct` für Datenstrukturen, z.B. `struct MyStruct { int a; float b; };`. -- Nutze `union` für gemeinsame Datenstrukturen, z.B. `union Data { int i; float f; };`. -- Nutze `typedef` für Zeiger auf Funktionen, z.B. `typedef void (*Callback)(int);`. -- Nutze `static_assert` für Compile-Zeit-Prüfungen, z.B. `static_assert(sizeof(int) == 4, "int must be 4 bytes");`. -- Nutze `restrict` für Zeiger, die nicht auf dieselben Daten zeigen, z.B. `void func(int * restrict a, int * restrict b);`. -- Nutze `volatile` für Variablen, die sich außerhalb des Programms ändern können, z.B. `volatile int *ptr;`. -- Nutze `inline` für kleine, häufig genutzte Funktionen, z.B. `inline int square(int x) { return x * x; }`. -- Vermeide `free()` auf NULL-Zeiger, aber setze Zeiger nach `free()` auf NULL. -- Vermeide `gets()`, nutze stattdessen `fgets()` oder `getline()`. -- Vermeide `strcpy()`, nutze stattdessen `strncpy()` oder `strlcpy()`. -- Vermeide `sprintf()`, nutze stattdessen `snprintf()` oder `asprintf()`. -- Vermeide `strcat()`, nutze stattdessen `strncat()` oder `strlcat()`. -- Vermeide ` strtok()`, nutze stattdessen `strsep()` oder `strtok_r()`. -- Vermeide `atoi()`, `atol()`, `atoll()`, nutze stattdessen `strtol()`, `strtoll()`, `strtof()`, `strtod()`, `strtold()`. -- Vermeide `printf()` für Fehler und Debugging, nutze stattdessen `fprintf(stderr, ...)`. -- Vermeide `exit()` in Bibliotheksfunktionen, nutze stattdessen `return` oder `longjmp()`. -- Vermeide `goto`, nutze statdessen Schleifen und Kontrollstrukturen. -- Vermeide globale Variablen, nutze stattdessen lokale Variablen oder Strukturen. -- Vermeide rekursive Funktionen, wenn möglich, nutze stattdessen Iteration. -- Vermeide unnötige Typumwandlungen, nutze stattdessen den richtigen Datentyp. -- Vermeide unnötige Zeigerarithmetik, nutze stattdessen Array-Indizes. -- Vermeide unnötige Funktionsaufrufe, nutze stattdessen Inline-Funktionen. -- Vermeide unnötige Schleifen, nutze stattdessen bedingte Anweisungen. -- Vermeide unnötige Speicherallokation, nutze stattdessen statische Arrays oder Strukturen. -- Vermeide unnötige Typdefinitionen, nutze stattdessen Standardtypen. -- Vermeide unnötige Makros, nutze stattdessen Inline-Funktionen oder Konstanten. -- Überprüfe Rückgabewerte von `malloc()`, `calloc()`, `realloc()`. -- Funktionsnamen sind Verben im snake_case, z.B. `calculate_sum()`, `parse_input()`. -- Variablen im snake_case, z.B. `user_count`. -- Konstanten und Makros in UPPER_CASE, z.B. `MAX_SIZE`, `PI`. -- Typdefinitionen in PascalCase, z.B. `MyType`. -- Enum-Namen in UPPER_CASE, z.B. `STATUS_OK`, ` -- Gib dynamisch reservierten Speicher frei und setze Zeiger danach auf NULL. -- Halte den Code strukturiert: Trenne Deklarationen, Definitionen und Implementierungen. -- Halte den Code sauber und lesbar: Einrückung mit 4 Leerzeichen, keine Tabulatoren. -- Vermeide unnötigen Code und redundante Kommentare. -- Halte den Code modular: Teile große Funktionen in kleinere auf. -- Halte den Code effizient: Vermeide unnötige Berechnungen und Schleifen. -- Halte den Code portabel: Vermeide plattformspezifische Funktionen und Bibliotheken. -- Halte den Code sicher: Vermeide Pufferüberläufe, nutze sichere Funktionen. -- Halte den Code wartbar: Schreibe klaren, verständlichen Code mit sinnvollen Kommentaren. -- Halte den Code testbar: Schreibe Unit-Tests für wichtige Funktionen. -- Halte den Code dokumentiert: Nutze Doxygen-Kommentare für Funktionen und Strukturen. -- Halte den Code performant: Optimiere nur, wenn es notwendig ist, und vermeide premature optimization. -- Halte den Code konsistent: Nutze einheitliche Stilrichtlinien und Namenskonventionen. -- Halte den Code lesbar: Nutze sprechende Namen und vermeide kryptische Abkürzungen. -- Halte den Code flexibel: Nutze Parameter und Rückgabewerte, um Funktionen anpassbar zu machen. -- Halte den Code erweiterbar: Schreibe Funktionen so, dass sie leicht erweitert werden können. -- Halte den Code robust: Behandle Fehlerfälle und unerwartete Eingaben angemessen. \ No newline at end of file From 23951fe156567344ebdec3cf2d99477af053a8c7 Mon Sep 17 00:00:00 2001 From: damachine Date: Fri, 12 Sep 2025 17:14:15 +0200 Subject: [PATCH 6/6] bump version to 1.58 bump version to 1.58 --- .SRCINFO | 2 +- .github/workflows/codacy.yml | 61 ---- .gitignore | 2 +- VERSION | 2 +- src/main.c | 557 +++++++++++++---------------------- 5 files changed, 206 insertions(+), 418 deletions(-) delete mode 100644 .github/workflows/codacy.yml diff --git a/.SRCINFO b/.SRCINFO index 79268ec..f42863f 100644 --- a/.SRCINFO +++ b/.SRCINFO @@ -1,6 +1,6 @@ pkgbase = coolerdash pkgdesc = Extends CoolerControl with a polished LCD dashboard - pkgver = 1.57 + pkgver = 1.58 pkgrel = 1 url = https://github.com/damachine/coolerdash install = coolerdash.install diff --git a/.github/workflows/codacy.yml b/.github/workflows/codacy.yml deleted file mode 100644 index 65b6c85..0000000 --- a/.github/workflows/codacy.yml +++ /dev/null @@ -1,61 +0,0 @@ -# This workflow uses actions that are not certified by GitHub. -# They are provided by a third-party and are governed by -# separate terms of service, privacy policy, and support -# documentation. - -# This workflow checks out code, performs a Codacy security scan -# and integrates the results with the -# GitHub Advanced Security code scanning feature. For more information on -# the Codacy security scan action usage and parameters, see -# https://github.com/codacy/codacy-analysis-cli-action. -# For more information on Codacy Analysis CLI in general, see -# https://github.com/codacy/codacy-analysis-cli. - -name: Codacy Security Scan - -on: - push: - branches: [ "main", "beta", "v2.00rc" ] - pull_request: - # The branches below must be a subset of the branches above - branches: [ "main" ] - schedule: - - cron: '41 7 * * 0' - -permissions: - contents: read - -jobs: - codacy-security-scan: - permissions: - contents: read # for actions/checkout to fetch code - security-events: write # for github/codeql-action/upload-sarif to upload SARIF results - actions: read # only required for a private repository by github/codeql-action/upload-sarif to get the Action run status - name: Codacy Security Scan - runs-on: ubuntu-latest - steps: - # Checkout the repository to the GitHub Actions runner - - name: Checkout code - uses: actions/checkout@v4 - - # Execute Codacy Analysis CLI and generate a SARIF output with the security issues identified during the analysis - - name: Run Codacy Analysis CLI - uses: codacy/codacy-analysis-cli-action@d840f886c4bd4edc059706d09c6a1586111c540b - with: - # Check https://github.com/codacy/codacy-analysis-cli#project-token to get your project token from your Codacy repository - # You can also omit the token and run the tools that support default configurations - project-token: ${{ secrets.CODACY_PROJECT_TOKEN }} - verbose: true - output: results.sarif - format: sarif - # Adjust severity of non-security issues - gh-code-scanning-compat: true - # Force 0 exit code to allow SARIF file generation - # This will handover control about PR rejection to the GitHub side - max-allowed-issues: 2147483647 - - # Upload the SARIF file generated in the previous step - - name: Upload SARIF results file - uses: github/codeql-action/upload-sarif@v3 - with: - sarif_file: results.sarif diff --git a/.gitignore b/.gitignore index 4335336..bf9d67d 100644 --- a/.gitignore +++ b/.gitignore @@ -50,4 +50,4 @@ debug-* #Ignore vscode AI rules .github/instructions/best_practices.md .github/instructions/codacy.instructions.md -.github/instructions/copilot-instructions.md \ No newline at end of file +.github/instructions/copilot.instructions.md \ No newline at end of file diff --git a/VERSION b/VERSION index c88cb2a..d467d91 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -1.57 +1.58 diff --git a/src/main.c b/src/main.c index 91af9ed..8ae29ce 100644 --- a/src/main.c +++ b/src/main.c @@ -71,38 +71,23 @@ 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 with security checks - struct stat version_stat; - FILE *fp = NULL; - - // Check VERSION file in current directory first - if (stat("VERSION", &version_stat) == 0 && S_ISREG(version_stat.st_mode)) - { - fp = fopen("VERSION", "r"); - } - - if (!fp) - { + + // Try to read from VERSION file + FILE *fp = fopen("VERSION", "r"); + if (!fp) { // Try alternative path for installed version - if (stat("/opt/coolerdash/VERSION", &version_stat) == 0 && S_ISREG(version_stat.st_mode)) - { - fp = fopen("/opt/coolerdash/VERSION", "r"); - } + 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; @@ -110,24 +95,19 @@ 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); } @@ -142,21 +122,17 @@ 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; } @@ -164,27 +140,23 @@ 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 } @@ -192,66 +164,45 @@ 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; } - - // Check if PID file exists and is a regular file - struct stat pid_stat; - if (stat(pid_file, &pid_stat) != 0) - { - return 0; // No PID file exists, no running instance - } - - if (!S_ISREG(pid_stat.st_mode)) - { - log_message(LOG_WARNING, "PID file '%s' is not a regular file", pid_file); - return 0; - } - + FILE *fp = fopen(pid_file, "r"); - if (!fp) - return 0; // Could not open PID file - + 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); @@ -262,110 +213,74 @@ 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; } - - // First, check if the target is a symlink to prevent symlink attacks - struct stat link_stat; - if (lstat(temp_file, &link_stat) == 0) - { - if (S_ISLNK(link_stat.st_mode)) - { - log_message(LOG_ERROR, "Symlink attack detected for PID file '%s'", temp_file); - return -1; - } - // File already exists, remove it if it's a regular file - if (S_ISREG(link_stat.st_mode)) - { - if (unlink(temp_file) == -1) - { - log_message(LOG_ERROR, "Could not remove existing temporary PID file '%s': %s", temp_file, strerror(errno)); - return -1; - } - } - else - { - log_message(LOG_ERROR, "Temporary PID file path '%s' exists but is not a regular file", temp_file); - 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) - { + if (fd == -1) { log_message(LOG_ERROR, "Could not create temporary PID file '%s': %s", temp_file, strerror(errno)); 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; } @@ -374,17 +289,12 @@ 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)); } } @@ -393,17 +303,12 @@ 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)); } } @@ -412,15 +317,13 @@ 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"); @@ -456,85 +359,70 @@ 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 and is a regular file (not a symlink or device) - struct stat file_stat; - if (stat(shutdown_image_path, &file_stat) == 0 && S_ISREG(file_stat.st_mode)) - { - // Image exists and is a regular file, send it normally + // Check if shutdown image file exists + FILE *image_file = fopen(shutdown_image_path, "r"); + 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 } @@ -545,55 +433,50 @@ 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; } @@ -602,35 +485,31 @@ 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"); } } @@ -639,47 +518,42 @@ 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; } @@ -687,42 +561,32 @@ 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"); @@ -735,9 +599,8 @@ 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"); @@ -746,13 +609,12 @@ 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; @@ -761,16 +623,14 @@ 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; @@ -778,16 +638,14 @@ 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; } @@ -800,56 +658,47 @@ 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